diff --git a/Diagnostics/UnfinishedBusinessBlueprints/Assets.txt b/Diagnostics/UnfinishedBusinessBlueprints/Assets.txt index dcb1bdaef7..0faeaf9f93 100644 --- a/Diagnostics/UnfinishedBusinessBlueprints/Assets.txt +++ b/Diagnostics/UnfinishedBusinessBlueprints/Assets.txt @@ -3862,6 +3862,7 @@ PowerUseModifierTacticianGambitPool00 FeatureDefinitionPowerUseModifier FeatureD PowerUseModifierTacticianGambitPool01 FeatureDefinitionPowerUseModifier FeatureDefinition 72c23326-29d2-5224-a708-c494fd573533 PowerUseModifierTacticianGambitPoolFeatTacticianAdept FeatureDefinitionPowerUseModifier FeatureDefinition b33418e8-2cd7-5940-983d-8107873b39e0 PowerUseModifierTacticianGambitPoolImproviseStrategy FeatureDefinitionPowerUseModifier FeatureDefinition 18ec73c9-8c06-5ef8-b4c9-9aba6a7e649f +PowerUseModifierTacticianGambitPoolMartialTactician FeatureDefinitionPowerUseModifier FeatureDefinition 142dbc79-602e-5057-aa01-646c3e046e07 PowerUseModifierTacticianGambitPoolRemarkableTechnique FeatureDefinitionPowerUseModifier FeatureDefinition aee88439-7ecc-5014-8c1e-52b30eb71dd4 PowerVanishSummon FeatureDefinitionPower FeatureDefinition 2e2b66aa-217d-5063-9073-32f0dca0efb1 PowerVitriolicSphere FeatureDefinitionPower FeatureDefinition 4c5d9fc8-78f1-57e9-831e-737b954d8e16 diff --git a/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/CollegeOfValiance.json b/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/CollegeOfValiance.json index b75a6fac29..070238740d 100644 --- a/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/CollegeOfValiance.json +++ b/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/CollegeOfValiance.json @@ -41,9 +41,9 @@ "description": "Subclass/&CollegeOfValianceDescription", "spriteReference": { "$type": "UnityEngine.AddressableAssets.AssetReferenceSprite, Unity.Addressables", - "m_AssetGUID": "a9f6675a067f5c74890a2acf2201e5a8", - "m_SubObjectName": "Monk_Light", - "m_SubObjectType": "UnityEngine.Sprite, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" + "m_AssetGUID": "c7be297a-7045-558e-b4d7-fda31d77a17d", + "m_SubObjectName": null, + "m_SubObjectType": null }, "color": { "$type": "UnityEngine.Color, UnityEngine.CoreModule", diff --git a/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/MartialTactician.json b/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/MartialTactician.json index d2cd458f31..630278b635 100644 --- a/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/MartialTactician.json +++ b/Diagnostics/UnfinishedBusinessBlueprints/CharacterSubclassDefinition/MartialTactician.json @@ -1,6 +1,11 @@ { "$type": "CharacterSubclassDefinition, Assembly-CSharp", "featureUnlocks": [ + { + "$type": "FeatureUnlockByLevel, Assembly-CSharp", + "featureDefinition": "Definition:PowerUseModifierTacticianGambitPoolMartialTactician:142dbc79-602e-5057-aa01-646c3e046e07", + "level": 3 + }, { "$type": "FeatureUnlockByLevel, Assembly-CSharp", "featureDefinition": "Definition:InvocationPoolGambitLearn4:bf787818-378a-5206-b5ea-3669089af63d", diff --git a/Diagnostics/UnfinishedBusinessBlueprints/ConditionDefinition/ConditionReverseGravity.json b/Diagnostics/UnfinishedBusinessBlueprints/ConditionDefinition/ConditionReverseGravity.json index fb0516cf7c..ab7e12a9d6 100644 --- a/Diagnostics/UnfinishedBusinessBlueprints/ConditionDefinition/ConditionReverseGravity.json +++ b/Diagnostics/UnfinishedBusinessBlueprints/ConditionDefinition/ConditionReverseGravity.json @@ -3,9 +3,7 @@ "inDungeonEditor": false, "parentCondition": "Definition:ConditionFlying:ec82fae48f9a55a41a49fd96d93b49b5", "conditionType": "Neutral", - "features": [ - "Definition:MoveModeFly2:0196408f7a256674d8272106dfa74a0a" - ], + "features": [], "allowMultipleInstances": false, "silentWhenAdded": false, "silentWhenRemoved": false, diff --git a/Diagnostics/UnfinishedBusinessBlueprints/FeatureDefinitionPower/PowerSorcerousPsionMindOverMatter.json b/Diagnostics/UnfinishedBusinessBlueprints/FeatureDefinitionPower/PowerSorcerousPsionMindOverMatter.json index 6f11403add..f05390263f 100644 --- a/Diagnostics/UnfinishedBusinessBlueprints/FeatureDefinitionPower/PowerSorcerousPsionMindOverMatter.json +++ b/Diagnostics/UnfinishedBusinessBlueprints/FeatureDefinitionPower/PowerSorcerousPsionMindOverMatter.json @@ -113,12 +113,22 @@ "$type": "EffectParticleParameters, Assembly-CSharp", "casterParticleReference": { "$type": "UnityEngine.AddressableAssets.AssetReference, Unity.Addressables", - "m_AssetGUID": "a3ab6be56385381458d6c6e9ed5e828b", + "m_AssetGUID": "de56dbcb8279e824d876e88bb3601378", + "m_SubObjectName": "", + "m_SubObjectType": "" + }, + "casterSelfParticleReference": { + "$type": "UnityEngine.AddressableAssets.AssetReference, Unity.Addressables", + "m_AssetGUID": "", + "m_SubObjectName": "", + "m_SubObjectType": "" + }, + "casterQuickSpellParticleReference": { + "$type": "UnityEngine.AddressableAssets.AssetReference, Unity.Addressables", + "m_AssetGUID": "", "m_SubObjectName": "", "m_SubObjectType": "" }, - "casterSelfParticleReference": null, - "casterQuickSpellParticleReference": null, "targetParticleReference": { "$type": "UnityEngine.AddressableAssets.AssetReference, Unity.Addressables", "m_AssetGUID": "", diff --git a/Diagnostics/UnfinishedBusinessBlueprints/FeatureDefinitionPowerUseModifier/PowerUseModifierTacticianGambitPoolMartialTactician.json b/Diagnostics/UnfinishedBusinessBlueprints/FeatureDefinitionPowerUseModifier/PowerUseModifierTacticianGambitPoolMartialTactician.json new file mode 100644 index 0000000000..c9e66e0d0d --- /dev/null +++ b/Diagnostics/UnfinishedBusinessBlueprints/FeatureDefinitionPowerUseModifier/PowerUseModifierTacticianGambitPoolMartialTactician.json @@ -0,0 +1,30 @@ +{ + "$type": "FeatureDefinitionPowerUseModifier, SolastaUnfinishedBusiness", + "guiPresentation": { + "$type": "GuiPresentation, Assembly-CSharp", + "hidden": false, + "title": "Feature/&PowerUseModifierTacticianGambitPoolTitle", + "description": "Feature/&PowerUseModifierTacticianGambitPoolDescription", + "spriteReference": { + "$type": "UnityEngine.AddressableAssets.AssetReferenceSprite, Unity.Addressables", + "m_AssetGUID": "", + "m_SubObjectName": null, + "m_SubObjectType": null + }, + "color": { + "$type": "UnityEngine.Color, UnityEngine.CoreModule", + "r": 1.0, + "g": 1.0, + "b": 1.0, + "a": 1.0 + }, + "symbolChar": "221E", + "sortOrder": 0, + "unusedInSolastaCOTM": false, + "usedInValleyDLC": false + }, + "contentCopyright": "UserContent", + "guid": "142dbc79-602e-5057-aa01-646c3e046e07", + "contentPack": 9999, + "name": "PowerUseModifierTacticianGambitPoolMartialTactician" +} \ No newline at end of file diff --git a/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs b/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs index 4ffc3c4432..1a5dda7ef7 100644 --- a/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs +++ b/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs @@ -29,7 +29,6 @@ internal static class ActionDefinitions internal static ActionDefinition ActionSurge { get; } = GetDefinition("ActionSurge"); internal static ActionDefinition CastInvocation { get; } = GetDefinition("CastInvocation"); internal static ActionDefinition DashBonus { get; } = GetDefinition("DashBonus"); - internal static ActionDefinition DisengageMain { get; } = GetDefinition("DisengageMain"); internal static ActionDefinition SpiritRage { get; } = GetDefinition("SpiritRage"); internal static ActionDefinition GrantBardicInspiration { get; } = @@ -1037,9 +1036,6 @@ internal static class FeatureDefinitionAdditionalDamages internal static FeatureDefinitionAdditionalDamage AdditionalDamageRogueSneakAttack { get; } = GetDefinition("AdditionalDamageRogueSneakAttack"); - internal static FeatureDefinitionAdditionalDamage AdditionalDamageRoguishHoodlumNonFinesseSneakAttack { get; } = - GetDefinition("AdditionalDamageRoguishHoodlumNonFinesseSneakAttack"); - internal static FeatureDefinitionAdditionalDamage AdditionalDamageTraditionLightRadiantStrikesLuminousKi { get; } = GetDefinition("AdditionalDamageTraditionLightRadiantStrikesLuminousKi"); @@ -2018,9 +2014,6 @@ internal static class FeatureDefinitionPowers internal static FeatureDefinitionPower PowerPaladinNeutralizePoison { get; } = GetDefinition("PowerPaladinNeutralizePoison"); - internal static FeatureDefinitionPower PowerPatronFiendDarkOnesBlessing { get; } = - GetDefinition("PowerPatronFiendDarkOnesBlessing"); - internal static FeatureDefinitionPower PowerPatronFiendDarkOnesOwnLuck { get; } = GetDefinition("PowerPatronFiendDarkOnesOwnLuck"); @@ -2102,9 +2095,6 @@ internal static class FeatureDefinitionPowers internal static FeatureDefinitionPower PowerSpiderQueenPoisonCloud { get; } = GetDefinition("PowerSpiderQueenPoisonCloud"); - internal static FeatureDefinitionPower PowerSymbolOfSleep { get; } = - GetDefinition("PowerSymbolOfSleep"); - internal static FeatureDefinitionPower PowerTraditionCourtMageSpellShield { get; } = GetDefinition("PowerTraditionCourtMageSpellShield"); diff --git a/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs b/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs index f3b3bcfc2e..9c07bf7bd5 100644 --- a/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs +++ b/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs @@ -79,7 +79,7 @@ internal static (RulesetAttackMode mode, ActionModifier modifier) GetFirstMeleeM { foreach (var mode in instance.RulesetCharacter.AttackModes) { - if (!ValidatorsWeapon.IsMelee(mode)) + if (!ValidatorsWeapon.IsMelee(mode.SourceObject as RulesetItem)) { continue; } diff --git a/SolastaUnfinishedBusiness/ChangelogHistory.txt b/SolastaUnfinishedBusiness/ChangelogHistory.txt index 566f5b30cb..e459a08973 100644 --- a/SolastaUnfinishedBusiness/ChangelogHistory.txt +++ b/SolastaUnfinishedBusiness/ChangelogHistory.txt @@ -1,3 +1,34 @@ +1.5.97.19: + +all fixes marked with * were introduced with the IsMelee change on v18 + +- added subclasses unique sprites [by otearaisu] +- fixed Martial Arcane Archer arcane shots weapon type validator +- fixed Martial Battle Master missing maneuvers initial pool amount +- fixed Martial Psi Warrior force of will saving throw behavior +- fixed Martial Psi Warrior psionic adept once in my turn validator +- fixed monster reaction powers effect forms application +- fixed Powerful metamagic interaction with Sunbeam spell +- fixed Reverse Gravity spell granting flying to creatures +- fixed Shapechange spell to delegate concentration to shape +* fixed Exploiter feat attack mode validator +* fixed GWM feat attack mode modifier, and additional attack validator +* fixed Lunging Attack maneuver increase weapon reach validator +* fixed Mage Slayer feat attack mode validator +* fixed Martial Warlord coordinated assault attack mode validator +* fixed Oath of Dread harrowing crusade attack mode validator +* fixed Old Tactics feat attack mode validator +* fixed Path of the Wild Magic wild surge attack mode validator +* fixed Power Attack feat attack mode modifier +* fixed Retaliate maneuver attack mode validator +* fixed Wendigo natural lunger increase weapon reach validator +* fixed Wyrmkin reactive retribution attack mode validator + +KNOWN ISSUES: + +- Artillerist Force Ballista tiny cannon doesn't force attack DIS within 5 ft +- Chaos Bolt damage will be of wrong type under multiplayer if twinned and any bolt misses + 1.5.97.18: - added Circle of the Wildfire, and Vitriolic Sphere spell @@ -24,11 +55,6 @@ - fixed Rescue the Dying spell triggering logic - fixed Skill Empowerment spell suggested list -KNOWN ISSUES: - -- Artillerist Force Ballista tiny cannon doesn't force attack DIS within 5 ft -- Chaos Bolt damage will be of wrong type under multiplayer if twinned and any bolt misses [cannot fix] - 1.5.97.17: - fixed 'After was attacked' condition interruption also triggering on non attack roll magic effects diff --git a/SolastaUnfinishedBusiness/Displays/CreditsDisplay.cs b/SolastaUnfinishedBusiness/Displays/CreditsDisplay.cs index e07644d76a..916447f5f0 100644 --- a/SolastaUnfinishedBusiness/Displays/CreditsDisplay.cs +++ b/SolastaUnfinishedBusiness/Displays/CreditsDisplay.cs @@ -58,7 +58,7 @@ internal static class CreditsDisplay "feats, fighting styles, items & crafting, Martial Eldritch Knight, Wizard Arcane Fighter, Wizard Spellmaster"), ("Otearaisu", - "Aasimar, Goliath, Imp, Lizardfolk, Oni, Warforged, Wendigo, Wildling, Wyrmkin, Path of the Battlerager, Path of the Beast, Path of the Wild Magic"), + "sprites, Aasimar, Goliath, Imp, Lizardfolk, Oni, Warforged, Wendigo, Wildling, Wyrmkin, Path of the Battlerager, Path of the Beast, Path of the Wild Magic"), ("Haxermn", "spells, Domain Defiler, Domain Forge, Oath of Ancients, Oath of Hatred, Way of Dragon"), ("SilverGriffon", "gameplay, visuals, spells, Dark Elf, Grey Dwarf, Kobold, Sorcerous Divine Soul"), ("tivie", "Circle of the Moon, Path of the Totem Warrior"), diff --git a/SolastaUnfinishedBusiness/Feats/ArmorFeats.cs b/SolastaUnfinishedBusiness/Feats/ArmorFeats.cs index b314ff1869..e4c1bfa42f 100644 --- a/SolastaUnfinishedBusiness/Feats/ArmorFeats.cs +++ b/SolastaUnfinishedBusiness/Feats/ArmorFeats.cs @@ -17,8 +17,6 @@ namespace SolastaUnfinishedBusiness.Feats; internal static class ArmorFeats { - internal const string ConditionShieldTechniquesResistanceName = "ConditionShieldTechniquesResistance"; - // this is entirely implemented on rulesetCharacterHero transpiler using context validations below // they change max dexterity to 3 and remove any instance of Stealth Disadvantage checks private static readonly FeatDefinition FeatMediumArmorMaster = FeatDefinitionBuilder diff --git a/SolastaUnfinishedBusiness/Feats/MeleeCombatFeats.cs b/SolastaUnfinishedBusiness/Feats/MeleeCombatFeats.cs index c56491480a..f9f391e1a8 100644 --- a/SolastaUnfinishedBusiness/Feats/MeleeCombatFeats.cs +++ b/SolastaUnfinishedBusiness/Feats/MeleeCombatFeats.cs @@ -1281,9 +1281,7 @@ private void InflictCondition(RulesetCharacter rulesetCharacter) } } - private sealed class ModifyWeaponAttackModeFeatCleavingAttack( - // ReSharper disable once SuggestBaseTypeForParameterInConstructor - FeatDefinition featDefinition) + private sealed class ModifyWeaponAttackModeFeatCleavingAttack(FeatDefinition featDefinition) : IModifyWeaponAttackMode { private const int ToHit = -5; @@ -1314,7 +1312,8 @@ public void ModifyAttackMode(RulesetCharacter character, RulesetAttackMode attac private static bool ValidateCleavingAttack(RulesetAttackMode attackMode, bool validateHeavy = false) { - return ValidatorsWeapon.IsMelee(attackMode) && + // don't use IsMelee(attackMode) in IModifyWeaponAttackMode as it will always fail + return ValidatorsWeapon.IsMelee(attackMode.SourceObject as RulesetItem) && (!validateHeavy || ValidatorsWeapon.HasAnyWeaponTag( attackMode.SourceDefinition as ItemDefinition, TagsDefinitions.WeaponTagHeavy)); @@ -1599,22 +1598,11 @@ private static FeatDefinition BuildFellHanded() return feat; } - private sealed class PhysicalAttackFinishedByMeFeatFellHanded : IPhysicalAttackFinishedByMe + private sealed class PhysicalAttackFinishedByMeFeatFellHanded( + FeatureDefinitionPower powerAdvantage, + FeatureDefinitionPower powerDisadvantage, + params WeaponTypeDefinition[] weaponTypeDefinition) : IPhysicalAttackFinishedByMe { - private readonly FeatureDefinitionPower _powerAdvantage; - private readonly FeatureDefinitionPower _powerDisadvantage; - private readonly List _weaponTypeDefinition = []; - - public PhysicalAttackFinishedByMeFeatFellHanded( - FeatureDefinitionPower powerAdvantage, - FeatureDefinitionPower powerDisadvantage, - params WeaponTypeDefinition[] weaponTypeDefinition) - { - _powerAdvantage = powerAdvantage; - _powerDisadvantage = powerDisadvantage; - _weaponTypeDefinition.AddRange(weaponTypeDefinition); - } - public IEnumerator OnPhysicalAttackFinishedByMe( GameLocationBattleManager battleManager, CharacterAction action, @@ -1625,7 +1613,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( int damageAmount) { if (attackMode?.sourceDefinition is not ItemDefinition { IsWeapon: true } sourceDefinition || - !_weaponTypeDefinition.Contains(sourceDefinition.WeaponDescription.WeaponTypeDefinition)) + weaponTypeDefinition.Contains(sourceDefinition.WeaponDescription.WeaponTypeDefinition)) { yield break; } @@ -1653,12 +1641,12 @@ public IEnumerator OnPhysicalAttackFinishedByMe( { case AdvantageType.Advantage when rollOutcome is RollOutcome.Success or RollOutcome.CriticalSuccess: attacker.UsedSpecialFeatures.TryGetValue("LowestAttackRoll", out attackRoll); - power = _powerAdvantage; + power = powerAdvantage; break; case AdvantageType.Disadvantage when rollOutcome is RollOutcome.Failure or RollOutcome.CriticalFailure: attacker.UsedSpecialFeatures.TryGetValue("HighestAttackRoll", out attackRoll); - power = _powerDisadvantage; + power = powerDisadvantage; break; case AdvantageType.None: @@ -1676,7 +1664,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( Gui.Game.GameConsole.AttackRolled( rulesetAttacker, rulesetDefender, - _powerAdvantage, + powerAdvantage, outcome, attackRoll + modifier, attackRoll, @@ -1857,17 +1845,15 @@ private static FeatDefinition BuildPowerAttack() return featPowerAttack; } - private sealed class ModifyWeaponAttackModeFeatPowerAttack( - // ReSharper disable once SuggestBaseTypeForParameterInConstructor - FeatDefinition featDefinition) : IModifyWeaponAttackMode - // thrown is allowed on power attack - //, IPhysicalAttackInitiatedByMe + private sealed class ModifyWeaponAttackModeFeatPowerAttack(FeatDefinition featDefinition) : IModifyWeaponAttackMode { private const int ToHit = 3; public void ModifyAttackMode(RulesetCharacter character, RulesetAttackMode attackMode) { - if (!ValidatorsWeapon.IsMelee(attackMode) && !ValidatorsWeapon.IsUnarmed(attackMode)) + // don't use IsMelee(attackMode) in IModifyWeaponAttackMode as it will always fail + if (!ValidatorsWeapon.IsMelee(attackMode.SourceObject as RulesetItem) && + !ValidatorsWeapon.IsUnarmed(attackMode)) { return; } @@ -1890,45 +1876,6 @@ public void ModifyAttackMode(RulesetCharacter character, RulesetAttackMode attac damage.DamageBonusTrends.Add(new TrendInfo(toDamage, FeatureSourceType.Feat, featDefinition.Name, featDefinition)); } - -// thrown is allowed on power attack -#if false - // this is required to handle thrown scenarios - public IEnumerator OnPhysicalAttackInitiatedByMe( - GameLocationBattleManager __instance, - CharacterAction action, - GameLocationCharacter attacker, - GameLocationCharacter defender, - ActionModifier attackModifier, - RulesetAttackMode attackMode) - { - var isMelee = ValidatorsWeapon.IsMelee(attackMode); - var isUnarmed = ValidatorsWeapon.IsUnarmed(attackMode); - var isPowerAttackValid = isMelee || isUnarmed; - - if (isPowerAttackValid) - { - yield break; - } - - attackModifier.AttacktoHitTrends.RemoveAll(x => x.sourceName == _featDefinition.Name); - attackMode.ToHitBonusTrends.RemoveAll(x => x.sourceName == _featDefinition.Name); - attackMode.ToHitBonus += ToHit; - - var damageForm = attackMode.EffectDescription.FindFirstDamageForm(); - - if (damageForm == null) - { - yield break; - } - - var proficiency = attacker.RulesetCharacter.TryGetAttributeValue(AttributeDefinitions.ProficiencyBonus); - var toDamage = ToHit + proficiency; - - damageForm.DamageBonusTrends.RemoveAll(x => x.sourceName == _featDefinition.Name); - damageForm.BonusDamage -= toDamage; - } -#endif } #endregion @@ -1994,22 +1941,11 @@ private static FeatDefinition BuildSlasherStr() .AddToDB(); } - private sealed class PhysicalAttackAfterDamageFeatSlasher : IPhysicalAttackFinishedByMe + private sealed class PhysicalAttackAfterDamageFeatSlasher( + ConditionDefinition conditionDefinition, + ConditionDefinition criticalConditionDefinition, + string damageType) : IPhysicalAttackFinishedByMe { - private readonly ConditionDefinition _conditionDefinition; - private readonly ConditionDefinition _criticalConditionDefinition; - private readonly string _damageType; - - internal PhysicalAttackAfterDamageFeatSlasher( - ConditionDefinition conditionDefinition, - ConditionDefinition criticalConditionDefinition, - string damageType) - { - _conditionDefinition = conditionDefinition; - _criticalConditionDefinition = criticalConditionDefinition; - _damageType = damageType; - } - public IEnumerator OnPhysicalAttackFinishedByMe( GameLocationBattleManager battleManager, CharacterAction action, @@ -2021,7 +1957,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( { var damage = attackMode?.EffectDescription?.FindFirstDamageForm(); - if (damage == null || damage.DamageType != _damageType) + if (damage == null || damage.DamageType != damageType) { yield break; } @@ -2038,7 +1974,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( if (rollOutcome is RollOutcome.Success or RollOutcome.CriticalSuccess) { rulesetDefender.InflictCondition( - _conditionDefinition.Name, + conditionDefinition.Name, DurationType.Round, 0, TurnOccurenceType.EndOfTurn, @@ -2046,7 +1982,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( rulesetAttacker.guid, rulesetAttacker.CurrentFaction.Name, 1, - _conditionDefinition.Name, + conditionDefinition.Name, 0, 0, 0); @@ -2058,7 +1994,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( } rulesetDefender.InflictCondition( - _criticalConditionDefinition.Name, + criticalConditionDefinition.Name, DurationType.Round, 0, TurnOccurenceType.EndOfTurn, @@ -2066,7 +2002,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( rulesetAttacker.guid, rulesetAttacker.CurrentFaction.Name, 1, - _criticalConditionDefinition.Name, + criticalConditionDefinition.Name, 0, 0, 0); diff --git a/SolastaUnfinishedBusiness/Feats/OtherFeats.cs b/SolastaUnfinishedBusiness/Feats/OtherFeats.cs index f0cd0492c9..dd41ae12fa 100644 --- a/SolastaUnfinishedBusiness/Feats/OtherFeats.cs +++ b/SolastaUnfinishedBusiness/Feats/OtherFeats.cs @@ -192,7 +192,11 @@ private static FeatDefinitionWithPrerequisites BuildArcaneArcherAdept() .SetFeatures( MartialArcaneArcher.PowerArcaneShot, MartialArcaneArcher.InvocationPoolArcaneShotChoice2, - MartialArcaneArcher.ModifyPowerArcaneShotAdditionalUse1, + FeatureDefinitionPowerUseModifierBuilder + .Create("PowerUseModifierMartialArcaneArcherArcaneShotUse1") + .SetGuiPresentation(Category.Feature) + .SetFixedValue(MartialArcaneArcher.PowerArcaneShot, 1) + .AddToDB(), MartialArcaneArcher.ActionAffinityArcaneArcherToggle) .SetValidators(ValidatorsFeat.IsLevel4) .AddToDB(); @@ -1412,26 +1416,20 @@ private static FeatDefinition BuildElementalAdept(List feats) return elementalAdeptGroup; } - private sealed class ModifyDamageResistanceElementalAdept : IModifyDamageAffinity, IValidateDieRollModifier + private sealed class ModifyDamageResistanceElementalAdept(params string[] damageTypes) + : IModifyDamageAffinity, IValidateDieRollModifier { - private readonly List _damageTypes = []; - - public ModifyDamageResistanceElementalAdept(params string[] damageTypes) - { - _damageTypes.AddRange(damageTypes); - } - public void ModifyDamageAffinity(RulesetActor attacker, RulesetActor defender, List features) { features.RemoveAll(x => x is IDamageAffinityProvider { DamageAffinityType: DamageAffinityType.Resistance } y && - _damageTypes.Contains(y.DamageType)); + damageTypes.Contains(y.DamageType)); } - public bool CanModifyRoll(RulesetCharacter character, List features, - List damageTypes) + public bool CanModifyRoll( + RulesetCharacter character, List features, List damageTypesRoll) { - return _damageTypes.Intersect(damageTypes).Any(); + return damageTypes.Intersect(damageTypesRoll).Any(); } } @@ -1492,26 +1490,20 @@ private static FeatDefinition BuildElementalMaster(List feats) return elementalAdeptGroup; } - private sealed class ModifyDamageResistanceElementalMaster : IModifyDamageAffinity, IValidateDieRollModifier + private sealed class ModifyDamageResistanceElementalMaster(params string[] damageTypes) + : IModifyDamageAffinity, IValidateDieRollModifier { - private readonly List _damageTypes = []; - - public ModifyDamageResistanceElementalMaster(params string[] damageTypes) - { - _damageTypes.AddRange(damageTypes); - } - public void ModifyDamageAffinity(RulesetActor defender, RulesetActor attacker, List features) { features.RemoveAll(x => x is IDamageAffinityProvider { DamageAffinityType: DamageAffinityType.Immunity } y && - _damageTypes.Contains(y.DamageType)); + damageTypes.Contains(y.DamageType)); } - public bool CanModifyRoll(RulesetCharacter character, List features, - List damageTypes) + public bool CanModifyRoll( + RulesetCharacter character, List features, List damageTypesRoll) { - return _damageTypes.Intersect(damageTypes).Any(); + return damageTypes.Intersect(damageTypesRoll).Any(); } } @@ -1955,13 +1947,12 @@ private sealed class CustomBehaviorLucky( FeatureDefinitionPower powerLucky) : ITryAlterOutcomeAttack, ITryAlterOutcomeAttributeCheck, ITryAlterOutcomeSavingThrow, IRollSavingThrowFinished { - private int _modifier; - - private int _saveDC; + private const string LuckyModifierTag = "LuckyModifierTag"; + private const string LuckySaveTag = "LuckySaveTag"; public void OnSavingThrowFinished( - RulesetCharacter caster, - RulesetCharacter defender, + RulesetCharacter rulesetCaster, + RulesetCharacter rulesetDefender, int saveBonus, string abilityScoreName, BaseDefinition sourceDefinition, @@ -1974,8 +1965,12 @@ public void OnSavingThrowFinished( ref int outcomeDelta, List effectForms) { - _saveDC = saveDC; - _modifier = saveBonus + rollModifier; + var caster = GameLocationCharacter.GetFromActor(rulesetCaster); + + caster.UsedSpecialFeatures.TryAdd(LuckyModifierTag, 0); + caster.UsedSpecialFeatures.TryAdd(LuckySaveTag, 0); + caster.UsedSpecialFeatures[LuckyModifierTag] = saveBonus + rollModifier; + caster.UsedSpecialFeatures[LuckySaveTag] = saveDC; } public int HandlerPriority => -10; @@ -2187,7 +2182,9 @@ public IEnumerator OnTryAlterOutcomeSavingThrow( if (!action.RolledSaveThrow || action.SaveOutcome != RollOutcome.Failure || helper != defender || - rulesetHelper.GetRemainingUsesOfPower(usablePower) == 0) + rulesetHelper.GetRemainingUsesOfPower(usablePower) == 0 || + !defender.UsedSpecialFeatures.TryGetValue(LuckyModifierTag, out var modifier) || + !defender.UsedSpecialFeatures.TryGetValue(LuckySaveTag, out var saveDC)) { yield break; } @@ -2220,7 +2217,7 @@ public IEnumerator OnTryAlterOutcomeSavingThrow( } var dieRoll = rulesetHelper.RollDie(DieType.D20, RollContext.None, false, AdvantageType.None, out _, out _); - var savingRoll = action.SaveOutcomeDelta - _modifier + _saveDC; + var savingRoll = action.SaveOutcomeDelta - modifier + saveDC; if (dieRoll <= savingRoll) { diff --git a/SolastaUnfinishedBusiness/Feats/RaceFeats.cs b/SolastaUnfinishedBusiness/Feats/RaceFeats.cs index 3ce4ad71ec..e0e897aa50 100644 --- a/SolastaUnfinishedBusiness/Feats/RaceFeats.cs +++ b/SolastaUnfinishedBusiness/Feats/RaceFeats.cs @@ -619,12 +619,12 @@ private sealed class CustomBehaviorBountifulLuck( ConditionDefinition conditionBountifulLuck) : ITryAlterOutcomeAttack, ITryAlterOutcomeAttributeCheck, ITryAlterOutcomeSavingThrow, IRollSavingThrowFinished { - private int _modifier; - private int _saveDC; + private const string BountifulLuckModifierTag = "BountifulLuckModifierTag"; + private const string BountifulLuckSaveTag = "BountifulLuckSaveTag"; public void OnSavingThrowFinished( - RulesetCharacter caster, - RulesetCharacter defender, + RulesetCharacter rulesetCaster, + RulesetCharacter rulesetDefender, int saveBonus, string abilityScoreName, BaseDefinition sourceDefinition, @@ -637,8 +637,12 @@ public void OnSavingThrowFinished( ref int outcomeDelta, List effectForms) { - _saveDC = saveDC; - _modifier = saveBonus + rollModifier; + var caster = GameLocationCharacter.GetFromActor(rulesetCaster); + + caster.UsedSpecialFeatures.TryAdd(BountifulLuckModifierTag, 0); + caster.UsedSpecialFeatures.TryAdd(BountifulLuckSaveTag, 0); + caster.UsedSpecialFeatures[BountifulLuckModifierTag] = saveBonus + rollModifier; + caster.UsedSpecialFeatures[BountifulLuckSaveTag] = saveDC; } public int HandlerPriority => -10; @@ -855,7 +859,9 @@ public IEnumerator OnTryAlterOutcomeSavingThrow( helper.IsOppositeSide(defender.Side) || !helper.CanReact() || !helper.IsWithinRange(defender, 6) || - !helper.CanPerceiveTarget(defender)) + !helper.CanPerceiveTarget(defender) || + !defender.UsedSpecialFeatures.TryGetValue(BountifulLuckModifierTag, out var modifier) || + !defender.UsedSpecialFeatures.TryGetValue(BountifulLuckSaveTag, out var saveDC)) { yield break; } @@ -884,7 +890,7 @@ public IEnumerator OnTryAlterOutcomeSavingThrow( var rulesetHelper = helper.RulesetCharacter; var dieRoll = rulesetHelper.RollDie(DieType.D20, RollContext.None, false, AdvantageType.None, out _, out _); - var savingRoll = action.SaveOutcomeDelta - _modifier + _saveDC; + var savingRoll = action.SaveOutcomeDelta - modifier + saveDC; if (dieRoll <= savingRoll) { @@ -1638,8 +1644,7 @@ private static FeatDefinition BuildOrcishFury(List feats) } private sealed class CustomBehaviorOrcishFury( - FeatureDefinitionPower power, - // ReSharper disable once SuggestBaseTypeForParameterInConstructor + FeatureDefinitionPower powerOrcishFury, ConditionDefinition conditionDefinition) : IPhysicalAttackBeforeHitConfirmedOnEnemy, IPhysicalAttackFinishedByMe, IPhysicalAttackBeforeHitConfirmedOnMe, IMagicEffectBeforeHitConfirmedOnMe, @@ -1664,7 +1669,7 @@ public IEnumerator OnPhysicalAttackBeforeHitConfirmedOnEnemy( if (!ValidatorsWeapon.IsOfWeaponType(CustomSituationalContext.SimpleOrMartialWeapons) (attackMode, null, null) || !rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.OrcishFuryToggle) || - rulesetAttacker.GetRemainingPowerUses(power) == 0) + rulesetAttacker.GetRemainingPowerUses(powerOrcishFury) == 0) { yield break; } @@ -1703,12 +1708,12 @@ public IEnumerator OnPhysicalAttackFinishedByMe( if (!ValidatorsWeapon.IsOfWeaponType(CustomSituationalContext.SimpleOrMartialWeapons) (attackMode, null, null) || !rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.OrcishFuryToggle) || - rulesetAttacker.GetRemainingPowerUses(power) == 0) + rulesetAttacker.GetRemainingPowerUses(powerOrcishFury) == 0) { yield break; } - var usablePower = PowerProvider.Get(power, rulesetAttacker); + var usablePower = PowerProvider.Get(powerOrcishFury, rulesetAttacker); rulesetAttacker.UsePower(usablePower); } @@ -1717,8 +1722,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( #region Knock-Out - private bool _isValid; - private bool _knockOutPrevented; + private readonly string _knockOutPreventedTag = powerOrcishFury.Name + "KnockoutPrevented"; public IEnumerator OnMagicEffectBeforeHitConfirmedOnMe( GameLocationBattleManager battleManager, @@ -1733,8 +1737,10 @@ public IEnumerator OnMagicEffectBeforeHitConfirmedOnMe( var rulesetDefender = defender.RulesetCharacter; rulesetDefender.KnockOutPrevented += KnockOutPreventedHandler; - _isValid = true; - _knockOutPrevented = false; + attacker.UsedSpecialFeatures.TryAdd(powerOrcishFury.Name, 0); + attacker.UsedSpecialFeatures[powerOrcishFury.Name] = 1; + attacker.UsedSpecialFeatures.TryAdd(_knockOutPreventedTag, 0); + attacker.UsedSpecialFeatures[_knockOutPreventedTag] = 0; yield break; } @@ -1765,8 +1771,10 @@ public IEnumerator OnPhysicalAttackBeforeHitConfirmedOnMe( var rulesetDefender = defender.RulesetCharacter; rulesetDefender.KnockOutPrevented += KnockOutPreventedHandler; - _isValid = true; - _knockOutPrevented = false; + attacker.UsedSpecialFeatures.TryAdd(powerOrcishFury.Name, 0); + attacker.UsedSpecialFeatures[powerOrcishFury.Name] = 1; + attacker.UsedSpecialFeatures.TryAdd(_knockOutPreventedTag, 0); + attacker.UsedSpecialFeatures[_knockOutPreventedTag] = 0; yield break; } @@ -1785,30 +1793,38 @@ public IEnumerator OnPhysicalAttackFinishedOnMe( yield break; } - private void KnockOutPreventedHandler(RulesetCharacter character, BaseDefinition source) + private void KnockOutPreventedHandler(RulesetCharacter rulesetCharacter, BaseDefinition source) { - _knockOutPrevented = source == DamageAffinityHalfOrcRelentlessEndurance; + var character = GameLocationCharacter.GetFromActor(rulesetCharacter); + + character.UsedSpecialFeatures.TryAdd(_knockOutPreventedTag, 0); + character.UsedSpecialFeatures[_knockOutPreventedTag] = + source == DamageAffinityHalfOrcRelentlessEndurance ? 1 : 0; } - private void HandleKnockOutBehavior(CharacterAction characterAction, GameLocationCharacter target) + private void HandleKnockOutBehavior(CharacterAction action, GameLocationCharacter target) { - if (!_isValid) + var actingCharacter = action.ActingCharacter; + + if (!actingCharacter.UsedSpecialFeatures.TryGetValue(powerOrcishFury.Name, out var value) || + value == 0) { return; } - _isValid = false; + actingCharacter.UsedSpecialFeatures.TryAdd(powerOrcishFury.Name, 0); var rulesetTarget = target.RulesetCharacter; rulesetTarget.KnockOutPrevented -= KnockOutPreventedHandler; - if (!_knockOutPrevented) + if (!actingCharacter.UsedSpecialFeatures.TryGetValue(_knockOutPreventedTag, out var knockOutPrevented) || + knockOutPrevented == 0) { return; } - _knockOutPrevented = false; + actingCharacter.UsedSpecialFeatures.TryAdd(_knockOutPreventedTag, 0); if (Gui.Battle == null || !Gui.Battle.InitiativeRollFinished) @@ -1832,11 +1848,11 @@ private void HandleKnockOutBehavior(CharacterAction characterAction, GameLocatio new CharacterActionParams(target, ActionDefinitions.Id.AttackOpportunity) { AttackMode = attackModeCopy, - TargetCharacters = { characterAction.ActingCharacter }, + TargetCharacters = { action.ActingCharacter }, ActionModifiers = { new ActionModifier() } }; - rulesetTarget.LogCharacterUsedPower(power); + rulesetTarget.LogCharacterUsedPower(powerOrcishFury); actionService.ExecuteAction(attackActionParams, null, true); } diff --git a/SolastaUnfinishedBusiness/Feats/_FeatHelpers.cs b/SolastaUnfinishedBusiness/Feats/_FeatHelpers.cs index aa23080c9f..8b67bfe7e7 100644 --- a/SolastaUnfinishedBusiness/Feats/_FeatHelpers.cs +++ b/SolastaUnfinishedBusiness/Feats/_FeatHelpers.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using SolastaUnfinishedBusiness.Interfaces; using static RuleDefinitions; @@ -7,29 +7,20 @@ namespace SolastaUnfinishedBusiness.Feats; internal static class FeatHelpers { - internal sealed class ModifyWeaponAttackModeTypeFilter : IModifyWeaponAttackMode + internal sealed class ModifyWeaponAttackModeTypeFilter( + FeatDefinition source, + params WeaponTypeDefinition[] weaponTypeDefinition) : IModifyWeaponAttackMode { - private readonly FeatDefinition _source; - private readonly List _weaponTypeDefinition = []; - - public ModifyWeaponAttackModeTypeFilter( - FeatDefinition source, - params WeaponTypeDefinition[] weaponTypeDefinition) - { - _source = source; - _weaponTypeDefinition.AddRange(weaponTypeDefinition); - } - public void ModifyAttackMode(RulesetCharacter character, [CanBeNull] RulesetAttackMode attackMode) { if (attackMode?.sourceDefinition is not ItemDefinition { IsWeapon: true } sourceDefinition || - !_weaponTypeDefinition.Contains(sourceDefinition.WeaponDescription.WeaponTypeDefinition)) + !weaponTypeDefinition.Contains(sourceDefinition.WeaponDescription.WeaponTypeDefinition)) { return; } attackMode.ToHitBonus += 1; - attackMode.ToHitBonusTrends.Add(new TrendInfo(1, FeatureSourceType.Feat, _source.Name, _source)); + attackMode.ToHitBonusTrends.Add(new TrendInfo(1, FeatureSourceType.Feat, source.Name, source)); } } diff --git a/SolastaUnfinishedBusiness/FightingStyles/Merciless.cs b/SolastaUnfinishedBusiness/FightingStyles/Merciless.cs index d8f096f60a..608b373211 100644 --- a/SolastaUnfinishedBusiness/FightingStyles/Merciless.cs +++ b/SolastaUnfinishedBusiness/FightingStyles/Merciless.cs @@ -68,10 +68,9 @@ internal sealed class Merciless : AbstractFightingStyle FightingStyleRanger ]; - private sealed class OnReducedToZeroHpByMeMerciless : IOnReducedToZeroHpByMe, IPhysicalAttackFinishedByMe + private sealed class OnReducedToZeroHpByMeMerciless + : IOnReducedToZeroHpByMe, IPhysicalAttackBeforeHitConfirmedOnEnemy, IPhysicalAttackFinishedByMe { - private bool _criticalHit; - public IEnumerator HandleReducedToZeroHpByMe( GameLocationCharacter attacker, GameLocationCharacter downedCreature, @@ -90,7 +89,9 @@ public IEnumerator HandleReducedToZeroHpByMe( var rulesetAttacker = attacker.RulesetCharacter; var proficiencyBonus = rulesetAttacker.TryGetAttributeValue(AttributeDefinitions.ProficiencyBonus); - var distance = _criticalHit ? proficiencyBonus : (proficiencyBonus + 1) / 2; + var distance = attacker.UsedSpecialFeatures.TryGetValue(MercilessName, out var value) && value == 1 + ? proficiencyBonus + : (proficiencyBonus + 1) / 2; var implementationManager = ServiceRepository.GetService() as RulesetImplementationManager; @@ -119,6 +120,23 @@ public IEnumerator HandleReducedToZeroHpByMe( .ExecuteAction(actionParams, null, true); } + public IEnumerator OnPhysicalAttackBeforeHitConfirmedOnEnemy( + GameLocationBattleManager battleManager, + GameLocationCharacter attacker, + GameLocationCharacter defender, + ActionModifier actionModifier, + RulesetAttackMode attackMode, + bool rangedAttack, + AdvantageType advantageType, + List actualEffectForms, + bool firstTarget, + bool criticalHit) + { + attacker.UsedSpecialFeatures.TryAdd(MercilessName, criticalHit ? 1 : 0); + + yield break; + } + public IEnumerator OnPhysicalAttackFinishedByMe( GameLocationBattleManager battleManager, CharacterAction action, @@ -128,7 +146,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( RollOutcome rollOutcome, int damageAmount) { - _criticalHit = rollOutcome == RollOutcome.CriticalSuccess; + attacker.UsedSpecialFeatures.Remove(MercilessName); yield break; } diff --git a/SolastaUnfinishedBusiness/Info.json b/SolastaUnfinishedBusiness/Info.json index 8eda5fac3a..f5f9e6a65b 100644 --- a/SolastaUnfinishedBusiness/Info.json +++ b/SolastaUnfinishedBusiness/Info.json @@ -1,7 +1,7 @@ { "Id": "SolastaUnfinishedBusiness", "DisplayName": "[Un] Finished Business", - "Version": "1.5.97.18", + "Version": "1.5.97.19", "GameVersion": "1.5.97", "ManagerVersion": "0.24.0", "AssemblyName": "SolastaUnfinishedBusiness.dll", diff --git a/SolastaUnfinishedBusiness/Models/FixesContext.cs b/SolastaUnfinishedBusiness/Models/FixesContext.cs index 63fd6931ce..2c4a764379 100644 --- a/SolastaUnfinishedBusiness/Models/FixesContext.cs +++ b/SolastaUnfinishedBusiness/Models/FixesContext.cs @@ -692,17 +692,18 @@ public IEnumerator OnPhysicalAttackFinishedByMe( } // ensure Zen Archer Hail of Arrows won't trigger stunning strike without ki points - if (attackMode.AttackTags.Contains(WayOfZenArchery.HailOfArrows)) + if (attacker.UsedSpecialFeatures.ContainsKey(WayOfZenArchery.HailOfArrowsAttack)) { - var hasTab = attacker.UsedSpecialFeatures.TryGetValue(WayOfZenArchery.HailOfArrows, out var attacks); + var hasTab = + attacker.UsedSpecialFeatures.TryGetValue(WayOfZenArchery.HailOfArrowsAttacksTab, out var attacks); if (hasTab) { - attacker.UsedSpecialFeatures[WayOfZenArchery.HailOfArrows] += 1; + attacker.UsedSpecialFeatures[WayOfZenArchery.HailOfArrowsAttacksTab] += 1; } else { - attacker.UsedSpecialFeatures.Add(WayOfZenArchery.HailOfArrows, 1); + attacker.UsedSpecialFeatures.Add(WayOfZenArchery.HailOfArrowsAttacksTab, 1); } if (rulesetAttacker.RemainingKiPoints - attacks <= 0) diff --git a/SolastaUnfinishedBusiness/Patches/CharacterActionPatcher.cs b/SolastaUnfinishedBusiness/Patches/CharacterActionPatcher.cs index b215aaa567..9b36c0b781 100644 --- a/SolastaUnfinishedBusiness/Patches/CharacterActionPatcher.cs +++ b/SolastaUnfinishedBusiness/Patches/CharacterActionPatcher.cs @@ -151,10 +151,6 @@ __instance.ActionParams.activeEffect is RulesetEffectSpell effectSpell && PowerBundle.SpendBundledPowerIfNeeded(spendPower); break; - case CharacterActionAttack characterActionAttack: - Global.CurrentAttackAction.Push(characterActionAttack); - break; - case CharacterActionMoveStepBase characterActionMoveStepBase: OtherFeats.NotifyFeatStealth(characterActionMoveStepBase); break; @@ -255,8 +251,6 @@ public static IEnumerator Postfix(IEnumerator values, CharacterAction __instance // ReSharper disable once InvertIf if (__instance is CharacterActionAttack actionAttack) { - Global.CurrentAttackAction.Pop(); - if (actionAttack.ActionParams.AttackMode != null) { rulesetCharacter.ProcessConditionsMatchingInterruption( diff --git a/SolastaUnfinishedBusiness/Patches/CharacterActionSpendPowerPatcher.cs b/SolastaUnfinishedBusiness/Patches/CharacterActionSpendPowerPatcher.cs index 93994052b4..f941c47629 100644 --- a/SolastaUnfinishedBusiness/Patches/CharacterActionSpendPowerPatcher.cs +++ b/SolastaUnfinishedBusiness/Patches/CharacterActionSpendPowerPatcher.cs @@ -61,8 +61,11 @@ private static IEnumerator ExecuteImpl(CharacterActionSpendPower __instance) if (__instance.activePower is { OriginItem: null }) { // Fire shield retaliation has no class or race origin - if (__instance.activePower.UsablePower.OriginClass || - __instance.activePower.UsablePower.OriginRace) + if (__instance.activePower.UsablePower.OriginClass) + { + actingCharacter.RulesetCharacter.UsePower(__instance.activePower.UsablePower); + } + else if (__instance.activePower.UsablePower.OriginRace) { actingCharacter.RulesetCharacter.UsePower(__instance.activePower.UsablePower); } @@ -165,18 +168,18 @@ private static IEnumerator ExecuteImpl(CharacterActionSpendPower __instance) var hero = controller.RulesetCharacter.GetOriginalHero(); - if (hero == null) + if (hero != null) { - continue; - } - - foreach (var magicalAttackBeforeHitConfirmedOnEnemy in hero.TrainedMetamagicOptions - .SelectMany(metamagic => - metamagic.GetAllSubFeaturesOfType())) - { - yield return magicalAttackBeforeHitConfirmedOnEnemy.OnMagicEffectBeforeHitConfirmedOnEnemy( - battleManager, controller, target, actionModifier, - rulesetEffect, effectForms, i == 0, false); + foreach (var magicalAttackBeforeHitConfirmedOnEnemy in hero.TrainedMetamagicOptions + .SelectMany(metamagic => + metamagic + .GetAllSubFeaturesOfType())) + { + yield return magicalAttackBeforeHitConfirmedOnEnemy + .OnMagicEffectBeforeHitConfirmedOnEnemy( + battleManager, controller, target, actionModifier, + rulesetEffect, effectForms, i == 0, false); + } } } diff --git a/SolastaUnfinishedBusiness/Patches/GameLocationBattleManagerPatcher.cs b/SolastaUnfinishedBusiness/Patches/GameLocationBattleManagerPatcher.cs index 957ca0abe4..6865b72865 100644 --- a/SolastaUnfinishedBusiness/Patches/GameLocationBattleManagerPatcher.cs +++ b/SolastaUnfinishedBusiness/Patches/GameLocationBattleManagerPatcher.cs @@ -40,11 +40,9 @@ public static void Postfix( if (__result && usablePower.PowerDefinition == DatabaseHelper.FeatureDefinitionPowers.PowerMonkMartialArts) { - var currentAttackAction = Global.CurrentAttackAction; + var character = GameLocationCharacter.GetFromActor(caster); - if (currentAttackAction.Count > 0 && - currentAttackAction.Peek().ActionParams.AttackMode.AttackTags - .Contains(WayOfZenArchery.HailOfArrows)) + if (character.UsedSpecialFeatures.Remove(WayOfZenArchery.HailOfArrowsAttack)) { __result = false; } diff --git a/SolastaUnfinishedBusiness/Patches/RulesetActorPatcher.cs b/SolastaUnfinishedBusiness/Patches/RulesetActorPatcher.cs index e8dac89c03..6e9d5ed757 100644 --- a/SolastaUnfinishedBusiness/Patches/RulesetActorPatcher.cs +++ b/SolastaUnfinishedBusiness/Patches/RulesetActorPatcher.cs @@ -11,7 +11,6 @@ using SolastaUnfinishedBusiness.Api.Helpers; using SolastaUnfinishedBusiness.Api.LanguageExtensions; using SolastaUnfinishedBusiness.Behaviors.Specific; -using SolastaUnfinishedBusiness.Feats; using SolastaUnfinishedBusiness.Interfaces; using SolastaUnfinishedBusiness.Models; using SolastaUnfinishedBusiness.Subclasses; @@ -674,16 +673,6 @@ private static void EnumerateIDamageAffinityProvider( .Where(definition => definition is IDamageAffinityProvider)); } } - - [UsedImplicitly] - public static void Postfix(RulesetActor __instance, ref int __result) - { - //TODO: convert to an interface if ever required - if (__instance.HasConditionOfType(ArmorFeats.ConditionShieldTechniquesResistanceName)) - { - __result /= 2; - } - } } [HarmonyPatch(typeof(RulesetActor), nameof(RulesetActor.RollDiceAndSum))] diff --git a/SolastaUnfinishedBusiness/Patches/RulesetImplementationManagerLocationPatcher.cs b/SolastaUnfinishedBusiness/Patches/RulesetImplementationManagerLocationPatcher.cs index deba46469d..39fc239863 100644 --- a/SolastaUnfinishedBusiness/Patches/RulesetImplementationManagerLocationPatcher.cs +++ b/SolastaUnfinishedBusiness/Patches/RulesetImplementationManagerLocationPatcher.cs @@ -10,6 +10,7 @@ using SolastaUnfinishedBusiness.Api.Helpers; using SolastaUnfinishedBusiness.Api.LanguageExtensions; using SolastaUnfinishedBusiness.Behaviors; +using SolastaUnfinishedBusiness.Spells; using SolastaUnfinishedBusiness.Validators; namespace SolastaUnfinishedBusiness.Patches; @@ -218,6 +219,7 @@ public static void Prefix(RulesetImplementationDefinitions.ApplyFormsParams form PowersUsedByMe.SetRange(source.PowersUsedByMe); source.PowersUsedByMe.Clear(); + //END BUGFIX } [UsedImplicitly] @@ -230,13 +232,20 @@ public static void Postfix( __instance.TryFindSubstituteOfCharacter(source, out var characterMonster); //BUGFIX: allow Druids to keep concentration on spells / powers with proxy summon forms - //TODO: do I need to add them back to source? source.SpellsCastByMe.SetRange(SpellsCastByMe); source.PowersUsedByMe.SetRange(PowersUsedByMe); characterMonster.SpellsCastByMe.SetRange(SpellsCastByMe); characterMonster.PowersUsedByMe.SetRange(PowersUsedByMe); + //END BUGFIX + + //PATCH: enforces concentration on shape change spell + if (formsParams.activeEffect is RulesetEffectSpell rulesetEffectSpell && + rulesetEffectSpell.SpellDefinition.Name == SpellBuilders.ShapechangeName) + { + characterMonster.concentratedSpell = rulesetEffectSpell; + } //PATCH: allows shape changers to get bonuses effects defined in features / feats / etc. var sourceAbilityBonus = formsParams.activeEffect.ComputeSourceAbilityBonus(source); diff --git a/SolastaUnfinishedBusiness/Portraits/Personal/Eryx.png b/SolastaUnfinishedBusiness/Portraits/Personal/Eryx.png new file mode 100644 index 0000000000..a35dec156c Binary files /dev/null and b/SolastaUnfinishedBusiness/Portraits/Personal/Eryx.png differ diff --git a/SolastaUnfinishedBusiness/Portraits/Personal/Khali.png b/SolastaUnfinishedBusiness/Portraits/Personal/Khali.png new file mode 100644 index 0000000000..055cfbdbc2 Binary files /dev/null and b/SolastaUnfinishedBusiness/Portraits/Personal/Khali.png differ diff --git a/SolastaUnfinishedBusiness/Portraits/Personal/Lawmanus.png b/SolastaUnfinishedBusiness/Portraits/Personal/Lawmanus.png new file mode 100644 index 0000000000..789ee7bae4 Binary files /dev/null and b/SolastaUnfinishedBusiness/Portraits/Personal/Lawmanus.png differ diff --git a/SolastaUnfinishedBusiness/Portraits/Personal/Serath.png b/SolastaUnfinishedBusiness/Portraits/Personal/Serath.png new file mode 100644 index 0000000000..f837643452 Binary files /dev/null and b/SolastaUnfinishedBusiness/Portraits/Personal/Serath.png differ diff --git a/SolastaUnfinishedBusiness/Properties/Resources.Designer.cs b/SolastaUnfinishedBusiness/Properties/Resources.Designer.cs index 5017a3b8e1..5344bc6a01 100644 --- a/SolastaUnfinishedBusiness/Properties/Resources.Designer.cs +++ b/SolastaUnfinishedBusiness/Properties/Resources.Designer.cs @@ -939,6 +939,16 @@ public static byte[] CircleOfTheNight { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] CircleOfTheWildfire { + get { + object obj = ResourceManager.GetObject("CircleOfTheWildfire", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// @@ -959,6 +969,16 @@ public static byte[] collapsed { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] CollegeOfElegance { + get { + object obj = ResourceManager.GetObject("CollegeOfElegance", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// @@ -989,6 +1009,16 @@ public static byte[] CollegeOfLife { } } + /// + /// Looks up a localized resource of type System.Byte[]. + /// + public static byte[] CollegeOfValiance { + get { + object obj = ResourceManager.GetObject("CollegeOfValiance", resourceCulture); + return ((byte[])(obj)); + } + } + /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/SolastaUnfinishedBusiness/Properties/Resources.resx b/SolastaUnfinishedBusiness/Properties/Resources.resx index 8f012423ed..bb0711bdd0 100644 --- a/SolastaUnfinishedBusiness/Properties/Resources.resx +++ b/SolastaUnfinishedBusiness/Properties/Resources.resx @@ -2025,6 +2025,21 @@ PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\Subclasses\CircleOfTheWildfire.png;System.Byte[], mscorlib, Version=4.0.0.0, + Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + + ..\Resources\Subclasses\CollegeOfElegance.png;System.Byte[], mscorlib, Version=4.0.0.0, + Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + + ..\Resources\Subclasses\CollegeOfValiance.png;System.Byte[], mscorlib, Version=4.0.0.0, + Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\Subclasses\CollegeOfGuts.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/SolastaUnfinishedBusiness/Resources/Subclasses/CircleOfTheWildfire.png b/SolastaUnfinishedBusiness/Resources/Subclasses/CircleOfTheWildfire.png new file mode 100644 index 0000000000..3b05dae85c Binary files /dev/null and b/SolastaUnfinishedBusiness/Resources/Subclasses/CircleOfTheWildfire.png differ diff --git a/SolastaUnfinishedBusiness/Resources/Subclasses/CollegeOfElegance.png b/SolastaUnfinishedBusiness/Resources/Subclasses/CollegeOfElegance.png new file mode 100644 index 0000000000..de652ccc16 Binary files /dev/null and b/SolastaUnfinishedBusiness/Resources/Subclasses/CollegeOfElegance.png differ diff --git a/SolastaUnfinishedBusiness/Resources/Subclasses/CollegeOfValiance.png b/SolastaUnfinishedBusiness/Resources/Subclasses/CollegeOfValiance.png new file mode 100644 index 0000000000..96062c5cb2 Binary files /dev/null and b/SolastaUnfinishedBusiness/Resources/Subclasses/CollegeOfValiance.png differ diff --git a/SolastaUnfinishedBusiness/SolastaUnfinishedBusiness.csproj b/SolastaUnfinishedBusiness/SolastaUnfinishedBusiness.csproj index 52f7d10a4b..4bb6fdbbaa 100644 --- a/SolastaUnfinishedBusiness/SolastaUnfinishedBusiness.csproj +++ b/SolastaUnfinishedBusiness/SolastaUnfinishedBusiness.csproj @@ -3,7 +3,7 @@ 12 net48 - 1.5.97.18 + 1.5.97.19 https://github.com/SolastaMods/SolastaUnfinishedBusiness git Debug Install;Release Install diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel01.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel01.cs index 3c85b43037..f2cadacbe3 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel01.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel01.cs @@ -1328,7 +1328,7 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, yield break; } - if (action is not CharacterActionCastSpell actionCastSpell) + if (action is not CharacterActionCastSpell actionCastSpell || action.Countered) { yield break; } @@ -1999,7 +1999,7 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, conditionSanctuary.Name, out var activeCondition)) { - yield break; + continue; } rulesetTarget.EnumerateFeaturesToBrowse( diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel02.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel02.cs index b382ac7b53..a2a4d5fa6c 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel02.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel02.cs @@ -634,7 +634,7 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, ServiceRepository.GetService() as GameLocationActionManager; var battleManager = ServiceRepository.GetService() as GameLocationBattleManager; - if (!actionManager || !battleManager) + if (!actionManager || !battleManager || action.Countered) { yield break; } @@ -1182,7 +1182,7 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, var battleManager = ServiceRepository.GetService() as GameLocationBattleManager; - if (!actionManager || !battleManager) + if (!actionManager || !battleManager || action.Countered) { yield break; } diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel03.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel03.cs index ec0a059ded..0985cdc30f 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel03.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel03.cs @@ -749,6 +749,11 @@ target.RulesetCharacter is not RulesetCharacterEffectProxy && public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition) { + if (action.Countered) + { + yield break; + } + var attacker = action.ActingCharacter; var rulesetAttacker = attacker.RulesetCharacter; @@ -775,8 +780,6 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, ServiceRepository.GetService()? .ExecuteAction(actionParams, null, true); - - yield break; } public IEnumerator OnPowerOrSpellInitiatedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition) @@ -1821,7 +1824,7 @@ private sealed class ModifyDiceRollVitalityTransfer : IPowerOrSpellFinishedByMe { public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition) { - if (action is not CharacterActionCastSpell actionCastSpell) + if (action is not CharacterActionCastSpell actionCastSpell || action.Countered) { yield break; } diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel05.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel05.cs index 2a227dbca2..46b48264c5 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel05.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel05.cs @@ -543,7 +543,7 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, ServiceRepository.GetService() as GameLocationActionManager; var battleManager = ServiceRepository.GetService() as GameLocationBattleManager; - if (!actionManager || !battleManager) + if (!actionManager || !battleManager || action.Countered) { yield break; } @@ -923,7 +923,7 @@ private sealed class MagicEffectBeforeHitConfirmedOnMeCircleOfMagicalNegation( ConditionDefinition conditionCircleOfMagicalNegation) : IMagicEffectBeforeHitConfirmedOnMe, IRollSavingThrowFinished { - private RollOutcome _saveOutcome; + private const string CircleOfMagicalNegationSavedTag = "CircleOfMagicalNegationSaved"; public IEnumerator OnMagicEffectBeforeHitConfirmedOnMe( GameLocationBattleManager battleManager, @@ -935,7 +935,7 @@ public IEnumerator OnMagicEffectBeforeHitConfirmedOnMe( bool firstTarget, bool criticalHit) { - if (_saveOutcome != RollOutcome.Success) + if (!defender.UsedSpecialFeatures.Remove(CircleOfMagicalNegationSavedTag)) { yield break; } @@ -963,7 +963,11 @@ public void OnSavingThrowFinished( ref int outcomeDelta, List effectForms) { - _saveOutcome = outcome; + if (outcome == RollOutcome.Success) + { + GameLocationCharacter.GetFromActor(defender).UsedSpecialFeatures + .TryAdd(CircleOfMagicalNegationSavedTag, 0); + } } } @@ -1156,6 +1160,11 @@ public IEnumerator ComputeValidPositions(CursorLocationSelectPosition cursorLoca public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition) { + if (action.Countered) + { + yield break; + } + var actingCharacter = action.ActingCharacter; var actingRulesetCharacter = actingCharacter.RulesetCharacter; diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel06.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel06.cs index c1866f94cf..dc3d4c3418 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel06.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel06.cs @@ -395,10 +395,9 @@ public bool IsValid(CursorLocationSelectTarget __instance, GameLocationCharacter private sealed class PowerOrSpellFinishedByMeFizbanPlatinumShield( SpellDefinition spell, - // ReSharper disable once SuggestBaseTypeForParameterInConstructor ConditionDefinition condition) : IPowerOrSpellFinishedByMe { - private int _remainingRounds; + private const string FizbanPlatinumShieldTag = "FizbanPlatinumShieldTag"; public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition) { @@ -416,7 +415,8 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, { case CharacterActionUsePower: { - _remainingRounds = rulesetSpell.RemainingRounds; + actingCharacter.UsedSpecialFeatures.TryAdd(FizbanPlatinumShieldTag, 0); + actingCharacter.UsedSpecialFeatures.TryAdd(FizbanPlatinumShieldTag, rulesetSpell.RemainingRounds); var spellRepertoire = rulesetSpell.SpellRepertoire; var actionService = ServiceRepository.GetService(); @@ -432,16 +432,18 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, actionService.ExecuteAction(actionParams, null, true); break; } - case CharacterActionCastSpell when _remainingRounds > 0: - rulesetSpell.RemainingRounds = _remainingRounds; + case CharacterActionCastSpell + when actingCharacter.UsedSpecialFeatures.TryGetValue(FizbanPlatinumShieldTag, out var value) && + value > 0: + rulesetSpell.RemainingRounds = value; if (action.ActionParams.TargetCharacters[0].RulesetActor.TryGetConditionOfCategoryAndType( AttributeDefinitions.TagEffect, condition.Name, out var activeCondition)) { - activeCondition.RemainingRounds = _remainingRounds; + activeCondition.RemainingRounds = value; } - _remainingRounds = 0; + actingCharacter.UsedSpecialFeatures.TryAdd(FizbanPlatinumShieldTag, 0); break; } } diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel07.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel07.cs index 764cf28661..aa03090bc0 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel07.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel07.cs @@ -53,7 +53,7 @@ internal static SpellDefinition BuildReverseGravity() .SetOrUpdateGuiPresentation(Category.Condition) .SetConditionType(ConditionType.Neutral) .SetParentCondition(ConditionDefinitions.ConditionFlying) - .SetFeatures(FeatureDefinitionMoveModes.MoveModeFly2) + .SetFeatures() .AddToDB(), ConditionForm.ConditionOperation.Add) .HasSavingThrow(EffectSavingThrowType.Negates) @@ -421,7 +421,7 @@ internal static SpellDefinition BuildCrownOfStars() Type = PowerPoolBonusCalculationType.ConditionAmount, Attribute = conditionCrownOfStars.Name }, - new CustomBehaviorPowerCrownOfStars(spell, conditionCrownOfStars)); + new CustomBehaviorPowerCrownOfStars(spell, powerCrownOfStars, conditionCrownOfStars)); return spell; } @@ -441,17 +441,12 @@ public void OnConditionRemoved(RulesetCharacter target, RulesetCondition ruleset private sealed class CustomBehaviorPowerCrownOfStars( SpellDefinition spellCrownOfStars, + FeatureDefinitionPower powerMotes, ConditionDefinition conditionCrownOfStars) : IPowerOrSpellFinishedByMe { public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition) { var rulesetCharacter = action.ActingCharacter.RulesetCharacter; - - if (baseDefinition is not FeatureDefinitionPower powerMotes) - { - yield break; - } - var remainingUses = action.ActingCharacter.RulesetCharacter.GetRemainingPowerUses(powerMotes); // ReSharper disable once ConvertIfStatementToSwitchStatement @@ -473,6 +468,8 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, rulesetCharacter.PersonalLightSource.brightRange = 0; } } + + yield break; } } diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel08.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel08.cs index 290d49633e..e91e4ab89f 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel08.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel08.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Linq; using SolastaUnfinishedBusiness.Api.GameExtensions; @@ -289,7 +288,18 @@ private sealed class ModifyEffectDescriptionSoulExpulsion(FeatureDefinitionPower { public void ModifyDamageAffinity(RulesetActor defender, RulesetActor attacker, List features) { - throw new NotImplementedException(); + if (defender is RulesetCharacterMonster rulesetCharacterMonster && + rulesetCharacterMonster.CharacterFamily == CharacterFamilyDefinitions.Undead.Name) + { + features.RemoveAll(x => + x is IDamageAffinityProvider + { + DamageAffinityType: DamageAffinityType.Resistance, DamageType: DamageTypeNecrotic + } or IDamageAffinityProvider + { + DamageAffinityType: DamageAffinityType.Immunity, DamageType: DamageTypeNecrotic + }); + } } public bool IsValid(BaseDefinition definition, RulesetCharacter character, EffectDescription effectDescription) @@ -321,7 +331,7 @@ private sealed class CustomBehaviorSoulExpulsion( { public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition) { - if (Gui.Battle == null) + if (Gui.Battle == null || action.Countered) { yield break; } diff --git a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel09.cs b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel09.cs index b034c1333a..41aedd0c41 100644 --- a/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel09.cs +++ b/SolastaUnfinishedBusiness/Spells/SpellBuildersLevel09.cs @@ -217,72 +217,6 @@ internal static SpellDefinition BuildPowerWordKill() #endregion - #region Shapechange - - internal static SpellDefinition BuildShapechange() - { - return SpellDefinitionBuilder - .Create("Shapechange") - .SetGuiPresentation(Category.Spell, Sprites.GetSprite("Shapechange", Resources.ShapeChange, 128)) - .SetSchoolOfMagic(SchoolOfMagicDefinitions.SchoolTransmutation) - .SetSpellLevel(9) - .SetCastingTime(ActivationTime.Action) - .SetMaterialComponent(MaterialComponentType.Specific) - .SetSpecificMaterialComponent("Diamond", 1500, false) - .SetSomaticComponent(true) - .SetVerboseComponent(true) - .SetVocalSpellSameType(VocalSpellSemeType.Buff) - .SetEffectDescription( - EffectDescriptionBuilder - .Create() - .SetParticleEffectParameters(PowerDruidWildShape) - .SetDurationData(DurationType.Hour, 1) - .SetTargetingData(Side.Ally, RangeType.Self, 0, TargetType.Self) - .SetEffectForms( - EffectFormBuilder - .Create() - .SetShapeChangeForm( - ShapeChangeForm.Type.FreeListSelection, - true, - ConditionDefinitions.ConditionWildShapeSubstituteForm, - [ - new ShapeOptionDescription - { - requiredLevel = 1, substituteMonster = BlackDragon_MasterOfNecromancy - }, - new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Divine_Avatar }, - new ShapeOptionDescription - { - requiredLevel = 1, substituteMonster = Emperor_Laethar - }, - new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Giant_Ape }, - new ShapeOptionDescription - { - requiredLevel = 1, substituteMonster = GoldDragon_AerElai - }, - new ShapeOptionDescription - { - requiredLevel = 1, substituteMonster = GreenDragon_MasterOfConjuration - }, - new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Remorhaz }, - new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Spider_Queen }, - new ShapeOptionDescription - { - requiredLevel = 1, substituteMonster = Sorr_Akkath_Shikkath - }, - new ShapeOptionDescription - { - requiredLevel = 1, substituteMonster = Sorr_Akkath_Tshar_Boss - } - ]) - .Build()) - .Build()) - .SetRequiresConcentration(true) - .AddToDB(); - } - - #endregion - #region Time Stop internal static SpellDefinition BuildTimeStop() @@ -370,4 +304,72 @@ internal static SpellDefinition BuildWeird() } #endregion + + #region Shapechange + + internal const string ShapechangeName = "Shapechange"; + + internal static SpellDefinition BuildShapechange() + { + return SpellDefinitionBuilder + .Create(ShapechangeName) + .SetGuiPresentation(Category.Spell, Sprites.GetSprite(ShapechangeName, Resources.ShapeChange, 128)) + .SetSchoolOfMagic(SchoolOfMagicDefinitions.SchoolTransmutation) + .SetSpellLevel(9) + .SetCastingTime(ActivationTime.Action) + .SetMaterialComponent(MaterialComponentType.Specific) + .SetSpecificMaterialComponent("Diamond", 1500, false) + .SetSomaticComponent(true) + .SetVerboseComponent(true) + .SetVocalSpellSameType(VocalSpellSemeType.Buff) + .SetEffectDescription( + EffectDescriptionBuilder + .Create() + .SetParticleEffectParameters(PowerDruidWildShape) + .SetDurationData(DurationType.Hour, 1) + .SetTargetingData(Side.Ally, RangeType.Self, 0, TargetType.Self) + .SetEffectForms( + EffectFormBuilder + .Create() + .SetShapeChangeForm( + ShapeChangeForm.Type.FreeListSelection, + true, + ConditionDefinitions.ConditionWildShapeSubstituteForm, + [ + new ShapeOptionDescription + { + requiredLevel = 1, substituteMonster = BlackDragon_MasterOfNecromancy + }, + new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Divine_Avatar }, + new ShapeOptionDescription + { + requiredLevel = 1, substituteMonster = Emperor_Laethar + }, + new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Giant_Ape }, + new ShapeOptionDescription + { + requiredLevel = 1, substituteMonster = GoldDragon_AerElai + }, + new ShapeOptionDescription + { + requiredLevel = 1, substituteMonster = GreenDragon_MasterOfConjuration + }, + new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Remorhaz }, + new ShapeOptionDescription { requiredLevel = 1, substituteMonster = Spider_Queen }, + new ShapeOptionDescription + { + requiredLevel = 1, substituteMonster = Sorr_Akkath_Shikkath + }, + new ShapeOptionDescription + { + requiredLevel = 1, substituteMonster = Sorr_Akkath_Tshar_Boss + } + ]) + .Build()) + .Build()) + .SetRequiresConcentration(true) + .AddToDB(); + } + + #endregion } diff --git a/SolastaUnfinishedBusiness/Subclasses/Builders/InvocationsBuilders.cs b/SolastaUnfinishedBusiness/Subclasses/Builders/InvocationsBuilders.cs index 771ae85cab..664ebb7722 100644 --- a/SolastaUnfinishedBusiness/Subclasses/Builders/InvocationsBuilders.cs +++ b/SolastaUnfinishedBusiness/Subclasses/Builders/InvocationsBuilders.cs @@ -926,26 +926,12 @@ public EffectDescription GetEffectDescription(BaseDefinition definition, } } - private sealed class AfterActionFinishedByMeAbilitiesChain : IActionFinishedByMe + private sealed class AfterActionFinishedByMeAbilitiesChain( + ConditionDefinition conditionSpriteAbility, + ConditionDefinition conditionImpAbility, + ConditionDefinition conditionQuasitAbility, + ConditionDefinition conditionPseudoAbility) : IActionFinishedByMe { - private readonly ConditionDefinition _conditionImpAbility; - - private readonly ConditionDefinition _conditionPseudoAbility; - - private readonly ConditionDefinition _conditionQuasitAbility; - private readonly ConditionDefinition _conditionSpriteAbility; - - internal AfterActionFinishedByMeAbilitiesChain(ConditionDefinition conditionSpriteAbility, - ConditionDefinition conditionImpAbility, - ConditionDefinition conditionQuasitAbility, - ConditionDefinition conditionPseudoAbility) - { - _conditionSpriteAbility = conditionSpriteAbility; - _conditionImpAbility = conditionImpAbility; - _conditionQuasitAbility = conditionQuasitAbility; - _conditionPseudoAbility = conditionPseudoAbility; - } - public IEnumerator OnActionFinishedByMe(CharacterAction action) { var actingCharacter = action.ActingCharacter; @@ -970,24 +956,24 @@ public IEnumerator OnActionFinishedByMe(CharacterAction action) if (rulesetCharacter.IsPowerActive(power)) { if (power.PowerDefinition == PowerPactChainImp && - !rulesetCharacter.HasConditionOfType(_conditionImpAbility.name)) + !rulesetCharacter.HasConditionOfType(conditionImpAbility.name)) { - SetChainBuff(rulesetCharacter, _conditionImpAbility); + SetChainBuff(rulesetCharacter, conditionImpAbility); } else if (power.PowerDefinition == PowerPactChainQuasit && - !rulesetCharacter.HasConditionOfType(_conditionQuasitAbility.name)) + !rulesetCharacter.HasConditionOfType(conditionQuasitAbility.name)) { - SetChainBuff(rulesetCharacter, _conditionQuasitAbility); + SetChainBuff(rulesetCharacter, conditionQuasitAbility); } else if (power.PowerDefinition == PowerPactChainSprite && - !rulesetCharacter.HasConditionOfType(_conditionSpriteAbility.name)) + !rulesetCharacter.HasConditionOfType(conditionSpriteAbility.name)) { - SetChainBuff(rulesetCharacter, _conditionSpriteAbility); + SetChainBuff(rulesetCharacter, conditionSpriteAbility); } else if (power.PowerDefinition == PowerPactChainPseudodragon && - !rulesetCharacter.HasConditionOfType(_conditionPseudoAbility.name)) + !rulesetCharacter.HasConditionOfType(conditionPseudoAbility.name)) { - SetChainBuff(rulesetCharacter, _conditionPseudoAbility); + SetChainBuff(rulesetCharacter, conditionPseudoAbility); } } else @@ -995,22 +981,22 @@ public IEnumerator OnActionFinishedByMe(CharacterAction action) if (power.PowerDefinition == PowerPactChainImp) { rulesetCharacter.RemoveAllConditionsOfCategoryAndType(AttributeDefinitions.TagEffect, - _conditionImpAbility.name); + conditionImpAbility.name); } else if (power.PowerDefinition == PowerPactChainQuasit) { rulesetCharacter.RemoveAllConditionsOfCategoryAndType(AttributeDefinitions.TagEffect, - _conditionQuasitAbility.name); + conditionQuasitAbility.name); } else if (power.PowerDefinition == PowerPactChainSprite) { rulesetCharacter.RemoveAllConditionsOfCategoryAndType(AttributeDefinitions.TagEffect, - _conditionSpriteAbility.name); + conditionSpriteAbility.name); } else if (power.PowerDefinition == PowerPactChainPseudodragon) { rulesetCharacter.RemoveAllConditionsOfCategoryAndType(AttributeDefinitions.TagEffect, - _conditionPseudoAbility.name); + conditionPseudoAbility.name); } } } diff --git a/SolastaUnfinishedBusiness/Subclasses/Builders/MetamagicBuilders.cs b/SolastaUnfinishedBusiness/Subclasses/Builders/MetamagicBuilders.cs index 6ffed7dfdf..39060cf473 100644 --- a/SolastaUnfinishedBusiness/Subclasses/Builders/MetamagicBuilders.cs +++ b/SolastaUnfinishedBusiness/Subclasses/Builders/MetamagicBuilders.cs @@ -214,12 +214,15 @@ internal static MetamagicOptionDefinition BuildMetamagicPowerfulSpell() { var validator = new ValidateMetamagicApplication(IsMetamagicPowerfulSpellValid); - return MetamagicOptionDefinitionBuilder + var metamagic = MetamagicOptionDefinitionBuilder .Create(MetamagicPowerful) .SetGuiPresentation(Category.Feature) .SetCost() - .AddCustomSubFeatures(new ModifyEffectDescriptionMetamagicPowerful(), validator) .AddToDB(); + + metamagic.AddCustomSubFeatures(new ModifyEffectDescriptionMetamagicPowerful(metamagic), validator); + + return metamagic; } private static void IsMetamagicPowerfulSpellValid( @@ -241,26 +244,29 @@ private static void IsMetamagicPowerfulSpellValid( result = false; } - private sealed class ModifyEffectDescriptionMetamagicPowerful : IModifyEffectDescription + private sealed class ModifyEffectDescriptionMetamagicPowerful( + MetamagicOptionDefinition metamagicOptionDefinition) : IMagicEffectBeforeHitConfirmedOnEnemy { - public bool IsValid( - BaseDefinition definition, - RulesetCharacter character, - EffectDescription effectDescription) + public IEnumerator OnMagicEffectBeforeHitConfirmedOnEnemy( + GameLocationBattleManager battleManager, + GameLocationCharacter attacker, + GameLocationCharacter defender, + ActionModifier actionModifier, + RulesetEffect rulesetEffect, + List actualEffectForms, + bool firstTarget, + bool criticalHit) { - return true; - } + if (rulesetEffect.MetamagicOption != metamagicOptionDefinition) + { + yield break; + } - public EffectDescription GetEffectDescription(BaseDefinition definition, EffectDescription effectDescription, - RulesetCharacter character, RulesetEffect rulesetEffect) - { - foreach (var effectForm in effectDescription.EffectForms + foreach (var effectForm in actualEffectForms .Where(x => x.FormType == EffectForm.EffectFormType.Damage)) { effectForm.DamageForm.diceNumber += 1; } - - return effectDescription; } } diff --git a/SolastaUnfinishedBusiness/Subclasses/CircleOfTheWildfire.cs b/SolastaUnfinishedBusiness/Subclasses/CircleOfTheWildfire.cs index 5a15719d2e..b31c3ed201 100644 --- a/SolastaUnfinishedBusiness/Subclasses/CircleOfTheWildfire.cs +++ b/SolastaUnfinishedBusiness/Subclasses/CircleOfTheWildfire.cs @@ -441,7 +441,7 @@ public CircleOfTheWildfire() Subclass = CharacterSubclassDefinitionBuilder .Create(Name) - .SetGuiPresentation(Category.Subclass, Sprites.GetSprite(Name, Resources.PatronElementalist, 256)) + .SetGuiPresentation(Category.Subclass, Sprites.GetSprite(Name, Resources.CircleOfTheWildfire, 256)) .AddFeaturesAtLevel(2, autoPreparedSpellsWildfire, featureSetSummonSpirit) .AddFeaturesAtLevel(6, featureEnhancedBond) .AddFeaturesAtLevel(10, featureSetCauterizingFlames) diff --git a/SolastaUnfinishedBusiness/Subclasses/CollegeOfElegance.cs b/SolastaUnfinishedBusiness/Subclasses/CollegeOfElegance.cs index 64fecbb260..ba36dc68e7 100644 --- a/SolastaUnfinishedBusiness/Subclasses/CollegeOfElegance.cs +++ b/SolastaUnfinishedBusiness/Subclasses/CollegeOfElegance.cs @@ -225,7 +225,7 @@ public CollegeOfElegance() Subclass = CharacterSubclassDefinitionBuilder .Create(Name) - .SetGuiPresentation(Category.Subclass, Sprites.GetSprite(Name, Resources.CollegeOfWarDancer, 256)) + .SetGuiPresentation(Category.Subclass, Sprites.GetSprite(Name, Resources.CollegeOfElegance, 256)) .AddFeaturesAtLevel(3, dieRollModifierGrace, featureSetElegantFighting) .AddFeaturesAtLevel(6, featureEvasiveFootwork, AttributeModifierCasterFightingExtraAttack) .AddFeaturesAtLevel(14, featureSetAmazingDisplay) diff --git a/SolastaUnfinishedBusiness/Subclasses/CollegeOfValiance.cs b/SolastaUnfinishedBusiness/Subclasses/CollegeOfValiance.cs index ccd27a01bf..3e1cb83f9a 100644 --- a/SolastaUnfinishedBusiness/Subclasses/CollegeOfValiance.cs +++ b/SolastaUnfinishedBusiness/Subclasses/CollegeOfValiance.cs @@ -5,7 +5,9 @@ using SolastaUnfinishedBusiness.Api.Helpers; using SolastaUnfinishedBusiness.Builders; using SolastaUnfinishedBusiness.Builders.Features; +using SolastaUnfinishedBusiness.CustomUI; using SolastaUnfinishedBusiness.Interfaces; +using SolastaUnfinishedBusiness.Properties; using static RuleDefinitions; using static SolastaUnfinishedBusiness.Api.DatabaseHelper; using static SolastaUnfinishedBusiness.Api.DatabaseHelper.ActionDefinitions; @@ -111,7 +113,7 @@ public CollegeOfValiance() Subclass = CharacterSubclassDefinitionBuilder .Create(Name) - .SetGuiPresentation(Category.Subclass, CharacterSubclassDefinitions.TraditionLight) + .SetGuiPresentation(Category.Subclass, Sprites.GetSprite(Name, Resources.CollegeOfValiance, 256)) .AddFeaturesAtLevel(3, featureCaptivatingPresence, powerSteadfastDishearteningPerformance) .AddFeaturesAtLevel(6, autoPreparedSpellsRecallLanguage, _featureSteadfastInspiration) .AddFeaturesAtLevel(14, actionAffinityHeroicInspiration, powerHeroicInspiration) diff --git a/SolastaUnfinishedBusiness/Subclasses/DomainDefiler.cs b/SolastaUnfinishedBusiness/Subclasses/DomainDefiler.cs index d5838472d2..026b74f29d 100644 --- a/SolastaUnfinishedBusiness/Subclasses/DomainDefiler.cs +++ b/SolastaUnfinishedBusiness/Subclasses/DomainDefiler.cs @@ -332,21 +332,15 @@ private IEnumerator TryAddCondition( // Defile Life // - private sealed class ModifyEffectDescriptionDefileLife : IModifyEffectDescription + private sealed class ModifyEffectDescriptionDefileLife(FeatureDefinitionPower baseDefinition) + : IModifyEffectDescription { - private readonly BaseDefinition _baseDefinition; - - internal ModifyEffectDescriptionDefileLife(BaseDefinition baseDefinition) - { - _baseDefinition = baseDefinition; - } - public bool IsValid( BaseDefinition definition, RulesetCharacter character, EffectDescription effectDescription) { - return definition == _baseDefinition; + return definition == baseDefinition; } public EffectDescription GetEffectDescription( @@ -394,18 +388,15 @@ private sealed class CustomBehaviorDyingLight(FeatureDefinitionPower powerDyingL : IForceMaxDamageTypeDependent, IModifyAdditionalDamage, IActionFinishedByMe, IMagicEffectBeforeHitConfirmedOnEnemy, IPhysicalAttackBeforeHitConfirmedOnEnemy { - private bool _isValid; - public IEnumerator OnActionFinishedByMe(CharacterAction action) { - if (!_isValid) - { - yield break; - } + var actingCharacter = action.ActingCharacter; + var hasTag = actingCharacter.UsedSpecialFeatures.TryGetValue(powerDyingLight.Name, out var value); - _isValid = false; + actingCharacter.UsedSpecialFeatures.TryAdd(powerDyingLight.Name, 0); - if (action is not (CharacterActionAttack or CharacterActionMagicEffect or CharacterActionSpendPower)) + if (action is not (CharacterActionAttack or CharacterActionMagicEffect or CharacterActionSpendPower) || + !hasTag || value == 0) { yield break; } @@ -418,7 +409,13 @@ public IEnumerator OnActionFinishedByMe(CharacterAction action) public bool IsValid(RulesetActor rulesetActor, DamageForm damageForm) { - return _isValid && damageForm.DamageType is DamageTypeNecrotic; + var character = GameLocationCharacter.GetFromActor(rulesetActor); + + return + character != null && + character.UsedSpecialFeatures.TryGetValue(powerDyingLight.Name, out var value) && + value == 1 && + damageForm.DamageType is DamageTypeNecrotic; } public IEnumerator OnMagicEffectBeforeHitConfirmedOnEnemy( @@ -431,7 +428,7 @@ public IEnumerator OnMagicEffectBeforeHitConfirmedOnEnemy( bool firstTarget, bool criticalHit) { - Validate(attacker.RulesetCharacter, actualEffectForms); + Validate(attacker, actualEffectForms); yield break; } @@ -447,16 +444,18 @@ public void ModifyAdditionalDamage( var damageType = GetAdditionalDamageType(attacker, additionalDamageForm, featureDefinitionAdditionalDamage); var rulesetAttacker = attacker.RulesetCharacter; var usablePower = PowerProvider.Get(powerDyingLight, rulesetAttacker); - var isValid = rulesetAttacker.GetRemainingUsesOfPower(usablePower) > 0 && - rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.DestructiveWrathToggle) && - damageType is DamageTypeNecrotic; + var isValid = + rulesetAttacker.GetRemainingUsesOfPower(usablePower) > 0 && + rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.DyingLightToggle) && + damageType is DamageTypeNecrotic; if (!isValid) { return; } - _isValid = true; + attacker.UsedSpecialFeatures.TryAdd(powerDyingLight.Name, 0); + attacker.UsedSpecialFeatures[powerDyingLight.Name] = 1; rulesetAttacker.LogCharacterUsedPower(powerDyingLight); } @@ -472,29 +471,32 @@ public IEnumerator OnPhysicalAttackBeforeHitConfirmedOnEnemy( bool firstTarget, bool criticalHit) { - Validate(attacker.RulesetCharacter, actualEffectForms); + Validate(attacker, actualEffectForms); yield break; } - private void Validate( - RulesetCharacter rulesetAttacker, - // ReSharper disable once ParameterTypeCanBeEnumerable.Local - List actualEffectForms) + private void Validate(GameLocationCharacter attacker, List actualEffectForms) { + var rulesetAttacker = attacker.RulesetCharacter; var usablePower = PowerProvider.Get(powerDyingLight, rulesetAttacker); - _isValid = + var isValid = rulesetAttacker.GetRemainingUsesOfPower(usablePower) > 0 && - rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.DestructiveWrathToggle) && + rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.DyingLightToggle) && actualEffectForms.Any(x => x.FormType == EffectForm.EffectFormType.Damage && x.DamageForm.DamageType is DamageTypeNecrotic); - if (_isValid) + attacker.UsedSpecialFeatures.TryAdd(powerDyingLight.Name, 0); + + if (!isValid) { - rulesetAttacker.LogCharacterUsedPower(powerDyingLight); + return; } + + attacker.UsedSpecialFeatures[powerDyingLight.Name] = 1; + rulesetAttacker.LogCharacterUsedPower(powerDyingLight); } } } diff --git a/SolastaUnfinishedBusiness/Subclasses/DomainTempest.cs b/SolastaUnfinishedBusiness/Subclasses/DomainTempest.cs index 2cf2399218..bb342c8e4b 100644 --- a/SolastaUnfinishedBusiness/Subclasses/DomainTempest.cs +++ b/SolastaUnfinishedBusiness/Subclasses/DomainTempest.cs @@ -419,18 +419,15 @@ action.AttackRollOutcome is not (RollOutcome.Success or RollOutcome.CriticalSucc private sealed class CustomBehaviorDestructiveWrath(FeatureDefinitionPower powerDestructiveWrath) : IForceMaxDamageTypeDependent, IActionFinishedByMe { - private bool _isValid; - public IEnumerator OnActionFinishedByMe(CharacterAction action) { - if (!_isValid) - { - yield break; - } + var actingCharacter = action.ActingCharacter; + var hasTag = actingCharacter.UsedSpecialFeatures.TryGetValue(powerDestructiveWrath.Name, out var value); - _isValid = false; + actingCharacter.UsedSpecialFeatures.TryAdd(powerDestructiveWrath.Name, 0); - if (action is not (CharacterActionAttack or CharacterActionMagicEffect or CharacterActionSpendPower)) + if (action is not (CharacterActionAttack or CharacterActionMagicEffect or CharacterActionSpendPower) || + !hasTag || value == 0) { yield break; } @@ -450,12 +447,20 @@ public bool IsValid(RulesetActor rulesetActor, DamageForm damageForm) } var rulesetAttacker = rulesetCharacter.GetEffectControllerOrSelf(); + var attacker = GameLocationCharacter.GetFromActor(rulesetActor); var usablePower = PowerProvider.Get(powerDestructiveWrath, rulesetAttacker); - var isValid = rulesetAttacker!.GetRemainingUsesOfPower(usablePower) > 0 && - rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.DestructiveWrathToggle) && - damageForm.DamageType is DamageTypeLightning or DamageTypeThunder; + var isValid = + rulesetAttacker!.GetRemainingUsesOfPower(usablePower) > 0 && + rulesetAttacker.IsToggleEnabled((ActionDefinitions.Id)ExtraActionId.DestructiveWrathToggle) && + damageForm.DamageType is DamageTypeLightning or DamageTypeThunder; + + if (attacker.UsedSpecialFeatures.TryGetValue(powerDestructiveWrath.Name, out var value) && value == 1) + { + return isValid; + } - _isValid = _isValid || isValid; + attacker.UsedSpecialFeatures.TryAdd(powerDestructiveWrath.Name, 0); + attacker.UsedSpecialFeatures[powerDestructiveWrath.Name] = isValid ? 1 : 0; return isValid; } diff --git a/SolastaUnfinishedBusiness/Subclasses/MartialArcaneArcher.cs b/SolastaUnfinishedBusiness/Subclasses/MartialArcaneArcher.cs index 1de60c7389..42b4a469df 100644 --- a/SolastaUnfinishedBusiness/Subclasses/MartialArcaneArcher.cs +++ b/SolastaUnfinishedBusiness/Subclasses/MartialArcaneArcher.cs @@ -23,11 +23,10 @@ namespace SolastaUnfinishedBusiness.Subclasses; public sealed class MartialArcaneArcher : AbstractSubclass { private const string Name = "MartialArcaneArcher"; - private const string ArcaneShotMarker = "ArcaneShot"; private const ActionDefinitions.Id ArcaneArcherToggle = (ActionDefinitions.Id)ExtraActionId.ArcaneArcherToggle; + // referenced by feat Arcane Archer Adept internal static FeatureDefinitionPower PowerArcaneShot; - internal static FeatureDefinitionPowerUseModifier ModifyPowerArcaneShotAdditionalUse1; internal static FeatureDefinitionActionAffinity ActionAffinityArcaneArcherToggle; internal static FeatureDefinitionCustomInvocationPool InvocationPoolArcaneShotChoice2; @@ -90,6 +89,8 @@ public MartialArcaneArcher() HasModifiedUses.Marker, new PhysicalAttackFinishedByMeArcaneShot(powerBurstingArrow, powerBurstingArrowDamage)); + PowerBundle.RegisterPowerBundle(PowerArcaneShot, false, arcaneShotPowers); + _ = ActionDefinitionBuilder .Create(MetamagicToggle, "ArcaneArcherToggle") .SetOrUpdateGuiPresentation(Category.Action) @@ -106,16 +107,6 @@ public MartialArcaneArcher() new ValidateDefinitionApplication(ValidatorsCharacter.HasAvailablePowerUsage(PowerArcaneShot))) .AddToDB(); - CreateArcaneArcherChoices(arcaneShotPowers); - - PowerBundle.RegisterPowerBundle(PowerArcaneShot, false, arcaneShotPowers); - - ModifyPowerArcaneShotAdditionalUse1 = FeatureDefinitionPowerUseModifierBuilder - .Create($"PowerUseModifier{Name}ArcaneShotUse1") - .SetGuiPresentation(Category.Feature) - .SetFixedValue(PowerArcaneShot, 1) - .AddToDB(); - var powerArcaneShotAdditionalUse2 = FeatureDefinitionPowerUseModifierBuilder .Create($"PowerUseModifier{Name}ArcaneShotUse2") .SetGuiPresentationNoContent(true) @@ -242,7 +233,7 @@ private static List BuildArcaneShotPowers( out FeatureDefinitionPower powerBurstingArrow, out FeatureDefinitionPower powerBurstingArrowDamage) { - var result = new List(); + var powers = new List(); // Banishing Arrow @@ -277,7 +268,7 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerBanishingArrow); + powers.Add(powerBanishingArrow); // Beguiling Arrow @@ -312,7 +303,7 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerBeguilingArrow); + powers.Add(powerBeguilingArrow); // Bursting Arrow @@ -348,7 +339,7 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerBurstingArrow); + powers.Add(powerBurstingArrow); // Enfeebling Arrow @@ -411,7 +402,7 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerEnfeeblingArrow); + powers.Add(powerEnfeeblingArrow); // Grasping Arrow @@ -453,7 +444,7 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerGraspingArrow); + powers.Add(powerGraspingArrow); // Insight Arrow @@ -504,7 +495,7 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerInsightArrow); + powers.Add(powerInsightArrow); // Shadow Arrow @@ -539,7 +530,7 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerShadowArrow); + powers.Add(powerShadowArrow); // Slowing Arrow @@ -574,13 +565,10 @@ private static List BuildArcaneShotPowers( .Build()) .AddToDB(); - result.Add(powerSlowingArrow); + powers.Add(powerSlowingArrow); - return result; - } + // create UI choices - private static void CreateArcaneArcherChoices(IEnumerable powers) - { foreach (var power in powers) { var name = power.Name.Replace("Power", string.Empty); @@ -594,6 +582,8 @@ private static void CreateArcaneArcherChoices(IEnumerable() as GameLocationActionManager; + var rulesetAttacker = attacker.RulesetCharacter; + var levels = rulesetAttacker.GetClassLevel(CharacterClassDefinitions.Fighter); - if (!actionManager) - { - yield break; - } - - if (defender.RulesetActor is not { IsDeadOrDyingOrUnconscious: false }) + if (!actionManager || + !attacker.UsedSpecialFeatures.TryGetValue(powerPsionicAdept.Name, out var value) || value == 0 || + defender.RulesetActor is not { IsDeadOrDyingOrUnconscious: false } || + levels < 7) { yield break; } - var rulesetCharacter = attacker.RulesetCharacter; - - var levels = rulesetCharacter.GetClassLevel(CharacterClassDefinitions.Fighter); - - if (levels < 7) - { - yield break; - } + attacker.UsedSpecialFeatures[powerPsionicAdept.Name] = 0; var implementationManager = ServiceRepository.GetService() as RulesetImplementationManager; - var usablePower = PowerProvider.Get(powerPsionicAdept, rulesetCharacter); + var usablePower = PowerProvider.Get(powerPsionicAdept, rulesetAttacker); var actionParams = new CharacterActionParams(attacker, ActionDefinitions.Id.PowerNoCost) { ActionModifiers = { new ActionModifier() }, StringParameter = "PsionicAdept", RulesetEffect = implementationManager - .MyInstantiateEffectPower(rulesetCharacter, usablePower, false), + .MyInstantiateEffectPower(rulesetAttacker, usablePower, false), UsablePower = usablePower, TargetCharacters = { defender } }; @@ -701,19 +687,6 @@ public IEnumerator OnPhysicalAttackFinishedByMe( yield return battleManager.WaitForReactions(attacker, actionManager, count); } - - public IEnumerator OnPhysicalAttackInitiatedByMe( - GameLocationBattleManager battleManager, - CharacterAction action, - GameLocationCharacter attacker, - GameLocationCharacter defender, - ActionModifier attackModifier, - RulesetAttackMode attackMode) - { - _considerTriggerPsionicAdept = false; - - yield break; - } } private sealed class ModifyAdditionalDamagePoweredStrike( @@ -934,33 +907,71 @@ public void OnSavingThrowInitiated( int outcomeDelta, List effectForms) { - var intelligence = defender.TryGetAttributeValue(AttributeDefinitions.Intelligence); + var changed = false; + var intelligence = ComputeBaseBonus(defender, AttributeDefinitions.Intelligence, out var intModifier); if (abilityScoreName == AttributeDefinitions.Wisdom) { - var wisdom = defender.TryGetAttributeValue(AttributeDefinitions.Wisdom); + var wisdom = ComputeBaseBonus(defender, AttributeDefinitions.Wisdom, out _); if (intelligence > wisdom) { abilityScoreName = AttributeDefinitions.Intelligence; - - defender.LogCharacterUsedFeature(featureForceOfWill); + changed = true; } } - // ReSharper disable once InvertIf if (abilityScoreName == AttributeDefinitions.Charisma) { - var charisma = defender.TryGetAttributeValue(AttributeDefinitions.Charisma); + var charisma = ComputeBaseBonus(defender, AttributeDefinitions.Charisma, out _); - // ReSharper disable once InvertIf if (intelligence > charisma) { abilityScoreName = AttributeDefinitions.Intelligence; - - defender.LogCharacterUsedFeature(featureForceOfWill); + changed = true; } } + + if (!changed) + { + return; + } + + saveBonus = intelligence; + modifierTrends.RemoveAll(x => + x.sourceType is FeatureSourceType.AbilityScore or FeatureSourceType.Proficiency); + modifierTrends.AddRange(intModifier); + defender.LogCharacterUsedFeature(featureForceOfWill); + } + + private static int ComputeBaseBonus( + RulesetCharacter defender, + string abilityScoreName, + out List savingThrowModifierTrends) + { + savingThrowModifierTrends = []; + + var bonus = + AttributeDefinitions.ComputeAbilityScoreModifier(defender.TryGetAttributeValue(abilityScoreName)); + + savingThrowModifierTrends.Add(new TrendInfo(bonus, FeatureSourceType.AbilityScore, abilityScoreName, null)); + + var proficiency = defender.GetFeaturesByType() + .FirstOrDefault(x => + x.ProficiencyType == ProficiencyType.SavingThrow && + x.Proficiencies.Contains(abilityScoreName)); + + if (!proficiency) + { + return bonus; + } + + var pb = defender.TryGetAttributeValue(AttributeDefinitions.ProficiencyBonus); + + bonus += pb; + savingThrowModifierTrends.Add(new TrendInfo(pb, FeatureSourceType.Proficiency, string.Empty, null)); + + return bonus; } } diff --git a/SolastaUnfinishedBusiness/Subclasses/MartialTactician.cs b/SolastaUnfinishedBusiness/Subclasses/MartialTactician.cs index c928b354c8..e4638a6291 100644 --- a/SolastaUnfinishedBusiness/Subclasses/MartialTactician.cs +++ b/SolastaUnfinishedBusiness/Subclasses/MartialTactician.cs @@ -33,8 +33,9 @@ public MartialTactician() .Create(Name) .SetGuiPresentation(Category.Subclass, Sprites.GetSprite(Name, Resources.MartialTactician, 256)) - .AddFeaturesAtLevel(3, BuildSharpMind(), GambitsBuilders.GambitPool, - GambitsBuilders.Learn3Gambit) + .AddFeaturesAtLevel(3, BuildSharpMind(), BuildGambitPoolIncrease(3, Name), + GambitsBuilders.Learn3Gambit, + GambitsBuilders.GambitPool) .AddFeaturesAtLevel(7, BuildHonedCraft(), BuildGambitPoolIncrease(), GambitsBuilders.Learn2Gambit, unlearn) diff --git a/SolastaUnfinishedBusiness/Subclasses/OathOfHatred.cs b/SolastaUnfinishedBusiness/Subclasses/OathOfHatred.cs index 92d03c9b74..6c3eedb948 100644 --- a/SolastaUnfinishedBusiness/Subclasses/OathOfHatred.cs +++ b/SolastaUnfinishedBusiness/Subclasses/OathOfHatred.cs @@ -209,15 +209,9 @@ public OathOfHatred() // ReSharper disable once UnassignedGetOnlyAutoProperty internal override DeityDefinition DeityDefinition { get; } - private sealed class PhysicalAttackFinishedByMeDauntlessPursuer : IPhysicalAttackFinishedByMe + private sealed class PhysicalAttackFinishedByMeDauntlessPursuer( + ConditionDefinition conditionDauntlessPursuerAfterAttack) : IPhysicalAttackFinishedByMe { - private readonly ConditionDefinition _conditionDauntlessPursuerAfterAttack; - - internal PhysicalAttackFinishedByMeDauntlessPursuer(ConditionDefinition conditionDauntlessPursuerAfterAttack) - { - _conditionDauntlessPursuerAfterAttack = conditionDauntlessPursuerAfterAttack; - } - public IEnumerator OnPhysicalAttackFinishedByMe( GameLocationBattleManager battleManager, CharacterAction action, @@ -241,7 +235,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( var rulesetAttacker = attacker.RulesetCharacter; rulesetAttacker.InflictCondition( - _conditionDauntlessPursuerAfterAttack.Name, + conditionDauntlessPursuerAfterAttack.Name, DurationType.Round, 1, TurnOccurenceType.StartOfTurn, @@ -249,7 +243,7 @@ public IEnumerator OnPhysicalAttackFinishedByMe( rulesetAttacker.guid, rulesetAttacker.CurrentFaction.Name, 1, - _conditionDauntlessPursuerAfterAttack.Name, + conditionDauntlessPursuerAfterAttack.Name, 0, 0, 0); diff --git a/SolastaUnfinishedBusiness/Subclasses/PathOfTheWildMagic.cs b/SolastaUnfinishedBusiness/Subclasses/PathOfTheWildMagic.cs index 5f1a4221ff..6b09df9427 100644 --- a/SolastaUnfinishedBusiness/Subclasses/PathOfTheWildMagic.cs +++ b/SolastaUnfinishedBusiness/Subclasses/PathOfTheWildMagic.cs @@ -537,12 +537,10 @@ private static WildSurgeEffect BuildWildSurgeAura() .SetRecurrentEffect( RecurrentEffect.OnActivation | RecurrentEffect.OnEnter | RecurrentEffect.OnTurnStart) .SetEffectForms( - //TODO: why adding double condition to self? EffectFormBuilder .Create() .SetConditionForm(conditionAuraBonus, ConditionForm.ConditionOperation.Add) - .Build() - ) + .Build()) .Build()) .AddToDB(); @@ -1095,7 +1093,8 @@ private sealed class WildSurgeWeaponModifyAttackMode : IModifyWeaponAttackMode { public void ModifyAttackMode(RulesetCharacter character, RulesetAttackMode attackMode) { - if (ValidatorsWeapon.IsMelee(attackMode)) + // don't use IsMelee(attackMode) in IModifyWeaponAttackMode as it will always fail + if (ValidatorsWeapon.IsMelee(attackMode.SourceObject as RulesetItem)) { attackMode.AddAttackTagAsNeeded(TagsDefinitions.WeaponTagThrown); attackMode.thrown = true; diff --git a/SolastaUnfinishedBusiness/Subclasses/SorcerousPsion.cs b/SolastaUnfinishedBusiness/Subclasses/SorcerousPsion.cs index d79c3fc93c..0e36240d9e 100644 --- a/SolastaUnfinishedBusiness/Subclasses/SorcerousPsion.cs +++ b/SolastaUnfinishedBusiness/Subclasses/SorcerousPsion.cs @@ -205,8 +205,7 @@ public SorcerousPsion() .SetMotionForm(MotionForm.MotionType.FallProne) .Build()) .SetParticleEffectParameters(PowerDomainSunHeraldOfTheSun) - .SetCasterEffectParameters(PowerPatronFiendDarkOnesBlessing.EffectDescription - .EffectParticleParameters.effectParticleReference) + .SetCasterEffectParameters(PowerDomainBattleDecisiveStrike) .Build()) .AddToDB(); @@ -269,7 +268,7 @@ public SorcerousPsion() private sealed class CustomBehaviorMindSculpt : IMagicEffectBeforeHitConfirmedOnEnemy, IMagicEffectFinishedByMe { - private bool _hasDamageChanged; + private const string MindSculptTag = "MindSculptTag"; public IEnumerator OnMagicEffectBeforeHitConfirmedOnEnemy( GameLocationBattleManager battleManager, @@ -281,8 +280,9 @@ public IEnumerator OnMagicEffectBeforeHitConfirmedOnEnemy( bool firstTarget, bool criticalHit) { - _hasDamageChanged = false; + attacker.UsedSpecialFeatures.TryAdd(MindSculptTag, 0); + var hasDamageChanged = false; var rulesetCharacter = attacker.RulesetCharacter; if (rulesetCharacter.RemainingSorceryPoints > 0 && @@ -291,11 +291,13 @@ public IEnumerator OnMagicEffectBeforeHitConfirmedOnEnemy( foreach (var effectForm in actualEffectForms .Where(x => x.FormType == EffectForm.EffectFormType.Damage)) { - _hasDamageChanged = _hasDamageChanged || effectForm.DamageForm.DamageType != DamageTypePsychic; + hasDamageChanged = hasDamageChanged || effectForm.DamageForm.DamageType != DamageTypePsychic; effectForm.DamageForm.DamageType = DamageTypePsychic; } } + attacker.UsedSpecialFeatures[MindSculptTag] = hasDamageChanged ? 1 : 0; + if (!firstTarget) { yield break; @@ -318,12 +320,12 @@ public IEnumerator OnMagicEffectFinishedByMe( GameLocationCharacter attacker, List targets) { - if (!_hasDamageChanged) + if (!attacker.UsedSpecialFeatures.TryGetValue(MindSculptTag, out var value) || value == 0) { yield break; } - _hasDamageChanged = false; + attacker.UsedSpecialFeatures.TryAdd(MindSculptTag, 0); var rulesetAttacker = attacker.RulesetCharacter; @@ -412,25 +414,21 @@ public IEnumerator HandleReducedToZeroHpByEnemy( private sealed class CustomBehaviorSupremeWill(FeatureDefinitionPower powerSupremeWill) : IModifyConcentrationRequirement, IMagicEffectFinishedByMe { - private bool _hasConcentrationChanged; - public IEnumerator OnMagicEffectFinishedByMe( CharacterActionMagicEffect action, GameLocationCharacter attacker, List targets) { - if (action is not CharacterActionCastSpell actionCastSpell) - { - yield break; - } + var hasTag = attacker.UsedSpecialFeatures.TryGetValue(powerSupremeWill.Name, out var value); + + attacker.UsedSpecialFeatures.TryAdd(powerSupremeWill.Name, 0); - if (!_hasConcentrationChanged) + if (action is not CharacterActionCastSpell actionCastSpell || + !hasTag || value == 0) { yield break; } - _hasConcentrationChanged = false; - var rulesetCharacter = attacker.RulesetCharacter; var usablePower = PowerProvider.Get(powerSupremeWill, rulesetCharacter); @@ -456,11 +454,14 @@ public bool RequiresConcentration(RulesetCharacter rulesetCharacter, RulesetEffe return rulesetEffectSpell.SpellDefinition.RequiresConcentration; } + var attacker = GameLocationCharacter.GetFromActor(rulesetCharacter); var requiredPoints = rulesetEffectSpell.EffectLevel * 2; + var hasConcentrationChanged = rulesetCharacter.RemainingSorceryPoints >= requiredPoints; - _hasConcentrationChanged = rulesetCharacter.RemainingSorceryPoints >= requiredPoints; + attacker.UsedSpecialFeatures.TryAdd(powerSupremeWill.Name, 0); + attacker.UsedSpecialFeatures[powerSupremeWill.Name] = hasConcentrationChanged ? 1 : 0; - return !_hasConcentrationChanged; + return !hasConcentrationChanged; } } } diff --git a/SolastaUnfinishedBusiness/Subclasses/WayOfTheZenArchery.cs b/SolastaUnfinishedBusiness/Subclasses/WayOfTheZenArchery.cs index 0d26a6979f..03e6bada5a 100644 --- a/SolastaUnfinishedBusiness/Subclasses/WayOfTheZenArchery.cs +++ b/SolastaUnfinishedBusiness/Subclasses/WayOfTheZenArchery.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using SolastaUnfinishedBusiness.Api; @@ -20,7 +21,8 @@ namespace SolastaUnfinishedBusiness.Subclasses; public sealed class WayOfZenArchery : AbstractSubclass { internal const string Name = "WayOfZenArchery"; - internal const string HailOfArrows = "HailOfArrows"; + internal const string HailOfArrowsAttack = "HailOfArrowsAttack"; + internal const string HailOfArrowsAttacksTab = "HailOfArrowsAttacksTab"; internal const int StunningStrikeWithBowAllowedLevel = 6; public WayOfZenArchery() @@ -270,7 +272,7 @@ public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, attackMode.Copy(attackModeMain); attackMode.ActionType = ActionDefinitions.ActionType.NoCost; - attackMode.AttackTags.Add(HailOfArrows); + actingCharacter.UsedSpecialFeatures.TryAdd(HailOfArrowsAttack, 0); // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator foreach (var target in targets) diff --git a/SolastaUnfinishedBusiness/Translations/ru/Others-ru.txt b/SolastaUnfinishedBusiness/Translations/ru/Others-ru.txt index 7d0eb3baaf..f88c3bba03 100644 --- a/SolastaUnfinishedBusiness/Translations/ru/Others-ru.txt +++ b/SolastaUnfinishedBusiness/Translations/ru/Others-ru.txt @@ -175,7 +175,7 @@ Feedback/&AdditionalDamageBrutalStrikeFormat=Жестокий удар Feedback/&AdditionalDamageBrutalStrikeLine=Жестокий удар наносит дополнительно +{2} урона! Feedback/&AdditionalDamageSunderingBlowFormat=Раскалывающий удар Feedback/&AdditionalDamageSunderingBlowLine=Раскалывающий наносит дополнительно +{2} урона! -Feedback/&ChangeGloombladeDieType={1} меняет тип кубика мрачного клинка с {2} на {3} +Feedback/&ChangeGloombladeDieType={1} меняет тип кости сумрачного клинка с {2} на {3} Feedback/&ChangeSneakDiceDamageType={1} изменяет урон костей скрытой атаки на {2} Feedback/&ChangeSneakDiceDieType={1} изменяет тип кости скрытой атаки с {2} на {3} Feedback/&ChangeSneakDiceNumber={1} изменяет число на кости скрытой атаки с {2} на {3}. diff --git a/SolastaUnfinishedBusiness/Translations/ru/SubClasses/RoguishDuelist-ru.txt b/SolastaUnfinishedBusiness/Translations/ru/SubClasses/RoguishDuelist-ru.txt index f513949599..8dbb6ae2d9 100644 --- a/SolastaUnfinishedBusiness/Translations/ru/SubClasses/RoguishDuelist-ru.txt +++ b/SolastaUnfinishedBusiness/Translations/ru/SubClasses/RoguishDuelist-ru.txt @@ -10,7 +10,7 @@ Feature/&FeatureSetRoguishDuelistMasterDuelistDescription=Сразу после Feature/&FeatureSetRoguishDuelistMasterDuelistTitle=Мастер дуэлянт Feature/&FeatureSetRoguishDuelistSureFootedDescription=Когда вы совершаете скрытую атаку по существу рукопашным оружием, вы получаете несуммируемый бонус 1d6 к вашему КД до начала вашего следующего хода. Feature/&FeatureSetRoguishDuelistSureFootedTitle=Бравада -Feedback/&RoguishDuelistBravado={0} бросает кубик {2}, добавляя {3} к AC. -Feedback/&RoguishDuelistBravadoReroll={0} бросает {3} на кубике {2}, но сохраняет предыдущий бонус {4} к AC. +Feedback/&RoguishDuelistBravado={0} бросает кость {2}, добавляя {3} к КД. +Feedback/&RoguishDuelistBravadoReroll={0} выкидывает {3} на кости {2}, но сохраняет предыдущий бонус {4} к КД. Subclass/&RoguishDuelistDescription=Дуэлисты отличаются заносчивой бравадой. Они быстро соображают, независимы и опасны для одиночных противников. Subclass/&RoguishDuelistTitle=Дуэлянт diff --git a/SolastaUnfinishedBusiness/Validators/ValidatorsWeapon.cs b/SolastaUnfinishedBusiness/Validators/ValidatorsWeapon.cs index 4e50d16aa1..a6f4e39692 100644 --- a/SolastaUnfinishedBusiness/Validators/ValidatorsWeapon.cs +++ b/SolastaUnfinishedBusiness/Validators/ValidatorsWeapon.cs @@ -1,6 +1,7 @@ using System.Linq; using System.Runtime.CompilerServices; using JetBrains.Annotations; +using SolastaUnfinishedBusiness.Api.GameExtensions; using SolastaUnfinishedBusiness.Behaviors.Specific; using static RuleDefinitions; using static SolastaUnfinishedBusiness.Api.DatabaseHelper.ArmorTypeDefinitions; @@ -82,12 +83,17 @@ private static bool IsMelee([CanBeNull] ItemDefinition itemDefinition) || itemDefinition.IsArmor /* for shields */); } +#pragma warning disable IDE0060 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsMelee( - [CanBeNull] RulesetAttackMode attackMode, [CanBeNull] RulesetItem rulesetItem, RulesetCharacter _) + [UsedImplicitly] [CanBeNull] RulesetAttackMode attackMode, + [CanBeNull] RulesetItem rulesetItem, + [UsedImplicitly] RulesetCharacter rulesetCharacter) { - return attackMode != null ? IsMelee(attackMode) : IsMelee(rulesetItem); + // don't use IsMelee(attackMode) in here as these are used before an attack initiates + return IsMelee(attackMode?.SourceObject as RulesetItem ?? rulesetItem ?? rulesetCharacter?.GetMainWeapon()); } +#pragma warning restore IDE0060 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static bool IsMelee([CanBeNull] RulesetItem rulesetItem) diff --git a/SolastaUnfinishedBusiness/_Globals.cs b/SolastaUnfinishedBusiness/_Globals.cs index bf6f118589..7697aeb03b 100644 --- a/SolastaUnfinishedBusiness/_Globals.cs +++ b/SolastaUnfinishedBusiness/_Globals.cs @@ -1,22 +1,17 @@ -using System.Collections.Generic; -using JetBrains.Annotations; +using JetBrains.Annotations; namespace SolastaUnfinishedBusiness; internal static class Global { - // required to correctly determine isMelee validation and ensure Zen Archer Hail of Arrows won't trigger PowerMonkMartialArts - internal static readonly Stack CurrentAttackAction = new(); - // true if in a multiplayer game internal static bool IsMultiplayer => - IsSettingUpMultiplayer - || ServiceRepository.GetService().IsMultiplayerGame; + IsSettingUpMultiplayer || ServiceRepository.GetService().IsMultiplayerGame; // true if on multiplayer setup screen internal static bool IsSettingUpMultiplayer { get; set; } - //PATCH: Keeps last level up hero selected + // last level up hero name internal static string LastLevelUpHeroName { get; set; } // level up hero @@ -32,25 +27,17 @@ private static GameLocationCharacter SelectedLocationCharacter { get { - var exploration = Gui.GuiService.GetScreen(); - - if (exploration.Visible) - { - return exploration.CharacterControlPanel.GuiCharacter?.GameLocationCharacter; - } - - var battle = Gui.GuiService.GetScreen(); + var gameLocationScreenExploration = Gui.GuiService.GetScreen(); + var gameLocationScreenBattle = Gui.GuiService.GetScreen(); - return battle.Visible - ? battle.CharacterControlPanel.GuiCharacter?.GameLocationCharacter - : null; + return gameLocationScreenExploration.Visible + ? gameLocationScreenExploration.CharacterControlPanel.GuiCharacter?.GameLocationCharacter + : gameLocationScreenBattle.CharacterControlPanel.GuiCharacter?.GameLocationCharacter; } } - //PATCH: used in UI references + // used in UI references [CanBeNull] internal static RulesetCharacter CurrentCharacter => - InspectedHero - ?? LevelUpHero - ?? SelectedLocationCharacter?.RulesetCharacter; + InspectedHero ?? LevelUpHero ?? SelectedLocationCharacter?.RulesetCharacter; }