mirror of
				https://bitbucket.org/Ioncannon/project-meteor-server.git
				synced 2025-05-20 08:26:59 -04:00 
			
		
		
		
	stubbed some more states
- stubbed some ability stuff - moved packet things to loop instead of session only - added mob roaming and aggro - todo: fix target find/detection/pathfinding speed/line of sight/line aoe length etc - todo: see "// todo:" in code
This commit is contained in:
		| @@ -73,8 +73,13 @@ namespace FFXIVClassic.Common | |||||||
|  |  | ||||||
|         public static float GetAngle(Vector3 lhs, Vector3 rhs) |         public static float GetAngle(Vector3 lhs, Vector3 rhs) | ||||||
|         { |         { | ||||||
|             var angle = (float)Math.Atan((rhs.Z - lhs.Z) / (rhs.X - lhs.X)); |             return GetAngle(lhs.X, lhs.Z, rhs.X, rhs.Z);  | ||||||
|             return lhs.X > rhs.X ? angle + (float)Math.PI : angle;  |         } | ||||||
|  |  | ||||||
|  |         public static float GetAngle(float x, float z, float x2, float z2) | ||||||
|  |         { | ||||||
|  |             var angle = (float)Math.Atan((z2 - z) / (x2 - x)); | ||||||
|  |             return x > x2 ? angle + (float)Math.PI : angle; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Vector3 NewHorizontalVector(float angle, float extents) |         public Vector3 NewHorizontalVector(float angle, float extents) | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ namespace FFXIVClassic_Map_Server | |||||||
|  |  | ||||||
|         internal bool DoCommand(string input, Session session) |         internal bool DoCommand(string input, Session session) | ||||||
|         { |         { | ||||||
|             if (!input.Any() || input.Equals("")) |             if (!input.Any() || input.Equals("") || input.Length == 1) | ||||||
|                 return false; |                 return false; | ||||||
|  |  | ||||||
|             input.Trim(); |             input.Trim(); | ||||||
|   | |||||||
| @@ -1833,6 +1833,66 @@ namespace FFXIVClassic_Map_Server | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public static Dictionary<ushort, Ability> LoadGlobalAbilityList() | ||||||
|  |         { | ||||||
|  |             var abilities = new Dictionary<ushort, Ability>(); | ||||||
|  |  | ||||||
|  |             using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) | ||||||
|  |             { | ||||||
|  |                 try | ||||||
|  |                 { | ||||||
|  |                     conn.Open(); | ||||||
|  |  | ||||||
|  |                     var query = ("SELECT id, name, classJob, level, requirements, validTarget, aoeTarget, aoeType, `range`, characterFind, statusDuration, " + | ||||||
|  |                         "castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, positionBonus, procRequirement FROM abilities;"); | ||||||
|  |  | ||||||
|  |                     MySqlCommand cmd = new MySqlCommand(query, conn); | ||||||
|  |  | ||||||
|  |                     using (MySqlDataReader reader = cmd.ExecuteReader()) | ||||||
|  |                     { | ||||||
|  |                         while (reader.Read()) | ||||||
|  |                         { | ||||||
|  |                             var id = reader.GetUInt16(0); | ||||||
|  |                             var name = reader.GetString(1); | ||||||
|  |                             var ability = new Ability(id, name); | ||||||
|  |  | ||||||
|  |                             ability.job = reader.GetByte(2); | ||||||
|  |                             ability.level = reader.GetByte(3); | ||||||
|  |                             ability.requirements = (AbilityRequirements)reader.GetUInt16(4); | ||||||
|  |                             ability.validTarget = (TargetFindFlags)reader.GetByte(5); | ||||||
|  |                             ability.aoeTarget = (TargetFindAOETarget)reader.GetByte(6); | ||||||
|  |                             ability.aoeType = (TargetFindAOEType)reader.GetByte(7); | ||||||
|  |                             ability.range = reader.GetInt32(8); | ||||||
|  |                             ability.characterFind = (TargetFindCharacterType)reader.GetByte(9); | ||||||
|  |                             ability.statusDurationSeconds = reader.GetUInt32(10); | ||||||
|  |                             ability.castTimeSeconds = reader.GetUInt32(11); | ||||||
|  |                             ability.recastTimeSeconds = reader.GetUInt32(12); | ||||||
|  |                             ability.mpCost = reader.GetUInt16(13); | ||||||
|  |                             ability.tpCost = reader.GetUInt16(14); | ||||||
|  |                             ability.animationType = reader.GetByte(15); | ||||||
|  |                             ability.effectAnimation = reader.GetUInt16(16); | ||||||
|  |                             ability.modelAnimation = reader.GetUInt16(17); | ||||||
|  |                             ability.animationDurationSeconds = reader.GetUInt16(18); | ||||||
|  |                             ability.positionBonus = (AbilityPositionBonus)reader.GetByte(19); | ||||||
|  |                             ability.procRequirement = (AbilityProcRequirement)reader.GetByte(20); | ||||||
|  |                              | ||||||
|  |                             abilities.Add(id, ability); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 catch (MySqlException e) | ||||||
|  |                 { | ||||||
|  |                     Program.Log.Error(e.ToString()); | ||||||
|  |                 } | ||||||
|  |                 finally | ||||||
|  |                 { | ||||||
|  |                     conn.Dispose(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return abilities; | ||||||
|  |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -93,12 +93,16 @@ | |||||||
|     <Compile Include="actors\chara\ai\controllers\PlayerController.cs" /> |     <Compile Include="actors\chara\ai\controllers\PlayerController.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\HateContainer.cs" /> |     <Compile Include="actors\chara\ai\HateContainer.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\PathFind.cs" /> |     <Compile Include="actors\chara\ai\PathFind.cs" /> | ||||||
|  |     <Compile Include="actors\chara\ai\Ability.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\state\AttackState.cs" /> |     <Compile Include="actors\chara\ai\state\AttackState.cs" /> | ||||||
|  |     <Compile Include="actors\chara\ai\state\DeathState.cs" /> | ||||||
|  |     <Compile Include="actors\chara\ai\state\MagicState.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\state\State.cs" /> |     <Compile Include="actors\chara\ai\state\State.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\StatusEffect.cs" /> |     <Compile Include="actors\chara\ai\StatusEffect.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\StatusEffectContainer.cs" /> |     <Compile Include="actors\chara\ai\StatusEffectContainer.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\TargetFind.cs" /> |     <Compile Include="actors\chara\ai\TargetFind.cs" /> | ||||||
|     <Compile Include="actors\chara\ai\utils\AttackUtils.cs" /> |     <Compile Include="actors\chara\ai\utils\AttackUtils.cs" /> | ||||||
|  |     <Compile Include="actors\chara\ai\utils\BattleUtils.cs" /> | ||||||
|     <Compile Include="actors\chara\npc\ActorClass.cs" /> |     <Compile Include="actors\chara\npc\ActorClass.cs" /> | ||||||
|     <Compile Include="actors\chara\npc\BattleNpc.cs" /> |     <Compile Include="actors\chara\npc\BattleNpc.cs" /> | ||||||
|     <Compile Include="actors\chara\npc\NpcWork.cs" /> |     <Compile Include="actors\chara\npc\NpcWork.cs" /> | ||||||
|   | |||||||
| @@ -55,6 +55,7 @@ namespace FFXIVClassic_Map_Server | |||||||
|             mWorldManager.LoadSpawnLocations(); |             mWorldManager.LoadSpawnLocations(); | ||||||
|             mWorldManager.SpawnAllActors(); |             mWorldManager.SpawnAllActors(); | ||||||
|             mWorldManager.LoadStatusEffects(); |             mWorldManager.LoadStatusEffects(); | ||||||
|  |             mWorldManager.LoadAbilities(); | ||||||
|             mWorldManager.StartZoneThread(); |             mWorldManager.StartZoneThread(); | ||||||
|  |  | ||||||
|             IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), int.Parse(ConfigConstants.OPTIONS_PORT)); |             IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ConfigConstants.OPTIONS_BINDIP), int.Parse(ConfigConstants.OPTIONS_PORT)); | ||||||
|   | |||||||
| @@ -36,7 +36,8 @@ namespace FFXIVClassic_Map_Server | |||||||
|         private Dictionary<uint, ZoneEntrance> zoneEntranceList; |         private Dictionary<uint, ZoneEntrance> zoneEntranceList; | ||||||
|         private Dictionary<uint, ActorClass> actorClasses = new Dictionary<uint,ActorClass>(); |         private Dictionary<uint, ActorClass> actorClasses = new Dictionary<uint,ActorClass>(); | ||||||
|         private Dictionary<ulong, Party> currentPlayerParties = new Dictionary<ulong, Party>(); //GroupId, Party object |         private Dictionary<ulong, Party> currentPlayerParties = new Dictionary<ulong, Party>(); //GroupId, Party object | ||||||
|         private Dictionary<uint, StatusEffect> effectList = new Dictionary<uint, StatusEffect>(); // todo: load these in from a db table |         private Dictionary<uint, StatusEffect> effectList = new Dictionary<uint, StatusEffect>(); | ||||||
|  |         private Dictionary<ushort, Ability> abilityList = new Dictionary<ushort, Ability>(); | ||||||
|  |  | ||||||
|         private Server mServer; |         private Server mServer; | ||||||
|  |  | ||||||
| @@ -586,7 +587,6 @@ namespace FFXIVClassic_Map_Server | |||||||
|             { |             { | ||||||
|                 oldZone.RemoveActorFromZone(player); |                 oldZone.RemoveActorFromZone(player); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             newArea.AddActorToZone(player); |             newArea.AddActorToZone(player); | ||||||
|  |  | ||||||
|             //Update player actor's properties |             //Update player actor's properties | ||||||
| @@ -761,6 +761,8 @@ namespace FFXIVClassic_Map_Server | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void ReloadZone(uint zoneId) |         public void ReloadZone(uint zoneId) | ||||||
|  |         { | ||||||
|  |             lock (zoneList) | ||||||
|             { |             { | ||||||
|                 if (!zoneList.ContainsKey(zoneId)) |                 if (!zoneList.ContainsKey(zoneId)) | ||||||
|                     return; |                     return; | ||||||
| @@ -768,7 +770,7 @@ namespace FFXIVClassic_Map_Server | |||||||
|                 Zone zone = zoneList[zoneId]; |                 Zone zone = zoneList[zoneId]; | ||||||
|                 //zone.clear(); |                 //zone.clear(); | ||||||
|                 //LoadNPCs(zone.actorId); |                 //LoadNPCs(zone.actorId); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public ContentGroup CreateContentGroup(Director director, params Actor[] actors) |         public ContentGroup CreateContentGroup(Director director, params Actor[] actors) | ||||||
| @@ -1038,6 +1040,8 @@ namespace FFXIVClassic_Map_Server | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Actor GetActorInWorld(uint charId) |         public Actor GetActorInWorld(uint charId) | ||||||
|  |         { | ||||||
|  |             lock (zoneList) | ||||||
|             { |             { | ||||||
|                 foreach (Zone zone in zoneList.Values) |                 foreach (Zone zone in zoneList.Values) | ||||||
|                 { |                 { | ||||||
| @@ -1045,10 +1049,13 @@ namespace FFXIVClassic_Map_Server | |||||||
|                     if (a != null) |                     if (a != null) | ||||||
|                         return a; |                         return a; | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Actor GetActorInWorldByUniqueId(string uid) |         public Actor GetActorInWorldByUniqueId(string uid) | ||||||
|  |         { | ||||||
|  |             lock (zoneList) | ||||||
|             { |             { | ||||||
|                 foreach (Zone zone in zoneList.Values) |                 foreach (Zone zone in zoneList.Values) | ||||||
|                 { |                 { | ||||||
| @@ -1056,23 +1063,31 @@ namespace FFXIVClassic_Map_Server | |||||||
|                     if (a != null) |                     if (a != null) | ||||||
|                         return a; |                         return a; | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Zone GetZone(uint zoneId) |         public Zone GetZone(uint zoneId) | ||||||
|  |         { | ||||||
|  |             lock (zoneList) | ||||||
|             { |             { | ||||||
|                 if (!zoneList.ContainsKey(zoneId)) |                 if (!zoneList.ContainsKey(zoneId)) | ||||||
|                     return null; |                     return null; | ||||||
|  |  | ||||||
|                 return zoneList[zoneId]; |                 return zoneList[zoneId]; | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public PrivateArea GetPrivateArea(uint zoneId, string privateArea, uint privateAreaType) |         public PrivateArea GetPrivateArea(uint zoneId, string privateArea, uint privateAreaType) | ||||||
|  |         { | ||||||
|  |             lock (zoneList) | ||||||
|             { |             { | ||||||
|                 if (!zoneList.ContainsKey(zoneId)) |                 if (!zoneList.ContainsKey(zoneId)) | ||||||
|                     return null; |                     return null; | ||||||
|  |  | ||||||
|                 return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType); |                 return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public WorldMaster GetActor() |         public WorldMaster GetActor() | ||||||
|         { |         { | ||||||
| @@ -1135,6 +1150,17 @@ namespace FFXIVClassic_Map_Server | |||||||
|  |  | ||||||
|             return effectList.TryGetValue(id, out effect) ? new StatusEffect(null, effect) : null; |             return effectList.TryGetValue(id, out effect) ? new StatusEffect(null, effect) : null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public void LoadAbilities() | ||||||
|  |         { | ||||||
|  |             abilityList = Database.LoadGlobalAbilityList(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Ability GetAbility(ushort id) | ||||||
|  |         { | ||||||
|  |             Ability ability; | ||||||
|  |             return abilityList.TryGetValue(id, out ability) ? ability.Clone() : null; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,6 +13,22 @@ using FFXIVClassic_Map_Server.packets.send.actor.battle; | |||||||
|  |  | ||||||
| namespace FFXIVClassic_Map_Server.Actors | namespace FFXIVClassic_Map_Server.Actors | ||||||
| { | { | ||||||
|  |     [Flags] | ||||||
|  |     enum ActorUpdateFlags | ||||||
|  |     { | ||||||
|  |         None = 0x00, | ||||||
|  |         Position = 0x01, | ||||||
|  |         HpTpMp = 0x02, | ||||||
|  |         State = 0x04, | ||||||
|  |         Combat = 0x07, | ||||||
|  |         Name = 0x08, | ||||||
|  |         Appearance = 0x10, | ||||||
|  |         Speed = 0x20, | ||||||
|  |  | ||||||
|  |         AllNpc = 0x2F, | ||||||
|  |         AllPlayer = 0x3F | ||||||
|  |     } | ||||||
|  |  | ||||||
|     class Actor |     class Actor | ||||||
|     { |     { | ||||||
|         public uint actorId; |         public uint actorId; | ||||||
| @@ -41,7 +57,7 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|         public string className; |         public string className; | ||||||
|         public List<LuaParam> classParams; |         public List<LuaParam> classParams; | ||||||
|  |  | ||||||
|         public List<Vector3> positionUpdates = new List<Vector3>(); |         public List<Vector3> positionUpdates; | ||||||
|         public DateTime lastMoveUpdate; |         public DateTime lastMoveUpdate; | ||||||
|         protected DateTime lastUpdate; |         protected DateTime lastUpdate; | ||||||
|         public Actor target; |         public Actor target; | ||||||
| @@ -49,6 +65,8 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|         public bool hasMoved = false; |         public bool hasMoved = false; | ||||||
|         public bool isAtSpawn = true; |         public bool isAtSpawn = true; | ||||||
|  |  | ||||||
|  |         public ActorUpdateFlags updateFlags; | ||||||
|  |  | ||||||
|         public EventList eventConditions; |         public EventList eventConditions; | ||||||
|  |  | ||||||
|         public Actor(uint actorId) |         public Actor(uint actorId) | ||||||
| @@ -84,7 +102,7 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void ResetMoveSpeedsToDefault() |         public virtual void ResetMoveSpeeds() | ||||||
|         { |         { | ||||||
|             this.moveSpeeds[0] = SetActorSpeedPacket.DEFAULT_STOP; |             this.moveSpeeds[0] = SetActorSpeedPacket.DEFAULT_STOP; | ||||||
|             this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK; |             this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK; | ||||||
| @@ -93,7 +111,7 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|  |  | ||||||
|             // todo: make this halal |             // todo: make this halal | ||||||
|             this.moveState = this.oldMoveState; |             this.moveState = this.oldMoveState; | ||||||
|             hasMoved = true; |             this.updateFlags |= ActorUpdateFlags.Speed; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public SubPacket CreateAddActorPacket(byte val) |         public SubPacket CreateAddActorPacket(byte val) | ||||||
| @@ -152,38 +170,10 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|             return spawnPacket; |             return spawnPacket; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public SubPacket CreatePositionUpdatePacket(bool forceUpdate = false) |         public SubPacket CreatePositionUpdatePacket() | ||||||
|         { |         { | ||||||
|             int updateMs = 300; |  | ||||||
|             var diffTime = (DateTime.Now - lastMoveUpdate); |  | ||||||
|  |  | ||||||
|             if (this.target != null) |  | ||||||
|             { |  | ||||||
|                 updateMs = 150; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (forceUpdate || (hasMoved && ((this is Player) || diffTime.TotalMilliseconds >= updateMs))) |  | ||||||
|             { |  | ||||||
|                 hasMoved = (this.positionUpdates != null && this.positionUpdates.Count > 0); |  | ||||||
|                 if (hasMoved) |  | ||||||
|                 { |  | ||||||
|                     var pos = positionUpdates[0]; |  | ||||||
|  |  | ||||||
|                     if (this is Character) |  | ||||||
|                         ((Character)this).OnPath(pos); |  | ||||||
|  |  | ||||||
|                     positionX = pos.X; |  | ||||||
|                     positionY = pos.Y; |  | ||||||
|                     positionZ = pos.Z; |  | ||||||
|                     //Program.Server.GetInstance().mLuaEngine.OnPath(actor, position, positionUpdates) |  | ||||||
|  |  | ||||||
|                     positionUpdates.RemoveAt(0); |  | ||||||
|                 } |  | ||||||
|                 lastMoveUpdate = DateTime.Now; |  | ||||||
|             return MoveActorToPositionPacket.BuildPacket(actorId, positionX, positionY, positionZ, rotation, moveState); |             return MoveActorToPositionPacket.BuildPacket(actorId, positionX, positionY, positionZ, rotation, moveState); | ||||||
|         } |         } | ||||||
|             return null; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public SubPacket CreateStatePacket() |         public SubPacket CreateStatePacket() | ||||||
|         { |         { | ||||||
| @@ -374,19 +364,18 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void ChangeState(ushort newState) |         public void ChangeState(ushort newState) | ||||||
|  |         { | ||||||
|  |             if (newState != currentMainState) | ||||||
|             { |             { | ||||||
|                 currentMainState = newState; |                 currentMainState = newState; | ||||||
|             SubPacket ChangeStatePacket = SetActorStatePacket.BuildPacket(actorId, newState, currentSubState);        |                 updateFlags |= ActorUpdateFlags.State; | ||||||
|             SubPacket battleActionPacket = BattleActionX01Packet.BuildPacket(actorId, actorId, actorId, 0x72000062, 1, 0, 0x05209, 0, 0); |             } | ||||||
|             zone.BroadcastPacketAroundActor(this, ChangeStatePacket); |  | ||||||
|             zone.BroadcastPacketAroundActor(this, battleActionPacket); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void ChangeSpeed(int type, float value) |         public void ChangeSpeed(int type, float value) | ||||||
|         { |         { | ||||||
|             moveSpeeds[type] = value; |             moveSpeeds[type] = value; | ||||||
|             SubPacket ChangeSpeedPacket = SetActorSpeedPacket.BuildPacket(actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3]); |             updateFlags |= ActorUpdateFlags.Speed; | ||||||
|             zone.BroadcastPacketAroundActor(this, ChangeSpeedPacket); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void ChangeSpeed(float speedStop, float speedWalk, float speedRun, float speedActive) |         public void ChangeSpeed(float speedStop, float speedWalk, float speedRun, float speedActive) | ||||||
| @@ -395,8 +384,7 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|             moveSpeeds[1] = speedWalk; |             moveSpeeds[1] = speedWalk; | ||||||
|             moveSpeeds[2] = speedRun; |             moveSpeeds[2] = speedRun; | ||||||
|             moveSpeeds[3] = speedActive; |             moveSpeeds[3] = speedActive; | ||||||
|             SubPacket ChangeSpeedPacket = SetActorSpeedPacket.BuildPacket(actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3]); |             updateFlags |= ActorUpdateFlags.Speed; | ||||||
|             zone.BroadcastPacketAroundActor(this, ChangeSpeedPacket); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public virtual void Update(DateTime tick) |         public virtual void Update(DateTime tick) | ||||||
| @@ -404,6 +392,48 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|  |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public virtual void PostUpdate(DateTime tick, List<SubPacket> packets = null) | ||||||
|  |         { | ||||||
|  |             if (updateFlags != ActorUpdateFlags.None) | ||||||
|  |             { | ||||||
|  |                 packets = packets ?? new List<SubPacket>(); | ||||||
|  |                 if ((updateFlags & ActorUpdateFlags.Position) != 0) | ||||||
|  |                 { | ||||||
|  |                     if (positionUpdates != null && positionUpdates.Count > 0) | ||||||
|  |                     { | ||||||
|  |                         // push latest for player | ||||||
|  |                         var pos = positionUpdates?[currentSubState == SetActorStatePacket.SUB_STATE_PLAYER ? positionUpdates.Count - 1 : 0]; | ||||||
|  |  | ||||||
|  |                         positionX = pos.X; | ||||||
|  |                         positionY = pos.Y; | ||||||
|  |                         positionZ = pos.Z; | ||||||
|  |                         //Program.Server.GetInstance().mLuaEngine.OnPath(actor, position, positionUpdates) | ||||||
|  |  | ||||||
|  |                         positionUpdates.RemoveAt(0); | ||||||
|  |                     } | ||||||
|  |                     lastMoveUpdate = DateTime.Now; | ||||||
|  |                     packets.Add(CreatePositionUpdatePacket()); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if ((updateFlags & ActorUpdateFlags.Speed) != 0) | ||||||
|  |                 { | ||||||
|  |                     packets.Add(SetActorSpeedPacket.BuildPacket(actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3])); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if ((updateFlags & ActorUpdateFlags.Name) != 0) | ||||||
|  |                 { | ||||||
|  |                     packets.Add(SetActorNamePacket.BuildPacket(actorId, displayNameId, customDisplayName)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if ((updateFlags & ActorUpdateFlags.State) != 0) | ||||||
|  |                 { | ||||||
|  |                     packets.Add(SetActorStatePacket.BuildPacket(actorId, currentMainState, currentSubState)); | ||||||
|  |                     packets.Add(BattleActionX01Packet.BuildPacket(actorId, actorId, actorId, 0x72000062, 1, 0, 0x05209, 0, 0)); | ||||||
|  |                 } | ||||||
|  |                 updateFlags = ActorUpdateFlags.None; | ||||||
|  |                 zone.BroadcastPacketsAroundActor(this, packets); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public void GenerateActorName(int actorNumber) |         public void GenerateActorName(int actorNumber) | ||||||
|         { |         { | ||||||
| @@ -518,7 +548,7 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|                         if (value.GetType() == curObj.GetType()) |                         if (value.GetType() == curObj.GetType()) | ||||||
|                             parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, value); |                             parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, value); | ||||||
|                         else |                         else | ||||||
|                             parentObj.GetType().GetField(split[split.Length-1]).SetValue(parentObj, TypeDescriptor.GetConverter(value.GetType()).ConvertTo(value, curObj.GetType())); |                             parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, TypeDescriptor.GetConverter(value.GetType()).ConvertTo(value, curObj.GetType())); | ||||||
|  |  | ||||||
|                         SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc); |                         SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc); | ||||||
|                         changeProperty.AddProperty(this, name); |                         changeProperty.AddProperty(this, name); | ||||||
| @@ -592,27 +622,6 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|             return rot1 == (float)dRot; |             return rot1 == (float)dRot; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // todo: do this properly |  | ||||||
|         public bool IsFacing(Actor target) |  | ||||||
|         { |  | ||||||
|             if (target == null) |  | ||||||
|             { |  | ||||||
|                 Program.Log.Error("[{0}][{1}] IsFacing no target!", actorId, actorName); |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             var rot1 = this.rotation; |  | ||||||
|  |  | ||||||
|             var dX = this.positionX - target.positionX; |  | ||||||
|             var dY = this.positionY - target.positionY; |  | ||||||
|  |  | ||||||
|             var rot2 = Math.Atan2(dY, dX); |  | ||||||
|  |  | ||||||
|             var dRot = Math.PI - rot2 + Math.PI / 2; |  | ||||||
|  |  | ||||||
|             return rot1 == (float)dRot; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         public void LookAt(Actor actor) |         public void LookAt(Actor actor) | ||||||
|         { |         { | ||||||
|             if (actor != null) |             if (actor != null) | ||||||
| @@ -637,19 +646,34 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|             var dRot = Math.PI - rot2 + Math.PI / 2; |             var dRot = Math.PI - rot2 + Math.PI / 2; | ||||||
|  |  | ||||||
|             // pending move, dont need to unset it |             // pending move, dont need to unset it | ||||||
|             if (!hasMoved) |             this.updateFlags = (rotation != (float)dRot) ? updateFlags |= ActorUpdateFlags.Position : updateFlags; | ||||||
|                 hasMoved = rot1 != (float)dRot; |  | ||||||
|  |  | ||||||
|             rotation = (float)dRot; |             rotation = (float)dRot; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public bool IsFacing(float x, float z, float angle = 40.0f) | ||||||
|  |         { | ||||||
|  |             return Vector3.GetAngle(positionX, positionZ, x, z) < angle; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // todo: is this legit? | ||||||
|  |         public bool IsFacing(Actor target, float angle = 40.0f) | ||||||
|  |         { | ||||||
|  |             if (target == null) | ||||||
|  |             { | ||||||
|  |                 Program.Log.Error("[{0}][{1}] IsFacing no target!", actorId, actorName); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             return IsFacing(target.positionX, target.positionY, angle); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public void QueuePositionUpdate(Vector3 pos) |         public void QueuePositionUpdate(Vector3 pos) | ||||||
|         { |         { | ||||||
|             if (positionUpdates == null) |             if (positionUpdates == null) | ||||||
|                 positionUpdates = new List<Vector3>(); |                 positionUpdates = new List<Vector3>(); | ||||||
|  |  | ||||||
|             positionUpdates.Add(pos); |             positionUpdates.Add(pos); | ||||||
|             this.hasMoved = true; |             this.updateFlags |= ActorUpdateFlags.Position; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void QueuePositionUpdate(float x, float y, float z) |         public void QueuePositionUpdate(float x, float y, float z) | ||||||
| @@ -662,15 +686,27 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|             positionUpdates.Clear(); |             positionUpdates.Clear(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Vector3 FindRandomPointAroundActor(float minRadius, float maxRadius) |         public Vector3 FindRandomPoint(float x, float y, float z, float minRadius, float maxRadius) | ||||||
|         { |         { | ||||||
|             var angle = Program.Random.NextDouble() * Math.PI * 2; |             var angle = Program.Random.NextDouble() * Math.PI * 2; | ||||||
|             var radius = Math.Sqrt(Program.Random.NextDouble() * (maxRadius - minRadius)) + minRadius; |             var radius = Math.Sqrt(Program.Random.NextDouble() * (maxRadius - minRadius)) + minRadius; | ||||||
|  |  | ||||||
|             float x = (float)(radius * Math.Cos(angle)); |             return new Vector3(x + (float)(radius * Math.Cos(angle)), y, z + (float)(radius * Math.Sin(angle))); | ||||||
|             float z = (float)(radius * Math.Sin(angle)); |         } | ||||||
|  |  | ||||||
|             return new Vector3(positionX + x, positionY, positionZ + z); |         public Vector3 FindRandomPointAroundTarget(Actor target, float minRadius, float maxRadius) | ||||||
|  |         { | ||||||
|  |             if (target == null) | ||||||
|  |             { | ||||||
|  |                 Program.Log.Error($"[{this.actorId}][{this.customDisplayName}] FindRandomPointAroundTarget: no target found!"); | ||||||
|  |                 return GetPosAsVector3(); | ||||||
|  |             } | ||||||
|  |             return FindRandomPoint(target.positionX, target.positionY, target.positionZ, minRadius, maxRadius); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Vector3 FindRandomPointAroundActor(float minRadius, float maxRadius) | ||||||
|  |         { | ||||||
|  |             return FindRandomPoint(positionX, positionY, positionZ, minRadius, maxRadius); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Player GetAsPlayer() |         public Player GetAsPlayer() | ||||||
|   | |||||||
| @@ -71,13 +71,17 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|         public Group currentParty = null; |         public Group currentParty = null; | ||||||
|         public ContentGroup currentContentGroup = null; |         public ContentGroup currentContentGroup = null; | ||||||
|  |  | ||||||
|         public DateTime lastAiUpdate; |         //public DateTime lastAiUpdate; | ||||||
|  |  | ||||||
|         public AIContainer aiContainer; |         public AIContainer aiContainer; | ||||||
|         public StatusEffectContainer statusEffects; |         public StatusEffectContainer statusEffects; | ||||||
|  |         public float meleeRange; | ||||||
|  |         protected uint attackDelayMs; | ||||||
|  |  | ||||||
|         public CharacterTargetingAllegiance allegiance; |         public CharacterTargetingAllegiance allegiance; | ||||||
|  |  | ||||||
|  |         public Pet pet; | ||||||
|  |  | ||||||
|         public Character(uint actorID) : base(actorID) |         public Character(uint actorID) : base(actorID) | ||||||
|         {             |         {             | ||||||
|             //Init timer array to "notimer" |             //Init timer array to "notimer" | ||||||
| @@ -85,6 +89,11 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|                 charaWork.statusShownTime[i] = 0xFFFFFFFF; |                 charaWork.statusShownTime[i] = 0xFFFFFFFF; | ||||||
|  |  | ||||||
|             this.statusEffects = new StatusEffectContainer(this); |             this.statusEffects = new StatusEffectContainer(this); | ||||||
|  |  | ||||||
|  |             // todo: move this somewhere more appropriate | ||||||
|  |             attackDelayMs = 4200; | ||||||
|  |             meleeRange = 2.5f; | ||||||
|  |             ResetMoveSpeeds(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public SubPacket CreateAppearancePacket() |         public SubPacket CreateAppearancePacket() | ||||||
| @@ -153,45 +162,7 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|  |  | ||||||
|         public void PathTo(float x, float y, float z, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f) |         public void PathTo(float x, float y, float z, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f) | ||||||
|         { |         { | ||||||
|             var pos = new Vector3(positionX, positionY, positionZ); |             aiContainer?.pathFind?.PreparePath(x, y, z, stepSize, maxPath, polyRadius); | ||||||
|             var dest = new Vector3(x, y, z); |  | ||||||
|  |  | ||||||
|             var sw = new System.Diagnostics.Stopwatch(); |  | ||||||
|             sw.Start(); |  | ||||||
|  |  | ||||||
|             var path = utils.NavmeshUtils.GetPath(((Zone)GetZone()), pos, dest, stepSize, maxPath, polyRadius); |  | ||||||
|  |  | ||||||
|             if (path != null) |  | ||||||
|             { |  | ||||||
|                 if (oldPositionX == 0.0f && oldPositionY == 0.0f && oldPositionZ == 0.0f) |  | ||||||
|                 { |  | ||||||
|                     oldPositionX = positionX; |  | ||||||
|                     oldPositionY = positionY; |  | ||||||
|                     oldPositionZ = positionZ; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // todo: something went wrong |  | ||||||
|                 if (path.Count == 0) |  | ||||||
|                 { |  | ||||||
|                     positionX = oldPositionX; |  | ||||||
|                     positionY = oldPositionY; |  | ||||||
|                     positionZ = oldPositionZ; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 positionUpdates = path; |  | ||||||
|  |  | ||||||
|                 this.hasMoved = true; |  | ||||||
|                 this.isAtSpawn = false; |  | ||||||
|  |  | ||||||
|                 sw.Stop(); |  | ||||||
|                 ((Zone)zone).pathCalls++; |  | ||||||
|                 ((Zone)zone).pathCallTime += sw.ElapsedMilliseconds; |  | ||||||
|  |  | ||||||
|                 if (path.Count == 1) |  | ||||||
|                     Program.Log.Info($"mypos: {positionX} {positionY} {positionZ} | targetPos: {x} {y} {z} | step {stepSize} | maxPath {maxPath} | polyRadius {polyRadius}"); |  | ||||||
|  |  | ||||||
|                 Program.Log.Error("[{0}][{1}] Created {2} points in {3} milliseconds", actorId, actorName, path.Count, sw.ElapsedMilliseconds); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void FollowTarget(Actor target, float stepSize = 1.2f, int maxPath = 25, float radius = 0.0f) |         public void FollowTarget(Actor target, float stepSize = 1.2f, int maxPath = 25, float radius = 0.0f) | ||||||
| @@ -204,204 +175,118 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|                 { |                 { | ||||||
|                     this.target = target; |                     this.target = target; | ||||||
|                 } |                 } | ||||||
|                 this.moveState = player.moveState; |                 // todo: move this to own function thing | ||||||
|                 this.moveSpeeds = player.moveSpeeds; |                 this.oldMoveState = this.moveState; | ||||||
|  |                 this.moveState = 2; | ||||||
|  |                 updateFlags |= ActorUpdateFlags.Position | ActorUpdateFlags.Speed; | ||||||
|  |                 //this.moveSpeeds = player.moveSpeeds; | ||||||
|  |  | ||||||
|                 PathTo(player.positionX, player.positionY, player.positionZ, stepSize, maxPath, radius); |                 PathTo(player.positionX, player.positionY, player.positionZ, stepSize, maxPath, radius); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void OnPath(Vector3 point) |         public virtual void OnPath(Vector3 point) | ||||||
|         { |         { | ||||||
|             if (positionUpdates != null && positionUpdates.Count > 0) |             lua.LuaEngine.CallLuaBattleAction(this, "onPath", this, point); | ||||||
|             { |  | ||||||
|                 if (point == positionUpdates[positionUpdates.Count - 1]) |             updateFlags |= ActorUpdateFlags.Position; | ||||||
|                 { |             this.isAtSpawn = false; | ||||||
|                     var myPos = new Vector3(positionX, positionY, positionZ); |  | ||||||
|                     //point = NavmeshUtils.GetPath((Zone)zone, myPos, point, 0.35f, 1, 0.000001f, true)?[0]; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Update(DateTime tick) |         public override void Update(DateTime tick) | ||||||
|         { |         { | ||||||
|             // todo: actual ai controllers |  | ||||||
|             // todo: mods to control different params instead of hardcode |  | ||||||
|             // todo: other ai helpers |  | ||||||
|  |  | ||||||
|             // time elapsed since last ai update |  | ||||||
|  |  | ||||||
|             this.aiContainer?.Update(tick); |  | ||||||
|      |  | ||||||
|             /* |  | ||||||
|             var diffTime = (tick - lastAiUpdate); |  | ||||||
|  |  | ||||||
|             if (this is Player) |  | ||||||
|             { |  | ||||||
|                 // todo: handle player stuff here |  | ||||||
|         } |         } | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 // todo: handle mobs only? |  | ||||||
|                 //if (this.isStatic) |  | ||||||
|                 //    return; |  | ||||||
|  |  | ||||||
|                 // todo: this too |         public override void PostUpdate(DateTime tick, List<SubPacket> packets = null) | ||||||
|                 if (diffTime.Milliseconds >= 10) |  | ||||||
|         { |         { | ||||||
|                     bool foundActor = false; |             if (updateFlags != ActorUpdateFlags.None) | ||||||
|  |             { | ||||||
|  |                 packets = packets ?? new List<SubPacket>(); | ||||||
|                  |                  | ||||||
|                     // leash back to spawn |                 if ((updateFlags & ActorUpdateFlags.Appearance) != 0) | ||||||
|                     if (!isMovingToSpawn && this.oldPositionX != 0.0f && this.oldPositionY != 0.0f && this.oldPositionZ != 0.0f) |  | ||||||
|                 { |                 { | ||||||
|                         //var spawnDistanceSq = Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ); |                     packets.Add(new SetActorAppearancePacket(modelId, appearanceIds).BuildPacket(actorId)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                         // todo: actual spawn leash and modifiers read from table |                 // todo: should probably add another flag for battleTemp since all this uses reflection | ||||||
|                         // set a leash to path back to spawn even if have target |                 if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0) | ||||||
|                         // (50 yalms) |  | ||||||
|                         if (Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ) >= 3025) |  | ||||||
|                 { |                 { | ||||||
|                             this.isMovingToSpawn = true; |                     var propPacketUtil = new ActorPropertyPacketUtil("charaWork.parameterSave", this); | ||||||
|                             this.target = null; |  | ||||||
|                             this.lastMoveUpdate = this.lastMoveUpdate.AddSeconds(-5); |                     //Parameters | ||||||
|                             this.hasMoved = false; |  | ||||||
|                             ClearPositionUpdates(); |                     propPacketUtil.AddProperty("charaWork.parameterSave.hp[0]"); | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.parameterSave.hpMax[0]"); | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.parameterSave.mp"); | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.parameterSave.mpMax"); | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.parameterTemp.tp"); | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkill[0]"); | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkillLevel"); | ||||||
|  |  | ||||||
|  |                     //General Parameters | ||||||
|  |                     for (int i = 3; i < charaWork.battleTemp.generalParameter.Length; i++) | ||||||
|  |                     { | ||||||
|  |                         if (charaWork.battleTemp.generalParameter[i] != 0) | ||||||
|  |                             propPacketUtil.AddProperty(String.Format("charaWork.battleTemp.generalParameter[{0}]", i)); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[0]"); | ||||||
|  |                     propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[1]"); | ||||||
|  |                     packets.AddRange(propPacketUtil.Done()); | ||||||
|  |                 } | ||||||
|  |                 base.PostUpdate(tick, packets); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|                     // check if player |         public virtual bool CanAttack() | ||||||
|                     if (target != null && target is Player) |  | ||||||
|         { |         { | ||||||
|                         var player = target as Player; |             return false; | ||||||
|  |  | ||||||
|                         // deaggro if zoning/logging |  | ||||||
|                         // todo: player.isZoning seems to be busted |  | ||||||
|                         if (player.playerSession.isUpdatesLocked) |  | ||||||
|                         { |  | ||||||
|                             target = null; |  | ||||||
|                             ClearPositionUpdates(); |  | ||||||
|                         } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|                     Player closestPlayer = null; |         public virtual bool CanCast() | ||||||
|                     float closestPlayerDistanceSq = 1000.0f; |  | ||||||
|  |  | ||||||
|                     // dont bother checking for any in-range players if going back to spawn |  | ||||||
|                     if (!this.isMovingToSpawn) |  | ||||||
|         { |         { | ||||||
|                         foreach (var actor in zone.GetActorsAroundActor(this, 65)) |             return false; | ||||||
|                         { |  | ||||||
|                             if (actor is Player && actor != this) |  | ||||||
|                             { |  | ||||||
|                                 var player = actor as Player; |  | ||||||
|  |  | ||||||
|                                 // skip if zoning/logging |  | ||||||
|                                 // todo: player.isZoning seems to be busted |  | ||||||
|                                 if (player != null && player.playerSession.isUpdatesLocked) |  | ||||||
|                                     continue; |  | ||||||
|  |  | ||||||
|                                 // find distance between self and target |  | ||||||
|                                 var distanceSq = Utils.DistanceSquared(positionX, positionY, positionZ, player.positionX, player.positionY, player.positionZ); |  | ||||||
|  |  | ||||||
|                                 int maxDistanceSq = player == target ? 900 : 100; |  | ||||||
|  |  | ||||||
|                                 // check target isnt too far |  | ||||||
|                                 // todo: create cone thing for IsFacing |  | ||||||
|                                 if (distanceSq <= maxDistanceSq && distanceSq <= closestPlayerDistanceSq && (IsFacing(player) || true)) |  | ||||||
|                                 { |  | ||||||
|                                     closestPlayerDistanceSq = distanceSq; |  | ||||||
|                                     closestPlayer = player; |  | ||||||
|                                     foundActor = true; |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|                         // found a target |         public virtual uint GetAttackDelayMs() | ||||||
|                         if (foundActor) |  | ||||||
|         { |         { | ||||||
|                             // make sure we're not already moving so we dont spam packets |             return attackDelayMs; | ||||||
|                             if (!hasMoved) |  | ||||||
|                             { |  | ||||||
|                                 // todo: include model size and mob specific distance checks |  | ||||||
|                                 if (closestPlayerDistanceSq >= 9) |  | ||||||
|                                 { |  | ||||||
|                                     FollowTarget(closestPlayer, 2.5f, 4); |  | ||||||
|                                 } |  | ||||||
|                                 // too close, spread out |  | ||||||
|                                 else if (closestPlayerDistanceSq <= 0.85f) |  | ||||||
|                                 { |  | ||||||
|                                     QueuePositionUpdate(target.FindRandomPointAroundActor(0.65f, 0.85f)); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|                                 // we have a target, face them |         public bool Engage(uint targid = 0) | ||||||
|                                 if (target != null) |  | ||||||
|         { |         { | ||||||
|                                     LookAt(target); |             // todo: attack the things | ||||||
|                                 } |             targid = targid == 0 ? currentTarget: targid; | ||||||
|                             } |             if (targid != 0) | ||||||
|  |             { | ||||||
|  |                 var targ = Server.GetWorldManager().GetActorInWorld(targid); | ||||||
|  |                 if (targ is Character) | ||||||
|  |                     aiContainer.Engage((Character)targ); | ||||||
|             } |             } | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|                     // time elapsed since last move update |         public bool Disengage() | ||||||
|                     var diffMove = (tick - lastMoveUpdate); |         { | ||||||
|  |             if (aiContainer != null) | ||||||
|  |             { | ||||||
|  |                 aiContainer.Disengage(); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|                     // todo: modifier for DelayBeforeRoamToSpawn |  | ||||||
|                     // player disappeared |  | ||||||
|                     if (!foundActor && diffMove.Seconds >= 5) |  | ||||||
|                     { |  | ||||||
|                         // dont path if havent moved before |  | ||||||
|                         if (!hasMoved && oldPositionX != 0.0f && oldPositionY != 0.0f && oldPositionZ != 0.0f) |  | ||||||
|                         { |  | ||||||
|                             // check within spawn radius |  | ||||||
|                             this.isAtSpawn = Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ) <= 625.0f; |  | ||||||
|  |  | ||||||
|                             // make sure we have no target |  | ||||||
|                             if (this.target == null) |  | ||||||
|                             { |  | ||||||
|                                 // path back to spawn |  | ||||||
|                                 if (!this.isAtSpawn) |  | ||||||
|                                 { |  | ||||||
|                                     PathTo(oldPositionX, oldPositionY, oldPositionZ, 2.8f); |  | ||||||
|                                 } |  | ||||||
|                                 // within spawn range, find a random point |  | ||||||
|                                 else if (diffMove.Seconds >= 15) |  | ||||||
|                                 { |  | ||||||
|                                     // todo: polyRadius isnt euclidean distance.. |  | ||||||
|                                     // pick a random point within 10 yalms of spawn |  | ||||||
|                                     PathTo(oldPositionX, oldPositionY, oldPositionZ, 2.5f, 7, 2.5f); |  | ||||||
|  |  | ||||||
|                                     // face destination |  | ||||||
|                                     if (positionUpdates.Count > 0) |  | ||||||
|                                     { |  | ||||||
|                                         var destinationPos = positionUpdates[positionUpdates.Count - 1]; |  | ||||||
|                                         LookAt(destinationPos.X, destinationPos.Y); |  | ||||||
|                                     } |  | ||||||
|                                     if (this.isMovingToSpawn) |  | ||||||
|                                     { |  | ||||||
|                                         this.isMovingToSpawn = false; |  | ||||||
|                                         this.ResetMoveSpeedsToDefault(); |  | ||||||
|                                         this.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD2); |  | ||||||
|                                     } |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         // todo: this is retarded. actually no it isnt, i didnt deaggro if out of range.. |  | ||||||
|                         target = null; |  | ||||||
|                     } |  | ||||||
|                     // update last ai update time to now |  | ||||||
|                     lastAiUpdate = DateTime.Now; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             */ |  | ||||||
|         } |  | ||||||
|         public virtual void Spawn(DateTime tick) |         public virtual void Spawn(DateTime tick) | ||||||
|         { |         { | ||||||
|  |             // todo: reset hp/mp/tp etc here | ||||||
|  |             RecalculateHpMpTp(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public virtual void Die(DateTime tick) |         public virtual void Die(DateTime tick) | ||||||
|         { |         { | ||||||
|  |             // todo: actual despawn timer | ||||||
|  |             aiContainer.InternalDie(tick, 10); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         protected virtual void Despawn(DateTime tick) |         protected virtual void Despawn(DateTime tick) | ||||||
| @@ -418,6 +303,54 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|         { |         { | ||||||
|             return !IsDead(); |             return !IsDead(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public virtual short GetHP() | ||||||
|  |         { | ||||||
|  |             // todo:  | ||||||
|  |             return charaWork.parameterSave.hp[0]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public virtual short GetMaxHP() | ||||||
|  |         { | ||||||
|  |             return charaWork.parameterSave.hpMax[0]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public virtual byte GetHPP() | ||||||
|  |         { | ||||||
|  |             return (byte)(charaWork.parameterSave.hp[0] / charaWork.parameterSave.hpMax[0]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public virtual void AddHP(short hp) | ||||||
|  |         { | ||||||
|  |             // todo: +/- hp and die | ||||||
|  |             // todo: battlenpcs probably have way more hp? | ||||||
|  |             var addHp = charaWork.parameterSave.hp[0] + hp; | ||||||
|  |             addHp = addHp.Clamp(short.MinValue, charaWork.parameterSave.hpMax[0]); | ||||||
|  |             charaWork.parameterSave.hp[0] = (short)addHp; | ||||||
|  |  | ||||||
|  |             if (charaWork.parameterSave.hp[0] < 1) | ||||||
|  |                 Die(Program.Tick); | ||||||
|  |  | ||||||
|  |             updateFlags |= ActorUpdateFlags.HpTpMp; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public virtual void DelHP(short hp) | ||||||
|  |         { | ||||||
|  |             AddHP((short)-hp); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // todo: should this include stats too? | ||||||
|  |         public virtual void RecalculateHpMpTp() | ||||||
|  |         { | ||||||
|  |             // todo: recalculate stats and crap | ||||||
|  |             updateFlags |= ActorUpdateFlags.HpTpMp; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public virtual float GetSpeed() | ||||||
|  |         { | ||||||
|  |             // todo: for battlenpc/player calculate speed | ||||||
|  |             return moveSpeeds[2]; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         private Stack<State> states; |         private Stack<State> states; | ||||||
|         private DateTime latestUpdate; |         private DateTime latestUpdate; | ||||||
|         private DateTime prevUpdate; |         private DateTime prevUpdate; | ||||||
|         private PathFind pathFind; |         public readonly PathFind pathFind; | ||||||
|         private TargetFind targetFind; |         private TargetFind targetFind; | ||||||
|         private ActionQueue actionQueue; |         private ActionQueue actionQueue; | ||||||
|  |  | ||||||
| @@ -43,16 +43,24 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|  |  | ||||||
|             // todo: trigger listeners |             // todo: trigger listeners | ||||||
|  |  | ||||||
|             // todo: action queues |             if (controller == null && pathFind != null) | ||||||
|             controller?.Update(tick); |  | ||||||
|             State currState; |  | ||||||
|             while (states.Count > 0 && (currState = states.Peek()).Update(tick)) |  | ||||||
|             { |  | ||||||
|                 if (currState  == GetCurrentState()) |  | ||||||
|             { |             { | ||||||
|  |                 pathFind.FollowPath(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // todo: action queues | ||||||
|  |             if (controller != null && controller.canUpdate) | ||||||
|  |                 controller.Update(tick); | ||||||
|  |  | ||||||
|  |             State top; | ||||||
|  |             while (states.Count > 0 && (top = states.Peek()).Update(tick)) | ||||||
|  |             { | ||||||
|  |                 if (top == GetCurrentState()) | ||||||
|  |                 { | ||||||
|  |                     states.Pop().Cleanup(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             owner.PostUpdate(tick); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void CheckCompletedStates() |         public void CheckCompletedStates() | ||||||
| @@ -93,6 +101,16 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             return controller; |             return controller; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public TargetFind GetTargetFind() | ||||||
|  |         { | ||||||
|  |             return targetFind; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool CanFollowPath() | ||||||
|  |         { | ||||||
|  |             return pathFind != null && (GetCurrentState() != null || GetCurrentState().CanChangeState()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public bool CanChangeState() |         public bool CanChangeState() | ||||||
|         { |         { | ||||||
|             return states.Count == 0 || states.Peek().CanInterrupt(); |             return states.Count == 0 || states.Peek().CanInterrupt(); | ||||||
| @@ -135,9 +153,14 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public bool IsCurrentState<T>() where T : State | ||||||
|  |         { | ||||||
|  |             return GetCurrentState() is T; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public State GetCurrentState() |         public State GetCurrentState() | ||||||
|         { |         { | ||||||
|             return states.Peek() ?? null; |             return states.Count > 0 ? states.Peek() : null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public DateTime GetLatestUpdate() |         public DateTime GetLatestUpdate() | ||||||
| @@ -145,10 +168,19 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             return latestUpdate; |             return latestUpdate; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public void Reset() | ||||||
|  |         { | ||||||
|  |             // todo: reset cooldowns and stuff here too? | ||||||
|  |             targetFind?.Reset(); | ||||||
|  |             pathFind?.Clear(); | ||||||
|  |             ClearStates(); | ||||||
|  |             InternalDisengage(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public bool IsSpawned() |         public bool IsSpawned() | ||||||
|         { |         { | ||||||
|             // todo: set a flag when finished spawning |             // todo: set a flag when finished spawning | ||||||
|             return true; |             return !IsDead(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public bool IsEngaged() |         public bool IsEngaged() | ||||||
| @@ -211,7 +243,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|  |  | ||||||
|         public void InternalChangeTarget(Character target) |         public void InternalChangeTarget(Character target) | ||||||
|         { |         { | ||||||
|  |             // todo: use invalid target id | ||||||
|  |             // todo: this is retarded, call entity's changetarget function | ||||||
|  |             owner.target = target; | ||||||
|  |             owner.currentLockedTarget = target != null ? target.actorId : 0xC0000000; | ||||||
|  |             owner.currentTarget = target != null ? target.actorId : 0xC0000000; | ||||||
|  |  | ||||||
|  |             if (IsEngaged() || target == null) | ||||||
|  |             { | ||||||
|  |  | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 Engage(target); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public bool InternalEngage(Character target) |         public bool InternalEngage(Character target) | ||||||
| @@ -236,7 +281,15 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|  |  | ||||||
|         public void InternalDisengage() |         public void InternalDisengage() | ||||||
|         { |         { | ||||||
|  |             pathFind?.Clear(); | ||||||
|  |             GetTargetFind()?.Reset(); | ||||||
|  |  | ||||||
|  |             owner.updateFlags |= (ActorUpdateFlags.State | ActorUpdateFlags.HpTpMp); | ||||||
|  |  | ||||||
|  |             // todo: use the update flags | ||||||
|  |             owner.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE); | ||||||
|  |  | ||||||
|  |             ChangeTarget(null); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void InternalCast(Character target, uint spellId) |         public void InternalCast(Character target, uint spellId) | ||||||
| @@ -256,7 +309,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|  |  | ||||||
|         public void InternalDie(DateTime tick, uint timeToFadeout) |         public void InternalDie(DateTime tick, uint timeToFadeout) | ||||||
|         { |         { | ||||||
|  |             ClearStates(); | ||||||
|  |             Disengage(); | ||||||
|  |             ForceChangeState(new DeathState(owner, tick, timeToFadeout)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void InternalRaise(Character target) |         public void InternalRaise(Character target) | ||||||
|   | |||||||
							
								
								
									
										100
									
								
								FFXIVClassic Map Server/actors/chara/ai/Ability.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								FFXIVClassic Map Server/actors/chara/ai/Ability.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | using FFXIVClassic_Map_Server.Actors; | ||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | namespace FFXIVClassic_Map_Server.actors.chara.ai | ||||||
|  | { | ||||||
|  |  | ||||||
|  |     public enum AbilityRequirements : ushort | ||||||
|  |     { | ||||||
|  |         None, | ||||||
|  |         DiscipleOfWar = 0x01, | ||||||
|  |         DiscipeOfMagic = 0x02, | ||||||
|  |         HandToHand = 0x04, | ||||||
|  |         Sword = 0x08, | ||||||
|  |         Shield = 0x10, | ||||||
|  |         Axe = 0x20, | ||||||
|  |         Archery = 0x40, | ||||||
|  |         Polearm = 0x80, | ||||||
|  |         Thaumaturgy = 0x100, | ||||||
|  |         Conjury = 0x200 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public enum AbilityPositionBonus : byte | ||||||
|  |     { | ||||||
|  |         None, | ||||||
|  |         Front = 0x01, | ||||||
|  |         Rear = 0x02, | ||||||
|  |         Flank = 0x04 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public enum AbilityProcRequirement : byte | ||||||
|  |     { | ||||||
|  |         None, | ||||||
|  |         Evade = 0x01, | ||||||
|  |         Block = 0x02, | ||||||
|  |         Parry = 0x04, | ||||||
|  |         Miss = 0x08 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     class Ability | ||||||
|  |     { | ||||||
|  |         public ushort abilityId; | ||||||
|  |         public string name; | ||||||
|  |         public byte job; | ||||||
|  |         public byte level; | ||||||
|  |         public AbilityRequirements requirements; | ||||||
|  |         public TargetFindFlags validTarget; | ||||||
|  |         public TargetFindAOETarget aoeTarget; | ||||||
|  |         public TargetFindAOEType aoeType; | ||||||
|  |         public int range; | ||||||
|  |         public TargetFindCharacterType characterFind; | ||||||
|  |         public uint statusDurationSeconds; | ||||||
|  |         public uint castTimeSeconds; | ||||||
|  |         public uint recastTimeSeconds; | ||||||
|  |         public ushort mpCost; | ||||||
|  |         public ushort tpCost; | ||||||
|  |         public byte animationType; | ||||||
|  |         public ushort effectAnimation; | ||||||
|  |         public ushort modelAnimation; | ||||||
|  |         public ushort animationDurationSeconds; | ||||||
|  |  | ||||||
|  |         public AbilityPositionBonus positionBonus; | ||||||
|  |         public AbilityProcRequirement procRequirement; | ||||||
|  |  | ||||||
|  |         public TargetFind targetFind; | ||||||
|  |  | ||||||
|  |         public Ability(ushort id, string name) | ||||||
|  |         { | ||||||
|  |             this.abilityId = id; | ||||||
|  |             this.name = name; | ||||||
|  |             this.range = -1; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Ability Clone() | ||||||
|  |         { | ||||||
|  |             return (Ability)MemberwiseClone(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool IsSpell() | ||||||
|  |         { | ||||||
|  |             return mpCost != 0 || castTimeSeconds != 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool IsInstantCast() | ||||||
|  |         { | ||||||
|  |             return castTimeSeconds == 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool CanPlayerUse(Character user, Character target) | ||||||
|  |         { | ||||||
|  |             // todo: set box length.. | ||||||
|  |             targetFind = new TargetFind(user); | ||||||
|  |             targetFind.SetAOEType(aoeTarget, aoeType, aoeType == TargetFindAOEType.Box ? range / 2 : range, 40); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -38,11 +38,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         public void AddBaseHate(Character target) |         public void AddBaseHate(Character target) | ||||||
|         { |         { | ||||||
|             if (!HasHateForTarget(target)) |             if (!HasHateForTarget(target)) | ||||||
|                 hateList.Add(target, new HateEntry(target, 0, 0, true)); |                 hateList.Add(target, new HateEntry(target, 1, 0, true)); | ||||||
|             else |             else | ||||||
|                 Program.Log.Error($"{target.actorName} is already on [{owner.actorId}]{owner.actorName}'s hate list!"); |                 Program.Log.Error($"{target.actorName} is already on [{owner.actorId}]{owner.actorName}'s hate list!"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public void UpdateHate(Character target, int damage) | ||||||
|  |         { | ||||||
|  |             if (HasHateForTarget(target)) | ||||||
|  |             { | ||||||
|  |                 //hateList[target].volatileEnmity += (uint)damage; | ||||||
|  |                 hateList[target].cumulativeEnmity += (uint)damage; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public void ClearHate(Character target = null) |         public void ClearHate(Character target = null) | ||||||
|         { |         { | ||||||
|             if (target != null) |             if (target != null) | ||||||
|   | |||||||
| @@ -8,20 +8,37 @@ using FFXIVClassic_Map_Server; | |||||||
| using FFXIVClassic_Map_Server.utils; | using FFXIVClassic_Map_Server.utils; | ||||||
| using FFXIVClassic.Common; | using FFXIVClassic.Common; | ||||||
| using FFXIVClassic_Map_Server.actors.area; | using FFXIVClassic_Map_Server.actors.area; | ||||||
|  | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
|  |  | ||||||
| namespace FFXIVClassic_Map_Server.actors.chara.ai | namespace FFXIVClassic_Map_Server.actors.chara.ai | ||||||
| { | { | ||||||
|  |     // todo: path flags, check for obstacles etc | ||||||
|  |     public enum PathFindFlags | ||||||
|  |     { | ||||||
|  |         None, | ||||||
|  |         Scripted = 0x01, | ||||||
|  |         IgnoreNav = 0x02, | ||||||
|  |     } | ||||||
|     class PathFind |     class PathFind | ||||||
|     { |     { | ||||||
|         private Character owner; |         private Character owner; | ||||||
|  |         private List<Vector3> path; | ||||||
|  |         private bool canFollowPath; | ||||||
|  |  | ||||||
|  |         private PathFindFlags pathFlags; | ||||||
|  |  | ||||||
|         public PathFind(Character owner) |         public PathFind(Character owner) | ||||||
|         { |         { | ||||||
|             this.owner = owner; |             this.owner = owner; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public void PreparePath(Vector3 dest, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f) | ||||||
|  |         { | ||||||
|  |             PreparePath(dest.X, dest.Y, dest.Z, stepSize, maxPath, polyRadius); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // todo: is this class even needed? |         // todo: is this class even needed? | ||||||
|         public void PathTo(float x, float y, float z, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f) |         public void PreparePath(float x, float y, float z, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f) | ||||||
|         { |         { | ||||||
|             var pos = new Vector3(owner.positionX, owner.positionY, owner.positionZ); |             var pos = new Vector3(owner.positionX, owner.positionY, owner.positionZ); | ||||||
|             var dest = new Vector3(x, y, z); |             var dest = new Vector3(x, y, z); | ||||||
| @@ -29,7 +46,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             var sw = new System.Diagnostics.Stopwatch(); |             var sw = new System.Diagnostics.Stopwatch(); | ||||||
|             sw.Start(); |             sw.Start(); | ||||||
|  |  | ||||||
|             var path = NavmeshUtils.GetPath(zone, pos, dest, stepSize, maxPath, polyRadius); |             if ((pathFlags & PathFindFlags.IgnoreNav) != 0) | ||||||
|  |                 path = new List<Vector3>(1) { new Vector3(x, y, z) }; | ||||||
|  |             else | ||||||
|  |                 path = NavmeshUtils.GetPath(zone, pos, dest, stepSize, maxPath, polyRadius); | ||||||
|  |  | ||||||
|             if (path != null) |             if (path != null) | ||||||
|             { |             { | ||||||
| @@ -48,11 +68,6 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|                     owner.positionZ = owner.oldPositionZ; |                     owner.positionZ = owner.oldPositionZ; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 owner.positionUpdates = path; |  | ||||||
|  |  | ||||||
|                 owner.hasMoved = true; |  | ||||||
|                 owner.isAtSpawn = false; |  | ||||||
|  |  | ||||||
|                 sw.Stop(); |                 sw.Stop(); | ||||||
|                 zone.pathCalls++; |                 zone.pathCalls++; | ||||||
|                 zone.pathCallTime += sw.ElapsedMilliseconds; |                 zone.pathCallTime += sw.ElapsedMilliseconds; | ||||||
| @@ -63,5 +78,70 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|                 Program.Log.Error("[{0}][{1}] Created {2} points in {3} milliseconds", owner.actorId, owner.actorName, path.Count, sw.ElapsedMilliseconds); |                 Program.Log.Error("[{0}][{1}] Created {2} points in {3} milliseconds", owner.actorId, owner.actorName, path.Count, sw.ElapsedMilliseconds); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public void PathInRange(Vector3 dest, float minRange, float maxRange) | ||||||
|  |         { | ||||||
|  |             PathInRange(dest.X, dest.Y, dest.Z, minRange, maxRange); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void PathInRange(float x, float y, float z, float minRange, float maxRange = 5.0f) | ||||||
|  |         { | ||||||
|  |             var dest = owner.FindRandomPoint(x, y, z, minRange, maxRange); | ||||||
|  |             PreparePath(dest.X, dest.Y, dest.Z); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         public void SetPathFlags(PathFindFlags flags) | ||||||
|  |         { | ||||||
|  |             this.pathFlags = flags; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool IsFollowingPath() | ||||||
|  |         { | ||||||
|  |             return path.Count > 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool IsFollowingScriptedPath() | ||||||
|  |         { | ||||||
|  |             return (pathFlags & PathFindFlags.Scripted) != 0; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void FollowPath() | ||||||
|  |         { | ||||||
|  |             if (path?.Count > 0) | ||||||
|  |             { | ||||||
|  |                 var point = path[0]; | ||||||
|  |  | ||||||
|  |                 owner.OnPath(point); | ||||||
|  |                 owner.QueuePositionUpdate(point); | ||||||
|  |                 path.Remove(point); | ||||||
|  |  | ||||||
|  |                 if (path.Count == 0) | ||||||
|  |                     owner.LookAt(point.X, point.Y); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void Clear() | ||||||
|  |         { | ||||||
|  |             // todo: | ||||||
|  |             path?.Clear(); | ||||||
|  |             pathFlags = PathFindFlags.None; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private float GetSpeed() | ||||||
|  |         { | ||||||
|  |             float baseSpeed = owner.GetSpeed(); | ||||||
|  |  | ||||||
|  |             // todo: get actual speed crap | ||||||
|  |             if (owner.currentSubState != SetActorStatePacket.SUB_STATE_NONE) | ||||||
|  |             { | ||||||
|  |                 if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER) | ||||||
|  |                 { | ||||||
|  |                     owner.ChangeSpeed(0.0f, SetActorSpeedPacket.DEFAULT_WALK - 2.0f, SetActorSpeedPacket.DEFAULT_RUN - 2.0f, SetActorSpeedPacket.DEFAULT_ACTIVE - 2.0f); | ||||||
|  |                 } | ||||||
|  |                 // baseSpeed += ConfigConstants.SPEED_MOD; | ||||||
|  |             } | ||||||
|  |             return baseSpeed; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ using System.Threading.Tasks; | |||||||
|  |  | ||||||
| namespace FFXIVClassic_Map_Server.actors.chara.ai | namespace FFXIVClassic_Map_Server.actors.chara.ai | ||||||
| { | { | ||||||
|     enum StatusEffectId |     enum StatusEffectId : uint | ||||||
|     { |     { | ||||||
|         RageofHalone = 221021, |         RageofHalone = 221021, | ||||||
|  |  | ||||||
| @@ -324,7 +324,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     [Flags] |     [Flags] | ||||||
|     enum StatusEffectFlags |     enum StatusEffectFlags : uint | ||||||
|     { |     { | ||||||
|         None = 0x00, |         None = 0x00, | ||||||
|         Silent = 0x01,             // dont display effect loss message |         Silent = 0x01,             // dont display effect loss message | ||||||
| @@ -338,6 +338,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         LoseOnDamageTaken = 0x100, // effects removed when owner takes damage |         LoseOnDamageTaken = 0x100, // effects removed when owner takes damage | ||||||
|  |  | ||||||
|         PreventAction = 0x200,     // effects which prevent actions such as sleep/paralyze/petrify |         PreventAction = 0x200,     // effects which prevent actions such as sleep/paralyze/petrify | ||||||
|  |         Stealth = 0x400,           // sneak/invis | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     enum StatusEffectOverwrite : byte |     enum StatusEffectOverwrite : byte | ||||||
| @@ -370,6 +371,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         public StatusEffect(Character owner, uint id, UInt64 magnitude, uint tickMs, uint durationMs, byte tier = 0) |         public StatusEffect(Character owner, uint id, UInt64 magnitude, uint tickMs, uint durationMs, byte tier = 0) | ||||||
|         { |         { | ||||||
|             this.owner = owner; |             this.owner = owner; | ||||||
|  |             this.source = owner; | ||||||
|             this.id = (StatusEffectId)id; |             this.id = (StatusEffectId)id; | ||||||
|             this.magnitude = magnitude; |             this.magnitude = magnitude; | ||||||
|             this.tickMs = tickMs; |             this.tickMs = tickMs; | ||||||
| @@ -390,6 +392,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         public StatusEffect(Character owner, StatusEffect effect) |         public StatusEffect(Character owner, StatusEffect effect) | ||||||
|         { |         { | ||||||
|             this.owner = owner; |             this.owner = owner; | ||||||
|  |             this.source = owner; | ||||||
|             this.id = effect.id; |             this.id = effect.id; | ||||||
|             this.magnitude = effect.magnitude; |             this.magnitude = effect.magnitude; | ||||||
|             this.tickMs = effect.tickMs; |             this.tickMs = effect.tickMs; | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ using FFXIVClassic_Map_Server.actors.area; | |||||||
| using FFXIVClassic_Map_Server.packets.send; | using FFXIVClassic_Map_Server.packets.send; | ||||||
| using FFXIVClassic_Map_Server.packets.send.actor; | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
| using System.Collections.ObjectModel; | using System.Collections.ObjectModel; | ||||||
|  | using FFXIVClassic_Map_Server.utils; | ||||||
|  |  | ||||||
| namespace FFXIVClassic_Map_Server.actors.chara.ai | namespace FFXIVClassic_Map_Server.actors.chara.ai | ||||||
| { | { | ||||||
| @@ -19,6 +20,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         private readonly Dictionary<uint, StatusEffect> effects; |         private readonly Dictionary<uint, StatusEffect> effects; | ||||||
|         public static readonly int MAX_EFFECTS = 20; |         public static readonly int MAX_EFFECTS = 20; | ||||||
|         private bool sendUpdate = false; |         private bool sendUpdate = false; | ||||||
|  |  | ||||||
|         public StatusEffectContainer(Character owner) |         public StatusEffectContainer(Character owner) | ||||||
|         { |         { | ||||||
|             this.owner = owner; |             this.owner = owner; | ||||||
| @@ -44,10 +46,30 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|  |  | ||||||
|             if (sendUpdate) |             if (sendUpdate) | ||||||
|             { |             { | ||||||
|                 owner.zone.BroadcastPacketsAroundActor(owner, owner.GetActorStatusPackets()); |                 var propPacketUtil = new ActorPropertyPacketUtil("charaWork.status", owner); | ||||||
|  |  | ||||||
|  |                 //Status Times | ||||||
|  |                 for (int i = 0; i < owner.charaWork.statusShownTime.Length; i++) | ||||||
|  |                 { | ||||||
|  |                     if (owner.charaWork.status[i] != 0xFFFF && owner.charaWork.status[i] != 0) | ||||||
|  |                         propPacketUtil.AddProperty(String.Format("charaWork.status[{0}]", i)); | ||||||
|  |  | ||||||
|  |                     if (owner.charaWork.statusShownTime[i] != 0xFFFFFFFF) | ||||||
|  |                         propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i)); | ||||||
|  |                 } | ||||||
|  |                 owner.zone.BroadcastPacketsAroundActor(owner, propPacketUtil.Done()); | ||||||
|  |                 sendUpdate = false; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|             sendUpdate = false; |         public bool HasStatusEffect(uint id) | ||||||
|  |         { | ||||||
|  |             return effects.ContainsKey(id); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool HasStatusEffect(StatusEffectId id) | ||||||
|  |         { | ||||||
|  |             return effects.ContainsKey((uint)id); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public bool AddStatusEffect(uint id, UInt64 magnitude, double tickMs, double durationMs, byte tier = 0) |         public bool AddStatusEffect(uint id, UInt64 magnitude, double tickMs, double durationMs, byte tier = 0) | ||||||
| @@ -74,8 +96,8 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|                 if (!silent || !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0) |                 if (!silent || !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0) | ||||||
|                 { |                 { | ||||||
|                     // todo: send packet to client with effect added message |                     // todo: send packet to client with effect added message | ||||||
|                     //foreach (var player in owner.zone.GetActorsAroundActor<Player>(owner, 50)) |                     foreach (var player in owner.zone.GetActorsAroundActor<Player>(owner, 50)) | ||||||
|                     //    player.QueuePacket(packets.send.actor.battle.BattleActionX01Packet.BuildPacket(player.actorId, effect.GetSource().actorId, owner.actorId, 0, effect.GetStatusEffectId(), 0, effect.GetStatusId(), 0, 0)); |                         player.QueuePacket(packets.send.actor.battle.BattleActionX01Packet.BuildPacket(player.actorId, newEffect.GetSource().actorId, owner.actorId, 0, newEffect.GetStatusEffectId(), 0, newEffect.GetStatusId(), 0, 0)); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // wont send a message about losing effect here |                 // wont send a message about losing effect here | ||||||
| @@ -93,6 +115,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|                         owner.charaWork.statusShownTime[index] = Utils.UnixTimeStampUTC() + (newEffect.GetDurationMs() / 1000); |                         owner.charaWork.statusShownTime[index] = Utils.UnixTimeStampUTC() + (newEffect.GetDurationMs() / 1000); | ||||||
|                         this.owner.zone.BroadcastPacketAroundActor(this.owner, SetActorStatusPacket.BuildPacket(this.owner.actorId, (ushort)index, (ushort)newEffect.GetStatusId())); |                         this.owner.zone.BroadcastPacketAroundActor(this.owner, SetActorStatusPacket.BuildPacket(this.owner.actorId, (ushort)index, (ushort)newEffect.GetStatusId())); | ||||||
|                     } |                     } | ||||||
|  |                     owner.RecalculateHpMpTp(); | ||||||
|                     sendUpdate = true; |                     sendUpdate = true; | ||||||
|                 } |                 } | ||||||
|                 return true; |                 return true; | ||||||
| @@ -122,6 +145,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|                 // function onLose(actor, effect) |                 // function onLose(actor, effect) | ||||||
|                 LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect); |                 LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect); | ||||||
|                 effects.Remove(effect.GetStatusEffectId()); |                 effects.Remove(effect.GetStatusEffectId()); | ||||||
|  |                 owner.RecalculateHpMpTp(); | ||||||
|                 sendUpdate = true; |                 sendUpdate = true; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -185,6 +209,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             return list; |             return list; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // todo: why the fuck cant c# convert enums/ | ||||||
|  |         public bool HasStatusEffectsByFlag(StatusEffectFlags flags) | ||||||
|  |         { | ||||||
|  |             return HasStatusEffectsByFlag((uint)flags); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public bool HasStatusEffectsByFlag(uint flag) |         public bool HasStatusEffectsByFlag(uint flag) | ||||||
|         { |         { | ||||||
|             foreach (var effect in effects.Values) |             foreach (var effect in effects.Values) | ||||||
|   | |||||||
| @@ -7,30 +7,30 @@ using FFXIVClassic_Map_Server.Actors; | |||||||
| using FFXIVClassic.Common; | using FFXIVClassic.Common; | ||||||
| using FFXIVClassic_Map_Server.actors.chara.ai; | using FFXIVClassic_Map_Server.actors.chara.ai; | ||||||
| using FFXIVClassic_Map_Server.actors.chara.ai.controllers; | using FFXIVClassic_Map_Server.actors.chara.ai.controllers; | ||||||
|  | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
|  |  | ||||||
| // port of dsp's ai code https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai/ | // port of dsp's ai code https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai/ | ||||||
|  |  | ||||||
| namespace FFXIVClassic_Map_Server.actors.chara.ai | namespace FFXIVClassic_Map_Server.actors.chara.ai | ||||||
| { | { | ||||||
|     /// <summary> todo: what even do i summarise this as? </summary> |     /// <summary> todo: what even do i summarise this as? </summary> | ||||||
|     [Flags] |     enum TargetFindFlags : byte | ||||||
|     enum TargetFindFlags |  | ||||||
|     { |     { | ||||||
|         None, |         None = 0x00, | ||||||
|         /// <summary> Able to target <see cref="Player"/>s even if not in target's party </summary> |         /// <summary> Able to target <see cref="Player"/>s even if not in target's party </summary> | ||||||
|         HitAll, |         HitAll = 0x01, | ||||||
|         /// <summary> Able to target all <see cref="Player"/>s in target's party/alliance </summary> |         /// <summary> Able to target all <see cref="Player"/>s in target's party/alliance </summary> | ||||||
|         Alliance, |         Alliance = 0x02, | ||||||
|         /// <summary> Able to target any <see cref="Pet"/> in target's party/alliance </summary> |         /// <summary> Able to target any <see cref="Pet"/> in target's party/alliance </summary> | ||||||
|         Pets, |         Pets = 0x04, | ||||||
|         /// <summary> Target all in zone, regardless of distance </summary> |         /// <summary> Target all in zone, regardless of distance </summary> | ||||||
|         ZoneWide, |         ZoneWide = 0x08, | ||||||
|         /// <summary> Able to target dead <see cref="Player"/>s </summary> |         /// <summary> Able to target dead <see cref="Player"/>s </summary> | ||||||
|         Dead, |         Dead = 0x10, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> Targeting from/to different entity types </summary> |     /// <summary> Targeting from/to different entity types </summary> | ||||||
|     enum TargetFindCharacterType |     enum TargetFindCharacterType : byte | ||||||
|     { |     { | ||||||
|         None, |         None, | ||||||
|         /// <summary> Player can target all <see cref="Player">s in party </summary> |         /// <summary> Player can target all <see cref="Player">s in party </summary> | ||||||
| @@ -44,7 +44,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> Type of AOE region to create </summary> |     /// <summary> Type of AOE region to create </summary> | ||||||
|     enum TargetFindAOEType |     enum TargetFindAOEType : byte | ||||||
|     { |     { | ||||||
|         None, |         None, | ||||||
|         /// <summary> Really a cylinder, uses extents parameter in SetAOEType </summary> |         /// <summary> Really a cylinder, uses extents parameter in SetAOEType </summary> | ||||||
| @@ -56,7 +56,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// <summary> Set AOE around self or target </summary> |     /// <summary> Set AOE around self or target </summary> | ||||||
|     enum TargetFindAOERadiusType |     enum TargetFindAOETarget : byte | ||||||
|     { |     { | ||||||
|         /// <summary> Set AOE's origin at target's position </summary> |         /// <summary> Set AOE's origin at target's position </summary> | ||||||
|         Target, |         Target, | ||||||
| @@ -73,7 +73,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         private TargetFindCharacterType findType; |         private TargetFindCharacterType findType; | ||||||
|         private TargetFindFlags findFlags; |         private TargetFindFlags findFlags; | ||||||
|         private TargetFindAOEType aoeType; |         private TargetFindAOEType aoeType; | ||||||
|         private TargetFindAOERadiusType radiusType; |         private TargetFindAOETarget aoeTarget; | ||||||
|         private Vector3 targetPosition; |         private Vector3 targetPosition; | ||||||
|         private float extents; |         private float extents; | ||||||
|         private float angle; |         private float angle; | ||||||
| @@ -91,7 +91,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             this.findType = TargetFindCharacterType.None; |             this.findType = TargetFindCharacterType.None; | ||||||
|             this.findFlags = TargetFindFlags.None; |             this.findFlags = TargetFindFlags.None; | ||||||
|             this.aoeType = TargetFindAOEType.None; |             this.aoeType = TargetFindAOEType.None; | ||||||
|             this.radiusType = TargetFindAOERadiusType.Self; |             this.aoeTarget = TargetFindAOETarget.Self; | ||||||
|             this.targetPosition = null; |             this.targetPosition = null; | ||||||
|             this.extents = 0.0f; |             this.extents = 0.0f; | ||||||
|             this.angle = 0.0f; |             this.angle = 0.0f; | ||||||
| @@ -114,12 +114,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         /// <param name="extents"> |         /// <param name="extents"> | ||||||
|         /// <see cref="TargetFindAOEType.Circle"/> - radius of circle <para/> |         /// <see cref="TargetFindAOEType.Circle"/> - radius of circle <para/> | ||||||
|         /// <see cref="TargetFindAOEType.Cone"/> - height of cone <para/> |         /// <see cref="TargetFindAOEType.Cone"/> - height of cone <para/> | ||||||
|         /// <see cref="TargetFindAOEType.Box"/> - width of box / 2 |         /// <see cref="TargetFindAOEType.Box"/> - width of box / 2 (todo: set box length not just between user and target) | ||||||
|         /// </param> |         /// </param> | ||||||
|         /// <param name="angle"> Angle in radians of cone </param> |         /// <param name="angle"> Angle in radians of cone </param> | ||||||
|         public void SetAOEType(TargetFindAOERadiusType radiusType, TargetFindAOEType aoeType, float extents = -1.0f, float angle = -1.0f) |         public void SetAOEType(TargetFindAOETarget aoeTarget, TargetFindAOEType aoeType, float extents = -1.0f, float angle = -1.0f) | ||||||
|         { |         { | ||||||
|             this.radiusType = TargetFindAOERadiusType.Target; |             this.aoeTarget = TargetFindAOETarget.Target; | ||||||
|             this.aoeType = aoeType; |             this.aoeType = aoeType; | ||||||
|             this.extents = extents != -1.0f ? extents : 0.0f; |             this.extents = extents != -1.0f ? extents : 0.0f; | ||||||
|             this.angle = angle != -1.0f ? angle : 0.0f; |             this.angle = angle != -1.0f ? angle : 0.0f; | ||||||
| @@ -146,7 +146,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             findFlags = flags; |             findFlags = flags; | ||||||
|             // todo: maybe we should keep a snapshot which is only updated on each tick for consistency |             // todo: maybe we should keep a snapshot which is only updated on each tick for consistency | ||||||
|             // are we creating aoe circles around target or self |             // are we creating aoe circles around target or self | ||||||
|             if ((aoeType & TargetFindAOEType.Circle) != 0 && radiusType != TargetFindAOERadiusType.Self) |             if ((aoeType & TargetFindAOEType.Circle) != 0 && aoeTarget != TargetFindAOETarget.Self) | ||||||
|                 this.targetPosition = owner.GetPosAsVector3(); |                 this.targetPosition = owner.GetPosAsVector3(); | ||||||
|             else |             else | ||||||
|                 this.targetPosition = target.GetPosAsVector3(); |                 this.targetPosition = target.GetPosAsVector3(); | ||||||
| @@ -306,10 +306,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public bool CanTarget(Character target, bool withPet = false) |         public bool CanTarget(Character target, bool withPet = false, bool retarget = false) | ||||||
|         { |         { | ||||||
|             // already targeted, dont target again |             // already targeted, dont target again | ||||||
|             if (targets.Contains(target)) |             if (target == null || !retarget && targets.Contains(target)) | ||||||
|                 return false; |                 return false; | ||||||
|  |  | ||||||
|             // cant target dead |             // cant target dead | ||||||
| @@ -318,8 +318,12 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|  |  | ||||||
|             bool targetingPlayer = target is Player; |             bool targetingPlayer = target is Player; | ||||||
|              |              | ||||||
|  |             // todo: why is player always zoning? | ||||||
|             // cant target if zoning |             // cant target if zoning | ||||||
|             if (target.isZoning || owner.isZoning || target.zone != owner.zone || targetingPlayer && ((Player)target).playerSession.isUpdatesLocked) |             if (/*target.isZoning || owner.isZoning || */target.zone != owner.zone || targetingPlayer && ((Player)target).playerSession.isUpdatesLocked) | ||||||
|  |                 return false; | ||||||
|  |  | ||||||
|  |             if (aoeTarget == TargetFindAOETarget.Self && aoeType != TargetFindAOEType.None && owner != target) | ||||||
|                 return false; |                 return false; | ||||||
|  |  | ||||||
|             // hit everything within zone or within aoe region |             // hit everything within zone or within aoe region | ||||||
| @@ -332,7 +336,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|             if (aoeType == TargetFindAOEType.Box && IsWithinBox(target, withPet)) |             if (aoeType == TargetFindAOEType.Box && IsWithinBox(target, withPet)) | ||||||
|                 return true; |                 return true; | ||||||
|  |  | ||||||
|             return false; |             return true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private bool IsPlayer(Character target) |         private bool IsPlayer(Character target) | ||||||
| @@ -359,12 +363,38 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai | |||||||
|         private bool IsBattleNpcOwner(Character target) |         private bool IsBattleNpcOwner(Character target) | ||||||
|         { |         { | ||||||
|             // i know i copied this from dsp but what even |             // i know i copied this from dsp but what even | ||||||
|             if (!(owner is Player) || target is Player) |             if (owner.currentSubState != SetActorStatePacket.SUB_STATE_PLAYER || target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) | ||||||
|                 return true; |                 return true; | ||||||
|  |  | ||||||
|             // todo: check hate list |             // todo: check hate list | ||||||
|  |             if (owner.currentSubState == SetActorStatePacket.SUB_STATE_MONSTER && ((BattleNpc)owner).hateContainer.GetMostHatedTarget() != target) | ||||||
|  |             { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public Character GetValidTarget(Character target, TargetFindFlags findFlags) | ||||||
|  |         { | ||||||
|  |             if (target == null || target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER && ((Player)target).playerSession.isUpdatesLocked) | ||||||
|  |                 return null; | ||||||
|  |  | ||||||
|  |             if ((findFlags & TargetFindFlags.Pets) != 0) | ||||||
|  |             { | ||||||
|  |                 return owner.pet; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // todo: this is beyond retarded | ||||||
|  |             var oldFlags = this.findFlags; | ||||||
|  |             this.findFlags = findFlags; | ||||||
|  |             if (CanTarget(target, false, true)) | ||||||
|  |             { | ||||||
|  |                 this.findFlags = oldFlags; | ||||||
|  |                 return target; | ||||||
|  |             } | ||||||
|  |             this.findFlags = oldFlags; | ||||||
|  |  | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,7 +3,11 @@ using System.Collections.Generic; | |||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
|  | using FFXIVClassic.Common; | ||||||
| using FFXIVClassic_Map_Server.Actors; | using FFXIVClassic_Map_Server.Actors; | ||||||
|  | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
|  | using FFXIVClassic_Map_Server.actors.area; | ||||||
|  | using FFXIVClassic_Map_Server.utils; | ||||||
|  |  | ||||||
| namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | ||||||
| { | { | ||||||
| @@ -20,29 +24,39 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|         private bool firstSpell = true; |         private bool firstSpell = true; | ||||||
|         private DateTime lastRoamScript; // todo: what even is this used as |         private DateTime lastRoamScript; // todo: what even is this used as | ||||||
|  |  | ||||||
|         public BattleNpcController(Character owner) |         private new BattleNpc owner; | ||||||
|  |         public BattleNpcController(BattleNpc owner) : | ||||||
|  |             base(owner) | ||||||
|         { |         { | ||||||
|             this.owner = owner; |             this.owner = owner; | ||||||
|             this.lastUpdate = DateTime.Now; |             this.lastUpdate = DateTime.Now; | ||||||
|  |             this.waitTime = lastUpdate.AddSeconds(5); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Update(DateTime tick) |         public override void Update(DateTime tick) | ||||||
|         { |  | ||||||
|             var battleNpc = this.owner as BattleNpc; |  | ||||||
|  |  | ||||||
|             if (battleNpc != null) |  | ||||||
|         { |         { | ||||||
|             // todo: handle aggro/deaggro and other shit here |             // todo: handle aggro/deaggro and other shit here | ||||||
|                 if (battleNpc.aiContainer.IsEngaged()) |             if (owner.aiContainer.IsEngaged()) | ||||||
|             { |             { | ||||||
|                 DoCombatTick(tick); |                 DoCombatTick(tick); | ||||||
|             } |             } | ||||||
|                 else if (!battleNpc.IsDead()) |             else if (!owner.IsDead()) | ||||||
|             { |             { | ||||||
|                 DoRoamTick(tick); |                 DoRoamTick(tick); | ||||||
|             } |             } | ||||||
|                 battleNpc.Update(tick); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public bool TryDeaggro() | ||||||
|  |         { | ||||||
|  |             if (owner.hateContainer.GetMostHatedTarget() == null || !owner.aiContainer.GetTargetFind().CanTarget(owner.target as Character)) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             else if (!owner.IsCloseToSpawn()) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override bool Engage(Character target) |         public override bool Engage(Character target) | ||||||
| @@ -53,7 +67,27 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|             { |             { | ||||||
|                 // reset casting |                 // reset casting | ||||||
|                 firstSpell = true; |                 firstSpell = true; | ||||||
|  |                 // todo: find a better place to put this? | ||||||
|  |                 if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE) | ||||||
|  |                     owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 // todo: check speed/is able to move | ||||||
|  |                 // 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) | ||||||
|  |                 { | ||||||
|  |                     // todo: actual stat based range | ||||||
|  |                     if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > 10) | ||||||
|  |                     { | ||||||
|  |                         owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None); | ||||||
|  |                         owner.aiContainer.pathFind.PreparePath(target.positionX, target.positionY, target.positionZ); | ||||||
|  |                         ChangeTarget(target); | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 lastActionTime = DateTime.Now; | ||||||
|                 // todo: adjust cooldowns with modifiers |                 // todo: adjust cooldowns with modifiers | ||||||
|             } |             } | ||||||
|             return canEngage; |             return canEngage; | ||||||
| @@ -65,10 +99,17 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override bool Disengage() |         public override void Disengage() | ||||||
|         { |         { | ||||||
|  |             var target = owner.target; | ||||||
|  |             base.Disengage(); | ||||||
|             // todo: |             // todo: | ||||||
|             return true; |             lastActionTime = lastUpdate; | ||||||
|  |             owner.isMovingToSpawn = true; | ||||||
|  |             neutralTime = lastUpdate; | ||||||
|  |             owner.hateContainer.ClearHate(); | ||||||
|  |             owner.moveState = 1; | ||||||
|  |             lua.LuaEngine.CallLuaBattleAction(owner, "onDisengage", owner, target); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Cast(Character target, uint spellId) |         public override void Cast(Character target, uint spellId) | ||||||
| @@ -93,25 +134,185 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|  |  | ||||||
|         private void DoRoamTick(DateTime tick) |         private void DoRoamTick(DateTime tick) | ||||||
|         { |         { | ||||||
|             var battleNpc = owner as BattleNpc; |             if (owner.hateContainer.GetHateList().Count > 0) | ||||||
|              |  | ||||||
|             if (battleNpc != null) |  | ||||||
|             { |             { | ||||||
|                 if (battleNpc.hateContainer.GetHateList().Count > 0) |                 Engage(owner.hateContainer.GetMostHatedTarget()); | ||||||
|                 { |  | ||||||
|                     Engage(battleNpc.hateContainer.GetMostHatedTarget()); |  | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|                 else if (battleNpc.currentLockedTarget != 0) |             //else if (owner.currentLockedTarget != 0) | ||||||
|  |             //{ | ||||||
|  |             //    ChangeTarget(Server.GetWorldManager().GetActorInWorld(owner.currentLockedTarget).GetAsCharacter()); | ||||||
|  |             //} | ||||||
|  |  | ||||||
|  |             if (tick >= waitTime) | ||||||
|  |             { | ||||||
|  |                 // todo: aggro cooldown | ||||||
|  |                 neutralTime = tick.AddSeconds(5); | ||||||
|  |                 if (owner.aiContainer.pathFind.IsFollowingPath()) | ||||||
|  |                 { | ||||||
|  |                     owner.aiContainer.pathFind.FollowPath(); | ||||||
|  |                     lastActionTime = tick.AddSeconds(-5); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     if (tick >= lastActionTime) | ||||||
|                     { |                     { | ||||||
|  |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |                 // todo: | ||||||
|  |                 waitTime = tick.AddSeconds(10); | ||||||
|  |                 owner.OnRoam(tick); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         private void DoCombatTick(DateTime tick) |         private void DoCombatTick(DateTime tick) | ||||||
|         { |         { | ||||||
|  |             HandleHate(); | ||||||
|  |  | ||||||
|  |             // todo: magic/attack/ws cooldowns etc | ||||||
|  |             if (TryDeaggro()) | ||||||
|  |             { | ||||||
|  |                 Disengage(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Move(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void Move() | ||||||
|  |         { | ||||||
|  |             if (!owner.aiContainer.CanFollowPath()) | ||||||
|  |             { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (owner.aiContainer.pathFind.IsFollowingScriptedPath()) | ||||||
|  |             { | ||||||
|  |                 owner.aiContainer.pathFind.FollowPath(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             var targetPos = owner.target.GetPosAsVector3(); | ||||||
|  |             var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, targetPos.X, targetPos.Y, targetPos.Z); | ||||||
|  |  | ||||||
|  |             if (distance > owner.meleeRange - 0.2f || owner.aiContainer.CanFollowPath()) | ||||||
|  |             { | ||||||
|  |                 if (CanMoveForward(distance)) | ||||||
|  |                 { | ||||||
|  |                     if (!owner.aiContainer.pathFind.IsFollowingPath() && distance > 3) | ||||||
|  |                     { | ||||||
|  |                         // pathfind if too far otherwise jump to target | ||||||
|  |                         owner.aiContainer.pathFind.SetPathFlags(distance > 3 ? PathFindFlags.None : PathFindFlags.IgnoreNav ); | ||||||
|  |                         owner.aiContainer.pathFind.PreparePath(targetPos, 0.7f, 5); | ||||||
|  |                     } | ||||||
|  |                     owner.aiContainer.pathFind.FollowPath(); | ||||||
|  |                     if (!owner.aiContainer.pathFind.IsFollowingPath()) | ||||||
|  |                     { | ||||||
|  |                         if (owner.target.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) | ||||||
|  |                         { | ||||||
|  |                             foreach (var battlenpc in owner.zone.GetActorsAroundActor<BattleNpc>(owner, 1)) | ||||||
|  |                             { | ||||||
|  |                                 battlenpc.aiContainer.pathFind.PathInRange(targetPos, 1.5f, 1.5f); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 FaceTarget(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void FaceTarget() | ||||||
|  |         { | ||||||
|  |             // todo: check if stunned etc | ||||||
|  |             if (owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventAction)) | ||||||
|  |             { | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 owner.LookAt(owner.target); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool CanMoveForward(float distance) | ||||||
|  |         { | ||||||
|  |             // todo: check spawn leash and stuff | ||||||
|  |             if (!owner.IsCloseToSpawn()) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool CanAggroTarget(Character target) | ||||||
|  |         { | ||||||
|  |             if (owner.neutral || owner.aggroType == AggroType.None || owner.IsDead()) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // todo: can mobs aggro mounted targets? | ||||||
|  |             if (target.IsDead() || target.currentMainState == SetActorStatePacket.MAIN_STATE_MOUNTED) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (owner.aiContainer.IsSpawned() && !owner.aiContainer.IsEngaged() && CanDetectTarget(target)) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool CanDetectTarget(Character target, bool forceSight = false) | ||||||
|  |         { | ||||||
|  |             // todo: handle sight/scent/hp etc | ||||||
|  |             if (target.IsDead() || target.currentMainState == SetActorStatePacket.MAIN_STATE_MOUNTED) | ||||||
|  |                 return false; | ||||||
|  |  | ||||||
|  |             float verticalDistance = Math.Abs(target.positionY - owner.positionY); | ||||||
|  |             if (verticalDistance > 8) | ||||||
|  |                 return false; | ||||||
|  |  | ||||||
|  |             var distance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ); | ||||||
|  |  | ||||||
|  |             bool detectSight = forceSight || (owner.aggroType & AggroType.Sight) != 0; | ||||||
|  |             bool hasSneak = false; | ||||||
|  |             bool hasInvisible = false; | ||||||
|  |             bool isFacing = owner.IsFacing(target); | ||||||
|  |  | ||||||
|  |             // todo: check line of sight and aggroTypes | ||||||
|  |             if (distance > 20) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // todo: seems ffxiv doesnt even differentiate between sneak/invis? | ||||||
|  |             { | ||||||
|  |                 hasSneak = target.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.Stealth); | ||||||
|  |                 hasInvisible = hasSneak; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (detectSight && !hasInvisible && owner.IsFacing(target)) | ||||||
|  |                 return CanSeePoint(target.positionX, target.positionY, target.positionZ); | ||||||
|  |  | ||||||
|  |             if ((owner.aggroType & AggroType.LowHp) != 0 && target.GetHPP() < 75) | ||||||
|  |                 return CanSeePoint(target.positionX, target.positionY, target.positionZ); | ||||||
|  |  | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public bool CanSeePoint(float x, float y, float z) | ||||||
|  |         { | ||||||
|  |             return NavmeshUtils.CanSee((Zone)owner.zone, owner.positionX, owner.positionY, owner.positionZ, x, y, z); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void HandleHate() | ||||||
|  |         { | ||||||
|  |             ChangeTarget(owner.hateContainer.GetMostHatedTarget()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,16 +12,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|         protected Character owner; |         protected Character owner; | ||||||
|  |  | ||||||
|         protected DateTime lastUpdate; |         protected DateTime lastUpdate; | ||||||
|         protected bool canUpdate = true; |         public bool canUpdate = true; | ||||||
|         protected bool autoAttackEnabled = true; |         protected bool autoAttackEnabled = true; | ||||||
|         protected bool castingEnabled = true; |         protected bool castingEnabled = true; | ||||||
|         protected bool weaponSkillEnabled = true; |         protected bool weaponSkillEnabled = true; | ||||||
|         protected PathFind pathFind; |         protected PathFind pathFind; | ||||||
|         protected TargetFind targetFind; |         protected TargetFind targetFind; | ||||||
|  |  | ||||||
|  |         public Controller(Character owner) | ||||||
|  |         { | ||||||
|  |             this.owner = owner; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public abstract void Update(DateTime tick); |         public abstract void Update(DateTime tick); | ||||||
|         public abstract bool Engage(Character target); |         public abstract bool Engage(Character target); | ||||||
|         public abstract bool Disengage(); |  | ||||||
|         public abstract void Cast(Character target, uint spellId); |         public abstract void Cast(Character target, uint spellId); | ||||||
|         public virtual void WeaponSkill(Character target, uint weaponSkillId) { } |         public virtual void WeaponSkill(Character target, uint weaponSkillId) { } | ||||||
|         public virtual void MonsterSkill(Character target, uint mobSkillId) { } |         public virtual void MonsterSkill(Character target, uint mobSkillId) { } | ||||||
| @@ -31,6 +35,11 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|         public virtual void Despawn() { } |         public virtual void Despawn() { } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         public virtual void Disengage() | ||||||
|  |         { | ||||||
|  |             owner.aiContainer.InternalDisengage(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public virtual void ChangeTarget(Character target) |         public virtual void ChangeTarget(Character target) | ||||||
|         { |         { | ||||||
|             owner.aiContainer.InternalChangeTarget(target); |             owner.aiContainer.InternalChangeTarget(target); | ||||||
|   | |||||||
| @@ -11,9 +11,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|     { |     { | ||||||
|         private Character petMaster; |         private Character petMaster; | ||||||
|  |  | ||||||
|         public PetController(Character owner) |         public PetController(Character owner) : | ||||||
|  |             base(owner) | ||||||
|         { |         { | ||||||
|             this.owner = owner; |  | ||||||
|             this.lastUpdate = Program.Tick; |             this.lastUpdate = Program.Tick; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -33,10 +33,10 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override bool Disengage() |         public override void Disengage() | ||||||
|         { |         { | ||||||
|             // todo: |             // todo: | ||||||
|             return true; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Cast(Character target, uint spellId) |         public override void Cast(Character target, uint spellId) | ||||||
|   | |||||||
| @@ -4,22 +4,22 @@ using System.Linq; | |||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using FFXIVClassic_Map_Server.Actors; | using FFXIVClassic_Map_Server.Actors; | ||||||
|  | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
|  | using FFXIVClassic.Common; | ||||||
|  |  | ||||||
| namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | ||||||
| { | { | ||||||
|     class PlayerController : Controller |     class PlayerController : Controller | ||||||
|     { |     { | ||||||
|         public PlayerController(Character owner) |         public PlayerController(Character owner) : | ||||||
|  |             base(owner) | ||||||
|         { |         { | ||||||
|             this.owner = owner; |  | ||||||
|             this.lastUpdate = DateTime.Now; |             this.lastUpdate = DateTime.Now; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Update(DateTime tick) |         public override void Update(DateTime tick) | ||||||
|         { |         { | ||||||
|             // todo: handle player stuff on tick |             // todo: handle player stuff on tick | ||||||
|  |  | ||||||
|             ((Player)this.owner).statusEffects.Update(tick); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void ChangeTarget(Character target) |         public override void ChangeTarget(Character target) | ||||||
| @@ -29,14 +29,33 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers | |||||||
|  |  | ||||||
|         public override bool Engage(Character target) |         public override bool Engage(Character target) | ||||||
|         { |         { | ||||||
|             // todo: check distance, last swing time, status effects |             var canEngage = this.owner.aiContainer.InternalEngage(target); | ||||||
|             return true; |             if (canEngage) | ||||||
|  |             { | ||||||
|  |                 // todo: find a better place to put this? | ||||||
|  |                 if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE) | ||||||
|  |                     owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 // todo: check speed/is able to move | ||||||
|  |                 // todo: too far, path to player if mob, message if player | ||||||
|  |  | ||||||
|  |                 // todo: actual stat based range | ||||||
|  |                 if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > 10) | ||||||
|  |                 { | ||||||
|  |                     owner.aiContainer.pathFind.PreparePath(target.positionX, target.positionY, target.positionZ); | ||||||
|  |                     ChangeTarget(target); | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |                 // todo: adjust cooldowns with modifiers | ||||||
|  |             } | ||||||
|  |             return canEngage; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override bool Disengage() |         public override void Disengage() | ||||||
|         { |         { | ||||||
|             // todo: |             // todo: | ||||||
|             return true; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Cast(Character target, uint spellId) |         public override void Cast(Character target, uint spellId) | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ using System.Collections.Generic; | |||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
|  | using FFXIVClassic.Common; | ||||||
| using FFXIVClassic_Map_Server.Actors; | using FFXIVClassic_Map_Server.Actors; | ||||||
| using FFXIVClassic_Map_Server.packets.send.actor; | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
| using FFXIVClassic_Map_Server.packets.send.actor.battle; | using FFXIVClassic_Map_Server.packets.send.actor.battle; | ||||||
| @@ -10,20 +11,31 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state | |||||||
| { | { | ||||||
|     class AttackState : State |     class AttackState : State | ||||||
|     { |     { | ||||||
|  |         private int damage = 0; | ||||||
|  |         private bool tooFar = false; | ||||||
|  |         private DateTime attackTime; | ||||||
|  |  | ||||||
|         public AttackState(Character owner, Character target) : |         public AttackState(Character owner, Character target) : | ||||||
|             base(owner, target) |             base(owner, target) | ||||||
|         { |         { | ||||||
|  |             owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); | ||||||
|  |             owner.aiContainer.ChangeTarget(target); | ||||||
|             this.startTime = DateTime.Now; |             this.startTime = DateTime.Now; | ||||||
|  |             attackTime = startTime; | ||||||
|  |             owner.aiContainer.pathFind?.Clear(); | ||||||
|             // todo: should handle everything here instead of on next tick.. |             // todo: should handle everything here instead of on next tick.. | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void OnStart() |         public override void OnStart() | ||||||
|         { |         { | ||||||
|  |             // todo: check within attack range | ||||||
|              |              | ||||||
|  |             owner.LookAt(target); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override bool Update(DateTime tick) |         public override bool Update(DateTime tick) | ||||||
|         { |         { | ||||||
|  |             /* | ||||||
|             TryInterrupt(); |             TryInterrupt(); | ||||||
|  |  | ||||||
|             if (interrupt) |             if (interrupt) | ||||||
| @@ -31,12 +43,32 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state | |||||||
|                 OnInterrupt(); |                 OnInterrupt(); | ||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
|  |             */ | ||||||
|  |             if (owner.target == null || target.IsDead()) | ||||||
|  |             { | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             if (IsAttackReady()) | ||||||
|  |             { | ||||||
|  |                 if (CanAttack()) | ||||||
|  |                 { | ||||||
|  |                     TryInterrupt(); | ||||||
|  |  | ||||||
|                     // todo: check weapon delay/haste etc and use that |                     // todo: check weapon delay/haste etc and use that | ||||||
|             if ((tick - startTime).TotalMilliseconds >= 0) |                     if (!interrupt) | ||||||
|                     { |                     { | ||||||
|                         OnComplete(); |                         OnComplete(); | ||||||
|                 return true; |                     } | ||||||
|  |                     else | ||||||
|  |                     { | ||||||
|  |  | ||||||
|  |                     } | ||||||
|  |                     SetInterrupted(false); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     // todo: handle interrupt/paralyze etc | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| @@ -48,18 +80,20 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state | |||||||
|  |  | ||||||
|         public override void OnComplete() |         public override void OnComplete() | ||||||
|         { |         { | ||||||
|             var damage = utils.AttackUtils.CalculateDamage(owner, target); |             damage = utils.AttackUtils.CalculateDamage(owner, target); | ||||||
|  |  | ||||||
|             // onAttack(actor, target, damage) |             // onAttack(actor, target, damage) | ||||||
|  |             utils.BattleUtils.DamageTarget(owner, target, damage); | ||||||
|             lua.LuaEngine.CallLuaBattleAction(owner, "onAttack", false, owner, target, damage); |             lua.LuaEngine.CallLuaBattleAction(owner, "onAttack", false, owner, target, damage); | ||||||
|  |  | ||||||
|             //var packet = BattleAction1Packet.BuildPacket(owner.actorId, target.actorId); |             foreach (var player in owner.zone.GetActorsAroundActor<Player>(owner, 50)) | ||||||
|  |                 player.QueuePacket(BattleActionX01Packet.BuildPacket(player.actorId, owner.actorId, target.actorId, 223001, 18, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0)); | ||||||
|  |             if (target is Player) | ||||||
|  |                 ((Player)target).SendPacket("139.bin"); | ||||||
|  |  | ||||||
|             // todo: find a better place to put this? |             target.AddHP((short)damage); | ||||||
|             if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE) |             attackTime = attackTime.AddMilliseconds(owner.GetAttackDelayMs()); | ||||||
|                 owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); |            //this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0); | ||||||
|  |  | ||||||
|             isCompleted = true; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void TryInterrupt() |         public override void TryInterrupt() | ||||||
| @@ -75,7 +109,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state | |||||||
|                     effectId = list[0].GetStatusEffectId(); |                     effectId = list[0].GetStatusEffectId(); | ||||||
|                 } |                 } | ||||||
|                 // todo: which is actually the swing packet |                 // todo: which is actually the swing packet | ||||||
|                 //this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, 0, 0); |                 //this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0); | ||||||
|                 //owner.zone.BroadcastPacketAroundActor(owner, errorPacket); |                 //owner.zone.BroadcastPacketAroundActor(owner, errorPacket); | ||||||
|                 //errorPacket = null; |                 //errorPacket = null; | ||||||
|                 interrupt = true; |                 interrupt = true; | ||||||
| @@ -85,22 +119,43 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state | |||||||
|             interrupt = !CanAttack(); |             interrupt = !CanAttack(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         private bool IsAttackReady() | ||||||
|  |         { | ||||||
|  |             return Program.Tick >= attackTime; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         private bool CanAttack() |         private bool CanAttack() | ||||||
|         { |         { | ||||||
|  |             if (target == null) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|             // todo: shouldnt need to check if owner is dead since all states would be cleared |             // todo: shouldnt need to check if owner is dead since all states would be cleared | ||||||
|             if (owner.aiContainer.IsDead() || target.aiContainer.IsDead()) |             if (owner.aiContainer.IsDead() || target.aiContainer.IsDead()) | ||||||
|             { |             { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|             else if (target.zone != owner.zone) |             else if (!owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) | ||||||
|             { |             { | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|             else if (target is Player && ((Player)target).playerSession.isUpdatesLocked) |             else if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) >= 7.5f) | ||||||
|             { |             { | ||||||
|  |                 //owner.aiContainer.GetpathFind?.PreparePath(target.positionX, target.positionY, target.positionZ, 2.5f, 4); | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public override void Cleanup() | ||||||
|  |         { | ||||||
|  |             if (owner.IsDead()) | ||||||
|  |                 owner.Disengage(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override bool CanChangeState() | ||||||
|  |         { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								FFXIVClassic Map Server/actors/chara/ai/state/DeathState.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								FFXIVClassic Map Server/actors/chara/ai/state/DeathState.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using FFXIVClassic_Map_Server.Actors; | ||||||
|  | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
|  |  | ||||||
|  | namespace FFXIVClassic_Map_Server.actors.chara.ai.state | ||||||
|  | { | ||||||
|  |     class DeathState : State | ||||||
|  |     { | ||||||
|  |         DateTime despawnTime; | ||||||
|  |         public DeathState(Character owner, DateTime tick, uint timeToFadeOut)  | ||||||
|  |             : base(owner, null) | ||||||
|  |         { | ||||||
|  |             owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD); | ||||||
|  |             canInterrupt = false; | ||||||
|  |             startTime = tick; | ||||||
|  |             despawnTime = startTime.AddSeconds(timeToFadeOut); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override bool Update(DateTime tick) | ||||||
|  |         { | ||||||
|  |             // todo: handle raise etc | ||||||
|  |             if (tick >= despawnTime) | ||||||
|  |             { | ||||||
|  |                 if (owner.currentSubState == SetActorStatePacket.SUB_STATE_PLAYER) | ||||||
|  |                 { | ||||||
|  |                     owner.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE); | ||||||
|  |                     Server.GetWorldManager().DoZoneChange(((Player)owner), 244, null, 0, 15, -160.048f, 0, -165.737f, 0.0f); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     owner.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE); | ||||||
|  |                     // todo: fadeout animation and crap | ||||||
|  |                     //owner.zone.DespawnActor(owner); | ||||||
|  |                 } | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										114
									
								
								FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  | using FFXIVClassic.Common; | ||||||
|  | using FFXIVClassic_Map_Server.Actors; | ||||||
|  | using FFXIVClassic_Map_Server.packets.send.actor; | ||||||
|  | using FFXIVClassic_Map_Server.packets.send.actor.battle; | ||||||
|  | namespace FFXIVClassic_Map_Server.actors.chara.ai.state | ||||||
|  | { | ||||||
|  |     class MagicState : State | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         private Ability spell; | ||||||
|  |  | ||||||
|  |         public MagicState(Character owner, Character target, ushort spellId) : | ||||||
|  |             base(owner, target) | ||||||
|  |         { | ||||||
|  |             this.startTime = DateTime.Now; | ||||||
|  |             // todo: lookup spell from global table | ||||||
|  |             this.spell = Server.GetWorldManager().GetAbility(spellId); | ||||||
|  |  | ||||||
|  |             if (spell != null) | ||||||
|  |             { | ||||||
|  |                 if (spell.CanPlayerUse(owner, target)) | ||||||
|  |                     OnStart(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void OnStart() | ||||||
|  |         { | ||||||
|  |             // todo: check within attack range | ||||||
|  |  | ||||||
|  |             owner.LookAt(target); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override bool Update(DateTime tick) | ||||||
|  |         { | ||||||
|  |             TryInterrupt(); | ||||||
|  |  | ||||||
|  |             if (interrupt) | ||||||
|  |             { | ||||||
|  |                 OnInterrupt(); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // todo: check weapon delay/haste etc and use that | ||||||
|  |             if ((tick - startTime).TotalMilliseconds >= 0) | ||||||
|  |             { | ||||||
|  |                 OnComplete(); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void OnInterrupt() | ||||||
|  |         { | ||||||
|  |             // todo: send paralyzed/sleep message etc. | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void OnComplete() | ||||||
|  |         { | ||||||
|  |             //this.errorPacket = BattleActionX01Packet.BuildPacket(target.actorId, owner.actorId, target.actorId, 0, effectId, 0, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, 0); | ||||||
|  |             isCompleted = true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void TryInterrupt() | ||||||
|  |         { | ||||||
|  |             if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAction)) | ||||||
|  |             { | ||||||
|  |                 // 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; | ||||||
|  |                 if (list.Count > 0) | ||||||
|  |                 { | ||||||
|  |                     // todo: actually check proc rate/random chance of whatever effect | ||||||
|  |                     effectId = list[0].GetStatusEffectId(); | ||||||
|  |                 } | ||||||
|  |                 // 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); | ||||||
|  |                 //owner.zone.BroadcastPacketAroundActor(owner, errorPacket); | ||||||
|  |                 //errorPacket = null; | ||||||
|  |                 interrupt = true; | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             interrupt = !CanAttack(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private bool CanAttack() | ||||||
|  |         { | ||||||
|  |             if (target == null) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             // todo: shouldnt need to check if owner is dead since all states would be cleared | ||||||
|  |             if (owner.aiContainer.IsDead() || target.aiContainer.IsDead()) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             else if (!owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) | ||||||
|  |             { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             else if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) >= 7.5f) | ||||||
|  |             { | ||||||
|  |                 owner.aiContainer.pathFind?.PreparePath(target.positionX, target.positionY, target.positionZ, 2.5f, 4); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -34,7 +34,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.state | |||||||
|         public virtual void OnStart() {  } |         public virtual void OnStart() {  } | ||||||
|         public virtual void OnInterrupt() { } |         public virtual void OnInterrupt() { } | ||||||
|         public virtual void OnComplete() { isCompleted = true; } |         public virtual void OnComplete() { isCompleted = true; } | ||||||
|  |         public virtual bool CanChangeState() { return false; } | ||||||
|         public virtual void TryInterrupt() { } |         public virtual void TryInterrupt() { } | ||||||
|  |  | ||||||
|         public virtual void Cleanup() { } |         public virtual void Cleanup() { } | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.ai.utils | |||||||
|  |  | ||||||
|             return dmg; |             return dmg; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public static int CalculateBaseDamage(Character attacker, Character defender) |         public static int CalculateBaseDamage(Character attacker, Character defender) | ||||||
|         { |         { | ||||||
|             // todo: actually calculate damage |             // todo: actually calculate damage | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								FFXIVClassic Map Server/actors/chara/ai/utils/BattleUtils.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								FFXIVClassic Map Server/actors/chara/ai/utils/BattleUtils.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | using System; | ||||||
|  | using System.Collections.Generic; | ||||||
|  | using System.Linq; | ||||||
|  | using System.Text; | ||||||
|  | using System.Threading.Tasks; | ||||||
|  |  | ||||||
|  | using FFXIVClassic_Map_Server.Actors; | ||||||
|  |  | ||||||
|  | namespace FFXIVClassic_Map_Server.actors.chara.ai.utils | ||||||
|  | { | ||||||
|  |     static class BattleUtils | ||||||
|  |     { | ||||||
|  |         public static void DamageTarget(Character attacker, Character defender, int damage) | ||||||
|  |         { | ||||||
|  |             // todo: other stuff too | ||||||
|  |             if (defender is BattleNpc) | ||||||
|  |             { | ||||||
|  |                 if (!((BattleNpc)defender).hateContainer.HasHateForTarget(attacker)) | ||||||
|  |                 { | ||||||
|  |                     ((BattleNpc)defender).hateContainer.AddBaseHate(attacker); | ||||||
|  |                 } | ||||||
|  |                 ((BattleNpc)defender).hateContainer.UpdateHate(attacker, damage); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -3,6 +3,7 @@ using System.Collections.Generic; | |||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Text; | using System.Text; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
|  | using FFXIVClassic.Common; | ||||||
| using FFXIVClassic_Map_Server.Actors; | using FFXIVClassic_Map_Server.Actors; | ||||||
| using FFXIVClassic_Map_Server.actors.chara.npc; | using FFXIVClassic_Map_Server.actors.chara.npc; | ||||||
| using FFXIVClassic_Map_Server.actors; | using FFXIVClassic_Map_Server.actors; | ||||||
| @@ -28,7 +29,11 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|     { |     { | ||||||
|         public HateContainer hateContainer; |         public HateContainer hateContainer; | ||||||
|         public AggroType aggroType; |         public AggroType aggroType; | ||||||
|  |         public bool neutral; | ||||||
|  |         private uint despawnTime; | ||||||
|  |         private uint spawnDistance; | ||||||
|  |  | ||||||
|  |         private float spawnX, spawnY, spawnZ; | ||||||
|         public BattleNpc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, |         public BattleNpc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, | ||||||
|             ushort actorState, uint animationId, string customDisplayName) |             ushort actorState, uint animationId, string customDisplayName) | ||||||
|             : base(actorNumber, actorClass, uniqueId, spawnedArea, posX, posY, posZ, rot, actorState, animationId, customDisplayName)   |             : base(actorNumber, actorClass, uniqueId, spawnedArea, posX, posY, posZ, rot, actorState, animationId, customDisplayName)   | ||||||
| @@ -43,14 +48,31 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|  |  | ||||||
|             this.hateContainer = new HateContainer(this); |             this.hateContainer = new HateContainer(this); | ||||||
|             this.allegiance = CharacterTargetingAllegiance.BattleNpcs; |             this.allegiance = CharacterTargetingAllegiance.BattleNpcs; | ||||||
|  |  | ||||||
|  |             spawnX = posX; | ||||||
|  |             spawnY = posY; | ||||||
|  |             spawnZ = posZ; | ||||||
|  |  | ||||||
|  |             // todo: read this from db | ||||||
|  |             aggroType = AggroType.Sight; | ||||||
|  |             this.moveState = 2; | ||||||
|  |             ResetMoveSpeeds(); | ||||||
|  |             this.meleeRange = 1.5f; | ||||||
|  |             despawnTime = 10; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Update(DateTime tick) |         public override void Update(DateTime tick) | ||||||
|         { |         { | ||||||
|             // todo: |             this.aiContainer.Update(tick); | ||||||
|             this.statusEffects.Update(tick); |             this.statusEffects.Update(tick); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public override bool CanAttack() | ||||||
|  |         { | ||||||
|  |              | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         ///<summary> // todo: create an action object? </summary> |         ///<summary> // todo: create an action object? </summary> | ||||||
|         public bool OnAttack(AttackState state) |         public bool OnAttack(AttackState state) | ||||||
|         { |         { | ||||||
| @@ -60,16 +82,77 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|         public override void Spawn(DateTime tick) |         public override void Spawn(DateTime tick) | ||||||
|         { |         { | ||||||
|             base.Spawn(tick); |             base.Spawn(tick); | ||||||
|  |  | ||||||
|  |             this.isMovingToSpawn = false; | ||||||
|  |             this.ResetMoveSpeeds(); | ||||||
|  |             this.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public override void Die(DateTime tick) |         public override void Die(DateTime tick) | ||||||
|         { |         { | ||||||
|             base.Die(tick); |             if (IsAlive()) | ||||||
|  |             { | ||||||
|  |                 aiContainer.InternalDie(tick, despawnTime); | ||||||
|  |  | ||||||
|  |                 this.ResetMoveSpeeds(); | ||||||
|  |                 this.positionX = oldPositionX; | ||||||
|  |                 this.positionY = oldPositionY; | ||||||
|  |                 this.positionZ = oldPositionZ; | ||||||
|  |                 this.isAtSpawn = true; | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 var err = $"[{actorId}][{customDisplayName}] {positionX} {positionY} {positionZ} {GetZoneID()} tried to die ded"; | ||||||
|  |                 Program.Log.Error(err); | ||||||
|  |                 //throw new Exception(err); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public void OnRoam(DateTime tick) |         public void OnRoam(DateTime tick) | ||||||
|         { |         { | ||||||
|  |             // todo: move this to battlenpccontroller.. | ||||||
|  |             bool foundActor = false; | ||||||
|  |  | ||||||
|  |             // leash back to spawn | ||||||
|  |             if (!IsCloseToSpawn()) | ||||||
|  |             { | ||||||
|  |                 isMovingToSpawn = true; | ||||||
|  |                 aiContainer.Reset(); | ||||||
|  |             } | ||||||
|  |             else | ||||||
|  |             { | ||||||
|  |                 this.isMovingToSpawn = false; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // dont bother checking for any in-range players if going back to spawn | ||||||
|  |             if (!this.isMovingToSpawn && 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); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (target == null) | ||||||
|  |                 aiContainer.pathFind.PathInRange(spawnX, spawnY, spawnZ, 1.0f, 35.0f); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public uint GetDespawnTime() | ||||||
|  |         { | ||||||
|  |             return despawnTime; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public void SetDespawnTime(uint seconds) | ||||||
|  |         { | ||||||
|  |             despawnTime = seconds; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         public bool IsCloseToSpawn() | ||||||
|  |         { | ||||||
|  |             return this.isAtSpawn = Utils.DistanceSquared(positionX, positionY, positionZ, spawnX, spawnY, spawnZ) <= 2500.0f; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -396,7 +396,8 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|  |  | ||||||
|         public override void Update(DateTime tick) |         public override void Update(DateTime tick) | ||||||
|         { |         { | ||||||
|          |             // todo: can normal npcs have status effects? | ||||||
|  |             aiContainer.Update(tick); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         //A party member list packet came, set the party |         //A party member list packet came, set the party | ||||||
|   | |||||||
| @@ -611,10 +611,12 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|         { |         { | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                // BasePacket packet = new BasePacket(path); |                 BasePacket packet = new BasePacket(path); | ||||||
|  |  | ||||||
|                 //packet.ReplaceActorID(actorId); |                 packet.ReplaceActorID(actorId); | ||||||
|                 //QueuePacket(packet); |                 var packets = packet.GetSubpackets(); | ||||||
|  |  | ||||||
|  |                 QueuePackets(packets); | ||||||
|             } |             } | ||||||
|             catch (Exception e) |             catch (Exception e) | ||||||
|             { |             { | ||||||
| @@ -1687,6 +1689,64 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|             LuaEngine.GetInstance().CallLuaFunction(this, this, "OnUpdate", true, delta); |             LuaEngine.GetInstance().CallLuaFunction(this, this, "OnUpdate", true, delta); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public override void Update(DateTime tick) | ||||||
|  |         { | ||||||
|  |             aiContainer.Update(tick); | ||||||
|  |             statusEffects.Update(tick); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void PostUpdate(DateTime tick, List<SubPacket> packets = null) | ||||||
|  |         { | ||||||
|  |             base.PostUpdate(tick); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override short GetHP() | ||||||
|  |         { | ||||||
|  |             return charaWork.parameterSave.hp[currentJob]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override short GetMaxHP() | ||||||
|  |         { | ||||||
|  |             return charaWork.parameterSave.hpMax[currentJob]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override byte GetHPP() | ||||||
|  |         { | ||||||
|  |             return (byte)(charaWork.parameterSave.hp[currentJob] / charaWork.parameterSave.hpMax[currentJob]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void AddHP(short hp) | ||||||
|  |         { | ||||||
|  |             // todo: +/- hp and die | ||||||
|  |             // todo: battlenpcs probably have way more hp? | ||||||
|  |             var addHp = charaWork.parameterSave.hp[currentJob] + hp; | ||||||
|  |             addHp = addHp.Clamp(short.MinValue, charaWork.parameterSave.hpMax[currentJob]); | ||||||
|  |             charaWork.parameterSave.hp[currentJob] = (short)addHp; | ||||||
|  |  | ||||||
|  |             if (charaWork.parameterSave.hp[0] < 1) | ||||||
|  |                 Die(Program.Tick); | ||||||
|  |  | ||||||
|  |             updateFlags |= ActorUpdateFlags.HpTpMp; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void DelHP(short hp) | ||||||
|  |         { | ||||||
|  |             AddHP((short)-hp); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // todo: should this include stats too? | ||||||
|  |         public override void RecalculateHpMpTp() | ||||||
|  |         { | ||||||
|  |             // todo: recalculate stats and crap | ||||||
|  |             updateFlags |= ActorUpdateFlags.HpTpMp; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public override void Die(DateTime tick) | ||||||
|  |         { | ||||||
|  |             // todo: death timer | ||||||
|  |             aiContainer.InternalDie(tick, 60); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         //Update all the hotbar slots past the commandborder. Commands before the commandborder only need to be sent on init since they never change |         //Update all the hotbar slots past the commandborder. Commands before the commandborder only need to be sent on init since they never change | ||||||
|         public ActorPropertyPacketUtil GetUpdateHotbarPacket(uint playerActorId) |         public ActorPropertyPacketUtil GetUpdateHotbarPacket(uint playerActorId) | ||||||
|         { |         { | ||||||
| @@ -1837,6 +1897,5 @@ namespace FFXIVClassic_Map_Server.Actors | |||||||
|  |  | ||||||
|             return firstSlot; |             return firstSlot; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ namespace FFXIVClassic_Map_Server.dataobjects | |||||||
|         public void QueuePacket(SubPacket subPacket) |         public void QueuePacket(SubPacket subPacket) | ||||||
|         { |         { | ||||||
|             subPacket.SetTargetId(id); |             subPacket.SetTargetId(id); | ||||||
|             Server.GetWorldConnection().QueuePacket(subPacket); |             Server.GetWorldConnection()?.QueuePacket(subPacket); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         public Player GetActor() |         public Player GetActor() | ||||||
| @@ -119,12 +119,12 @@ namespace FFXIVClassic_Map_Server.dataobjects | |||||||
|                 { |                 { | ||||||
|                     //Don't send for static characters (npcs) |                     //Don't send for static characters (npcs) | ||||||
|                     // todo: this is retarded, need actual mob class |                     // todo: this is retarded, need actual mob class | ||||||
|                     if (actor is Character && ((Character)actor).isStatic) |                     //if (actor is Character && ((Character)actor).isStatic) | ||||||
|                         continue; |                     //    continue; | ||||||
|  |  | ||||||
|                     var packet = actor.CreatePositionUpdatePacket(); |                     //var packet = actor.CreatePositionUpdatePacket(); | ||||||
|                     if (packet != null) |                     //if (packet != null) | ||||||
|                         QueuePacket(packet); |                     //    QueuePacket(packet); | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|                 {    |                 {    | ||||||
|   | |||||||
| @@ -491,9 +491,13 @@ namespace FFXIVClassic_Map_Server.lua | |||||||
|         public static void RunGMCommand(Player player, String cmd, string[] param, bool help = false) |         public static void RunGMCommand(Player player, String cmd, string[] param, bool help = false) | ||||||
|         { |         { | ||||||
|             bool playerNull = player == null; |             bool playerNull = player == null; | ||||||
|             if (playerNull && param.Length >= 2) |             if (playerNull) | ||||||
|                 player = Server.GetWorldManager().GetPCInWorld(param[1].Contains("\"") ? param[1] : param[1] + " " + param[2]); |             { | ||||||
|  |                 if (param.Length >= 2 && param[1].Contains("\"")) | ||||||
|  |                     player = Server.GetWorldManager().GetPCInWorld(param[1]); | ||||||
|  |                 else if (param.Length > 2) | ||||||
|  |                     player = Server.GetWorldManager().GetPCInWorld(param[1] + param[2]); | ||||||
|  |             } | ||||||
|             // load from scripts/commands/gm/ directory |             // load from scripts/commands/gm/ directory | ||||||
|             var path = String.Format("./scripts/commands/gm/{0}.lua", cmd.ToLower()); |             var path = String.Format("./scripts/commands/gm/{0}.lua", cmd.ToLower()); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,11 +16,35 @@ namespace FFXIVClassic_Map_Server.utils | |||||||
|     { |     { | ||||||
|  |  | ||||||
|         // navmesh |         // navmesh | ||||||
|         public static bool CanSee(float x1, float y1, float z1, float x2, float y2, float z2) |         public static bool CanSee(actors.area.Zone zone, float x1, float y1, float z1, float x2, float y2, float z2) | ||||||
|         { |         { | ||||||
|  |             // todo: prolly shouldnt raycast | ||||||
|  |             var navMesh = zone.tiledNavMesh; | ||||||
|  |             if (navMesh != null) | ||||||
|  |             { | ||||||
|  |                 var navMeshQuery = zone.navMeshQuery; | ||||||
|  |  | ||||||
|  |                 NavPoint startPt, endPt; | ||||||
|  |                 SharpNav.Pathfinding.Path path = new SharpNav.Pathfinding.Path(); | ||||||
|  |  | ||||||
|  |                 RaycastHit hit = new RaycastHit(); | ||||||
|  |  | ||||||
|  |                 SharpNav.Geometry.Vector3 c = new SharpNav.Geometry.Vector3(x1, y1, z1); | ||||||
|  |                 SharpNav.Geometry.Vector3 ep = new SharpNav.Geometry.Vector3(x2, y2, z2); | ||||||
|  |  | ||||||
|  |                 SharpNav.Geometry.Vector3 e = new SharpNav.Geometry.Vector3(5, 5, 5); | ||||||
|  |                 navMeshQuery.FindNearestPoly(ref c, ref e, out startPt); | ||||||
|  |                 navMeshQuery.FindNearestPoly(ref ep, ref e, out endPt); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 if (navMeshQuery.Raycast(ref startPt, ref ep, RaycastOptions.None, out hit, path)) | ||||||
|  |                 { | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         public static SharpNav.TiledNavMesh LoadNavmesh(TiledNavMesh navmesh, string filePath) |         public static SharpNav.TiledNavMesh LoadNavmesh(TiledNavMesh navmesh, string filePath) | ||||||
|         { |         { | ||||||
| @@ -61,7 +85,7 @@ namespace FFXIVClassic_Map_Server.utils | |||||||
|             // no point pathing if in range |             // no point pathing if in range | ||||||
|             if (distanceSquared < 4 && Math.Abs(startVec.Y - endVec.Y) < 1.1f) |             if (distanceSquared < 4 && Math.Abs(startVec.Y - endVec.Y) < 1.1f) | ||||||
|             { |             { | ||||||
|                 return null; |                 return new List<Vector3>() { endVec }; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             var smoothPath = new List<Vector3>(pathSize) { }; |             var smoothPath = new List<Vector3>(pathSize) { }; | ||||||
|   | |||||||
| @@ -58,4 +58,7 @@ Global | |||||||
| 	GlobalSection(Performance) = preSolution | 	GlobalSection(Performance) = preSolution | ||||||
| 		HasPerformanceSessions = true | 		HasPerformanceSessions = true | ||||||
| 	EndGlobalSection | 	EndGlobalSection | ||||||
|  | 	GlobalSection(Performance) = preSolution | ||||||
|  | 		HasPerformanceSessions = true | ||||||
|  | 	EndGlobalSection | ||||||
| EndGlobal | EndGlobal | ||||||
|   | |||||||
| @@ -11,12 +11,12 @@ Switches between active and passive mode states | |||||||
| function onEventStarted(player, command, triggerName)		 | function onEventStarted(player, command, triggerName)		 | ||||||
| 	 | 	 | ||||||
| 	if (player:GetState() == 0) then | 	if (player:GetState() == 0) then | ||||||
| 		player:ChangeState(2); | 		player.Engage(); | ||||||
| 	elseif (player:GetState() == 2) then | 	elseif (player:GetState() == 2) then | ||||||
| 		player:ChangeState(0); | 		player:ChangeState(0); | ||||||
|  |         player.Disengage(); | ||||||
| 	end		 | 	end		 | ||||||
| 	player:endEvent(); | 	player:endEvent(); | ||||||
| 	 |  | ||||||
| 	sendSignal("playerActive"); | 	sendSignal("playerActive"); | ||||||
| 	 | 	 | ||||||
| end | end | ||||||
		Reference in New Issue
	
	Block a user