diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index d0bd5f1f..0077549a 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -23,6 +23,7 @@ using FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group; using System.Threading; using System.Diagnostics; using FFXIVClassic_Map_Server.actors.director; +using FFXIVClassic_Map_Server.actors.chara.ai; namespace FFXIVClassic_Map_Server { @@ -35,6 +36,7 @@ namespace FFXIVClassic_Map_Server private Dictionary zoneEntranceList; private Dictionary actorClasses = new Dictionary(); private Dictionary currentPlayerParties = new Dictionary(); //GroupId, Party object + private Dictionary effectList = new Dictionary(); // todo: load these in from a db table private Server mServer; diff --git a/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs index 0fc56836..60f44983 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs @@ -44,8 +44,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai // todo: trigger listeners // todo: action queues - - + controller.Update(tick); } public void InterruptStates() diff --git a/FFXIVClassic Map Server/actors/chara/ai/StatusEffects.cs b/FFXIVClassic Map Server/actors/chara/ai/StatusEffects.cs index 95b9a3ce..371817dd 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/StatusEffects.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/StatusEffects.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.lua; +using FFXIVClassic_Map_Server.actors.area; namespace FFXIVClassic_Map_Server.actors.chara.ai { @@ -322,6 +323,31 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai // custom effects here } + [Flags] + enum StatusEffectFlags + { + None = 0x00, + Silent = 0x01, // dont display effect loss message + LoseOnDeath = 0x02, // effects removed on death + LoseOnZoning = 0x04, // effects removed on zoning + LoseOnEsuna = 0x08, // effects which can be removed with esuna (debuffs) + LoseOnDispel = 0x10, // some buffs which player might be able to dispel from mob + LoseOnLogout = 0x20, // effects removed on logging out + LoseOnAttacking = 0x40, // effects removed when owner attacks another entity + LoseOnCasting = 0x80, // effects removed when owner starts casting + LoseOnDamageTaken = 0x100, // effects removed when owner takes damage + + PreventAction = 0x200, // effects which prevent actions such as sleep/paralyze/petrify + } + + enum StatusEffectOverwrite : byte + { + None, + Always, + GreaterOrEqualTo, + GreaterOnly, + } + class StatusEffect { // todo: probably use get;set; @@ -336,9 +362,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai private int magnitude; // a value specified by scripter which is guaranteed to be used by all effects private byte tier; // same effect with higher tier overwrites this private Dictionary variables; // list of variables which belong to this effect, to be set/retrieved with GetVariable(key), SetVariable(key, val) + private StatusEffectFlags flags; // death/erase/dispel etc + private StatusEffectOverwrite overwrite; // - public StatusEffect(uint id, int magnitude, uint tickMs, uint durationMs, byte tier = 0) + public StatusEffect(Character owner, uint id, int magnitude, uint tickMs, uint durationMs, byte tier = 0) { + this.owner = owner; this.id = (StatusEffectId)id; this.magnitude = magnitude; this.tickMs = tickMs; @@ -353,6 +382,24 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai // name = WorldManager.GetEffectInfo(id).Name; // todo: check if can gain effect // todo: call effect's onGain + // todo: broadcast effect gain packet + } + + public StatusEffect(Character owner, StatusEffect effect) + { + this.owner = owner; + this.id = effect.id; + this.magnitude = effect.magnitude; + this.tickMs = effect.tickMs; + this.durationMs = effect.durationMs; + this.tier = effect.tier; + this.startTime = effect.startTime; + this.lastTick = effect.lastTick; + + this.name = effect.name; + this.flags = effect.flags; + this.overwrite = effect.overwrite; + this.variables = effect.variables; } // return true when duration has elapsed @@ -365,14 +412,21 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai // todo: maybe keep a global lua object instead of creating a new one each time we wanna call a script lastTick = tick; } - if (startTime.Millisecond + durationMs >= tick.Millisecond) + // todo: handle infinite duration effects? + if (durationMs != 0 && startTime.Millisecond + durationMs >= tick.Millisecond) { // todo: call effect's onLose + // todo: broadcast effect lost packet return true; } return false; } + public Character GetOwner() + { + return owner; + } + public uint GetEffectId() { return (uint)id; @@ -408,6 +462,21 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai return variables?[key] ?? 0; } + public uint GetFlags() + { + return (uint)flags; + } + + public byte GetOverwritable() + { + return (byte)overwrite; + } + + public void SetOwner(Character owner) + { + this.owner = owner; + } + public void SetName(string name) { this.name = name; @@ -440,5 +509,98 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai variables[key] = val; } } + + public void SetFlags(uint flags) + { + this.flags = (StatusEffectFlags)flags; + } + + public void SetOverwritable(byte overwrite) + { + this.overwrite = (StatusEffectOverwrite)overwrite; + } + } + + class StatusEffects + { + private Character owner; + private List effects; + + public StatusEffects(Character owner) + { + this.owner = owner; + this.effects = new List(); + } + + public void Update(DateTime tick) + { + // list of effects to remove + var removeEffects = new List(); + foreach (var effect in effects) + { + // effect's update function returns true if effect has completed + if (effect.Update(tick)) + removeEffects.Add(effect); + } + + // remove effects from this list + foreach (var effect in removeEffects) + effects.Remove(effect); + } + + public bool AddStatusEffect(StatusEffect effect) + { + // todo: check flags/overwritable and add effect to list + effects.Add(effect); + return true; + } + + public StatusEffect CopyEffect(StatusEffect effect) + { + var newEffect = new StatusEffect(this.owner, effect); + newEffect.SetOwner(this.owner); + + return AddStatusEffect(newEffect) ? newEffect : null; + } + + public bool RemoveStatusEffectsByFlags(uint flags) + { + // build list of effects to remove + var removeEffects = new List(); + foreach (var effect in effects) + if ((effect.GetFlags() & flags) > 0) + removeEffects.Add(effect); + + // remove effects from main list + foreach (var effect in removeEffects) + effects.Remove(effect); + + // removed an effect with one of these flags + return removeEffects.Count > 0; + } + + public StatusEffect GetStatusEffectById(uint id, uint tier = 0xFF) + { + foreach (var effect in effects) + { + if (effect.GetEffectId() == id && (tier != 0xFF ? effect.GetTier() == tier : true)) + return effect; + } + return null; + } + + public List GetStatusEffectsByFlag(uint flag) + { + var list = new List(); + foreach (var effect in effects) + { + if ((effect.GetFlags() & flag) > 0) + { + list.Add(effect); + } + } + return list; + } + } } diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/MobController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/MobController.cs index b6da397f..f97e76b5 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/controllers/MobController.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/MobController.cs @@ -17,7 +17,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers public override void Update(DateTime tick) { - + // todo: handle aggro/deaggro and other shit here + ((Mob)this.owner).statusEffects.Update(tick); } public override bool Engage(Character target) diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs index b920c690..bffacc54 100644 --- a/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs @@ -17,7 +17,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers public override void Update(DateTime tick) { + // todo: handle player stuff on tick + ((Player)this.owner).statusEffects.Update(tick); } public override void ChangeTarget(Character target) diff --git a/FFXIVClassic Map Server/actors/chara/npc/Mob.cs b/FFXIVClassic Map Server/actors/chara/npc/Mob.cs index d0616af7..73b227f6 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/Mob.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/Mob.cs @@ -16,6 +16,8 @@ namespace FFXIVClassic_Map_Server.Actors class Mob : Npc { public HateContainer hateContainer; + public StatusEffects statusEffects; + public Mob(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, ushort actorState, uint animationId, string customDisplayName) : base(actorNumber, actorClass, uniqueId, spawnedArea, posX, posY, posZ, rot, actorState, animationId, customDisplayName) @@ -23,6 +25,7 @@ namespace FFXIVClassic_Map_Server.Actors this.aiContainer = new AIContainer(this, new MobController(this), new PathFind(this), new TargetFind(this)); this.currentSubState = SetActorStatePacket.SUB_STATE_MONSTER; this.hateContainer = new HateContainer(this); + this.statusEffects = new StatusEffects(this); } } } diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index a63dc7cc..63eb4845 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -141,6 +141,8 @@ namespace FFXIVClassic_Map_Server.Actors public Session playerSession; + public StatusEffects statusEffects; + public Player(Session cp, uint actorID) : base(actorID) { playerSession = cp; @@ -249,7 +251,9 @@ namespace FFXIVClassic_Map_Server.Actors Database.LoadPlayerCharacter(this); lastPlayTimeUpdate = Utils.UnixTimeStampUTC(); + this.aiContainer = new AIContainer(this, new PlayerController(this), null, new TargetFind(this)); + this.statusEffects = new StatusEffects(this); } public List Create0x132Packets(uint playerActorId)