diff --git a/Ambermoon.Common/Util.cs b/Ambermoon.Common/Util.cs index 8e809fec..6ff7052b 100644 --- a/Ambermoon.Common/Util.cs +++ b/Ambermoon.Common/Util.cs @@ -30,6 +30,11 @@ public static float Limit(float minValue, float value, float maxValue) return Math.Max(minValue, Math.Min(value, maxValue)); } + public static uint Limit(uint minValue, uint value, uint maxValue) + { + return Math.Max(minValue, Math.Min(value, maxValue)); + } + public static int Limit(int minValue, int value, int maxValue) { return Math.Max(minValue, Math.Min(value, maxValue)); diff --git a/Ambermoon.Core/Battle.cs b/Ambermoon.Core/Battle.cs index 65b9be78..4e3f2872 100644 --- a/Ambermoon.Core/Battle.cs +++ b/Ambermoon.Core/Battle.cs @@ -937,17 +937,20 @@ void AttackAnimationFinished() var spellInfo = SpellInfos.Entries[spell]; + if (itemIndex == 0) + { + battleAction.Character.SpellPoints.CurrentValue = Math.Max(0, battleAction.Character.SpellPoints.CurrentValue - spellInfo.SP); + + if (battleAction.Character is PartyMember partyMember) + layout.FillCharacterBars(game.SlotFromPartyMember(partyMember).Value, partyMember); + } + if (!CheckSpellCast(battleAction.Character, spellInfo)) { EndCast(); return; } - battleAction.Character.SpellPoints.CurrentValue -= spellInfo.SP; - - if (battleAction.Character is PartyMember partyMember) - layout.FillCharacterBars(game.SlotFromPartyMember(partyMember).Value, partyMember); - if (spell != Spell.Firebeam && spell != Spell.Fireball && spell != Spell.Firestorm && @@ -960,7 +963,9 @@ void AttackAnimationFinished() spell != Spell.DestroyUndead && spell != Spell.HolyWord && spell != Spell.MagicalProjectile && - spell != Spell.MagicalArrows) // TODO: REMOVE. For now we only allow some spells for testing. + spell != Spell.MagicalArrows && + spell != Spell.LPStealer && + spell != Spell.SPStealer) // TODO: REMOVE. For now we only allow some spells for testing. { if (spell < Spell.Lame || spell > Spell.Drug) break; @@ -1459,6 +1464,9 @@ void HandleCharacterDeath(Character attacker, Character target, Action finishAct } } + /// + /// The boolean argument of the finish action means: NeedsClickAfterwards + /// void ApplySpellEffect(Character caster, Character target, Spell spell, uint ticks, Action finishAction) { switch (spell) @@ -1548,6 +1556,24 @@ void ApplySpellEffect(Character caster, Character target, Spell spell, uint tick // Those deal half the caster level as damage. DealDamage(Math.Max(1, (uint)caster.Level / 2), 0); return; + case Spell.LPStealer: + { + DealDamage(caster.Level, 0); + caster.HitPoints.CurrentValue = Math.Min(caster.HitPoints.MaxValue, caster.HitPoints.CurrentValue + + Math.Min(caster.Level, caster.HitPoints.MaxValue - caster.HitPoints.CurrentValue)); + if (caster is PartyMember castingMember) + layout.FillCharacterBars(game.SlotFromPartyMember(castingMember).Value, castingMember); + return; + } + case Spell.SPStealer: + // TODO: what happens if a monster wants to cast a spell afterwards but has not enough SP through SP stealer anymore? + target.SpellPoints.CurrentValue = (uint)Math.Max(0, (int)target.SpellPoints.CurrentValue - caster.Level); + caster.SpellPoints.CurrentValue += Math.Min(caster.Level, caster.SpellPoints.MaxValue - caster.SpellPoints.CurrentValue); + if (target is PartyMember targetMember) + layout.FillCharacterBars(game.SlotFromPartyMember(targetMember).Value, targetMember); + else if (caster is PartyMember castingMember) + layout.FillCharacterBars(game.SlotFromPartyMember(castingMember).Value, castingMember); + break; // Winddevil: seen 10-15 // Windhowler: seen 35-46 default: diff --git a/Ambermoon.Core/Game.cs b/Ambermoon.Core/Game.cs index 0142c0d5..07692774 100644 --- a/Ambermoon.Core/Game.cs +++ b/Ambermoon.Core/Game.cs @@ -3030,6 +3030,8 @@ void ShowBattleWindow(Event nextEvent) spell != Spell.HolyWord && spell != Spell.MagicalProjectile && spell != Spell.MagicalArrows && + spell != Spell.LPStealer && + spell != Spell.SPStealer && !(spell >= Spell.Lame && spell <= Spell.Drug)) pickedSpell = Spell.Iceball; // TODO else diff --git a/Ambermoon.Core/Render/SpellAnimation.cs b/Ambermoon.Core/Render/SpellAnimation.cs index b31d544b..5f89740e 100644 --- a/Ambermoon.Core/Render/SpellAnimation.cs +++ b/Ambermoon.Core/Render/SpellAnimation.cs @@ -366,14 +366,14 @@ public void Play(Action finishAction) case Spell.Windhowler: case Spell.MagicalProjectile: case Spell.MagicalArrows: + case Spell.LPStealer: + case Spell.SPStealer: + case Spell.GhostWeapon: // Those spells use only the MoveTo method. this.finishAction?.Invoke(); break; - case Spell.LPStealer: - case Spell.SPStealer: case Spell.MonsterKnowledge: case Spell.ShowMonsterLP: - case Spell.GhostWeapon: case Spell.Blink: case Spell.Flight: return; // TODO @@ -728,11 +728,27 @@ void PlayHolyLight() case Spell.AntiMagicSphere: case Spell.Hurry: case Spell.MassHurry: - case Spell.LPStealer: - case Spell.SPStealer: case Spell.MonsterKnowledge: case Spell.ShowMonsterLP: return; // TODO + case Spell.LPStealer: + case Spell.SPStealer: + { + // Note: The hurt animation comes first so we immediately call the passed finish action + // which will display the hurt animation. + finishAction?.Invoke(game.CurrentBattleTicks, true, false); // Play hurt animation but do not finish. + this.finishAction = () => finishAction?.Invoke(game.CurrentBattleTicks, false, true); // This is called after the animation to finish. + + float endScale = renderView.GraphicProvider.GetMonsterRowImageScaleFactor((MonsterRow)(tile / 6)); + game.AddTimedEvent(TimeSpan.FromMilliseconds(500), () => + { + byte displayLayer = (byte)(fromMonster ? 255 : ((tile / 6) * 60 + 60)); + AddAnimation(spell == Spell.LPStealer ? CombatGraphicIndex.BlueBeam : CombatGraphicIndex.GreenBeam, 1, + GetTargetPosition(tile), GetSourcePosition(), BattleEffects.GetFlyDuration((uint)tile, (uint)startPosition), + fromMonster ? 2.5f: endScale, fromMonster ? endScale : 2.5f, displayLayer); + }); + break; + } case Spell.MagicalProjectile: case Spell.MagicalArrows: { diff --git a/Ambermoon.Data.Common/CharacterValue.cs b/Ambermoon.Data.Common/CharacterValue.cs index ac1f858e..1f33bf7e 100644 --- a/Ambermoon.Data.Common/CharacterValue.cs +++ b/Ambermoon.Data.Common/CharacterValue.cs @@ -11,7 +11,7 @@ public class CharacterValue public uint MaxValue { get; set; } public uint BonusValue { get; set; } public uint Unknown { get; set; } - public uint TotalCurrentValue => CurrentValue + BonusValue; + public uint TotalCurrentValue => Util.Limit(0, CurrentValue + BonusValue, MaxValue); } [Serializable]