Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 93 additions & 75 deletions SolastaUnfinishedBusiness/Spells/SpellBuildersLevel01.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2950,9 +2950,9 @@ internal static SpellDefinition BuildWitchBolt()
WitchBoltPower = FeatureDefinitionPowerBuilder
.Create($"Power{NAME}")
.SetGuiPresentation(NAME, Category.Spell, LightningBolt)
.SetUsesFixed(ActivationTime.Action)
.SetUsesFixed(ActivationTime.BonusAction, RechargeRate.TurnStart)
.SetEffectDescription(EffectDescriptionBuilder.Create()
.SetTargetingData(Side.Enemy, RangeType.Distance, 6, TargetType.IndividualsUnique)
.SetTargetingData(Side.Enemy, RangeType.Distance, 12, TargetType.IndividualsUnique, requireVisibility: false)
.SetEffectForms(EffectFormBuilder.DamageForm(DamageTypeLightning, 1, DieType.D12))
.SetParticleEffectParameters(ChainLightning)
.SetImpactEffectParameters(LightningBolt)
Expand All @@ -2966,8 +2966,8 @@ internal static SpellDefinition BuildWitchBolt()
.SetFeatures(WitchBoltPower)
.AddToDB();

var spell = SpellDefinitionBuilder
.Create(NAME)
var spellPt2 = SpellDefinitionBuilder
.Create(NAME+"ApplyConditions")
.SetGuiPresentation(Category.Spell, Sprites.GetSprite(NAME, Resources.WitchBolt, 128))
.SetSchoolOfMagic(SchoolOfMagicDefinitions.SchoolEvocation)
.SetSpellLevel(1)
Expand All @@ -2979,50 +2979,81 @@ internal static SpellDefinition BuildWitchBolt()
.SetRequiresConcentration(true)
.SetEffectDescription(EffectDescriptionBuilder.Create()
.SetDurationData(DurationType.Minute, 1)
.SetTargetingData(Side.Enemy, RangeType.RangeHit, 6, TargetType.IndividualsUnique)
.SetTargetingData(Side.Enemy, RangeType.Distance, 12, TargetType.IndividualsUnique)
.SetEffectAdvancement(EffectIncrementMethod.PerAdditionalSlotLevel, additionalDicePerIncrement: 1)
.SetEffectForms(
EffectFormBuilder.DamageForm(DamageTypeLightning, 1, DieType.D12),
//EffectFormBuilder.DamageForm(DamageTypeLightning, 2, DieType.D12),
EffectFormBuilder.AddConditionForm(conditionWitchBolt),
EffectFormBuilder.AddConditionForm(conditionWitchBoltSelf, true))
.SetAnimationMagicEffect(AnimationDefinitions.AnimationMagicEffect.Count)
.UseQuickAnimations()
.Build())
.AddToDB();

var spell = SpellDefinitionBuilder
.Create(NAME)
.SetGuiPresentation(Category.Spell, Sprites.GetSprite(NAME, Resources.WitchBolt, 128))
.SetSchoolOfMagic(SchoolOfMagicDefinitions.SchoolEvocation)
.SetSpellLevel(1)
.SetCastingTime(ActivationTime.NoCost)
.SetMaterialComponent(MaterialComponentType.None)
.SetSomaticComponent(false)
.SetVerboseComponent(false)
.SetVocalSpellSameType(VocalSpellSemeType.None)
.SetRequiresConcentration(false)
.SetEffectDescription(EffectDescriptionBuilder.Create()
.SetDurationData(DurationType.Minute, 1)
.SetTargetingData(Side.Enemy, RangeType.RangeHit, 6, TargetType.IndividualsUnique)
.SetEffectAdvancement(EffectIncrementMethod.PerAdditionalSlotLevel, additionalDicePerIncrement: 1)
.SetEffectForms(EffectFormBuilder.DamageForm(DamageTypeLightning, 2, DieType.D12))
.SetParticleEffectParameters(ChainLightning)
.SetImpactEffectParameters(LightningBolt)
.Build())
.AddToDB();

var witchBoltDuration = ComputeRoundsDuration(DurationType.Minute, 1);
WitchBoltPower.AddCustomSubFeatures(
new CustomBehaviorWitchBolt(spell, WitchBoltPower, conditionWitchBolt),
new ModifyPowerVisibility((character, power, _) =>
{
if (power.activationTime == ActivationTime.Action) { return true; }
spell.AddCustomSubFeatures(new OnPowerOrSpellFinishedByMeWitchBolt(spell, spellPt2));

if (character.TryGetConditionOfCategoryAndType(AttributeDefinitions.TagEffect,
conditionWitchBoltSelf.Name, out var condition))
{
return condition.RemainingRounds < witchBoltDuration;
}
var witchBoltDuration = ComputeRoundsDuration(DurationType.Minute, 1);
WitchBoltPower.AddCustomSubFeatures(new CustomBehaviorWitchBolt(WitchBoltPower, conditionWitchBolt));

return true;
}));

conditionWitchBolt.AddCustomSubFeatures(
new ActionFinishedByMeWitchBoltEnemy(spell, conditionWitchBolt));
new ActionFinishedByMeWitchBoltEnemy(spellPt2, conditionWitchBolt));

