Skip to content

Commit

Permalink
Replace usage of DoL events for pet releases, fix Charm related issues
Browse files Browse the repository at this point in the history
* Fixes release button not working sometimes. Caused by effects being replaced by a new one when being refreshed, and DoL's event system losing the spell handler (since it uses weak references).
* Fixes pulsing charms being allowed to add a new brain to the NPC when it shouldn't, resulting in a memory leak and ignoring the previous brain (if there was any).
* Fixes Animist being unable to release Forestheart Ambusher. Fixed by allowing any pet to be released with the release command (Theurgist pets, turrets, and Bonedancer sub pets were already allowed, so there wasn't really any reason to not make a general rule out of it instead).
* Reduces the amount of redundant work done in `ABrain.Stop` and overrides. It is sometimes called 3 or 4 times.
  • Loading branch information
bm01 committed Aug 24, 2024
1 parent bcba5e6 commit 7823453
Show file tree
Hide file tree
Showing 25 changed files with 274 additions and 733 deletions.
128 changes: 59 additions & 69 deletions GameServer/ECS-Effects/CharmECSEffect.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using DOL.AI.Brain;
using DOL.Events;
using DOL.GS.PacketHandler;
using DOL.GS.Spells;
using DOL.Language;
Expand All @@ -13,128 +13,118 @@ public CharmECSGameEffect(ECSGameEffectInitParams initParams) : base(initParams)

public override void OnStartEffect()
{
if (SpellHandler.Caster is not GamePlayer casterPlayer || Owner is not GameNPC charmMob)
if (Owner is not GameNPC charmNpc)
return;

CharmSpellHandler charmSpellHandler = SpellHandler as CharmSpellHandler;
ControlledMobBrain newBrain = new(SpellHandler.Caster);
charmNpc.AddBrain(newBrain);
charmNpc.TargetObject = null;

if (charmSpellHandler.m_controlledBrain == null && charmMob.Brain is not ControlledMobBrain)
charmSpellHandler.m_controlledBrain = new ControlledMobBrain(casterPlayer);
else
{
charmSpellHandler.m_controlledBrain = charmMob.Brain as ControlledMobBrain;
charmSpellHandler.m_isBrainSet = true;
}

if (!charmSpellHandler.m_isBrainSet && !charmSpellHandler.m_controlledBrain.IsActive)
{
charmMob.AddBrain(charmSpellHandler.m_controlledBrain);
charmMob.TargetObject = null;
charmSpellHandler.m_isBrainSet = true;
GameEventMgr.AddHandler(charmMob, GameLivingEvent.PetReleased, charmSpellHandler.ReleaseEventHandler);
}

if (casterPlayer.ControlledBrain != charmSpellHandler.m_controlledBrain)
if (SpellHandler.Caster is GamePlayer playerCaster)
{
// Message: "{0}The slough serpent} is now enthralled!"
if (!string.IsNullOrEmpty(SpellHandler.Spell.Message1))
Message.SystemToArea(charmMob, Util.MakeSentence(SpellHandler.Spell.Message1, charmMob.GetName(0, true)), eChatType.CT_System, charmMob, casterPlayer);
Message.SystemToArea(charmNpc, Util.MakeSentence(SpellHandler.Spell.Message1, charmNpc.GetName(0, true)), eChatType.CT_System, charmNpc, playerCaster);

// Message: {0} is now under your control.
if (!string.IsNullOrEmpty(SpellHandler.Spell.Message2))
charmSpellHandler.MessageToCaster(Util.MakeSentence(SpellHandler.Spell.Message2, charmMob.GetName(0, true)), eChatType.CT_Spell);
charmSpellHandler.MessageToCaster(Util.MakeSentence(SpellHandler.Spell.Message2, charmNpc.GetName(0, true)), eChatType.CT_Spell);
else
charmSpellHandler.MessageToCaster(LanguageMgr.GetTranslation(casterPlayer.Client, "GamePlayer.GamePet.StartSpell.UnderControl", charmMob.GetName(0, true)), eChatType.CT_Spell);
charmSpellHandler.MessageToCaster(LanguageMgr.GetTranslation(playerCaster.Client, "GamePlayer.GamePet.StartSpell.UnderControl", charmNpc.GetName(0, true)), eChatType.CT_Spell);

casterPlayer.SetControlledBrain(charmSpellHandler.m_controlledBrain);
ClientService.CreateNpcForPlayers(charmMob);
playerCaster.SetControlledBrain(newBrain);
}

charmSpellHandler.SendEffectAnimation(charmMob, 0, false, 1);
ClientService.CreateNpcForPlayers(charmNpc);
charmSpellHandler.SendEffectAnimation(charmNpc, 0, false, 1);
}

public override void OnStopEffect()
{
GamePlayer casterPlayer = SpellHandler.Caster as GamePlayer;
GameNPC charmMob = Owner as GameNPC;
CharmSpellHandler charmSpellHandler = SpellHandler as CharmSpellHandler;
bool keepSongAlive = false;
if (Owner is not GameNPC charmNpc)
return;

if (casterPlayer != null && charmMob != null)
{
GameEventMgr.RemoveHandler(charmMob, GameLivingEvent.PetReleased, charmSpellHandler.ReleaseEventHandler);
ControlledMobBrain oldBrain = casterPlayer.ControlledBrain as ControlledMobBrain;
casterPlayer.SetControlledBrain(null);
ECSGameSpellEffect[] immunityEffects = charmNpc.effectListComponent.GetSpellEffects().Where(e => e.TriggersImmunity).ToArray();

for (int i = 0; i < immunityEffects.Length; i++)
EffectService.RequestImmediateCancelEffect(immunityEffects[i]);

var immunityEffects = charmMob.effectListComponent.GetSpellEffects().Where(e => e.TriggersImmunity).ToArray();
ControlledMobBrain oldBrain = SpellHandler.Caster.ControlledBrain as ControlledMobBrain;
SpellHandler.Caster.SetControlledBrain(null);
bool keepSongAlive = false;

for (int i = 0; i < immunityEffects.Length; i++)
EffectService.RequestImmediateCancelEffect(immunityEffects[i]);
if (oldBrain != null)
{
oldBrain.ClearAggroList();
charmNpc.StopAttack();
charmNpc.StopCurrentSpellcast();
charmNpc.RemoveBrain(oldBrain);

charmMob.StopAttack();
charmMob.StopCurrentSpellcast();
charmMob.RemoveBrain(oldBrain);
StandardMobBrain newBrain = new();
charmMob.AddBrain(newBrain);
charmSpellHandler.m_isBrainSet = false;
if (charmNpc.Brain == null)
charmNpc.AddBrain(new StandardMobBrain());

if (newBrain is IOldAggressiveBrain)
if (charmNpc.Brain is IOldAggressiveBrain aggroBrain)
{
newBrain.ClearAggroList();
aggroBrain.ClearAggroList();

if (SpellHandler.Spell.Pulse != 0 &&
SpellHandler.Caster.ObjectState == GameObject.eObjectState.Active &&
SpellHandler.Caster.ObjectState is GameObject.eObjectState.Active &&
SpellHandler.Caster.IsAlive &&
!SpellHandler.Caster.IsStealthed)
{
newBrain.FSM.SetCurrentState(eFSMStateType.AGGRO);
newBrain.AddToAggroList(SpellHandler.Caster, SpellHandler.Caster.Level * 10);
charmMob.StartAttack(SpellHandler.Caster);
charmMob.LastAttackedByEnemyTickPvE = GameLoop.GameLoopTime;
aggroBrain.AddToAggroList(SpellHandler.Caster, SpellHandler.Caster.Level * 10);
charmNpc.StartAttack(SpellHandler.Caster);
charmNpc.LastAttackedByEnemyTickPvE = GameLoop.GameLoopTime;
}
}

// remove NPC with new brain from all attackers aggro list
foreach (GameLiving attacker in charmMob.attackComponent.Attackers.Keys)
// Remove NPC with new brain from all attackers aggro list.
foreach (GameLiving attacker in charmNpc.attackComponent.Attackers.Keys)
{
if (attacker is GameNPC npcAttacker && npcAttacker.Brain is IOldAggressiveBrain aggressiveBrain)
if (attacker is GameNPC npcAttacker && npcAttacker.Brain is IOldAggressiveBrain attackerAggroBrain)
{
aggressiveBrain.RemoveFromAggroList(charmMob);
aggressiveBrain.AddToAggroList(casterPlayer, casterPlayer.Level * 10);
npcAttacker.StartAttack(casterPlayer);
attackerAggroBrain.RemoveFromAggroList(charmNpc);
attackerAggroBrain.AddToAggroList(SpellHandler.Caster, SpellHandler.Caster.Level * 10);
npcAttacker.StartAttack(SpellHandler.Caster);
npcAttacker.LastAttackedByEnemyTickPvE = GameLoop.GameLoopTime;
}
}

charmSpellHandler.m_controlledBrain?.ClearAggroList();
charmMob.StopFollowing();
charmMob.TempProperties.SetProperty(GameNPC.CHARMED_TICK_PROP, charmMob.CurrentRegion.Time);
charmNpc.TempProperties.SetProperty(GameNPC.CHARMED_TICK_PROP, charmNpc.CurrentRegion.Time);

foreach (GamePlayer playerInRadius in charmMob.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE))
foreach (GamePlayer playerInRadius in charmNpc.GetPlayersInRadius(WorldMgr.VISIBILITY_DISTANCE))
{
if (charmMob.IsAlive)
if (charmNpc.IsAlive)
{
playerInRadius.Out.SendNPCCreate(charmMob);
playerInRadius.Out.SendNPCCreate(charmNpc);

if (charmMob.Inventory != null)
playerInRadius.Out.SendLivingEquipmentUpdate(charmMob);
if (charmNpc.Inventory != null)
playerInRadius.Out.SendLivingEquipmentUpdate(charmNpc);

playerInRadius.Out.SendObjectGuildID(charmMob, null);
playerInRadius.Out.SendObjectGuildID(charmNpc, null);
}
}

keepSongAlive = charmMob.IsAlive && charmMob.IsWithinRadius(casterPlayer, ControlledMobBrain.MAX_OWNER_FOLLOW_DIST);
keepSongAlive = charmNpc.IsAlive && charmNpc.IsWithinRadius(SpellHandler.Caster, ControlledMobBrain.MAX_OWNER_FOLLOW_DIST);
}

if (!keepSongAlive)
{
ECSPulseEffect song = EffectListService.GetPulseEffectOnTarget(casterPlayer, SpellHandler.Spell);
ECSPulseEffect song = EffectListService.GetPulseEffectOnTarget(SpellHandler.Caster, SpellHandler.Spell);

if (song != null)
EffectService.RequestImmediateCancelConcEffect(song);
}
}

public static void FindAndCancelEffectOnTarget(GameNPC target)
{
if (target.Brain is not IControlledBrain)
return;

charmSpellHandler.m_controlledBrain = null;
if (target.effectListComponent.Effects.TryGetValue(eEffect.Charm, out List<ECSGameEffect> charms))
EffectService.RequestImmediateCancelEffect(charms.FirstOrDefault());
}
}
}
7 changes: 5 additions & 2 deletions GameServer/ECS-Effects/PetECSEffect.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace DOL.GS
using DOL.GS.Spells;

