renamed tables for consistency

- added magic.lua (todo: enumerate modifiers and stuff)
- moved aggro handling to session position update
- some cleanup
This commit is contained in:
Tahir Akhlaq 2017-08-29 01:15:12 +01:00
parent 6c74222b68
commit f4016e1a12
25 changed files with 539 additions and 442 deletions

View file

@ -1343,7 +1343,7 @@ namespace FFXIVClassic_Map_Server
player.charaWork.parameterSave.commandSlot_recastTime[index - player.charaWork.commandBorder] = reader.GetUInt32(2);
//Recast timer
BattleCommand ability = Server.GetWorldManager().GetAbility((ushort)(trueCommandId ^ 2700083200));
BattleCommand ability = Server.GetWorldManager().GetBattleCommand((ushort)(trueCommandId ^ 2700083200));
player.charaWork.parameterTemp.maxCommandRecastTime[index - player.charaWork.commandBorder] = (ushort) (ability != null ? ability.recastTimeSeconds : 1);
//Previous recast timer
player.charaWork.parameterSave.commandSlot_recastTime[index - player.charaWork.commandBorder] = reader.GetUInt32(2);
@ -2085,7 +2085,7 @@ namespace FFXIVClassic_Map_Server
{
conn.Open();
var query = @"SELECT id, name, flags, overwrite FROM status_effects;";
var query = @"SELECT id, name, flags, overwrite FROM server_statuseffects;";
MySqlCommand cmd = new MySqlCommand(query, conn);
@ -2093,10 +2093,10 @@ namespace FFXIVClassic_Map_Server
{
while (reader.Read())
{
var id = reader.GetUInt32(0);
var name = reader.GetString(1);
var flags = reader.GetUInt32(2);
var overwrite = reader.GetUInt32(3);
var id = reader.GetUInt32("id");
var name = reader.GetString("name");
var flags = reader.GetUInt32("flags");
var overwrite = reader.GetByte("overwrite");
var effect = new StatusEffect(id, name, flags, overwrite);
effects.Add(id, effect);
@ -2160,7 +2160,7 @@ namespace FFXIVClassic_Map_Server
conn.Open();
var query = ("SELECT `id`, name, classJob, lvl, requirements, validTarget, aoeType, numHits, positionBonus, procRequirement, `range`, buffDuration, debuffDuration, " +
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, aoeRange FROM battle_commands;");
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, aoeRange FROM server_battle_commands;");
MySqlCommand cmd = new MySqlCommand(query, conn);

View file

@ -55,7 +55,7 @@ namespace FFXIVClassic_Map_Server
mWorldManager.LoadSpawnLocations();
mWorldManager.SpawnAllActors();
mWorldManager.LoadStatusEffects();
mWorldManager.LoadAbilities();
mWorldManager.LoadBattleCommands();
mWorldManager.StartZoneThread();
IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), int.Parse(ConfigConstants.OPTIONS_PORT));

View file

@ -36,8 +36,8 @@ namespace FFXIVClassic_Map_Server
private Dictionary<uint, ZoneEntrance> zoneEntranceList;
private Dictionary<uint, ActorClass> actorClasses = new Dictionary<uint,ActorClass>();
private Dictionary<ulong, Party> currentPlayerParties = new Dictionary<ulong, Party>(); //GroupId, Party object
private Dictionary<uint, StatusEffect> effectList = new Dictionary<uint, StatusEffect>();
private Dictionary<ushort, BattleCommand> abilityList = new Dictionary<ushort, BattleCommand>();
private Dictionary<uint, StatusEffect> statusEffectList = new Dictionary<uint, StatusEffect>();
private Dictionary<ushort, BattleCommand> battleCommandList = new Dictionary<ushort, BattleCommand>();
private Server mServer;
@ -1141,25 +1141,25 @@ namespace FFXIVClassic_Map_Server
}
public void LoadStatusEffects()
{
effectList = Database.LoadGlobalStatusEffectList();
statusEffectList = Database.LoadGlobalStatusEffectList();
}
public StatusEffect GetStatusEffect(uint id)
{
StatusEffect effect;
StatusEffect statusEffect;
return effectList.TryGetValue(id, out effect) ? new StatusEffect(null, effect) : null;
return statusEffectList.TryGetValue(id, out statusEffect) ? new StatusEffect(null, statusEffect) : null;
}
public void LoadAbilities()
public void LoadBattleCommands()
{
abilityList = Database.LoadGlobalBattleCommandList();
battleCommandList = Database.LoadGlobalBattleCommandList();
}
public BattleCommand GetAbility(uint id)
public BattleCommand GetBattleCommand(uint id)
{
BattleCommand ability;
return abilityList.TryGetValue((ushort)id, out ability) ? ability.Clone() : null;
BattleCommand battleCommand;
return battleCommandList.TryGetValue((ushort)id, out battleCommand) ? battleCommand.Clone() : null;
}
}
}