conditionWitchBoltSelf.AddCustomSubFeatures(
AddUsablePowersFromCondition.Marker,
new ActionFinishedByMeWitchBolt(spell, WitchBoltPower, conditionWitchBolt));
new ActionFinishedByMeWitchBolt(spellPt2, WitchBoltPower, conditionWitchBolt));

return spell;
}

private sealed class OnPowerOrSpellFinishedByMeWitchBolt(SpellDefinition witchBolt, SpellDefinition witchBoltPt2) : IPowerOrSpellFinishedByMe
{
// Trigger the concentration part of the Witch Bolt Spell
// Separate from the attack roll, as it still applies even if you miss the atk roll
public IEnumerator OnPowerOrSpellFinishedByMe(CharacterActionMagicEffect action, BaseDefinition baseDefinition)
{
if (baseDefinition.Name != witchBolt.Name || action.Countered || action.ExecutionFailed) { yield break; }

var rulesetEffect = action.ActionParams.RulesetEffect;

var actionParams = new CharacterActionParams(action.ActingCharacter, Id.CastNoCost)
{
ActionModifiers = { new ActionModifier() },
IntParameter = rulesetEffect.EffectLevel,
StringParameter = witchBoltPt2.Name,
targetCharacters = action.rawTargets
};

action.ActingCharacter.MyExecuteActionCastNoCost(witchBoltPt2, rulesetEffect.EffectLevel, actionParams, action.ActionParams.SpellRepertoire);
}
}

