Refactor StatusEffectContainer to better handle messages using

CommandResultContainer.

Alter Modifiers to be the same as ParamNames.

Add LoseOnClassChange flag for status effects.

Add a few missing status effects.

Fix EndTime for stance status effects to stop icon from blinking.
This commit is contained in:
Yogurt
2019-05-27 23:05:20 -07:00
parent cc07e1f453
commit 26ef649a6c
17 changed files with 586 additions and 381 deletions

View File

@@ -336,17 +336,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
enum StatusEffectFlags : uint
{
None = 0,
Silent = 1 << 0, // dont display effect loss message
//Loss flags
LoseOnDeath = 1 << 1, // effects removed on death
LoseOnZoning = 1 << 2, // effects removed on zoning
LoseOnEsuna = 1 << 3, // effects which can be removed with esuna (debuffs)
LoseOnDispel = 1 << 4, // some buffs which player might be able to dispel from mob
LoseOnLogout = 1 << 5, // effects removed on logging out
LoseOnAttacking = 1 << 6, // effects removed when owner attacks another entity
LoseOnCastStart = 1 << 7, // effects removed when owner starts casting
LoseOnAggro = 1 << 8, // effects removed when owner gains enmity (swiftsong)
//Loss flags - Do we need loseonattacking/caststart? Could just be done with activate flags
LoseOnDeath = 1 << 0, // effects removed on death
LoseOnZoning = 1 << 1, // effects removed on zoning
LoseOnEsuna = 1 << 2, // effects which can be removed with esuna (debuffs)
LoseOnDispel = 1 << 3, // some buffs which player might be able to dispel from mob
LoseOnLogout = 1 << 4, // effects removed on logging out
LoseOnAttacking = 1 << 5, // effects removed when owner attacks another entity
LoseOnCastStart = 1 << 6, // effects removed when owner starts casting
LoseOnAggro = 1 << 7, // effects removed when owner gains enmity (swiftsong)
LoseOnClassChange = 1 << 8, //Effect falls off whhen changing class
//Activate flags
ActivateOnCastStart = 1 << 9, //Activates when a cast starts.
@@ -373,9 +373,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
PreventMovement = 1 << 26, // effects which prevent movement such as bind, still allows turning in place
PreventTurn = 1 << 27, // effects which prevent turning, such as stun
PreventUntarget = 1 << 28, // effects which prevent changing targets, such as fixation
Stealth = 1 << 29, // sneak/invis
Stance = 1 << 30, // effects that do not have a timer
Stance = 1 << 29 // effects that do not have a timer
}
enum StatusEffectOverwrite : byte
@@ -392,19 +390,22 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
private Character owner;
private Character source;
private StatusEffectId id;
private string name; // name of this effect
private DateTime startTime; // when was this effect added
private DateTime endTime; // when this status falls off
private DateTime lastTick; // when did this effect last tick
private uint duration; // how long should this effect last in seconds
private uint tickMs; // how often should this effect proc
private double 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 double extra; // optional value
private StatusEffectFlags flags; // death/erase/dispel etc
private StatusEffectOverwrite overwrite; // how to handle adding an effect with same id (see StatusEfectOverwrite)
private bool silent = false; // do i send a message on losing effect
private bool hidden = false;
private string name; // name of this effect
private DateTime startTime; // when was this effect added
private DateTime endTime; // when this status falls off
private DateTime lastTick; // when did this effect last tick
private uint duration; // how long should this effect last in seconds
private uint tickMs; // how often should this effect proc
private double 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 double extra; // optional value
private StatusEffectFlags flags; // death/erase/dispel etc
private StatusEffectOverwrite overwrite; // how to handle adding an effect with same id (see StatusEfectOverwrite)
private bool silentOnGain = false; //Whether a message is sent when the status is gained
private bool silentOnLoss = false; //Whether a message is sent when the status is lost
private bool hidden = false; //Whether this status is shown. Used for things that aren't really status effects like exp chains and procs
private ushort statusGainTextId; //The text id used when the status is gained
private ushort statusLossTextId; //The text id used when the status effect falls off when its time runs out
public LuaScript script;
HitEffect animationEffect;
@@ -438,26 +439,34 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
this.name = effect.name;
this.flags = effect.flags;
this.overwrite = effect.overwrite;
this.statusGainTextId = effect.statusGainTextId;
this.statusLossTextId = effect.statusLossTextId;
this.extra = effect.extra;
this.script = effect.script;
this.silentOnGain = effect.silentOnGain;
this.silentOnLoss = effect.silentOnLoss;
this.hidden = effect.hidden;
}
public StatusEffect(uint id, string name, uint flags, uint overwrite, uint tickMs)
public StatusEffect(uint id, string name, uint flags, uint overwrite, uint tickMs, bool hidden, bool silentOnGain, bool silentOnLoss)
{
this.id = (StatusEffectId)id;
this.name = name;
this.flags = (StatusEffectFlags)flags;
this.overwrite = (StatusEffectOverwrite)overwrite;
this.tickMs = tickMs;
this.hidden = hidden;
this.silentOnGain = silentOnGain;
this.silentOnLoss = silentOnLoss;
}
// return true when duration has elapsed
public bool Update(DateTime tick)
public bool Update(DateTime tick, CommandResultContainer resultContainer = null)
{
if (tickMs != 0 && (tick - lastTick).TotalMilliseconds >= tickMs)
{
lastTick = tick;
if (LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this) > 0)
if (LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this, resultContainer) > 0)
return true;
}
@@ -553,9 +562,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return (byte)overwrite;
}
public bool GetSilent()
public bool GetSilentOnGain()
{
return silent;
return silentOnGain;
}
public bool GetSilentOnLoss()
{
return silentOnLoss;
}
public bool GetHidden()
@@ -563,6 +577,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return hidden;
}
public ushort GetStatusGainTextId()
{
return 30328;
return statusGainTextId;
}
public ushort GetStatusLossTextId()
{
return statusLossTextId;
}
public void SetStartTime(DateTime time)
{
this.startTime = time;
@@ -571,7 +596,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public void SetEndTime(DateTime time)
{
endTime = time;
//If it's a stance, just set endtime to highest number possible for XIV
if ((flags & StatusEffectFlags.Stance) != 0)
{
endTime = Utils.UnixTimeStampToDateTime(4294967295);
}
else
{
endTime = time;
}
}
//Refresh the status, updating the end time based on the duration of the status and broadcasts the new time
@@ -634,9 +667,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
this.overwrite = (StatusEffectOverwrite)overwrite;
}
public void SetSilent(bool silent)
public void SetSilentOnGain(bool silent)
{
this.silent = silent;
this.silentOnGain = silent;
}
public void SetSilentOnLoss(bool silent)
{
this.silentOnLoss = silent;
}
public void SetHidden(bool hidden)
@@ -644,6 +682,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
this.hidden = hidden;
}
public void SetStatusGainTextId(ushort textId)
{
this.statusGainTextId = textId;
}
public void SetStatusLossTextId(ushort textId)
{
this.statusLossTextId = textId;
}
public void SetAnimation(uint hitEffect)
{
animationEffect = (HitEffect)hitEffect;

View File

@@ -109,55 +109,44 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return effects.ContainsKey((uint)id);
}
public CommandResult AddStatusForCommandResult(uint id, byte tier = 1, ulong magnitude = 0, uint duration = 0)
{
CommandResult action = null;
if (AddStatusEffect(id, tier, magnitude, duration))
action = new CommandResult(owner.actorId, 30328, id | (uint)HitEffect.StatusEffectType);
return action;
}
public bool AddStatusEffect(uint id)
public bool AddStatusEffect(uint id, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
return AddStatusEffect(se, owner);
return AddStatusEffect(se, owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(uint id, byte tier)
public bool AddStatusEffect(uint id, byte tier, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
se.SetTier(tier);
return AddStatusEffect(se, owner);
return AddStatusEffect(se, owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(uint id, byte tier, double magnitude)
public bool AddStatusEffect(uint id, byte tier, double magnitude, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
se.SetMagnitude(magnitude);
se.SetTier(tier);
return AddStatusEffect(se, owner);
return AddStatusEffect(se, owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(uint id, byte tier, double magnitude, uint duration, int tickMs = 3000)
public bool AddStatusEffect(uint id, byte tier, double magnitude, uint duration, int tickMs, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
var se = Server.GetWorldManager().GetStatusEffect(id);
if (se != null)
{
se.SetDuration(duration);
se.SetStartTime(DateTime.Now);
se.SetOwner(owner);
}
return AddStatusEffect(se ?? new StatusEffect(this.owner, id, magnitude, 3000, duration, tier), owner);
return AddStatusEffect(se ?? new StatusEffect(this.owner, id, magnitude, 3000, duration, tier), owner, actionContainer, worldmasterTextId);
}
public bool AddStatusEffect(StatusEffect newEffect, Character source, bool silent = false, bool hidden = false)
public bool AddStatusEffect(StatusEffect newEffect, Character source, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328)
{
/*
worldMasterTextId
@@ -179,9 +168,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (canOverwrite || effect == null)
{
// send packet to client with effect added message
if (effect != null && (!silent || !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0))
if (newEffect != null && !newEffect.GetSilentOnGain())
{
// todo: send packet to client with effect added message
if (actionContainer != null)
actionContainer.AddAction(new CommandResult(owner.actorId, worldmasterTextId, newEffect.GetStatusEffectId() | (uint)HitEffect.StatusEffectType));
}
// wont send a message about losing effect here
@@ -194,13 +184,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
if (effects.Count < MAX_EFFECTS)
{
if(newEffect.script != null)
newEffect.CallLuaFunction(this.owner, "onGain", this.owner, newEffect);
else
LuaEngine.CallLuaStatusEffectFunction(this.owner, newEffect, "onGain", this.owner, newEffect);
newEffect.CallLuaFunction(this.owner, "onGain", this.owner, newEffect, actionContainer);
effects.Add(newEffect.GetStatusEffectId(), newEffect);
//newEffect.SetSilent(silent);
newEffect.SetHidden(hidden);
if (!newEffect.GetHidden())
{
@@ -225,15 +211,19 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return false;
}
public bool RemoveStatusEffect(StatusEffect effect, bool silent = false)
//playEffect determines whether the effect of the animation that's going to play with actionContainer is going to play on owner
//Generally, for abilities removing an effect, this is true and for effects removing themselves it's false.
public bool RemoveStatusEffect(StatusEffect effect, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30331, bool playEffect = true)
{
bool removedEffect = false;
if (effect != null && effects.ContainsKey(effect.GetStatusEffectId()))
{
// send packet to client with effect remove message
if (!silent && !effect.GetSilent() && (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0)
if (!effect.GetSilentOnLoss())
{
owner.DoBattleAction(0, 0, new CommandResult(owner.actorId, 30331, effect.GetStatusEffectId()));
//Only send a message if we're using an actioncontainer and the effect normally sends a message when it's lost
if (actionContainer != null)
actionContainer.AddAction(new CommandResult(owner.actorId, worldmasterTextId, effect.GetStatusEffectId() | (playEffect ? 0 : (uint)HitEffect.StatusLossType)));
}
//hidden effects not in charawork
@@ -243,54 +233,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
SetStatusAtIndex(index, 0);
SetTimeAtIndex(index, 0);
}
// function onLose(actor, effect)
effects.Remove(effect.GetStatusEffectId());
if(effect.script != null)
effect.CallLuaFunction(owner, "onLose", owner, effect);
else
LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect);
effect.CallLuaFunction(owner, "onLose", owner, effect, actionContainer);
owner.RecalculateStats();
sendUpdate = true;
removedEffect = true;
}
return removedEffect;
}
public bool RemoveStatusEffect(uint effectId, bool silent = false)
public bool RemoveStatusEffect(uint effectId, CommandResultContainer resultContainer = null, ushort worldmasterTextId = 30331, bool playEffect = true)
{
bool removedEffect = false;
foreach (var effect in effects.Values)
{
if (effect.GetStatusEffectId() == effectId)
{
RemoveStatusEffect(effect, effect.GetSilent() || silent);
removedEffect = true;
break;
}
}
return removedEffect;
}
//Remove status effect and return the CommandResult message instead of sending it immediately
public CommandResult RemoveStatusEffectForCommandResult(uint effectId, ushort worldMasterTextId = 30331)
{
CommandResult action = null;
if (RemoveStatusEffect(effectId, true))
action = new CommandResult(owner.actorId, worldMasterTextId, effectId);
return action;
}
//Remove status effect and return the CommandResult message instead of sending it immediately
public CommandResult RemoveStatusEffectForCommandResult(StatusEffect effect, ushort worldMasterTextId = 30331)
{
CommandResult action = null;
if (RemoveStatusEffect(effect, true))
action = new CommandResult(owner.actorId, worldMasterTextId, effect.GetStatusEffectId());
return action;
return RemoveStatusEffect(GetStatusEffectById(effectId), resultContainer, worldmasterTextId, playEffect);
}
public StatusEffect CopyEffect(StatusEffect effect)
@@ -301,14 +257,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return AddStatusEffect(newEffect, effect.GetSource()) ? newEffect : null;
}
public bool RemoveStatusEffectsByFlags(uint flags, bool silent = false)
public bool RemoveStatusEffectsByFlags(uint flags, CommandResultContainer resultContainer = null)
{
// build list of effects to remove
var removeEffects = GetStatusEffectsByFlag(flags);
// remove effects from main list
foreach (var effect in removeEffects)
RemoveStatusEffect(effect, silent);
RemoveStatusEffect(effect, resultContainer, effect.GetStatusLossTextId(), true);
// removed an effect with one of these flags
return removeEffects.Count > 0;
@@ -334,6 +290,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
return list;
}
public StatusEffect GetRandomEffectByFlag(uint flag)
{
var list = GetStatusEffectsByFlag(flag);
if (list.Count > 0)
return list[Program.Random.Next(list.Count)];
return null;
}
// todo: why the fuck cant c# convert enums/
public bool HasStatusEffectsByFlag(StatusEffectFlags flags)
{
@@ -442,7 +408,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
SetStatusAtIndex(index, (ushort) (newEffectId - 200000));
SetTimeAtIndex(index, time);
return new CommandResult(owner.actorId, 30328, (uint) HitEffect.StatusEffectType | newEffectId);
return new CommandResult(owner.actorId, 30330, (uint) HitEffect.StatusEffectType | newEffectId);
}
}
}

View File

@@ -210,7 +210,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
Disengage();
return;
}
owner.SetMod((uint)Modifier.Speed, 5);
owner.SetMod((uint)Modifier.MovementSpeed, 5);
if ((tick - lastCombatTickScript).TotalSeconds > 3)
{
Move();
@@ -363,7 +363,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
// todo: seems ffxiv doesnt even differentiate between sneak/invis?
{
hasSneak = target.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.Stealth);
hasSneak = target.GetMod(Modifier.Stealth) > 0;
hasInvisible = hasSneak;
}

View File

@@ -16,7 +16,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
owner.Disengage();
owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD);
owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath, true);
owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath);
//var deathStatePacket = SetActorStatePacket.BuildPacket(owner.actorId, SetActorStatePacket.MAIN_STATE_DEAD2, owner.currentSubState);
//owner.zone.BroadcastPacketAroundActor(owner, deathStatePacket);
canInterrupt = false;

View File

@@ -303,7 +303,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
public static double GetParryRate(Character attacker, Character defender, BattleCommand skill, CommandResult action)
{
//Can't parry with shield, can't parry rear attacks
if (defender.GetMod((uint)Modifier.HasShield) != 0 || action.param == (byte) HitDirection.Rear)
if (defender.GetMod((uint)Modifier.CanBlock) != 0 || action.param == (byte) HitDirection.Rear)
return 0;
double parryRate = 10.0;
@@ -348,7 +348,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
public static double GetBlockRate(Character attacker, Character defender, BattleCommand skill, CommandResult action)
{
//Shields are required to block and can't block from rear.
if (defender.GetMod((uint)Modifier.HasShield) == 0 || action.param == (byte)HitDirection.Rear)
if (defender.GetMod((uint)Modifier.CanBlock) == 0 || action.param == (byte)HitDirection.Rear)
return 0;
short dlvl = (short)(defender.GetLevel() - attacker.GetLevel());
@@ -894,10 +894,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
totalBonus += GetChainBonus(expChainNumber);
StatusEffect newChain = Server.GetWorldManager().GetStatusEffect((uint)StatusEffectId.EXPChain);
newChain.SetSilent(true);
newChain.SetDuration(timeLimit);
newChain.SetTier((byte)(Math.Min(expChainNumber + 1, 255)));
attacker.statusEffects.AddStatusEffect(newChain, attacker, true, true);
attacker.statusEffects.AddStatusEffect(newChain, attacker);
actionContainer?.AddEXPActions(attacker.AddExp(baseExp, (byte)attacker.GetClass(), (byte)(totalBonus.Min(255))));
}