View file

@ -613,21 +613,6 @@ namespace FFXIVClassic_Map_Server.Actors
return zoneId;
}
// todo: do this properly
public bool IsFacing(float x, float 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;
return rot1 == (float)dRot;
}
public void LookAt(Actor actor)
{
if (actor != null)
@ -667,7 +652,7 @@ namespace FFXIVClassic_Map_Server.Actors
public bool IsFacing(float x, float z, float angle = 40.0f)
{
angle = (float)(Math.PI * angle / 180);
return Vector3.GetAngle(positionX, positionZ, x, z) <= angle;
return Vector3.GetAngle(positionX, positionZ, x, z) < angle;
}
// todo: is this legit?
@ -725,31 +710,6 @@ namespace FFXIVClassic_Map_Server.Actors
}
#endregion
public Player GetAsPlayer()
{
return currentSubState == SetActorStatePacket.SUB_STATE_PLAYER && this is Player ? ((Player)this) : null;
}
public BattleNpc GetAsMob()
{
return currentSubState == SetActorStatePacket.SUB_STATE_MONSTER && this is BattleNpc ? ((BattleNpc)this) : null;
}
public Npc GetAsNpc()
{
return currentSubState != SetActorStatePacket.SUB_STATE_PLAYER && this is Npc ? ((Npc)this) : null;
}
public Actor GetAsActor()
{
return this is Actor ? ((Actor)this) : null;
}
public Character GetAsCharacter()
{
return this is Character ? ((Character)this) : null;
}
public SubPacket CreateGameMessagePacket(Actor textIdOwner, ushort textId, byte log, params object[] msgParams)
{
if (msgParams == null || msgParams.Length == 0)

View file

@ -15,6 +15,7 @@ using System.Threading.Tasks;
using FFXIVClassic_Map_Server.packets.send;
using FFXIVClassic_Map_Server.actors.group;
using FFXIVClassic_Map_Server.actors.director;
using FFXIVClassic_Map_Server.actors.chara.ai.controllers;
namespace FFXIVClassic_Map_Server.Actors
{

View file

@ -12,6 +12,7 @@ using FFXIVClassic_Map_Server.actors.chara;
using FFXIVClassic_Map_Server.packets.send.actor.battle;
using FFXIVClassic_Map_Server.packets.send;
using FFXIVClassic_Map_Server.actors.chara.ai.state;
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
namespace FFXIVClassic_Map_Server.Actors
{
@ -473,22 +474,33 @@ namespace FFXIVClassic_Map_Server.Actors
return moveSpeeds[2] + GetMod((uint)Modifier.Speed);
}
public virtual void OnAttack(State state, BattleAction action)
public virtual void OnAttack(State state, BattleAction action, ref SubPacket errorPacket)
{
// todo: change animation based on equipped weapon
action.effectId |= (uint)HitEffect.HitVisual1; // melee
var target = state.GetTarget();
// todo: get hitrate and shit, handle protect effect and whatever
if (BattleUtils.TryAttack(this, target, action, ref errorPacket))
{
action.amount = BattleUtils.CalculateAttackDamage(this, target, action);
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
}
target.DelHP(action.amount);
}
public virtual void OnCast(State state, BattleAction action, ref SubPacket errorPacket)
{
}
public virtual void OnCast(State state, BattleAction action)
public virtual void OnWeaponSkill(State state, BattleAction action, ref SubPacket errorPacket)
{
}
public virtual void OnWeaponSkill(State state, BattleAction action)
{
}
public virtual void OnAbility(State state, BattleAction action)
public virtual void OnAbility(State state, BattleAction action, ref SubPacket errorPacket)
{
}

View file

@ -126,7 +126,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
public void ChangeState(State state)
{
if (GetCurrentState() != null)
if (CanChangeState())
{
if (states.Count <= 10)
{

View file

@ -139,6 +139,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai
}
else
{
distanceFromPoint = 0;
return true;
}

View file

@ -22,7 +22,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
private DateTime waitTime;
private bool firstSpell = true;
private DateTime lastRoamScript; // todo: what even is this used as
private DateTime lastRoamUpdate;
private new BattleNpc owner;
public BattleNpcController(BattleNpc owner) :
@ -76,7 +76,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
// todo: too far, path to player if mob, message if player
// owner.ResetMoveSpeeds();
owner.moveState = 2;
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER && owner.moveSpeeds[1] != 0)
if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER && owner.GetSpeed() != 0)
{
// todo: actual stat based range
if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > 10)
@ -106,6 +106,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
// todo:
lastActionTime = lastUpdate.AddSeconds(5);
owner.isMovingToSpawn = true;
owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None);
owner.aiContainer.pathFind.PreparePath(owner.spawnX, owner.spawnY, owner.spawnZ, 1.5f, 10);
neutralTime = lastActionTime;
owner.hateContainer.ClearHate();
owner.moveState = 1;
@ -147,7 +149,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
if (tick >= waitTime)
{
// todo: aggro cooldown
neutralTime = tick.AddSeconds(5);
neutralTime = tick.AddSeconds(3);
if (owner.aiContainer.pathFind.IsFollowingPath())
{
owner.aiContainer.pathFind.FollowPath();
@ -157,8 +159,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
{
if (tick >= lastActionTime)
{
var battlenpc = owner as BattleNpc;
owner.aiContainer.pathFind.PathInRange(battlenpc.spawnX, battlenpc.spawnY, battlenpc.spawnZ, 1.5f, 15.0f);
}
}
// todo:
@ -166,6 +167,28 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
owner.OnRoam(tick);
}
if (tick >= lastRoamUpdate && !owner.aiContainer.pathFind.IsFollowingPath())
{
// will move on next tick
owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None);
owner.aiContainer.pathFind.PathInRange(owner.spawnX, owner.spawnY, owner.spawnZ, 1.5f, 20.0f);
}
if (tick >= neutralTime)
{
foreach (var player in owner.zone.GetActorsAroundActor<Player>(owner, 50))
{
if (!owner.isMovingToSpawn && owner.aiContainer.pathFind.AtPoint() && owner.aggroType != AggroType.None)
{
uint levelDifference = (uint)Math.Abs(owner.charaWork.parameterSave.state_mainSkillLevel - ((Player)player).charaWork.parameterSave.state_mainSkillLevel);
if (levelDifference < 10 || (owner.aggroType & AggroType.IgnoreLevelDifference) != 0 && ((BattleNpcController)owner.aiContainer.GetController()).CanAggroTarget((Player)player))
owner.hateContainer.AddBaseHate((Player)player);
}
}
}
if (owner.aiContainer.pathFind.IsFollowingPath())
{
owner.aiContainer.pathFind.FollowPath();
@ -223,7 +246,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers
continue;
float mobDistance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, chara.positionX, chara.positionY, chara.positionZ);
if (mobDistance < 0.90f && (chara.updateFlags & ActorUpdateFlags.Position) == 0)
if (mobDistance < 0.70f && (chara.updateFlags & ActorUpdateFlags.Position) == 0)
{
owner.aiContainer.pathFind.PathInRange(targetPos, 1.3f, 1.8f);
break;

View file

@ -88,14 +88,38 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
// todo: possible underflow
BattleAction action = new BattleAction();
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
errorPacket = null;
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
action.animation = 0x19001000;
action.targetId = target.actorId;
action.effectId = (uint)HitEffect.Hit;
action.worldMasterTextId = 0x765D;
action.param = 1; // todo: hit effect doesnt display without this?
owner.OnAttack(this, action);
//this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0);
action.param = (byte)HitDirection.None; // HitDirection (auto attack shouldnt need this afaik)
// todo: implement auto attack damage bonus in Character.OnAttack
/*
Auto-attack Damage Bonus
Class Bonus 1 Bonus 2
Pugilist Intelligence Strength
Gladiator Mind Strength
Marauder Vitality Strength
Archer Dexterity Piety
Lancer Piety Strength
Conjurer Mind Piety
Thaumaturge Mind Piety
* The above damage bonus also applies to Shot attacks by archers.
*/
owner.OnAttack(this, action, ref errorPacket);
// handle paralyze/intimidate/sleep/whatever in character thing
if (errorPacket == null)
owner.zone.BroadcastPacketAroundActor(owner, BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, action.targetId, action.animation,
0x8000000 | action.effectId, action.worldMasterTextId, (ushort)BattleActionX01PacketCommand.Attack, action.amount, action.param)
);
else
owner.zone.BroadcastPacketAroundActor(owner, errorPacket);
//this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0);
}
public override void TryInterrupt()
@ -104,14 +128,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
// todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack
var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction);
uint effectId = 0;
uint statusId = 0;
if (list.Count > 0)
{
// todo: actually check proc rate/random chance of whatever effect
effectId = list[0].GetStatusEffectId();
statusId = list[0].GetStatusId();
}
// todo: which is actually the swing packet
//this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0);
//this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, statusId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0);
//owner.zone.BroadcastPacketAroundActor(owner, errorPacket);
//errorPacket = null;
interrupt = true;
@ -135,6 +159,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
// todo: shouldnt need to check if owner is dead since all states would be cleared
if (owner.aiContainer.IsDead() || target.aiContainer.IsDead())
{
target = null;
return false;
}
else if (!owner.aiContainer.GetTargetFind().CanTarget(target, false, true))