private sealed class CustomBehaviorWitchBolt(
// ReSharper disable once SuggestBaseTypeForParameterInConstructor
SpellDefinition spellWitchBolt,
// ReSharper disable once SuggestBaseTypeForParameterInConstructor
FeatureDefinitionPower powerWitchBolt,
// ReSharper disable once SuggestBaseTypeForParameterInConstructor
ConditionDefinition conditionWitchBolt) : IFilterTargetingCharacter, IModifyEffectDescription
ConditionDefinition conditionWitchBolt) : IFilterTargetingCharacter
{
public bool EnforceFullSelection => false;

Expand All @@ -3048,22 +3079,6 @@ public bool IsValid(BaseDefinition definition, RulesetCharacter character, Effec
{
return definition == powerWitchBolt;
}

public EffectDescription GetEffectDescription(
BaseDefinition definition,
EffectDescription effectDescription,
RulesetCharacter character,
RulesetEffect rulesetEffect)
{
if (character.ConcentratedSpell != null &&
character.ConcentratedSpell.SpellDefinition == spellWitchBolt)
{
effectDescription.EffectForms[0].DamageForm.DiceNumber =
1 + (character.ConcentratedSpell.EffectLevel - 1);
}

return effectDescription;
}
}

private sealed class ActionFinishedByMeWitchBolt(
Expand All @@ -3072,7 +3087,7 @@ private sealed class ActionFinishedByMeWitchBolt(
// ReSharper disable once SuggestBaseTypeForParameterInConstructor
FeatureDefinitionPower powerWitchBolt,
// ReSharper disable once SuggestBaseTypeForParameterInConstructor
ConditionDefinition conditionWitchBolt) : IActionFinishedByMe
ConditionDefinition conditionWitchBolt) : IActionFinishedByMe, IOnConditionAddedOrRemoved
{
public IEnumerator OnActionFinishedByMe(CharacterAction action)
{
Expand All @@ -3090,43 +3105,47 @@ or ActivationTime.OnAttackOrSpellHitAuto
case CharacterActionCastSpell actionCastSpell when
actionCastSpell.activeSpell.SpellDefinition == spellWitchBolt:
action.ActingCharacter.UsedSpecialFeatures.TryAdd(powerWitchBolt.Name, 0);
action.ActingCharacter.RulesetCharacter.UpdateUsageForPower(powerWitchBolt, 1);
yield break;
}

var actingCharacter = action.ActingCharacter;
var rulesetCharacter = actingCharacter.RulesetCharacter;

if (action.ActionType
is ActionType.Move
// these although allowed could potentially move both contenders off range
or ActionType.Bonus
or ActionType.Reaction
or ActionType.NoCost)
if (Gui.Battle == null)
{
if (Gui.Battle == null)
{
yield break;
}
yield break;
}

var stillInRange = Gui.Battle
.GetContenders(actingCharacter, withinRange: 6)
.Any(x =>
x.RulesetCharacter.TryGetConditionOfCategoryAndType(
AttributeDefinitions.TagEffect, conditionWitchBolt.Name, out var activeCondition) &&
rulesetCharacter.Guid == activeCondition.SourceGuid);

var stillInRange = Gui.Battle
.GetContenders(actingCharacter, withinRange: 6)
.Any(x =>
x.RulesetCharacter.TryGetConditionOfCategoryAndType(
AttributeDefinitions.TagEffect, conditionWitchBolt.Name, out var activeCondition) &&
rulesetCharacter.Guid == activeCondition.SourceGuid);
if (!stillInRange)
{
var rulesetSpell = rulesetCharacter.SpellsCastByMe.FirstOrDefault(x => x.SpellDefinition == spellWitchBolt);

if (stillInRange)
if (rulesetSpell != null)
{
yield break;
rulesetCharacter.TerminateSpell(rulesetSpell);
}
}
}

var rulesetSpell = rulesetCharacter.SpellsCastByMe.FirstOrDefault(x => x.SpellDefinition == spellWitchBolt);
public void OnConditionAdded(RulesetCharacter target, RulesetCondition rulesetCondition)
{
var glc = GameLocationCharacter.GetFromActor(target);
glc.UsedSpecialFeatures.TryAdd(powerWitchBolt.Name, 0);
// can't use the Bonus Action power on the turn the spell was cast
glc.RulesetCharacter.UpdateUsageForPower(powerWitchBolt, 1);
}

if (rulesetSpell != null)
{
rulesetCharacter.TerminateSpell(rulesetSpell);
}
public void OnConditionRemoved(RulesetCharacter target, RulesetCondition rulesetCondition)
{
// Empty
}
}

Expand All @@ -3152,27 +3171,26 @@ public IEnumerator OnActionFinishedByMe(CharacterAction action)
yield break;
}

var stillInRange = Gui.Battle.GetContenders(actingCharacter, withinRange: 6).Any(x =>
x.RulesetCharacter.Guid == activeCondition.SourceGuid);

if (stillInRange)
{
yield break;
}

var rulesetCaster = EffectHelpers.GetCharacterByGuid(activeCondition.SourceGuid);

if (rulesetCaster == null)
{
yield break;
}

var rulesetSpell = rulesetCharacter.SpellsCastByMe.FirstOrDefault(x => x.SpellDefinition == spellWitchBolt);
var stillInRange = Gui.Battle.GetContenders(actingCharacter, withinRange: 6).Any(x =>
x.RulesetCharacter.Guid == activeCondition.SourceGuid);

if (rulesetSpell != null)
if (!stillInRange)
{
rulesetCaster.TerminateSpell(rulesetSpell);
var rulesetSpell = rulesetCharacter.SpellsCastByMe.FirstOrDefault(x => x.SpellDefinition == spellWitchBolt);

if (rulesetSpell != null)
{
rulesetCaster.TerminateSpell(rulesetSpell);
}
}

}

public void OnConditionAdded(RulesetCharacter target, RulesetCondition rulesetCondition)
Expand Down
2 changes: 1 addition & 1 deletion SolastaUnfinishedBusiness/Spells/SpellBuildersLevel03.cs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ internal static SpellDefinition BuildAshardalonStride()
.SetGuiPresentation(Category.Spell, Sprites.GetSprite(Name, Resources.AshardalonStride, 128))
.SetSchoolOfMagic(SchoolOfMagicDefinitions.SchoolTransmutation)
.SetSpellLevel(3)
.SetCastingTime(ActivationTime.Action)
.SetCastingTime(ActivationTime.BonusAction)
.SetMaterialComponent(MaterialComponentType.None)
.SetVerboseComponent(true)
.SetSomaticComponent(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,10 @@ Spell/&VileBrewDescription=A stream of acid emanates from you in a line 30 feet
Spell/&VileBrewTitle=Tasha's Caustic Brew
Spell/&VoidGraspDescription=You invoke the power of malevolent forces. Tendrils of dark energy erupt from you and batter all creatures within 10 feet of you. Each creature in that area must make a Strength saving throw. On a failed save, a target takes 2d6 necrotic damage and can't take reactions until the start of your next turn. On a successful save, the creature takes half damage, but suffers no other effect. When you cast this spell using a spell slot of 2nd level or higher, the damage increases by 1d6 for each slot level above 1st.
Spell/&VoidGraspTitle=Arms of Hadar
Spell/&WitchBoltDescription=A beam of crackling, blue energy lances out toward a creature within range, forming a sustained arc of lightning between you and the target. Make a ranged spell attack against that creature. On a hit, the target takes 1d12 lightning damage, and on each of your turns for the duration, you can use your action to deal 1d12 lightning damage to the target automatically. The spell ends if you use your action to do anything else. The spell also ends if the target is ever outside the spell's range. When you cast this spell using a spell slot of 2nd level or higher, the damage increases by 1d12 for each slot level above 1st.
Spell/&WitchBoltDescription=A beam of crackling, blue energy lances out toward a creature within range, forming a sustained arc of lightning between you and the target. Make a ranged spell attack against that creature. On a hit, the target takes 2d12 lightning damage, and on each of your turns for the duration, you can use your bonus action to deal 1d12 lightning damage to the target automatically. The spell ends if the target is ever outside the spell's range. When you cast this spell using a spell slot of 2nd level or higher, the initial damage increases by 1d12 for each slot level above 1st.
Spell/&WitchBoltTitle=Witch Bolt
Spell/&WitchBoltApplyConditionsDescription=A beam of crackling, blue energy lances out toward a creature within range, forming a sustained arc of lightning between you and the target. Make a ranged spell attack against that creature. On a hit, the target takes 2d12 lightning damage, and on each of your turns for the duration, you can use your bonus action to deal 1d12 lightning damage to the target automatically. The spell ends if the target is ever outside the spell's range. When you cast this spell using a spell slot of 2nd level or higher, the initial damage increases by 1d12 for each slot level above 1st.
Spell/&WitchBoltApplyConditionsTitle=Witch Bolt
Spell/&WrathfulSmiteDescription=The target takes an extra 1d6 Necrotic damage from the attack, and it must succeed on a Wisdom saving throw or have the Frightened condition until the spell ends. At the end of each of its turns, the Frightened target repeats the save, ending the spell on itself on a success. The damage increases by 1d6 for each spell slot level above 1.
Spell/&WrathfulSmiteTitle=Wrathful Smite
Tooltip/&TagDamagePureTitle=Chaotic Damage