Combat additions

Added formulas for base EXP gain and chain experience
Added basic scripts for most player abilities and effects
Added stat gains for some abilities
Changed status flags
Fixed bug with player death
Fixed bug where auto attacks didnt work when not locked on
Added traits
This commit is contained in:
yogurt 2018-04-18 16:06:41 -05:00
parent b8d6a943aa
commit c5ce2ec771
239 changed files with 5125 additions and 1237 deletions

View file

@ -117,7 +117,7 @@ namespace FFXIVClassic_Map_Server.Actors
public Pet pet;
private Dictionary<Modifier, Int64> modifiers = new Dictionary<Modifier, long>();
private Dictionary<Modifier, double> modifiers = new Dictionary<Modifier, double>();
protected ushort hpBase, hpMaxBase, mpBase, mpMaxBase, tpBase;
protected BattleTemp baseStats = new BattleTemp();
@ -191,6 +191,7 @@ namespace FFXIVClassic_Map_Server.Actors
zone.BroadcastPacketsAroundActor(this, propPacketUtil.Done());
}
//This logic isn't correct, order of GetStatusEffects() is not necessarily the same as the actual effects in game. Also sending every time at once isn't needed
public List<SubPacket> GetActorStatusPackets()
{
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/status", this);
@ -260,7 +261,7 @@ namespace FFXIVClassic_Map_Server.Actors
public void DoBattleAction(ushort commandId, uint animationId, List<BattleAction> actions)
{
int currentIndex = 0;
int currentIndex = 0;
while (true)
{
@ -275,7 +276,11 @@ namespace FFXIVClassic_Map_Server.Actors
}
else
break;
animationId = 0; //If more than one packet is sent out, only send the animation once to avoid double playing.
//Sending multiple packets at once causes some issues. Setting any combination of these to zero changes what breaks
//animationId = 0; //If more than one packet is sent out, only send the animation once to avoid double playing.
//commandId = 0;
//sourceActorId = 0;
}
}
@ -291,20 +296,51 @@ namespace FFXIVClassic_Map_Server.Actors
PathTo(target.positionX, target.positionY, target.positionZ, stepSize, maxPath, radius);
}
public Int64 GetMod(uint modifier)
public double GetMod(Modifier modifier)
{
Int64 res;
return GetMod((uint)modifier);
}
public double GetMod(uint modifier)
{
double res;
if (modifiers.TryGetValue((Modifier)modifier, out res))
return res;
return 0;
}
public void SetMod(uint modifier, Int64 val)
public void SetMod(uint modifier, double val)
{
if (modifiers.ContainsKey((Modifier)modifier))
modifiers[(Modifier)modifier] = val;
else
modifiers.Add((Modifier)modifier, val);
if (modifier <= 35)
updateFlags |= ActorUpdateFlags.Stats;
}
public void AddMod(Modifier modifier, double val)
{
AddMod((uint)modifier, val);
}
public void AddMod(uint modifier, double val)
{
double newVal = GetMod(modifier) + val;
SetMod(modifier, newVal);
}
public void SubtractMod(Modifier modifier, double val)
{
AddMod((uint)modifier, val);
}
public void SubtractMod(uint modifier, double val)
{
double newVal = GetMod(modifier) - val;
SetMod(modifier, newVal);
}
public virtual void OnPath(Vector3 point)
@ -341,6 +377,21 @@ namespace FFXIVClassic_Map_Server.Actors
//DoBattleAction(21001, 0x7C000062, new BattleAction(this.actorId, 0, 1, 0, 0, 1)); //Attack Mode
}
if ((updateFlags & ActorUpdateFlags.Status) != 0)
{
List<SubPacket> statusPackets = statusEffects.GetStatusPackets();
packets.AddRange(statusPackets);
statusPackets.Clear();
updateFlags &= ~ActorUpdateFlags.Status;
}
if ((updateFlags & ActorUpdateFlags.StatusTime) != 0)
{
packets.AddRange(statusEffects.GetStatusTimerPackets());
updateFlags &= ~ActorUpdateFlags.StatusTime;
}
if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0)
{
var propPacketUtil = new ActorPropertyPacketUtil("charaWork/stateAtQuicklyForAll", this);
@ -351,6 +402,7 @@ namespace FFXIVClassic_Map_Server.Actors
propPacketUtil.AddProperty("charaWork.parameterTemp.tp");
packets.AddRange(propPacketUtil.Done());
}
base.PostUpdate(tick, packets);
}
}
@ -393,11 +445,12 @@ namespace FFXIVClassic_Map_Server.Actors
public virtual bool Engage(uint targid = 0, ushort newMainState = 0xFFFF)
{
// todo: attack the things
if (newMainState != 0xFFFF)
/*if (newMainState != 0xFFFF)
{
this.newMainState = newMainState;
currentMainState = newMainState;// this.newMainState = newMainState;
updateFlags |= ActorUpdateFlags.State;
}
else if (aiContainer.CanChangeState())
else*/ if (aiContainer.CanChangeState())
{
if (targid == 0)
{
@ -423,11 +476,12 @@ namespace FFXIVClassic_Map_Server.Actors
public virtual bool Disengage(ushort newMainState = 0xFFFF)
{
if (newMainState != 0xFFFF)
/*if (newMainState != 0xFFFF)
{
this.newMainState = newMainState;
currentMainState = newMainState;// this.newMainState = newMainState;
updateFlags |= ActorUpdateFlags.State;
}
else if (IsEngaged())
else*/ if (IsEngaged())
{
aiContainer.Disengage();
return true;
@ -463,7 +517,8 @@ namespace FFXIVClassic_Map_Server.Actors
RecalculateStats();
}
public virtual void Die(DateTime tick)
//AdditionalActions is the list of actions that EXP/Chain messages are added to
public virtual void Die(DateTime tick, BattleActionContainer actionContainer = null)
{
// todo: actual despawn timer
aiContainer.InternalDie(tick, 10);
@ -523,7 +578,7 @@ namespace FFXIVClassic_Map_Server.Actors
public byte GetHPP()
{
return (byte)(charaWork.parameterSave.hp[0] == 0 ? 0 : (charaWork.parameterSave.hp[0] / charaWork.parameterSave.hpMax[0]) * 100);
return (byte)(charaWork.parameterSave.hp[0] == 0 ? 0 : (charaWork.parameterSave.hp[0] / (float) charaWork.parameterSave.hpMax[0]) * 100);
}
public void SetHP(uint hp)
@ -558,8 +613,8 @@ namespace FFXIVClassic_Map_Server.Actors
// 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)
{
// dont wanna die ded
if (IsAlive())
// dont wanna die ded, don't want to send update if hp isn't actually changed
if (IsAlive() && hp != 0)
{
// todo: +/- hp and die
// todo: battlenpcs probably have way more hp?
@ -567,9 +622,6 @@ namespace FFXIVClassic_Map_Server.Actors
addHp = addHp.Clamp((short)GetMod((uint)Modifier.MinimumHpLock), charaWork.parameterSave.hpMax[0]);
charaWork.parameterSave.hp[0] = (short)addHp;
if (charaWork.parameterSave.hp[0] < 1)
Die(Program.Tick);
updateFlags |= ActorUpdateFlags.HpTpMp;
}
}
@ -586,18 +638,24 @@ namespace FFXIVClassic_Map_Server.Actors
public void AddMP(int mp)
{
charaWork.parameterSave.mp = (short)(charaWork.parameterSave.mp + mp).Clamp(ushort.MinValue, charaWork.parameterSave.mpMax);
if (IsAlive() && mp != 0)
{
charaWork.parameterSave.mp = (short)(charaWork.parameterSave.mp + mp).Clamp(ushort.MinValue, charaWork.parameterSave.mpMax);
// todo: check hidden effects and shit
// todo: check hidden effects and shit
updateFlags |= ActorUpdateFlags.HpTpMp;
updateFlags |= ActorUpdateFlags.HpTpMp;
}
}
public void AddTP(int tp)
{
if (IsAlive())
if (IsAlive() && tp != 0)
{
charaWork.parameterTemp.tp = (short)((charaWork.parameterTemp.tp + tp).Clamp(0, 3000));
var addTp = charaWork.parameterTemp.tp + tp;
addTp = addTp.Clamp((int) GetMod(Modifier.MinimumTpLock), 3000);
charaWork.parameterTemp.tp = (short) addTp;
tpBase = (ushort)charaWork.parameterTemp.tp;
updateFlags |= ActorUpdateFlags.HpTpMp;
@ -661,12 +719,12 @@ namespace FFXIVClassic_Map_Server.Actors
//CalculateBaseStats();
}
public void SetStat(uint statId, uint val)
public void SetStat(uint statId, int val)
{
charaWork.battleTemp.generalParameter[statId] = (ushort)val;
charaWork.battleTemp.generalParameter[statId] = (short)val;
}
public ushort GetStat(uint statId)
public short GetStat(uint statId)
{
return charaWork.battleTemp.generalParameter[statId];
}
@ -674,7 +732,7 @@ namespace FFXIVClassic_Map_Server.Actors
public virtual float GetSpeed()
{
// todo: for battlenpc/player calculate speed
return GetMod((uint)Modifier.Speed);
return (float) GetMod((uint)Modifier.Speed);
}
public virtual void OnAttack(State state, BattleAction action, ref BattleAction error)
@ -688,7 +746,7 @@ namespace FFXIVClassic_Map_Server.Actors
}
// todo: call onAttack/onDamageTaken
BattleUtils.DamageTarget(this, target, action, DamageTakenType.Attack);
//BattleUtils.DamageTarget(this, target, DamageTakenType.Attack, action);
AddTP(200);
target.AddTP(100);
}
@ -704,7 +762,7 @@ namespace FFXIVClassic_Map_Server.Actors
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
{
//BattleUtils.HandleHitType(this, chara, action);
BattleUtils.DoAction(this, chara, action, DamageTakenType.Magic);
//BattleUtils.DoAction(this, chara, action, DamageTakenType.Magic);
}
}
lua.LuaEngine.GetInstance().OnSignal("spellUsed");
@ -719,14 +777,11 @@ namespace FFXIVClassic_Map_Server.Actors
//Should we just store the character insteado f having to find it again?
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
{
BattleUtils.DoAction(this, chara, action, DamageTakenType.Weaponskill);
//BattleUtils.DoAction(this, chara, action, DamageTakenType.Weaponskill);
}
}
this.DelTP(skill.tpCost);
//Do procs reset on weaponskills?
ResetProcs();
lua.LuaEngine.GetInstance().OnSignal("weaponskillUsed");
}
public virtual void OnAbility(State state, BattleAction[] actions, BattleCommand ability, ref BattleAction[] errors)
@ -735,7 +790,7 @@ namespace FFXIVClassic_Map_Server.Actors
{
if (zone.FindActorInArea<Character>(action.targetId) is Character chara)
{
BattleUtils.DoAction(this, chara, action, DamageTakenType.Ability);
//BattleUtils.DoAction(this, chara, action, DamageTakenType.Ability);
}
}
}
@ -755,9 +810,55 @@ namespace FFXIVClassic_Map_Server.Actors
}
public virtual void OnDamageTaken(Character attacker, BattleAction action, DamageTakenType damageTakenType)
public virtual void OnDamageDealt(Character defender, BattleAction action, BattleActionContainer actionContainer = null)
{
switch (action.hitType)
{
case (HitType.Miss):
OnMiss(this, action, actionContainer);
break;
default:
OnHit(defender, action, actionContainer);
break;
}
//TP is only gained from autoattacks and abilities
if (action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability)
{
//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.1);
AddTP((int)(weaponDelay * 100 * storeTPPercent));
}
}
public virtual void OnDamageTaken(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
{
switch (action.hitType)
{
case (HitType.Miss):
OnEvade(attacker, action, actionContainer);
break;
case (HitType.Parry):
OnParry(attacker, action, actionContainer);
break;
case (HitType.Block):
OnBlock(attacker, action, actionContainer);
break;
}
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, action);
//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)
@ -865,29 +966,8 @@ namespace FFXIVClassic_Map_Server.Actors
if (val)
{
StatusEffect procEffect = Server.GetWorldManager().GetStatusEffect(effectId);
procEffect.SetDuration(5000);
procEffect.SetDuration(5);
statusEffects.AddStatusEffect(procEffect, this, true, true);
string procFunc = "";
//is there any reason we need this
switch(procId)
{
case (0):
procFunc = "onEvade";
break;
case (1):
procFunc = "onBlock";
break;
case (2):
procFunc = "onParry";
break;
case (3):
procFunc = "onMiss";
break;
}
//lua.LuaEngine.CallLuaBattleFunction(this, procFunc, this);
}
//Otherwise we're reseting a proc, remove the status
else
@ -921,9 +1001,124 @@ namespace FFXIVClassic_Map_Server.Actors
}
//Called when this character evades attacker's action
public void OnEvade(Character attacker, BattleAction action)
public void OnEvade(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
{
SetProc((ushort)HitType.Evade);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnEvade, "onEvade", attacker, this, action, actionContainer);
}
//Called when this character blocks attacker's action
public void OnBlock(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
{
SetProc((ushort)HitType.Block);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnBlock, "onBlock", attacker, this, action, actionContainer);
}
//Called when this character parries attacker's action
public void OnParry(Character attacker, BattleAction action, BattleActionContainer actionContainer = null)
{
SetProc((ushort)HitType.Parry);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, action, actionContainer);
}
//Called when this character misses
public void OnMiss(Character defender, BattleAction action, BattleActionContainer actionContainer = null)
{
SetProc((ushort)HitType.Miss);
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, action, actionContainer);
}
public void OnHit(Character defender, BattleAction action, BattleActionContainer actionContainer = null)
{
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, action, actionContainer);
}
//The order of messages that appears after using a command is:
//1. Cast start messages. (ie "You begin casting... ")
//2. Messages from buffs that activate before the command actually starts, like Power Surge or Presence of Mind. (This may be wrong and these could be the same as 4.)
//3. If the command is a multi-hit command, this is where the "You use [command] on [target]" message goes
//Then, for each hit:
//4. Buffs that activate before a command hits, like Blindside
//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
//For every target defeated:
//8. Defeat message
//9. EXP message
//10. EXP chain message
//folder is probably temporary until move to cached scripts is complete
public void DoBattleCommand(BattleCommand command, string folder)
{
//List<BattleAction> actions = new List<BattleAction>();
BattleActionContainer actions = new BattleActionContainer();
var targets = command.targetFind.GetTargets();
bool hitTarget = false;
if (targets.Count > 0)
{
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandStart, "onCommandStart", this, command, actions);
foreach (var chara in targets)
{
ushort hitCount = 0;
for (int hitNum = 1; hitNum <= command.numHits; hitNum++)
{
var action = new BattleAction(chara.actorId, command, (byte)GetHitDirection(chara), (byte)hitNum);
//uncached script
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)
{
hitTarget = true;
hitCount++;
}
}
if (command.numHits > 1)
{
//You use [command] on [target].
actions.AddAction(new BattleAction(chara.actorId, 30442, 0, 0, (byte)hitCount));
}
}
statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandFinish, "onCommandFinish", this, command, actions);
}
else
{
actions.AddAction(new BattleAction(target.actorId, 30202, 0));
}
//Now that we know if we hit the target we can check if the combo continues
if (this is Player player && command.commandType == CommandType.WeaponSkill)
if (command.isCombo && hitTarget)
player.SetCombos(command.comboNextCommandId);
else
player.SetCombos();
BattleAction error = new BattleAction(actorId, 0, 0);
actions.CombineLists();
DoBattleAction(command.id, command.battleAnimation, actions.GetList());
}
public List<Character> GetPartyMembersInRange(uint range)
{
TargetFind targetFind = new TargetFind(this);
targetFind.SetAOEType(ValidTarget.PartyMember, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range);
targetFind.FindWithinArea(this, ValidTarget.PartyMember, TargetFindAOETarget.Self);
return targetFind.GetTargets();
}
}
}