private static List <DebugMenuOption> Options_AddAffix() { List <DebugMenuOption> debugMenuOptionList = new List <DebugMenuOption>(); foreach (LootAffixDef affixDef in DefDatabase <LootAffixDef> .AllDefs.OrderBy(lad => lad.affixCost)) { LootAffixDef localDef = affixDef; debugMenuOptionList.Add(new DebugMenuOption(localDef.defName, DebugMenuOptionMode.Tool, () => { CompLootAffixableThing comp = Find.CurrentMap.thingGrid. ThingsAt(UI.MouseCell()). Where(t => t is ThingWithComps).Cast <ThingWithComps>(). Select(twc => twc.TryGetComp <CompLootAffixableThing>()). Where(c => c is CompLootAffixableThing). FirstOrDefault() ; var lads = comp.AllAffixDefs; if (lads.Contains(localDef) || lads.Count >= 4) { return; } lads.Add(localDef); comp.PostAffixCleanup(); })); } return(debugMenuOptionList); }
static public void AddNewAffixes(this CompLootAffixableThing comp, float affixPoints = 0, int ttlAffixes = 0) // options for debug only { List <LootAffixDef> affixes = comp.affixes; ThingWithComps thing = comp.parent; affixes.Clear(); if (affixPoints == 0) { affixPoints = CalculateTotalLootAffixPoints(thing); } if (ttlAffixes == 0) { for (int i = 1; i <= 4; i++) { // FIXME: Add config sliders for percentages here // 25% chance for each affix (compounded) if (0.25f < Random.Range(0.0f, 1.0f)) { break; } ttlAffixes = i; } } if (ttlAffixes == 0) { return; } // Baseline of affixes that can be used (since affixPoints could change upward or downward) List <LootAffixDef> baseAffixDefs = DefDatabase <LootAffixDef> .AllDefsListForReading. FindAll(lad => lad.CanBeAppliedToThing(thing)) ; // Affix picking loop for (int curAffixes = affixes.Count + 1; curAffixes <= 4; curAffixes++) { LootAffixDef newAffix = PickAffix(thing, baseAffixDefs, curAffixes, ttlAffixes, affixPoints); if (newAffix == null) { return; } affixes.Add(newAffix); affixPoints -= newAffix.GetRealAffixCost(thing); baseAffixDefs = baseAffixDefs.FindAll(lad => lad.groupName != newAffix.groupName); } }
private static List <DebugMenuOption> Options_RemoveAffix(CompLootAffixableThing comp) { List <DebugMenuOption> debugMenuOptionList = new List <DebugMenuOption>(); foreach (LootAffixDef affixDef in comp.AllAffixDefs) { LootAffixDef localDef = affixDef; debugMenuOptionList.Add(new DebugMenuOption(comp.AllAffixesByAffixDefs[localDef], DebugMenuOptionMode.Action, () => { int i = comp.AllAffixDefs.IndexOf(localDef); comp.AllAffixDefs.RemoveAt(i); comp.PostAffixCleanup(); })); } return(debugMenuOptionList); }
static public void PostAffixCleanup(this CompLootAffixableThing comp, bool fixLabel = true) { ThingWithComps thing = comp.parent; comp.ClearAffixCaches(); if (fixLabel) { comp.affixRules.Clear(); comp.fullStuffLabel = null; string name = thing.LabelNoCount; name = comp.TransformLabel(name); } thing.def.SpecialDisplayStats(StatRequest.For(thing)); foreach (LootAffixDef affix in comp.affixes) { affix.PostApplyAffix(thing); } }
static IEnumerable <StatDrawEntry> Postfix(IEnumerable <StatDrawEntry> values, ThingDef __instance, StatRequest req) { CompLootAffixableThing comp = null; if (req.Thing is ThingWithComps thing) { comp = thing.TryGetComp <CompLootAffixableThing>(); } // Cycle through the entries foreach (StatDrawEntry value in values) { // Give it to the comp to meddle with if (comp != null) { comp.SpecialDisplayStatsInjectors(value); } yield return(value); } // Go back to the old set. Iterators cannot have refs, so we have to replace this with reflection. if (!Base.origVerbPropertiesCache.ContainsKey(__instance.defName)) { if (comp != null && !req.Thing.def.Verbs.NullOrEmpty()) { Log.Error("Old VerbProperties lost from SpecialDisplayStats swap!"); } yield break; } // [Reflection] thing.verbs = Base.origVerbPropertiesCache[__instance.defName]; FieldInfo verbsField = AccessTools.Field(typeof(ThingDef), "verbs"); verbsField.SetValue(__instance, Base.origVerbPropertiesCache[__instance.defName]); }
static public void InitializeAffixes(this CompLootAffixableThing comp, float affixPoints = 0, int ttlAffixes = 0) // options for debug only { comp.affixes.Clear(); comp.AddNewAffixes(affixPoints, ttlAffixes); comp.PostAffixCleanup(); }
static public string GetSetFullStuffLabel(this CompLootAffixableThing comp, string label) { List <LootAffixDef> affixes = comp.affixes; List <Rule> affixRules = comp.affixRules; ThingWithComps thing = comp.parent; // Make sure we're not saving color tag information label = label.StripTags(); // Short-circuit: No affixes if (comp.AffixCount == 0) { return(label); } // Short-circuit: Already calculated the full label and no replacement required string stuffLabel = GenLabel.ThingLabel(thing.def, thing.Stuff, 1).StripTags(); if (comp.fullStuffLabel != null && stuffLabel == label) { return(comp.fullStuffLabel); } // Short-circuit: Still have the calculated full label string preExtra = ""; string postExtra = ""; int pos = label.IndexOf(stuffLabel); if (pos >= 0) { preExtra = label.Substring(0, pos); postExtra = label.Substring(pos + stuffLabel.Length); } if (comp.fullStuffLabel != null) { return(preExtra + comp.fullStuffLabel + postExtra); } // Need the calculate the label then... var namerDef = DefDatabase <LootAffixNamerRulePackDef> .GetNamed("RimLoot_LootAffixNamer"); GrammarRequest request = new GrammarRequest(); request.Includes.Add(namerDef); // Word class counter set-up Dictionary <string, int> maxWordClasses = namerDef.maxWordClasses; Dictionary <string, int> curWordClasses = new Dictionary <string, int> (); for (int i = 0; i < 5; i++) { curWordClasses = maxWordClasses.ToDictionary(k => k.Key, v => 0); // shallow clone to k=0 // Add in the affixes foreach (LootAffixDef affix in affixes) { foreach (Rule rule in affix.PickAffixRulesForLabeling(curWordClasses, maxWordClasses)) { if (rule.keyword.StartsWith("AFFIX_")) { comp.affixRules.Add(rule); } request.Rules.Add(rule); if (rule.keyword.StartsWith("AFFIXPROP_")) { request.Constants.Add(rule.keyword, rule.Generate()); } } } // Double-check we didn't hit one of those disallowed combinations if (namerDef.IsWordClassComboAllowed(affixRules)) { break; } else { affixRules.Clear(); continue; } } if (affixRules.Count != comp.AffixCount) { Log.Error("Chosen affix words for " + thing + " don't match the number of affixes:\n" + string.Join( "\nvs.\n", string.Join(" | ", affixRules), string.Join(", ", affixes) )); } // Add a few types of labels for maximum language flexibility request.Rules.Add(new Rule_String("STUFF_label", thing.Stuff != null ? thing.Stuff.LabelAsStuff : "")); request.Rules.Add(new Rule_String("THING_defLabel", thing.def.label)); request.Rules.Add(new Rule_String("THING_stuffLabel", stuffLabel)); string rootKeyword = "r_affix" + comp.AffixCount; comp.fullStuffLabel = NameGenerator.GenerateName(request, null, false, rootKeyword, rootKeyword); // It's possible we might end up hitting this later than we expected, and run into affixes/word // desyncs, so clear the cache, just in case. comp.ClearAffixCaches(); return(preExtra + comp.fullStuffLabel + postExtra); }
public override float GetValueUnfinalized(StatRequest req, bool applyPostProcess = true) { if (!(req.Def is ThingDef def)) { return(0.0f); } CompEquippable equipComp = null; CompLootAffixableThing lootComp = null; if (req.HasThing) { var thing = (ThingWithComps)req.Thing; equipComp = thing.TryGetComp <CompEquippable>(); lootComp = thing.TryGetComp <CompLootAffixableThing>(); } Verb verb = equipComp?.AllVerbs.First(v => v.verbProps.isPrimary); VerbProperties verbProps = verb != null ? verb.verbProps : def.Verbs.First(vp => vp.isPrimary); Pawn attacker = req.HasThing ? GetCurrentWeaponUser(req.Thing) : null; var projProps = verbProps.defaultProjectile.projectile; var projModifier = (LootAffixModifier_VerbPropertiesChange_Def)lootComp?.AllModifiers.FirstOrFallback( lam => lam.AppliesTo == ModifierTarget.VerbProperties && lam is LootAffixModifier_VerbPropertiesChange_Def lamVPCD && lamVPCD.affectedField == "defaultProjectile" ); ThingDef modProjectile = projModifier != null ? (ThingDef)projModifier.resolvedDef : null; var modProjProps = modProjectile?.projectile; float chance = modProjProps != null ? 1f - projModifier.GetRealChance(lootComp.parent) : 1f; if (chance <= 0.05f) { chance = 1f; // already permanently set to "base" verbProps } float baseDamage = projProps.damageDef.harmsHealth == false ? 0f : req.HasThing ? projProps.GetDamageAmount(req.Thing) : req.StuffDef != null?projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier, req.StuffDef)) : projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier)) ; float damage = baseDamage * verbProps.burstShotCount * chance; if (chance < 1f) { float modChance = 1f - chance; float modBaseDamage = modProjProps.damageDef.harmsHealth == false ? 0f : modProjProps.GetDamageAmount(req.Thing) ; damage += modBaseDamage * verbProps.burstShotCount * modChance; } // FIXME: Confirm warmupTime (and AimingDelayFactor) is used in a full shot cycle // FIXME: warmupTime * this.CasterPawn.GetStatValue(StatDefOf.AimingDelayFactor, true)).SecondsToTicks() float secondsSpent = 0; if (verb != null) { secondsSpent = verbProps.AdjustedFullCycleTime(verb, attacker); } else { secondsSpent = verbProps.warmupTime + ((verbProps.burstShotCount - 1) * verbProps.ticksBetweenBurstShots).TicksToSeconds(); secondsSpent += req.HasThing ? req.Thing.GetStatValue(StatDefOf.RangedWeapon_Cooldown, true) : req.StuffDef != null?def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown, req.StuffDef) : def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown) ; } // Every integer range possible as an average float avgAccuracy = 0; for (int i = 3; i <= verbProps.range; i++) { float rngAccuracy = verbProps.GetHitChanceFactor(req.Thing, i); if (attacker != null) { rngAccuracy *= ShotReport.HitFactorFromShooter(attacker, i); } avgAccuracy += rngAccuracy; } if (verbProps.range >= 3) { avgAccuracy /= verbProps.range - 2; } return(secondsSpent == 0 ? 0.0f : damage / secondsSpent * avgAccuracy); }
public override string GetExplanationUnfinalized(StatRequest req, ToStringNumberSense numberSense) { if (!(req.Def is ThingDef def)) { return(null); } /* Damage section */ CompEquippable equipComp = null; CompLootAffixableThing lootComp = null; if (req.HasThing) { var thing = (ThingWithComps)req.Thing; equipComp = thing.TryGetComp <CompEquippable>(); lootComp = thing.TryGetComp <CompLootAffixableThing>(); } Verb verb = equipComp?.AllVerbs.First(v => v.verbProps.isPrimary); VerbProperties verbProps = verb != null ? verb.verbProps : def.Verbs.First(vp => vp.isPrimary); Pawn attacker = req.HasThing ? GetCurrentWeaponUser(req.Thing) : null; var projProps = verbProps.defaultProjectile.projectile; var projModifier = (LootAffixModifier_VerbPropertiesChange_Def)lootComp?.AllModifiers.FirstOrFallback( lam => lam.AppliesTo == ModifierTarget.VerbProperties && lam is LootAffixModifier_VerbPropertiesChange_Def lamVPCD && lamVPCD.affectedField == "defaultProjectile" ); ThingDef modProjectile = projModifier != null ? (ThingDef)projModifier.resolvedDef : null; var modProjProps = modProjectile?.projectile; float chance = modProjProps != null ? 1f - projModifier.GetRealChance(lootComp.parent) : 1f; if (chance <= 0.05f) { chance = 1f; // already permanently set to "base" verbProps } string chanceStr = GenText.ToStringPercent(chance); float baseDamage = projProps.damageDef.harmsHealth == false ? 0f : req.HasThing ? projProps.GetDamageAmount(req.Thing) : req.StuffDef != null?projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier, req.StuffDef)) : projProps.GetDamageAmount(def.GetStatValueAbstract(StatDefOf.RangedWeapon_DamageMultiplier)) ; float damage = baseDamage * verbProps.burstShotCount * chance; string reportText = "Damage".Translate() + ":\n"; if (chance < 1f) { reportText += " " + "RimLoot_StatsReport_ProjectileWithChance".Translate( verbProps.defaultProjectile.Named("PROJECTILE"), chanceStr.Named("chance") ) + "\n"; reportText += string.Format(" {0}: {1} * {2} * {3} = {4}\n\n", "Damage".Translate(), baseDamage.ToStringDecimalIfSmall(), verbProps.burstShotCount, chanceStr, damage.ToStringDecimalIfSmall() ); float modChance = 1f - chance; string modChanceStr = GenText.ToStringPercent(modChance); float modBaseDamage = modProjProps.damageDef.harmsHealth == false ? 0f : modProjProps.GetDamageAmount(req.Thing) ; float modDamage = modBaseDamage * verbProps.burstShotCount * modChance; reportText += " " + "RimLoot_StatsReport_ProjectileWithChance".Translate( modProjectile.Named("PROJECTILE"), modChanceStr.Named("chance") ) + "\n"; reportText += string.Format(" {0}: {1} * {2} * {3} = {4}\n\n", "Damage".Translate(), modBaseDamage.ToStringDecimalIfSmall(), verbProps.burstShotCount, modChanceStr, modDamage.ToStringDecimalIfSmall() ); reportText += string.Format("{0}: {1}\n\n", "StatsReport_TotalValue".Translate(), (damage + modDamage).ToStringDecimalIfSmall()); } else { reportText += " " + "RimLoot_StatsReport_Projectile".Translate(verbProps.defaultProjectile.Named("PROJECTILE")) + "\n"; reportText += string.Format(" {0}: {1} * {2} = {3}\n\n", "Damage".Translate(), baseDamage.ToStringDecimalIfSmall(), verbProps.burstShotCount, damage.ToStringDecimalIfSmall() ); } /* Seconds per attack */ float secondsSpent = 0; float cooldown = req.HasThing ? req.Thing.GetStatValue(StatDefOf.RangedWeapon_Cooldown, true) : req.StuffDef != null?def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown, req.StuffDef) : def.GetStatValueAbstract(StatDefOf.RangedWeapon_Cooldown) ; float burstShotTime = ((verbProps.burstShotCount - 1) * verbProps.ticksBetweenBurstShots).TicksToSeconds(); if (verb != null) { secondsSpent = verbProps.AdjustedFullCycleTime(verb, attacker); } else { secondsSpent = verbProps.warmupTime + cooldown + burstShotTime; } reportText += GenText.ToTitleCaseSmart("SecondsPerAttackLower".Translate()) + ":\n"; reportText += string.Format(" {0}: {1}\n", "WarmupTime".Translate(), "PeriodSeconds".Translate(verbProps.warmupTime.ToStringDecimalIfSmall())); if (burstShotTime > 0) { reportText += string.Format(" {0}: {1}\n", "BurstShotFireRate".Translate(), "PeriodSeconds".Translate(burstShotTime.ToStringDecimalIfSmall())); } reportText += string.Format(" {0}: {1}\n", "CooldownTime".Translate(), "PeriodSeconds".Translate(cooldown.ToStringDecimalIfSmall())); reportText += string.Format("{0}: {1}\n\n", "StatsReport_TotalValue".Translate(), "PeriodSeconds".Translate(secondsSpent.ToStringDecimalIfSmall())); /* Average accuracy */ // Every integer range possible as an average float wpnAccuracy = 0; float pawnAccuracy = 0; for (int i = 3; i <= verbProps.range; i++) { wpnAccuracy += verbProps.GetHitChanceFactor(req.Thing, i); if (attacker != null) { pawnAccuracy += ShotReport.HitFactorFromShooter(attacker, i); } } if (verbProps.range >= 3) { wpnAccuracy /= verbProps.range - 2; if (attacker != null) { pawnAccuracy /= verbProps.range - 2; } } reportText += "AverageAccuracy".Translate() + ":\n"; reportText += string.Format(" {0}: {1}\n", "ShootReportWeapon".Translate(), wpnAccuracy.ToStringPercent("F1")); if (pawnAccuracy > 0) { reportText += string.Format(" {0}: {1}\n", "ShootReportShooterAbility".Translate(), pawnAccuracy.ToStringPercent("F1")); } return(reportText); }