namespace DOL.GS
{
public class PetECSGameEffect : ECSGameSpellEffect
{
Expand All @@ -7,8 +9,9 @@ public PetECSGameEffect(ECSGameEffectInitParams initParams) : base(initParams) {
public override void OnStopEffect()
{
SpellHandler.Caster.UpdatePetCount(false);
Owner.Health = 0; // to send proper remove packet
Owner.Health = 0; // To send proper remove packet.
Owner.Delete();
(SpellHandler as SummonSpellHandler)?.OnPetReleased(Owner as GameSummonedPet);
}
}
}
2 changes: 2 additions & 0 deletions GameServer/ECS-Services/EffectService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,8 @@ public static eEffect GetEffectFromSpell(Spell spell)
case eSpellType.SummonNecroPet:
case eSpellType.SummonCommander:
case eSpellType.SummonMinion:
case eSpellType.SummonJuggernaut:
case eSpellType.SummonAnimistAmbusher:
return eEffect.Pet;

default:
Expand Down
3 changes: 3 additions & 0 deletions GameServer/ai/ABrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public virtual bool Start()
/// <returns>true if stopped</returns>
public virtual bool Stop()
{
if (EntityManagerId.IsPendingRemoval)
return false; // Prevents overrides from doing any redundant work. Maybe counter intuitive.

bool wasReturningToSpawnPoint = Body.IsReturningToSpawnPoint;
Body.StopMoving();
FSM?.SetCurrentState(eFSMStateType.IDLE);
Expand Down
14 changes: 0 additions & 14 deletions GameServer/ai/brain/Bonedancer/BdPetBrain.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using DOL.Events;
using DOL.GS;

namespace DOL.AI.Brain
Expand Down Expand Up @@ -69,19 +68,6 @@ public override void OnAttackedByEnemy(AttackData ad)
/// </summary>
public override void UpdatePetWindow() { }

/// <summary>
/// Stops the brain thinking
/// </summary>
/// <returns>true if stopped</returns>
public override bool Stop()
{
if (!base.Stop())
return false;

GameEventMgr.Notify(GameLivingEvent.PetReleased, Body);
return true;
}

/// <summary>
/// Start following the owner
/// </summary>
Expand Down
26 changes: 12 additions & 14 deletions GameServer/ai/brain/ControlledMob/ControlledMobBrain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -370,28 +370,15 @@ public override bool Start()
return true;
}

/// <summary>
/// Stops the brain thinking
/// </summary>
/// <returns>true if stopped</returns>
public override bool Stop()
{
if (!base.Stop())
return false;

StripCastedBuffs();
GameEventMgr.Notify(GameLivingEvent.PetReleased, Body);
OnRelease();
return true;
}

/// <summary>
/// Do the mob AI
/// </summary>
public override void Think()
{
base.Think();
}

/// <summary>
/// Checks the Abilities
/// </summary>
Expand Down Expand Up @@ -945,6 +932,17 @@ public virtual void OnOwnerAttacked(AttackData ad)
AttackMostWanted();
}