View file

@ -24,7 +24,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
this.startPos = owner.GetPosAsVector3();
this.startTime = DateTime.Now;
// todo: lookup spell from global table
this.spell = Server.GetWorldManager().GetAbility(spellId);
this.spell = Server.GetWorldManager().GetBattleCommand(spellId);
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicPrepare", owner, target, spell);
// todo: check recast
@ -125,20 +125,21 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
var action = new BattleAction();
action.effectId = spell.effectAnimation;
action.param = 1;
action.param = (byte)HitDirection.None; // HitDirection (magic shouldnt need this afaik)
action.unknown = 1;
action.targetId = chara.actorId;
action.worldMasterTextId = spell.worldMasterTextId;
action.animation = spell.battleAnimation;
action.amount = (ushort)lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "onMagicFinish", owner, chara, spell, action);
actions[i++] = action;
//packets.Add(BattleActionX01Packet.BuildPacket(chara.actorId, owner.actorId, action.targetId, spell.battleAnimation, action.effectId, action.worldMasterTextId, spell.id, action.amount, action.param));
}
owner.zone.BroadcastPacketAroundActor(owner,
spell.aoeType != TargetFindAOEType.None ? (BattleActionX10Packet.BuildPacket(owner.target.actorId, owner.actorId, spell.battleAnimation, spell.id, actions)) :
spell.aoeType != TargetFindAOEType.None ? (BattleActionX10Packet.BuildPacket(owner.actorId, owner.actorId, actions[0].animation, spell.id, actions)) :
BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, spell.battleAnimation, actions[0].effectId, actions[0].worldMasterTextId, spell.id, actions[0].amount, actions[0].param)
);
owner.zone.BroadcastPacketsAroundActor(owner, packets);
//owner.zone.BroadcastPacketsAroundActor(owner, packets);
}
public override void TryInterrupt()

