mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-06-11 23:14:39 +02:00
Merged in takhlaq/ffxiv-classic-server (pull request #61)
Combat changes and fixes. Approved-by: Filip Maj <filipmaj@gmail.com>
This commit is contained in:
commit
ec85cfd590
169 changed files with 2595 additions and 1449 deletions
|
@ -925,13 +925,13 @@ namespace FFXIVClassic_Map_Server
|
|||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
var id = reader.GetUInt32(0);
|
||||
var duration = reader.GetUInt32(1);
|
||||
var magnitude = reader.GetUInt64(2);
|
||||
var tick = reader.GetUInt32(3);
|
||||
var tier = reader.GetByte(4);
|
||||
var extra = reader.GetUInt64(5);
|
||||
|
||||
var id = reader.GetUInt32("statusId");
|
||||
var duration = reader.GetUInt32("duration");
|
||||
var magnitude = reader.GetUInt64("magnitude");
|
||||
var tick = reader.GetUInt32("tick");
|
||||
var tier = reader.GetByte("tier");
|
||||
var extra = reader.GetUInt64("extra");
|
||||
|
||||
var effect = Server.GetWorldManager().GetStatusEffect(id);
|
||||
if (effect != null)
|
||||
{
|
||||
|
@ -942,7 +942,7 @@ namespace FFXIVClassic_Map_Server
|
|||
effect.SetExtra(extra);
|
||||
|
||||
// dont wanna send ton of messages on login (i assume retail doesnt)
|
||||
player.statusEffects.AddStatusEffect(effect, null, true);
|
||||
player.statusEffects.AddStatusEffect(effect, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2287,7 +2287,7 @@ namespace FFXIVClassic_Map_Server
|
|||
{
|
||||
conn.Open();
|
||||
|
||||
var query = @"SELECT id, name, flags, overwrite, tickMs FROM server_statuseffects;";
|
||||
var query = @"SELECT id, name, flags, overwrite, tickMs, hidden, silentOnGain, silentOnLoss, statusGainTextId, statusLossTextId FROM server_statuseffects;";
|
||||
|
||||
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||
|
||||
|
@ -2300,7 +2300,14 @@ namespace FFXIVClassic_Map_Server
|
|||
var flags = reader.GetUInt32("flags");
|
||||
var overwrite = reader.GetByte("overwrite");
|
||||
var tickMs = reader.GetUInt32("tickMs");
|
||||
var effect = new StatusEffect(id, name, flags, overwrite, tickMs);
|
||||
var hidden = reader.GetBoolean("hidden");
|
||||
var silentOnGain = reader.GetBoolean("silentOnGain");
|
||||
var silentOnLoss = reader.GetBoolean("silentOnLoss");
|
||||
var statusGainTextId = reader.GetUInt16("statusGainTextId");
|
||||
var statusLossTextId = reader.GetUInt16("statusLossTextId");
|
||||
|
||||
var effect = new StatusEffect(id, name, flags, overwrite, tickMs, hidden, silentOnGain, silentOnLoss, statusGainTextId, statusLossTextId);
|
||||
|
||||
lua.LuaEngine.LoadStatusEffectScript(effect);
|
||||
effects.Add(id, effect);
|
||||
}
|
||||
|
@ -2392,8 +2399,8 @@ namespace FFXIVClassic_Map_Server
|
|||
battleCommand.job = reader.GetByte("classJob");
|
||||
battleCommand.level = reader.GetByte("lvl");
|
||||
battleCommand.requirements = (BattleCommandRequirements)reader.GetUInt16("requirements");
|
||||
battleCommand.mainTarget = (ValidTarget)reader.GetByte("mainTarget");
|
||||
battleCommand.validTarget = (ValidTarget)reader.GetByte("validTarget");
|
||||
battleCommand.mainTarget = (ValidTarget)reader.GetUInt16("mainTarget");
|
||||
battleCommand.validTarget = (ValidTarget)reader.GetUInt16("validTarget");
|
||||
battleCommand.aoeType = (TargetFindAOEType)reader.GetByte("aoeType");
|
||||
battleCommand.basePotency = reader.GetUInt16("basePotency");
|
||||
battleCommand.numHits = reader.GetByte("numHits");
|
||||
|
@ -2410,8 +2417,8 @@ namespace FFXIVClassic_Map_Server
|
|||
battleCommand.castTimeMs = reader.GetUInt32("castTime");
|
||||
battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime");
|
||||
battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000;
|
||||
battleCommand.mpCost = reader.GetUInt16("mpCost");
|
||||
battleCommand.tpCost = reader.GetUInt16("tpCost");
|
||||
battleCommand.mpCost = reader.GetInt16("mpCost");
|
||||
battleCommand.tpCost = reader.GetInt16("tpCost");
|
||||
battleCommand.animationType = reader.GetByte("animationType");
|
||||
battleCommand.effectAnimation = reader.GetUInt16("effectAnimation");
|
||||
battleCommand.modelAnimation = reader.GetUInt16("modelAnimation");
|
||||
|
@ -2433,7 +2440,26 @@ namespace FFXIVClassic_Map_Server
|
|||
battleCommand.actionType = (ActionType)reader.GetInt16("actionType");
|
||||
battleCommand.accuracyModifier = reader.GetFloat("accuracyMod");
|
||||
battleCommand.worldMasterTextId = reader.GetUInt16("worldMasterTextId");
|
||||
lua.LuaEngine.LoadBattleCommandScript(battleCommand, "weaponskill");
|
||||
|
||||
string folderName = "";
|
||||
|
||||
switch (battleCommand.commandType)
|
||||
{
|
||||
case CommandType.AutoAttack:
|
||||
folderName = "autoattack";
|
||||
break;
|
||||
case CommandType.WeaponSkill:
|
||||
folderName = "weaponskill";
|
||||
break;
|
||||
case CommandType.Ability:
|
||||
folderName = "ability";
|
||||
break;
|
||||
case CommandType.Spell:
|
||||
folderName = "magic";
|
||||
break;
|
||||
}
|
||||
|
||||
lua.LuaEngine.LoadBattleCommandScript(battleCommand, folderName);
|
||||
battleCommandDict.Add(id, battleCommand);
|
||||
|
||||
Tuple<byte, short> tuple = Tuple.Create<byte, short>(battleCommand.job, battleCommand.level);
|
||||
|
|
|
@ -478,7 +478,7 @@ namespace FFXIVClassic_Map_Server
|
|||
battleNpcGenusMods.TryGetValue(battleNpc.genusId, out battleNpc.genusMods);
|
||||
battleNpcSpawnMods.TryGetValue(battleNpc.GetBattleNpcId(), out battleNpc.spawnMods);
|
||||
|
||||
battleNpc.SetMod((uint)Modifier.Speed, reader.GetByte("speed"));
|
||||
battleNpc.SetMod((uint)Modifier.MovementSpeed, reader.GetByte("speed"));
|
||||
battleNpc.neutral = reader.GetByte("aggroType") == 0;
|
||||
|
||||
battleNpc.SetDetectionType(reader.GetUInt32("detection"));
|
||||
|
@ -603,7 +603,7 @@ namespace FFXIVClassic_Map_Server
|
|||
reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), "");
|
||||
|
||||
battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId"));
|
||||
battleNpc.SetMod((uint)Modifier.Speed, reader.GetByte("speed"));
|
||||
battleNpc.SetMod((uint)Modifier.MovementSpeed, reader.GetByte("speed"));
|
||||
battleNpc.neutral = reader.GetByte("aggroType") == 0;
|
||||
|
||||
// set mob mods
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
Stats = 0x100,
|
||||
Status = 0x200,
|
||||
StatusTime = 0x400,
|
||||
Hotbar = 0x800,
|
||||
|
||||
AllNpc = 0xDF,
|
||||
AllPlayer = 0x13F
|
||||
|
@ -92,6 +93,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK;
|
||||
this.moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN;
|
||||
this.moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE;
|
||||
positionUpdates = new List<Vector3>();
|
||||
}
|
||||
|
||||
public void SetPushCircleRange(string triggerName, float size)
|
||||
|
@ -650,7 +652,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
public void LookAt(Actor actor)
|
||||
{
|
||||
if (actor != null && actor != this)
|
||||
if (actor != null)
|
||||
{
|
||||
LookAt(actor.positionX, actor.positionZ);
|
||||
}
|
||||
|
@ -670,16 +672,20 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
public void LookAt(float x, float z)
|
||||
{
|
||||
var rot1 = this.rotation;
|
||||
//Don't rotate if the lookat position is same as our current position
|
||||
if (positionX != x || positionZ != z)
|
||||
{
|
||||
var rot1 = this.rotation;
|
||||
|
||||
var dX = this.positionX - x;
|
||||
var dY = this.positionZ - z;
|
||||
var rot2 = Math.Atan2(dY, dX);
|
||||
var dRot = Math.PI - rot2 + Math.PI / 2;
|
||||
var dX = this.positionX - x;
|
||||
var dY = this.positionZ - z;
|
||||
var rot2 = Math.Atan2(dY, dX);
|
||||
var dRot = Math.PI - rot2 + Math.PI / 2;
|
||||
|
||||
// pending move, dont need to unset it
|
||||
this.updateFlags |= ActorUpdateFlags.Position;
|
||||
rotation = (float)dRot;
|
||||
// pending move, dont need to unset it
|
||||
this.updateFlags |= ActorUpdateFlags.Position;
|
||||
rotation = (float)dRot;
|
||||
}
|
||||
}
|
||||
|
||||
// todo: is this legit?
|
||||
|
|
|
@ -149,8 +149,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
// todo: move this somewhere more appropriate
|
||||
// todo: base this on equip and shit
|
||||
SetMod((uint)Modifier.AttackRange, 3);
|
||||
SetMod((uint)Modifier.AttackDelay, (Program.Random.Next(30, 60) * 100));
|
||||
SetMod((uint)Modifier.Speed, (uint)moveSpeeds[2]);
|
||||
SetMod((uint)Modifier.Delay, (Program.Random.Next(30, 60) * 100));
|
||||
SetMod((uint)Modifier.MovementSpeed, (uint)moveSpeeds[2]);
|
||||
|
||||
spawnX = positionX;
|
||||
spawnY = positionY;
|
||||
|
@ -238,8 +238,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
public void DoBattleAction(ushort commandId, uint animationId, CommandResult[] results)
|
||||
{
|
||||
int currentIndex = 0;
|
||||
//AoE abilities only ever hit 16 people, so we probably won't need this loop anymore
|
||||
//Apparently aoe are limited to 8?
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (results.Length - currentIndex >= 10)
|
||||
|
@ -253,9 +252,6 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
else
|
||||
break;
|
||||
|
||||
//I think aoe effects play on all hit enemies. Firaga does at least
|
||||
//animationId = 0; //If more than one packet is sent out, only send the animation once to avoid double playing.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,7 +313,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
else
|
||||
modifiers.Add((Modifier)modifier, val);
|
||||
|
||||
if (modifier <= 35)
|
||||
if (modifier >= 3 && modifier <= 35)
|
||||
updateFlags |= ActorUpdateFlags.Stats;
|
||||
}
|
||||
|
||||
|
@ -344,6 +340,29 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
SetMod(modifier, newVal);
|
||||
}
|
||||
|
||||
public void MultiplyMod(Modifier modifier, double val)
|
||||
{
|
||||
MultiplyMod((uint)modifier, val);
|
||||
}
|
||||
|
||||
public void MultiplyMod(uint modifier, double val)
|
||||
{
|
||||
double newVal = GetMod(modifier) * val;
|
||||
SetMod(modifier, newVal);
|
||||
}
|
||||
|
||||
public void DivideMod(Modifier modifier, double val)
|
||||
{
|
||||
DivideMod((uint)modifier, val);
|
||||
}
|
||||
|
||||
public void DivideMod(uint modifier, double val)
|
||||
{
|
||||
double newVal = GetMod(modifier) / val;
|
||||
SetMod(modifier, newVal);
|
||||
}
|
||||
|
||||
|
||||
public virtual void OnPath(Vector3 point)
|
||||
{
|
||||
//lua.LuaEngine.CallLuaBattleFunction(this, "onPath", this, point);
|
||||
|
@ -380,7 +399,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
if ((updateFlags & ActorUpdateFlags.SubState) != 0)
|
||||
{
|
||||
//packets.Add(SetActorSubStatePacket.BuildPacket(actorId, currentSubState));
|
||||
packets.Add(SetActorSubStatePacket.BuildPacket(actorId, currentSubState));
|
||||
//packets.Add(CommandResultX00Packet.BuildPacket(actorId, 0x72000062, 0));
|
||||
//packets.Add(CommandResultX01Packet.BuildPacket(actorId, 0x7C000062, 21001, new CommandResult(actorId, 0, 1)));
|
||||
|
||||
|
@ -390,6 +409,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
if ((updateFlags & ActorUpdateFlags.Status) != 0)
|
||||
{
|
||||
|
||||
List<SubPacket> statusPackets = statusEffects.GetStatusPackets();
|
||||
packets.AddRange(statusPackets);
|
||||
statusPackets.Clear();
|
||||
|
@ -428,24 +448,15 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanCast(Character target, BattleCommand spell)
|
||||
public virtual bool CanUse(Character target, BattleCommand skill, CommandResult error = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool CanWeaponSkill(Character target, BattleCommand skill)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual bool CanUseAbility(Character target, BattleCommand ability)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual uint GetAttackDelayMs()
|
||||
{
|
||||
return (uint)GetMod((uint)Modifier.AttackDelay);
|
||||
return (uint)GetMod((uint)Modifier.Delay);
|
||||
}
|
||||
|
||||
public virtual uint GetAttackRange()
|
||||
|
@ -523,8 +534,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
aiContainer.Reset();
|
||||
// todo: reset hp/mp/tp etc here
|
||||
ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE);
|
||||
charaWork.parameterSave.hp = charaWork.parameterSave.hpMax;
|
||||
charaWork.parameterSave.mp = charaWork.parameterSave.mpMax;
|
||||
SetHP((uint) GetMaxHP());
|
||||
SetMP((uint) GetMaxMP());
|
||||
RecalculateStats();
|
||||
}
|
||||
|
||||
|
@ -609,8 +620,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
public void SetMP(uint mp)
|
||||
{
|
||||
charaWork.parameterSave.mpMax = (short)mp;
|
||||
if (mp > charaWork.parameterSave.hpMax[0])
|
||||
charaWork.parameterSave.mp = (short)mp;
|
||||
if (mp > charaWork.parameterSave.mpMax)
|
||||
SetMaxMP(mp);
|
||||
|
||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||
|
@ -621,8 +632,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
charaWork.parameterSave.mp = (short)mp;
|
||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||
}
|
||||
|
||||
// todo: the following functions are virtuals since we want to check hidden item bonuses etc on player for certain conditions
|
||||
public virtual void AddHP(int hp)
|
||||
public virtual void AddHP(int hp, CommandResultContainer resultContainer = null)
|
||||
{
|
||||
// dont wanna die ded, don't want to send update if hp isn't actually changed
|
||||
if (IsAlive() && hp != 0)
|
||||
|
@ -634,6 +646,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
charaWork.parameterSave.hp[0] = (short)addHp;
|
||||
|
||||
updateFlags |= ActorUpdateFlags.HpTpMp;
|
||||
|
||||
if (charaWork.parameterSave.hp[0] < 1)
|
||||
Die(Program.Tick, resultContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -675,9 +690,9 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
}
|
||||
|
||||
public void DelHP(int hp)
|
||||
public void DelHP(int hp, CommandResultContainer resultContainer = null)
|
||||
{
|
||||
AddHP((short)-hp);
|
||||
AddHP((short)-hp, resultContainer);
|
||||
}
|
||||
|
||||
public void DelMP(int mp)
|
||||
|
@ -743,7 +758,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
public virtual float GetSpeed()
|
||||
{
|
||||
// todo: for battlenpc/player calculate speed
|
||||
return (float) GetMod((uint)Modifier.Speed);
|
||||
return (float) GetMod((uint)Modifier.MovementSpeed);
|
||||
}
|
||||
|
||||
public virtual void OnAttack(State state, CommandResult action, ref CommandResult error)
|
||||
|
@ -821,55 +836,55 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
}
|
||||
|
||||
public virtual void OnDamageDealt(Character defender, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public virtual void OnDamageDealt(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
switch (action.hitType)
|
||||
{
|
||||
case (HitType.Miss):
|
||||
OnMiss(this, action, actionContainer);
|
||||
OnMiss(defender, skill, action, actionContainer);
|
||||
break;
|
||||
case (HitType.Crit):
|
||||
OnCrit(defender, skill, action, actionContainer);
|
||||
OnHit(defender, skill, action, actionContainer);
|
||||
break;
|
||||
default:
|
||||
OnHit(defender, action, actionContainer);
|
||||
OnHit(defender, skill, action, actionContainer);
|
||||
break;
|
||||
}
|
||||
|
||||
//TP is only gained from autoattacks and abilities
|
||||
if (action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability)
|
||||
if ((action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability) && action.hitType != HitType.Miss)
|
||||
{
|
||||
//TP gained on an attack is usually 100 * delay.
|
||||
//Store TP seems to add .1% per point.
|
||||
double weaponDelay = GetMod(Modifier.AttackDelay) / 1000.0;
|
||||
var storeTPPercent = 1 + (GetMod(Modifier.StoreTP) * 0.001);
|
||||
double weaponDelay = GetMod(Modifier.Delay) / 1000.0;
|
||||
var storeTPPercent = 1 + (GetMod(Modifier.StoreTp) * 0.001);
|
||||
AddTP((int)(weaponDelay * 100 * storeTPPercent));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void OnDamageTaken(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public virtual void OnDamageTaken(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
switch (action.hitType)
|
||||
{
|
||||
case (HitType.Miss):
|
||||
OnEvade(attacker, action, actionContainer);
|
||||
OnEvade(attacker, skill, action, actionContainer);
|
||||
break;
|
||||
case (HitType.Parry):
|
||||
OnParry(attacker, action, actionContainer);
|
||||
OnParry(attacker, skill, action, actionContainer);
|
||||
break;
|
||||
case (HitType.Block):
|
||||
OnBlock(attacker, action, actionContainer);
|
||||
OnBlock(attacker, skill, action, actionContainer);
|
||||
break;
|
||||
}
|
||||
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, action);
|
||||
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, skill, action, actionContainer);
|
||||
|
||||
//TP gain formula seems to be something like 5 * e ^ ( -0.667 * [defender's level] ) * damage taken, rounded up
|
||||
//This should be completely accurate at level 50, but isn't totally accurate at lower levels.
|
||||
//Don't know if store tp impacts this
|
||||
double tpModifier = 5 * Math.Pow(Math.E, (-0.0667 * GetLevel()));
|
||||
AddTP((int)Math.Ceiling(tpModifier * action.amount));
|
||||
|
||||
|
||||
if (charaWork.parameterSave.hp[0] < 1)
|
||||
Die(Program.Tick, actionContainer);
|
||||
}
|
||||
|
||||
public UInt64 GetTempVar(string name)
|
||||
|
@ -978,8 +993,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
{
|
||||
StatusEffect procEffect = Server.GetWorldManager().GetStatusEffect(effectId);
|
||||
procEffect.SetDuration(5);
|
||||
procEffect.SetSilent(true);
|
||||
statusEffects.AddStatusEffect(procEffect, this, true, true);
|
||||
statusEffects.AddStatusEffect(procEffect, this);
|
||||
}
|
||||
//Otherwise we're reseting a proc, remove the status
|
||||
else
|
||||
|
@ -1013,36 +1027,41 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
|
||||
//Called when this character evades attacker's action
|
||||
public void OnEvade(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public void OnEvade(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
SetProc((ushort)HitType.Evade);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnEvade, "onEvade", attacker, this, action, actionContainer);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnEvade, "onEvade", attacker, this, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
//Called when this character blocks attacker's action
|
||||
public void OnBlock(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public void OnBlock(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
SetProc((ushort)HitType.Block);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnBlock, "onBlock", attacker, this, action, actionContainer);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnBlock, "onBlock", attacker, this, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
//Called when this character parries attacker's action
|
||||
public void OnParry(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public void OnParry(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
SetProc((ushort)HitType.Parry);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, action, actionContainer);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
//Called when this character misses
|
||||
public void OnMiss(Character defender, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public void OnMiss(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
SetProc((ushort)HitType.Miss);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, action, actionContainer);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
public void OnHit(Character defender, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public void OnHit(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, action, actionContainer);
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
public void OnCrit(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCrit, "onCrit", this, defender, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
//The order of messages that appears after using a command is:
|
||||
|
@ -1056,7 +1075,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
//5. The hit itself. For single hit commands this message is "Your [command] hits [target] for x damage" for multi hits it's "[Target] takes x points of damage"
|
||||
//6. Stoneskin falling off
|
||||
//6. Buffs that activate after a command hits, like Aegis Boon and Divine Veil
|
||||
|
||||
|
||||
//After all hits
|
||||
//7. If it's a multi-hit command there's a "{numhits]fold attack..." message or if all hits miss an "All attacks missed" message
|
||||
//8. Buffs that fall off after the skill ends, like Excruciate
|
||||
|
@ -1092,7 +1111,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
lua.LuaEngine.CallLuaBattleCommandFunction(this, command, folder, "onSkillFinish", this, chara, command, action, actions);
|
||||
//cached script
|
||||
//skill.CallLuaFunction(owner, "onSkillFinish", this, chara, command, action, actions);
|
||||
if (action.hitType > HitType.Evade && action.hitType != HitType.Resist)
|
||||
if (action.ActionLanded())
|
||||
{
|
||||
hitTarget = true;
|
||||
hitCount++;
|
||||
|
@ -1116,18 +1135,20 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
actions.AddAction(new CommandResult(actorId, 30202, 0));
|
||||
}
|
||||
|
||||
DelMP(command.CalculateMpCost(this));
|
||||
DelTP(command.CalculateTpCost(this));
|
||||
|
||||
//Now that we know if we hit the target we can check if the combo continues
|
||||
if (this is Player)
|
||||
{
|
||||
if (command.isCombo && hitTarget)
|
||||
((Player)this).SetCombos(command.comboNextCommandId);
|
||||
else
|
||||
//Only reset combo if the command is a spell or weaponskill, since abilities can be used between combo skills
|
||||
else if (command.commandType == CommandType.Spell || command.commandType == CommandType.WeaponSkill)
|
||||
((Player)this).SetCombos();
|
||||
}
|
||||
|
||||
CommandResult error = new CommandResult(actorId, 0, 0);
|
||||
DelMP(command.CalculateMpCost(this));
|
||||
DelTP(command.CalculateTpCost(this));
|
||||
|
||||
actions.CombineLists();
|
||||
DoBattleAction(command.id, command.battleAnimation, actions.GetList());
|
||||
}
|
||||
|
@ -1135,8 +1156,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
public List<Character> GetPartyMembersInRange(uint range)
|
||||
{
|
||||
TargetFind targetFind = new TargetFind(this);
|
||||
targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0);
|
||||
targetFind.FindWithinArea(this, ValidTarget.PartyMember, TargetFindAOETarget.Self);
|
||||
targetFind.SetAOEType(ValidTarget.Party, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0);
|
||||
targetFind.FindWithinArea(this, ValidTarget.Party, TargetFindAOETarget.Self);
|
||||
return targetFind.GetTargets();
|
||||
}
|
||||
|
||||
|
|
|
@ -7,88 +7,157 @@ namespace FFXIVClassic_Map_Server.actors.chara
|
|||
//Also, 0-35 should probably match with up BattleTemp
|
||||
enum Modifier : UInt32
|
||||
{
|
||||
NAMEPLATE_SHOWN = 0,
|
||||
TARGETABLE = 1,
|
||||
NAMEPLATE_SHOWN2 = 2,
|
||||
//NAMEPLATE_SHOWN2 = 3,
|
||||
//These line up with ParamNames starting at 15001 and appear on gear
|
||||
//Health
|
||||
Hp = 0, //Max HP
|
||||
Mp = 1, //Max MP
|
||||
Tp = 2, //Max TP
|
||||
|
||||
Strength = 3,
|
||||
Vitality = 4,
|
||||
Dexterity = 5,
|
||||
Intelligence = 6,
|
||||
Mind = 7,
|
||||
Piety = 8,
|
||||
//Main stats
|
||||
Strength = 3,
|
||||
Vitality = 4,
|
||||
Dexterity = 5,
|
||||
Intelligence = 6,
|
||||
Mind = 7,
|
||||
Piety = 8,
|
||||
|
||||
ResistFire = 9,
|
||||
ResistIce = 10,
|
||||
ResistWind = 11,
|
||||
ResistLightning = 12,
|
||||
ResistEarth = 13,
|
||||
ResistWater = 14,
|
||||
//Elemental Resistances
|
||||
FireResistance = 9, //Lowers Fire damage taken
|
||||
IceResistance = 10, //Lowers Ice damage taken
|
||||
WindResistance = 11, //Lowers Wind damage taken
|
||||
EarthResistance = 12, //Lowers Earth damage taken
|
||||
LightningResistance = 13, //Lowers Lightning damage taken
|
||||
WaterResistance = 14, //Lowers Water damage taken
|
||||
|
||||
Accuracy = 15,
|
||||
Evasion = 16,
|
||||
Attack = 17,
|
||||
Defense = 18, //Is there a magic defense stat? 19 maybe?
|
||||
MagicAttack = 23,
|
||||
MagicHeal = 24,
|
||||
MagicEnhancePotency = 25,
|
||||
MagicEnfeeblingPotency = 26,
|
||||
//Physical Secondary stats
|
||||
Accuracy = 15, //Increases chance to hit with physical attacks
|
||||
Evasion = 16, //Decreases chance to be hit by physical attacks
|
||||
Attack = 17, //Increases damage done with physical attacks
|
||||
Defense = 18, //Decreases damage taken from physical attacks
|
||||
|
||||
MagicAccuracy = 27,
|
||||
MagicEvasion = 28,
|
||||
//Physical crit stats
|
||||
CriticalHitRating = 19, //Increases chance to crit with physical attacks
|
||||
CriticalHitEvasion = 20, //Decreases chance to be crit by physical attacks
|
||||
CriticalHitAttackPower = 21, //Increases damage done by critical physical attacks
|
||||
CriticalHitResilience = 22, //Decreases damage taken from critical physical attacks
|
||||
|
||||
CraftProcessing = 30,
|
||||
CraftMagicProcessing = 31,
|
||||
CraftProcessControl = 32,
|
||||
//Magic secondary stats
|
||||
AttackMagicPotency = 23, //Increases damage done with magical attacks
|
||||
HealingMagicPotency = 24, //Increases healing done with magic healing
|
||||
EnhancementMagicPotency = 25, //Increases effect of enhancement magic
|
||||
EnfeeblingMagicPotency = 26, //Increases effect of enfeebling magic
|
||||
MagicAccuracy = 27, //Decreases chance for magic to be evaded
|
||||
MagicEvasion = 28, //Increases chance to evade magic
|
||||
|
||||
HarvestPotency = 33,
|
||||
HarvestLimit = 34,
|
||||
HarvestRate = 35,
|
||||
//Crafting stats
|
||||
Craftsmanship = 29,
|
||||
MagicCraftsmanship = 30,
|
||||
Control = 31,
|
||||
Gathering = 32,
|
||||
Output = 33,
|
||||
Perception = 34,
|
||||
|
||||
None = 36,
|
||||
Hp = 37,
|
||||
HpPercent = 38,
|
||||
Mp = 39,
|
||||
MpPercent = 40,
|
||||
Tp = 41,
|
||||
TpPercent = 42,
|
||||
Regen = 43,
|
||||
Refresh = 44,
|
||||
//Magic crit stats
|
||||
MagicCriticalHitRating = 35, //Increases chance to crit with magical attacks
|
||||
MagicCriticalHitEvasion = 36, //Decreases chance to be crit by magical attacks
|
||||
MagicCriticalHitPotency = 37, //Increases damage done by critical magical attacks
|
||||
MagicCriticalHitResilience = 38, //Decreases damage taken from critical magical attacks
|
||||
|
||||
AttackRange = 45,
|
||||
Speed = 46,
|
||||
AttackDelay = 47,
|
||||
//Blocking stats
|
||||
Parry = 39, //Increases chance to parry
|
||||
BlockRate = 40, //Increases chance to block
|
||||
Block = 41, //Reduces damage taken from blocked attacks
|
||||
|
||||
Raise = 48,
|
||||
MinimumHpLock = 49, // hp cannot fall below this value
|
||||
AttackType = 50, // slashing, piercing, etc
|
||||
BlockRate = 51,
|
||||
Block = 52,
|
||||
CritRating = 53,
|
||||
HasShield = 54, // Need this because shields are required for blocks. Could have used BlockRate or Block but BlockRate is provided by Gallant Sollerets and Block is provided by some buffs.
|
||||
HitCount = 55, // Amount of hits in an auto attack. Usually 1, 2 for h2h, 3 with spinning heel
|
||||
//Elemental Potencies
|
||||
FireMagicPotency = 42, //Increases damage done by Fire Magic
|
||||
IceMagicPotency = 43, //Increases damage done by Ice Magic
|
||||
WindMagicPotency = 44, //Increases damage done by Wind Magic
|
||||
EarthMagicPotency = 45, //Increases damage done by Earth Magic
|
||||
LightningMagicPotency = 46, //Increases damage done by Lightning Magic
|
||||
WaterMagicPotency = 47, //Increases damage done by Water Magic
|
||||
|
||||
//Flat percent increases to these rates. Probably a better way to do this
|
||||
RawEvadeRate = 56,
|
||||
RawParryRate = 57,
|
||||
RawBlockRate = 58,
|
||||
RawResistRate = 59,
|
||||
RawHitRate = 60,
|
||||
RawCritRate = 61,
|
||||
//Miscellaneous
|
||||
Regen = 48, //Restores health over time
|
||||
Refresh = 49, //Restores MP over time
|
||||
StoreTp = 50, //Increases TP gained by auto attacks and damaging abiltiies
|
||||
Enmity = 51, //Increases enmity gained from actions
|
||||
Spikes = 52, //Deals damage or status to attacker when hit
|
||||
Haste = 53, //Increases attack speed
|
||||
//54 and 55 didn't have names and seem to be unused
|
||||
ReducedDurabilityLoss = 56, //Reduces durability loss
|
||||
IncreasedSpiritbondGain = 57, //Increases rate of spiritbonding
|
||||
Damage = 58, //Increases damage of auto attacks
|
||||
Delay = 59, //Increases rate of auto attacks
|
||||
Fastcast = 60, //Increases speed of casts
|
||||
MovementSpeed = 61, //Increases movement speed
|
||||
Exp = 62, //Increases experience gained
|
||||
RestingHp = 63, //?
|
||||
RestingMp = 64, //?
|
||||
|
||||
DamageTakenDown = 62, // Percent damage taken down
|
||||
StoreTP = 63, //.1% extra tp per point. Lancer trait is 50 StoreTP
|
||||
PhysicalCritRate = 64, //CritRating but only for physical attacks. Increases chance of critting.
|
||||
PhysicalCritEvasion = 65, //Opposite of CritRating. Reduces chance of being crit by phyiscal attacks
|
||||
PhysicalCritAttack = 66, //Increases damage done by Physical Critical hits
|
||||
PhysicalCritResilience = 67, //Decreases damage taken by Physical Critical hits
|
||||
Parry = 68, //Increases chance to parry
|
||||
MagicCritPotency = 69, //Increases
|
||||
Regain = 70, //TP regen, should be -90 out of combat, Invigorate sets to 100+ depending on traits
|
||||
RegenDown = 71, //Damage over time effects. Separate from normal Regen because of how they are displayed in game
|
||||
Stoneskin = 72, //Nullifies damage
|
||||
MinimumTpLock = 73, //Don't let TP fall below this, used in openings
|
||||
KnockbackImmune = 74 //Immune to knockback effects when above 0
|
||||
//Attack property resistances
|
||||
SlashingResistance = 65, //Reduces damage taken by slashing attacks
|
||||
PiercingResistance = 66, //Reduces damage taken by piercing attacks
|
||||
BluntResistance = 67, //Reduces damage taken by blunt attacks
|
||||
ProjectileResistance = 68, //Reduces damage taken by projectile attacks
|
||||
SonicResistance = 69, //Reduces damage taken by sonic attacks
|
||||
BreathResistance = 70, //Reduces damage taken by breath attacks
|
||||
PhysicalResistance = 71, //Reduces damage taken by physical attacks
|
||||
MagicResistance = 72, //Reduces damage taken by magic attacks
|
||||
|
||||
//Status resistances
|
||||
SlowResistance = 73, //Reduces chance to be inflicted with slow by status magic
|
||||
PetrificationResistance = 74, //Reduces chance to be inflicted with petrification by status magic
|
||||
ParalysisResistance = 75, //Reduces chance to be inflicted with paralysis by status magic
|
||||
SilenceResistance = 76, //Reduces chance to be inflicted with silence by status magic
|
||||
BlindResistance = 77, //Reduces chance to be inflicted with blind by status magic
|
||||
PoisonResistance = 78, //Reduces chance to be inflicted with poison by status magic
|
||||
StunResistance = 79, //Reduces chance to be inflicted with stun by status magic
|
||||
SleepResistance = 80, //Reduces chance to be inflicted with sleep by status magic
|
||||
BindResistance = 81, //Reduces chance to be inflicted with bind by status magic
|
||||
HeavyResistance = 82, //Reduces chance to be inflicted with heavy by status magic
|
||||
DoomResistance = 83, //Reduces chance to be inflicted with doom by status magic
|
||||
|
||||
//84-101 didn't have names and seem to be unused
|
||||
//Miscellaneous
|
||||
ConserveMp = 101, //Chance to reduce mp used by actions
|
||||
SpellInterruptResistance = 102, //Reduces chance to be interrupted by damage while casting
|
||||
DoubleDownOdds = 103, //Increases double down odds
|
||||
HqDiscoveryRate = 104,
|
||||
|
||||
|
||||
//Non-gear mods
|
||||
None = 105,
|
||||
NAMEPLATE_SHOWN = 106,
|
||||
TARGETABLE = 107,
|
||||
NAMEPLATE_SHOWN2 = 108,
|
||||
|
||||
HpPercent = 109,
|
||||
MpPercent = 110,
|
||||
TpPercent = 111,
|
||||
|
||||
AttackRange = 112, //How far away in yalms this character can attack from (probably won't need this when auto attack skills are done)
|
||||
|
||||
Raise = 113,
|
||||
MinimumHpLock = 114, //Stops HP from falling below this value
|
||||
MinimumMpLock = 115, //Stops MP from falling below this value
|
||||
MinimumTpLock = 116, //Stops TP from falling below this value
|
||||
AttackType = 117, //Attack property of auto attacks (might not need this when auto attack skills are done, unsure)
|
||||
CanBlock = 118, //Whether the character can block attacks. (For players this is only true when they have a shield)
|
||||
HitCount = 119, //Amount of hits in an auto attack. Usually 1, 2 for h2h, 3 with spinning heel
|
||||
|
||||
//Flat percent increases to these rates. Might not need these?
|
||||
RawEvadeRate = 120,
|
||||
RawParryRate = 121,
|
||||
RawBlockRate = 122,
|
||||
RawResistRate = 123,
|
||||
RawHitRate = 124,
|
||||
RawCritRate = 125,
|
||||
|
||||
DamageTakenDown = 126, //Percent damage taken down
|
||||
Regain = 127, //TP regen, should be -90 out of combat, Invigorate sets to 100+ depending on traits
|
||||
RegenDown = 128, //Damage over time effects. Separate from normal Regen because of how they are displayed in game
|
||||
Stoneskin = 129, //Nullifies damage
|
||||
KnockbackImmune = 130, //Immune to knockback effects when above 0
|
||||
Stealth = 131, //Not visisble when above 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,8 +109,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
public uint castTimeMs; //cast time in milliseconds
|
||||
public uint recastTimeMs; //recast time in milliseconds
|
||||
public uint maxRecastTimeSeconds; //maximum recast time in seconds
|
||||
public ushort mpCost;
|
||||
public ushort tpCost;
|
||||
public short mpCost; //short in case these casts can have negative cost
|
||||
public short tpCost; //short because there are certain cases where we want weaponskills to have negative costs (such as Feint)
|
||||
public byte animationType;
|
||||
public ushort effectAnimation;
|
||||
public ushort modelAnimation;
|
||||
|
@ -188,10 +188,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
return castTimeMs == 0;
|
||||
}
|
||||
|
||||
//Checks whether the skill can be used on the given target
|
||||
public bool IsValidMainTarget(Character user, Character target)
|
||||
//Checks whether the skill can be used on the given targets, uses error to return specific text ids for errors
|
||||
public bool IsValidMainTarget(Character user, Character target, CommandResult error = null)
|
||||
{
|
||||
targetFind = new TargetFind(user);
|
||||
targetFind = new TargetFind(user, target);
|
||||
|
||||
if (aoeType == TargetFindAOEType.Box)
|
||||
{
|
||||
|
@ -204,6 +204,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
|
||||
/*
|
||||
worldMasterTextId
|
||||
32511 Target does not exist
|
||||
32512 cannot be performed on a KO'd target.
|
||||
32513 can only be performed on a KO'd target.
|
||||
32514 cannot be performed on yourself.
|
||||
|
@ -211,117 +212,112 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
32516 cannot be performed on a friendly target.
|
||||
32517 can only be performed on a friendly target.
|
||||
32518 cannot be performed on an enemy.
|
||||
32519 can only be performed on an enemy,
|
||||
32556 unable to execute [weaponskill]. Conditions for use are not met.
|
||||
32519 can only be performed on an enemy.
|
||||
32547 That command cannot be performed on the current target.
|
||||
32548 That command cannot be performed on a party member
|
||||
*/
|
||||
|
||||
// cant target dead
|
||||
if ((mainTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead())
|
||||
if (target == null)
|
||||
{
|
||||
// cannot be perfomed on
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id);
|
||||
error?.SetTextId(32511);
|
||||
return false;
|
||||
}
|
||||
|
||||
//level too high
|
||||
if (level > user.GetLevel())
|
||||
//This skill can't be used on a corpse and target is dead
|
||||
if ((mainTarget & ValidTarget.Corpse) == 0 && target.IsDead())
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32527, 0x20, (uint)id);
|
||||
//return false;
|
||||
}
|
||||
|
||||
//Proc requirement
|
||||
if (procRequirement != BattleCommandProcRequirement.None && !user.charaWork.battleTemp.timingCommandFlag[(int) procRequirement - 1])
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id);
|
||||
error?.SetTextId(32512);
|
||||
return false;
|
||||
}
|
||||
|
||||
//costs too much tp
|
||||
if (CalculateTpCost(user) > user.GetTP())
|
||||
//This skill must be used on a corpse and target is alive
|
||||
if ((mainTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive())
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id);
|
||||
error?.SetTextId(32513);
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: calculate cost based on modifiers also (probably in BattleUtils)
|
||||
if (BattleUtils.CalculateSpellCost(user, target, this) > user.GetMP())
|
||||
//This skill can't be used on self and target is self
|
||||
if ((mainTarget & ValidTarget.Self) == 0 && target == user)
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32545, 0x20, (uint)id);
|
||||
error?.SetTextId(32514);
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: check target requirements
|
||||
if (requirements != BattleCommandRequirements.None)
|
||||
//This skill must be used on self and target isn't self
|
||||
if ((mainTarget & ValidTarget.SelfOnly) != 0 && target != user)
|
||||
{
|
||||
if (false)
|
||||
{
|
||||
// Unable to execute [@SHEET(xtx/command,$E8(1),2)]. Conditions for use are not met.
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id);
|
||||
return false;
|
||||
}
|
||||
error?.SetTextId(32515);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// todo: i dont care to message for each scenario, just the most common ones..
|
||||
if ((mainTarget & ValidTarget.CorpseOnly) != 0)
|
||||
//This skill can't be used on an ally and target is an ally
|
||||
if ((mainTarget & ValidTarget.Ally) == 0 && target.allegiance == user.allegiance)
|
||||
{
|
||||
if (target != null && target.IsAlive())
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32513, 0x20, (uint)id);
|
||||
return false;
|
||||
}
|
||||
error?.SetTextId(32516);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mainTarget & ValidTarget.Enemy) != 0)
|
||||
//This skill must be used on an ally and target is not an ally
|
||||
if ((mainTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != user.allegiance)
|
||||
{
|
||||
if (target == user || target != null &&
|
||||
user.allegiance == target.allegiance)
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32519, 0x20, (uint)id);
|
||||
return false;
|
||||
}
|
||||
error?.SetTextId(32517);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mainTarget & ValidTarget.Ally) != 0)
|
||||
//This skill can't be used on an enemu and target is an enemy
|
||||
if ((mainTarget & ValidTarget.Enemy) == 0 && target.allegiance != user.allegiance)
|
||||
{
|
||||
if (target == null || target.allegiance != user.allegiance)
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
|
||||
return false;
|
||||
}
|
||||
error?.SetTextId(32518);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mainTarget & ValidTarget.PartyMember) != 0)
|
||||
//This skill must be used on an enemy and target is an ally
|
||||
if ((mainTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == user.allegiance)
|
||||
{
|
||||
if (target == null || target.currentParty != user.currentParty)
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20, (uint)id);
|
||||
return false;
|
||||
}
|
||||
error?.SetTextId(32519);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((mainTarget & ValidTarget.Player) != 0)
|
||||
//This skill can't be used on party members and target is a party member
|
||||
if ((mainTarget & ValidTarget.Party) == 0 && target.currentParty == user.currentParty)
|
||||
{
|
||||
if (!(target is Player))
|
||||
{
|
||||
if (user is Player)
|
||||
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
|
||||
return false;
|
||||
}
|
||||
error?.SetTextId(32548);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;// targetFind.CanTarget(target, true, true, true); //this will be done later
|
||||
//This skill must be used on party members and target is not a party member
|
||||
if ((mainTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != user.currentParty)
|
||||
{
|
||||
error?.SetTextId(32547);
|
||||
return false;
|
||||
}
|
||||
|
||||
//This skill can't be used on NPCs and target is an npc
|
||||
if ((mainTarget & ValidTarget.NPC) == 0 && target.isStatic)
|
||||
{
|
||||
error?.SetTextId(32547);
|
||||
return false;
|
||||
}
|
||||
|
||||
//This skill must be used on NPCs and target is not an npc
|
||||
if ((mainTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic)
|
||||
{
|
||||
error?.SetTextId(32547);
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo: why is player always zoning?
|
||||
// cant target if zoning
|
||||
if (target is Player && ((Player)target).playerSession.isUpdatesLocked)
|
||||
{
|
||||
user.aiContainer.ChangeTarget(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target.zone != user.zone)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ushort CalculateMpCost(Character user)
|
||||
|
@ -363,15 +359,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
//Calculate TP cost taking into considerating the combo bonus rate for players
|
||||
//Should this set tpCost or should it be called like CalculateMp where it gets calculated each time?
|
||||
//Might cause issues with the delay between starting and finishing a WS
|
||||
public ushort CalculateTpCost(Character user)
|
||||
public short CalculateTpCost(Character user)
|
||||
{
|
||||
ushort tp = tpCost;
|
||||
short tp = tpCost;
|
||||
//Calculate tp cost
|
||||
if (user is Player)
|
||||
{
|
||||
var player = user as Player;
|
||||
if (player.playerWork.comboNextCommandId[0] == id || player.playerWork.comboNextCommandId[1] == id)
|
||||
tp = (ushort)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate));
|
||||
tp = (short)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate));
|
||||
}
|
||||
|
||||
return tp;
|
||||
|
@ -386,5 +382,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
{
|
||||
return (ushort) commandType;
|
||||
}
|
||||
|
||||
public ushort GetActionType()
|
||||
{
|
||||
return (ushort) actionType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -320,28 +320,28 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
|
||||
// custom effects here
|
||||
// status for having procs fall off
|
||||
EvadeProc = 253003,
|
||||
BlockProc = 253004,
|
||||
ParryProc = 253005,
|
||||
MissProc = 253006,
|
||||
EXPChain = 253007
|
||||
EvadeProc = 300000,
|
||||
BlockProc = 300001,
|
||||
ParryProc = 300002,
|
||||
MissProc = 300003,
|
||||
EXPChain = 300004
|
||||
}
|
||||
|
||||
[Flags]
|
||||
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.
|
||||
|
@ -368,9 +368,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
|
||||
|
@ -387,19 +385,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 = 30328; //The text id used when the status is gained. 30328: [Command] grants you the effect of [status] (Used for buffs)
|
||||
private ushort statusLossTextId = 30331; //The text id used when the status effect falls off when its time runs out. 30331: You are no longer under the effect of [status] (Used for buffs)
|
||||
public LuaScript script;
|
||||
|
||||
HitEffect animationEffect;
|
||||
|
@ -433,26 +434,36 @@ 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, ushort statusGainTextId, ushort statusLossTextId)
|
||||
{
|
||||
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;
|
||||
this.statusGainTextId = statusGainTextId;
|
||||
this.statusLossTextId = statusLossTextId;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -548,9 +559,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()
|
||||
|
@ -558,6 +574,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
return hidden;
|
||||
}
|
||||
|
||||
public ushort GetStatusGainTextId()
|
||||
{
|
||||
return statusGainTextId;
|
||||
}
|
||||
|
||||
public ushort GetStatusLossTextId()
|
||||
{
|
||||
return statusLossTextId;
|
||||
}
|
||||
|
||||
public void SetStartTime(DateTime time)
|
||||
{
|
||||
this.startTime = time;
|
||||
|
@ -566,7 +592,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
|
||||
|
@ -629,9 +663,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)
|
||||
|
@ -639,6 +678,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;
|
||||
|
|
|
@ -30,39 +30,41 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
|
||||
public void Update(DateTime tick)
|
||||
{
|
||||
CommandResultContainer resultContainer = new CommandResultContainer();
|
||||
|
||||
//Regen/Refresh/Regain effects tick every 3 seconds
|
||||
if ((DateTime.Now - lastTick).Seconds >= 3)
|
||||
{
|
||||
RegenTick(tick);
|
||||
RegenTick(tick, resultContainer);
|
||||
lastTick = DateTime.Now;
|
||||
}
|
||||
// list of effects to remove
|
||||
|
||||
// if (owner is Player) UpdateTimeAtIndex(4, 4294967295);
|
||||
// list of effects to remove
|
||||
var removeEffects = new List<StatusEffect>();
|
||||
for (int i = 0; i < effects.Values.Count; i++)
|
||||
var effectsList = effects.Values;
|
||||
for (int i = effectsList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
// effect's update function returns true if effect has completed
|
||||
if (effects.Values.ElementAt(i).Update(tick))
|
||||
removeEffects.Add(effects.Values.ElementAt(i));
|
||||
|
||||
if (effectsList.ElementAt(i).Update(tick, resultContainer))
|
||||
removeEffects.Add(effectsList.ElementAt(i));
|
||||
}
|
||||
|
||||
// remove effects from this list
|
||||
foreach (var effect in removeEffects)
|
||||
{
|
||||
RemoveStatusEffect(effect);
|
||||
RemoveStatusEffect(effect, resultContainer, effect.GetStatusLossTextId());
|
||||
}
|
||||
|
||||
if (sendUpdate)
|
||||
resultContainer.CombineLists();
|
||||
|
||||
if (resultContainer.GetList().Count > 0)
|
||||
{
|
||||
owner.zone.BroadcastPacketsAroundActor(owner, owner.GetActorStatusPackets());
|
||||
sendUpdate = false;
|
||||
owner.DoBattleAction(0, 0x7c000062, resultContainer.GetList());
|
||||
}
|
||||
}
|
||||
|
||||
//regen/refresh/regain
|
||||
public void RegenTick(DateTime tick)
|
||||
public void RegenTick(DateTime tick, CommandResultContainer resultContainer)
|
||||
{
|
||||
ushort dotTick = (ushort) owner.GetMod(Modifier.RegenDown);
|
||||
ushort regenTick = (ushort) owner.GetMod(Modifier.Regen);
|
||||
|
@ -72,18 +74,24 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
//DoTs tick before regen and the full dot damage is displayed, even if some or all of it is nullified by regen. Only effects like stoneskin actually alter the number shown
|
||||
if (dotTick > 0)
|
||||
{
|
||||
CommandResult action = new CommandResult(owner.actorId, 30331, (uint)(HitEffect.HitEffectType | HitEffect.Hit), dotTick);
|
||||
//Unsure why 10105 is the textId used
|
||||
//Also unsure why magicshield is used
|
||||
CommandResult action = new CommandResult(owner.actorId, 10105, (uint)(HitEffect.MagicEffectType | HitEffect.MagicShield | HitEffect.NoResist), dotTick);
|
||||
utils.BattleUtils.HandleStoneskin(owner, action);
|
||||
// todo: figure out how to make red numbers appear for enemies getting hurt by dots
|
||||
//owner.DelHP(action.amount);
|
||||
utils.BattleUtils.DamageTarget(owner, owner, action, null);
|
||||
owner.DoBattleAction(0, 0, action);
|
||||
resultContainer.AddAction(action);
|
||||
owner.DelHP(action.amount, resultContainer);
|
||||
}
|
||||
|
||||
//DoTs are the only effect to show numbers, so that doesnt need to be handled for these
|
||||
owner.AddHP(regenTick);
|
||||
owner.AddMP(refreshtick);
|
||||
owner.AddTP(regainTick);
|
||||
if (regenTick != 0)
|
||||
owner.AddHP(regenTick);
|
||||
|
||||
if (refreshtick != 0)
|
||||
owner.AddMP(refreshtick);
|
||||
|
||||
if (regainTick != 0)
|
||||
owner.AddTP(regainTick);
|
||||
}
|
||||
|
||||
public bool HasStatusEffect(uint id)
|
||||
|
@ -96,55 +104,59 @@ 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);
|
||||
if (se != null)
|
||||
{
|
||||
worldmasterTextId = se.GetStatusGainTextId();
|
||||
}
|
||||
|
||||
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);
|
||||
if (se != null)
|
||||
{
|
||||
se.SetTier(tier);
|
||||
worldmasterTextId = se.GetStatusGainTextId();
|
||||
}
|
||||
|
||||
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);
|
||||
if (se != null)
|
||||
{
|
||||
se.SetMagnitude(magnitude);
|
||||
se.SetTier(tier);
|
||||
worldmasterTextId = se.GetStatusGainTextId();
|
||||
}
|
||||
|
||||
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);
|
||||
worldmasterTextId = se.GetStatusGainTextId();
|
||||
}
|
||||
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
|
||||
|
@ -166,9 +178,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
|
||||
|
@ -181,13 +194,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())
|
||||
{
|
||||
|
@ -212,15 +221,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
|
||||
|
@ -230,54 +243,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)
|
||||
|
@ -288,14 +267,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;
|
||||
|
@ -321,6 +300,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)
|
||||
{
|
||||
|
@ -429,7 +418,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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
@ -223,7 +223,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
|
|||
|
||||
protected virtual void Move()
|
||||
{
|
||||
if (!owner.aiContainer.CanFollowPath())
|
||||
if (!owner.aiContainer.CanFollowPath() || owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventMovement))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,21 +10,28 @@ using FFXIVClassic_Map_Server.actors.group;
|
|||
|
||||
namespace FFXIVClassic_Map_Server.actors.chara.ai
|
||||
{
|
||||
// https://github.com/Windower/POLUtils/blob/master/PlayOnline.FFXI/Enums.cs
|
||||
[Flags]
|
||||
public enum ValidTarget : ushort
|
||||
{
|
||||
None = 0x00,
|
||||
Self = 0x01,
|
||||
Player = 0x02,
|
||||
PartyMember = 0x04,
|
||||
Ally = 0x08,
|
||||
NPC = 0x10,
|
||||
Enemy = 0x20,
|
||||
Unknown = 0x40,
|
||||
Object = 0x60,
|
||||
CorpseOnly = 0x80,
|
||||
Corpse = 0x9D // CorpseOnly + NPC + Ally + Partymember + Self
|
||||
Self = 0x01, //Can be used on self (if this flag isn't set and target is self, return false)
|
||||
SelfOnly = 0x02, //Must be used on self (if this flag is set and target isn't self, return false)
|
||||
Party = 0x4, //Can be used on party members
|
||||
PartyOnly = 0x8, //Must be used on party members
|
||||
Ally = 0x10, //Can be used on allies
|
||||
AllyOnly = 0x20, //Must be used on allies
|
||||
NPC = 0x40, //Can be used on static NPCs
|
||||
NPCOnly = 0x80, //Must be used on static NPCs
|
||||
Enemy = 0x100, //Can be used on enemies
|
||||
EnemyOnly = 0x200, //Must be used on enemies
|
||||
Object = 0x400, //Can be used on objects
|
||||
ObjectOnly = 0x800, //Must be used on objects
|
||||
Corpse = 0x1000, //Can be used on corpses
|
||||
CorpseOnly = 0x2000, //Must be used on corpses
|
||||
|
||||
//These are only used for ValidTarget, not MainTarget
|
||||
MainTargetParty = 0x4000, //Can be used on main target's party (This will basically always be true.)
|
||||
MainTargetPartyOnly = 0x8000, //Must be used on main target's party (This is for Protect basically.)
|
||||
}
|
||||
|
||||
/// <summary> Targeting from/to different entity types </summary>
|
||||
|
@ -66,12 +73,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
class TargetFind
|
||||
{
|
||||
private Character owner;
|
||||
private Character masterTarget; // if target is a pet, this is the owner
|
||||
private Character mainTarget; //This is the target that the skill is being used on
|
||||
private Character masterTarget; //If mainTarget is a pet, this is the owner
|
||||
private TargetFindCharacterType findType;
|
||||
private ValidTarget validTarget;
|
||||
private TargetFindAOETarget aoeTarget;
|
||||
private TargetFindAOEType aoeType;
|
||||
private Vector3 aoeTargetPosition; //This is the center of circle an cone AOEs and the position where line aoes come out
|
||||
private Vector3 aoeTargetPosition; //This is the center of circle of cone AOEs and the position where line aoes come out. If we have mainTarget this might not be needed?
|
||||
private float aoeTargetRotation; //This is the direction the aoe target is facing
|
||||
private float maxDistance; //Radius for circle and cone AOEs, length for line AOEs
|
||||
private float minDistance; //Minimum distance to that target must be to be able to be hit
|
||||
|
@ -82,14 +90,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
private float param;
|
||||
private List<Character> targets;
|
||||
|
||||
public TargetFind(Character owner)
|
||||
public TargetFind(Character owner, Character mainTarget = null)
|
||||
{
|
||||
this.owner = owner;
|
||||
Reset();
|
||||
this.owner = owner;
|
||||
this.mainTarget = mainTarget == null ? owner : mainTarget;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
this.mainTarget = owner;
|
||||
this.findType = TargetFindCharacterType.None;
|
||||
this.validTarget = ValidTarget.Enemy;
|
||||
this.aoeType = TargetFindAOEType.None;
|
||||
|
@ -201,11 +211,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
|
||||
//if (targets.Count > 8)
|
||||
//targets.RemoveRange(8, targets.Count - 8);
|
||||
|
||||
//Curaga starts with lowest health players, so the targets are definitely sorted at least for some abilities
|
||||
//Other aoe abilities might be sorted by distance?
|
||||
//Protect is random
|
||||
targets.Sort(delegate (Character a, Character b) { return a.GetHP().CompareTo(b.GetHP()); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -323,41 +328,57 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
if (target == null || !retarget && targets.Contains(target))
|
||||
return false;
|
||||
|
||||
//This skill can't be used on self and target is self, return false
|
||||
if ((validTarget & ValidTarget.Self) == 0 && target == owner)
|
||||
if (target == null)
|
||||
return false;
|
||||
|
||||
//This skill can't be used on NPCs and target is an NPC, return false
|
||||
if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic)
|
||||
return false;
|
||||
|
||||
//This skill can't be used on corpses and target is dead, return false
|
||||
//This skill can't be used on a corpse and target is dead
|
||||
if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead())
|
||||
return false;
|
||||
|
||||
//This skill must be used on Allies and target is not an ally, return false
|
||||
if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != owner.allegiance)
|
||||
return false;
|
||||
|
||||
|
||||
//This skill can't be used on players and target is a player, return false
|
||||
//Do we need a player flag? Ally/Enemy flags probably serve the same purpose
|
||||
//if ((validTarget & ValidTarget.Player) == 0 && target is Player)
|
||||
//return false;
|
||||
|
||||
|
||||
//This skill must be used on enemies an target is not an enemy
|
||||
if ((validTarget & ValidTarget.Enemy) != 0 && target.allegiance == owner.allegiance)
|
||||
return false;
|
||||
|
||||
//This skill must be used on a party member and target is not in owner's party, return false
|
||||
if ((validTarget & ValidTarget.PartyMember) != 0 && target.currentParty != owner.currentParty)
|
||||
return false;
|
||||
|
||||
//This skill must be used on a corpse and target is alive, return false
|
||||
//This skill must be used on a corpse and target is alive
|
||||
if ((validTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive())
|
||||
return false;
|
||||
|
||||
//This skill can't be used on self and target is self
|
||||
if ((validTarget & ValidTarget.Self) == 0 && target == owner)
|
||||
return false;
|
||||
|
||||
//This skill must be used on self and target isn't self
|
||||
if ((validTarget & ValidTarget.SelfOnly) != 0 && target != owner)
|
||||
return false;
|
||||
|
||||
//This skill can't be used on an ally and target is an ally
|
||||
if ((validTarget & ValidTarget.Ally) == 0 && target.allegiance == owner.allegiance)
|
||||
return false;
|
||||
|
||||
//This skill must be used on an ally and target is not an ally
|
||||
if ((validTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != owner.allegiance)
|
||||
return false;
|
||||
|
||||
//This skill can't be used on an enemu and target is an enemy
|
||||
if ((validTarget & ValidTarget.Enemy) == 0 && target.allegiance != owner.allegiance)
|
||||
return false;
|
||||
|
||||
//This skill must be used on an enemy and target is an ally
|
||||
if ((validTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == owner.allegiance)
|
||||
return false;
|
||||
|
||||
//This skill can't be used on party members and target is a party member
|
||||
if ((validTarget & ValidTarget.Party) == 0 && target.currentParty == owner.currentParty)
|
||||
return false;
|
||||
|
||||
//This skill must be used on party members and target is not a party member
|
||||
if ((validTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != owner.currentParty)
|
||||
return false;
|
||||
|
||||
//This skill can't be used on NPCs and target is an npc
|
||||
if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic)
|
||||
return false;
|
||||
|
||||
//This skill must be used on NPCs and target is not an npc
|
||||
if ((validTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic)
|
||||
return false;
|
||||
|
||||
// todo: why is player always zoning?
|
||||
// cant target if zoning
|
||||
if (target is Player && ((Player)target).playerSession.isUpdatesLocked)
|
||||
|
@ -372,6 +393,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
|
|||
if (validTarget == ValidTarget.Self && aoeType == TargetFindAOEType.None && owner != target)
|
||||
return false;
|
||||
|
||||
//This skill can't be used on main target's party members and target is a party member of main target
|
||||
if ((validTarget & ValidTarget.MainTargetParty) == 0 && target.currentParty == mainTarget.currentParty)
|
||||
return false;
|
||||
|
||||
//This skill must be used on main target's party members and target is not a party member of main target
|
||||
if ((validTarget & ValidTarget.MainTargetPartyOnly) != 0 && target.currentParty != mainTarget.currentParty)
|
||||
return false;
|
||||
|
||||
|
||||
// this is fuckin retarded, think of a better way l8r
|
||||
if (!ignoreAOE)
|
||||
{
|
||||
|
|
|
@ -15,10 +15,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
{
|
||||
this.startTime = DateTime.Now;
|
||||
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "onAbilityPrepare", owner, target, skill);
|
||||
var returnCode = skill.CallLuaFunction(owner, "onAbilityPrepare", owner, target, skill);
|
||||
|
||||
this.target = target != null ? target : owner;
|
||||
this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
|
||||
|
||||
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||
if (returnCode == 0)
|
||||
{
|
||||
OnStart();
|
||||
|
@ -32,7 +33,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
public override void OnStart()
|
||||
{
|
||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "onAbilityStart", owner, target, skill);
|
||||
var returnCode = skill.CallLuaFunction(owner, "onAbilityStart", owner, target, skill);
|
||||
|
||||
if (returnCode != 0)
|
||||
{
|
||||
|
|
|
@ -100,26 +100,35 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
//List<BattleAction> actions = new List<BattleAction>();
|
||||
CommandResultContainer actions = new CommandResultContainer();
|
||||
|
||||
var i = 0;
|
||||
for (int hitNum = 0; hitNum < 1 /* owner.GetMod((uint) Modifier.HitCount)*/; hitNum++)
|
||||
{
|
||||
CommandResult action = new CommandResult(target.actorId, 0x765D, (uint)HitEffect.Hit, 100, (byte)HitDirection.None, (byte) hitNum);
|
||||
action.commandType = CommandType.AutoAttack;
|
||||
action.actionType = ActionType.Physical;
|
||||
action.actionProperty = (ActionProperty) owner.GetMod(Modifier.AttackType);
|
||||
// evasion, miss, dodge, etc to be handled in script, calling helpers from scripts/weaponskills.lua
|
||||
// temporary evade/miss/etc function to test hit effects
|
||||
action.DoAction(owner, target, null, actions);
|
||||
}
|
||||
|
||||
// todo: this is fuckin stupid, probably only need *one* error packet, not an error for each action
|
||||
CommandResult[] errors = (CommandResult[])actions.GetList().ToArray().Clone();
|
||||
CommandResult error = null;// new BattleAction(0, null, 0, 0);
|
||||
//owner.DoActions(null, actions.GetList(), ref error);
|
||||
//owner.OnAttack(this, actions[0], ref errorResult);
|
||||
var anim = (uint)(17 << 24 | 1 << 12);
|
||||
owner.DoBattleAction(22104, anim, actions.GetList());
|
||||
//This is all temporary until the skill sheet is finishd and the different auto attacks are added to the database
|
||||
//Some mobs have multiple unique auto attacks that they switch between as well as ranged auto attacks, so we'll need a way to handle that
|
||||
//For now, just use a temporary hardcoded BattleCommand that's the same for everyone.
|
||||
BattleCommand attackCommand = new BattleCommand(22104, "Attack");
|
||||
attackCommand.range = 5;
|
||||
attackCommand.rangeHeight = 10;
|
||||
attackCommand.worldMasterTextId = 0x765D;
|
||||
attackCommand.mainTarget = (ValidTarget)768;
|
||||
attackCommand.validTarget = (ValidTarget)17152;
|
||||
attackCommand.commandType = CommandType.AutoAttack;
|
||||
attackCommand.numHits = (byte)owner.GetMod(Modifier.HitCount);
|
||||
attackCommand.basePotency = 100;
|
||||
ActionProperty property = (owner.GetMod(Modifier.AttackType) != 0) ? (ActionProperty)owner.GetMod(Modifier.AttackType) : ActionProperty.Slashing;
|
||||
attackCommand.actionProperty = property;
|
||||
attackCommand.actionType = ActionType.Physical;
|
||||
|
||||
uint anim = (17 << 24 | 1 << 12);
|
||||
|
||||
if (owner is Player)
|
||||
anim = (25 << 24 | 1 << 12);
|
||||
|
||||
attackCommand.battleAnimation = anim;
|
||||
|
||||
if (owner.CanUse(target, attackCommand))
|
||||
{
|
||||
attackCommand.targetFind.FindWithinArea(target, attackCommand.validTarget, attackCommand.aoeTarget);
|
||||
owner.DoBattleCommand(attackCommand, "autoattack");
|
||||
}
|
||||
}
|
||||
|
||||
public override void TryInterrupt()
|
||||
|
|
|
@ -12,7 +12,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;
|
||||
|
|
|
@ -18,32 +18,28 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
this.startPos = owner.GetPosAsVector3();
|
||||
this.startTime = DateTime.Now;
|
||||
this.spell = Server.GetWorldManager().GetBattleCommand(spellId);
|
||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicPrepare", owner, target, spell);
|
||||
|
||||
//Modify spell based on status effects. Need to do it here because they can modify cast times
|
||||
List<StatusEffect> effects = owner.statusEffects.GetStatusEffectsByFlag((uint)(StatusEffectFlags.ActivateOnCastStart));
|
||||
var returnCode = spell.CallLuaFunction(owner, "onMagicPrepare", owner, target, spell);
|
||||
|
||||
//modify skill based on status effects
|
||||
//Do this here to allow buffs like Resonance to increase range before checking CanCast()
|
||||
foreach (var effect in effects)
|
||||
lua.LuaEngine.CallLuaStatusEffectFunction(owner, effect, "onMagicCast", owner, effect, spell);
|
||||
owner.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCastStart, "onMagicCast", owner, spell);
|
||||
|
||||
this.target = target != null ? target : owner;
|
||||
this.target = (spell.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
|
||||
|
||||
if (returnCode == 0 && owner.CanCast(this.target, spell))
|
||||
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||
if (returnCode == 0 && owner.CanUse(this.target, spell, errorResult))
|
||||
{
|
||||
OnStart();
|
||||
}
|
||||
else
|
||||
{
|
||||
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||
interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicStart", owner, target, spell);
|
||||
var returnCode = spell.CallLuaFunction(owner, "onMagicStart", owner, target, spell);
|
||||
|
||||
if (returnCode != 0)
|
||||
{
|
||||
|
@ -62,7 +58,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
Player p = (Player)owner;
|
||||
if (spell.comboStep == 1 || ((p.playerWork.comboNextCommandId[0] == spell.id || p.playerWork.comboNextCommandId[1] == spell.id)))
|
||||
{
|
||||
lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onCombo", owner, target, spell);
|
||||
spell.CallLuaFunction(owner, "onCombo", owner, target, spell);
|
||||
spell.isCombo = true;
|
||||
}
|
||||
}
|
||||
|
@ -168,7 +164,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
private bool CanCast()
|
||||
{
|
||||
return owner.CanCast(target, spell) && spell.IsValidMainTarget(owner, target) && !HasMoved();
|
||||
return owner.CanUse(target, spell);
|
||||
}
|
||||
|
||||
private bool HasMoved()
|
||||
|
|
|
@ -14,11 +14,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
base(owner, target)
|
||||
{
|
||||
this.startTime = DateTime.Now;
|
||||
//this.target = skill.targetFind.
|
||||
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillPrepare", owner, target, skill);
|
||||
|
||||
if (returnCode == 0 && owner.CanWeaponSkill(target, skill))
|
||||
var returnCode = skill.CallLuaFunction(owner, "onSkillPrepare", owner, target, skill);
|
||||
|
||||
this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target;
|
||||
|
||||
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||
if (returnCode == 0 && owner.CanUse(this.target, skill, errorResult))
|
||||
{
|
||||
OnStart();
|
||||
}
|
||||
|
@ -31,7 +34,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
public override void OnStart()
|
||||
{
|
||||
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillStart", owner, target, skill);
|
||||
var returnCode = skill.CallLuaFunction(owner, "onSkillStart", owner, target, skill);
|
||||
|
||||
if (returnCode != 0)
|
||||
{
|
||||
|
@ -48,8 +51,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
{
|
||||
//If there is a position bonus
|
||||
if (skill.positionBonus != BattleCommandPositionBonus.None)
|
||||
//lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onPositional", owner, target, skill);
|
||||
skill.CallLuaFunction(owner, "onPositional", owner, target, skill);
|
||||
skill.CallLuaFunction(owner, "weaponskill", "onPositional", owner, target, skill);
|
||||
|
||||
//Combo stuff
|
||||
if (owner is Player)
|
||||
|
@ -61,7 +63,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
//If owner is a player and the skill being used is part of the current combo
|
||||
if (p.playerWork.comboNextCommandId[0] == skill.id || p.playerWork.comboNextCommandId[1] == skill.id)
|
||||
{
|
||||
lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onCombo", owner, target, skill);
|
||||
skill.CallLuaFunction(owner, "onCombo", owner, target, skill);
|
||||
skill.isCombo = true;
|
||||
}
|
||||
|
@ -161,7 +162,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
|
|||
|
||||
private bool CanUse()
|
||||
{
|
||||
return owner.CanWeaponSkill(target, skill) && skill.IsValidMainTarget(owner, target);
|
||||
return owner.CanUse(target, skill);
|
||||
}
|
||||
|
||||
public BattleCommand GetWeaponSkill()
|
||||
|
|
|
@ -12,37 +12,55 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
static class BattleUtils
|
||||
{
|
||||
|
||||
public static Dictionary<HitType, ushort> SingleHitTypeTextIds = new Dictionary<HitType, ushort>()
|
||||
public static Dictionary<HitType, ushort> PhysicalHitTypeTextIds = new Dictionary<HitType, ushort>()
|
||||
{
|
||||
{ HitType.Miss, 30311 },
|
||||
{ HitType.Evade, 30310 },
|
||||
{ HitType.Parry, 30308 },
|
||||
{ HitType.Block, 30306 },
|
||||
{ HitType.Resist, 30310 }, //Resists seem to use the evade text id
|
||||
{ HitType.Hit, 30301 },
|
||||
{ HitType.Crit, 30302 }
|
||||
};
|
||||
|
||||
public static Dictionary<HitType, ushort> MagicalHitTypeTextIds = new Dictionary<HitType, ushort>()
|
||||
{
|
||||
{ HitType.SingleResist,30318 },
|
||||
{ HitType.DoubleResist,30317 },
|
||||
{ HitType.TripleResist, 30316 },//Triple Resists seem to use the same text ID as full resists
|
||||
{ HitType.FullResist,30316 },
|
||||
{ HitType.Hit, 30319 },
|
||||
{ HitType.Crit, 30392 } //Unsure why crit is separated from the rest of the ids
|
||||
};
|
||||
|
||||
public static Dictionary<HitType, ushort> MultiHitTypeTextIds = new Dictionary<HitType, ushort>()
|
||||
{
|
||||
{ HitType.Miss, 30449 }, //The attack misses.
|
||||
{ HitType.Evade, 0 }, //Evades were removed before multi hit skills got their own messages, so this doesnt exist
|
||||
{ HitType.Parry, 30448 }, //[Target] parries, taking x points of damage.
|
||||
{ HitType.Block, 30447 }, //[Target] blocks, taking x points of damage.
|
||||
{ HitType.Resist, 0 }, //No spells are multi-hit, so this doesn't exist
|
||||
{ HitType.Hit, 30443 }, //[Target] tales x points of damage
|
||||
{ HitType.Crit, 30444 } //Critical! [Target] takes x points of damage.
|
||||
};
|
||||
|
||||
public static Dictionary<HitType, HitEffect> HitTypeEffects = new Dictionary<HitType, HitEffect>()
|
||||
public static Dictionary<HitType, HitEffect> HitTypeEffectsPhysical = new Dictionary<HitType, HitEffect>()
|
||||
{
|
||||
{ HitType.Miss, 0 },
|
||||
{ HitType.Evade, HitEffect.Evade },
|
||||
{ HitType.Parry, HitEffect.Parry },
|
||||
{ HitType.Block, HitEffect.Block },
|
||||
{ HitType.Resist, HitEffect.RecoilLv1 },//Probably don't need this, resists are handled differently to the rest
|
||||
{ HitType.Hit, HitEffect.Hit },
|
||||
{ HitType.Crit, HitEffect.Crit }
|
||||
{ HitType.Crit, HitEffect.Crit | HitEffect.CriticalHit }
|
||||
};
|
||||
|
||||
//Magic attacks can't miss, be blocked, or parried. Resists are technically evades
|
||||
public static Dictionary<HitType, HitEffect> HitTypeEffectsMagical = new Dictionary<HitType, HitEffect>()
|
||||
{
|
||||
{ HitType.SingleResist, HitEffect.WeakResist },
|
||||
{ HitType.DoubleResist, HitEffect.WeakResist },
|
||||
{ HitType.TripleResist, HitEffect.WeakResist },
|
||||
{ HitType.FullResist, HitEffect.FullResist },
|
||||
{ HitType.Hit, HitEffect.NoResist },
|
||||
{ HitType.Crit, HitEffect.Crit }
|
||||
};
|
||||
|
||||
public static Dictionary<KnockbackType, HitEffect> KnockbackEffects = new Dictionary<KnockbackType, HitEffect>()
|
||||
|
@ -201,7 +219,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
//Or we could have HitTypes for DoubleResist, TripleResist, and FullResist that get used here.
|
||||
public static void CalculateResistDamage(Character attacker, Character defender, BattleCommand skill, CommandResult action)
|
||||
{
|
||||
double percentResist = 0.5;
|
||||
//Every tier of resist is a 25% reduction in damage. ie SingleResist is 25% damage taken down, Double is 50% damage taken down, etc
|
||||
double percentResist = 0.25 * (action.hitType - HitType.SingleResist + 1);
|
||||
|
||||
action.amountMitigated = (ushort)(action.amount * (1 - percentResist));
|
||||
action.amount = (ushort)(action.amount * percentResist);
|
||||
|
@ -217,7 +236,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
defender.SubtractMod((uint)Modifier.Stoneskin, mitigation);
|
||||
}
|
||||
|
||||
public static void DamageTarget(Character attacker, Character defender, CommandResult action, CommandResultContainer actionContainer= null)
|
||||
public static void DamageTarget(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer= null)
|
||||
{
|
||||
if (defender != null)
|
||||
{
|
||||
|
@ -230,9 +249,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
bnpc.lastAttacker = attacker;
|
||||
}
|
||||
|
||||
defender.DelHP((short)action.amount);
|
||||
attacker.OnDamageDealt(defender, action, actionContainer);
|
||||
defender.OnDamageTaken(attacker, action, actionContainer);
|
||||
defender.DelHP((short)action.amount, actionContainer);
|
||||
attacker.OnDamageDealt(defender, skill, action, actionContainer);
|
||||
defender.OnDamageTaken(attacker, skill, action, actionContainer);
|
||||
|
||||
// todo: other stuff too
|
||||
if (defender is BattleNpc)
|
||||
|
@ -248,13 +267,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
}
|
||||
}
|
||||
|
||||
public static void HealTarget(Character caster, Character target, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public static void HealTarget(Character caster, Character target, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
target.AddHP(action.amount);
|
||||
target.AddHP(action.amount, actionContainer);
|
||||
|
||||
target.statusEffects.CallLuaFunctionByFlag((uint) StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, action, actionContainer);
|
||||
target.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, skill, action, actionContainer);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,7 +298,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;
|
||||
|
@ -324,7 +343,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());
|
||||
|
@ -355,11 +374,26 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
return false;
|
||||
}
|
||||
|
||||
//This probably isn't totally correct but it's close enough for now.
|
||||
//Full Resists seem to be calculated in a different way because the resist rates don't seem to line up with kanikan's testing (their tests didn't show any full resists)
|
||||
//Non-spells with elemental damage can be resisted, it just doesnt say in the chat that they were. As far as I can tell, all mob-specific attacks are considered not to be spells
|
||||
public static bool TryResist(Character attacker, Character defender, BattleCommand skill, CommandResult action)
|
||||
{
|
||||
if ((Program.Random.NextDouble() * 100) <= action.resistRate)
|
||||
//The rate degrades for each check. Meaning with 100% resist, the attack will always be resisted, but it won't necessarily be a triple or full resist
|
||||
//Rates beyond 100 still increase the chance for higher resist tiers though
|
||||
double rate = action.resistRate;
|
||||
|
||||
int i = -1;
|
||||
|
||||
while ((Program.Random.NextDouble() * 100) <= rate && i < 4)
|
||||
{
|
||||
action.hitType = HitType.Resist;
|
||||
rate /= 2;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i != -1)
|
||||
{
|
||||
action.hitType = (HitType) ((int) HitType.SingleResist + i);
|
||||
CalculateResistDamage(attacker, defender, skill, action);
|
||||
return true;
|
||||
}
|
||||
|
@ -425,6 +459,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
FinishActionStatus(caster, target, skill, action, actionContainer);
|
||||
break;
|
||||
default:
|
||||
action.effectId = (uint) HitEffect.AnimationEffectType;
|
||||
actionContainer.AddAction(action);
|
||||
break;
|
||||
}
|
||||
|
@ -450,7 +485,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
}
|
||||
|
||||
//Actions have different text ids depending on whether they're a part of a multi-hit ws or not.
|
||||
Dictionary<HitType, ushort> textIds = SingleHitTypeTextIds;
|
||||
Dictionary<HitType, ushort> textIds = PhysicalHitTypeTextIds;
|
||||
|
||||
//If this is the first hit of a multi hit command, add the "You use [command] on [target]" action
|
||||
//Needs to be done here because certain buff messages appear before it.
|
||||
|
@ -473,23 +508,27 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
|
||||
actionContainer.AddAction(action);
|
||||
action.enmity = (ushort) (action.enmity * (skill != null ? skill.enmityModifier : 1));
|
||||
|
||||
//Damage the target
|
||||
DamageTarget(attacker, defender, action, actionContainer);
|
||||
DamageTarget(attacker, defender, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
//I'm assuming that like physical attacks stoneskin is taken into account before mitigation
|
||||
HandleStoneskin(defender, action);
|
||||
|
||||
//Determine the hit type of the action
|
||||
if (!TryMiss(attacker, defender, skill, action))
|
||||
//Spells don't seem to be able to miss, instead magic acc/eva is used for resists (which are generally called evades in game)
|
||||
//Unlike blocks and parries, crits do not go through resists.
|
||||
if (!TryResist(attacker, defender, skill, action))
|
||||
{
|
||||
HandleStoneskin(defender, action);
|
||||
if (!TryCrit(attacker, defender, skill, action))
|
||||
if (!TryResist(attacker, defender, skill, action))
|
||||
action.hitType = HitType.Hit;
|
||||
action.hitType = HitType.Hit;
|
||||
}
|
||||
|
||||
//There are no multi-hit spells
|
||||
action.worldMasterTextId = SingleHitTypeTextIds[action.hitType];
|
||||
//There are no multi-hit spells, so we don't need to take that into account
|
||||
action.worldMasterTextId = MagicalHitTypeTextIds[action.hitType];
|
||||
|
||||
//Set the hit effect
|
||||
SetHitEffectSpell(attacker, defender, skill, action);
|
||||
|
@ -500,7 +539,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
|
||||
actionContainer.AddAction(action);
|
||||
|
||||
DamageTarget(attacker, defender, action, actionContainer);
|
||||
DamageTarget(attacker, defender, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
public static void FinishActionHeal(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
|
@ -510,7 +549,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
|
||||
actionContainer.AddAction(action);
|
||||
|
||||
HealTarget(attacker, defender, action, actionContainer);
|
||||
HealTarget(attacker, defender, skill, action, actionContainer);
|
||||
}
|
||||
|
||||
public static void FinishActionStatus(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
|
@ -542,10 +581,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
hitEffect |= HitEffect.RecoilLv3;
|
||||
}
|
||||
|
||||
hitEffect |= HitTypeEffects[hitType];
|
||||
hitEffect |= HitTypeEffectsPhysical[hitType];
|
||||
|
||||
//For combos that land, add the combo effect
|
||||
if (skill != null && skill.isCombo && hitType > HitType.Evade && hitType != HitType.Evade && !skill.comboEffectAdded)
|
||||
if (skill != null && skill.isCombo && action.ActionLanded() && !skill.comboEffectAdded)
|
||||
{
|
||||
hitEffect |= (HitEffect)(skill.comboStep << 15);
|
||||
skill.comboEffectAdded = true;
|
||||
|
@ -555,7 +594,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
if (hitType >= HitType.Parry)
|
||||
{
|
||||
//Protect / Shell only show on physical/ magical attacks respectively.
|
||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect))
|
||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect) || defender.statusEffects.HasStatusEffect(StatusEffectId.Protect2))
|
||||
if (action != null)
|
||||
hitEffect |= HitEffect.Protect;
|
||||
|
||||
|
@ -572,20 +611,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
var hitEffect = HitEffect.MagicEffectType;
|
||||
HitType hitType = action.hitType;
|
||||
|
||||
//Recoil levels for spells are a bit different than physical. Recoil levels are used for resists.
|
||||
//Lv1 is for larger resists, Lv2 is for smaller resists and Lv3 is for no resists. Crit is still used for crits
|
||||
if (hitType == HitType.Resist)
|
||||
{
|
||||
//todo: calculate resist levels and figure out what the difference between Lv1 and 2 in retail was. For now assuming a full resist with 0 damage dealt is Lv1, all other resists Lv2
|
||||
if (action.amount == 0)
|
||||
hitEffect |= HitEffect.RecoilLv1;
|
||||
else
|
||||
hitEffect |= HitEffect.RecoilLv2;
|
||||
}
|
||||
else
|
||||
hitEffect |= HitEffect.RecoilLv3;
|
||||
|
||||
hitEffect |= HitTypeEffects[hitType];
|
||||
hitEffect |= HitTypeEffectsMagical[hitType];
|
||||
|
||||
if (skill != null && skill.isCombo && !skill.comboEffectAdded)
|
||||
{
|
||||
|
@ -594,16 +621,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
}
|
||||
|
||||
//if attack hit the target, take into account protective status effects
|
||||
if (hitType >= HitType.Block)
|
||||
if (action.ActionLanded())
|
||||
{
|
||||
//Protect / Shell only show on physical/ magical attacks respectively.
|
||||
//The magic hit effect category only has a flag for shell (and another shield effect that seems unused)
|
||||
//Even though traited protect gives magic defense, the shell effect doesn't play on attacks
|
||||
//This also means stoneskin doesnt show, but it does reduce damage
|
||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell))
|
||||
if (action != null)
|
||||
hitEffect |= HitEffect.Shell;
|
||||
|
||||
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
|
||||
if (action != null)
|
||||
hitEffect |= HitEffect.Stoneskin;
|
||||
hitEffect |= HitEffect.MagicShell;
|
||||
}
|
||||
action.effectId = (uint)hitEffect;
|
||||
}
|
||||
|
@ -654,7 +680,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
double rand = Program.Random.NextDouble();
|
||||
|
||||
//Statuses only land for non-resisted attacks and attacks that hit
|
||||
if (skill != null && skill.statusId != 0 && (action.hitType > HitType.Evade && action.hitType != HitType.Resist) && rand < skill.statusChance)
|
||||
if (skill != null && skill.statusId != 0 && (action.ActionLanded()) && rand < skill.statusChance)
|
||||
{
|
||||
StatusEffect effect = Server.GetWorldManager().GetStatusEffect(skill.statusId);
|
||||
//Because combos might change duration or tier
|
||||
|
@ -670,7 +696,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
{
|
||||
//If we need an extra action to show the status text
|
||||
if (isAdditional)
|
||||
results.AddAction(target.actorId, 30328, skill.statusId | (uint) HitEffect.StatusEffectType);
|
||||
results.AddAction(target.actorId, effect.GetStatusGainTextId(), skill.statusId | (uint) HitEffect.StatusEffectType);
|
||||
}
|
||||
else
|
||||
action.worldMasterTextId = 32002;//Is this right?
|
||||
|
@ -733,7 +759,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
|
|||
return 0;
|
||||
|
||||
int baseLevel = Math.Min(player.GetLevel(), mob.GetLevel());
|
||||
ushort baseEXP = BASEEXP[baseLevel];
|
||||
ushort baseEXP = BASEEXP[baseLevel - 1];
|
||||
|
||||
double dlvlModifier = 1.0;
|
||||
|
||||
|
@ -864,10 +890,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))));
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
return true;
|
||||
}
|
||||
|
||||
public override bool CanCast(Character target, BattleCommand spell)
|
||||
public override bool CanUse(Character target, BattleCommand spell, CommandResult error = null)
|
||||
{
|
||||
// todo:
|
||||
if (target == null)
|
||||
|
@ -202,18 +202,6 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
return true;
|
||||
}
|
||||
|
||||
public override bool CanWeaponSkill(Character target, BattleCommand skill)
|
||||
{
|
||||
// todo:
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanUseAbility(Character target, BattleCommand ability)
|
||||
{
|
||||
// todo:
|
||||
return false;
|
||||
}
|
||||
|
||||
public uint GetDespawnTime()
|
||||
{
|
||||
return despawnTime;
|
||||
|
@ -256,8 +244,6 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
this.hateContainer.ClearHate();
|
||||
zone.BroadcastPacketsAroundActor(this, GetSpawnPackets(null, 0x01));
|
||||
zone.BroadcastPacketsAroundActor(this, GetInitPackets());
|
||||
charaWork.parameterSave.hp = charaWork.parameterSave.hpMax;
|
||||
charaWork.parameterSave.mp = charaWork.parameterSave.mpMax;
|
||||
RecalculateStats();
|
||||
|
||||
OnSpawn();
|
||||
|
@ -280,6 +266,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
// <actor> defeat/defeats <target>
|
||||
if (actionContainer != null)
|
||||
actionContainer.AddEXPAction(new CommandResult(actorId, 30108, 0));
|
||||
|
||||
if (lastAttacker.currentParty != null && lastAttacker.currentParty is Party)
|
||||
{
|
||||
foreach (var memberId in ((Party)lastAttacker.currentParty).members)
|
||||
|
@ -303,6 +290,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
if (positionUpdates != null)
|
||||
positionUpdates.Clear();
|
||||
|
||||
aiContainer.InternalDie(tick, despawnTime);
|
||||
//this.ResetMoveSpeeds();
|
||||
// todo: reset cooldowns
|
||||
|
@ -446,11 +434,11 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
mobModifiers.Add((MobModifier)mobModId, val);
|
||||
}
|
||||
|
||||
public override void OnDamageTaken(Character attacker, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
public override void OnDamageTaken(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||
{
|
||||
if (GetMobMod((uint)MobModifier.DefendScript) != 0)
|
||||
lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount);
|
||||
base.OnDamageTaken(attacker, action, actionContainer);
|
||||
base.OnDamageTaken(attacker, skill, action, actionContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
private List<Director> ownedDirectors = new List<Director>();
|
||||
private Director loginInitDirector = null;
|
||||
|
||||
List<ushort> hotbarSlotsToUpdate = new List<ushort>();
|
||||
|
||||
public PlayerWork playerWork = new PlayerWork();
|
||||
|
||||
public Session playerSession;
|
||||
|
@ -759,10 +761,11 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
this.positionZ = destinationZ;
|
||||
this.rotation = destinationRot;
|
||||
|
||||
this.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnZoning);
|
||||
|
||||
//Save Player
|
||||
Database.SavePlayerPlayTime(this);
|
||||
Database.SavePlayerPosition(this);
|
||||
this.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnZoning, true);
|
||||
Database.SavePlayerStatusEffects(this);
|
||||
}
|
||||
|
||||
|
@ -1018,6 +1021,12 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
|
||||
//Check if bonus point available... set
|
||||
|
||||
//Remove buffs that fall off when changing class
|
||||
CommandResultContainer resultContainer = new CommandResultContainer();
|
||||
statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnClassChange, resultContainer);
|
||||
resultContainer.CombineLists();
|
||||
DoBattleAction(0, 0x7c000062, resultContainer.GetList());
|
||||
|
||||
//Set rested EXP
|
||||
charaWork.parameterSave.state_mainSkill[0] = classId;
|
||||
charaWork.parameterSave.state_mainSkillLevel = charaWork.battleSave.skillLevel[classId-1];
|
||||
|
@ -1956,7 +1965,14 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
|
||||
QueuePackets(propPacketUtil.Done());
|
||||
}
|
||||
|
||||
if ((updateFlags & ActorUpdateFlags.Hotbar) != 0)
|
||||
{
|
||||
UpdateHotbar(hotbarSlotsToUpdate);
|
||||
hotbarSlotsToUpdate.Clear();
|
||||
|
||||
updateFlags ^= ActorUpdateFlags.Hotbar;
|
||||
}
|
||||
|
||||
|
||||
|
@ -1972,12 +1988,11 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
//Update commands and recast timers for the entire hotbar
|
||||
public void UpdateHotbar()
|
||||
{
|
||||
List<ushort> slotsToUpdate = new List<ushort>();
|
||||
for (ushort i = charaWork.commandBorder; i < charaWork.commandBorder + 30; i++)
|
||||
{
|
||||
slotsToUpdate.Add(i);
|
||||
hotbarSlotsToUpdate.Add(i);
|
||||
}
|
||||
UpdateHotbar(slotsToUpdate);
|
||||
updateFlags |= ActorUpdateFlags.Hotbar;
|
||||
}
|
||||
|
||||
//Updates the hotbar and recast timers for only certain hotbar slots
|
||||
|
@ -2049,7 +2064,6 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
ushort lowHotbarSlot = (ushort)(hotbarSlot - charaWork.commandBorder);
|
||||
ushort maxRecastTime = (ushort)(ability != null ? ability.maxRecastTimeSeconds : 5);
|
||||
uint recastEnd = Utils.UnixTimeStampUTC() + maxRecastTime;
|
||||
List<ushort> slotsToUpdate = new List<ushort>();
|
||||
|
||||
Database.EquipAbility(this, classId, (ushort) (hotbarSlot - charaWork.commandBorder), commandId, recastEnd);
|
||||
//If the class we're equipping for is the current class (need to find out if state_mainSkill is supposed to change when you're a job)
|
||||
|
@ -2061,8 +2075,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot] = maxRecastTime;
|
||||
charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot] = recastEnd;
|
||||
|
||||
slotsToUpdate.Add(hotbarSlot);
|
||||
UpdateHotbar(slotsToUpdate);
|
||||
hotbarSlotsToUpdate.Add(hotbarSlot);
|
||||
updateFlags |= ActorUpdateFlags.Hotbar;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2098,25 +2112,23 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
Database.EquipAbility(this, GetCurrentClassOrJob(), (ushort)(lowHotbarSlot2), 0xA0F00000 ^ charaWork.command[hotbarSlot2], charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2]);
|
||||
|
||||
//Update slots on client
|
||||
List<ushort> slotsToUpdate = new List<ushort>();
|
||||
slotsToUpdate.Add(hotbarSlot1);
|
||||
slotsToUpdate.Add(hotbarSlot2);
|
||||
UpdateHotbar(slotsToUpdate);
|
||||
hotbarSlotsToUpdate.Add(hotbarSlot1);
|
||||
hotbarSlotsToUpdate.Add(hotbarSlot2);
|
||||
updateFlags |= ActorUpdateFlags.Hotbar;
|
||||
}
|
||||
|
||||
public void UnequipAbility(ushort hotbarSlot, bool printMessage = true)
|
||||
{
|
||||
List<ushort> slotsToUpdate = new List<ushort>();
|
||||
ushort trueHotbarSlot = (ushort)(hotbarSlot + charaWork.commandBorder - 1);
|
||||
ushort trueHotbarSlot = (ushort)(hotbarSlot + charaWork.commandBorder);
|
||||
uint commandId = charaWork.command[trueHotbarSlot];
|
||||
Database.UnequipAbility(this, (ushort)(trueHotbarSlot - charaWork.commandBorder));
|
||||
Database.UnequipAbility(this, hotbarSlot);
|
||||
charaWork.command[trueHotbarSlot] = 0;
|
||||
slotsToUpdate.Add(trueHotbarSlot);
|
||||
hotbarSlotsToUpdate.Add(trueHotbarSlot);
|
||||
|
||||
if(printMessage)
|
||||
if (printMessage && commandId != 0)
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 30604, 0x20, 0, 0xA0F00000 ^ commandId);
|
||||
|
||||
UpdateHotbar(slotsToUpdate);
|
||||
updateFlags |= ActorUpdateFlags.Hotbar;
|
||||
}
|
||||
|
||||
//Finds the first hotbar slot with a given commandId.
|
||||
|
@ -2290,110 +2302,75 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
return true;
|
||||
}
|
||||
|
||||
public override bool CanCast(Character target, BattleCommand spell)
|
||||
//Do we need separate functions? they check the same things
|
||||
public override bool CanUse(Character target, BattleCommand skill, CommandResult error = null)
|
||||
{
|
||||
//Might want to do these with a CommandResult instead to be consistent with the rest of command stuff
|
||||
if (GetHotbarTimer(spell.id) > Utils.UnixTimeStampUTC())
|
||||
{
|
||||
// todo: this needs confirming
|
||||
// Please wait a moment and try again.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (target == null)
|
||||
{
|
||||
// Target does not exist.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) > spell.range)
|
||||
{
|
||||
// The target is too far away.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32539, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) < spell.minRange)
|
||||
{
|
||||
// The target is too close.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32538, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (target.positionY - positionY > (spell.rangeHeight / 2))
|
||||
{
|
||||
// The target is too far above you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32540, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (positionY - target.positionY > (spell.rangeHeight / 2))
|
||||
{
|
||||
// The target is too far below you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32541, 0x20, (uint)spell.id);
|
||||
return false;
|
||||
}
|
||||
if (!IsValidTarget(target, spell.mainTarget) || !spell.IsValidMainTarget(this, target))
|
||||
if (!skill.IsValidMainTarget(this, target, error) || !IsValidTarget(target, skill.mainTarget))
|
||||
{
|
||||
// error packet is set in IsValidTarget
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool CanWeaponSkill(Character target, BattleCommand skill)
|
||||
{
|
||||
// todo: see worldmaster ids 32558~32557 for proper ko message and stuff
|
||||
//Might want to do these with a BattleAction instead to be consistent with the rest of command stuff
|
||||
if (GetHotbarTimer(skill.id) > Utils.UnixTimeStampUTC())
|
||||
{
|
||||
// todo: this needs confirming
|
||||
// Please wait a moment and try again.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20, (uint)skill.id);
|
||||
error?.SetTextId(32535);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) > skill.range)
|
||||
{
|
||||
// Target does not exist.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20, (uint)skill.id);
|
||||
// The target is too far away.
|
||||
error?.SetTextId(32539);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) < skill.minRange)
|
||||
{
|
||||
// The target is too close.
|
||||
error?.SetTextId(32538);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Original game checked height difference before horizontal distance
|
||||
if (target.positionY - positionY > (skill.rangeHeight / 2))
|
||||
{
|
||||
// The target is too far above you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32540, 0x20, (uint)skill.id);
|
||||
error?.SetTextId(32540);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionY - target.positionY > (skill.rangeHeight / 2))
|
||||
{
|
||||
// The target is too far below you.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32541, 0x20, (uint)skill.id);
|
||||
error?.SetTextId(32541);
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetDist = Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ);
|
||||
|
||||
if (targetDist > skill.range)
|
||||
if (skill.CalculateMpCost(this) > GetMP())
|
||||
{
|
||||
// The target is out of range.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32537, 0x20, (uint)skill.id);
|
||||
// You do not have enough MP.
|
||||
error?.SetTextId(32545);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetDist < skill.minRange)
|
||||
if (skill.CalculateTpCost(this) > GetTP())
|
||||
{
|
||||
// The target is too close.
|
||||
SendGameMessage(Server.GetWorldManager().GetActor(), 32538, 0x20, (uint)skill.id);
|
||||
// You do not have enough TP.
|
||||
error?.SetTextId(32546);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!IsValidTarget(target, skill.validTarget) || !skill.IsValidMainTarget(this, target))
|
||||
//Proc requirement
|
||||
if (skill.procRequirement != BattleCommandProcRequirement.None && !charaWork.battleTemp.timingCommandFlag[(int)skill.procRequirement - 1])
|
||||
{
|
||||
// error packet is set in IsValidTarget
|
||||
//Conditions for use are not met
|
||||
error?.SetTextId(32556);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2598,7 +2575,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
StatusEffect comboEffect = new StatusEffect(this, Server.GetWorldManager().GetStatusEffect((uint) StatusEffectId.Combo));
|
||||
comboEffect.SetDuration(13);
|
||||
comboEffect.SetOverwritable(1);
|
||||
statusEffects.AddStatusEffect(comboEffect, this, true);
|
||||
statusEffects.AddStatusEffect(comboEffect, this);
|
||||
playerWork.comboCostBonusRate = 1;
|
||||
}
|
||||
//Otherwise we're ending a combo, remove the status
|
||||
|
@ -2634,10 +2611,10 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
}
|
||||
|
||||
var hasShield = equip.GetItemAtSlot(SLOT_OFFHAND) != null ? 1 : 0;
|
||||
SetMod((uint)Modifier.HasShield, hasShield);
|
||||
SetMod((uint)Modifier.CanBlock, hasShield);
|
||||
|
||||
SetMod((uint)Modifier.AttackType, damageAttribute);
|
||||
SetMod((uint)Modifier.AttackDelay, attackDelay);
|
||||
SetMod((uint)Modifier.Delay, attackDelay);
|
||||
SetMod((uint)Modifier.HitCount, hitCount);
|
||||
|
||||
//These stats all correlate in a 3:2 fashion
|
||||
|
@ -2647,13 +2624,13 @@ namespace FFXIVClassic_Map_Server.Actors
|
|||
AddMod((uint)Modifier.Defense, (long)(GetMod(Modifier.Vitality) * 0.667));
|
||||
|
||||
//These stats correlate in a 4:1 fashion. (Unsure if MND is accurate but it would make sense for it to be)
|
||||
AddMod((uint)Modifier.MagicAttack, (long)((float)GetMod(Modifier.Intelligence) * 0.25));
|
||||
AddMod((uint)Modifier.AttackMagicPotency, (long)((float)GetMod(Modifier.Intelligence) * 0.25));
|
||||
|
||||
AddMod((uint)Modifier.MagicAccuracy, (long)((float)GetMod(Modifier.Mind) * 0.25));
|
||||
AddMod((uint)Modifier.MagicHeal, (long)((float)GetMod(Modifier.Mind) * 0.25));
|
||||
AddMod((uint)Modifier.HealingMagicPotency, (long)((float)GetMod(Modifier.Mind) * 0.25));
|
||||
|
||||
AddMod((uint)Modifier.MagicEvasion, (long)((float)GetMod(Modifier.Piety) * 0.25));
|
||||
AddMod((uint)Modifier.MagicEnfeeblingPotency, (long)((float)GetMod(Modifier.Piety) * 0.25));
|
||||
AddMod((uint)Modifier.EnfeeblingMagicPotency, (long)((float)GetMod(Modifier.Piety) * 0.25));
|
||||
|
||||
//VIT correlates to HP in a 1:1 fashion
|
||||
AddMod((uint)Modifier.Hp, (long)((float)Modifier.Vitality));
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using FFXIVClassic_Map_Server.dataobjects;
|
||||
using FFXIVClassic_Map_Server.packets.send.group;
|
||||
using FFXIVClassic_Map_Server.packets.send.groups;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FFXIVClassic_Map_Server.actors.group
|
||||
|
@ -126,13 +127,14 @@ namespace FFXIVClassic_Map_Server.actors.group
|
|||
|
||||
while (true)
|
||||
{
|
||||
if (GetMemberCount() - currentIndex >= 64)
|
||||
int memberCount = Math.Min(GetMemberCount(), members.Count);
|
||||
if (memberCount - currentIndex >= 64)
|
||||
session.QueuePacket(GroupMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
|
||||
else if (GetMemberCount() - currentIndex >= 32)
|
||||
else if (memberCount - currentIndex >= 32)
|
||||
session.QueuePacket(GroupMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
|
||||
else if (GetMemberCount() - currentIndex >= 16)
|
||||
else if (memberCount - currentIndex >= 16)
|
||||
session.QueuePacket(GroupMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
|
||||
else if (GetMemberCount() - currentIndex > 0)
|
||||
else if (memberCount - currentIndex > 0)
|
||||
session.QueuePacket(GroupMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
|
||||
else
|
||||
break;
|
||||
|
|
|
@ -8,13 +8,26 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||
//These flags can be stacked and mixed, but the client will prioritize certain flags over others.
|
||||
[Flags]
|
||||
public enum HitEffect : uint
|
||||
{
|
||||
//All HitEffects have the last byte 0x8
|
||||
{
|
||||
//This is used for physical attacks
|
||||
HitEffectType = 8 << 24,
|
||||
//This is used for additioanl effect hits. Only difference from HitEffectType is that it does not play audio.
|
||||
AdditionalEffectType = 24 << 24,
|
||||
//Status effects use 32 << 24
|
||||
StatusEffectType = 32 << 24,
|
||||
//Magic effects use 48 << 24
|
||||
//When losing a status effect while using a skill, this prevents the hit effect from playing on the actor playing the animation
|
||||
StatusLossType = 40 << 24,
|
||||
//Magic effects use 48 << 24, this is also used for when statuses are lost on attack
|
||||
MagicEffectType = 48 << 24,
|
||||
//This places the number on the user regardless of the target this hit effect is for, used for things like bloodbath
|
||||
SelfHealType = 72 << 24,
|
||||
//Plays the effect animation with no text or additional effects. Unsure if there are any flags. Used for things like Convert
|
||||
AnimationEffectType = 96 << 24,
|
||||
|
||||
//Each Type has it's own set of flags. These should be split into their own enums,
|
||||
//but for now just keep them all under HitEffect so we don't have to change anything.
|
||||
|
||||
//HitEffectType flags
|
||||
|
||||
//Not setting RecoilLv2 or RecoilLv3 results in the weaker RecoilLv1.
|
||||
//These are the recoil animations that play on the target, ranging from weak to strong.
|
||||
|
@ -54,15 +67,10 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||
//Another effect plays when both Protect and Shell flags are activated.
|
||||
//Not sure what this effect is.
|
||||
//Random guess: if the attack was a hybrid of both physical and magical and the target had both Protect and Shell buffs applied.
|
||||
Protect = 1 << 6 | HitEffectType,
|
||||
Shell = 1 << 7 | HitEffectType,
|
||||
Protect = 1 << 6,
|
||||
Shell = 1 << 7,
|
||||
ProtectShellSpecial = Protect | Shell,
|
||||
|
||||
// Required for heal text to be blue, not sure if that's all it's used for
|
||||
Heal = 1 << 8,
|
||||
MP = 1 << 9, //Causes "MP" text to appear when used with MagicEffectType. | with Heal to make text blue
|
||||
TP = 1 << 10,//Causes "TP" text to appear when used with MagicEffectType. | with Heal to make text blue
|
||||
|
||||
//If only HitEffect1 is set out of the hit effects, the "Evade!" pop-up text triggers along with the evade visual.
|
||||
//If no hit effects are set, the "Miss!" pop-up is triggered and no hit visual is played.
|
||||
HitEffect1 = 1 << 9,
|
||||
|
@ -103,17 +111,69 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||
UnknownShieldEffect = HitEffect5 | HitEffect4,
|
||||
Stoneskin = HitEffect5 | HitEffect4 | HitEffect1,
|
||||
|
||||
//Unknown = 1 << 14, -- Not sure what this flag does; might be another HitEffect.
|
||||
|
||||
//A special effect when performing appropriate skill combos in succession.
|
||||
//Ex: Thunder (SkillCombo1 Effect) -> Thundara (SkillCombo2 Effect) -> Thundaga (SkillCombo3 Effect)
|
||||
//Special Note: SkillCombo4 was never actually used in 1.0 since combos only chained up to 3 times maximum.
|
||||
SkillCombo1 = 1 << 15,
|
||||
SkillCombo2 = 1 << 16,
|
||||
SkillCombo3 = SkillCombo1 | SkillCombo2,
|
||||
SkillCombo4 = 1 << 17
|
||||
SkillCombo4 = 1 << 17,
|
||||
|
||||
//Flags beyond here are unknown/untested.
|
||||
//This is used in the absorb effect for some reason
|
||||
Unknown = 1 << 19,
|
||||
|
||||
//AdditionalEffectType flags
|
||||
//The AdditionalEffectType is used for the additional effects some weapons have.
|
||||
//These effect ids do not repeat the effect of the attack and will not show without a preceding HitEffectType or MagicEffectType
|
||||
|
||||
//It's unclear what this is for. The ifrit fight capture has a BLM using the garuda weapon
|
||||
//and this flag is set every time but has no apparent effect.
|
||||
UnknownAdditionalFlag = 1,
|
||||
|
||||
//These play effects on the target
|
||||
FireEffect = 1 << 10,
|
||||
IceEffect = 2 << 10,
|
||||
WindEffect = 3 << 10,
|
||||
EarthEffect = 4 << 10,
|
||||
LightningEffect = 5 << 10,
|
||||
WaterEffect = 6 << 10,
|
||||
AstralEffect = 7 << 10, //Possibly for blind?
|
||||
UmbralEffect = 8 << 10, //Posibly for poison?
|
||||
|
||||
//Unknown status effect effects
|
||||
StatusEffect1 = 12 << 10,
|
||||
StatusEffect2 = 13 << 10,
|
||||
|
||||
HPAbsorbEffect = 14 << 10,
|
||||
MPAbsorbEffect = 15 << 10,
|
||||
TPAbsorbEffect = 16 << 10,
|
||||
TripleAbsorbEffect = 17 << 10, //Not sure about this
|
||||
MoogleEffect = 18 << 10,
|
||||
|
||||
//MagicEffectType Flags
|
||||
//THese are used for magic effects that deal or heal damage as well as damage over time effects
|
||||
//Crit is the same as HitEffectType
|
||||
FullResist = 0,
|
||||
WeakResist = 1 << 0, //Used for level 1, 2, and 3 resists probably
|
||||
NoResist = 1 << 1,
|
||||
|
||||
MagicShell = 1 << 4, //Used when casting on target with shell effects. MagicEffectType doesnt have a flag for protect or stoneskin
|
||||
MagicShield = 1 << 5, //When used with an command that has an animation, this plays a purple shield effect. DoTs also have this flag set (at least on ifrit) but they have no animations so it doesnt show
|
||||
|
||||
// Required for heal text to be blue, not sure if that's all it's used for
|
||||
Heal = 1 << 8,
|
||||
MP = 1 << 9, //Causes "MP" text to appear when used with MagicEffectType. | with Heal to make text blue
|
||||
TP = 1 << 10, //Causes "TP" text to appear when used with MagicEffectType. | with Heal to make text blue
|
||||
|
||||
//SelfHealType flags
|
||||
//This category causes numbers to appear on the user rather regardless of the target associated with the hit effect and do not play an animation
|
||||
//These determine the text that displays (HP has no text)
|
||||
SelfHealHP = 0,
|
||||
SelfHealMP = 1 << 0, //Shows MP text on self. | with SelfHeal to make blue
|
||||
SelfHealTP = 1 << 1, //Shows TP text on self. | with SelfHeal to make blue
|
||||
|
||||
//Causes self healing numbers to be blue
|
||||
SelfHeal = 1 << 10,
|
||||
}
|
||||
|
||||
//Mixing some of these flags will cause the client to crash.
|
||||
|
@ -134,9 +194,12 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||
Evade = 1,
|
||||
Parry = 2,
|
||||
Block = 3,
|
||||
Resist = 4,
|
||||
Hit = 5,
|
||||
Crit = 6
|
||||
SingleResist = 4,
|
||||
DoubleResist = 5,
|
||||
TripleResist = 6,
|
||||
FullResist = 7,
|
||||
Hit = 8,
|
||||
Crit = 9
|
||||
}
|
||||
|
||||
//Type of action
|
||||
|
@ -326,5 +389,16 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||
{
|
||||
return (ushort)hitType;
|
||||
}
|
||||
|
||||
public void SetTextId(ushort id)
|
||||
{
|
||||
worldMasterTextId = id;
|
||||
}
|
||||
|
||||
//Whether this action didn't miss, and wasn't evaded or resisted
|
||||
public bool ActionLanded()
|
||||
{
|
||||
return hitType > HitType.Evade && hitType != HitType.SingleResist && hitType != HitType.DoubleResist && hitType != HitType.FullResist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||
}
|
||||
|
||||
//Just to make scripting simpler
|
||||
//These have to be split into the normal actions and absorb actions because they use different flags
|
||||
//AddMP/HP/TPAction are for actions where the targetID is the person being targeted by command. Like Sanguine Rite would use AddMPAction
|
||||
public void AddMPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||
{
|
||||
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.MP | HitEffect.Heal);
|
||||
|
@ -28,13 +30,38 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|||
|
||||
public void AddHPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||
{
|
||||
uint effectId = (uint)(HitEffect.MagicEffectType | HitEffect.Heal);
|
||||
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.Heal);
|
||||
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||
}
|
||||
|
||||
public void AddTPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||
{
|
||||
uint effectId = (uint)(HitEffect.MagicEffectType | HitEffect.TP);
|
||||
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.TP | HitEffect.Heal);
|
||||
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||
}
|
||||
|
||||
//These are used for skills where the targetId is the person using a command. For example casting with parsimony would use AddMPAbsorbAction
|
||||
public void AddMPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||
{
|
||||
uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHealMP | HitEffect.SelfHeal);
|
||||
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||
}
|
||||
|
||||
public void AddHPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||
{
|
||||
uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHeal | HitEffect.SelfHeal);
|
||||
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||
}
|
||||
|
||||
public void AddTPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||
{
|
||||
uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHealTP | HitEffect.SelfHeal);
|
||||
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||
}
|
||||
|
||||
public void AddHitAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||
{
|
||||
uint effectId = (uint) (HitEffect.HitEffectType | HitEffect.Hit);
|
||||
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue