private static void ApplyMutations(CharacterTemplate buildTemplate, List <MLNode> mutationNodes) { if (!buildTemplate.genotypeEntry.IsMutant) { return; } Mutations part = buildTemplate.PlayerBody.GetPart <Mutations>(); foreach (MLNode node in mutationNodes) { if (node.Entry != null && node.Selected > 0) { if (node.Entry.Mutation != null) { BaseMutation baseMutation = node.Entry.CreateInstance(); baseMutation.SetVariant(node.Variant); part.AddMutation(baseMutation, node.Selected); } else if (node.Entry.DisplayName == "Chimera") { buildTemplate.MutationLevel = "Chimera"; } else if (node.Entry.DisplayName == "Esper") { buildTemplate.MutationLevel = "Esper"; } } } }
/// <summary> /// Creates an ability description for a mutation. The description is constructed from the /// mutation description and the GetLevelText() for the mutation at its current level. /// Cooldown information is also appended to the description. /// </summary> /// TODO: Determine if the Level logic has changed with the new chimera rapid advancements. /// For example, will level return 6 if that's my actual level, or will it return /// 5 if my mutation level is restricted based on my character level? public List <string> MakeMutationAbilityDescription(string category, ActivatedAbilityEntry ability) { if (!this.Categories.ContainsKey(category)) { LogUnique($"(FYI) Activated ability description for '{SimplifiedAbilityName(ability?.DisplayName)}'" + $" won't be updated because QudUX didn't recognize it's activated ability category, '{category}'"); return(SpecialFormatDescription(ability?.Description, SourceAbility: ability)); } List <AbilityXmlInfo> abilityData = this.Categories[category]; foreach (AbilityXmlInfo abilityDataEntry in abilityData) { if (abilityDataEntry.Name == SimplifiedAbilityName(ability.DisplayName)) { //match AbilityDataEntry to the Ability name BaseMutation abilitySourceMutation = null; BaseMutation secondaryMatch = null; foreach (BaseMutation playerMutation in this.PlayerMutations) { MutationEntry mutationEntry = playerMutation.GetMutationEntry(); if (mutationEntry != null && mutationEntry.DisplayName == abilityDataEntry.MutationName) { abilitySourceMutation = playerMutation; break; } if (playerMutation.DisplayName == abilityDataEntry.MutationName) { secondaryMatch = playerMutation; //less desirable match method, but necessary for some NPC mutations that don't have a MutationEntry } } if (abilitySourceMutation == null && secondaryMatch != null) { abilitySourceMutation = secondaryMatch; } if (abilitySourceMutation == null) { LogUnique($"(FYI) Mutation activated ability '{SimplifiedAbilityName(ability?.DisplayName)}'" + $" in category '{category}' has no description available in game and no backup description" + " provided by QudUX, so a description won't be shown on the Manage Abilities screen."); break; } string abilityDescription = abilitySourceMutation.GetDescription() + "\n\n" + abilitySourceMutation.GetLevelText(abilitySourceMutation.Level); abilityDescription = abilityDescription.TrimEnd('\r', '\n', ' '); //updated Cooldown based on wisdom: if (abilityDescription.Contains("Cooldown:") || !string.IsNullOrEmpty(abilityDataEntry.BaseCooldown)) { if (string.IsNullOrEmpty(abilityDataEntry.NoCooldownReduction) || abilityDataEntry.NoCooldownReduction != "true") { string updatedDescription = string.Empty; string extractedCooldownString = GetAdjustedBaseCooldown(abilityDataEntry); string[] descriptionParts = abilityDescription.Split('\n'); foreach (string descriptionPart in descriptionParts) { if (descriptionPart.Contains("Cooldown:")) { string[] words = descriptionPart.Split(' '); foreach (string word in words) { int o; if (int.TryParse(word, out o)) { extractedCooldownString = this.GetCooldownString(word); break; } } if (string.IsNullOrEmpty(extractedCooldownString)) { updatedDescription += (updatedDescription != string.Empty ? "\n" : string.Empty) + descriptionPart; //restore line in case we didn't find the number (should never happen) } } else { updatedDescription += (updatedDescription != string.Empty ? "\n" : string.Empty) + descriptionPart; } } abilityDescription = updatedDescription + (!string.IsNullOrEmpty(extractedCooldownString) ? "\n\n" + extractedCooldownString : string.Empty); } } return(SpecialFormatDescription(abilityDescription, abilityDataEntry.DeleteLines, abilityDataEntry.DeletePhrases, ability)); } } return(SpecialFormatDescription(ability?.Description, SourceAbility: ability)); }
public override bool FireEvent(Event E) { if (E.ID == "ApplyTonic") { GameObject target = E.GetGameObjectParameter("Target"); target.PermuteRandomMutationBuys(); var Messages = new List <string>(); var gainLimb = false; Mutations mutations = null; if (target.IsPlayer()) { Popup.Show("As the tonic floods your veins, you feel the latent genetic pressure of eons."); } if (ParentObject.HasPart("Temporary") && !target.HasPart("Temporary")) { Messages.Add((target.IsPlayer() ? "Your" : Grammar.MakePossessive(target.The + target.ShortDisplayName)) + " genes ache longingly."); } else { var Random = Utility.Random(this, target); var goodColor = ColorCoding.ConsequentialColor(ColorAsGoodFor: target); var badColor = ColorCoding.ConsequentialColor(ColorAsBadFor: target); if (target.IsTrueKin()) { var which = new[] { "Strength", "Agility", "Toughness" }.GetRandomElement(Random); if (target.HasStat(which)) { ++target.Statistics[which].BaseValue; Messages.Add(goodColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("gain") + " 1 point of " + which + "!"); } } else /*if mutant*/ { mutations = target.GetPart <Mutations>(); if (target.IsEsper()) { // can't chimerify; reduce glimmer instead int glimmerReduction = Random.Next(1, 5); int currentGlimmer = target.GetPsychicGlimmer(); if (glimmerReduction > currentGlimmer) { glimmerReduction = currentGlimmer; } _ = target.ModIntProperty("GlimmerModifier", -glimmerReduction); target.SyncMutationLevelAndGlimmer(); Messages.Add("Nerves weave and ossify, unable to escape " + (target.IsPlayer() ? "your" : Grammar.MakePossessive(target.the + target.ShortDisplayName)) + " psychic cage."); } else if (!target.IsChimera()) { var mentals = target.GetMentalMutations(); var mentalDefects = target.GetMutationsOfCategory("MentalDefects"); var mpToTransfer = 0; if (mentals.Count > 0 || mentalDefects.Count > 0) { Messages.Add((target.IsPlayer() ? "Your" : Grammar.MakePossessive(target.The + target.ShortDisplayName)) + " genome sloughs off superfluous layers of alien thoughtstuff."); Messages.Add(""); } if (mentals.Count > 0) { // choose 1d4 MP of investment in mental mutations to remove... mpToTransfer = Random.Next(1, 5); var totalLevels = mentals.Map(m => m.BaseLevel).Sum(); var toReduce = new List <Tuple <BaseMutation, int> >(mpToTransfer); if (totalLevels <= mpToTransfer) { // remove everything, will be eligible for Chimera mpToTransfer = totalLevels; foreach (var mental in mentals) { toReduce.Add(Tuple.Create(mental, mental.BaseLevel)); } } else { // magically pick mpToTransfer mutations weighted by level var remainingLevels = totalLevels; var remainingReduction = mpToTransfer; foreach (var mental in mentals) { var thisMentalReduction = 0; while (0 < remainingReduction && Random.Next(0, remainingLevels) < mental.BaseLevel - thisMentalReduction + remainingReduction - 1) { ++thisMentalReduction; --remainingLevels; --remainingReduction; } if (0 < thisMentalReduction) { toReduce.Add(Tuple.Create(mental, thisMentalReduction)); } remainingLevels -= mental.BaseLevel - thisMentalReduction; if (0 >= remainingReduction) { break; } } } // ... remove them... var lostMentals = new List <MutationEntry>(); foreach (var mental in toReduce) { var mutation = mental.Item1; var reduction = mental.Item2; if (mutation.BaseLevel <= reduction) { // remove the mutation altogether lostMentals.Add(mutation.GetMutationEntry()); mutations.RemoveMutation(mutation); } else { // reduce the mutation level mutations.LevelMutation(mutation, mutation.BaseLevel - reduction); } } // ... and replace any lost mental mutations with physical mutations foreach (var mental in lostMentals) { // expensive to regenerate this each time, but won't be very many times and // want to make sure exclusions from e.g. Stinger are handled correctly Messages.Add(badColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("lose") + " " + mental.DisplayName + "!"); var eligiblePhysicals = mutations.GetMutatePool(m => m.Category.Name.EndsWith("Physical")).Shuffle(Random); var similarPhysicals = eligiblePhysicals.Where(p => p.Cost == mental.Cost); var otherPhysicals = eligiblePhysicals.Where(p => p.Cost != mental.Cost); foreach (var physical in similarPhysicals.Concat(otherPhysicals)) { _ = mutations.AddMutation(physical, 1); Messages.Add(goodColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("gain") + " " + physical.DisplayName + "!"); --mpToTransfer; break; } // else if there are no valid physical mutations, don't add anything new } } while (0 < mpToTransfer) { var physicals = target.GetPhysicalMutations(); BaseMutation which = null; var canLevelNormally = physicals.Where(m => m.CanIncreaseLevel()); if (canLevelNormally.Any()) { which = canLevelNormally.GetRandomElement(Random); } else { var underMaxLevel = physicals.Where(m => m.BaseLevel < m.MaxLevel); if (underMaxLevel.Any()) { which = underMaxLevel.GetRandomElement(Random); } } if (which != null) { mutations.LevelMutation(which, which.BaseLevel + 1); --mpToTransfer; } else { // no physical mutations to put the levels in, spend the rest on a new one foreach (var physical in mutations.GetMutatePool(m => m.Category.Name.EndsWith("Physical")).Shuffle(Random)) { _ = mutations.AddMutation(physical, 1); Messages.Add(goodColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("gain") + " " + physical.DisplayName + "!"); break; } mpToTransfer = 0; } } if (target.GetMentalMutations().Count == 0) { foreach (var mutation in mentalDefects) { mutations.RemoveMutation(mutation); var mental = mutation.GetMutationEntry(); Messages.Add(goodColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("lose") + " " + mental.DisplayName + "!"); var eligibleDefects = MutationFactory.GetMutationsOfCategory("PhysicalDefects").Shuffle(Random); var similarDefects = eligibleDefects.Where(p => p.Cost == mental.Cost); var otherDefects = eligibleDefects.Where(p => p.Cost != mental.Cost); foreach (var physical in similarDefects.Concat(otherDefects)) { _ = mutations.AddMutation(physical, 1); Messages.Add(badColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("gain") + " " + physical.DisplayName + "!"); break; } // else if there are no valid physical defects, don't add anything new } target.Property["MutationLevel"] = target.Property.GetValueOrDefault("MutationLevel", "") + "Chimera"; Messages.Add(""); Messages.Add(goodColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("become") + " a Chimera!"); } } else /*target.IsChimera*/ { // 50% chance of new limb, 50% chance of mutation level gain if (Random.Next(2) == 0) { // new limb! defer until the other messages are shown to actually gain it gainLimb = true; } else { var physicals = target.GetPhysicalMutations().Where(m => m.CanLevel()); if (physicals.Any()) { // +1 to level of a physical mutation, uncapped var which = physicals.GetRandomElement(Random); const string source = "{{r-r-r-R-R-W distribution|limbic fluid}} injections"; var found = false; foreach (var mod in mutations.MutationMods) { if (mod.sourceName == source && mod.mutationName == which.Name) { ++mod.bonus; found = true; } } if (!found) { _ = mutations.AddMutationMod(which.Name, 1, Mutations.MutationModifierTracker.SourceType.StatMod, source); } Messages.Add(goodColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("gain") + " one rank of " + which.DisplayName + "!"); } else { // +1 MP if we can't level anything target.GainMP(1); Messages.Add(goodColor + (target.IsPlayer() ? "You" : target.The + target.ShortDisplayName) + target.GetVerb("gain") + " one MP!"); } } } } } if (target.IsPlayer() && Messages.Count > 0) { var output = string.Join("\n", Messages); output = _extraLines.Replace(output, "\n\n"); Popup.Show(output); } else { foreach (var Message in Messages) { AddPlayerMessage(Message); } } if (gainLimb) { _ = mutations.AddChimericBodyPart(); } } return(base.FireEvent(E)); }
public static void RemoveMutation(Mutations mutations, BaseMutation mutation) { mutations.RemoveMutation(mutation); Show($"Om nom nom! {mutation.DisplayName} is gone! {{{{w|*belch*}}}}"); }
public void UpdateMutationAbilityDescription(string category, ActivatedAbilityEntry ability) { if (!this.Categories.ContainsKey(category)) { Debug.Log("QudUX Mod: Couldn't find any data for activated ability category '" + category + "'. Activated ability description for " + this.SimplifiedAbilityName(ability.DisplayName) + " won't be updated."); return; } List <Egcb_AbilityDataEntry> abilityData = this.Categories[category]; foreach (Egcb_AbilityDataEntry abilityDataEntry in abilityData) { if (abilityDataEntry.Name == this.SimplifiedAbilityName(ability.DisplayName)) { //match AbilityDataEntry to the Ability name BaseMutation abilitySourceMutation = null; BaseMutation secondaryMatch = null; foreach (BaseMutation playerMutation in this.PlayerMutations) { MutationEntry mutationEntry = playerMutation.GetMutationEntry(); if (mutationEntry != null && mutationEntry.DisplayName == abilityDataEntry.MutationName) { abilitySourceMutation = playerMutation; break; } if (playerMutation.DisplayName == abilityDataEntry.MutationName) { secondaryMatch = playerMutation; //less desirable match method, but necessary for some NPC mutations that don't have a MutationEntry } } if (abilitySourceMutation == null && secondaryMatch != null) { abilitySourceMutation = secondaryMatch; } if (abilitySourceMutation == null) { Debug.Log("QudUX Mod: Unexpectedly failed to load mutation description data for '" + this.SimplifiedAbilityName(ability.DisplayName) + "' activated ability."); continue; } if (!this.VanillaDescriptionText.ContainsKey(ability.ID)) { this.VanillaDescriptionText.Add(ability.ID, ability.Description); } ability.Description = abilitySourceMutation.GetDescription() + "\n\n" + abilitySourceMutation.GetLevelText(abilitySourceMutation.Level); ability.Description = ability.Description.TrimEnd('\r', '\n', ' '); //updated Cooldown based on wisdom: if (ability.Description.Contains("Cooldown:") || !string.IsNullOrEmpty(abilityDataEntry.BaseCooldown)) { string updatedDescription = string.Empty; string extractedCooldownString = !string.IsNullOrEmpty(abilityDataEntry.BaseCooldown) ? this.GetCooldownString(abilityDataEntry.BaseCooldown) : string.Empty; string[] descriptionParts = ability.Description.Split('\n'); foreach (string descriptionPart in descriptionParts) { if (descriptionPart.Contains("Cooldown:")) { string[] words = descriptionPart.Split(' '); foreach (string word in words) { int o; if (int.TryParse(word, out o)) { extractedCooldownString = this.GetCooldownString(word); break; } } if (string.IsNullOrEmpty(extractedCooldownString)) { updatedDescription += (updatedDescription != string.Empty ? "\n" : string.Empty) + descriptionPart; //restore line in case we didn't find the number (should never happen) } } else { updatedDescription += (updatedDescription != string.Empty ? "\n" : string.Empty) + descriptionPart; } } ability.Description = updatedDescription + (!string.IsNullOrEmpty(extractedCooldownString) ? "\n\n" + extractedCooldownString : string.Empty); } ability.Description = Egcb_AbilityData.SpecialFormatDescription(ability.Description, abilityDataEntry.DeleteLines, abilityDataEntry.DeletePhrases); break; } } }