View file

@ -21,7 +21,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
this.startTime = DateTime.Now;
// todo: lookup skill from global table
this.skill = Server.GetWorldManager().GetAbility(skillId);
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillPrepare", owner, target, skill);
// todo: check recast
@ -109,10 +109,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
{
var action = new BattleAction();
action.effectId = (uint)HitEffect.Hit;
action.param = 1;
action.param = 1; // HitDirection
action.unknown = 1;
action.targetId = chara.actorId;
action.worldMasterTextId = skill.worldMasterTextId;
action.animation = skill.battleAnimation;
// evasion, miss, dodge, etc to be handled in script, calling helpers from scripts/weaponskills.lua
action.amount = (ushort)lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillFinish", owner, target, skill, action);
actions[i++] = action;
@ -121,7 +122,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state
}
owner.zone.BroadcastPacketAroundActor(owner,
skill.aoeType != TargetFindAOEType.None ? (BattleActionX10Packet.BuildPacket(owner.target.actorId, owner.actorId, skill.battleAnimation, skill.id, actions)) :
skill.aoeType != TargetFindAOEType.None ? (BattleActionX10Packet.BuildPacket(owner.target.actorId, owner.actorId, actions[0].animation, skill.id, actions)) :
BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, skill.battleAnimation, actions[0].effectId, actions[0].worldMasterTextId, skill.id, actions[0].amount, actions[0].param)
);
}

View file

@ -7,16 +7,17 @@ using System.Threading.Tasks;
using FFXIVClassic_Map_Server.Actors;
using FFXIVClassic_Map_Server.packets.send.actor;
using FFXIVClassic_Map_Server.packets.send.actor.battle;
using FFXIVClassic.Common;
namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
{
static class BattleUtils
{
public static void TryAttack(Character attacker, Character defender, BattleAction action)
public static bool TryAttack(Character attacker, Character defender, BattleAction action, ref SubPacket errorPacket)
{
// todo: get hit rate, hit count, set hit effect
action.effectId |= (uint)(HitEffect.RecoilLv2 | HitEffect.Hit | HitEffect.HitVisual1);
return true;
}
public static ushort CalculateAttackDamage(Character attacker, Character defender, BattleAction action)
@ -40,6 +41,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils
return damage;
}
public static ushort GetCriticalHitDamage(Character attacker, Character defender, BattleAction action)
{
ushort damage = action.amount;
// todo:
//
// action.effectId |= (uint)HitEffect.Critical;
//
return damage;
}
public static ushort CalculateSpellDamage(Character attacker, Character defender, BattleAction action)
{
ushort damage = 0;

View file

@ -65,6 +65,16 @@ namespace FFXIVClassic_Map_Server.Actors
CalculateBaseStats();
}
public uint GetAggroType()
{
return (uint)aggroType;
}
public void SetAggroType(uint aggroType)
{
this.aggroType = (AggroType)aggroType;
}
public override void Update(DateTime tick)
{
this.aiContainer.Update(tick);
@ -137,6 +147,7 @@ namespace FFXIVClassic_Map_Server.Actors
this.isMovingToSpawn = false;
this.ResetMoveSpeeds();
this.hateContainer.ClearHate();
this.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE);
}
@ -145,11 +156,14 @@ namespace FFXIVClassic_Map_Server.Actors
if (IsAlive())
{
aiContainer.InternalDie(tick, despawnTime);
aiContainer.pathFind.Clear();
this.ResetMoveSpeeds();
this.positionX = oldPositionX;
this.positionY = oldPositionY;
this.positionZ = oldPositionZ;
this.isAtSpawn = true;
}
else
@ -176,25 +190,13 @@ namespace FFXIVClassic_Map_Server.Actors
else
{
if (target == null && !aiContainer.pathFind.IsFollowingPath())
aiContainer.pathFind.PathInRange(spawnX, spawnY, spawnZ, 1.0f, 15.0f);
aiContainer.pathFind.PathInRange(spawnX, spawnY, spawnZ, 1.5f, 15.0f);
}
}
else
{
this.isMovingToSpawn = false;
}
// dont bother checking for any in-range players if going back to spawn
if (!this.isMovingToSpawn && this.aiContainer.pathFind.AtPoint() && this.aggroType != AggroType.None)
{
foreach (var player in zone.GetActorsAroundActor<Player>(this, 50))
{
uint levelDifference = (uint)Math.Abs(this.charaWork.parameterSave.state_mainSkillLevel - player.charaWork.parameterSave.state_mainSkillLevel);
if (levelDifference < 10 && ((BattleNpcController)aiContainer.GetController()).CanAggroTarget(player))
hateContainer.AddBaseHate(player);
}
}
}
public bool IsCloseToSpawn()
@ -202,17 +204,9 @@ namespace FFXIVClassic_Map_Server.Actors
return this.isAtSpawn = Utils.DistanceSquared(positionX, positionY, positionZ, spawnX, spawnY, spawnZ) <= 2500.0f;
}
public override void OnAttack(State state, BattleAction action)
public override void OnAttack(State state, BattleAction action, ref SubPacket errorPacket)
{
// player melee animation
uint battleAnimation = 0x19001000;
// todo: get hitrate and shit, handle protect effect and whatever
BattleUtils.TryAttack(this, state.GetTarget(), action);
zone.BroadcastPacketAroundActor(this,
BattleActionX01Packet.BuildPacket(actorId, actorId, target.actorId, (uint)battleAnimation,
(uint)action.effectId, (ushort)action.worldMasterTextId, (ushort)BattleActionX01PacketCommand.Attack, (ushort)action.amount, (byte)action.param)
);
base.OnAttack(state, action, ref errorPacket);
}
}
}

View file

@ -1894,7 +1894,7 @@ namespace FFXIVClassic_Map_Server.Actors
charaWork.commandCategory[trueHotbarSlot] = 1;
//Set recast time
ushort maxRecastTime = (ushort)Server.GetWorldManager().GetAbility(commandId).recastTimeSeconds;
ushort maxRecastTime = (ushort)Server.GetWorldManager().GetBattleCommand(commandId).recastTimeSeconds;
uint recastEnd = Utils.UnixTimeStampUTC() + maxRecastTime;
charaWork.parameterTemp.maxCommandRecastTime[trueHotbarSlot - charaWork.commandBorder] = maxRecastTime;
charaWork.parameterSave.commandSlot_recastTime[trueHotbarSlot - charaWork.commandBorder] = recastEnd;
@ -2064,23 +2064,20 @@ namespace FFXIVClassic_Map_Server.Actors
return true;
}
public override void OnAttack(State state, BattleAction action)
public override void OnAttack(State state, BattleAction action, ref SubPacket errorPacket)
{
// melee attack animation
uint battleAnimation = 0x19001000;
// todo: change animation based on equipped weapon
action.effectId |= (uint)HitEffect.HitVisual1; // melee
// todo: get hitrate and shit, handle protect effect and whatever
BattleUtils.TryAttack(this, state.GetTarget(), action);
action.amount = BattleUtils.CalculateAttackDamage(this, state.GetTarget(), action);
//var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1);
zone.BroadcastPacketAroundActor(this, BattleActionX01Packet.BuildPacket(actorId, actorId, action.targetId, (uint)battleAnimation,
0x8000000 | action.effectId, (ushort)action.worldMasterTextId, (ushort)BattleActionX01PacketCommand.Attack, (ushort)action.amount, (byte)action.param)
);
base.OnAttack(state, action, ref errorPacket);
if (errorPacket == null)
{
// melee attack animation
action.animation = 0x19001000;
}
var target = state.GetTarget();
//if (target is BattleNpc)
{
((BattleNpc)target).hateContainer.UpdateHate(this, action.amount);
}
}
}
}

View file

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FFXIVClassic_Map_Server.actors.chara.ai.controllers;
namespace FFXIVClassic_Map_Server.dataobjects
{

View file

@ -1,7 +1,102 @@
using FFXIVClassic.Common;
using System;
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
{
//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.
//The recoil that gets set was likely based on the percentage of HP lost from the attack.
RecoilLv1 = 0,
RecoilLv2 = 1 << 0,
RecoilLv3 = 1 << 1,
//Setting both recoil flags triggers the "Critical!" pop-up text and hit visual effect.
CriticalHit = RecoilLv2 | RecoilLv3,
//Hit visual and sound effects when connecting with the target.
//Mixing these flags together will yield different results.
//Each visual likely relates to a specific weapon.
//Ex: HitVisual4 flag alone appears to be the visual and sound effect for hand-to-hand attacks.
HitVisual1 = 1 << 2,
HitVisual2 = 1 << 3,
HitVisual3 = 1 << 4,
HitVisual4 = 1 << 5,
//An additional visual effect that plays on the target when attacked if:
//The attack is physical and they have the protect buff on.
//The attack is magical and they have the shell buff on.
//Special Note: Shell was removed in later versions of the game.
//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,
Shell = 1 << 7,
ProtectShellSpecial = Protect | Shell,
//Unknown = 1 << 8, -- Not sure what this flag does.
//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,
HitEffect2 = 1 << 10, //Plays the standard hit visual effect, but with no sound if used alone.
Hit = HitEffect1 | HitEffect2, //A standard hit effect with sound effect.
HitEffect3 = 1 << 11,
HitEffect4 = 1 << 12,
HitEffect5 = 1 << 13,
GustyHitEffect = HitEffect3 | HitEffect2,
GreenTintedHitEffect = HitEffect4 | HitEffect1,
//Knocks you back away from the attacker.
KnockbackLv1 = HitEffect4 | HitEffect2 | HitEffect1,
KnockbackLv2 = HitEffect4 | HitEffect3,
KnockbackLv3 = HitEffect4 | HitEffect3 | HitEffect1,
KnockbackLv4 = HitEffect4 | HitEffect3 | HitEffect2,
KnockbackLv5 = HitEffect4 | HitEffect3 | HitEffect2 | HitEffect1,
//Knocks you away from the attacker in a counter-clockwise direction.
KnockbackCounterClockwiseLv1 = HitEffect5,
KnockbackCounterClockwiseLv2 = HitEffect5 | HitEffect1,
//Knocks you away from the attacker in a clockwise direction.
KnockbackClockwiseLv1 = HitEffect5 | HitEffect2,
KnockbackClockwiseLv2 = HitEffect5 | HitEffect2 | HitEffect1,
//Completely drags target to the attacker, even across large distances.
DrawIn = HitEffect5 | HitEffect3,
//An additional visual effect that plays on the target based on according buff.
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
//Flags beyond here are unknown/untested.
}
//Mixing some of these flags will cause the client to crash.
//Setting a flag higher than Left (0x10-0x80) will cause the client to crash.
[Flags]
public enum HitDirection : byte
{
None = 0,
Front = 1 << 0,
Right = 1 << 1,
Rear = 1 << 2,
Left = 1 << 3
}
class BattleAction
{
public uint targetId;
@ -10,5 +105,10 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
public uint effectId;
public byte param;
public byte unknown;
/// <summary>
/// this field is not actually part of the packet struct
/// </summary>
public uint animation;
}
}