public virtual void OnRelease()
{
StripCastedBuffs();

foreach (ECSGameSpellEffect effect in Body.effectListComponent.GetSpellEffects())
{
if (effect.EffectType is eEffect.Pet or eEffect.Charm)
EffectService.RequestImmediateCancelEffect(effect);
}
}

public void AddBuffedTarget(GameLiving living)
{
if (living == Body)
Expand Down
4 changes: 0 additions & 4 deletions GameServer/events/gameobjects/GameLivingEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,6 @@ public override bool IsValidFor(object o)
/// </summary>
public static readonly GameLivingEvent HealthChanged = new GameLivingEvent("GameLiving.HealthChanged");
/// <summary>
/// The PetReleased event is fired whenever the player commands to release controlled NPC
/// </summary>
public static readonly GameLivingEvent PetReleased = new GameLivingEvent("GameLiving.PetReleased");
/// <summary>
/// The RegionChanging event is fired when a living is changing regions
/// </summary>
public static readonly GameLivingEvent RegionChanging = new GameLivingEvent("GameLiving.RegionChanging");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ public override void SetControlledBrain(IControlledBrain controlledNpcBrain)
/// </summary>
public override void CommandNpcRelease()
{
m_savedPetHealthPercent = (Player.ControlledBrain != null) ? (int)Player.ControlledBrain.Body.HealthPercent : 0;

m_savedPetHealthPercent = Player.ControlledBrain != null ? Player.ControlledBrain.Body.HealthPercent : 0;
base.CommandNpcRelease();
OnPetReleased();
}
Expand Down
19 changes: 7 additions & 12 deletions GameServer/gameobjects/CharacterClasses/CharacterClassBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,19 +329,15 @@ public virtual void SetControlledBrain(IControlledBrain controlledBrain)
/// </summary>
public virtual void CommandNpcRelease()
{
IControlledBrain controlledBrain = Player.ControlledBrain;
ControlledMobBrain controlledBrain;

if (controlledBrain == null)
return;

(controlledBrain as ControlledMobBrain)?.StripCastedBuffs();

GameNPC npc = controlledBrain.Body;

if (npc == null)
return;
if (Player.TargetObject is not GameNPC targetNpc || !Player.IsControlledNPC(targetNpc))
controlledBrain = Player.ControlledBrain as ControlledMobBrain;
else
controlledBrain = targetNpc.Brain as ControlledMobBrain;

Player.Notify(GameLivingEvent.PetReleased, npc);
controlledBrain?.OnRelease();
return;
}

/// <summary>
Expand All @@ -359,7 +355,6 @@ public virtual bool StartAttack(GameObject attackTarget)
return true;
}


/// <summary>
/// Return the health percent of this character
/// </summary>
Expand Down
Loading

0 comments on commit 7823453

Please sign in to comment.