From f2c566b0ca87983e013477745523c8f6aeabcef2 Mon Sep 17 00:00:00 2001 From: ThyWolf Date: Wed, 17 Jan 2024 22:12:27 -0800 Subject: [PATCH] improve obscurement rules after first QA round --- .../Api/DatabaseHelper-RELEASE.cs | 22 ++- .../GameLocationCharacterExtensions.cs | 5 +- ...GameLocationVisibilityManagerExtensions.cs | 4 +- .../GameExtensions/RulesetActorExtensions.cs | 13 +- .../Displays/RulesDisplay.cs | 1 + .../Models/SrdAndHouseRulesContext.cs | 172 +++++++++++++----- .../ConsiderationCanCastMagicPatcher.cs | 3 +- .../CursorLocationSelectTargetPatcher.cs | 2 +- 8 files changed, 149 insertions(+), 73 deletions(-) diff --git a/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs b/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs index 918dccd064..a0d718d58e 100644 --- a/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs +++ b/SolastaUnfinishedBusiness/Api/DatabaseHelper-RELEASE.cs @@ -9,6 +9,12 @@ namespace SolastaUnfinishedBusiness.Api; internal static partial class DatabaseHelper { + internal static class FeatureDefinitionPerceptionAffinitys + { + internal static FeatureDefinitionPerceptionAffinity PerceptionAffinityConditionBlinded { get; } = + GetDefinition("PerceptionAffinityConditionBlinded"); + } + internal static class ActionDefinitions { internal static ActionDefinition ActionSurge { get; } = GetDefinition("ActionSurge"); @@ -316,6 +322,12 @@ internal static class ConditionDefinitions internal static ConditionDefinition ConditionBlinded { get; } = GetDefinition("ConditionBlinded"); + internal static ConditionDefinition ConditionBlinded_Sunburst { get; } = + GetDefinition("ConditionBlinded_Sunburst"); + + internal static ConditionDefinition ConditionBlindedEndOfNextTurn { get; } = + GetDefinition("ConditionBlindedEndOfNextTurn"); + internal static ConditionDefinition ConditionBlurred { get; } = GetDefinition("ConditionBlurred"); @@ -1216,9 +1228,6 @@ internal static class FeatureDefinitionCombatAffinitys internal static FeatureDefinitionCombatAffinity CombatAffinityStealthy { get; } = GetDefinition("CombatAffinityStealthy"); - - internal static FeatureDefinitionCombatAffinity CombatAffinityVeil { get; } = - GetDefinition("CombatAffinityVeil"); } internal static class FeatureDefinitionConditionAffinitys @@ -1273,6 +1282,9 @@ internal static FeatureDefinitionConditionAffinity internal static FeatureDefinitionConditionAffinity ConditionAffinityHinderedByFrostImmunity { get; } = GetDefinition("ConditionAffinityHinderedByFrostImmunity"); + internal static FeatureDefinitionConditionAffinity ConditionAffinityInvocationDevilsSight { get; } = + GetDefinition("ConditionAffinityInvocationDevilsSight"); + internal static FeatureDefinitionConditionAffinity ConditionAffinityMindControlledImmunity { get; } = GetDefinition("ConditionAffinityMindControlledImmunity"); @@ -1294,7 +1306,6 @@ internal static FeatureDefinitionConditionAffinity internal static FeatureDefinitionConditionAffinity ConditionAffinityRestrainedmmunity { get; } = GetDefinition("ConditionAffinityRestrainedmmunity"); - internal static FeatureDefinitionConditionAffinity ConditionAffinityVeilImmunity { get; } = GetDefinition("ConditionAffinityVeilImmunity"); @@ -1646,6 +1657,9 @@ internal static class FeatureDefinitionPowers internal static FeatureDefinitionPower PowerCollegeLoreCuttingWords { get; } = GetDefinition("PowerCollegeLoreCuttingWords"); + internal static FeatureDefinitionPower PowerDefilerDarkness { get; } = + GetDefinition("PowerDefilerDarkness"); + internal static FeatureDefinitionPower PowerDruidCircleBalanceBalanceOfPower { get; } = GetDefinition("PowerDruidCircleBalanceBalanceOfPower"); diff --git a/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs b/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs index 65e7585ccd..ec3c712425 100644 --- a/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs +++ b/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationCharacterExtensions.cs @@ -27,7 +27,7 @@ public static bool IsWithinRange(this GameLocationCharacter source, GameLocation return int3.Distance(source.LocationPosition, target.LocationPosition) <= range; } - public static bool IsMagicEffectValidUnderObscurementOrMagicalDarkness( + public static bool IsMagicEffectValidUnderBlindness( this GameLocationCharacter source, IMagicEffect magicEffect, GameLocationCharacter target) @@ -45,8 +45,7 @@ public static bool IsMagicEffectValidUnderObscurementOrMagicalDarkness( var rulesetSource = source.RulesetActor; var rulesetTarget = target.RulesetActor; - if (!rulesetSource.IsUnderHeavyObscurementOrMagicalDarkness() && - !rulesetTarget.IsUnderHeavyObscurementOrMagicalDarkness()) + if (!rulesetSource.HasBlindness() && !rulesetTarget.HasBlindness()) { return true; } diff --git a/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationVisibilityManagerExtensions.cs b/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationVisibilityManagerExtensions.cs index 2f8f7c139a..32e8159f0f 100644 --- a/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationVisibilityManagerExtensions.cs +++ b/SolastaUnfinishedBusiness/Api/GameExtensions/GameLocationVisibilityManagerExtensions.cs @@ -37,9 +37,7 @@ public static bool MyIsCellPerceivedByCharacter( var inRange = false; var distance = DistanceCalculation.GetDistanceFromTwoPositions(sensor.LocationPosition, cellPosition); var lightingState = sensor.ComputeLightingStateOnTargetPosition(cellPosition); - var nonMagicalDarkness = - target != null && target.RulesetCharacter.IsUnderHeavyObscurement(); - + var nonMagicalDarkness = target?.LightingState != LocationDefinitions.LightingState.Darkness; var selectedSenseType = SenseMode.Type.None; var selectedSenseRange = 0; diff --git a/SolastaUnfinishedBusiness/Api/GameExtensions/RulesetActorExtensions.cs b/SolastaUnfinishedBusiness/Api/GameExtensions/RulesetActorExtensions.cs index cd3f9b54d7..e0dbdfc80b 100644 --- a/SolastaUnfinishedBusiness/Api/GameExtensions/RulesetActorExtensions.cs +++ b/SolastaUnfinishedBusiness/Api/GameExtensions/RulesetActorExtensions.cs @@ -199,18 +199,9 @@ internal static bool IsTemporarilyFlying(this RulesetActor actor) );*/ } - internal static bool IsUnderHeavyObscurement(this RulesetActor character) + internal static bool HasBlindness(this RulesetActor character) { - return character.HasConditionOfType(DatabaseHelper.ConditionDefinitions.ConditionHeavilyObscured.Name) || - character.HasConditionOfType(DatabaseHelper.ConditionDefinitions.ConditionInStinkingCloud.Name) || - character.HasConditionOfType(DatabaseHelper.ConditionDefinitions.ConditionSleetStorm.Name); - } - - internal static bool IsUnderHeavyObscurementOrMagicalDarkness(this RulesetActor character) - { - return character.IsUnderHeavyObscurement() || - character.HasConditionOfType(DatabaseHelper.ConditionDefinitions.ConditionDarkness.Name) || - character.HasConditionOfType(DatabaseHelper.ConditionDefinitions.ConditionVeil.Name); + return character.HasConditionOfTypeOrSubType(DatabaseHelper.ConditionDefinitions.ConditionBlinded.Name); } internal static bool HasAnyConditionOfType(this RulesetActor actor, params string[] conditions) diff --git a/SolastaUnfinishedBusiness/Displays/RulesDisplay.cs b/SolastaUnfinishedBusiness/Displays/RulesDisplay.cs index 8d32bdabbc..9b193cad46 100644 --- a/SolastaUnfinishedBusiness/Displays/RulesDisplay.cs +++ b/SolastaUnfinishedBusiness/Displays/RulesDisplay.cs @@ -96,6 +96,7 @@ internal static void DisplayRules() SrdAndHouseRulesContext.SwitchOfficialObscurementRules(); } + UI.Label(); UI.Label(); UI.Label(); diff --git a/SolastaUnfinishedBusiness/Models/SrdAndHouseRulesContext.cs b/SolastaUnfinishedBusiness/Models/SrdAndHouseRulesContext.cs index 5b919dc529..49e6c40763 100644 --- a/SolastaUnfinishedBusiness/Models/SrdAndHouseRulesContext.cs +++ b/SolastaUnfinishedBusiness/Models/SrdAndHouseRulesContext.cs @@ -16,6 +16,8 @@ using static SolastaUnfinishedBusiness.Api.DatabaseHelper.SpellDefinitions; using static SolastaUnfinishedBusiness.Api.DatabaseHelper.MonsterDefinitions; using static SolastaUnfinishedBusiness.Api.DatabaseHelper.FeatureDefinitionCombatAffinitys; +using static SolastaUnfinishedBusiness.Api.DatabaseHelper.FeatureDefinitionConditionAffinitys; +using static SolastaUnfinishedBusiness.Api.DatabaseHelper.FeatureDefinitionPowers; using static SolastaUnfinishedBusiness.Api.DatabaseHelper.FeatureDefinitionSenses; using static SolastaUnfinishedBusiness.Api.DatabaseHelper.CharacterClassDefinitions; using static SolastaUnfinishedBusiness.Api.DatabaseHelper.ItemDefinitions; @@ -77,13 +79,10 @@ internal static class SrdAndHouseRulesContext .SetForbiddenActions(Id.AttackOpportunity) .AddToDB(); - private static readonly FeatureDefinitionConditionAffinity ConditionAffinityDarknessImmunity = - FeatureDefinitionConditionAffinityBuilder - .Create("ConditionAffinityDarknessImmunity") - .SetGuiPresentationNoContent(true) - .SetConditionType(ConditionDefinitions.ConditionDarkness) - .SetConditionAffinityType(ConditionAffinityType.Immunity) - .AddToDB(); + private static readonly EffectForm FormBlinded = EffectFormBuilder + .Create() + .SetConditionForm(ConditionDefinitions.ConditionBlinded, ConditionForm.ConditionOperation.Add) + .Build(); private static SpellDefinition ConjureElementalInvisibleStalker { get; set; } @@ -302,17 +301,11 @@ internal static void ApplyConditionBlindedShouldNotAllowOpportunityAttack() { if (Main.Settings.BlindedConditionDontAllowAttackOfOpportunity) { - if (!ConditionDefinitions.ConditionBlinded.Features.Contains(ActionAffinityConditionBlind)) - { - ConditionDefinitions.ConditionBlinded.Features.Add(ActionAffinityConditionBlind); - } + ConditionDefinitions.ConditionBlinded.Features.TryAdd(ActionAffinityConditionBlind); } else { - if (ConditionDefinitions.ConditionBlinded.Features.Contains(ActionAffinityConditionBlind)) - { - ConditionDefinitions.ConditionBlinded.Features.Remove(ActionAffinityConditionBlind); - } + ConditionDefinitions.ConditionBlinded.Features.Remove(ActionAffinityConditionBlind); } } @@ -409,55 +402,136 @@ internal static void SwitchOfficialObscurementRules() { if (Main.Settings.UseOfficialLightingObscurementAndVisionRules) { - foreach (var monster in DatabaseRepository.GetDatabase() - .Where(x => x.Features.Contains( - FeatureDefinitionConditionAffinitys.ConditionAffinityVeilImmunity))) + // reuse blinded condition as many depend on it as parent + ConditionDefinitions.ConditionBlinded.Features.SetRange( + CombatAffinityHeavilyObscured, + CombatAffinityHeavilyObscuredSelf, + FeatureDefinitionPerceptionAffinitys.PerceptionAffinityConditionBlinded); + + if (Main.Settings.BlindedConditionDontAllowAttackOfOpportunity) { - monster.Features.Add(ConditionAffinityDarknessImmunity); + ConditionDefinitions.ConditionBlinded.Features.Add(ActionAffinityConditionBlind); } - // vanilla has this set as disadvantage so we flip it and update senses + ConditionDefinitions.ConditionBlinded.GuiPresentation.description = + "Rules/&ConditionBlindedExtendedDescription"; + + // >> ConditionVeil + // ConditionAffinityVeilImmunity + // PowerDefilerDarkness + + ConditionAffinityVeilImmunity.conditionType = + ConditionDefinitions.ConditionBlinded.Name; + + PowerDefilerDarkness.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionDefinitions.ConditionBlinded; + + // >> ConditionDarkness + // ConditionAffinityInvocationDevilsSight + // Darkness + + ConditionAffinityInvocationDevilsSight.conditionType = + ConditionDefinitions.ConditionBlinded.Name; + + Darkness.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionDefinitions.ConditionBlinded; + + // >> ConditionHeavilyObscured + // FogCloud + + FogCloud.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionDefinitions.ConditionBlinded; + + // >> ConditionInStinkingCloud + // StinkingCloud + + StinkingCloud.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionDefinitions.ConditionBlinded; + + // >> ConditionSleetStorm + // SleetStorm + + SleetStorm.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionDefinitions.ConditionBlinded; + + // Cloud Kill / Incendiary Cloud + + CloudKill.EffectDescription.EffectForms.Add(FormBlinded); + IncendiaryCloud.EffectDescription.EffectForms.Add(FormBlinded); + + // vanilla has this set as disadvantage so we flip it with nullified requirements CombatAffinityHeavilyObscured.attackOnMeAdvantage = AdvantageType.Advantage; (CombatAffinityHeavilyObscured.nullifiedBySenses, CombatAffinityHeavilyObscured.nullifiedBySelfSenses) = (CombatAffinityHeavilyObscured.nullifiedBySelfSenses, CombatAffinityHeavilyObscured.nullifiedBySenses); - // vanilla reuses Heavily Obscured terms - ConditionDefinitions.ConditionDarkness.GuiPresentation.title = - "Tooltip/&LightingDarknessFormat"; - ConditionDefinitions.ConditionDarkness.GuiPresentation.description = - "Rules/&ConditionHeavilyObscuredExtendedDescription"; - - ConditionDefinitions.ConditionDarkness.conditionType = ConditionType.Detrimental; - ConditionDefinitions.ConditionDarkness.possessive = false; - ConditionDefinitions.ConditionDarkness.Features.SetRange( - CombatAffinityHeavilyObscured, - CombatAffinityHeavilyObscuredSelf); - - ConditionVeil.Features.SetRange(CombatAffinityHeavilyObscured, CombatAffinityHeavilyObscuredSelf); + // replace sight impaired from all non magical light and heavy obscurement effects + CloudKill.EffectDescription.EffectForms[2].TopologyForm.changeType = TopologyForm.Type.None; + FogCloud.EffectDescription.EffectForms[1].TopologyForm.changeType = TopologyForm.Type.None; + IncendiaryCloud.EffectDescription.EffectForms[2].TopologyForm.changeType = TopologyForm.Type.None; + InsectPlague.effectDescription.EffectForms[1].TopologyForm.changeType = TopologyForm.Type.SightBlocker; + SleetStorm.EffectDescription.EffectForms[5].TopologyForm.changeType = TopologyForm.Type.None; + StinkingCloud.EffectDescription.EffectForms[1].TopologyForm.changeType = TopologyForm.Type.None; } else { - foreach (var monster in DatabaseRepository.GetDatabase() - .Where(x => x.Features.Contains( - FeatureDefinitionConditionAffinitys.ConditionAffinityVeilImmunity))) - { - monster.Features.Remove(ConditionAffinityDarknessImmunity); - } + ConditionDefinitions.ConditionBlinded.GuiPresentation.description = + "Rules/&ConditionBlindedExtendedDescription"; + + // >> ConditionVeil + // ConditionAffinityVeilImmunity + // PowerDefilerDarkness + + ConditionAffinityVeilImmunity.conditionType = + ConditionVeil.Name; + + PowerDefilerDarkness.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionVeil; + + // >> ConditionDarkness + // ConditionAffinityInvocationDevilsSight + // Darkness + + ConditionAffinityInvocationDevilsSight.conditionType = + ConditionDefinitions.ConditionDarkness.Name; + Darkness.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionDefinitions.ConditionDarkness; + + // >> ConditionHeavilyObscured + // FogCloud + + FogCloud.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionHeavilyObscured; + + // >> ConditionInStinkingCloud + // StinkingCloud + + StinkingCloud.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionInStinkingCloud; + + // >> ConditionSleetStorm + // SleetStorm + + SleetStorm.EffectDescription.EffectForms[1].ConditionForm.ConditionDefinition = + ConditionSleetStorm; + + // Cloud Kill / Incendiary Cloud + + CloudKill.EffectDescription.EffectForms.Remove(FormBlinded); + IncendiaryCloud.EffectDescription.EffectForms.Remove(FormBlinded); + + // vanilla has this set as disadvantage so we flip it with nullified requirements CombatAffinityHeavilyObscured.attackOnMeAdvantage = AdvantageType.Disadvantage; (CombatAffinityHeavilyObscured.nullifiedBySelfSenses, CombatAffinityHeavilyObscured.nullifiedBySenses) = (CombatAffinityHeavilyObscured.nullifiedBySenses, CombatAffinityHeavilyObscured.nullifiedBySelfSenses); - ConditionDefinitions.ConditionDarkness.GuiPresentation.title = - "Rules/&ConditionHeavilyObscuredTitle"; - ConditionDefinitions.ConditionDarkness.GuiPresentation.description = - "Rules/&ConditionHeavilyObscuredDescription"; - - ConditionDefinitions.ConditionDarkness.conditionType = ConditionType.Neutral; - ConditionDefinitions.ConditionDarkness.possessive = true; - ConditionDefinitions.ConditionDarkness.Features.SetRange(CombatAffinityVeil); - - ConditionVeil.Features.SetRange(CombatAffinityVeil); + // add sight impaired from all non magical light and heavy obscurement effects + CloudKill.EffectDescription.EffectForms[2].TopologyForm.changeType = TopologyForm.Type.SightImpaired; + FogCloud.EffectDescription.EffectForms[1].TopologyForm.changeType = TopologyForm.Type.SightImpaired; + IncendiaryCloud.EffectDescription.EffectForms[2].TopologyForm.changeType = TopologyForm.Type.SightImpaired; + InsectPlague.effectDescription.EffectForms[1].TopologyForm.changeType = TopologyForm.Type.SightImpaired; + SleetStorm.EffectDescription.EffectForms[5].TopologyForm.changeType = TopologyForm.Type.SightImpaired; + StinkingCloud.EffectDescription.EffectForms[1].TopologyForm.changeType = TopologyForm.Type.SightImpaired; } } diff --git a/SolastaUnfinishedBusiness/Patches/ConsiderationCanCastMagicPatcher.cs b/SolastaUnfinishedBusiness/Patches/ConsiderationCanCastMagicPatcher.cs index 258a4b6103..42c99a60e5 100644 --- a/SolastaUnfinishedBusiness/Patches/ConsiderationCanCastMagicPatcher.cs +++ b/SolastaUnfinishedBusiness/Patches/ConsiderationCanCastMagicPatcher.cs @@ -48,8 +48,7 @@ public static bool Prefix( { // BEGIN PATCH if (!Main.Settings.UseOfficialLightingObscurementAndVisionRules && - !locationCharacter.IsMagicEffectValidUnderObscurementOrMagicalDarkness( - availableMagicEffect, context.character)) + !locationCharacter.IsMagicEffectValidUnderBlindness(availableMagicEffect, context.character)) { Main.Info($"{locationCharacter.Name} => {availableMagicEffect.Name} : OBSCUREMENT DISCARDED"); diff --git a/SolastaUnfinishedBusiness/Patches/CursorLocationSelectTargetPatcher.cs b/SolastaUnfinishedBusiness/Patches/CursorLocationSelectTargetPatcher.cs index d124484e6a..83b8ea6a32 100644 --- a/SolastaUnfinishedBusiness/Patches/CursorLocationSelectTargetPatcher.cs +++ b/SolastaUnfinishedBusiness/Patches/CursorLocationSelectTargetPatcher.cs @@ -37,7 +37,7 @@ public static void Postfix( if (__result && Main.Settings.UseOfficialLightingObscurementAndVisionRules && definition is IMagicEffect magicEffect && - !actingCharacter.IsMagicEffectValidUnderObscurementOrMagicalDarkness(magicEffect, target)) + !actingCharacter.IsMagicEffectValidUnderBlindness(magicEffect, target)) { __instance.actionModifier.FailureFlags.Add("Failure/&FailureFlagNoPerceptionOfTargetDescription"); __result = false;