private bool Admire(Character player, Character npc, int bribeMod = 0) { // This essentially makes the calculations more likely to move towards 50 var disposition = npc.GetDisposition(player); var d = 1 - Abs(disposition - 50) * 0.02f; var target = d * (GetAdmireRating(player) - GetAdmireRating(npc) + 50); target = Max(PerMinChance, target); target -= Random.Range(0, 100); DialogRecord dialog; var change = (int)(DieRollMult * target); if (change >= 0) { change = Max(PerMinChange, change); dialog = DialogRecord.GetPersuasionDialog(player, npc, PersuasionResult.AdmireSuccess); } else { dialog = DialogRecord.GetPersuasionDialog(player, npc, PersuasionResult.AdmireFail); } // Update the disposition in the UI npc.DispositionMod += Clamp(change, -disposition, 100 - disposition); controller.SetDisposition(npc.GetDisposition(player)); // Display the response controller.DisplayTopic(dialog); return(change >= 0); }
// Displays a dialog choice, and assigns it a callback void IDialogView.DisplayChoice(string text, DialogRecord dialog, int choice) { var textClone = Instantiate(choicePrefab, infoParent); textClone.Initialize(text, () => ChoiceSelected(dialog, choice)); currentChoices.Add(textClone); ScrollToBottom(); }
private void DisplayInfo(DialogRecord dialog, InfoRecord info) { // Replace any text defines with actual text var dialogText = TextDefineProcessor.ProcessText(info.Response, player, Npc); dialogText = CheckForTopicsInText(dialogText); dialogView.DisplayInfo(dialogText, dialog?.name); if (info.Result != null) { DialogResultProcessor.ProcessResult(this, info.Result, dialog, player, Npc); } }
// Don't need to do this every frame. Should make it a coroutine like above private void Update() { var random = Random.Range(0, 10000); var chance = voiceIdleOdds * Time.deltaTime; if (random < chance) { var audioClip = DialogRecord.GetDialogInfo(DialogType.Voice, GetComponent <Character>(), null, VoiceType.Idle.ToString()); if (audioClip != null && audio != null) { audio.PlayOneShot(audioClip.AudioClip); } } }
private bool Bribe(Character player, Character npc, int amount) { var disposition = npc.GetDisposition(player); var d = 1 - 0.02f * Abs(disposition - 50); var playerRating = (player.GetSkill(CharacterSkill.sSkillMercantile) + player.LuckTerm + player.PersonalityTerm) * player.FatigueTerm; var npcRating = (npc.GetSkill(CharacterSkill.sSkillMercantile) + npc.ReputationTerm + npc.LuckTerm + npc.PersonalityTerm) * npc.FatigueTerm; float bribeMod; if (amount == 10) { bribeMod = GameSetting.Get("fBribe10Mod").FloatValue; } else if (amount == 100) { bribeMod = GameSetting.Get("fBribe100Mod").FloatValue; } else { bribeMod = GameSetting.Get("fBribe1000Mod").FloatValue; } var target3 = Max(PerMinChance, d * (playerRating - npcRating + 50) + bribeMod); var roll = Random.Range(0, 100); var change = (int)((target3 - roll) * DieRollMult); DialogRecord dialog; if (roll <= target3) { change = Max(PerMinChange, change); dialog = DialogRecord.GetPersuasionDialog(player, npc, PersuasionResult.BribeSuccess); } else { dialog = DialogRecord.GetPersuasionDialog(player, npc, PersuasionResult.BribeFail); } // Update the disposition in the UI npc.DispositionMod += Clamp(change, -disposition, 100 - disposition); controller.SetDisposition(npc.GetDisposition(player)); // Display the response controller.DisplayTopic(dialog); return(roll <= target3); }
void IDialogController.DisplayService(CharacterService service) { // Check if the service can be refused if (service.IsRefusable) { var dialog = DialogRecord.GetPersuasionDialog(player, Npc, PersuasionResult.ServiceRefusal); var info = dialog.GetInfo(player, Npc, isServiceRefusal: true); // If a response was found, the service has been refused by the npc if (info != null) { DisplayInfo(dialog, info); return; } } service.DisplayService(this, player, Npc); }
// Adds a new topic to the topic list void IDialogView.AddTopic(DialogRecord topic) { // Convert tile to title case var title = char.ToUpper(topic.name[0]) + topic.name.Substring(1); // Create the UI game object var clone = Instantiate(topicPrefab, topicsParent); clone.Initialize(title, () => controller.DisplayTopic(topic)); // Add it to the current list of topics, and sort currentTopics.Add(topic); currentTopics.Sort((x, y) => x.name.CompareTo(y.name)); // Get the index and use it to position the transform in the list correctly var index = currentTopics.IndexOf(topic); clone.transform.SetSiblingIndex(index); }
void IActivatable.Activate(GameObject target) { // Get the dialog component of the listener, so we can use it's NpcRecord player = target.GetComponent <Character>(); // Get the npc greeting, as if there is no greeting, the Npc can't be talked to. var greeting = DialogRecord.GetDialogInfo(DialogType.Greeting, Npc, player); if (greeting == null) { return; } // Check if the listener knows the current topic, otherwise it will not be available // This could be done within the same loop as getting the dialog topic topics = DialogRecord.GetDialog(Npc, player); var knownTopics = new List <DialogRecord>(); foreach (var topic in topics) { // Only show topics that the player knows if (player.Journal.Topics.ContainsKey(topic.name)) { knownTopics.Add(topic); } } // Display services var services = GetComponents <CharacterService>(); // Load the dialog and instantiate dialogView = DialogView.Create(this, record.FullName, knownTopics, services, Npc.GetDisposition(player)); // Process the greeting after getting topics, or there will be no topics to check against DisplayInfo(null, greeting); // Set this last, so it isn't true the first time the player talks to an Npc. GetComponent <Character>().HasTalkedToPlayer = true; }
private bool Taunt(Character player, Character npc) { var disposition = npc.GetDisposition(player); var d = 1 - 0.02f * Abs(disposition - 50); var target = d * (GetAdmireRating(player) - GetAdmireRating(npc) + 50); target = Max(PerMinChance, target); var roll = Random.Range(0, 100); var win = roll <= target; var change = Abs((int)(target - roll)); var x = (int)(-change * DieRollMult); if (win) { var s = change * DieRollMult * PerTempMult; var flee = Min(-PerMinChange, (int)(-s)); var fight = Max(PerMinChange, (int)(s)); if (Abs(x) < PerMinChange) { x = -PerMinChange; } } // Update the disposition in the UI npc.DispositionMod += Clamp(x, -disposition, 100 - disposition); controller.SetDisposition(npc.GetDisposition(player)); // Display the response var dialog = DialogRecord.GetPersuasionDialog(player, npc, win ? PersuasionResult.TauntSuccess : PersuasionResult.TauntFail); controller.DisplayTopic(dialog); return(win); }
private IEnumerator Start() { // Do an intial overlap sphere so that any Npc's already in range of others don't greet eachother var colliders = Physics.OverlapSphere(transform.position, GreetDistance * greetDistanceMultiplier, LayerMask.GetMask("Npc")); foreach (var collider in colliders) { // Do a raycast to make sure the NPC is actually visible var direction = collider.bounds.center - headTransform.position; RaycastHit hit; if (!Physics.Raycast(headTransform.position, direction, out hit, GreetDistance * greetDistanceMultiplier, LayerMask.GetMask("Default", "Npc", "Raycast Only")) || hit.collider != collider) { continue; } greetedNpcs.Add(collider); } while (isActiveAndEnabled) { yield return(new WaitForSeconds(GreetUpdateInterval)); // First, check if any of the previously-greeted Npc's have moved far enough away that they should be re-greeted if they get close. Add the greet distance to the rest ddistance, so that npcs with a long greet range don't re-greet players early greetedNpcs.RemoveWhere(col => Vector3.Distance(transform.position, col.transform.position) >= greetDistanceReset + GreetDistance * greetDistanceMultiplier); // Now periodically check for new Npcs, and if so, greet them colliders = Physics.OverlapSphere(headTransform.position, GreetDistance * greetDistanceMultiplier, LayerMask.GetMask("Npc")); foreach (var collider in colliders) { if (greetedNpcs.Contains(collider)) { continue; } // Raycast to the NPC first, to make sure there's nothing in the way. (This may not work, as it will probably raycast from the foot position var direction = collider.bounds.center - headTransform.position; RaycastHit hit; if (!Physics.Raycast(headTransform.position, direction, out hit, GreetDistance * greetDistanceMultiplier, LayerMask.GetMask("Default", "Npc", "Raycast Only")) || hit.collider != collider) { continue; } // Record this npc so we don't forget them greetedNpcs.Add(collider); // Get the Npc's NpcRecord var listener = collider.GetComponentInParent <DialogController>(); if (listener == null) { continue; } // Play greeting for any new Npc's detected var audioClip = DialogRecord.GetDialogInfo(DialogType.Voice, GetComponent <Character>(), listener.GetComponent <Character>(), VoiceType.Hello.ToString()); if (audio != null) { audio.PlayOneShot(audioClip.AudioClip); } } } }
private bool Intimidate(Character player, Character npc) { // Common var disposition = npc.GetDisposition(player); var playerRating2 = GetAdmireRating(player) + player.LevelTerm; var npcRating2 = (npc.LevelTerm + npc.ReputationTerm + npc.LuckTerm + npc.PersonalityTerm + npc.GetSkill(CharacterSkill.sSkillSpeechcraft)) * npc.FatigueTerm; var d = 1 - 0.02f * Abs(disposition - 50); var target2 = d * (playerRating2 - npcRating2 + 50); // Specific target2 = Max(PerMinChance, target2); var roll = Random.Range(0, 100); var win = roll <= target2; var r = roll != target2?RoundToInt(target2 - roll) : 1; if (roll <= target2) { var s = (int)(r * DieRollMult * PerTempMult); var flee = Max(PerMinChange, s); var fight = Min(-PerMinChange, -s); } var c = -Abs((int)(r * DieRollMult)); int x, y; if (win) { if (Abs(c) < PerMinChange) { x = 0; y = -PerMinChange; // bug, see comments? } else { x = -(int)(c * PerTempMult); y = c; } } else { x = (int)(c * PerTempMult); y = c; } var tempChange = x; // Clamp to 100 var cappedDispositionChange = Clamp(disposition + tempChange, 0, 100) - disposition; // ANother clamp method? if (disposition + tempChange > 100) { cappedDispositionChange = 100 - disposition; } else if (disposition + tempChange < 0) { cappedDispositionChange = disposition; } if (win) { npc.DispositionMod += (int)(cappedDispositionChange / PerTempMult); } else { npc.DispositionMod += y; } // Update the disposition in the UI controller.SetDisposition(npc.GetDisposition(player)); var dialog = DialogRecord.GetPersuasionDialog(player, npc, win ? PersuasionResult.IntimidateSuccess : PersuasionResult.IntimidateFail); // Display the response controller.DisplayTopic(dialog); return(win); }
private static void ShowChoices(IDialogController controller, IEnumerable <MethodArgument> parameters, DialogRecord dialog) { // Every second parameter should be a number? var previousChoice = string.Empty; foreach (var parameter in parameters) { if (parameter.Type == ArgumentType.String) { previousChoice = parameter.StringValue; } else if (parameter.Type == ArgumentType.Int) { var choice = parameter.IntValue; controller.DisplayChoice(previousChoice, dialog, choice); } else { throw new ArgumentException("Incorrect argument passed to Choices. Must be a String or Int"); } } }
// Journal IC14_Ponius 45 // player->additem gold_001 1000 // Choice "Offer to track down the clerk and recover the gold." 1 "Thank him for his frankness and leave." 2 // Journal "VA_VampChild" 100 // Journal "VA_VampChild" 30 // Player->additem "extravagant_ring_aund_uni" 1 // Identifier space string space int // identifier arrow identifier space string space int // Identifier space string space int space string space int // identifier string int // "Minabibi Assardarainat"->PositionCell 217 846 74 190 "Sadrith Mora, Wolverine Hall: Mage's Guild" // String arrow Identifier int int int int public static void ProcessResult(IDialogController dialogView, string result, DialogRecord dialog, Character player, Character Npc) { var lexer = new DialogResultLexer(result); var tokens = lexer.ProcessResult().ToList(); var parser = new DialogResultParser(); var methodCalls = parser.ParseFile(tokens); foreach (var methodCall in methodCalls) { switch (methodCall.Method) { // Choice "Offer to track down the clerk and recover the gold." 1 "Thank him for his frankness and leave." 2 case "choice": case "Choice": case "Choice,": ShowChoices(dialogView, methodCall.Args, dialog); break; default: Debug.Log(methodCall); break; } } return; // split the result into lines var stringReader = new StringReader(result); var parameters = new List <string>(); // Iterate through each line string line; while ((line = stringReader.ReadLine()) != null) { // Ignore any lines starting with a semi colon as they are comments if (line.StartsWith(";")) { continue; } // Get the function name by splitting the first two words. If no spaces are found, then no extra parameters exist // Split by spaces, and quotes? var phrase = new StringBuilder(); var inQuotes = false; for (var i = 0; i < line.Length; i++) { switch (line[i]) { case ' ': // If in quotes, append spaces to the parameter if (inQuotes) { phrase.Append(line[i]); } else { // If not in quotes, add the current phrase to the list of parameters, and clear the string builder // Ensure we're not adding empty spaces to the parameter list if (phrase.ToString() != string.Empty) { parameters.Add(phrase.ToString()); } phrase.Clear(); } break; case '"': inQuotes = !inQuotes; break; default: phrase.Append(line[i]); break; } } // Append the last phrase from the string builder parameters.Add(phrase.ToString()); // Process the function // This will probably eventually be replaced by a script interpreter/tokenizer thing, but for now see if this works. switch (parameters[0]) { //case "additem": // break; //case "addtopic": //case "Addtopic": // break; // Choice is a word, followed by an option in Quotes, then a number specifying the index. This may occur multipel times //case "choice": //case "Choice": // Choice "Offer to track down the clerk and recover the gold." 1 "Thank him for his frankness and leave." 2 //case "Choice,": // ShowChoices(dialogView, parameters, dialog); // break; //case "ClearInfoActor": // should not add this entry to journal //break; //case "Goodbye": // Show Goodbye choice, close dialog //break; case "Journal": //Jouranl ID Index, ID may be in quotes (But not always?) case "Journal,": case "Journal.": var exists = player.Journal.AddOrUpdateEntry(parameters[1], int.Parse(parameters[2])); if (!exists) { dialogView.DisplayResult(GameSetting.Get("sJournalEntry").StringValue); } break; case "moddisposition": case "ModDisposition": // Followed by a space? and then a number (can be positive or negative) Npc.DispositionMod += int.Parse(parameters[1]); dialogView.SetDisposition(Npc.GetDisposition(player)); break; //case "ModPCFacRep": //ModPCFacRep 10 "Fighters Guild" // break; case "player->AddItem,": case "player->Additem,": case "Player->AddItem": { var item = Record.GetRecord <ItemRecord>(parameters[1].TrimEnd(',')); // If a second parameter exists, it may be the count int quantity = parameters.Count > 2 ? int.Parse(parameters[2]) : 1; player.Inventory.AddItem(item, quantity); var message = GameSetting.Get(quantity == 1 ? "sNotifyMessage60" : "sNotifyMessage61").StringValue; message = message.Replace("%s", item.FullName); message = message.Replace("%d", quantity.ToString()); dialogView.DisplayResult(message); } break; //case "PCClearExpelled": // Makes the PC no longer expelled from the speakers faction (Unsure if sometimes followed by a parameter) //break; case "PCJoinFaction": // Should be followed by the faction name eg PCJoinFaction "Mages Guild" player.JoinFaction(Record.GetRecord <Faction>(parameters[1])); break; // This can also appear instead of joining the faction, so must join the faction first if not a member //PCRaiseRank "Hlaalu" (This may not have the faction name written after, in which case the speaker's faction should be used) case "PCRaiseRank": if (!Npc.IsInSameFaction(player)) { player.JoinFaction(Npc.Factions.First().Key); } else { player.Factions[Npc.Factions.First().Key].Rank++; } break; case "player->removeitem": { var item = Record.GetRecord <ItemRecord>(parameters[1].TrimEnd(',')); player.Inventory.RemoveItem(item, 1); var message = GameSetting.Get("sNotifyMessage62").StringValue; message = message.Replace("%s", item.FullName); dialogView.DisplayResult(message); } break; //case "set": // if (parameters.Count > 1) Debug.Log(parameters[1]); // break; //case "ShowMap": // break; default: // Should check string for a "->" character, if found, the first part is object id, i.e.: // arrille->moddisposition // Should search for an object ID here, and if found, look for the -> symbol, then switch on what follows eg(Player->AddItem Gold_001 500) parameters.ForEach((param) => Debug.Log(param)); break; } // Clear the list of parameters for the next iteration parameters.Clear(); } }
public void AddTopic(string topic, DialogRecord dialog) { topics[topic] = dialog; }
void IDialogController.DisplayTopic(DialogRecord dialog, int choice) { var info = dialog.GetInfo(player, Npc, choice); DisplayInfo(dialog, info); }
private void ChoiceSelected(DialogRecord dialog, int choice) { controller.DisplayTopic(dialog, choice); currentChoices.ForEach(component => Destroy(component.gameObject)); currentChoices.Clear(); }
public static void Create(BinaryReader reader) { var header = new RecordHeader(reader); switch (header.Type) { case RecordType.BirthSign: BirthSignRecord.Create(reader, header); break; case RecordType.BodyPart: BodyPartRecord.Create(reader, header); break; case RecordType.Cell: CellRecord.Create(reader, header); break; case RecordType.Dialogue: DialogRecord.Create(reader, header); break; case RecordType.GameSetting: GameSetting.Create(reader, header); break; case RecordType.Info: InfoRecord.Create(reader, header); break; case RecordType.Land: LandRecord.Create(reader, header); break; case RecordType.LandTexture: LandTextureRecord.Create(reader, header); break; case RecordType.MagicEffect: MagicEffectRecord.Create(reader, header); break; case RecordType.PathGrid: Pathgrid.Create(reader, header); break; case RecordType.Script: Script.Create(reader, header); break; case RecordType.Skill: SkillRecord.Create(reader, header); break; case RecordType.SoundGenerator: SoundGenerator.Create(reader, header); break; case RecordType.Tes3: Tes3Record.Create(reader, header); break; default: { var size = GotoSubrecord(SubRecordType.Id, header); var id = reader.ReadString(size); reader.BaseStream.Position = header.DataOffset + header.DataSize; var recordData = CreateRecordData(header.Type); recordData.Header = header; Records.Add(id, recordData); break; } } }
void IDialogController.DisplayChoice(string description, DialogRecord dialog, int choice) => dialogView.DisplayChoice(description, dialog, choice);