View file

@ -11,88 +11,6 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
Attack = 22104,
}
//These flags can be stacked and mixed, but the client will prioritize certain flags over others.
[Flags]
public enum HitEffect : uint
{
//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.
//The recoil that gets set was likely based on the percentage of HP lost from the attack.
RecoilLv1 = 0,
RecoilLv2 = 1 << 0,
RecoilLv3 = 1 << 1,
//Setting both recoil flags triggers the "Critical!" pop-up text and hit visual effect.
CriticalHit = RecoilLv2 | RecoilLv3,
//Hit visual and sound effects when connecting with the target.
//Mixing these flags together will yield different results.
//Each visual likely relates to a specific weapon.
//Ex: HitVisual4 flag alone appears to be the visual and sound effect for hand-to-hand attacks.
HitVisual1 = 1 << 2,
HitVisual2 = 1 << 3,
HitVisual3 = 1 << 4,
HitVisual4 = 1 << 5,
//An additional visual effect that plays on the target when attacked if:
//The attack is physical and they have the protect buff on.
//The attack is magical and they have the shell buff on.
//Special Note: Shell was removed in later versions of the game.
//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,
Shell = 1 << 7,
ProtectShellSpecial = Protect | Shell,
//Unknown = 1 << 8, -- Not sure what this flag does.
//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,
HitEffect2 = 1 << 10, //Plays the standard hit visual effect, but with no sound if used alone.
Hit = HitEffect1 | HitEffect2, //A standard hit effect with sound effect.
HitEffect3 = 1 << 11,
HitEffect4 = 1 << 12,
HitEffect5 = 1 << 13,
GustyHitEffect = HitEffect3 | HitEffect2,
GreenTintedHitEffect = HitEffect4 | HitEffect1,
//Knocks you back away from the attacker.
KnockbackLv1 = HitEffect4 | HitEffect2 | HitEffect1,
KnockbackLv2 = HitEffect4 | HitEffect3,
KnockbackLv3 = HitEffect4 | HitEffect3 | HitEffect1,
KnockbackLv4 = HitEffect4 | HitEffect3 | HitEffect2,
KnockbackLv5 = HitEffect4 | HitEffect3 | HitEffect2 | HitEffect1,
//Knocks you away from the attacker in a counter-clockwise direction.
KnockbackCounterClockwiseLv1 = HitEffect5,
KnockbackCounterClockwiseLv2 = HitEffect5 | HitEffect1,
//Knocks you away from the attacker in a clockwise direction.
KnockbackClockwiseLv1 = HitEffect5 | HitEffect2,
KnockbackClockwiseLv2 = HitEffect5 | HitEffect2 | HitEffect1,
//Completely drags target to the attacker, even across large distances.
DrawIn = HitEffect5 | HitEffect3,
//An additional visual effect that plays on the target based on according buff.
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
//Flags beyond here are unknown/untested.
}
class BattleActionX01Packet
{
public const ushort OPCODE = 0x0139;