diff --git a/FFXIVClassic Common Class Lib/BasePacket.cs b/FFXIVClassic Common Class Lib/BasePacket.cs index 9f9a4c4c..5532d079 100644 --- a/FFXIVClassic Common Class Lib/BasePacket.cs +++ b/FFXIVClassic Common Class Lib/BasePacket.cs @@ -239,7 +239,14 @@ namespace FFXIVClassic.Common { var subpacketData = subpacket.GetBytes(); Array.Copy(subpacketData, 0, data, offset, subpacketData.Length); - offset += (ushort) subpacketData.Length; + offset += (ushort)subpacketData.Length; + } + + //Compress this array into a new one if needed + if (isCompressed) + { + data = CompressData(data); + header.packetSize = (ushort)(BASEPACKET_SIZE + data.Length); } Debug.Assert(data != null && offset == data.Length && header.packetSize == 0x10 + offset); @@ -266,7 +273,15 @@ namespace FFXIVClassic.Common data = new byte[header.packetSize - 0x10]; //Add Subpackets - var subpacketData = subpacket.GetBytes(); + byte[] subpacketData = subpacket.GetBytes(); + + //Compress this array into a new one if needed + if (isCompressed) + { + subpacketData = CompressData(subpacketData); + header.packetSize = (ushort)(BASEPACKET_SIZE + data.Length); + } + Array.Copy(subpacketData, 0, data, 0, subpacketData.Length); Debug.Assert(data != null); @@ -291,6 +306,13 @@ namespace FFXIVClassic.Common //Get packet size header.packetSize += (ushort) data.Length; + //Compress this array into a new one if needed + if (isCompressed) + { + data = CompressData(data); + header.packetSize = (ushort)(BASEPACKET_SIZE + data.Length); + } + var packet = new BasePacket(header, data); return packet; } @@ -390,17 +412,31 @@ namespace FFXIVClassic.Common { zipStream.CopyTo(resultStream); packet.data = resultStream.ToArray(); + packet.header.isCompressed = 0; + packet.header.packetSize = (ushort)(BASEPACKET_SIZE + packet.data.Length); } } - public static unsafe void CompressPacket(ref BasePacket packet) + public static unsafe BasePacket CompressPacket(BasePacket uncompressedPacket) { - using (var compressedStream = new MemoryStream(packet.data)) + using (var compressedStream = new MemoryStream(uncompressedPacket.data)) using (var zipStream = new ZlibStream(compressedStream, Ionic.Zlib.CompressionMode.Compress)) using (var resultStream = new MemoryStream()) { zipStream.CopyTo(resultStream); - packet.data = resultStream.ToArray(); + BasePacket compressedPacket = BasePacket.CreatePacket(resultStream.ToArray(), uncompressedPacket.header.isAuthenticated == 1, true); + return compressedPacket; + } + } + + public static unsafe byte[] CompressData(byte[] data) + { + using (var compressedStream = new MemoryStream(data)) + using (var zipStream = new ZlibStream(compressedStream, Ionic.Zlib.CompressionMode.Compress)) + using (var resultStream = new MemoryStream()) + { + zipStream.CopyTo(resultStream); + return resultStream.ToArray(); } } diff --git a/FFXIVClassic Common Class Lib/Bitfield.cs b/FFXIVClassic Common Class Lib/Bitfield.cs index 8a54eec3..3ef5e7b5 100644 --- a/FFXIVClassic Common Class Lib/Bitfield.cs +++ b/FFXIVClassic Common Class Lib/Bitfield.cs @@ -60,7 +60,7 @@ namespace FFXIVClassic.Common // Calculate a bitmask of the desired length long mask = 0; for (int i = 0; i < fieldLength; i++) - mask |= 1 << i; + mask |= 1L << i; r |= ((UInt32)f.GetValue(t) & mask) << offset; diff --git a/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj b/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj index a08e253f..a2c8c826 100644 --- a/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj +++ b/FFXIVClassic Common Class Lib/FFXIVClassic Common Class Lib.csproj @@ -1,7 +1,7 @@  - - + + Debug AnyCPU @@ -10,7 +10,7 @@ Properties FFXIVClassic.Common FFXIVClassic.Common - v4.5 + v4.5.1 512 @@ -36,6 +36,27 @@ prompt 4 false + true + + + true + bin\Debug\ + DEBUG;TRACE + true + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + TRACE + true + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset @@ -51,6 +72,7 @@ + @@ -68,6 +90,7 @@ + diff --git a/FFXIVClassic Common Class Lib/SubPacket.cs b/FFXIVClassic Common Class Lib/SubPacket.cs index 38394e29..585e5c6b 100644 --- a/FFXIVClassic Common Class Lib/SubPacket.cs +++ b/FFXIVClassic Common Class Lib/SubPacket.cs @@ -72,9 +72,9 @@ namespace FFXIVClassic.Common offset += header.subpacketSize; } - public SubPacket(ushort opcode, uint sourceId, uint targetId, byte[] data) : this(true, opcode, sourceId, targetId, data) { } + public SubPacket(ushort opcode, uint sourceId, byte[] data) : this(true, opcode, sourceId, data) { } - public SubPacket(bool isGameMessage, ushort opcode, uint sourceId, uint targetId, byte[] data) + public SubPacket(bool isGameMessage, ushort opcode, uint sourceId, byte[] data) { header = new SubPacketHeader(); @@ -89,7 +89,7 @@ namespace FFXIVClassic.Common } header.sourceId = sourceId; - header.targetId = targetId; + header.targetId = 0; if (isGameMessage) header.type = 0x03; @@ -117,6 +117,11 @@ namespace FFXIVClassic.Common data = original.data; } + public void SetTargetId(uint target) + { + this.header.targetId = target; + } + public byte[] GetHeaderBytes() { var size = Marshal.SizeOf(header); diff --git a/FFXIVClassic Common Class Lib/Utils.cs b/FFXIVClassic Common Class Lib/Utils.cs index a6479b78..3465b5ab 100644 --- a/FFXIVClassic Common Class Lib/Utils.cs +++ b/FFXIVClassic Common Class Lib/Utils.cs @@ -7,6 +7,7 @@ namespace FFXIVClassic.Common public static class Utils { private static readonly uint[] _lookup32 = CreateLookup32(); + private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static uint[] CreateLookup32() { @@ -84,10 +85,10 @@ namespace FFXIVClassic.Common return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); } - public static uint UnixTimeStampUTC() + public static uint UnixTimeStampUTC(DateTime? time = null) { uint unixTimeStamp; - var currentTime = DateTime.Now; + var currentTime = time ?? DateTime.Now; var zuluTime = currentTime.ToUniversalTime(); var unixEpoch = new DateTime(1970, 1, 1); unixTimeStamp = (uint)zuluTime.Subtract(unixEpoch).TotalSeconds; @@ -95,10 +96,10 @@ namespace FFXIVClassic.Common return unixTimeStamp; } - public static ulong MilisUnixTimeStampUTC() + public static ulong MilisUnixTimeStampUTC(DateTime? time = null) { ulong unixTimeStamp; - var currentTime = DateTime.Now; + var currentTime = time ?? DateTime.Now; var zuluTime = currentTime.ToUniversalTime(); var unixEpoch = new DateTime(1970, 1, 1); unixTimeStamp = (ulong)zuluTime.Subtract(unixEpoch).TotalMilliseconds; @@ -106,6 +107,11 @@ namespace FFXIVClassic.Common return unixTimeStamp; } + public static DateTime UnixTimeStampToDateTime(uint timestamp) + { + return epoch.AddSeconds(timestamp); + } + public static ulong SwapEndian(ulong input) { return 0x00000000000000FF & (input >> 56) | @@ -351,5 +357,95 @@ namespace FFXIVClassic.Common { return (value >> bits) | (value << (16 - bits)); } + + public static T Clamp(this T value, T min, T max) where T : IComparable + { + if (value.CompareTo(min) < 0) + return min; + else if (value.CompareTo(max) > 0) + return max; + else + return value; + } + + public static T Min(this T value, T min) where T : IComparable + { + if (value.CompareTo(min) > 0) + return min; + else + return value; + } + + public static T Max(this T value, T max) where T : IComparable + { + + if (value.CompareTo(max) < 0) + return max; + else + return value; + } + + public static float DistanceSquared(Vector3 lhs, Vector3 rhs) + { + return DistanceSquared(lhs.X, lhs.Y, lhs.Z, rhs.X, rhs.Y, rhs.Z); + } + + public static float Distance(Vector3 lhs, Vector3 rhs) + { + return Distance(lhs.X, lhs.Y, lhs.Z, rhs.X, rhs.Y, rhs.Z); + } + + public static float Distance(float x, float y, float z, float x2, float y2, float z2) + { + if (x == x2 && y == y2 && z == z2) + return 0.0f; + + return (float)Math.Sqrt(DistanceSquared(x, y, z, x2, y2, z2)); + } + + public static float DistanceSquared(float x, float y, float z, float x2, float y2, float z2) + { + if (x == x2 && y == y2 && z == z2) + return 0.0f; + + // todo: my maths is shit + var dx = x - x2; + var dy = y - y2; + var dz = z - z2; + + return dx * dx + dy * dy + dz * dz; + } + + //Distance of just the x and z valeus, ignoring y + public static float XZDistanceSquared(Vector3 lhs, Vector3 rhs) + { + return XZDistanceSquared(lhs.X, lhs.Z, rhs.X, rhs.Z); + } + + public static float XZDistance(Vector3 lhs, Vector3 rhs) + { + return XZDistance(lhs.X, lhs.Z, rhs.X, rhs.Z); + } + + public static float XZDistance(float x, float z, float x2, float z2) + { + if (x == x2 && z == z2) + return 0.0f; + + return (float)Math.Sqrt(XZDistanceSquared(x, z, x2, z2)); + } + + + public static float XZDistanceSquared(float x, float z, float x2, float z2) + { + if (x == x2 && z == z2) + return 0.0f; + + // todo: mz maths is shit + var dx = x - x2; + var dz = z - z2; + + return dx * dx + dz * dz; + } } } \ No newline at end of file diff --git a/FFXIVClassic Common Class Lib/Vector3.cs b/FFXIVClassic Common Class Lib/Vector3.cs new file mode 100644 index 00000000..a938c8cb --- /dev/null +++ b/FFXIVClassic Common Class Lib/Vector3.cs @@ -0,0 +1,159 @@ +using System; + +namespace FFXIVClassic.Common +{ + public class Vector3 + { + public float X; + public float Y; + public float Z; + public static Vector3 Zero = new Vector3(); + + public Vector3(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + + public Vector3() + { + X = 0.0f; + Y = 0.0f; + Z = 0.0f; + } + + public static Vector3 operator +(Vector3 lhs, Vector3 rhs) + { + Vector3 newVec = new Vector3(lhs.X, lhs.Y, lhs.Z); + newVec.X += rhs.X; + newVec.Y += rhs.Y; + newVec.Z += rhs.Z; + return newVec; + } + + public static Vector3 operator -(Vector3 lhs, Vector3 rhs) + { + return new Vector3(lhs.X - rhs.X, lhs.Y - rhs.Y, lhs.Z - rhs.Z); + } + + public static Vector3 operator *(Vector3 lhs, Vector3 rhs) + { + return new Vector3(lhs.X * rhs.X, lhs.Y * rhs.Y, lhs.Z * rhs.Z); + } + + public static Vector3 operator *(float scalar, Vector3 rhs) + { + return new Vector3(scalar * rhs.X, scalar * rhs.Y, scalar * rhs.Z); + } + + public static Vector3 operator /(Vector3 lhs, float scalar) + { + return new Vector3(lhs.X / scalar, lhs.Y / scalar, lhs.Z / scalar); + } + + public static bool operator !=(Vector3 lhs, Vector3 rhs) + { + return !(lhs?.X == rhs?.X && lhs?.Y == rhs?.Y && lhs?.Z == rhs?.Z); + } + + public static bool operator ==(Vector3 lhs, Vector3 rhs) + { + return (lhs?.X == rhs?.X && lhs?.Y == rhs?.Y && lhs?.Z == rhs?.Z); + } + + public float Length() + { + return (float)Math.Sqrt(this.LengthSquared()); + } + + public float LengthSquared() + { + return (this.X * this.X) + (this.Y * this.Y) + (this.Z * this.Z); + } + + public static float Dot(Vector3 lhs, Vector3 rhs) + { + return (lhs.X * rhs.X) + (lhs.Y * rhs.Y) + (lhs.Z * rhs.Z); + } + + public static float GetAngle(Vector3 lhs, Vector3 rhs) + { + return GetAngle(lhs.X, lhs.Z, rhs.X, rhs.Z); + } + + public static float GetAngle(float x, float z, float x2, float z2) + { + if (x == x2) + return 0.0f; + + var angle = (float)(Math.Atan((z2 - z) / (x2 - x))); + return (float)(x > x2 ? angle + Math.PI : angle); + } + + public Vector3 NewHorizontalVector(float angle, float extents) + { + var newVec = new Vector3(); + newVec.Y = this.Y; + newVec.X = this.X + (float)Math.Cos(angle) * extents; + newVec.Z = this.Z + (float)Math.Sin(angle) * extents; + + return newVec; + } + + public bool IsWithinCircle(Vector3 center, float maxRadius, float minRadius) + { + if (this.X == center.X && this.Z == center.Z) + return true; + + float diffX = center.X - this.X; + float diffZ = center.Z - this.Z; + + float distance = Utils.XZDistance(center.X, center.Z, X, Z); + + return distance <= maxRadius && distance >= minRadius; + } + + public bool IsWithinBox(Vector3 upperLeftCorner, Vector3 lowerRightCorner) + { + return upperLeftCorner.X <= this.X && + upperLeftCorner.Y <= this.Y && + upperLeftCorner.Z <= this.Z && + lowerRightCorner.X >= this.X && + lowerRightCorner.Y >= this.Y && + lowerRightCorner.Z >= this.Z; + } + + //Checks if this vector is in a cone, note it doesn't check for distance + public bool IsWithinCone(Vector3 coneCenter, float coneRotation, float coneAngle) + { + float angleToTarget = GetAngle(coneCenter, this); + float halfAngleOfAoe = (float) (coneAngle * Math.PI / 2); + float rotationToAdd = coneRotation + halfAngleOfAoe; + + //This is the angle relative to the lower angle of the cone + angleToTarget = (angleToTarget + rotationToAdd - (0.5f * (float)Math.PI)) % (2 * (float) Math.PI); + + //If the relative angle is less than the total angle of the cone, the target is inside the cone + return angleToTarget >= 0 && angleToTarget <= (coneAngle * Math.PI); + } + + public override bool Equals(object obj) + { + var vector = obj as Vector3; + return vector != null && + X == vector.X && + Y == vector.Y && + Z == vector.Z; + } + + public override int GetHashCode() + { + var hashCode = -307843816; + hashCode = hashCode * -1521134295 + X.GetHashCode(); + hashCode = hashCode * -1521134295 + Y.GetHashCode(); + hashCode = hashCode * -1521134295 + Z.GetHashCode(); + return hashCode; + } + } +} diff --git a/FFXIVClassic Common Class Lib/app.config b/FFXIVClassic Common Class Lib/app.config index 143834c2..e418f4e3 100644 --- a/FFXIVClassic Common Class Lib/app.config +++ b/FFXIVClassic Common Class Lib/app.config @@ -1,9 +1,9 @@ - + - - + + - \ No newline at end of file + diff --git a/FFXIVClassic Lobby Server/ClientConnection.cs b/FFXIVClassic Lobby Server/ClientConnection.cs index 1bc1208b..91be70d6 100644 --- a/FFXIVClassic Lobby Server/ClientConnection.cs +++ b/FFXIVClassic Lobby Server/ClientConnection.cs @@ -58,7 +58,7 @@ namespace FFXIVClassic_Lobby_Server socket.Send(packetBytes); } catch(Exception e) - { Program.Log.Error("Weird case, socket was d/ced: {0}", e); } + { Program.Log.Error(e, "Weird case, socket was d/ced: {0}"); } } } diff --git a/FFXIVClassic Lobby Server/Database.cs b/FFXIVClassic Lobby Server/Database.cs index deb776b4..cd17e498 100644 --- a/FFXIVClassic Lobby Server/Database.cs +++ b/FFXIVClassic Lobby Server/Database.cs @@ -1,6 +1,5 @@ using FFXIVClassic_Lobby_Server.dataobjects; using MySql.Data.MySqlClient; -using Dapper; using System; using System.Collections.Generic; using System.Linq; @@ -219,7 +218,7 @@ namespace FFXIVClassic_Lobby_Server { MySqlCommand cmd = new MySqlCommand(); cmd.Connection = conn; - cmd.CommandText = String.Format("INSERT INTO characters_parametersave(characterId, hp, hpMax, mp, mpMax, mainSkill, mainSkillLevel) VALUES(@characterId, 1, 1, 1, 1, @mainSkill, 1);", CharacterCreatorUtils.GetClassNameForId((short)charaInfo.currentClass)); + cmd.CommandText = String.Format("INSERT INTO characters_parametersave(characterId, hp, hpMax, mp, mpMax, mainSkill, mainSkillLevel) VALUES(@characterId, 1900, 1000, 115, 115, @mainSkill, 1);", CharacterCreatorUtils.GetClassNameForId((short)charaInfo.currentClass)); cmd.Prepare(); cmd.Parameters.AddWithValue("@characterId", cid); @@ -231,15 +230,51 @@ namespace FFXIVClassic_Lobby_Server catch (MySqlException e) { Program.Log.Error(e.ToString()); - + conn.Dispose(); + return; + } + //Create Hotbar + try + { + MySqlCommand cmd = new MySqlCommand(); + cmd.Connection = conn; + cmd.CommandText = "SELECT id FROM server_battle_commands WHERE classJob = @classjob AND lvl = 1 ORDER BY id DESC"; + cmd.Prepare(); + + cmd.Parameters.AddWithValue("@classJob", charaInfo.currentClass); + List defaultActions = new List(); + using (var reader = cmd.ExecuteReader()) + { + while(reader.Read()) + { + defaultActions.Add(reader.GetUInt32("id")); + } + } + MySqlCommand cmd2 = new MySqlCommand(); + cmd2.Connection = conn; + cmd2.CommandText = "INSERT INTO characters_hotbar (characterId, classId, hotbarSlot, commandId, recastTime) VALUES (@characterId, @classId, @hotbarSlot, @commandId, 0)"; + cmd2.Prepare(); + cmd2.Parameters.AddWithValue("@characterId", cid); + cmd2.Parameters.AddWithValue("@classId", charaInfo.currentClass); + cmd2.Parameters.Add("@hotbarSlot", MySqlDbType.Int16); + cmd2.Parameters.Add("@commandId", MySqlDbType.Int16); + + for(int i = 0; i < defaultActions.Count; i++) + { + cmd2.Parameters["@hotbarSlot"].Value = i; + cmd2.Parameters["@commandId"].Value = defaultActions[i]; + cmd2.ExecuteNonQuery(); + } + } + catch(MySqlException e) + { + Program.Log.Error(e.ToString()); } finally { conn.Dispose(); } - - } Program.Log.Debug("[SQL] CID={0} state updated to active(2).", cid); @@ -325,48 +360,105 @@ namespace FFXIVClassic_Lobby_Server public static List GetServers() { - using (var 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))) + string query; + MySqlCommand cmd; + List worldList = new List(); + + 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))) { - List worldList = null; try { conn.Open(); - worldList = conn.Query("SELECT * FROM servers WHERE isActive=true").ToList(); + query = "SELECT * FROM servers WHERE isActive=true"; + cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + ushort id; + string address; + ushort port; + ushort listPosition; + ushort population; + string name; + bool isActive; + + id = reader.GetUInt16("id"); + address = reader.GetString("address"); + port = reader.GetUInt16("port"); + listPosition = reader.GetUInt16("listPosition"); + population = 2; + name = reader.GetString("name"); + isActive = reader.GetBoolean("isActive"); + + worldList.Add(new World(id, address, port, listPosition, population, name, isActive)); + } + } } catch (MySqlException e) { Program.Log.Error(e.ToString()); - worldList = new List(); } + worldList = new List(); + } finally - { + { conn.Dispose(); } - return worldList; } + return worldList; } public static World GetServer(uint serverId) { - using (var 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))) + string query; + MySqlCommand cmd; + World world = null; + + 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))) { - World world = null; try { conn.Open(); - world = conn.Query("SELECT * FROM servers WHERE id=@ServerId", new {ServerId = serverId}).SingleOrDefault(); + query = "SELECT * FROM servers WHERE id=@ServerId"; + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@ServerId", serverId); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + ushort id; + string address; + ushort port; + ushort listPosition; + ushort population; + string name; + bool isActive; + + id = reader.GetUInt16("id"); + address = reader.GetString("address"); + port = reader.GetUInt16("port"); + listPosition = reader.GetUInt16("listPosition"); + population = 2; //TODO + name = reader.GetString("name"); + isActive = reader.GetBoolean("isActive"); + + world = new World(id, address, port, listPosition, population, name, isActive); + } + } } catch (MySqlException e) { - Program.Log.Error(e.ToString()); - + Program.Log.Error(e.ToString()); } finally { conn.Dispose(); } - - return world; } + + return world; } public static List GetCharacters(uint userId) @@ -432,9 +524,11 @@ namespace FFXIVClassic_Lobby_Server Character chara = null; using (var 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))) { - conn.Open(); + try + { + conn.Open(); - string query = @" + string query = @" SELECT id, slot, @@ -454,100 +548,179 @@ namespace FFXIVClassic_Lobby_Server INNER JOIN characters_parametersave ON id = characters_parametersave.characterId WHERE id = @charId"; - MySqlCommand cmd = new MySqlCommand(query, conn); - cmd.Parameters.AddWithValue("@charId", charId); - using (MySqlDataReader reader = cmd.ExecuteReader()) - { - if (reader.Read()) + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", charId); + using (MySqlDataReader reader = cmd.ExecuteReader()) { - chara = new Character(); - chara.id = reader.GetUInt32("id"); - chara.slot = reader.GetUInt16("slot"); - chara.serverId = reader.GetUInt16("serverId"); - chara.name = reader.GetString("name"); - chara.isLegacy = reader.GetBoolean("isLegacy"); - chara.doRename = reader.GetBoolean("doRename"); - chara.currentZoneId = reader.GetUInt32("currentZoneId"); - chara.guardian = reader.GetByte("guardian"); - chara.birthMonth = reader.GetByte("birthMonth"); - chara.birthDay = reader.GetByte("birthDay"); - chara.initialTown = reader.GetByte("initialTown"); - chara.tribe = reader.GetByte("tribe"); - chara.currentClass = reader.GetByte("mainSkill"); - //chara.currentJob = ??? - chara.currentLevel = reader.GetInt16("mainSkillLevel"); + if (reader.Read()) + { + chara = new Character(); + chara.id = reader.GetUInt32("id"); + chara.slot = reader.GetUInt16("slot"); + chara.serverId = reader.GetUInt16("serverId"); + chara.name = reader.GetString("name"); + chara.isLegacy = reader.GetBoolean("isLegacy"); + chara.doRename = reader.GetBoolean("doRename"); + chara.currentZoneId = reader.GetUInt32("currentZoneId"); + chara.guardian = reader.GetByte("guardian"); + chara.birthMonth = reader.GetByte("birthMonth"); + chara.birthDay = reader.GetByte("birthDay"); + chara.initialTown = reader.GetByte("initialTown"); + chara.tribe = reader.GetByte("tribe"); + chara.currentClass = reader.GetByte("mainSkill"); + //chara.currentJob = ??? + chara.currentLevel = reader.GetInt16("mainSkillLevel"); + } } } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + + } + finally + { + conn.Dispose(); + } } return chara; } public static Appearance GetAppearance(uint charaId) { + Appearance appearance = new Appearance(); using (var 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))) { - Appearance appearance = null; try { conn.Open(); - appearance = conn.Query("SELECT * FROM characters_appearance WHERE characterId=@CharaId", new { CharaId = charaId }).SingleOrDefault(); + //Load appearance + string query = @" + SELECT + baseId, + size, + voice, + skinColor, + hairStyle, + hairColor, + hairHighlightColor, + eyeColor, + characteristics, + characteristicsColor, + faceType, + ears, + faceMouth, + faceFeatures, + faceNose, + faceEyeShape, + faceIrisSize, + faceEyebrows, + mainHand, + offHand, + head, + body, + legs, + hands, + feet, + waist, + leftFinger, + rightFinger, + leftEar, + rightEar + FROM characters_appearance WHERE characterId = @charaId"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charaId", charaId); + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + appearance.size = reader.GetByte("size"); + appearance.voice = reader.GetByte("voice"); + appearance.skinColor = reader.GetUInt16("skinColor"); + appearance.hairStyle = reader.GetUInt16("hairStyle"); + appearance.hairColor = reader.GetUInt16("hairColor"); + appearance.hairHighlightColor = reader.GetUInt16("hairHighlightColor"); + appearance.eyeColor = reader.GetUInt16("eyeColor"); + appearance.characteristics = reader.GetByte("characteristics"); + appearance.characteristicsColor = reader.GetByte("characteristicsColor"); + appearance.faceType = reader.GetByte("faceType"); + appearance.ears = reader.GetByte("ears"); + appearance.faceMouth = reader.GetByte("faceMouth"); + appearance.faceFeatures = reader.GetByte("faceFeatures"); + appearance.faceNose = reader.GetByte("faceNose"); + appearance.faceEyeShape = reader.GetByte("faceEyeShape"); + appearance.faceIrisSize = reader.GetByte("faceIrisSize"); + appearance.faceEyebrows = reader.GetByte("faceEyebrows"); + + appearance.mainHand = reader.GetUInt32("mainHand"); + appearance.offHand = reader.GetUInt32("offHand"); + appearance.head = reader.GetUInt32("head"); + appearance.body = reader.GetUInt32("body"); + appearance.mainHand = reader.GetUInt32("mainHand"); + appearance.legs = reader.GetUInt32("legs"); + appearance.hands = reader.GetUInt32("hands"); + appearance.feet = reader.GetUInt32("feet"); + appearance.waist = reader.GetUInt32("waist"); + appearance.leftFinger = reader.GetUInt32("leftFinger"); + appearance.rightFinger = reader.GetUInt32("rightFinger"); + appearance.leftEar = reader.GetUInt32("leftEar"); + appearance.rightEar = reader.GetUInt32("rightEar"); + } + + } } catch (MySqlException e) { Program.Log.Error(e.ToString()); - + } finally { conn.Dispose(); } - - return appearance; } + + return appearance; } public static List GetReservedNames(uint userId) { + List reservedNames = new List(); using (var 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))) { - List nameList = null; try { conn.Open(); - nameList = conn.Query("SELECT name FROM reserved_names WHERE userId=@UserId", new { UserId = userId }).ToList(); + + string query = "SELECT name FROM reserved_names WHERE userId=@UserId"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@UserId", userId); + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + reservedNames.Add(reader.GetString("name")); + } + } } catch (MySqlException e) { Program.Log.Error(e.ToString()); - nameList = new List(); } + + } finally { conn.Dispose(); } - return nameList; } + return reservedNames; } public static List GetRetainers(uint userId) { - using (var 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))) - { - List retainerList = null; - try - { - conn.Open(); - retainerList = conn.Query("SELECT * FROM retainers WHERE id=@UserId ORDER BY characterId, slot", new { UserId = userId }).ToList(); - } - catch (MySqlException e) - { - Program.Log.Error(e.ToString()); - retainerList = new List(); } - finally - { - conn.Dispose(); - } - return retainerList; - } + return new List(); } } diff --git a/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj b/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj index 962c3676..640d1111 100644 --- a/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj +++ b/FFXIVClassic Lobby Server/FFXIVClassic Lobby Server.csproj @@ -10,7 +10,7 @@ Properties FFXIVClassic_Lobby_Server FFXIVClassic_Lobby_Server - v4.5 + v4.5.1 512 false publish\ @@ -28,6 +28,7 @@ false true cc1ba6f5 + AnyCPU @@ -50,13 +51,32 @@ 4 true + + true + bin\Debug\ + DEBUG;TRACE + true + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + ..\packages\Cyotek.CircularBuffer.1.0.0.0\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll - - ..\packages\Dapper.1.42\lib\net45\Dapper.dll - ..\FFXIVClassic Common Class Lib\bin\Debug\FFXIVClassic.Common.dll @@ -64,10 +84,6 @@ ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll True - - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True - ..\packages\NLog.4.3.4\lib\net45\NLog.dll True diff --git a/FFXIVClassic Lobby Server/PacketProcessor.cs b/FFXIVClassic Lobby Server/PacketProcessor.cs index 8ccbbf08..c4f27e85 100644 --- a/FFXIVClassic Lobby Server/PacketProcessor.cs +++ b/FFXIVClassic Lobby Server/PacketProcessor.cs @@ -90,14 +90,15 @@ namespace FFXIVClassic_Lobby_Server if (userId == 0) { - ErrorPacket errorPacket = new ErrorPacket(sessionPacket.sequence, 0, 0, 13001, "Your session has expired, please login again."); - SubPacket subpacket = errorPacket.BuildPacket(); - BasePacket errorBasePacket = BasePacket.CreatePacket(subpacket, true, false); - BasePacket.EncryptPacket(client.blowfish, errorBasePacket); - client.QueuePacket(errorBasePacket); + ErrorPacket errorPacket = new ErrorPacket(sessionPacket.sequence, 0, 0, 13001, "Your session has expired, please login again."); + SubPacket subpacket = errorPacket.BuildPacket(); + subpacket.SetTargetId(0xe0006868); + BasePacket errorBasePacket = BasePacket.CreatePacket(subpacket, true, false); + BasePacket.EncryptPacket(client.blowfish, errorBasePacket); + client.QueuePacket(errorBasePacket); - Program.Log.Info("Invalid session, kicking..."); - return; + Program.Log.Info("Invalid session, kicking..."); + return; } Program.Log.Info("USER ID: {0}", userId); diff --git a/FFXIVClassic Lobby Server/Program.cs b/FFXIVClassic Lobby Server/Program.cs index f45ee786..2d14bb84 100644 --- a/FFXIVClassic Lobby Server/Program.cs +++ b/FFXIVClassic Lobby Server/Program.cs @@ -20,7 +20,10 @@ namespace FFXIVClassic_Lobby_Server TextWriterTraceListener myWriter = new TextWriterTraceListener(System.Console.Out); Debug.Listeners.Add(myWriter); #endif - Program.Log.Info("--------FFXIV 1.0 Lobby Server--------"); + Log.Info("=================================="); + Log.Info("FFXIV Classic Lobby Server"); + Log.Info("Version: 0.1"); + Log.Info("=================================="); bool startServer = true; diff --git a/FFXIVClassic Lobby Server/app.config b/FFXIVClassic Lobby Server/app.config index 143834c2..e418f4e3 100644 --- a/FFXIVClassic Lobby Server/app.config +++ b/FFXIVClassic Lobby Server/app.config @@ -1,9 +1,9 @@ - + - - + + - \ No newline at end of file + diff --git a/FFXIVClassic Lobby Server/dataobjects/Retainer.cs b/FFXIVClassic Lobby Server/dataobjects/Retainer.cs index 465747e4..2585ef0b 100644 --- a/FFXIVClassic Lobby Server/dataobjects/Retainer.cs +++ b/FFXIVClassic Lobby Server/dataobjects/Retainer.cs @@ -2,10 +2,17 @@ { class Retainer { - public uint id; - public uint characterId; - public string name; - public ushort slot; - public bool doRename; + public readonly uint id; + public readonly uint characterId; + public readonly string name; + public readonly bool doRename; + + public Retainer(uint characterId, uint retainerId, string name, bool doRename) + { + this.id = retainerId; + this.characterId = characterId; + this.name = name; + this.doRename = doRename; + } } } \ No newline at end of file diff --git a/FFXIVClassic Lobby Server/dataobjects/World.cs b/FFXIVClassic Lobby Server/dataobjects/World.cs index 8cab7502..67749216 100644 --- a/FFXIVClassic Lobby Server/dataobjects/World.cs +++ b/FFXIVClassic Lobby Server/dataobjects/World.cs @@ -2,12 +2,30 @@ { class World { - public ushort id; - public string address; - public ushort port; - public ushort listPosition; - public ushort population; - public string name; - public bool isActive; + public readonly ushort id; + public readonly string address; + public readonly ushort port; + public readonly ushort listPosition; + public readonly ushort population; + public readonly string name; + public readonly bool isActive; + + public World( + ushort id, + string address, + ushort port, + ushort listPosition, + ushort population, + string name, + bool isActive) + { + this.id = id; + this.address = address; + this.port = port; + this.listPosition = listPosition; + this.population = population; + this.name = name; + this.isActive = isActive; + } } } diff --git a/FFXIVClassic Lobby Server/packages.config b/FFXIVClassic Lobby Server/packages.config index 54b0ad82..6d704229 100644 --- a/FFXIVClassic Lobby Server/packages.config +++ b/FFXIVClassic Lobby Server/packages.config @@ -1,10 +1,8 @@  - - diff --git a/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs b/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs index a6c69341..2ec17d81 100644 --- a/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/AccountListPacket.cs @@ -61,7 +61,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); accountCount = 0; } @@ -88,7 +89,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); } diff --git a/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs b/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs index 5825c7be..31c9a882 100644 --- a/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/CharaCreatorPacket.cs @@ -59,7 +59,7 @@ namespace FFXIVClassic_Lobby_Server.packets binWriter.Dispose(); memStream.Dispose(); - return new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + return new SubPacket(OPCODE, 0xe0006868, data); } } } diff --git a/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs b/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs index 42db777f..bceeac8c 100644 --- a/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/CharacterListPacket.cs @@ -87,7 +87,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); characterCount = 0; } @@ -133,7 +134,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); characterCount = 0; } @@ -145,7 +147,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); } diff --git a/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs b/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs index 5578ee52..621732d3 100644 --- a/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/ErrorPacket.cs @@ -38,7 +38,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); return subpacket; } } diff --git a/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs b/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs index ac389071..ffb3a9cd 100644 --- a/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/ImportListPacket.cs @@ -64,7 +64,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); namesCount = 0; } @@ -91,7 +92,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); } diff --git a/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs b/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs index c9a6377d..e17e39b2 100644 --- a/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/RetainerListPacket.cs @@ -51,7 +51,7 @@ namespace FFXIVClassic_Lobby_Server.packets //Write Entries binWriter.Write((uint)retainer.id); binWriter.Write((uint)retainer.characterId); - binWriter.Write((ushort)retainer.slot); + binWriter.Write((ushort)totalCount); binWriter.Write((ushort)(retainer.doRename ? 0x04 : 0x00)); binWriter.Write((uint)0); binWriter.Write(Encoding.ASCII.GetBytes(retainer.name.PadRight(0x20, '\0'))); @@ -65,7 +65,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); retainerCount = 0; } @@ -92,7 +93,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); } diff --git a/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs b/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs index 6b57da3f..35686caa 100644 --- a/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/SelectCharacterConfirmPacket.cs @@ -49,7 +49,8 @@ namespace FFXIVClassic_Lobby_Server.packets data = memStream.GetBuffer(); } - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); return subPackets; diff --git a/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs b/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs index 0e939700..86e4712a 100644 --- a/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs +++ b/FFXIVClassic Lobby Server/packets/send/WorldListPacket.cs @@ -63,7 +63,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); serverCount = 0; } @@ -90,7 +91,8 @@ namespace FFXIVClassic_Lobby_Server.packets byte[] data = memStream.GetBuffer(); binWriter.Dispose(); memStream.Dispose(); - SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); + SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data); + subpacket.SetTargetId(0xe0006868); subPackets.Add(subpacket); } diff --git a/FFXIVClassic Map Server/App.config b/FFXIVClassic Map Server/App.config index 63198b4c..3c9bd9c6 100644 --- a/FFXIVClassic Map Server/App.config +++ b/FFXIVClassic Map Server/App.config @@ -1,11 +1,20 @@  - + - \ No newline at end of file + + + + + + + + + + diff --git a/FFXIVClassic Map Server/CommandProcessor.cs b/FFXIVClassic Map Server/CommandProcessor.cs index bae38bb2..447bfd57 100644 --- a/FFXIVClassic Map Server/CommandProcessor.cs +++ b/FFXIVClassic Map Server/CommandProcessor.cs @@ -14,7 +14,7 @@ namespace FFXIVClassic_Map_Server { class CommandProcessor { - private static Dictionary gamedataItems = Server.GetGamedataItems(); + private static Dictionary gamedataItems = Server.GetGamedataItems(); const UInt32 ITEM_GIL = 1000001; @@ -27,12 +27,12 @@ namespace FFXIVClassic_Map_Server private void SendMessage(Session session, String message) { if (session != null) - session.GetActor().QueuePacket(SendMessagePacket.BuildPacket(session.id, session.id, SendMessagePacket.MESSAGE_TYPE_GENERAL_INFO, "", message)); + session.GetActor().QueuePacket(SendMessagePacket.BuildPacket(session.id, SendMessagePacket.MESSAGE_TYPE_GENERAL_INFO, "", message)); } internal bool DoCommand(string input, Session session) { - if (!input.Any() || input.Equals("")) + if (!input.Any() || input.Equals("") || input.Length == 1) return false; input.Trim(); @@ -47,10 +47,10 @@ namespace FFXIVClassic_Map_Server split = split.ToArray(); // Ignore case on commands - var cmd = split[0]; - - if (cmd.Any()) + if (split.Length > 0) { + var cmd = split[0]; + // if client isnt null, take player to be the player actor Player player = null; if (session != null) diff --git a/FFXIVClassic Map Server/Database.cs b/FFXIVClassic Map Server/Database.cs index c4858f73..579e6568 100644 --- a/FFXIVClassic Map Server/Database.cs +++ b/FFXIVClassic Map Server/Database.cs @@ -1,8 +1,6 @@ using MySql.Data.MySqlClient; -using Dapper; using System; using System.Collections.Generic; -using System.Linq; using FFXIVClassic.Common; using FFXIVClassic_Map_Server.utils; @@ -10,6 +8,11 @@ using FFXIVClassic_Map_Server.packets.send.player; using FFXIVClassic_Map_Server.dataobjects; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.actors.chara.player; +using FFXIVClassic_Map_Server.packets.receive.supportdesk; +using FFXIVClassic_Map_Server.actors.chara.npc; +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.actors.chara; namespace FFXIVClassic_Map_Server { @@ -29,10 +32,10 @@ namespace FFXIVClassic_Map_Server cmd.Parameters.AddWithValue("@sessionId", sessionId); using (MySqlDataReader Reader = cmd.ExecuteReader()) { - while (Reader.Read()) - { - id = Reader.GetUInt32("userId"); - } + while (Reader.Read()) + { + id = Reader.GetUInt32("userId"); + } } } catch (MySqlException e) @@ -42,40 +45,17 @@ namespace FFXIVClassic_Map_Server finally { conn.Dispose(); - } + } } return id; - } - - public static List GetNpcList() - { - using (var 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))) - { - List npcList = null; - try - { - conn.Open(); - npcList = conn.Query("SELECT * FROM npc_list").ToList(); - } - catch (MySqlException e) - { - Program.Log.Error(e.ToString()); - } - finally - { - conn.Dispose(); - } - - return npcList; - } } - public static Dictionary GetItemGamedata() + public static Dictionary GetItemGamedata() { using (var 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))) { - Dictionary gamedataItems = new Dictionary(); - + Dictionary gamedataItems = new Dictionary(); + try { conn.Open(); @@ -84,11 +64,12 @@ namespace FFXIVClassic_Map_Server SELECT * FROM gamedata_items - LEFT JOIN gamedata_items_equipment ON gamedata_items.catalogID = gamedata_items_equipment.catalogID - LEFT JOIN gamedata_items_accessory ON gamedata_items.catalogID = gamedata_items_accessory.catalogID - LEFT JOIN gamedata_items_armor ON gamedata_items.catalogID = gamedata_items_armor.catalogID - LEFT JOIN gamedata_items_weapon ON gamedata_items.catalogID = gamedata_items_weapon.catalogID - LEFT JOIN gamedata_items_graphics ON gamedata_items.catalogID = gamedata_items_graphics.catalogID + LEFT JOIN gamedata_items_equipment ON gamedata_items.catalogID = gamedata_items_equipment.catalogID + LEFT JOIN gamedata_items_accessory ON gamedata_items.catalogID = gamedata_items_accessory.catalogID + LEFT JOIN gamedata_items_armor ON gamedata_items.catalogID = gamedata_items_armor.catalogID + LEFT JOIN gamedata_items_weapon ON gamedata_items.catalogID = gamedata_items_weapon.catalogID + LEFT JOIN gamedata_items_graphics ON gamedata_items.catalogID = gamedata_items_graphics.catalogID + LEFT JOIN gamedata_items_graphics_extra ON gamedata_items.catalogID = gamedata_items_graphics_extra.catalogID "; MySqlCommand cmd = new MySqlCommand(query, conn); @@ -98,16 +79,16 @@ namespace FFXIVClassic_Map_Server while (reader.Read()) { uint id = reader.GetUInt32("catalogID"); - Item item = null; + ItemData item = null; - if (Item.IsWeapon(id)) + if (ItemData.IsWeapon(id)) item = new WeaponItem(reader); - else if (Item.IsArmor(id)) + else if (ItemData.IsArmor(id)) item = new ArmorItem(reader); - else if (Item.IsAccessory(id)) + else if (ItemData.IsAccessory(id)) item = new AccessoryItem(reader); else - item = new Item(reader); + item = new ItemData(reader); gamedataItems.Add(item.catalogID, item); } @@ -121,11 +102,52 @@ namespace FFXIVClassic_Map_Server { conn.Dispose(); } - + return gamedataItems; } } + public static Dictionary GetGuildleveGamedata() + { + using (var 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))) + { + Dictionary gamedataGuildleves = new Dictionary(); + + try + { + conn.Open(); + + string query = @" + SELECT + * + FROM gamedata_guildleves + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + uint id = reader.GetUInt32("id"); + GuildleveData guildleve = new GuildleveData(reader); + gamedataGuildleves.Add(guildleve.id, guildleve); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + + return gamedataGuildleves; + } + } + public static void SavePlayerAppearance(Player player) { string query; @@ -220,7 +242,7 @@ namespace FFXIVClassic_Map_Server public static void SavePlayerPosition(Player player) { - string query; + string query; MySqlCommand cmd; 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))) @@ -242,7 +264,7 @@ namespace FFXIVClassic_Map_Server currentPrivateAreaType = @privateAreaType WHERE id = @charaId "; - + cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charaId", player.actorId); cmd.Parameters.AddWithValue("@x", player.positionX); @@ -391,6 +413,115 @@ namespace FFXIVClassic_Map_Server } } + public static void MarkGuildleve(Player player, uint glId, bool isAbandoned, bool isCompleted) + { + string query; + MySqlCommand cmd; + + 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(); + + query = @" + UPDATE characters_quest_guildleve_regional + SET abandoned = @abandoned, completed = @completed + WHERE characterId = @charaId and guildleveId = @guildleveId + "; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charaId", player.actorId); + cmd.Parameters.AddWithValue("@guildleveId", glId); + cmd.Parameters.AddWithValue("@abandoned", isAbandoned); + cmd.Parameters.AddWithValue("@completed", isCompleted); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void SaveGuildleve(Player player, uint glId, int slot) + { + string query; + MySqlCommand cmd; + + 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(); + + query = @" + INSERT INTO characters_quest_guildleve_regional + (characterId, slot, guildleveId, abandoned, completed) + VALUES + (@charaId, @slot, @guildleveId, @abandoned, @completed) + ON DUPLICATE KEY UPDATE + guildleveId = @guildleveId, abandoned = @abandoned, completed = @completed + "; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charaId", player.actorId); + cmd.Parameters.AddWithValue("@slot", slot); + cmd.Parameters.AddWithValue("@guildleveId", glId); + cmd.Parameters.AddWithValue("@abandoned", 0); + cmd.Parameters.AddWithValue("@completed", 0); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void RemoveGuildleve(Player player, uint glId) + { + string query; + MySqlCommand cmd; + + 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(); + + query = @" + DELETE FROM characters_quest_guildleve_regional + WHERE characterId = @charaId and guildleveId = @guildleveId + "; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charaId", player.actorId); + cmd.Parameters.AddWithValue("@guildleveId", glId); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + public static void RemoveQuest(Player player, uint questId) { string query; @@ -459,7 +590,7 @@ namespace FFXIVClassic_Map_Server } } } - + public static bool IsQuestCompleted(Player player, uint questId) { bool isCompleted = false; @@ -486,9 +617,9 @@ namespace FFXIVClassic_Map_Server } public static void LoadPlayerCharacter(Player player) - { + { string query; - MySqlCommand cmd; + MySqlCommand cmd; 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))) { @@ -525,7 +656,7 @@ namespace FFXIVClassic_Map_Server currentPrivateAreaType, homepoint, homepointInn - FROM characters WHERE id = @charId"; + FROM characters WHERE id = @charId"; cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); @@ -541,7 +672,7 @@ namespace FFXIVClassic_Map_Server player.oldRotation = player.rotation = reader.GetFloat(4); player.currentMainState = reader.GetUInt16(5); player.zoneId = reader.GetUInt32(6); - player.isZoning = true; + player.isZoning = true; player.gcCurrent = reader.GetByte(7); player.gcRankLimsa = reader.GetByte(8); player.gcRankGridania = reader.GetByte(9); @@ -566,14 +697,14 @@ namespace FFXIVClassic_Map_Server if (player.destinationZone != 0) player.zoneId = player.destinationZone; - + if (player.privateArea != null && !player.privateArea.Equals("")) player.zone = Server.GetWorldManager().GetPrivateArea(player.zoneId, player.privateArea, player.privateAreaType); else player.zone = Server.GetWorldManager().GetZone(player.zoneId); } } - + //Get class levels query = @" SELECT @@ -606,7 +737,7 @@ namespace FFXIVClassic_Map_Server { if (reader.Read()) { - player.charaWork.battleSave.skillLevel[Player.CLASSID_PUG-1] = reader.GetInt16("pug"); + player.charaWork.battleSave.skillLevel[Player.CLASSID_PUG - 1] = reader.GetInt16("pug"); player.charaWork.battleSave.skillLevel[Player.CLASSID_GLA - 1] = reader.GetInt16("gla"); player.charaWork.battleSave.skillLevel[Player.CLASSID_MRD - 1] = reader.GetInt16("mrd"); player.charaWork.battleSave.skillLevel[Player.CLASSID_ARC - 1] = reader.GetInt16("arc"); @@ -630,6 +761,62 @@ namespace FFXIVClassic_Map_Server } } + //Get class experience + query = @" + SELECT + pug, + gla, + mrd, + arc, + lnc, + + thm, + cnj, + + crp, + bsm, + arm, + gsm, + ltw, + wvr, + alc, + cul, + + min, + btn, + fsh + FROM characters_class_exp WHERE characterId = @charId"; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + player.charaWork.battleSave.skillPoint[Player.CLASSID_PUG - 1] = reader.GetInt32("pug"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_GLA - 1] = reader.GetInt32("gla"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_MRD - 1] = reader.GetInt32("mrd"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_ARC - 1] = reader.GetInt32("arc"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_LNC - 1] = reader.GetInt32("lnc"); + + player.charaWork.battleSave.skillPoint[Player.CLASSID_THM - 1] = reader.GetInt32("thm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_CNJ - 1] = reader.GetInt32("cnj"); + + player.charaWork.battleSave.skillPoint[Player.CLASSID_CRP - 1] = reader.GetInt32("crp"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_BSM - 1] = reader.GetInt32("bsm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_ARM - 1] = reader.GetInt32("arm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_GSM - 1] = reader.GetInt32("gsm"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_LTW - 1] = reader.GetInt32("ltw"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_WVR - 1] = reader.GetInt32("wvr"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_ALC - 1] = reader.GetInt32("alc"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_CUL - 1] = reader.GetInt32("cul"); + + player.charaWork.battleSave.skillPoint[Player.CLASSID_MIN - 1] = reader.GetInt32("min"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_BTN - 1] = reader.GetInt32("btn"); + player.charaWork.battleSave.skillPoint[Player.CLASSID_FSH - 1] = reader.GetInt32("fsh"); + } + } + //Load Saved Parameters query = @" SELECT @@ -655,7 +842,7 @@ namespace FFXIVClassic_Map_Server player.charaWork.parameterSave.state_mainSkillLevel = player.charaWork.battleSave.skillLevel[reader.GetByte(4) - 1]; } } - + //Load appearance query = @" SELECT @@ -726,18 +913,38 @@ namespace FFXIVClassic_Map_Server query = @" SELECT statusId, - expireTime + duration, + magnitude, + tick, + tier, + extra FROM characters_statuseffect WHERE characterId = @charId"; cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); using (MySqlDataReader reader = cmd.ExecuteReader()) { - int count = 0; while (reader.Read()) { - player.charaWork.status[count] = reader.GetUInt16(0); - player.charaWork.statusShownTime[count] = reader.GetUInt32(1); + var id = reader.GetUInt32("statusId"); + var duration = reader.GetUInt32("duration"); + var magnitude = reader.GetUInt64("magnitude"); + var tick = reader.GetUInt32("tick"); + var tier = reader.GetByte("tier"); + var extra = reader.GetUInt64("extra"); + + var effect = Server.GetWorldManager().GetStatusEffect(id); + if (effect != null) + { + effect.SetDuration(duration); + effect.SetMagnitude(magnitude); + effect.SetTickMs(tick); + effect.SetTier(tier); + effect.SetExtra(extra); + + // dont wanna send ton of messages on login (i assume retail doesnt) + player.statusEffects.AddStatusEffect(effect, null); + } } } @@ -798,27 +1005,9 @@ namespace FFXIVClassic_Map_Server player.timers[i] = reader.GetUInt32(i); } } - - //Load Hotbar - query = @" - SELECT - hotbarSlot, - commandId, - recastTime - FROM characters_hotbar WHERE characterId = @charId AND classId = @classId"; - cmd = new MySqlCommand(query, conn); - cmd.Parameters.AddWithValue("@charId", player.actorId); - cmd.Parameters.AddWithValue("@classId", player.charaWork.parameterSave.state_mainSkill[0]); - using (MySqlDataReader reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - int index = reader.GetUInt16(0); - player.charaWork.command[index+32] = reader.GetUInt32(1); - player.charaWork.parameterSave.commandSlot_recastTime[index] = reader.GetUInt32(2); - } - } + //Load Hotbar + LoadHotbar(player); //Load Scenario Quests query = @" @@ -829,7 +1018,7 @@ namespace FFXIVClassic_Map_Server questFlags, currentPhase FROM characters_quest_scenario WHERE characterId = @charId"; - + cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); using (MySqlDataReader reader = cmd.ExecuteReader()) @@ -918,13 +1107,13 @@ namespace FFXIVClassic_Map_Server { int npcLSId = reader.GetUInt16(0); player.playerWork.npcLinkshellChatCalling[npcLSId] = reader.GetBoolean(1); - player.playerWork.npcLinkshellChatExtra[npcLSId] = reader.GetBoolean(2); + player.playerWork.npcLinkshellChatExtra[npcLSId] = reader.GetBoolean(2); } } player.GetInventory(Inventory.NORMAL).InitList(GetInventory(player, 0, Inventory.NORMAL)); player.GetInventory(Inventory.KEYITEMS).InitList(GetInventory(player, 0, Inventory.KEYITEMS)); - player.GetInventory(Inventory.CURRENCY).InitList(GetInventory(player, 0, Inventory.CURRENCY)); + player.GetInventory(Inventory.CURRENCY_CRYSTALS).InitList(GetInventory(player, 0, Inventory.CURRENCY_CRYSTALS)); player.GetInventory(Inventory.BAZAAR).InitList(GetInventory(player, 0, Inventory.BAZAAR)); player.GetInventory(Inventory.MELDREQUEST).InitList(GetInventory(player, 0, Inventory.MELDREQUEST)); player.GetInventory(Inventory.LOOT).InitList(GetInventory(player, 0, Inventory.LOOT)); @@ -970,7 +1159,7 @@ namespace FFXIVClassic_Map_Server { ushort equipSlot = reader.GetUInt16(0); ulong uniqueItemId = reader.GetUInt16(1); - InventoryItem item = player.GetInventory(Inventory.NORMAL).GetItemById(uniqueItemId); + InventoryItem item = player.GetInventory(Inventory.NORMAL).GetItemByUniqueId(uniqueItemId); equipment[equipSlot] = item; } } @@ -1057,7 +1246,195 @@ namespace FFXIVClassic_Map_Server } } + public static void EquipAbility(Player player, byte classId, ushort hotbarSlot, uint commandId, uint recastTime) + { + commandId ^= 0xA0F00000; + if (commandId > 0) + { + 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(); + MySqlCommand cmd; + string query = @" + INSERT INTO characters_hotbar + (characterId, classId, hotbarSlot, commandId, recastTime) + VALUES + (@charId, @classId, @hotbarSlot, @commandId, @recastTime) + ON DUPLICATE KEY UPDATE commandId=@commandId, recastTime=@recastTime; + "; + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@classId", classId); + cmd.Parameters.AddWithValue("@commandId", commandId); + cmd.Parameters.AddWithValue("@hotbarSlot", hotbarSlot); + cmd.Parameters.AddWithValue("@recastTime", recastTime); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + else + UnequipAbility(player, hotbarSlot); + } + + //Unequipping is done by sending an equip packet with 0xA0F00000 as the ability and the hotbar slot of the action being unequipped + public static void UnequipAbility(Player player, ushort hotbarSlot) + { + 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(); + MySqlCommand cmd; + string query = ""; + + query = @" + DELETE FROM characters_hotbar + WHERE characterId = @charId AND classId = @classId AND hotbarSlot = @hotbarSlot + "; + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@classId", player.charaWork.parameterSave.state_mainSkill[0]); + cmd.Parameters.AddWithValue("@hotbarSlot", hotbarSlot); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + + } + + public static void LoadHotbar(Player player) + { + string query; + MySqlCommand cmd; + + 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(); + //Load Hotbar + query = @" + SELECT + hotbarSlot, + commandId, + recastTime + FROM characters_hotbar WHERE characterId = @charId AND classId = @classId + ORDER BY hotbarSlot"; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@classId", player.GetCurrentClassOrJob()); + + player.charaWork.commandBorder = 32; + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int hotbarSlot = reader.GetUInt16("hotbarSlot"); + uint commandId = reader.GetUInt32("commandId"); + player.charaWork.command[hotbarSlot + player.charaWork.commandBorder] = 0xA0F00000 | commandId; + player.charaWork.commandCategory[hotbarSlot + player.charaWork.commandBorder] = 1; + player.charaWork.parameterSave.commandSlot_recastTime[hotbarSlot] = reader.GetUInt32("recastTime"); + + //Recast timer + BattleCommand ability = Server.GetWorldManager().GetBattleCommand((ushort)(commandId)); + player.charaWork.parameterTemp.maxCommandRecastTime[hotbarSlot] = (ushort) (ability != null ? ability.maxRecastTimeSeconds : 1); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static ushort FindFirstCommandSlot(Player player, byte classId) + { + ushort slot = 0; + 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(); + MySqlCommand cmd; + string query = ""; + + //Drop + List> hotbarList = new List>(); + query = @" + SELECT hotbarSlot + FROM characters_hotbar + WHERE characterId = @charId AND classId = @classId + ORDER BY hotbarSlot + "; + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@classId", classId); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + if (slot != reader.GetUInt16("hotbarSlot")) + break; + + slot++; + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + return slot; + } public static List GetInventory(Player player, uint slotOffset, uint type) { List items = new List(); @@ -1073,7 +1450,6 @@ namespace FFXIVClassic_Map_Server serverItemId, itemId, quantity, - slot, itemType, quality, durability, @@ -1085,35 +1461,37 @@ namespace FFXIVClassic_Map_Server materia5 FROM characters_inventory INNER JOIN server_items ON serverItemId = server_items.id - WHERE characterId = @charId AND inventoryType = @type AND slot >= @slot ORDER BY slot"; + WHERE characterId = @charId AND inventoryType = @type"; MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); - cmd.Parameters.AddWithValue("@slot", slotOffset); cmd.Parameters.AddWithValue("@type", type); + ushort slot = 0; using (MySqlDataReader reader = cmd.ExecuteReader()) - { + { while (reader.Read()) { - uint uniqueId = reader.GetUInt32(0); - uint itemId = reader.GetUInt32(1); - int quantity = reader.GetInt32(2); - ushort slot = reader.GetUInt16(3); + uint uniqueId = reader.GetUInt32("serverItemId"); + uint itemId = reader.GetUInt32("itemId"); + int quantity = reader.GetInt32("quantity"); - byte itemType = reader.GetByte(4); - byte qualityNumber = reader.GetByte(5); + byte itemType = reader.GetByte("itemType"); + byte qualityNumber = reader.GetByte("quality"); - int durability = reader.GetInt32(6); - ushort spiritBind = reader.GetUInt16(7); + int durability = reader.GetInt32("durability"); + ushort spiritBind = reader.GetUInt16("spiritBind"); - byte materia1 = reader.GetByte(8); - byte materia2 = reader.GetByte(9); - byte materia3 = reader.GetByte(10); - byte materia4 = reader.GetByte(11); - byte materia5 = reader.GetByte(12); + byte materia1 = reader.GetByte("materia1"); + byte materia2 = reader.GetByte("materia2"); + byte materia3 = reader.GetByte("materia3"); + byte materia4 = reader.GetByte("materia4"); + byte materia5 = reader.GetByte("materia5"); - items.Add(new InventoryItem(uniqueId, itemId, quantity, slot, itemType, qualityNumber, durability, spiritBind, materia1, materia2, materia3, materia4, materia5)); + InventoryItem item = new InventoryItem(uniqueId, itemId, quantity, itemType, qualityNumber, durability, spiritBind, materia1, materia2, materia3, materia4, materia5); + item.slot = slot; + slot++; + items.Add(item); } } } @@ -1130,7 +1508,80 @@ namespace FFXIVClassic_Map_Server return items; } - public static InventoryItem AddItem(Player player, uint itemId, int quantity, byte quality, byte itemType, int durability, ushort type) + public static List GetInventory(Retainer retainer, uint type) + { + List items = new List(); + + 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(); + + string query = @" + SELECT + serverItemId, + itemId, + quantity, + itemType, + quality, + durability, + spiritBind, + materia1, + materia2, + materia3, + materia4, + materia5 + FROM retainers_inventory + INNER JOIN server_items ON serverItemId = server_items.id + WHERE retainerId = @retainerId AND inventoryType = @type"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@retainerId", retainer.getRetainerId()); + cmd.Parameters.AddWithValue("@type", type); + + ushort slot = 0; + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + uint uniqueId = reader.GetUInt32("serverItemId"); + uint itemId = reader.GetUInt32("itemId"); + int quantity = reader.GetInt32("quantity"); + + byte itemType = reader.GetByte("itemType"); + byte qualityNumber = reader.GetByte("quality"); + + int durability = reader.GetInt32("durability"); + ushort spiritBind = reader.GetUInt16("spiritBind"); + + byte materia1 = reader.GetByte("materia1"); + byte materia2 = reader.GetByte("materia2"); + byte materia3 = reader.GetByte("materia3"); + byte materia4 = reader.GetByte("materia4"); + byte materia5 = reader.GetByte("materia5"); + + InventoryItem item = new InventoryItem(uniqueId, itemId, quantity, itemType, qualityNumber, durability, spiritBind, materia1, materia2, materia3, materia4, materia5); + item.slot = slot; + slot++; + items.Add(item); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + + return items; + } + + public static InventoryItem CreateItem(uint itemId, int quantity, byte quality, byte itemType, int durability) { InventoryItem insertedItem = null; @@ -1140,10 +1591,10 @@ namespace FFXIVClassic_Map_Server { conn.Open(); - + string query = @" - INSERT INTO server_items + INSERT INTO server_items (itemId, quality, itemType, durability) VALUES (@itemId, @quality, @itemType, @durability); @@ -1151,27 +1602,14 @@ namespace FFXIVClassic_Map_Server MySqlCommand cmd = new MySqlCommand(query, conn); - string query2 = @" - INSERT INTO characters_inventory - (characterId, slot, inventoryType, serverItemId, quantity) - SELECT @charId, IFNULL(MAX(SLOT)+1, 0), @inventoryType, LAST_INSERT_ID(), @quantity FROM characters_inventory WHERE characterId = @charId AND inventoryType = @inventoryType; - "; - - MySqlCommand cmd2 = new MySqlCommand(query2, conn); - cmd.Parameters.AddWithValue("@itemId", itemId); cmd.Parameters.AddWithValue("@quality", quality); cmd.Parameters.AddWithValue("@itemType", itemType); cmd.Parameters.AddWithValue("@durability", durability); - cmd2.Parameters.AddWithValue("@charId", player.actorId); - cmd2.Parameters.AddWithValue("@inventoryType", type); - cmd2.Parameters.AddWithValue("@quantity", quantity); - cmd.ExecuteNonQuery(); - cmd2.ExecuteNonQuery(); - insertedItem = new InventoryItem((uint)cmd.LastInsertedId, itemId, quantity, (ushort)player.GetInventory(type).GetNextEmptySlot(), itemType, quality, durability, 0, 0, 0, 0, 0, 0); + insertedItem = new InventoryItem((uint)cmd.LastInsertedId, itemId, quantity, itemType, quality, durability, 0, 0, 0, 0, 0, 0); } catch (MySqlException e) { @@ -1186,7 +1624,42 @@ namespace FFXIVClassic_Map_Server return insertedItem; } - public static void SetQuantity(Player player, uint slot, ushort type, int quantity) + public static void AddItem(Player player, InventoryItem addedItem, uint type) + { + 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(); + + string query = @" + INSERT INTO characters_inventory + (characterId, inventoryType, serverItemId, quantity) + VALUES + (@charId, @inventoryType, @serverItemId, @quantity) + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Parameters.AddWithValue("@serverItemId", addedItem.uniqueId); + cmd.Parameters.AddWithValue("@charId", player.actorId); + cmd.Parameters.AddWithValue("@inventoryType", type); + cmd.Parameters.AddWithValue("@quantity", addedItem.quantity); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void SetQuantity(Player player, ulong serverItemId, int quantity) { 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))) { @@ -1194,57 +1667,16 @@ namespace FFXIVClassic_Map_Server { conn.Open(); - string query = @" + string query = @" UPDATE characters_inventory SET quantity = @quantity - WHERE characterId = @charId AND slot = @slot AND inventoryType = @type; + WHERE characterId = @charId and serverItemId = @serverItemId; "; - - MySqlCommand cmd = new MySqlCommand(query, conn); - cmd.Parameters.AddWithValue("@charId", player.actorId); - cmd.Parameters.AddWithValue("@quantity", quantity); - cmd.Parameters.AddWithValue("@slot", slot); - cmd.Parameters.AddWithValue("@type", type); - cmd.ExecuteNonQuery(); - - } - catch (MySqlException e) - { - Program.Log.Error(e.ToString()); - } - finally - { - conn.Dispose(); - } - } - - } - - public static void RemoveItem(Player player, ulong serverItemId, ushort type) - { - using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}; Allow User Variables=True", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) - { - try - { - conn.Open(); - - string query = @" - SELECT slot INTO @slotToDelete FROM characters_inventory WHERE serverItemId = @serverItemId; - UPDATE characters_inventory - SET slot = slot - 1 - WHERE characterId = @charId AND slot > @slotToDelete AND inventoryType = @type; - - DELETE FROM characters_inventory - WHERE serverItemId = @serverItemId AND inventoryType = @type; - - DELETE FROM server_items - WHERE id = @serverItemId; - "; MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); cmd.Parameters.AddWithValue("@serverItemId", serverItemId); - cmd.Parameters.AddWithValue("@type", type); + cmd.Parameters.AddWithValue("@quantity", quantity); cmd.ExecuteNonQuery(); } @@ -1260,7 +1692,7 @@ namespace FFXIVClassic_Map_Server } - public static void RemoveItem(Player player, ushort slot, ushort type) + public static void RemoveItem(Player player, ulong serverItemId) { using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}; Allow User Variables=True", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) { @@ -1269,23 +1701,112 @@ namespace FFXIVClassic_Map_Server conn.Open(); string query = @" - SELECT serverItemId INTO @serverItemId FROM characters_inventory WHERE characterId = @charId AND slot = @slot; - DELETE FROM characters_inventory - WHERE characterId = @charId AND slot = @slot AND inventoryType = @type; - - DELETE FROM server_items - WHERE id = @serverItemId; - - UPDATE characters_inventory - SET slot = slot - 1 - WHERE characterId = @charId AND slot > @slot AND inventoryType = @type; + WHERE characterId = @charId and serverItemId = @serverItemId; "; MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); - cmd.Parameters.AddWithValue("@slot", slot); - cmd.Parameters.AddWithValue("@type", type); + cmd.Parameters.AddWithValue("@serverItemId", serverItemId); + cmd.ExecuteNonQuery(); + + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + + } + + public static void AddItem(Retainer retainer, InventoryItem addedItem, uint type) + { + 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(); + + string query = @" + INSERT INTO retainers_inventory + (retainerId, inventoryType, serverItemId, quantity) + VALUES + (@retainerId, @inventoryType, @serverItemId, @quantity) + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Parameters.AddWithValue("@serverItemId", addedItem.uniqueId); + cmd.Parameters.AddWithValue("@retainerId", retainer.getRetainerId()); + cmd.Parameters.AddWithValue("@inventoryType", type); + cmd.Parameters.AddWithValue("@quantity", addedItem.quantity); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void SetQuantity(Retainer retainer, ulong serverItemId, int quantity) + { + 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(); + + string query = @" + UPDATE retainers_inventory + SET quantity = @quantity + WHERE retainerId = @retainerId and serverItemId = @serverItemId; + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@retainerId", retainer.getRetainerId()); + cmd.Parameters.AddWithValue("@serverItemId", serverItemId); + cmd.Parameters.AddWithValue("@quantity", quantity); + cmd.ExecuteNonQuery(); + + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + + } + + public static void RemoveItem(Retainer retainer, ulong serverItemId) + { + using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}; Allow User Variables=True", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD))) + { + try + { + conn.Open(); + + string query = @" + DELETE FROM retainers_inventory + WHERE retainerId = @retainerId and serverItemId = @serverItemId; + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@retainerId", retainer.getRetainerId()); + cmd.Parameters.AddWithValue("@serverItemId", serverItemId); cmd.ExecuteNonQuery(); } @@ -1309,13 +1830,13 @@ namespace FFXIVClassic_Map_Server try { conn.Open(); - + //Load Last 5 Completed string query = @" SELECT characters_achievements.achievementId FROM characters_achievements INNER JOIN gamedata_achievements ON characters_achievements.achievementId = gamedata_achievements.achievementId - WHERE characterId = @charId AND rewardPoints <> 0 ORDER BY timeDone LIMIT 5"; + WHERE characterId = @charId AND rewardPoints <> 0 AND timeDone IS NOT NULL ORDER BY timeDone LIMIT 5"; MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@charId", player.actorId); @@ -1324,7 +1845,7 @@ namespace FFXIVClassic_Map_Server int count = 0; while (reader.Read()) { - uint id = reader.GetUInt32(0); + uint id = reader.GetUInt32(0); latestAchievements[count++] = id; } } @@ -1351,7 +1872,7 @@ namespace FFXIVClassic_Map_Server try { conn.Open(); - + string query = @" SELECT packetOffsetId FROM characters_achievements @@ -1363,7 +1884,7 @@ namespace FFXIVClassic_Map_Server using (MySqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) - { + { uint offset = reader.GetUInt32(0); if (offset < 0 || offset >= cheevosPacket.achievementFlags.Length) @@ -1371,7 +1892,7 @@ namespace FFXIVClassic_Map_Server Program.Log.Error("SQL Error; achievement flag offset id out of range: " + offset); continue; } - cheevosPacket.achievementFlags[offset] = true; + cheevosPacket.achievementFlags[offset] = true; } } } @@ -1387,7 +1908,6 @@ namespace FFXIVClassic_Map_Server return cheevosPacket.BuildPacket(player.actorId); } - public static bool CreateLinkshell(Player player, string lsName, ushort lsCrest) { bool success = false; @@ -1398,7 +1918,7 @@ namespace FFXIVClassic_Map_Server conn.Open(); string query = @" - INSERT INTO server_linkshells + INSERT INTO server_linkshells (name, master, crest) VALUES (@lsName, @master, @crest) @@ -1408,7 +1928,7 @@ namespace FFXIVClassic_Map_Server MySqlCommand cmd = new MySqlCommand(query, conn); cmd.Parameters.AddWithValue("@lsName", lsName); cmd.Parameters.AddWithValue("@master", player.actorId); - cmd.Parameters.AddWithValue("@crest", lsCrest); + cmd.Parameters.AddWithValue("@crest", lsCrest); cmd.ExecuteNonQuery(); success = true; @@ -1464,6 +1984,696 @@ namespace FFXIVClassic_Map_Server } } } - } + public static bool SaveSupportTicket(GMSupportTicketPacket gmTicket, string playerName) + { + string query; + MySqlCommand cmd; + bool wasError = false; + + 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(); + + query = @" + INSERT INTO supportdesk_tickets + (name, title, body, langCode) + VALUES + (@name, @title, @body, @langCode)"; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@name", playerName); + cmd.Parameters.AddWithValue("@title", gmTicket.ticketTitle); + cmd.Parameters.AddWithValue("@body", gmTicket.ticketBody); + cmd.Parameters.AddWithValue("@langCode", gmTicket.langCode); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + wasError = true; + } + finally + { + conn.Dispose(); + } + } + + return wasError; + } + + public static bool isTicketOpen(string playerName) + { + bool isOpen = false; + 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(); + + string query = @" + SELECT + isOpen + FROM supportdesk_tickets + WHERE name = @name + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Parameters.AddWithValue("@name", playerName); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + isOpen = reader.GetBoolean(0); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + + return isOpen; + } + + public static void closeTicket(string playerName) + { + bool isOpen = false; + 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(); + + string query = @" + UPDATE + supportdesk_tickets + SET isOpen = 0 + WHERE name = @name + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@name", playerName); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static string[] getFAQNames(uint langCode = 1) + { + string[] faqs = null; + List raw = new List(); + 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(); + + string query = @" + SELECT + title + FROM supportdesk_faqs + WHERE languageCode = @langCode + ORDER BY slot + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Parameters.AddWithValue("@langCode", langCode); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + string label = reader.GetString(0); + raw.Add(label); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + faqs = raw.ToArray(); + } + } + return faqs; + } + + public static string getFAQBody(uint slot, uint langCode = 1) + { + string body = string.Empty; + 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(); + + string query = @" + SELECT + body + FROM supportdesk_faqs + WHERE slot=@slot and languageCode=@langCode"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@slot", slot); + cmd.Parameters.AddWithValue("@langCode", langCode); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + body = reader.GetString(0); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + return body; + } + + public static string[] getIssues(uint lanCode = 1) + { + string[] issues = null; + List raw = new List(); + 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(); + + string query = @" + SELECT + title + FROM supportdesk_issues + ORDER BY slot"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + string label = reader.GetString(0); + raw.Add(label); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + issues = raw.ToArray(); + } + } + return issues; + } + + public static void IssuePlayerChocobo(Player player, byte appearanceId, string name) + { + string query; + MySqlCommand cmd; + + 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(); + + query = @" + INSERT INTO characters_chocobo + (characterId, hasChocobo, chocoboAppearance, chocoboName) + VALUES + (@characterId, @hasChocobo, @chocoboAppearance, @chocoboName) + ON DUPLICATE KEY UPDATE + hasChocobo=@hasChocobo, chocoboAppearance=@chocoboAppearance, chocoboName=@chocoboName"; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@characterId", player.actorId); + cmd.Parameters.AddWithValue("@hasChocobo", 1); + cmd.Parameters.AddWithValue("@chocoboAppearance", appearanceId); + cmd.Parameters.AddWithValue("@chocoboName", name); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void ChangePlayerChocoboAppearance(Player player, byte appearanceId) + { + string query; + MySqlCommand cmd; + + 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(); + + query = @" + UPDATE characters_chocobo + SET + chocoboAppearance=@chocoboAppearance + WHERE + characterId = @characterId"; + + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@characterId", player.actorId); + cmd.Parameters.AddWithValue("@chocoboAppearance", appearanceId); + + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static Dictionary LoadGlobalStatusEffectList() + { + var effects = new Dictionary(); + + 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, flags, overwrite, tickMs, hidden, silentOnGain, silentOnLoss FROM server_statuseffects;"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var id = reader.GetUInt32("id"); + var name = reader.GetString("name"); + var flags = reader.GetUInt32("flags"); + var overwrite = reader.GetByte("overwrite"); + var tickMs = reader.GetUInt32("tickMs"); + var hidden = reader.GetBoolean("hidden"); + var silentOnGain = reader.GetBoolean("silentOnGain"); + var silentOnLoss = reader.GetBoolean("silentOnLoss"); + + var effect = new StatusEffect(id, name, flags, overwrite, tickMs, hidden, silentOnGain, silentOnLoss); + + lua.LuaEngine.LoadStatusEffectScript(effect); + effects.Add(id, effect); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + return effects; + } + + public static void SavePlayerStatusEffects(Player player) + { + string[] faqs = null; + List raw = new List(); + 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(); + + using (MySqlTransaction trns = conn.BeginTransaction()) + { + string query = @" + REPLACE INTO characters_statuseffect + (characterId, statusId, magnitude, duration, tick, tier, extra) + VALUES + (@actorId, @statusId, @magnitude, @duration, @tick, @tier, @extra) + "; + using (MySqlCommand cmd = new MySqlCommand(query, conn, trns)) + { + foreach (var effect in player.statusEffects.GetStatusEffects()) + { + var duration = Utils.UnixTimeStampUTC(effect.GetEndTime()) - Utils.UnixTimeStampUTC(); + + cmd.Parameters.AddWithValue("@actorId", player.actorId); + cmd.Parameters.AddWithValue("@statusId", effect.GetStatusEffectId()); + cmd.Parameters.AddWithValue("@magnitude", effect.GetMagnitude()); + cmd.Parameters.AddWithValue("@duration", duration); + cmd.Parameters.AddWithValue("@tick", effect.GetTickMs()); + cmd.Parameters.AddWithValue("@tier", effect.GetTier()); + cmd.Parameters.AddWithValue("@extra", effect.GetExtra()); + + cmd.ExecuteNonQuery(); + } + trns.Commit(); + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void LoadGlobalBattleCommandList(Dictionary battleCommandDict, Dictionary, List> battleCommandIdByLevel) + { + 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 + { + int count = 0; + conn.Open(); + + var query = ("SELECT `id`, name, classJob, lvl, requirements, mainTarget, validTarget, aoeType, aoeRange, aoeMinRange, aoeConeAngle, aoeRotateAngle, aoeTarget, basePotency, numHits, positionBonus, procRequirement, `range`, minRange, rangeHeight, rangeWidth, statusId, statusDuration, statusChance, " + + "castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, battleAnimation, validUser, comboId1, comboId2, comboStep, accuracyMod, worldMasterTextId, commandType, actionType, actionProperty FROM server_battle_commands;"); + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var id = reader.GetUInt16("id"); + var name = reader.GetString("name"); + var battleCommand = new BattleCommand(id, name); + + battleCommand.job = reader.GetByte("classJob"); + battleCommand.level = reader.GetByte("lvl"); + battleCommand.requirements = (BattleCommandRequirements)reader.GetUInt16("requirements"); + battleCommand.mainTarget = (ValidTarget)reader.GetUInt16("mainTarget"); + battleCommand.validTarget = (ValidTarget)reader.GetUInt16("validTarget"); + battleCommand.aoeType = (TargetFindAOEType)reader.GetByte("aoeType"); + battleCommand.basePotency = reader.GetUInt16("basePotency"); + battleCommand.numHits = reader.GetByte("numHits"); + battleCommand.positionBonus = (BattleCommandPositionBonus)reader.GetByte("positionBonus"); + battleCommand.procRequirement = (BattleCommandProcRequirement)reader.GetByte("procRequirement"); + battleCommand.range = reader.GetFloat("range"); + battleCommand.minRange = reader.GetFloat("minRange"); + battleCommand.rangeHeight = reader.GetInt32("rangeHeight"); + battleCommand.rangeWidth = reader.GetInt32("rangeWidth"); + battleCommand.statusId = reader.GetUInt32("statusId"); + battleCommand.statusDuration = reader.GetUInt32("statusDuration"); + battleCommand.statusChance = reader.GetFloat("statusChance"); + battleCommand.castType = reader.GetByte("castType"); + battleCommand.castTimeMs = reader.GetUInt32("castTime"); + battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime"); + battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000; + battleCommand.mpCost = reader.GetInt16("mpCost"); + battleCommand.tpCost = reader.GetInt16("tpCost"); + battleCommand.animationType = reader.GetByte("animationType"); + battleCommand.effectAnimation = reader.GetUInt16("effectAnimation"); + battleCommand.modelAnimation = reader.GetUInt16("modelAnimation"); + battleCommand.animationDurationSeconds = reader.GetUInt16("animationDuration"); + battleCommand.aoeRange = reader.GetFloat("aoeRange"); + battleCommand.aoeMinRange = reader.GetFloat("aoeMinRange"); + battleCommand.aoeConeAngle = reader.GetFloat("aoeConeAngle"); + battleCommand.aoeRotateAngle = reader.GetFloat("aoeRotateAngle"); + battleCommand.aoeTarget = (TargetFindAOETarget)reader.GetByte("aoeTarget"); + + battleCommand.battleAnimation = reader.GetUInt32("battleAnimation"); + battleCommand.validUser = (BattleCommandValidUser)reader.GetByte("validUser"); + + battleCommand.comboNextCommandId[0] = reader.GetInt32("comboId1"); + battleCommand.comboNextCommandId[1] = reader.GetInt32("comboId2"); + battleCommand.comboStep = reader.GetInt16("comboStep"); + battleCommand.commandType = (CommandType) reader.GetInt16("commandType"); + battleCommand.actionProperty = (ActionProperty)reader.GetInt16("actionProperty"); + battleCommand.actionType = (ActionType)reader.GetInt16("actionType"); + battleCommand.accuracyModifier = reader.GetFloat("accuracyMod"); + battleCommand.worldMasterTextId = reader.GetUInt16("worldMasterTextId"); + + string folderName = ""; + + switch (battleCommand.commandType) + { + case CommandType.AutoAttack: + folderName = "autoattack"; + break; + case CommandType.WeaponSkill: + folderName = "weaponskill"; + break; + case CommandType.Ability: + folderName = "ability"; + break; + case CommandType.Spell: + folderName = "magic"; + break; + } + + lua.LuaEngine.LoadBattleCommandScript(battleCommand, folderName); + battleCommandDict.Add(id, battleCommand); + + Tuple tuple = Tuple.Create(battleCommand.job, battleCommand.level); + if (battleCommandIdByLevel.ContainsKey(tuple)) + { + battleCommandIdByLevel[tuple].Add(id); + } + else + { + List list = new List() { id }; + battleCommandIdByLevel.Add(tuple, list); + } + count++; + } + } + + Program.Log.Info(String.Format("Loaded {0} battle commands.", count)); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void LoadGlobalBattleTraitList(Dictionary battleTraitDict, Dictionary> battleTraitJobDict) + { + 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 + { + int count = 0; + conn.Open(); + + var query = ("SELECT `id`, name, classJob, lvl, modifier, bonus FROM server_battle_traits;"); + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var id = reader.GetUInt16("id"); + var name = reader.GetString("name"); + var job = reader.GetByte("classJob"); + var level = reader.GetByte("lvl"); + uint modifier = reader.GetUInt32("modifier"); + var bonus = reader.GetInt32("bonus"); + + var trait = new BattleTrait(id, name, job, level, modifier, bonus); + + battleTraitDict.Add(id, trait); + + if(battleTraitJobDict.ContainsKey(job)) + { + battleTraitJobDict[job].Add(id); + } + else + { + battleTraitJobDict[job] = new List(); + battleTraitJobDict[job].Add(id); + } + + count++; + } + } + Program.Log.Info(String.Format("Loaded {0} battle traits.", count)); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void SetExp(Player player, byte classId, int exp) + { + 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 = String.Format(@" + UPDATE characters_class_exp + SET + {0} = @exp + WHERE + characterId = @characterId", CharacterUtils.GetClassNameForId(classId)); + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Prepare(); + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@characterId", player.actorId); + cmd.Parameters.AddWithValue("@exp", exp); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static void SetLevel(Player player, byte classId, short level) + { + 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 = String.Format(@" + UPDATE characters_class_levels + SET + {0} = @lvl + WHERE + characterId = @characterId", CharacterUtils.GetClassNameForId(classId)); + MySqlCommand cmd = new MySqlCommand(query, conn); + + cmd.Prepare(); + cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@characterId", player.actorId); + cmd.Parameters.AddWithValue("@lvl", level); + cmd.ExecuteNonQuery(); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + + public static Retainer LoadRetainer(Player player, int retainerIndex) + { + Retainer retainer = null; + + 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(); + + string query = @" + SELECT server_retainers.id as retainerId, server_retainers.name as name, actorClassId FROM characters_retainers + INNER JOIN server_retainers ON characters_retainers.retainerId = server_retainers.id + WHERE characterId = @charaId + ORDER BY id + LIMIT 1 OFFSET @retainerIndex + "; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@charaId", player.actorId); + cmd.Parameters.AddWithValue("@retainerIndex", retainerIndex-1); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + uint retainerId = reader.GetUInt32("retainerId"); + string name = reader.GetString("name"); + uint actorClassId = reader.GetUInt32("actorClassId"); + + ActorClass actorClass = Server.GetWorldManager().GetActorClass(actorClassId); + + retainer = new Retainer(retainerId, actorClass, player, 0, 0, 0, 0); + retainer.customDisplayName = name; + retainer.LoadEventConditions(actorClass.eventConditions); + } + } + + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + + return retainer; + } + } + + } } diff --git a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj index 39a99994..63fe6bca 100644 --- a/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj +++ b/FFXIVClassic Map Server/FFXIVClassic Map Server.csproj @@ -1,7 +1,7 @@  - - + + Debug AnyCPU @@ -10,9 +10,10 @@ Properties FFXIVClassic_Map_Server FFXIVClassic Map Server - v4.5 + v4.5.1 512 1d22ec4a + AnyCPU @@ -24,6 +25,7 @@ prompt 4 true + true AnyCPU @@ -38,15 +40,33 @@ Always + + true + bin\Debug\ + DEBUG;TRACE + true + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + ..\packages\Cyotek.CircularBuffer.1.0.0.0\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll True - - ..\packages\Dapper.1.42\lib\net45\Dapper.dll - True - False ..\FFXIVClassic Common Class Lib\bin\Debug\FFXIVClassic.Common.dll @@ -58,16 +78,21 @@ ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll True - - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + + False + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll ..\packages\NLog.4.3.5\lib\net45\NLog.dll True + + False + navmesh\SharpNav.dll + + @@ -79,31 +104,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + @@ -123,7 +186,7 @@ - + @@ -137,6 +200,7 @@ + @@ -155,11 +219,11 @@ - - - - - + + + + + @@ -183,9 +247,10 @@ + - + @@ -196,10 +261,9 @@ - + - - + @@ -221,25 +285,24 @@ - + - - - - - - - + + + + + + + - @@ -249,15 +312,30 @@ + - + + + + + + + + + + + + + + + @@ -277,7 +355,7 @@ - + @@ -309,6 +387,7 @@ + @@ -331,10 +410,13 @@ Resources.Designer.cs + + + - xcopy "$(SolutionDir)data\map_config.ini" "$(SolutionDir)$(ProjectName)\$(OutDir)" /d -xcopy "$(SolutionDir)data\scripts" "$(SolutionDir)$(ProjectName)\$(OutDir)scripts\" /e /d /y /s + + diff --git a/FFXIVClassic Map Server/PacketProcessor.cs b/FFXIVClassic Map Server/PacketProcessor.cs index 783dfba0..e0ebcbdf 100644 --- a/FFXIVClassic Map Server/PacketProcessor.cs +++ b/FFXIVClassic Map Server/PacketProcessor.cs @@ -35,7 +35,7 @@ namespace FFXIVClassic_Map_Server public void ProcessPacket(ZoneConnection client, SubPacket subpacket) { - Session session = mServer.GetSession(subpacket.header.targetId); + Session session = mServer.GetSession(subpacket.header.sourceId); if (session == null && subpacket.gameMessage.opcode != 0x1000) return; @@ -56,9 +56,12 @@ namespace FFXIVClassic_Map_Server //World Server - Session Begin case 0x1000: subpacket.DebugPrintSubPacket(); - session = mServer.AddSession(subpacket.header.targetId); - if (session.GetActor().destinationZone != 0) + SessionBeginPacket beginSessionPacket = new SessionBeginPacket(subpacket.data); + + session = mServer.AddSession(subpacket.header.sourceId); + + if (!beginSessionPacket.isLogin) Server.GetWorldManager().DoZoneIn(session.GetActor(), false, session.GetActor().destinationSpawnType); Program.Log.Info("{0} has been added to the session list.", session.GetActor().customDisplayName); @@ -77,7 +80,7 @@ namespace FFXIVClassic_Map_Server Server.GetServer().RemoveSession(session.id); Program.Log.Info("{0} has been removed from the session list.", session.GetActor().customDisplayName); - client.QueuePacket(SessionEndConfirmPacket.BuildPacket(session, endSessionPacket.destinationZoneId), true, false); + session.QueuePacket(SessionEndConfirmPacket.BuildPacket(session, endSessionPacket.destinationZoneId)); client.FlushQueuedSendPackets(); break; //World Server - Party Synch @@ -89,14 +92,14 @@ namespace FFXIVClassic_Map_Server case 0x0001: //subpacket.DebugPrintSubPacket(); PingPacket pingPacket = new PingPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(PongPacket.BuildPacket(session.id, pingPacket.time), true, false)); + session.QueuePacket(PongPacket.BuildPacket(session.id, pingPacket.time)); session.Ping(); break; //Unknown case 0x0002: subpacket.DebugPrintSubPacket(); - client.QueuePacket(_0x2Packet.BuildPacket(session.id), true, false); + session.QueuePacket(_0x2Packet.BuildPacket(session.id)); client.FlushQueuedSendPackets(); break; @@ -112,14 +115,12 @@ namespace FFXIVClassic_Map_Server } if (chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SAY || chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SHOUT) - session.GetActor().BroadcastPacket(SendMessagePacket.BuildPacket(session.id, session.id, chatMessage.logType, session.GetActor().customDisplayName, chatMessage.message), false); + session.GetActor().BroadcastPacket(SendMessagePacket.BuildPacket(session.id, chatMessage.logType, session.GetActor().customDisplayName, chatMessage.message), false); break; //Langauge Code (Client safe to send packets to now) case 0x0006: LangaugeCodePacket langCode = new LangaugeCodePacket(subpacket.data); - session = mServer.AddSession(subpacket.header.targetId); - LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onBeginLogin", true); Server.GetWorldManager().DoZoneIn(session.GetActor(), true, 0x1); LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onLogin", true); @@ -147,7 +148,8 @@ namespace FFXIVClassic_Map_Server SetTargetPacket setTarget = new SetTargetPacket(subpacket.data); session.GetActor().currentTarget = setTarget.actorID; - session.GetActor().BroadcastPacket(SetActorTargetAnimatedPacket.BuildPacket(session.id, session.id, setTarget.actorID), true); + session.GetActor().isAutoAttackEnabled = setTarget.attackTarget != 0xE0000000; + session.GetActor().BroadcastPacket(SetActorTargetAnimatedPacket.BuildPacket(session.id, setTarget.actorID), true); break; //Lock Target case 0x00CC: @@ -181,8 +183,12 @@ namespace FFXIVClassic_Map_Server if (ownerActor == null) { + //Is it your retainer? + if (session.GetActor().currentSpawnedRetainer != null && session.GetActor().currentSpawnedRetainer.actorId == eventStart.scriptOwnerActorID) + ownerActor = session.GetActor().currentSpawnedRetainer; //Is it a instance actor? - ownerActor = session.GetActor().zone.FindActorInArea(session.GetActor().currentEventOwner); + if (ownerActor == null) + ownerActor = session.GetActor().zone.FindActorInArea(session.GetActor().currentEventOwner); if (ownerActor == null) { //Is it a Director? @@ -205,6 +211,11 @@ namespace FFXIVClassic_Map_Server case 0x00CE: subpacket.DebugPrintSubPacket(); break; + //Countdown requested + case 0x00CF: + CountdownRequestPacket countdownPacket = new CountdownRequestPacket(subpacket.data); + session.GetActor().BroadcastCountdown(countdownPacket.countdownLength, countdownPacket.syncTime); + break; //Event Result case 0x012E: subpacket.DebugPrintSubPacket(); @@ -244,15 +255,15 @@ namespace FFXIVClassic_Map_Server //Start Recruiting case 0x01C3: StartRecruitingRequestPacket recruitRequestPacket = new StartRecruitingRequestPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(StartRecruitingResponse.BuildPacket(session.id, true), true, false)); + session.QueuePacket(StartRecruitingResponse.BuildPacket(session.id, true)); break; //End Recruiting case 0x01C4: - client.QueuePacket(BasePacket.CreatePacket(EndRecruitmentPacket.BuildPacket(session.id), true, false)); + session.QueuePacket(EndRecruitmentPacket.BuildPacket(session.id)); break; //Party Window Opened, Request State case 0x01C5: - client.QueuePacket(BasePacket.CreatePacket(RecruiterStatePacket.BuildPacket(session.id, false, false, 0), true, false)); + session.QueuePacket(RecruiterStatePacket.BuildPacket(session.id, false, false, 0)); break; //Search Recruiting case 0x01C7: @@ -268,7 +279,7 @@ namespace FFXIVClassic_Map_Server details.subTaskId = 1; details.comment = "This is a test details packet sent by the server. No implementation has been Created yet..."; details.num[0] = 1; - client.QueuePacket(BasePacket.CreatePacket(CurrentRecruitmentDetailsPacket.BuildPacket(session.id, details), true, false)); + session.QueuePacket(CurrentRecruitmentDetailsPacket.BuildPacket(session.id, details)); break; //Accepted Recruiting case 0x01C6: @@ -277,64 +288,64 @@ namespace FFXIVClassic_Map_Server /* SOCIAL STUFF */ case 0x01C9: AddRemoveSocialPacket addBlackList = new AddRemoveSocialPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(BlacklistAddedPacket.BuildPacket(session.id, true, addBlackList.name), true, false)); + session.QueuePacket(BlacklistAddedPacket.BuildPacket(session.id, true, addBlackList.name)); break; case 0x01CA: AddRemoveSocialPacket RemoveBlackList = new AddRemoveSocialPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(BlacklistRemovedPacket.BuildPacket(session.id, true, RemoveBlackList.name), true, false)); + session.QueuePacket(BlacklistRemovedPacket.BuildPacket(session.id, true, RemoveBlackList.name)); break; case 0x01CB: int offset1 = 0; - client.QueuePacket(BasePacket.CreatePacket(SendBlacklistPacket.BuildPacket(session.id, new String[] { "Test" }, ref offset1), true, false)); + session.QueuePacket(SendBlacklistPacket.BuildPacket(session.id, new String[] { "Test" }, ref offset1)); break; case 0x01CC: AddRemoveSocialPacket addFriendList = new AddRemoveSocialPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(FriendlistAddedPacket.BuildPacket(session.id, true, (uint)addFriendList.name.GetHashCode(), true, addFriendList.name), true, false)); + session.QueuePacket(FriendlistAddedPacket.BuildPacket(session.id, true, (uint)addFriendList.name.GetHashCode(), true, addFriendList.name)); break; case 0x01CD: AddRemoveSocialPacket RemoveFriendList = new AddRemoveSocialPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(FriendlistRemovedPacket.BuildPacket(session.id, true, RemoveFriendList.name), true, false)); + session.QueuePacket(FriendlistRemovedPacket.BuildPacket(session.id, true, RemoveFriendList.name)); break; case 0x01CE: int offset2 = 0; - client.QueuePacket(BasePacket.CreatePacket(SendFriendlistPacket.BuildPacket(session.id, new Tuple[] { new Tuple(01, "Test2") }, ref offset2), true, false)); + session.QueuePacket(SendFriendlistPacket.BuildPacket(session.id, new Tuple[] { new Tuple(01, "Test2") }, ref offset2)); break; case 0x01CF: - client.QueuePacket(BasePacket.CreatePacket(FriendStatusPacket.BuildPacket(session.id, null), true, false)); + session.QueuePacket(FriendStatusPacket.BuildPacket(session.id, null)); break; /* SUPPORT DESK STUFF */ //Request for FAQ/Info List case 0x01D0: FaqListRequestPacket faqRequest = new FaqListRequestPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(FaqListResponsePacket.BuildPacket(session.id, new string[] { "Testing FAQ1", "Coded style!" }), true, false)); + session.QueuePacket(FaqListResponsePacket.BuildPacket(session.id, new string[] { "Testing FAQ1", "Coded style!" })); break; //Request for body of a faq/info selection case 0x01D1: FaqBodyRequestPacket faqBodyRequest = new FaqBodyRequestPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(FaqBodyResponsePacket.BuildPacket(session.id, "HERE IS A GIANT BODY. Nothing else to say!"), true, false)); + session.QueuePacket(FaqBodyResponsePacket.BuildPacket(session.id, "HERE IS A GIANT BODY. Nothing else to say!")); break; //Request issue list case 0x01D2: GMTicketIssuesRequestPacket issuesRequest = new GMTicketIssuesRequestPacket(subpacket.data); - client.QueuePacket(BasePacket.CreatePacket(IssueListResponsePacket.BuildPacket(session.id, new string[] { "Test1", "Test2", "Test3", "Test4", "Test5" }), true, false)); + session.QueuePacket(IssueListResponsePacket.BuildPacket(session.id, new string[] { "Test1", "Test2", "Test3", "Test4", "Test5" })); break; //Request if GM ticket exists case 0x01D3: - client.QueuePacket(BasePacket.CreatePacket(StartGMTicketPacket.BuildPacket(session.id, false), true, false)); + session.QueuePacket(StartGMTicketPacket.BuildPacket(session.id, false)); break; //Request for GM response message case 0x01D4: - client.QueuePacket(BasePacket.CreatePacket(GMTicketPacket.BuildPacket(session.id, "This is a GM Ticket Title", "This is a GM Ticket Body."), true, false)); + session.QueuePacket(GMTicketPacket.BuildPacket(session.id, "This is a GM Ticket Title", "This is a GM Ticket Body.")); break; //GM Ticket Sent case 0x01D5: GMSupportTicketPacket gmTicket = new GMSupportTicketPacket(subpacket.data); Program.Log.Info("Got GM Ticket: \n" + gmTicket.ticketTitle + "\n" + gmTicket.ticketBody); - client.QueuePacket(BasePacket.CreatePacket(GMTicketSentResponsePacket.BuildPacket(session.id, true), true, false)); + session.QueuePacket(GMTicketSentResponsePacket.BuildPacket(session.id, true)); break; //Request to end ticket case 0x01D6: - client.QueuePacket(BasePacket.CreatePacket(EndGMTicketPacket.BuildPacket(session.id), true, false)); + session.QueuePacket(EndGMTicketPacket.BuildPacket(session.id)); break; default: Program.Log.Debug("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode); @@ -346,3 +357,4 @@ namespace FFXIVClassic_Map_Server } } + diff --git a/FFXIVClassic Map Server/Program.cs b/FFXIVClassic Map Server/Program.cs index eb24fc4f..1954b553 100644 --- a/FFXIVClassic Map Server/Program.cs +++ b/FFXIVClassic Map Server/Program.cs @@ -16,10 +16,13 @@ namespace FFXIVClassic_Map_Server class Program { public static Logger Log; + public static Server Server; + public static Random Random; + public static DateTime LastTick = DateTime.Now; + public static DateTime Tick = DateTime.Now; static void Main(string[] args) { - // set up logging Log = LogManager.GetCurrentClassLogger(); #if DEBUG @@ -28,7 +31,10 @@ namespace FFXIVClassic_Map_Server #endif bool startServer = true; - Program.Log.Info("---------FFXIV 1.0 Map Server---------"); + Log.Info("=================================="); + Log.Info("FFXIV Classic Map Server"); + Log.Info("Version: 0.1"); + Log.Info("=================================="); //Load Config ConfigConstants.Load(); @@ -55,9 +61,10 @@ namespace FFXIVClassic_Map_Server //Start server if A-OK if (startServer) { - Server server = new Server(); - - server.StartServer(); + Random = new Random(); + Server = new Server(); + Tick = DateTime.Now; + Server.StartServer(); while (startServer) { diff --git a/FFXIVClassic Map Server/Properties/Resources.Designer.cs b/FFXIVClassic Map Server/Properties/Resources.Designer.cs index 75ee4efc..11a22948 100644 --- a/FFXIVClassic Map Server/Properties/Resources.Designer.cs +++ b/FFXIVClassic Map Server/Properties/Resources.Designer.cs @@ -17,7 +17,7 @@ namespace FFXIVClassic_Map_Server.Properties { /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. - // To add or Remove a member, edit your .ResX file then rerun ResGen + // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] @@ -105,7 +105,7 @@ namespace FFXIVClassic_Map_Server.Properties { /// ///Available commands: ///Standard: mypos, music, warp - ///Server Administration: givecurrency, giveitem, givekeyitem, Removecurrency, Removekeyitem, reloaditems, reloadzones + ///Server Administration: givecurrency, giveitem, givekeyitem, removecurrency, removekeyitem, reloaditems, reloadzones ///Test: test weather. /// public static string CPhelp { @@ -176,38 +176,38 @@ namespace FFXIVClassic_Map_Server.Properties { /// /// Looks up a localized string similar to Removes the specified currency from the current player's inventory /// - ///*Syntax: Removecurrency <quantity> - /// Removecurrency <type> <quantity> + ///*Syntax: removecurrency <quantity> + /// removecurrency <type> <quantity> ///<type> is the specific type of currency desired, defaults to gil if no type specified. /// - public static string CPRemovecurrency { + public static string CPremovecurrency { get { - return ResourceManager.GetString("CPRemovecurrency", resourceCulture); + return ResourceManager.GetString("CPremovecurrency", resourceCulture); } } /// /// Looks up a localized string similar to Removes the specified items to the current player's inventory /// - ///*Syntax: Removeitem <itemid> - /// Removeitem <itemid> <quantity> + ///*Syntax: removeitem <itemid> + /// removeitem <itemid> <quantity> ///<item id> is the item's specific id as defined in the server database. /// - public static string CPRemoveitem { + public static string CPremoveitem { get { - return ResourceManager.GetString("CPRemoveitem", resourceCulture); + return ResourceManager.GetString("CPremoveitem", resourceCulture); } } /// /// Looks up a localized string similar to Removes the specified key item to the current player's inventory /// - ///*Syntax: Removekeyitem <itemid> + ///*Syntax: removekeyitem <itemid> ///<item id> is the key item's specific id as defined in the server database. /// - public static string CPRemovekeyitem { + public static string CPremovekeyitem { get { - return ResourceManager.GetString("CPRemovekeyitem", resourceCulture); + return ResourceManager.GetString("CPremovekeyitem", resourceCulture); } } diff --git a/FFXIVClassic Map Server/Server.cs b/FFXIVClassic Map Server/Server.cs index d39bcce0..97f201f5 100644 --- a/FFXIVClassic Map Server/Server.cs +++ b/FFXIVClassic Map Server/Server.cs @@ -27,7 +27,8 @@ namespace FFXIVClassic_Map_Server private static CommandProcessor mCommandProcessor = new CommandProcessor(); private static ZoneConnection mWorldConnection = new ZoneConnection(); private static WorldManager mWorldManager; - private static Dictionary mGamedataItems; + private static Dictionary mGamedataItems; + private static Dictionary mGamedataGuildleves; private static StaticActors mStaticActors; private PacketProcessor mProcessor; @@ -43,6 +44,8 @@ namespace FFXIVClassic_Map_Server mGamedataItems = Database.GetItemGamedata(); Program.Log.Info("Loaded {0} items.", mGamedataItems.Count); + mGamedataGuildleves = Database.GetGuildleveGamedata(); + Program.Log.Info("Loaded {0} guildleves.", mGamedataGuildleves.Count); mWorldManager = new WorldManager(this); mWorldManager.LoadZoneList(); @@ -50,6 +53,10 @@ namespace FFXIVClassic_Map_Server mWorldManager.LoadSeamlessBoundryList(); mWorldManager.LoadActorClasses(); mWorldManager.LoadSpawnLocations(); + mWorldManager.LoadBattleNpcs(); + mWorldManager.LoadStatusEffects(); + mWorldManager.LoadBattleCommands(); + mWorldManager.LoadBattleTraits(); mWorldManager.SpawnAllActors(); mWorldManager.StartZoneThread(); @@ -267,7 +274,7 @@ namespace FFXIVClassic_Map_Server return mWorldManager; } - public static Dictionary GetGamedataItems() + public static Dictionary GetGamedataItems() { return mGamedataItems; } @@ -282,7 +289,7 @@ namespace FFXIVClassic_Map_Server return mStaticActors.FindStaticActor(name); } - public static Item GetItemGamedata(uint id) + public static ItemData GetItemGamedata(uint id) { if (mGamedataItems.ContainsKey(id)) return mGamedataItems[id]; @@ -290,5 +297,13 @@ namespace FFXIVClassic_Map_Server return null; } + public static GuildleveData GetGuildleveGamedata(uint id) + { + if (mGamedataGuildleves.ContainsKey(id)) + return mGamedataGuildleves[id]; + else + return null; + } + } } \ No newline at end of file diff --git a/FFXIVClassic Map Server/SharpNav.dll b/FFXIVClassic Map Server/SharpNav.dll new file mode 100644 index 00000000..3c648e5c Binary files /dev/null and b/FFXIVClassic Map Server/SharpNav.dll differ diff --git a/FFXIVClassic Map Server/WorldManager.cs b/FFXIVClassic Map Server/WorldManager.cs index 047ec221..e5ff47f4 100644 --- a/FFXIVClassic Map Server/WorldManager.cs +++ b/FFXIVClassic Map Server/WorldManager.cs @@ -3,7 +3,6 @@ using FFXIVClassic.Common; using FFXIVClassic_Map_Server.actors.area; using FFXIVClassic_Map_Server.actors.chara.npc; using FFXIVClassic_Map_Server.Actors; -using FFXIVClassic.Common; using FFXIVClassic_Map_Server.dataobjects; using FFXIVClassic_Map_Server.dataobjects.chara; using FFXIVClassic_Map_Server.lua; @@ -23,6 +22,9 @@ using FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group; using System.Threading; using System.Diagnostics; using FFXIVClassic_Map_Server.actors.director; +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara; +using FFXIVClassic_Map_Server.Actors.Chara; namespace FFXIVClassic_Map_Server { @@ -35,10 +37,18 @@ namespace FFXIVClassic_Map_Server private Dictionary zoneEntranceList; private Dictionary actorClasses = new Dictionary(); private Dictionary currentPlayerParties = new Dictionary(); //GroupId, Party object + private Dictionary statusEffectList = new Dictionary(); + private Dictionary battleCommandList = new Dictionary(); + private Dictionary, List> battleCommandIdByLevel = new Dictionary, List>();//Holds battle command ids keyed by class id and level (in that order) + private Dictionary battleTraitList = new Dictionary(); + private Dictionary> battleTraitIdsForClass = new Dictionary>(); + private Dictionary battleNpcGenusMods = new Dictionary(); + private Dictionary battleNpcPoolMods = new Dictionary(); + private Dictionary battleNpcSpawnMods = new Dictionary(); private Server mServer; - private const int MILIS_LOOPTIME = 10; + private const int MILIS_LOOPTIME = 333; private Timer mZoneTimer; //Content Groups @@ -76,7 +86,8 @@ namespace FFXIVClassic_Map_Server isInn, canRideChocobo, canStealth, - isInstanceRaid + isInstanceRaid, + loadNavMesh FROM server_zones WHERE zoneName IS NOT NULL and serverIp = @ip and serverPort = @port"; @@ -89,7 +100,8 @@ namespace FFXIVClassic_Map_Server { while (reader.Read()) { - Zone zone = new Zone(reader.GetUInt32(0), reader.GetString(1), reader.GetUInt16(2), reader.GetString(3), reader.GetUInt16(4), reader.GetUInt16(5), reader.GetUInt16(6), reader.GetBoolean(7), reader.GetBoolean(8), reader.GetBoolean(9), reader.GetBoolean(10), reader.GetBoolean(11)); + Zone zone = new Zone(reader.GetUInt32(0), reader.GetString(1), reader.GetUInt16(2), reader.GetString(3), reader.GetUInt16(4), reader.GetUInt16(5), + reader.GetUInt16(6), reader.GetBoolean(7), reader.GetBoolean(8), reader.GetBoolean(9), reader.GetBoolean(10), reader.GetBoolean(11), reader.GetBoolean(12)); zoneList[zone.actorId] = zone; count1++; } @@ -413,6 +425,121 @@ namespace FFXIVClassic_Map_Server Program.Log.Info(String.Format("Loaded {0} spawn(s).", count)); } + public void LoadBattleNpcs() + { + LoadBattleNpcModifiers("server_battlenpc_genus_mods", "genusId", battleNpcGenusMods); + LoadBattleNpcModifiers("server_battlenpc_pool_mods", "poolId", battleNpcPoolMods); + LoadBattleNpcModifiers("server_battlenpc_spawn_mods", "bnpcId", battleNpcSpawnMods); + + 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 bsl.bnpcId, bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation, + bgr.groupId, bgr.poolId, bgr.scriptName, bgr.minLevel, bgr.maxLevel, bgr.respawnTime, bgr.hp, bgr.mp, + bgr.dropListId, bgr.allegiance, bgr.spawnType, bgr.animationId, bgr.actorState, bgr.privateAreaName, bgr.privateAreaLevel, bgr.zoneId, + bpo.poolId, bpo.genusId, bpo.actorClassId, bpo.currentJob, bpo.combatSkill, bpo.combatDelay, bpo.combatDmgMult, bpo.aggroType, + bpo.immunity, bpo.linkType, bpo.skillListId, bpo.spellListId, + bge.genusId, bge.modelSize, bge.speed, bge.kindredId, bge.detection, bge.hpp, bge.mpp, bge.tpp, bge.str, bge.vit, bge.dex, + bge.int, bge.mnd, bge.pie, bge.att, bge.acc, bge.def, bge.eva, bge.slash, bge.pierce, bge.h2h, bge.blunt, + bge.fire, bge.ice, bge.wind, bge.lightning, bge.earth, bge.water, bge.element + FROM server_battlenpc_spawn_locations bsl + INNER JOIN server_battlenpc_groups bgr ON bsl.groupId = bgr.groupId + INNER JOIN server_battlenpc_pools bpo ON bgr.poolId = bpo.poolId + INNER JOIN server_battlenpc_genus bge ON bpo.genusId = bge.genusId + WHERE bgr.zoneId = @zoneId GROUP BY bsl.bnpcId; + "; + + var count = 0; + foreach (var zonePair in zoneList) + { + Area zone = zonePair.Value; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@zoneId", zonePair.Key); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int actorId = zone.GetActorCount() + 1; + + // todo: add to private areas, set up immunity, mob linking, + // - load skill/spell/drop lists, set detection icon, load pool/family/group mods + + var battleNpc = new BattleNpc(actorId, Server.GetWorldManager().GetActorClass(reader.GetUInt32("actorClassId")), + reader.GetString("scriptName"), zone, reader.GetFloat("positionX"), reader.GetFloat("positionY"), reader.GetFloat("positionZ"), reader.GetFloat("rotation"), + reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), ""); + + battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId")); + + battleNpc.poolId = reader.GetUInt32("poolId"); + battleNpc.genusId = reader.GetUInt32("genusId"); + battleNpcPoolMods.TryGetValue(battleNpc.poolId, out battleNpc.poolMods); + battleNpcGenusMods.TryGetValue(battleNpc.genusId, out battleNpc.genusMods); + battleNpcSpawnMods.TryGetValue(battleNpc.GetBattleNpcId(), out battleNpc.spawnMods); + + battleNpc.SetMod((uint)Modifier.MovementSpeed, reader.GetByte("speed")); + battleNpc.neutral = reader.GetByte("aggroType") == 0; + + battleNpc.SetDetectionType(reader.GetUInt32("detection")); + battleNpc.kindredType = (KindredType)reader.GetUInt32("kindredId"); + battleNpc.npcSpawnType = (NpcSpawnType)reader.GetUInt32("spawnType"); + + battleNpc.charaWork.parameterSave.state_mainSkill[0] = reader.GetByte("currentJob"); + battleNpc.charaWork.parameterSave.state_mainSkillLevel = (short)Program.Random.Next(reader.GetByte("minLevel"), reader.GetByte("maxLevel")); + + battleNpc.allegiance = (CharacterTargetingAllegiance)reader.GetByte("allegiance"); + + // todo: setup private areas and other crap and + // set up rest of stat resists + battleNpc.SetMod((uint)Modifier.Hp, reader.GetUInt32("hp")); + battleNpc.SetMod((uint)Modifier.HpPercent, reader.GetUInt32("hpp")); + battleNpc.SetMod((uint)Modifier.Mp, reader.GetUInt32("mp")); + battleNpc.SetMod((uint)Modifier.MpPercent, reader.GetUInt32("mpp")); + battleNpc.SetMod((uint)Modifier.TpPercent, reader.GetUInt32("tpp")); + + battleNpc.SetMod((uint)Modifier.Strength, reader.GetUInt32("str")); + battleNpc.SetMod((uint)Modifier.Vitality, reader.GetUInt32("vit")); + battleNpc.SetMod((uint)Modifier.Dexterity, reader.GetUInt32("dex")); + battleNpc.SetMod((uint)Modifier.Intelligence, reader.GetUInt32("int")); + battleNpc.SetMod((uint)Modifier.Mind, reader.GetUInt32("mnd")); + battleNpc.SetMod((uint)Modifier.Piety, reader.GetUInt32("pie")); + battleNpc.SetMod((uint)Modifier.Attack, reader.GetUInt32("att")); + battleNpc.SetMod((uint)Modifier.Accuracy, reader.GetUInt32("acc")); + battleNpc.SetMod((uint)Modifier.Defense, reader.GetUInt32("def")); + battleNpc.SetMod((uint)Modifier.Evasion, reader.GetUInt32("eva")); + + battleNpc.dropListId = reader.GetUInt32("dropListId"); + battleNpc.spellListId = reader.GetUInt32("spellListId"); + battleNpc.skillListId = reader.GetUInt32("skillListId"); + + //battleNpc.SetMod((uint)Modifier.ResistFire, ) + + // todo: this is dumb + if (battleNpc.npcSpawnType == NpcSpawnType.Normal) + { + zone.AddActorToZone(battleNpc); + count++; + } + } + } + } + Program.Log.Info("Loaded {0} monsters.", count); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + public void SpawnAllActors() { Program.Log.Info("Spawning actors..."); @@ -420,6 +547,200 @@ namespace FFXIVClassic_Map_Server z.SpawnAllActors(true); } + public BattleNpc SpawnBattleNpcById(uint id, Area area = null) + { + BattleNpc bnpc = null; + // todo: this is stupid duplicate code and really needs to die, think of a better way later + 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 bsl.bnpcId, bsl.groupId, bsl.positionX, bsl.positionY, bsl.positionZ, bsl.rotation, + bgr.groupId, bgr.poolId, bgr.scriptName, bgr.minLevel, bgr.maxLevel, bgr.respawnTime, bgr.hp, bgr.mp, + bgr.dropListId, bgr.allegiance, bgr.spawnType, bgr.animationId, bgr.actorState, bgr.privateAreaName, bgr.privateAreaLevel, bgr.zoneId, + bpo.poolId, bpo.genusId, bpo.actorClassId, bpo.currentJob, bpo.combatSkill, bpo.combatDelay, bpo.combatDmgMult, bpo.aggroType, + bpo.immunity, bpo.linkType, bpo.skillListId, bpo.spellListId, + bge.genusId, bge.modelSize, bge.speed, bge.kindredId, bge.detection, bge.hpp, bge.mpp, bge.tpp, bge.str, bge.vit, bge.dex, + bge.int, bge.mnd, bge.pie, bge.att, bge.acc, bge.def, bge.eva, bge.slash, bge.pierce, bge.h2h, bge.blunt, + bge.fire, bge.ice, bge.wind, bge.lightning, bge.earth, bge.water, bge.element + FROM server_battlenpc_spawn_locations bsl + INNER JOIN server_battlenpc_groups bgr ON bsl.groupId = bgr.groupId + INNER JOIN server_battlenpc_pools bpo ON bgr.poolId = bpo.poolId + INNER JOIN server_battlenpc_genus bge ON bpo.genusId = bge.genusId + WHERE bsl.bnpcId = @bnpcId GROUP BY bsl.bnpcId; + "; + + var count = 0; + + MySqlCommand cmd = new MySqlCommand(query, conn); + cmd.Parameters.AddWithValue("@bnpcId", id); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + area = area ?? Server.GetWorldManager().GetZone(reader.GetUInt16("zoneId")); + int actorId = area.GetActorCount() + 1; + bnpc = area.GetBattleNpcById(id); + + if (bnpc != null) + { + bnpc.ForceRespawn(); + break; + } + + // todo: add to private areas, set up immunity, mob linking, + // - load skill/spell/drop lists, set detection icon, load pool/family/group mods + var allegiance = (CharacterTargetingAllegiance)reader.GetByte("allegiance"); + BattleNpc battleNpc = null; + + if (allegiance == CharacterTargetingAllegiance.Player) + battleNpc = new Ally(actorId, Server.GetWorldManager().GetActorClass(reader.GetUInt32("actorClassId")), + reader.GetString("scriptName"), area, reader.GetFloat("positionX"), reader.GetFloat("positionY"), reader.GetFloat("positionZ"), reader.GetFloat("rotation"), + reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), ""); + else + battleNpc = new BattleNpc(actorId, Server.GetWorldManager().GetActorClass(reader.GetUInt32("actorClassId")), + reader.GetString("scriptName"), area, reader.GetFloat("positionX"), reader.GetFloat("positionY"), reader.GetFloat("positionZ"), reader.GetFloat("rotation"), + reader.GetUInt16("actorState"), reader.GetUInt32("animationId"), ""); + + battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId")); + battleNpc.SetMod((uint)Modifier.MovementSpeed, reader.GetByte("speed")); + battleNpc.neutral = reader.GetByte("aggroType") == 0; + + // set mob mods + battleNpc.poolId = reader.GetUInt32("poolId"); + battleNpc.genusId = reader.GetUInt32("genusId"); + battleNpcPoolMods.TryGetValue(battleNpc.poolId, out battleNpc.poolMods); + battleNpcGenusMods.TryGetValue(battleNpc.genusId, out battleNpc.genusMods); + battleNpcSpawnMods.TryGetValue(battleNpc.GetBattleNpcId(), out battleNpc.spawnMods); + + battleNpc.SetDetectionType(reader.GetUInt32("detection")); + battleNpc.kindredType = (KindredType)reader.GetUInt32("kindredId"); + battleNpc.npcSpawnType = (NpcSpawnType)reader.GetUInt32("spawnType"); + + battleNpc.charaWork.parameterSave.state_mainSkill[0] = reader.GetByte("currentJob"); + battleNpc.charaWork.parameterSave.state_mainSkillLevel = (short)Program.Random.Next(reader.GetByte("minLevel"), reader.GetByte("maxLevel")); + + battleNpc.allegiance = (CharacterTargetingAllegiance)reader.GetByte("allegiance"); + + // todo: setup private areas and other crap and + // set up rest of stat resists + battleNpc.SetMod((uint)Modifier.Hp, reader.GetUInt32("hp")); + battleNpc.SetMod((uint)Modifier.HpPercent, reader.GetUInt32("hpp")); + battleNpc.SetMod((uint)Modifier.Mp, reader.GetUInt32("mp")); + battleNpc.SetMod((uint)Modifier.MpPercent, reader.GetUInt32("mpp")); + battleNpc.SetMod((uint)Modifier.TpPercent, reader.GetUInt32("tpp")); + + battleNpc.SetMod((uint)Modifier.Strength, reader.GetUInt32("str")); + battleNpc.SetMod((uint)Modifier.Vitality, reader.GetUInt32("vit")); + battleNpc.SetMod((uint)Modifier.Dexterity, reader.GetUInt32("dex")); + battleNpc.SetMod((uint)Modifier.Intelligence, reader.GetUInt32("int")); + battleNpc.SetMod((uint)Modifier.Mind, reader.GetUInt32("mnd")); + battleNpc.SetMod((uint)Modifier.Piety, reader.GetUInt32("pie")); + battleNpc.SetMod((uint)Modifier.Attack, reader.GetUInt32("att")); + battleNpc.SetMod((uint)Modifier.Accuracy, reader.GetUInt32("acc")); + battleNpc.SetMod((uint)Modifier.Defense, reader.GetUInt32("def")); + battleNpc.SetMod((uint)Modifier.Evasion, reader.GetUInt32("eva")); + + if (battleNpc.poolMods != null) + { + foreach (var a in battleNpc.poolMods.mobModList) + { + battleNpc.SetMobMod(a.Value.id, (long)(a.Value.value)); + } + foreach (var a in battleNpc.poolMods.modList) + { + battleNpc.SetMod(a.Key, (long)(a.Value.value)); + } + } + + if (battleNpc.genusMods != null) + { + foreach (var a in battleNpc.genusMods.mobModList) + { + battleNpc.SetMobMod(a.Key, (long)(a.Value.value)); + } + foreach (var a in battleNpc.genusMods.modList) + { + battleNpc.SetMod(a.Key, (long)(a.Value.value)); + } + } + + if(battleNpc.spawnMods != null) + { + foreach (var a in battleNpc.spawnMods.mobModList) + { + battleNpc.SetMobMod(a.Key, (long)(a.Value.value)); + } + + foreach (var a in battleNpc.spawnMods.modList) + { + battleNpc.SetMod(a.Key, (long)(a.Value.value)); + } + } + + battleNpc.dropListId = reader.GetUInt32("dropListId"); + battleNpc.spellListId = reader.GetUInt32("spellListId"); + battleNpc.skillListId = reader.GetUInt32("skillListId"); + battleNpc.SetBattleNpcId(reader.GetUInt32("bnpcId")); + battleNpc.SetRespawnTime(reader.GetUInt32("respawnTime")); + battleNpc.CalculateBaseStats(); + battleNpc.RecalculateStats(); + //battleNpc.SetMod((uint)Modifier.ResistFire, ) + bnpc = battleNpc; + area.AddActorToZone(battleNpc); + count++; + } + } + Program.Log.Info("WorldManager.SpawnBattleNpcById spawned BattleNpc {0}.", id); + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + return bnpc; + } + + public void LoadBattleNpcModifiers(string tableName, string primaryKey, Dictionary list) + { + 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 {primaryKey}, modId, modVal, isMobMod FROM {tableName}"; + + MySqlCommand cmd = new MySqlCommand(query, conn); + + using (MySqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var id = reader.GetUInt32(primaryKey); + ModifierList modList = list.TryGetValue(id, out modList) ? modList : new ModifierList(id); + modList.SetModifier(reader.GetUInt16("modId"), reader.GetInt64("modVal"), reader.GetBoolean("isMobMod")); + list[id] = modList; + } + } + } + catch (MySqlException e) + { + Program.Log.Error(e.ToString()); + } + finally + { + conn.Dispose(); + } + } + } + //Moves the actor to the new zone if exists. No packets are sent nor position changed. Merged zone is removed. public void DoSeamlessZoneChange(Player player, uint destinationZoneId) { @@ -582,7 +903,6 @@ namespace FFXIVClassic_Map_Server { oldZone.RemoveActorFromZone(player); } - newArea.AddActorToZone(player); //Update player actor's properties @@ -596,19 +916,24 @@ namespace FFXIVClassic_Map_Server player.positionZ = spawnZ; player.rotation = spawnRotation; + //Delete any GL directors + GuildleveDirector glDirector = player.GetGuildleveDirector(); + if (glDirector != null) + player.RemoveDirector(glDirector); + //Delete content if have if (player.currentContentGroup != null) { player.currentContentGroup.RemoveMember(player.actorId); - player.SetCurrentContentGroup(null, player); + player.SetCurrentContentGroup(null); if (oldZone is PrivateAreaContent) ((PrivateAreaContent)oldZone).CheckDestroy(); } //Send packets - player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId), true, false); - player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10), true, false); + player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId)); + player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x2)); player.SendZoneInPackets(this, spawnType); player.playerSession.ClearInstance(); player.SendInstanceUpdate(); @@ -645,7 +970,8 @@ namespace FFXIVClassic_Map_Server //Remove player from currentZone if transfer else it's login if (player.zone != null) { - player.zone.RemoveActorFromZone(player); + player.playerSession.LockUpdates(true); + player.zone.RemoveActorFromZone(player); player.zone.AddActorToZone(player); //Update player actor's properties; @@ -655,10 +981,11 @@ namespace FFXIVClassic_Map_Server player.rotation = spawnRotation; //Send packets - player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10), true, false); - player.playerSession.QueuePacket(player.CreateSpawnTeleportPacket(player.actorId, spawnType), true, false); - player.SendInstanceUpdate(); + player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10)); + player.playerSession.QueuePacket(player.CreateSpawnTeleportPacket(spawnType)); + player.playerSession.LockUpdates(false); + player.SendInstanceUpdate(); } } @@ -698,8 +1025,8 @@ namespace FFXIVClassic_Map_Server player.SendGameMessage(GetActor(), 34108, 0x20); //Send packets - player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId), true, false); - player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10), true, false); + player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId)); + player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10)); player.SendZoneInPackets(this, spawnType); player.playerSession.ClearInstance(); player.SendInstanceUpdate(); @@ -733,9 +1060,9 @@ namespace FFXIVClassic_Map_Server //Send packets if (!isLogin) { - player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId), true, false); - player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x2), true, false); - player.SendZoneInPackets(this, spawnType); + player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId)); + player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x2)); + //player.SendZoneInPackets(this, spawnType); } player.SendZoneInPackets(this, spawnType); @@ -751,18 +1078,15 @@ namespace FFXIVClassic_Map_Server public void ReloadZone(uint zoneId) { - if (!zoneList.ContainsKey(zoneId)) - return; + lock (zoneList) + { + if (!zoneList.ContainsKey(zoneId)) + return; - Zone zone = zoneList[zoneId]; - //zone.clear(); - //LoadNPCs(zone.actorId); - - } - - public ContentGroup CreateContentGroup(Director director) - { - return CreateContentGroup(director, null); + Zone zone = zoneList[zoneId]; + //zone.clear(); + //LoadNPCs(zone.actorId); + } } public ContentGroup CreateContentGroup(Director director, params Actor[] actors) @@ -793,6 +1117,62 @@ namespace FFXIVClassic_Map_Server } } + public ContentGroup CreateContentGroup(Director director, List actors) + { + if (director == null) + return null; + + lock (groupLock) + { + uint[] initialMembers = null; + + if (actors != null) + { + initialMembers = new uint[actors.Count]; + for (int i = 0; i < actors.Count; i++) + initialMembers[i] = actors[i].actorId; + } + + groupIndexId = groupIndexId | 0x3000000000000000; + + ContentGroup contentGroup = new ContentGroup(groupIndexId, director, initialMembers); + mContentGroups.Add(groupIndexId, contentGroup); + groupIndexId++; + if (initialMembers != null && initialMembers.Length != 0) + contentGroup.SendAll(); + + return contentGroup; + } + } + + public ContentGroup CreateGLContentGroup(Director director, List actors) + { + if (director == null) + return null; + + lock (groupLock) + { + uint[] initialMembers = null; + + if (actors != null) + { + initialMembers = new uint[actors.Count]; + for (int i = 0; i < actors.Count; i++) + initialMembers[i] = actors[i].actorId; + } + + groupIndexId = groupIndexId | 0x2000000000000000; + + GLContentGroup contentGroup = new GLContentGroup(groupIndexId, director, initialMembers); + mContentGroups.Add(groupIndexId, contentGroup); + groupIndexId++; + if (initialMembers != null && initialMembers.Length != 0) + contentGroup.SendAll(); + + return contentGroup; + } + } + public void DeleteContentGroup(ulong groupId) { lock (groupLock) @@ -800,7 +1180,6 @@ namespace FFXIVClassic_Map_Server if (mContentGroups.ContainsKey(groupId) && mContentGroups[groupId] is ContentGroup) { ContentGroup group = (ContentGroup)mContentGroups[groupId]; - group.SendDeletePackets(); mContentGroups.Remove(groupId); } } @@ -819,55 +1198,55 @@ namespace FFXIVClassic_Map_Server public void RequestWorldLinkshellCreate(Player player, string name, ushort crest) { SubPacket packet = CreateLinkshellPacket.BuildPacket(player.playerSession, name, crest, player.actorId); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellCrestModify(Player player, string name, ushort crest) { SubPacket packet = ModifyLinkshellPacket.BuildPacket(player.playerSession, 1, name, null, crest, 0); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellDelete(Player player, string name) { SubPacket packet = DeleteLinkshellPacket.BuildPacket(player.playerSession, name); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellRankChange(Player player, string lsname, string memberName, byte newRank) { SubPacket packet = LinkshellRankChangePacket.BuildPacket(player.playerSession, memberName, lsname, newRank); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellInviteMember(Player player, string lsname, uint invitedActorId) { SubPacket packet = LinkshellInvitePacket.BuildPacket(player.playerSession, invitedActorId, lsname); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellCancelInvite(Player player) { SubPacket packet = LinkshellInviteCancelPacket.BuildPacket(player.playerSession); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellLeave(Player player, string lsname) { SubPacket packet = LinkshellLeavePacket.BuildPacket(player.playerSession, lsname, null, false); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellKick(Player player, string lsname, string kickedName) { SubPacket packet = LinkshellLeavePacket.BuildPacket(player.playerSession, lsname, kickedName, true); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } public void RequestWorldLinkshellChangeActive(Player player, string lsname) { SubPacket packet = LinkshellChangePacket.BuildPacket(player.playerSession, lsname); - Server.GetWorldConnection().QueuePacket(packet, true, false); + player.QueuePacket(packet); } private void RequestWorldServerZoneChange(Player player, uint destinationZoneId, byte spawnType, float spawnX, float spawnY, float spawnZ, float spawnRotation) @@ -949,11 +1328,17 @@ namespace FFXIVClassic_Map_Server } public void ZoneThreadLoop(Object state) - { + { + // todo: coroutines GetActorInWorld stuff seems to be causing it to hang + // todo: spawn new thread for each zone on startup lock (zoneList) { - foreach (Area area in zoneList.Values) - area.Update(MILIS_LOOPTIME); + Program.Tick = DateTime.Now; + foreach (Zone zone in zoneList.Values) + { + zone.Update(Program.Tick); + } + Program.LastTick = Program.Tick; } } @@ -975,39 +1360,52 @@ namespace FFXIVClassic_Map_Server public Actor GetActorInWorld(uint charId) { - foreach (Zone zone in zoneList.Values) + lock (zoneList) { - Actor a = zone.FindActorInZone(charId); - if (a != null) - return a; + foreach (Zone zone in zoneList.Values) + { + Actor a = zone.FindActorInZone(charId); + if (a != null) + return a; + } } return null; } public Actor GetActorInWorldByUniqueId(string uid) { - foreach (Zone zone in zoneList.Values) + lock (zoneList) { - Actor a = zone.FindActorInZoneByUniqueID(uid); - if (a != null) - return a; + foreach (Zone zone in zoneList.Values) + { + Actor a = zone.FindActorInZoneByUniqueID(uid); + if (a != null) + return a; + } } return null; } public Zone GetZone(uint zoneId) { - if (!zoneList.ContainsKey(zoneId)) - return null; - return zoneList[zoneId]; + lock (zoneList) + { + if (!zoneList.ContainsKey(zoneId)) + return null; + + return zoneList[zoneId]; + } } public PrivateArea GetPrivateArea(uint zoneId, string privateArea, uint privateAreaType) { - if (!zoneList.ContainsKey(zoneId)) - return null; + lock (zoneList) + { + if (!zoneList.ContainsKey(zoneId)) + return null; - return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType); + return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType); + } } public WorldMaster GetActor() @@ -1059,7 +1457,52 @@ namespace FFXIVClassic_Map_Server else return null; } + public void LoadStatusEffects() + { + statusEffectList = Database.LoadGlobalStatusEffectList(); + } + + public StatusEffect GetStatusEffect(uint id) + { + StatusEffect statusEffect; + + return statusEffectList.TryGetValue(id, out statusEffect) ? new StatusEffect(null, statusEffect) : null; + } + + public void LoadBattleCommands() + { + Database.LoadGlobalBattleCommandList(battleCommandList, battleCommandIdByLevel); + } + + public void LoadBattleTraits() + { + Database.LoadGlobalBattleTraitList(battleTraitList, battleTraitIdsForClass); + } + + public BattleCommand GetBattleCommand(uint id) + { + BattleCommand battleCommand; + return battleCommandList.TryGetValue((ushort)id, out battleCommand) ? battleCommand.Clone() : null; + } + + public List GetBattleCommandIdByLevel(byte classId, short level) + { + List ids; + return battleCommandIdByLevel.TryGetValue(Tuple.Create(classId, level), out ids) ? ids : new List(); + } + + public BattleTrait GetBattleTrait(ushort id) + { + BattleTrait battleTrait; + battleTraitList.TryGetValue(id, out battleTrait); + return battleTrait; + } + + public List GetAllBattleTraitIdsForClass(byte classId) + { + List ids; + return battleTraitIdsForClass.TryGetValue(classId, out ids) ? ids : new List(); + } } - } diff --git a/FFXIVClassic Map Server/actors/Actor.cs b/FFXIVClassic Map Server/actors/Actor.cs index 640e2cea..b9126e46 100644 --- a/FFXIVClassic Map Server/actors/Actor.cs +++ b/FFXIVClassic Map Server/actors/Actor.cs @@ -9,11 +9,36 @@ using System.Collections.Generic; using FFXIVClassic_Map_Server.actors.area; using System.Reflection; using System.ComponentModel; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.packets.send; +using FFXIVClassic_Map_Server.actors.chara; namespace FFXIVClassic_Map_Server.Actors { + [Flags] + enum ActorUpdateFlags + { + None = 0x00, + Position = 0x01, + HpTpMp = 0x02, + State = 0x04, + SubState = 0x08, + Combat = 0x0F, + Name = 0x10, + Appearance = 0x20, + Speed = 0x40, + Work = 0x80, + Stats = 0x100, + Status = 0x200, + StatusTime = 0x400, + + AllNpc = 0xDF, + AllPlayer = 0x13F + } + class Actor { + public static uint INVALID_ACTORID = 0xC0000000; public uint actorId; public string actorName; @@ -21,7 +46,9 @@ namespace FFXIVClassic_Map_Server.Actors public string customDisplayName; public ushort currentMainState = SetActorStatePacket.MAIN_STATE_PASSIVE; - public ushort currentSubState = SetActorStatePacket.SUB_STATE_NONE; + + public SubState currentSubState = new SubState(); + public float positionX, positionY, positionZ, rotation; public float oldPositionX, oldPositionY, oldPositionZ, oldRotation; public ushort moveState, oldMoveState; @@ -40,6 +67,15 @@ namespace FFXIVClassic_Map_Server.Actors public string className; public List classParams; + public List positionUpdates; + protected DateTime lastUpdateScript; + protected DateTime lastUpdate; + public Actor target; + + public bool isAtSpawn = true; + + public ActorUpdateFlags updateFlags; + public EventList eventConditions; public Actor(uint actorId) @@ -58,6 +94,7 @@ namespace FFXIVClassic_Map_Server.Actors this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK; this.moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN; this.moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE; + positionUpdates = new List(); } public void SetPushCircleRange(string triggerName, float size) @@ -75,66 +112,84 @@ namespace FFXIVClassic_Map_Server.Actors } } - public SubPacket CreateAddActorPacket(uint playerActorId, byte val) + public virtual void ResetMoveSpeeds() { - return AddActorPacket.BuildPacket(actorId, playerActorId, val); + this.moveSpeeds[0] = SetActorSpeedPacket.DEFAULT_STOP; + this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK; + this.moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN; + this.moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE; + + this.moveState = this.oldMoveState; + this.updateFlags |= ActorUpdateFlags.Speed; } - public SubPacket CreateNamePacket(uint playerActorId) + public SubPacket CreateAddActorPacket(byte val) { - return SetActorNamePacket.BuildPacket(actorId, playerActorId, displayNameId, displayNameId == 0xFFFFFFFF | displayNameId == 0x0 ? customDisplayName : ""); + return AddActorPacket.BuildPacket(actorId, val); } - public SubPacket CreateSpeedPacket(uint playerActorId) + public SubPacket CreateNamePacket() { - return SetActorSpeedPacket.BuildPacket(actorId, playerActorId); + return SetActorNamePacket.BuildPacket(actorId, customDisplayName != null ? 0 : displayNameId, displayNameId == 0xFFFFFFFF | displayNameId == 0x0 | customDisplayName != null ? customDisplayName : ""); } - public SubPacket CreateSpawnPositonPacket(uint playerActorId, ushort spawnType) + public SubPacket CreateSpeedPacket() { + return SetActorSpeedPacket.BuildPacket(actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3]); + } + + public SubPacket CreateSpawnPositonPacket(ushort spawnType) + { + return CreateSpawnPositonPacket(null, spawnType); + } + + public SubPacket CreateSpawnPositonPacket(Player player, ushort spawnType) + { + //TODO: FIX THIS IF + uint playerActorId = player == null ? 0 : player.actorId; //Get Rid SubPacket spawnPacket; if (!spawnedFirstTime && playerActorId == actorId) - spawnPacket = SetActorPositionPacket.BuildPacket(actorId, playerActorId, 0, positionX, positionY, positionZ, rotation, 0x1, false); + spawnPacket = SetActorPositionPacket.BuildPacket(actorId, 0, positionX, positionY, positionZ, rotation, 0x1, false); else if (playerActorId == actorId) - spawnPacket = SetActorPositionPacket.BuildPacket(actorId, playerActorId, 0xFFFFFFFF, positionX, positionY, positionZ, rotation, spawnType, true); + spawnPacket = SetActorPositionPacket.BuildPacket(actorId, 0xFFFFFFFF, positionX, positionY, positionZ, rotation, spawnType, true); else { if (this is Player) - spawnPacket = SetActorPositionPacket.BuildPacket(actorId, playerActorId, 0, positionX, positionY, positionZ, rotation, spawnType, false); + spawnPacket = SetActorPositionPacket.BuildPacket(actorId, 0, positionX, positionY, positionZ, rotation, spawnType, false); else - spawnPacket = SetActorPositionPacket.BuildPacket(actorId, playerActorId, actorId, positionX, positionY, positionZ, rotation, spawnType, false); + spawnPacket = SetActorPositionPacket.BuildPacket(actorId, actorId, positionX, positionY, positionZ, rotation, spawnType, false); } - //return SetActorPositionPacket.BuildPacket(actorId, playerActorId, -211.895477f, 190.000000f, 29.651011f, 2.674819f, SetActorPositionPacket.SPAWNTYPE_PLAYERWAKE); + //return SetActorPositionPacket.BuildPacket(actorId, -211.895477f, 190.000000f, 29.651011f, 2.674819f, SetActorPositionPacket.SPAWNTYPE_PLAYERWAKE); spawnedFirstTime = true; return spawnPacket; } - public SubPacket CreateSpawnTeleportPacket(uint playerActorId, ushort spawnType) + public SubPacket CreateSpawnTeleportPacket(ushort spawnType) { SubPacket spawnPacket; - spawnPacket = SetActorPositionPacket.BuildPacket(actorId, playerActorId, 0xFFFFFFFF, positionX, positionY, positionZ, rotation, spawnType, false); + spawnPacket = SetActorPositionPacket.BuildPacket(actorId, 0xFFFFFFFF, positionX, positionY, positionZ, rotation, spawnType, false); - //return SetActorPositionPacket.BuildPacket(actorId, playerActorId, -211.895477f, 190.000000f, 29.651011f, 2.674819f, SetActorPositionPacket.SPAWNTYPE_PLAYERWAKE); + //return SetActorPositionPacket.BuildPacket(actorId, -211.895477f, 190.000000f, 29.651011f, 2.674819f, SetActorPositionPacket.SPAWNTYPE_PLAYERWAKE); //spawnPacket.DebugPrintSubPacket(); return spawnPacket; } - public SubPacket CreatePositionUpdatePacket(uint playerActorId) + public SubPacket CreatePositionUpdatePacket() { - return MoveActorToPositionPacket.BuildPacket(actorId, playerActorId, positionX, positionY, positionZ, rotation, moveState); + return MoveActorToPositionPacket.BuildPacket(actorId, positionX, positionY, positionZ, rotation, moveState); } - public SubPacket CreateStatePacket(uint playerActorID) + public SubPacket CreateStatePacket() { - return SetActorStatePacket.BuildPacket(actorId, playerActorID, currentMainState, currentSubState); + return SetActorStatePacket.BuildPacket(actorId, currentMainState, 0); } - public List GetEventConditionPackets(uint playerActorId) + public List GetEventConditionPackets() { List subpackets = new List(); @@ -145,126 +200,147 @@ namespace FFXIVClassic_Map_Server.Actors if (eventConditions.talkEventConditions != null) { foreach (EventList.TalkEventCondition condition in eventConditions.talkEventConditions) - subpackets.Add(SetTalkEventCondition.BuildPacket(playerActorId, actorId, condition)); + subpackets.Add(SetTalkEventCondition.BuildPacket(actorId, condition)); } if (eventConditions.noticeEventConditions != null) { foreach (EventList.NoticeEventCondition condition in eventConditions.noticeEventConditions) - subpackets.Add(SetNoticeEventCondition.BuildPacket(playerActorId, actorId, condition)); + subpackets.Add(SetNoticeEventCondition.BuildPacket(actorId, condition)); } if (eventConditions.emoteEventConditions != null) { foreach (EventList.EmoteEventCondition condition in eventConditions.emoteEventConditions) - subpackets.Add(SetEmoteEventCondition.BuildPacket(playerActorId, actorId, condition)); + subpackets.Add(SetEmoteEventCondition.BuildPacket(actorId, condition)); } if (eventConditions.pushWithCircleEventConditions != null) { foreach (EventList.PushCircleEventCondition condition in eventConditions.pushWithCircleEventConditions) - subpackets.Add(SetPushEventConditionWithCircle.BuildPacket(playerActorId, actorId, condition)); + subpackets.Add(SetPushEventConditionWithCircle.BuildPacket(actorId, condition)); } if (eventConditions.pushWithFanEventConditions != null) { foreach (EventList.PushFanEventCondition condition in eventConditions.pushWithFanEventConditions) - subpackets.Add(SetPushEventConditionWithFan.BuildPacket(playerActorId, actorId, condition)); + subpackets.Add(SetPushEventConditionWithFan.BuildPacket(actorId, condition)); } if (eventConditions.pushWithBoxEventConditions != null) { foreach (EventList.PushBoxEventCondition condition in eventConditions.pushWithBoxEventConditions) - subpackets.Add(SetPushEventConditionWithTriggerBox.BuildPacket(playerActorId, actorId, condition)); + subpackets.Add(SetPushEventConditionWithTriggerBox.BuildPacket(actorId, condition)); } return subpackets; } - public BasePacket GetSetEventStatusPackets(uint playerActorId) + public List GetSetEventStatusPackets() { List subpackets = new List(); //Return empty list if (eventConditions == null) - return BasePacket.CreatePacket(subpackets, true, false); + return subpackets; if (eventConditions.talkEventConditions != null) { foreach (EventList.TalkEventCondition condition in eventConditions.talkEventConditions) - subpackets.Add(SetEventStatus.BuildPacket(playerActorId, actorId, true, 1, condition.conditionName)); + subpackets.Add(SetEventStatus.BuildPacket(actorId, true, 1, condition.conditionName)); } if (eventConditions.noticeEventConditions != null) { foreach (EventList.NoticeEventCondition condition in eventConditions.noticeEventConditions) - subpackets.Add(SetEventStatus.BuildPacket(playerActorId, actorId, true, 1, condition.conditionName)); + subpackets.Add(SetEventStatus.BuildPacket(actorId, true, 1, condition.conditionName)); } if (eventConditions.emoteEventConditions != null) { foreach (EventList.EmoteEventCondition condition in eventConditions.emoteEventConditions) - subpackets.Add(SetEventStatus.BuildPacket(playerActorId, actorId, true, 3, condition.conditionName)); + subpackets.Add(SetEventStatus.BuildPacket(actorId, true, 3, condition.conditionName)); } if (eventConditions.pushWithCircleEventConditions != null) { foreach (EventList.PushCircleEventCondition condition in eventConditions.pushWithCircleEventConditions) - subpackets.Add(SetEventStatus.BuildPacket(playerActorId, actorId, true, 2, condition.conditionName)); + subpackets.Add(SetEventStatus.BuildPacket(actorId, true, 2, condition.conditionName)); } if (eventConditions.pushWithFanEventConditions != null) { foreach (EventList.PushFanEventCondition condition in eventConditions.pushWithFanEventConditions) - subpackets.Add(SetEventStatus.BuildPacket(playerActorId, actorId, true, 2, condition.conditionName)); + subpackets.Add(SetEventStatus.BuildPacket(actorId, true, 2, condition.conditionName)); } if (eventConditions.pushWithBoxEventConditions != null) { foreach (EventList.PushBoxEventCondition condition in eventConditions.pushWithBoxEventConditions) - subpackets.Add(SetEventStatus.BuildPacket(playerActorId, actorId, true, 2, condition.conditionName)); + subpackets.Add(SetEventStatus.BuildPacket(actorId, true, 2, condition.conditionName)); } - return BasePacket.CreatePacket(subpackets, true, false); + return subpackets; } - public SubPacket CreateIsZoneingPacket(uint playerActorId) + public SubPacket CreateIsZoneingPacket() { - return SetActorIsZoningPacket.BuildPacket(actorId, playerActorId, false); + return SetActorIsZoningPacket.BuildPacket(actorId, false); } - public virtual SubPacket CreateScriptBindPacket(uint playerActorId) + public virtual SubPacket CreateScriptBindPacket(Player player) { - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, classParams); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, classParams); } - public virtual BasePacket GetSpawnPackets(uint playerActorId) + public virtual SubPacket CreateScriptBindPacket() { - return GetSpawnPackets(playerActorId, 0x1); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, classParams); } - public virtual BasePacket GetSpawnPackets(uint playerActorId, ushort spawnType) + public virtual List GetSpawnPackets(Player player, ushort spawnType) { List subpackets = new List(); - subpackets.Add(CreateAddActorPacket(playerActorId, 8)); - subpackets.AddRange(GetEventConditionPackets(playerActorId)); - subpackets.Add(CreateSpeedPacket(playerActorId)); - subpackets.Add(CreateSpawnPositonPacket(playerActorId, spawnType)); - subpackets.Add(CreateNamePacket(playerActorId)); - subpackets.Add(CreateStatePacket(playerActorId)); - subpackets.Add(CreateIsZoneingPacket(playerActorId)); - subpackets.Add(CreateScriptBindPacket(playerActorId)); - return BasePacket.CreatePacket(subpackets, true, false); + subpackets.Add(CreateAddActorPacket(8)); + subpackets.AddRange(GetEventConditionPackets()); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(player, spawnType)); + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket(player)); + return subpackets; } - public virtual BasePacket GetInitPackets(uint playerActorId) + public virtual List GetSpawnPackets() { + return GetSpawnPackets(0x1); + } + + public virtual List GetSpawnPackets(ushort spawnType) + { + List subpackets = new List(); + subpackets.Add(CreateAddActorPacket(8)); + subpackets.AddRange(GetEventConditionPackets()); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(null, spawnType)); + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket()); + return subpackets; + } + + public virtual List GetInitPackets() + { + List packets = new List(); SetActorPropetyPacket initProperties = new SetActorPropetyPacket("/_init"); initProperties.AddByte(0xE14B0CA8, 1); initProperties.AddByte(0x2138FD71, 1); initProperties.AddByte(0xFBFBCFB1, 1); initProperties.AddTarget(); - return BasePacket.CreatePacket(initProperties.BuildPacket(playerActorId, actorId), true, false); + packets.Add(initProperties.BuildPacket(actorId)); + return packets; } public override bool Equals(Object obj) @@ -296,20 +372,40 @@ namespace FFXIVClassic_Map_Server.Actors return classParams; } + //character's newMainState kind of messes with this public void ChangeState(ushort newState) { - currentMainState = newState; - SubPacket ChangeStatePacket = SetActorStatePacket.BuildPacket(actorId, actorId, newState, currentSubState); - SubPacket battleActionPacket = BattleAction1Packet.BuildPacket(actorId, actorId); - zone.BroadcastPacketAroundActor(this, ChangeStatePacket); - zone.BroadcastPacketAroundActor(this, battleActionPacket); + if (newState != currentMainState) + { + currentMainState = newState; + + updateFlags |= (ActorUpdateFlags.State | ActorUpdateFlags.Position); + } + } + + public SubState GetSubState() + { + return currentSubState; + } + + public void SubstateModified() + { + updateFlags |= (ActorUpdateFlags.SubState); + } + + public void ModifySpeed(float mod) + { + for (int i = 0; i < 4; i++) + { + moveSpeeds[i] *= mod; + } + updateFlags |= ActorUpdateFlags.Speed; } public void ChangeSpeed(int type, float value) { moveSpeeds[type] = value; - SubPacket ChangeSpeedPacket = SetActorSpeedPacket.BuildPacket(actorId, actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3]); - zone.BroadcastPacketAroundActor(this, ChangeSpeedPacket); + updateFlags |= ActorUpdateFlags.Speed; } public void ChangeSpeed(float speedStop, float speedWalk, float speedRun, float speedActive) @@ -318,12 +414,68 @@ namespace FFXIVClassic_Map_Server.Actors moveSpeeds[1] = speedWalk; moveSpeeds[2] = speedRun; moveSpeeds[3] = speedActive; - SubPacket ChangeSpeedPacket = SetActorSpeedPacket.BuildPacket(actorId, actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3]); - zone.BroadcastPacketAroundActor(this, ChangeSpeedPacket); + updateFlags |= ActorUpdateFlags.Speed; } - public void Update(double deltaTime) - { + public virtual void Update(DateTime tick) + { + + } + + public virtual void PostUpdate(DateTime tick, List packets = null) + { + if (updateFlags != ActorUpdateFlags.None) + { + packets = packets ?? new List(); + if ((updateFlags & ActorUpdateFlags.Position) != 0) + { + if (positionUpdates != null && positionUpdates.Count > 0) + { + var pos = positionUpdates[0]; + if (pos != null) + { + oldPositionX = positionX; + oldPositionY = positionY; + oldPositionZ = positionZ; + oldRotation = rotation; + + positionX = pos.X; + positionY = pos.Y; + positionZ = pos.Z; + + zone.UpdateActorPosition(this); + + //Program.Server.GetInstance().mLuaEngine.OnPath(actor, position, positionUpdates) + } + positionUpdates.Remove(pos); + + } + 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, 0x3B)); + } + + if ((updateFlags & ActorUpdateFlags.SubState) != 0) + { + packets.Add(SetActorSubStatePacket.BuildPacket(actorId, currentSubState)); + } + + updateFlags = ActorUpdateFlags.None; + } + zone.BroadcastPacketsAroundActor(this, packets); } public void GenerateActorName(int actorNumber) @@ -358,7 +510,7 @@ namespace FFXIVClassic_Map_Server.Actors { className = className.Substring(0, 20 - zoneName.Length); } - catch (ArgumentOutOfRangeException e) + catch (ArgumentOutOfRangeException) { } //Convert actor number to base 63 @@ -426,8 +578,8 @@ namespace FFXIVClassic_Map_Server.Actors SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc); changeProperty.AddProperty(this, name); changeProperty.AddTarget(); - SubPacket subpacket = changeProperty.BuildPacket(player.actorId, player.actorId); - player.playerSession.QueuePacket(subpacket, true, false); + SubPacket subpacket = changeProperty.BuildPacket(player.actorId); + player.playerSession.QueuePacket(subpacket); subpacket.DebugPrintSubPacket(); return true; } @@ -439,21 +591,22 @@ namespace FFXIVClassic_Map_Server.Actors if (value.GetType() == curObj.GetType()) parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, value); 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); changeProperty.AddProperty(this, name); changeProperty.AddTarget(); - SubPacket subpacket = changeProperty.BuildPacket(player.actorId, player.actorId); - player.playerSession.QueuePacket(subpacket, true, false); + SubPacket subpacket = changeProperty.BuildPacket(player.actorId); + player.playerSession.QueuePacket(subpacket); subpacket.DebugPrintSubPacket(); return true; } } return false; } - } + } + #region positioning public List GetPos() { List pos = new List(); @@ -467,6 +620,11 @@ namespace FFXIVClassic_Map_Server.Actors return pos; } + public Vector3 GetPosAsVector3() + { + return new Vector3(positionX, positionY, positionZ); + } + public void SetPos(float x, float y, float z, float rot = 0, uint zoneId = 0) { oldPositionX = positionX; @@ -480,7 +638,7 @@ namespace FFXIVClassic_Map_Server.Actors rotation = rot; // todo: handle zone? - zone.BroadcastPacketAroundActor(this, MoveActorToPositionPacket.BuildPacket(this.actorId, this.actorId, x, y, z, rot, moveState)); + zone.BroadcastPacketAroundActor(this, MoveActorToPositionPacket.BuildPacket(actorId, x, y, z, rot, moveState)); } public Area GetZone() @@ -492,6 +650,107 @@ namespace FFXIVClassic_Map_Server.Actors { return zoneId; } + + public void LookAt(Actor actor) + { + if (actor != null) + { + LookAt(actor.positionX, actor.positionZ); + } + else + { + Program.Log.Error("[{0}][{1}] Actor.LookAt() unable to find actor!", actorId, actorName); + } + } + + public void LookAt(Vector3 pos) + { + if (pos != null) + { + LookAt(pos.X, pos.Z); + } + } + + public void LookAt(float x, float z) + { + //Don't rotate if the lookat position is same as our current position + if (positionX != x || positionZ != z) + { + var rot1 = this.rotation; + + var dX = this.positionX - x; + var dY = this.positionZ - z; + var rot2 = Math.Atan2(dY, dX); + var dRot = Math.PI - rot2 + Math.PI / 2; + + // pending move, dont need to unset it + this.updateFlags |= ActorUpdateFlags.Position; + rotation = (float)dRot; + } + } + + // todo: is this legit? + public bool IsFacing(float x, float z, float angle = 90.0f) + { + angle = (float)(Math.PI * angle / 180); + var a = Vector3.GetAngle(positionX, positionZ, x, z); + return new Vector3(x, 0, z).IsWithinCone(GetPosAsVector3(), rotation, angle); + } + + 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.positionZ, angle); + } + + public void QueuePositionUpdate(Vector3 pos) + { + if (positionUpdates == null) + positionUpdates = new List(); + + positionUpdates.Add(pos); + this.updateFlags |= ActorUpdateFlags.Position; + } + + public void QueuePositionUpdate(float x, float y, float z) + { + QueuePositionUpdate(new Vector3(x, y, z)); + } + + public void ClearPositionUpdates() + { + positionUpdates.Clear(); + } + + public Vector3 FindRandomPoint(float x, float y, float z, float minRadius, float maxRadius) + { + var angle = Program.Random.NextDouble() * Math.PI * 2; + var radius = Math.Sqrt(Program.Random.NextDouble() * (maxRadius - minRadius)) + minRadius; + + return new Vector3(x + (float)(radius * Math.Cos(angle)), y, z + (float)(radius * Math.Sin(angle))); + } + + public Vector3 FindRandomPointAroundTarget(Actor target, float minRadius, float maxRadius) + { + if (target == null) + { + Program.Log.Error(String.Format("[{0} {1}] FindRandomPointAroundTarget: no target found!", this.actorId, this.customDisplayName)); + 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); + } + #endregion + } } diff --git a/FFXIVClassic Map Server/actors/StaticActors.cs b/FFXIVClassic Map Server/actors/StaticActors.cs index 998cdb06..153b3092 100644 --- a/FFXIVClassic Map Server/actors/StaticActors.cs +++ b/FFXIVClassic Map Server/actors/StaticActors.cs @@ -41,7 +41,7 @@ namespace FFXIVClassic_Map_Server.Actors byte byteOut = (Byte)(byteIn ^ 0x73); binWriter.Write((Byte)byteOut); } - catch (EndOfStreamException e) { break; } + catch (EndOfStreamException) { break; } } binReader.Close(); @@ -88,7 +88,7 @@ namespace FFXIVClassic_Map_Server.Actors } } } - catch(FileNotFoundException e) + catch(FileNotFoundException) { Program.Log.Error("Could not find staticactors file."); return false; } Program.Log.Info("Loaded {0} static actors.", mStaticActors.Count()); diff --git a/FFXIVClassic Map Server/actors/area/Area.cs b/FFXIVClassic Map Server/actors/area/Area.cs index df792e0b..1c16b1f5 100644 --- a/FFXIVClassic Map Server/actors/area/Area.cs +++ b/FFXIVClassic Map Server/actors/area/Area.cs @@ -1,20 +1,13 @@ using FFXIVClassic_Map_Server; using FFXIVClassic.Common; - using FFXIVClassic_Map_Server.actors.area; using FFXIVClassic_Map_Server.actors.chara.npc; -using FFXIVClassic_Map_Server.dataobjects; -using FFXIVClassic_Map_Server.dataobjects.chara; using FFXIVClassic_Map_Server.lua; using FFXIVClassic_Map_Server.packets.send.actor; -using MoonSharp.Interpreter; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using FFXIVClassic_Map_Server.packets.send; -using FFXIVClassic_Map_Server.actors.group; using FFXIVClassic_Map_Server.actors.director; namespace FFXIVClassic_Map_Server.Actors @@ -30,7 +23,7 @@ namespace FFXIVClassic_Map_Server.Actors protected string classPath; public int boundingGridSize = 50; - public int minX = -1000, minY = -1000, maxX = 1000, maxY = 1000; + public int minX = -5000, minY = -5000, maxX = 5000, maxY = 5000; protected int numXBlocks, numYBlocks; protected int halfWidth, halfHeight; @@ -84,79 +77,87 @@ namespace FFXIVClassic_Map_Server.Actors } } - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket() { List lParams; lParams = LuaUtils.CreateLuaParamList(classPath, false, true, zoneName, "/Area/Zone/ZoneDefault", -1, (byte)1, true, false, false, false, false, false, false, false); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, "ZoneDefault", lParams); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, "ZoneDefault", lParams); } - public override BasePacket GetSpawnPackets(uint playerActorId) + public override List GetSpawnPackets() { List subpackets = new List(); - subpackets.Add(CreateAddActorPacket(playerActorId, 0)); - subpackets.Add(CreateSpeedPacket(playerActorId)); - subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x1)); - subpackets.Add(CreateNamePacket(playerActorId)); - subpackets.Add(CreateStatePacket(playerActorId)); - subpackets.Add(CreateIsZoneingPacket(playerActorId)); - subpackets.Add(CreateScriptBindPacket(playerActorId)); - return BasePacket.CreatePacket(subpackets, true, false); + subpackets.Add(CreateAddActorPacket(0)); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(0x1)); + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket()); + return subpackets; } + // todo: handle instance areas in derived class? (see virtuals) #region Actor Management public void AddActorToZone(Actor actor) { lock (mActorList) { + if (actor is Character) + ((Character)actor).ResetTempVars(); + if (!mActorList.ContainsKey(actor.actorId)) mActorList.Add(actor.actorId, actor); + + + int gridX = (int)actor.positionX / boundingGridSize; + int gridY = (int)actor.positionZ / boundingGridSize; + + gridX += halfWidth; + gridY += halfHeight; + + //Boundries + if (gridX < 0) + gridX = 0; + if (gridX >= numXBlocks) + gridX = numXBlocks - 1; + if (gridY < 0) + gridY = 0; + if (gridY >= numYBlocks) + gridY = numYBlocks - 1; + + lock (mActorBlock) + mActorBlock[gridX, gridY].Add(actor); } - - int gridX = (int)actor.positionX / boundingGridSize; - int gridY = (int)actor.positionZ / boundingGridSize; - - gridX += halfWidth; - gridY += halfHeight; - - //Boundries - if (gridX < 0) - gridX = 0; - if (gridX >= numXBlocks) - gridX = numXBlocks - 1; - if (gridY < 0) - gridY = 0; - if (gridY >= numYBlocks) - gridY = numYBlocks - 1; - - lock (mActorBlock) - mActorBlock[gridX, gridY].Add(actor); } public void RemoveActorFromZone(Actor actor) { - lock (mActorList) - mActorList.Remove(actor.actorId); + if (actor != null) + lock (mActorList) + { + mActorList.Remove(actor.actorId); - int gridX = (int)actor.positionX / boundingGridSize; - int gridY = (int)actor.positionZ / boundingGridSize; + int gridX = (int)actor.positionX / boundingGridSize; + int gridY = (int)actor.positionZ / boundingGridSize; - gridX += halfWidth; - gridY += halfHeight; + gridX += halfWidth; + gridY += halfHeight; - //Boundries - if (gridX < 0) - gridX = 0; - if (gridX >= numXBlocks) - gridX = numXBlocks - 1; - if (gridY < 0) - gridY = 0; - if (gridY >= numYBlocks) - gridY = numYBlocks - 1; + //Boundries + if (gridX < 0) + gridX = 0; + if (gridX >= numXBlocks) + gridX = numXBlocks - 1; + if (gridY < 0) + gridY = 0; + if (gridY >= numYBlocks) + gridY = numYBlocks - 1; - lock (mActorBlock) - mActorBlock[gridX, gridY].Remove(actor); + lock (mActorBlock) + mActorBlock[gridX, gridY].Remove(actor); + } } public void UpdateActorPosition(Actor actor) @@ -204,12 +205,12 @@ namespace FFXIVClassic_Map_Server.Actors } } - public List GetActorsAroundPoint(float x, float y, int checkDistance) + public virtual List GetActorsAroundPoint(float x, float y, int checkDistance) where T : Actor { checkDistance /= boundingGridSize; - int gridX = (int)x/boundingGridSize; - int gridY = (int)y/boundingGridSize; + int gridX = (int)x / boundingGridSize; + int gridY = (int)y / boundingGridSize; gridX += halfWidth; gridY += halfHeight; @@ -224,7 +225,7 @@ namespace FFXIVClassic_Map_Server.Actors if (gridY >= numYBlocks) gridY = numYBlocks - 1; - List result = new List(); + List result = new List(); lock (mActorBlock) { @@ -232,7 +233,7 @@ namespace FFXIVClassic_Map_Server.Actors { for (int gy = gridY - checkDistance; gy <= gridY + checkDistance; gy++) { - result.AddRange(mActorBlock[gx, gy]); + result.AddRange(mActorBlock[gx, gy].OfType()); } } } @@ -246,11 +247,20 @@ namespace FFXIVClassic_Map_Server.Actors result.RemoveAt(i); } } - return result; } - public List GetActorsAroundActor(Actor actor, int checkDistance) + public virtual List GetActorsAroundPoint(float x, float y, int checkDistance) + { + return GetActorsAroundPoint(x, y, checkDistance); + } + + public virtual List GetActorsAroundActor(Actor actor, int checkDistance) + { + return GetActorsAroundActor(actor, checkDistance); + } + + public virtual List GetActorsAroundActor(Actor actor, int checkDistance) where T : Actor { checkDistance /= boundingGridSize; @@ -270,7 +280,7 @@ namespace FFXIVClassic_Map_Server.Actors if (gridY >= numYBlocks) gridY = numYBlocks - 1; - List result = new List(); + var result = new List(); lock (mActorBlock) { @@ -278,10 +288,11 @@ namespace FFXIVClassic_Map_Server.Actors { for (int gx = ((gridX - checkDistance) < 0 ? 0 : (gridX - checkDistance)); gx <= ((gridX + checkDistance) >= numXBlocks ? numXBlocks - 1 : (gridX + checkDistance)); gx++) { - result.AddRange(mActorBlock[gx, gy]); + result.AddRange(mActorBlock[gx, gy].OfType()); } } } + //Remove players if isolation zone if (isIsolated) { @@ -307,6 +318,11 @@ namespace FFXIVClassic_Map_Server.Actors } } + public T FindActorInArea(uint id) where T : Actor + { + return FindActorInArea(id) as T; + } + public Actor FindActorInZoneByUniqueID(string uniqueId) { lock (mActorList) @@ -327,13 +343,10 @@ namespace FFXIVClassic_Map_Server.Actors { lock (mActorList) { - foreach (Actor a in mActorList.Values) + foreach (Player player in mActorList.Values.OfType()) { - if (a is Player) - { - if (((Player)a).customDisplayName.ToLower().Equals(name.ToLower())) - return (Player)a; - } + if (player.customDisplayName.ToLower().Equals(name.ToLower())) + return player; } return null; } @@ -368,6 +381,51 @@ namespace FFXIVClassic_Map_Server.Actors } } + // todo: for zones override this to search contentareas (assuming flag is passed) + public virtual List GetAllActors() where T : Actor + { + lock (mActorList) + { + List actorList = new List(mActorList.Count); + actorList.AddRange(mActorList.Values.OfType()); + return actorList; + } + } + + public int GetActorCount() + { + lock (mActorList) + { + return mActorList.Count; + } + } + + public virtual List GetAllActors() + { + return GetAllActors(); + } + + public virtual List GetPlayers() + { + return GetAllActors(); + } + + public virtual List GetMonsters() + { + return GetAllActors(); + } + + public virtual List GetAllies() + { + return GetAllActors(); + } + + public void BroadcastPacketsAroundActor(Actor actor, List packets) + { + foreach (SubPacket packet in packets) + BroadcastPacketAroundActor(actor, packet); + } + public void BroadcastPacketAroundActor(Actor actor, SubPacket packet) { if (isIsolated) @@ -378,7 +436,7 @@ namespace FFXIVClassic_Map_Server.Actors { if (a is Player) { - if (isIsolated && packet.header.sourceId != a.actorId) + if (isIsolated) continue; SubPacket clonedPacket = new SubPacket(packet, a.actorId); @@ -390,70 +448,95 @@ namespace FFXIVClassic_Map_Server.Actors public void SpawnActor(SpawnLocation location) { - ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId); - - if (actorClass == null) - return; + lock (mActorList) + { + ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId); - uint zoneId; + if (actorClass == null) + return; - if (this is PrivateArea) - zoneId = ((PrivateArea)this).GetParentZone().actorId; - else - zoneId = actorId; + uint zoneId; - Npc npc = new Npc(mActorList.Count + 1, actorClass, location.uniqueId, this, location.x, location.y, location.z, location.rot, location.state, location.animId, null); + if (this is PrivateArea) + zoneId = ((PrivateArea)this).GetParentZone().actorId; + else + zoneId = actorId; + + Npc npc = new Npc(mActorList.Count + 1, actorClass, location.uniqueId, this, location.x, location.y, location.z, location.rot, location.state, location.animId, null); - npc.LoadEventConditions(actorClass.eventConditions); + npc.LoadEventConditions(actorClass.eventConditions); - AddActorToZone(npc); + AddActorToZone(npc); + } } - public Npc SpawnActor(uint classId, string uniqueId, float x, float y, float z, float rot = 0, ushort state = 0, uint animId = 0) + public Npc SpawnActor(uint classId, string uniqueId, float x, float y, float z, float rot = 0, ushort state = 0, uint animId = 0, bool isMob = false) { - ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId); + lock (mActorList) + { + ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId); - if (actorClass == null) - return null; + if (actorClass == null) + return null; - uint zoneId; + uint zoneId; + if (this is PrivateArea) + zoneId = ((PrivateArea)this).GetParentZone().actorId; + else + zoneId = actorId; - if (this is PrivateArea) - zoneId = ((PrivateArea)this).GetParentZone().actorId; - else - zoneId = actorId; + Npc npc; + if (isMob) + npc = new BattleNpc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null); + else + npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null); - Npc npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null); + npc.LoadEventConditions(actorClass.eventConditions); + npc.SetMaxHP(100); + npc.SetHP(100); + npc.ResetMoveSpeeds(); - npc.LoadEventConditions(actorClass.eventConditions); + AddActorToZone(npc); - AddActorToZone(npc); - - return npc; + return npc; + } } public Npc SpawnActor(uint classId, string uniqueId, float x, float y, float z, uint regionId, uint layoutId) { - ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId); + lock (mActorList) + { + ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId); - if (actorClass == null) - return null; + if (actorClass == null) + return null; - uint zoneId; + uint zoneId; - if (this is PrivateArea) - zoneId = ((PrivateArea)this).GetParentZone().actorId; - else - zoneId = actorId; + if (this is PrivateArea) + zoneId = ((PrivateArea)this).GetParentZone().actorId; + else + zoneId = actorId; - Npc npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, 0, regionId, layoutId); + Npc npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, 0, regionId, layoutId); - npc.LoadEventConditions(actorClass.eventConditions); + npc.LoadEventConditions(actorClass.eventConditions); - AddActorToZone(npc); + AddActorToZone(npc); - return npc; + return npc; + } + } + + public BattleNpc GetBattleNpcById(uint id) + { + foreach (var bnpc in GetAllActors()) + { + if (bnpc.GetBattleNpcId() == id) + return bnpc; + } + return null; } public void DespawnActor(string uniqueId) @@ -461,6 +544,11 @@ namespace FFXIVClassic_Map_Server.Actors RemoveActorFromZone(FindActorInZoneByUniqueID(uniqueId)); } + public void DespawnActor(Actor actor) + { + RemoveActorFromZone(actor); + } + public Director GetWeatherDirector() { return mWeatherDirector; @@ -472,7 +560,7 @@ namespace FFXIVClassic_Map_Server.Actors if (player != null && !zoneWide) { - player.QueuePacket(BasePacket.CreatePacket(SetWeatherPacket.BuildPacket(player.actorId, weather, transitionTime), true, false)); + player.QueuePacket(SetWeatherPacket.BuildPacket(player.actorId, weather, transitionTime)); } if (zoneWide) { @@ -483,23 +571,67 @@ namespace FFXIVClassic_Map_Server.Actors if (actor.Value is Player) { player = ((Player)actor.Value); - player.QueuePacket(BasePacket.CreatePacket(SetWeatherPacket.BuildPacket(player.actorId, weather, transitionTime), true, false)); + player.QueuePacket(SetWeatherPacket.BuildPacket(player.actorId, weather, transitionTime)); } } } } } - public Director CreateDirector(string path) + public Director CreateDirector(string path, bool hasContentGroup, params object[] args) { lock (directorLock) { - Director director = new Director(directorIdCount, this, path); + Director director = new Director(directorIdCount, this, path, hasContentGroup, args); + currentDirectors.Add(director.actorId, director); + directorIdCount++; + return director; + } + } - if (!director.IsCreated()) - return null; + public Director CreateGuildleveDirector(uint glid, byte difficulty, Player owner, params object[] args) + { + String directorScriptPath = ""; - currentDirectors.Add(directorIdCount, director); + uint type = Server.GetGuildleveGamedata(glid).plateId; + + if (glid == 10801 || glid == 12401 || glid == 11601) + directorScriptPath = "Guildleve/PrivateGLBattleTutorial"; + else + { + switch (type) + { + case 20021: + directorScriptPath = "Guildleve/PrivateGLBattleSweepNormal"; + break; + case 20022: + directorScriptPath = "Guildleve/PrivateGLBattleChaseNormal"; + break; + case 20023: + directorScriptPath = "Guildleve/PrivateGLBattleOrbNormal"; + break; + case 20024: + directorScriptPath = "Guildleve/PrivateGLBattleHuntNormal"; + break; + case 20025: + directorScriptPath = "Guildleve/PrivateGLBattleGatherNormal"; + break; + case 20026: + directorScriptPath = "Guildleve/PrivateGLBattleRoundNormal"; + break; + case 20027: + directorScriptPath = "Guildleve/PrivateGLBattleSurviveNormal"; + break; + case 20028: + directorScriptPath = "Guildleve/PrivateGLBattleDetectNormal"; + break; + } + } + + lock (directorLock) + { + GuildleveDirector director = new GuildleveDirector(directorIdCount, this, directorScriptPath, glid, difficulty, owner, args); + currentDirectors.Add(director.actorId, director); directorIdCount++; return director; } @@ -511,7 +643,8 @@ namespace FFXIVClassic_Map_Server.Actors { if (currentDirectors.ContainsKey(id)) { - currentDirectors[id].RemoveChildren(); + if (!currentDirectors[id].IsDeleted()) + currentDirectors[id].EndDirector(); currentDirectors.Remove(id); } } @@ -524,12 +657,18 @@ namespace FFXIVClassic_Map_Server.Actors return null; } - public void Update(double deltaTime) + public override void Update(DateTime tick) { lock (mActorList) { - foreach (Actor a in mActorList.Values) - a.Update(deltaTime); + foreach (Actor a in mActorList.Values.ToList()) + a.Update(tick); + + if ((tick - lastUpdateScript).TotalMilliseconds > 1500) + { + //LuaEngine.GetInstance().CallLuaFunctionForReturn(LuaEngine.GetScriptPath(this), "onUpdate", true, this, tick); + lastUpdateScript = tick; + } } } diff --git a/FFXIVClassic Map Server/actors/area/PrivateArea.cs b/FFXIVClassic Map Server/actors/area/PrivateArea.cs index d5f653bc..1fe98393 100644 --- a/FFXIVClassic Map Server/actors/area/PrivateArea.cs +++ b/FFXIVClassic Map Server/actors/area/PrivateArea.cs @@ -41,7 +41,7 @@ namespace FFXIVClassic_Map_Server.actors.area return parentZone; } - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket() { List lParams; @@ -50,8 +50,8 @@ namespace FFXIVClassic_Map_Server.actors.area string realClassName = className.Substring(className.LastIndexOf("/") + 1); lParams = LuaUtils.CreateLuaParamList(classPath, false, true, zoneName, privateAreaName, privateAreaType, canRideChocobo ? (byte)1 : (byte)0, canStealth, isInn, false, false, false, false, false, false); - ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, realClassName, lParams).DebugPrintSubPacket(); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, realClassName, lParams); + ActorInstantiatePacket.BuildPacket(actorId, actorName, realClassName, lParams).DebugPrintSubPacket(); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, realClassName, lParams); } diff --git a/FFXIVClassic Map Server/actors/area/PrivateAreaContent.cs b/FFXIVClassic Map Server/actors/area/PrivateAreaContent.cs index e82727e7..d97b7efc 100644 --- a/FFXIVClassic Map Server/actors/area/PrivateAreaContent.cs +++ b/FFXIVClassic Map Server/actors/area/PrivateAreaContent.cs @@ -14,7 +14,6 @@ namespace FFXIVClassic_Map_Server.actors.area class PrivateAreaContent : PrivateArea { private Director currentDirector; - private ContentGroup currentContentGroup; private bool isContentFinished = false; public static PrivateAreaContent CreateContentArea(String scriptPath) @@ -26,8 +25,7 @@ namespace FFXIVClassic_Map_Server.actors.area : base(parent, parent.actorId, classPath, privateAreaName, privateAreaType, 0, 0, 0) { currentDirector = director; - currentContentGroup = Server.GetWorldManager().CreateContentGroup(director); - LuaEngine.GetInstance().CallLuaFunction(contentStarter, this, "onCreate", false, currentContentGroup, currentDirector); + LuaEngine.GetInstance().CallLuaFunction(contentStarter, this, "onCreate", false, currentDirector); } public Director GetContentDirector() @@ -35,11 +33,6 @@ namespace FFXIVClassic_Map_Server.actors.area return currentDirector; } - public ContentGroup GetContentGroup() - { - return currentContentGroup; - } - public void ContentFinished() { isContentFinished = true; @@ -47,17 +40,20 @@ namespace FFXIVClassic_Map_Server.actors.area public void CheckDestroy() { - if (isContentFinished) + lock (mActorList) { - bool noPlayersLeft = true; - foreach (Actor a in mActorList.Values) + if (isContentFinished) { - if (a is Player) - noPlayersLeft = false; + bool noPlayersLeft = true; + foreach (Actor a in mActorList.Values) + { + if (a is Player) + noPlayersLeft = false; + } + if (noPlayersLeft) + GetParentZone().DeleteContentArea(this); } - if (noPlayersLeft) - GetParentZone().DeleteContentArea(this); - } + } } } diff --git a/FFXIVClassic Map Server/actors/area/Zone.cs b/FFXIVClassic Map Server/actors/area/Zone.cs index 0f26abfb..d198fff5 100644 --- a/FFXIVClassic Map Server/actors/area/Zone.cs +++ b/FFXIVClassic Map Server/actors/area/Zone.cs @@ -10,6 +10,8 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.IO; + using FFXIVClassic_Map_Server.actors.director; namespace FFXIVClassic_Map_Server.actors.area @@ -20,10 +22,31 @@ namespace FFXIVClassic_Map_Server.actors.area Dictionary> contentAreas = new Dictionary>(); Object contentAreasLock = new Object(); - public Zone(uint id, string zoneName, ushort regionId, string classPath, ushort bgmDay, ushort bgmNight, ushort bgmBattle, bool isIsolated, bool isInn, bool canRideChocobo, bool canStealth, bool isInstanceRaid) + public SharpNav.TiledNavMesh tiledNavMesh; + public SharpNav.NavMeshQuery navMeshQuery; + + public Int64 pathCalls; + public Int64 prevPathCalls = 0; + public Int64 pathCallTime; + + public Zone(uint id, string zoneName, ushort regionId, string classPath, ushort bgmDay, ushort bgmNight, ushort bgmBattle, bool isIsolated, bool isInn, bool canRideChocobo, bool canStealth, bool isInstanceRaid, bool loadNavMesh = false) : base(id, zoneName, regionId, classPath, bgmDay, bgmNight, bgmBattle, isIsolated, isInn, canRideChocobo, canStealth, isInstanceRaid) { + if (loadNavMesh) + { + try + { + tiledNavMesh = utils.NavmeshUtils.LoadNavmesh(tiledNavMesh, zoneName + ".snb"); + navMeshQuery = new SharpNav.NavMeshQuery(tiledNavMesh, 100); + if (tiledNavMesh != null && tiledNavMesh.Tiles[0].PolyCount > 0) + Program.Log.Info($"Loaded navmesh for {zoneName}"); + } + catch (Exception e) + { + Program.Log.Error(e.Message); + } + } } public void AddPrivateArea(PrivateArea pa) @@ -51,13 +74,13 @@ namespace FFXIVClassic_Map_Server.actors.area return null; } - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket() { bool isEntranceDesion = false; List lParams; lParams = LuaUtils.CreateLuaParamList(classPath, false, true, zoneName, "", -1, canRideChocobo ? (byte)1 : (byte)0, canStealth, isInn, false, false, false, true, isInstanceRaid, isEntranceDesion); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, lParams); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams); } public void AddSpawnLocation(SpawnLocation spawn) @@ -97,28 +120,43 @@ namespace FFXIVClassic_Map_Server.actors.area public Actor FindActorInZone(uint id) { - if (!mActorList.ContainsKey(id)) + lock (mActorList) { - foreach(Dictionary paList in privateAreas.Values) + if (!mActorList.ContainsKey(id)) { - foreach(PrivateArea pa in paList.Values) + foreach (Dictionary paList in privateAreas.Values) { - Actor actor = pa.FindActorInArea(id); - if (actor != null) - return actor; + foreach (PrivateArea pa in paList.Values) + { + Actor actor = pa.FindActorInArea(id); + if (actor != null) + return actor; + } } + + foreach (List paList in contentAreas.Values) + { + foreach (PrivateArea pa in paList) + { + Actor actor = pa.FindActorInArea(id); + if (actor != null) + return actor; + } + } + + + return null; } - return null; + else + return mActorList[id]; } - else - return mActorList[id]; } - public PrivateAreaContent CreateContentArea(Player starterPlayer, string areaClassPath, string contentScript, string areaName, string directorName) + public PrivateAreaContent CreateContentArea(Player starterPlayer, string areaClassPath, string contentScript, string areaName, string directorName, params object[] args) { lock (contentAreasLock) { - Director director = CreateDirector(directorName); + Director director = CreateDirector(directorName, true, args); if (director == null) return null; @@ -127,6 +165,7 @@ namespace FFXIVClassic_Map_Server.actors.area contentAreas.Add(areaName, new List()); PrivateAreaContent contentArea = new PrivateAreaContent(this, classPath, areaName, 1, director, starterPlayer); contentAreas[areaName].Add(contentArea); + return contentArea; } } @@ -139,5 +178,30 @@ namespace FFXIVClassic_Map_Server.actors.area } } + public override void Update(DateTime tick) + { + base.Update(tick); + + foreach (var a in privateAreas.Values) + foreach(var b in a.Values) + b.Update(tick); + + foreach (var a in contentAreas.Values) + foreach (var b in a) + b.Update(tick); + + // todo: again, this is retarded but debug stuff + var diffTime = tick - lastUpdate; + + if (diffTime.TotalSeconds >= 10) + { + if (this.pathCalls > 0) + { + Program.Log.Debug("Number of pathfinding calls {0} average time {1}ms. {2} this tick", pathCalls, (float)(pathCallTime / pathCalls), pathCalls - prevPathCalls); + prevPathCalls = pathCalls; + } + lastUpdate = tick; + } + } } } diff --git a/FFXIVClassic Map Server/actors/chara/BattleSave.cs b/FFXIVClassic Map Server/actors/chara/BattleSave.cs index bf9a6000..8d2928e4 100644 --- a/FFXIVClassic Map Server/actors/chara/BattleSave.cs +++ b/FFXIVClassic Map Server/actors/chara/BattleSave.cs @@ -5,7 +5,7 @@ public float potencial = 6.6f; public short[] skillLevel = new short[52]; public short[] skillLevelCap = new short[52]; - public short[] skillPoint = new short[52]; + public int[] skillPoint = new int[52]; public short physicalLevel; public int physicalExp; diff --git a/FFXIVClassic Map Server/actors/chara/BattleTemp.cs b/FFXIVClassic Map Server/actors/chara/BattleTemp.cs index 0d1c861f..9f46b35f 100644 --- a/FFXIVClassic Map Server/actors/chara/BattleTemp.cs +++ b/FFXIVClassic Map Server/actors/chara/BattleTemp.cs @@ -2,10 +2,11 @@ { class BattleTemp { + //Are these right? public const uint NAMEPLATE_SHOWN = 0; public const uint TARGETABLE = 1; - //public const uint NAMEPLATE_SHOWN2 = 2; - public const uint NAMEPLATE_SHOWN2 = 3; + public const uint NAMEPLATE_SHOWN2 = 2; + //public const uint NAMEPLATE_SHOWN2 = 3; public const uint STAT_STRENGTH = 3; public const uint STAT_VITALITY = 4; @@ -25,13 +26,13 @@ public const uint STAT_ACCURACY = 15; public const uint STAT_NORMALDEFENSE = 18; public const uint STAT_EVASION = 16; - public const uint STAT_ATTACK_MAGIC = 24; - public const uint STAT_HEAL_MAGIC = 25; - public const uint STAT_ENCHANCEMENT_MAGIC_POTENCY = 26; - public const uint STAT_ENFEEBLING_MAGIC_POTENCY = 27; - - public const uint STAT_MAGIC_ACCURACY = 28; - public const uint STAT_MAGIC_EVASION = 29; + public const uint STAT_ATTACK_MAGIC = 23; + public const uint STAT_HEAL_MAGIC = 24; + public const uint STAT_ENCHANCEMENT_MAGIC_POTENCY = 25; + public const uint STAT_ENFEEBLING_MAGIC_POTENCY = 26; + + public const uint STAT_MAGIC_ACCURACY = 27; + public const uint STAT_MAGIC_EVASION = 28; public const uint STAT_CRAFT_PROCESSING = 30; public const uint STAT_CRAFT_MAGIC_PROCESSING = 31; @@ -43,6 +44,6 @@ public float[] castGauge_speed = { 1.0f, 0.25f}; public bool[] timingCommandFlag = new bool[4]; - public ushort[] generalParameter = new ushort[35]; + public short[] generalParameter = new short[35]; } } diff --git a/FFXIVClassic Map Server/actors/chara/CharaWork.cs b/FFXIVClassic Map Server/actors/chara/CharaWork.cs index 652178d6..45afc964 100644 --- a/FFXIVClassic Map Server/actors/chara/CharaWork.cs +++ b/FFXIVClassic Map Server/actors/chara/CharaWork.cs @@ -23,7 +23,7 @@ public uint[] command = new uint[64]; //ACTORS public byte[] commandCategory = new byte[64]; public byte commandBorder = 0x20; - public bool[] commandAcquired = new bool[4096]; + public bool[] commandAcquired = new bool[4096]; public bool[] additionalCommandAcquired = new bool[36]; public uint currentContentGroup; diff --git a/FFXIVClassic Map Server/actors/chara/Character.cs b/FFXIVClassic Map Server/actors/chara/Character.cs index 849f7b81..697be351 100644 --- a/FFXIVClassic Map Server/actors/chara/Character.cs +++ b/FFXIVClassic Map Server/actors/chara/Character.cs @@ -1,14 +1,63 @@  using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.area; using FFXIVClassic_Map_Server.actors.group; using FFXIVClassic_Map_Server.Actors.Chara; using FFXIVClassic_Map_Server.packets.send.actor; using FFXIVClassic_Map_Server.utils; +using FFXIVClassic_Map_Server.actors.chara.ai; +using System; +using System.Collections.Generic; +using FFXIVClassic_Map_Server.actors.chara; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.packets.send; +using FFXIVClassic_Map_Server.actors.chara.ai.state; +using FFXIVClassic_Map_Server.actors.chara.ai.utils; +using FFXIVClassic_Map_Server.actors.chara.npc; namespace FFXIVClassic_Map_Server.Actors { - class Character:Actor + /// Which Character types am I friendly with + enum CharacterTargetingAllegiance { + /// Friendly to BattleNpcs + BattleNpcs, + /// Friendly to Players + Player + } + + enum DamageTakenType + { + None, + Attack, + Magic, + Weaponskill, + Ability + } + + class Character : Actor + { + public const int CLASSID_PUG = 2; + public const int CLASSID_GLA = 3; + public const int CLASSID_MRD = 4; + public const int CLASSID_ARC = 7; + public const int CLASSID_LNC = 8; + public const int CLASSID_THM = 22; + public const int CLASSID_CNJ = 23; + + public const int CLASSID_CRP = 29; + public const int CLASSID_BSM = 30; + public const int CLASSID_ARM = 31; + public const int CLASSID_GSM = 32; + public const int CLASSID_LTW = 33; + public const int CLASSID_WVR = 34; + public const int CLASSID_ALC = 35; + public const int CLASSID_CUL = 36; + + public const int CLASSID_MIN = 39; + public const int CLASSID_BTN = 40; + public const int CLASSID_FSH = 41; + public const int SIZE = 0; public const int COLORINFO = 1; public const int FACEINFO = 2; @@ -40,13 +89,16 @@ namespace FFXIVClassic_Map_Server.Actors public bool isStatic = false; + public bool isMovingToSpawn = false; + public bool isAutoAttackEnabled = true; + public uint modelId; public uint[] appearanceIds = new uint[28]; public uint animationId = 0; - public uint currentTarget = 0xC0000000; - public uint currentLockedTarget = 0xC0000000; + public uint currentTarget = Actor.INVALID_ACTORID; + public uint currentLockedTarget = Actor.INVALID_ACTORID; public uint currentActorIcon = 0; @@ -56,40 +108,76 @@ namespace FFXIVClassic_Map_Server.Actors public Group currentParty = null; public ContentGroup currentContentGroup = null; + //public DateTime lastAiUpdate; + + public AIContainer aiContainer; + public StatusEffectContainer statusEffects; + + public CharacterTargetingAllegiance allegiance; + + public Pet pet; + + private Dictionary modifiers = new Dictionary(); + + protected ushort hpBase, hpMaxBase, mpBase, mpMaxBase, tpBase; + protected BattleTemp baseStats = new BattleTemp(); + public ushort currentJob; + public ushort newMainState; + public float spawnX, spawnY, spawnZ; + + //I needed some values I could reuse for random stuff, delete later + public int extraInt; + public uint extraUint; + public float extraFloat; + + protected Dictionary tempVars = new Dictionary(); + public Character(uint actorID) : base(actorID) - { + { //Init timer array to "notimer" for (int i = 0; i < charaWork.statusShownTime.Length; i++) - charaWork.statusShownTime[i] = 0xFFFFFFFF; + charaWork.statusShownTime[i] = 0; + + this.statusEffects = new StatusEffectContainer(this); + + // todo: move this somewhere more appropriate + // todo: base this on equip and shit + SetMod((uint)Modifier.AttackRange, 3); + SetMod((uint)Modifier.Delay, (Program.Random.Next(30, 60) * 100)); + SetMod((uint)Modifier.MovementSpeed, (uint)moveSpeeds[2]); + + spawnX = positionX; + spawnY = positionY; + spawnZ = positionZ; } - public SubPacket CreateAppearancePacket(uint playerActorId) + public SubPacket CreateAppearancePacket() { SetActorAppearancePacket setappearance = new SetActorAppearancePacket(modelId, appearanceIds); - return setappearance.BuildPacket(actorId, playerActorId); + return setappearance.BuildPacket(actorId); } - public SubPacket CreateInitStatusPacket(uint playerActorId) + public SubPacket CreateInitStatusPacket() { - return (SetActorStatusAllPacket.BuildPacket(actorId, playerActorId, charaWork.status)); + return (SetActorStatusAllPacket.BuildPacket(actorId, charaWork.status)); } - public SubPacket CreateSetActorIconPacket(uint playerActorId) + public SubPacket CreateSetActorIconPacket() { - return SetActorIconPacket.BuildPacket(actorId, playerActorId, currentActorIcon); + return SetActorIconPacket.BuildPacket(actorId, currentActorIcon); } - public SubPacket CreateIdleAnimationPacket(uint playerActorId) + public SubPacket CreateSubStatePacket() { - return SetActorSubStatPacket.BuildPacket(actorId, playerActorId, 0, 0, 0, 0, 0, 0, animationId); + return SetActorSubStatePacket.BuildPacket(actorId, currentSubState); } public void SetQuestGraphic(Player player, int graphicNum) { - player.QueuePacket(SetActorQuestGraphicPacket.BuildPacket(player.actorId, actorId, graphicNum)); + player.QueuePacket(SetActorQuestGraphicPacket.BuildPacket(actorId, graphicNum)); } - public void SetCurrentContentGroup(ContentGroup group, Player player = null) + public void SetCurrentContentGroup(ContentGroup group) { if (group != null) charaWork.currentContentGroup = group.GetTypeId(); @@ -98,19 +186,972 @@ namespace FFXIVClassic_Map_Server.Actors currentContentGroup = group; - if (player != null) - { - ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", this, actorId); - propPacketUtil.AddProperty("charaWork.currentContentGroup"); - player.QueuePackets(propPacketUtil.Done()); - } - } - - public void PlayAnimation(uint animId) - { - zone.BroadcastPacketAroundActor(this, PlayAnimationOnActorPacket.BuildPacket(actorId, actorId, animId)); + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", this); + propPacketUtil.AddProperty("charaWork.currentContentGroup"); + zone.BroadcastPacketsAroundActor(this, propPacketUtil.Done()); } - } + //This logic isn't correct, order of GetStatusEffects() is not necessarily the same as the actual effects in game. Also sending every time at once isn't needed + public List GetActorStatusPackets() + { + var propPacketUtil = new ActorPropertyPacketUtil("charaWork/status", this); + var i = 0; + foreach (var effect in statusEffects.GetStatusEffects()) + { + if (!effect.GetHidden()) + { + propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i)); + propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i)); + i++; + } + } + return propPacketUtil.Done(); + } -} + public void PlayAnimation(uint animId, bool onlySelf = false) + { + if (onlySelf) + { + if (this is Player) + ((Player)this).QueuePacket(PlayAnimationOnActorPacket.BuildPacket(actorId, animId)); + } + else + zone.BroadcastPacketAroundActor(this, PlayAnimationOnActorPacket.BuildPacket(actorId, animId)); + } + + public void DoBattleAction(ushort commandId, uint animationId) + { + zone.BroadcastPacketAroundActor(this, CommandResultX00Packet.BuildPacket(actorId, animationId, commandId)); + } + + public void DoBattleAction(ushort commandId, uint animationId, CommandResult result) + { + zone.BroadcastPacketAroundActor(this, CommandResultX01Packet.BuildPacket(actorId, animationId, commandId, result)); + } + + public void DoBattleAction(ushort commandId, uint animationId, CommandResult[] results) + { + int currentIndex = 0; + + while (true) + { + if (results.Length - currentIndex >= 10) + zone.BroadcastPacketAroundActor(this, CommandResultX18Packet.BuildPacket(actorId, animationId, commandId, results, ref currentIndex)); + else if (results.Length - currentIndex > 1) + zone.BroadcastPacketAroundActor(this, CommandResultX10Packet.BuildPacket(actorId, animationId, commandId, results, ref currentIndex)); + else if (results.Length - currentIndex == 1) + { + zone.BroadcastPacketAroundActor(this, CommandResultX01Packet.BuildPacket(actorId, animationId, commandId, results[currentIndex])); + currentIndex++; + } + else + break; + } + } + + public void DoBattleAction(ushort commandId, uint animationId, List results) + { + int currentIndex = 0; + + while (true) + { + if (results.Count - currentIndex >= 10) + zone.BroadcastPacketAroundActor(this, CommandResultX18Packet.BuildPacket(actorId, animationId, commandId, results, ref currentIndex)); + else if (results.Count - currentIndex > 1) + zone.BroadcastPacketAroundActor(this, CommandResultX10Packet.BuildPacket(actorId, animationId, commandId, results, ref currentIndex)); + else if (results.Count - currentIndex == 1) + { + zone.BroadcastPacketAroundActor(this, CommandResultX01Packet.BuildPacket(actorId, animationId, commandId, results[currentIndex])); + currentIndex++; + } + else + break; + + //Sending multiple packets at once causes some issues. Setting any combination of these to zero changes what breaks + //animationId = 0; //If more than one packet is sent out, only send the animation once to avoid double playing. + //commandId = 0; + //sourceActorId = 0; + } + } + + #region ai stuff + public void PathTo(float x, float y, float z, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f) + { + if (aiContainer != null && aiContainer.pathFind != null) + aiContainer.pathFind.PreparePath(x, y, z, stepSize, maxPath, polyRadius); + } + + public void FollowTarget(Actor target, float stepSize = 1.2f, int maxPath = 25, float radius = 0.0f) + { + if (target != null) + PathTo(target.positionX, target.positionY, target.positionZ, stepSize, maxPath, radius); + } + + public double GetMod(Modifier modifier) + { + return GetMod((uint)modifier); + } + + public double GetMod(uint modifier) + { + double res; + if (modifiers.TryGetValue((Modifier)modifier, out res)) + return res; + return 0; + } + + public void SetMod(uint modifier, double val) + { + if (modifiers.ContainsKey((Modifier)modifier)) + modifiers[(Modifier)modifier] = val; + else + modifiers.Add((Modifier)modifier, val); + + if (modifier >= 3 && modifier <= 35) + updateFlags |= ActorUpdateFlags.Stats; + } + + public void AddMod(Modifier modifier, double val) + { + AddMod((uint)modifier, val); + } + + public void AddMod(uint modifier, double val) + { + + double newVal = GetMod(modifier) + val; + SetMod(modifier, newVal); + } + + public void SubtractMod(Modifier modifier, double val) + { + AddMod((uint)modifier, val); + } + + public void SubtractMod(uint modifier, double val) + { + double newVal = GetMod(modifier) - val; + SetMod(modifier, newVal); + } + + public void MultiplyMod(Modifier modifier, double val) + { + MultiplyMod((uint)modifier, val); + } + + public void MultiplyMod(uint modifier, double val) + { + double newVal = GetMod(modifier) * val; + SetMod(modifier, newVal); + } + + public void DivideMod(Modifier modifier, double val) + { + DivideMod((uint)modifier, val); + } + + public void DivideMod(uint modifier, double val) + { + double newVal = GetMod(modifier) / val; + SetMod(modifier, newVal); + } + + + public virtual void OnPath(Vector3 point) + { + //lua.LuaEngine.CallLuaBattleFunction(this, "onPath", this, point); + + updateFlags |= ActorUpdateFlags.Position; + this.isAtSpawn = false; + } + + public override void Update(DateTime tick) + { + + } + + public override void PostUpdate(DateTime tick, List packets = null) + { + if (updateFlags != ActorUpdateFlags.None) + { + packets = packets ?? new List(); + + if ((updateFlags & ActorUpdateFlags.Appearance) != 0) + { + packets.Add(new SetActorAppearancePacket(modelId, appearanceIds).BuildPacket(actorId)); + } + + if ((updateFlags & ActorUpdateFlags.State) != 0) + { + packets.Add(SetActorStatePacket.BuildPacket(actorId, currentMainState, 0x0)); + packets.Add(CommandResultX00Packet.BuildPacket(actorId, 0x72000062, 0)); + packets.Add(CommandResultX01Packet.BuildPacket(actorId, 0x7C000062, 21001, new CommandResult(actorId, 0, 1))); + + updateFlags &= ~ActorUpdateFlags.State; + //DoBattleAction(21001, 0x7C000062, new BattleAction(this.actorId, 0, 1, 0, 0, 1)); //Attack Mode + } + + if ((updateFlags & ActorUpdateFlags.SubState) != 0) + { + //packets.Add(SetActorSubStatePacket.BuildPacket(actorId, currentSubState)); + //packets.Add(CommandResultX00Packet.BuildPacket(actorId, 0x72000062, 0)); + //packets.Add(CommandResultX01Packet.BuildPacket(actorId, 0x7C000062, 21001, new CommandResult(actorId, 0, 1))); + + updateFlags &= ~ActorUpdateFlags.SubState; + //DoBattleAction(21001, 0x7C000062, new BattleAction(this.actorId, 0, 1, 0, 0, 1)); //Attack Mode + } + + if ((updateFlags & ActorUpdateFlags.Status) != 0) + { + + List statusPackets = statusEffects.GetStatusPackets(); + packets.AddRange(statusPackets); + statusPackets.Clear(); + updateFlags &= ~ActorUpdateFlags.Status; + } + + if ((updateFlags & ActorUpdateFlags.StatusTime) != 0) + { + packets.AddRange(statusEffects.GetStatusTimerPackets()); + statusEffects.ResetPropPacketUtil(); + updateFlags &= ~ActorUpdateFlags.StatusTime; + } + + if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0) + { + var propPacketUtil = new ActorPropertyPacketUtil("charaWork/stateAtQuicklyForAll", this); + 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"); + packets.AddRange(propPacketUtil.Done()); + } + + base.PostUpdate(tick, packets); + } + } + + public virtual bool IsValidTarget(Character target, ValidTarget validTarget) + { + return !target.isStatic; + } + + public virtual bool CanAttack() + { + return true; + } + + public virtual bool CanUse(Character target, BattleCommand skill, CommandResult error = null) + { + return false; + } + + + public virtual uint GetAttackDelayMs() + { + return (uint)GetMod((uint)Modifier.Delay); + } + + public virtual uint GetAttackRange() + { + return (uint)(GetMod((uint)Modifier.AttackRange) == 0 ? 3 : GetMod((uint)Modifier.AttackRange)); + } + + public virtual bool Engage(uint targid = 0, ushort newMainState = 0xFFFF) + { + // todo: attack the things + /*if (newMainState != 0xFFFF) + { + currentMainState = newMainState;// this.newMainState = newMainState; + updateFlags |= ActorUpdateFlags.State; + } + else*/ if (aiContainer.CanChangeState()) + { + if (targid == 0) + { + if (currentTarget != Actor.INVALID_ACTORID) + targid = currentTarget; + else if (currentLockedTarget != Actor.INVALID_ACTORID) + targid = currentLockedTarget; + } + //if (targid != 0) + { + aiContainer.Engage(zone.FindActorInArea(targid)); + } + } + + return false; + } + + public virtual bool Engage(Character target) + { + aiContainer.Engage(target); + return false; + } + + public virtual bool Disengage(ushort newMainState = 0xFFFF) + { + /*if (newMainState != 0xFFFF) + { + currentMainState = newMainState;// this.newMainState = newMainState; + updateFlags |= ActorUpdateFlags.State; + } + else*/ if (IsEngaged()) + { + aiContainer.Disengage(); + return true; + } + return false; + } + + public virtual void Cast(uint spellId, uint targetId = 0) + { + if (aiContainer.CanChangeState()) + aiContainer.Cast(zone.FindActorInArea(targetId == 0 ? currentTarget : targetId), spellId); + } + + public virtual void Ability(uint abilityId, uint targetId = 0) + { + if (aiContainer.CanChangeState()) + aiContainer.Ability(zone.FindActorInArea(targetId == 0 ? currentTarget : targetId), abilityId); + } + + public virtual void WeaponSkill(uint skillId, uint targetId = 0) + { + if (aiContainer.CanChangeState()) + aiContainer.WeaponSkill(zone.FindActorInArea(targetId == 0 ? currentTarget : targetId), skillId); + } + + public virtual void Spawn(DateTime tick) + { + aiContainer.Reset(); + // todo: reset hp/mp/tp etc here + ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE); + charaWork.parameterSave.hp = charaWork.parameterSave.hpMax; + charaWork.parameterSave.mp = charaWork.parameterSave.mpMax; + RecalculateStats(); + } + + //AdditionalActions is the list of actions that EXP/Chain messages are added to + public virtual void Die(DateTime tick, CommandResultContainer actionContainer = null) + { + // todo: actual despawn timer + aiContainer.InternalDie(tick, 10); + ChangeSpeed(0.0f, 0.0f, 0.0f, 0.0f); + } + + public virtual void Despawn(DateTime tick) + { + + } + + public bool IsDead() + { + return !IsAlive(); + } + + public bool IsAlive() + { + return !aiContainer.IsDead();// && GetHP() > 0; + } + + public short GetHP() + { + // todo: + return charaWork.parameterSave.hp[0]; + } + + public short GetMaxHP() + { + return charaWork.parameterSave.hpMax[0]; + } + + public short GetMP() + { + return charaWork.parameterSave.mp; + } + + public ushort GetTP() + { + return tpBase; + } + + public short GetMaxMP() + { + return charaWork.parameterSave.mpMax; + } + + public byte GetMPP() + { + return (byte)((charaWork.parameterSave.mp / charaWork.parameterSave.mpMax) * 100); + } + + public byte GetTPP() + { + return (byte)((tpBase / 3000) * 100); + } + + public byte GetHPP() + { + return (byte)(charaWork.parameterSave.hp[0] == 0 ? 0 : (charaWork.parameterSave.hp[0] / (float) charaWork.parameterSave.hpMax[0]) * 100); + } + + public void SetHP(uint hp) + { + charaWork.parameterSave.hp[0] = (short)hp; + if (hp > charaWork.parameterSave.hpMax[0]) + SetMaxHP(hp); + + updateFlags |= ActorUpdateFlags.HpTpMp; + } + + public void SetMaxHP(uint hp) + { + charaWork.parameterSave.hpMax[0] = (short)hp; + updateFlags |= ActorUpdateFlags.HpTpMp; + } + + public void SetMP(uint mp) + { + charaWork.parameterSave.mpMax = (short)mp; + if (mp > charaWork.parameterSave.mpMax) + SetMaxMP(mp); + + updateFlags |= ActorUpdateFlags.HpTpMp; + } + + public void SetMaxMP(uint mp) + { + charaWork.parameterSave.mp = (short)mp; + updateFlags |= ActorUpdateFlags.HpTpMp; + } + + // todo: the following functions are virtuals since we want to check hidden item bonuses etc on player for certain conditions + public virtual void AddHP(int hp, CommandResultContainer resultContainer = null) + { + // dont wanna die ded, don't want to send update if hp isn't actually changed + if (IsAlive() && hp != 0) + { + // todo: +/- hp and die + // todo: battlenpcs probably have way more hp? + var addHp = charaWork.parameterSave.hp[0] + hp; + addHp = addHp.Clamp((short)GetMod((uint)Modifier.MinimumHpLock), charaWork.parameterSave.hpMax[0]); + charaWork.parameterSave.hp[0] = (short)addHp; + + updateFlags |= ActorUpdateFlags.HpTpMp; + + if (charaWork.parameterSave.hp[0] < 1) + Die(Program.Tick, resultContainer); + } + } + + public short GetClass() + { + return charaWork.parameterSave.state_mainSkill[0]; + } + + public short GetLevel() + { + return charaWork.parameterSave.state_mainSkillLevel; + } + + public void AddMP(int mp) + { + if (IsAlive() && mp != 0) + { + charaWork.parameterSave.mp = (short)(charaWork.parameterSave.mp + mp).Clamp(ushort.MinValue, charaWork.parameterSave.mpMax); + + // todo: check hidden effects and shit + + updateFlags |= ActorUpdateFlags.HpTpMp; + } + } + + public void AddTP(int tp) + { + if (IsAlive() && tp != 0) + { + var addTp = charaWork.parameterTemp.tp + tp; + + addTp = addTp.Clamp((int) GetMod(Modifier.MinimumTpLock), 3000); + charaWork.parameterTemp.tp = (short) addTp; + tpBase = (ushort)charaWork.parameterTemp.tp; + updateFlags |= ActorUpdateFlags.HpTpMp; + + if (tpBase >= 1000) + lua.LuaEngine.GetInstance().OnSignal("tpOver1000"); + } + } + + public void DelHP(int hp, CommandResultContainer resultContainer = null) + { + AddHP((short)-hp, resultContainer); + } + + public void DelMP(int mp) + { + AddMP(-mp); + } + + public void DelTP(int tp) + { + AddTP(-tp); + } + + virtual public void CalculateBaseStats() + { + // todo: apply mods and shit here, get race/level/job and shit + uint hpMod = (uint) GetMod((uint)Modifier.Hp); + if (hpMod != 0) + { + SetMaxHP(hpMod); + uint hpp = (uint) GetMod((uint) Modifier.HpPercent); + uint hp = hpMod; + if(hpp != 0) + { + hp = (uint) Math.Ceiling(((float)hpp / 100.0f) * hpMod); + } + SetHP(hp); + } + + uint mpMod = (uint)GetMod((uint)Modifier.Mp); + if (mpMod != 0) + { + SetMaxMP(mpMod); + uint mpp = (uint)GetMod((uint)Modifier.MpPercent); + uint mp = mpMod; + if (mpp != 0) + { + mp = (uint)Math.Ceiling(((float)mpp / 100.0f) * mpMod); + } + SetMP(mp); + } + // todo: recalculate stats and crap + updateFlags |= ActorUpdateFlags.HpTpMp; + + + SetMod((uint)Modifier.HitCount, 1); + } + + public void RecalculateStats() + { + //CalculateBaseStats(); + } + + public void SetStat(uint statId, int val) + { + charaWork.battleTemp.generalParameter[statId] = (short)val; + } + + public short GetStat(uint statId) + { + return charaWork.battleTemp.generalParameter[statId]; + } + + public virtual float GetSpeed() + { + // todo: for battlenpc/player calculate speed + return (float) GetMod((uint)Modifier.MovementSpeed); + } + + public virtual void OnAttack(State state, CommandResult action, ref CommandResult error) + { + var target = state.GetTarget(); + // todo: change animation based on equipped weapon + // todo: get hitrate and shit, handle protect effect and whatever + if (BattleUtils.TryAttack(this, target, action, ref error)) + { + //var packet = BattleActionX01Packet.BuildPacket(owner.actorId, owner.actorId, target.actorId, (uint)0x19001000, (uint)0x8000604, (ushort)0x765D, (ushort)BattleActionX01PacketCommand.Attack, (ushort)damage, (byte)0x1); + } + + // todo: call onAttack/onDamageTaken + //BattleUtils.DamageTarget(this, target, DamageTakenType.Attack, action); + AddTP(200); + target.AddTP(100); + } + + public virtual void OnCast(State state, CommandResult[] actions, BattleCommand spell, ref CommandResult[] errors) + { + // damage is handled in script + var spellCost = spell.CalculateMpCost(this); + this.DelMP(spellCost); // mpCost can be set in script e.g. if caster has something for free spells + + foreach (CommandResult action in actions) + { + if (zone.FindActorInArea(action.targetId) is Character) + { + //BattleUtils.HandleHitType(this, chara, action); + //BattleUtils.DoAction(this, chara, action, DamageTakenType.Magic); + } + } + lua.LuaEngine.GetInstance().OnSignal("spellUsed"); + } + + public virtual void OnWeaponSkill(State state, CommandResult[] actions, BattleCommand skill, ref CommandResult[] errors) + { + // damage is handled in script + + foreach (CommandResult action in actions) + { + //Should we just store the character insteado f having to find it again? + if (zone.FindActorInArea(action.targetId) is Character) + { + //BattleUtils.DoAction(this, chara, action, DamageTakenType.Weaponskill); + } + } + + this.DelTP(skill.tpCost); + } + + public virtual void OnAbility(State state, CommandResult[] actions, BattleCommand ability, ref CommandResult[] errors) + { + foreach (var action in actions) + { + if (zone.FindActorInArea(action.targetId) is Character) + { + //BattleUtils.DoAction(this, chara, action, DamageTakenType.Ability); + } + } + } + + public virtual void OnSpawn() + { + + } + + public virtual void OnDeath() + { + + } + + public virtual void OnDespawn() + { + + } + + public virtual void OnDamageDealt(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + switch (action.hitType) + { + case (HitType.Miss): + OnMiss(defender, skill, action, actionContainer); + break; + case (HitType.Crit): + OnCrit(defender, skill, action, actionContainer); + OnHit(defender, skill, action, actionContainer); + break; + default: + OnHit(defender, skill, action, actionContainer); + break; + } + + //TP is only gained from autoattacks and abilities + if (action.commandType == CommandType.AutoAttack || action.commandType == CommandType.Ability) + { + //TP gained on an attack is usually 100 * delay. + //Store TP seems to add .1% per point. + double weaponDelay = GetMod(Modifier.Delay) / 1000.0; + var storeTPPercent = 1 + (GetMod(Modifier.StoreTp) * 0.001); + AddTP((int)(weaponDelay * 100 * storeTPPercent)); + } + } + + public virtual void OnDamageTaken(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + switch (action.hitType) + { + case (HitType.Miss): + OnEvade(attacker, skill, action, actionContainer); + break; + case (HitType.Parry): + OnParry(attacker, skill, action, actionContainer); + break; + case (HitType.Block): + OnBlock(attacker, skill, action, actionContainer); + break; + } + + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnDamageTaken, "onDamageTaken", attacker, this, skill, action, actionContainer); + + //TP gain formula seems to be something like 5 * e ^ ( -0.667 * [defender's level] ) * damage taken, rounded up + //This should be completely accurate at level 50, but isn't totally accurate at lower levels. + //Don't know if store tp impacts this + double tpModifier = 5 * Math.Pow(Math.E, (-0.0667 * GetLevel())); + AddTP((int)Math.Ceiling(tpModifier * action.amount)); + } + + public UInt64 GetTempVar(string name) + { + UInt64 retVal = 0; + if (tempVars.TryGetValue(name, out retVal)) + return retVal; + return 0; + } + + // cause lua is a dick + public void SetTempVar(string name, uint val) + { + if (tempVars.ContainsKey(name)) + tempVars[name] = val; + } + + public void SetTempVar(string name, UInt64 val) + { + if (tempVars.ContainsKey(name)) + tempVars[name] = val; + } + + public void ResetTempVars() + { + tempVars.Clear(); + } + + #region lua helpers + public bool IsEngaged() + { + return aiContainer.IsEngaged(); + } + + public bool IsPlayer() + { + return this is Player; + } + + public bool IsMonster() + { + return this is BattleNpc; + } + + public bool IsPet() + { + return this is Pet; + } + + public bool IsAlly() + { + return this is Ally; + } + + public bool IsDiscipleOfWar() + { + return GetClass() < CLASSID_THM; + } + + public bool IsDiscipleOfMagic() + { + return GetClass() >= CLASSID_THM && currentJob < CLASSID_CRP; + } + + public bool IsDiscipleOfHand() + { + return GetClass() >= CLASSID_CRP && currentJob < CLASSID_MIN; + } + + public bool IsDiscipleOfLand() + { + return GetClass() >= CLASSID_MIN; + } + #endregion lua helpers + #endregion ai stuff + + //Reset procs. Only send packet if any procs were actually reset. + //This assumes you can't use weaponskills between getting a proc and using the procced ability + public void ResetProcs() + { + var propPacketUtil = new ActorPropertyPacketUtil("charaWork/timingCommand", this); + bool shouldSend = false; + for (int i = 0; i < 4; i++) + { + if (charaWork.battleTemp.timingCommandFlag[i]) + { + shouldSend = true; + charaWork.battleTemp.timingCommandFlag[i] = false; + propPacketUtil.AddProperty(String.Format("charaWork.battleTemp.timingCommandFlag[{0}]", i)); + } + } + + if (shouldSend && this is Player) + ((Player)this).QueuePackets(propPacketUtil.Done()); + } + + //Set given proc to true and send packet if this is a player + // todo: hidden status effects for timing when the procs fall off + public void SetProc(int procId, bool val = true) + { + charaWork.battleTemp.timingCommandFlag[procId] = val; + uint effectId = (uint)StatusEffectId.EvadeProc + (uint)procId; + + //If a proc just occurred, add a hidden effect effect + if (val) + { + StatusEffect procEffect = Server.GetWorldManager().GetStatusEffect(effectId); + procEffect.SetDuration(5); + statusEffects.AddStatusEffect(procEffect, this); + } + //Otherwise we're reseting a proc, remove the status + else + { + statusEffects.RemoveStatusEffect(statusEffects.GetStatusEffectById((uint)effectId)); + } + + if (this is Player) + { + var propPacketUtil = new ActorPropertyPacketUtil("charaWork/timingCommand", this); + propPacketUtil.AddProperty(String.Format("charaWork.battleTemp.timingCommandFlag[{0}]", procId)); + ((Player)this).QueuePackets(propPacketUtil.Done()); + } + } + + public HitDirection GetHitDirection(Actor target) + { + //Get between taget's position and our position + double angle = Vector3.GetAngle(target.GetPosAsVector3(), GetPosAsVector3()); + //Add to the target's rotation, mod by 2pi. This is the angle relative to where the target is looking + //Actor's rotation is 0 degrees on their left side, rotate it by 45 degrees so that quadrants line up with sides + angle = (angle + target.rotation - (.25 * Math.PI)) % (2 * Math.PI); + //Make positive + if (angle < 0) + angle = angle + (2 * Math.PI); + + //Get the side we're on. 0 is front, 1 is right, 2 is rear, 3 is left + var side = (int) (angle / (.5 * Math.PI)) % 4; + + return (HitDirection) (1 << side); + } + + //Called when this character evades attacker's action + public void OnEvade(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + SetProc((ushort)HitType.Evade); + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnEvade, "onEvade", attacker, this, skill, action, actionContainer); + } + + //Called when this character blocks attacker's action + public void OnBlock(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + SetProc((ushort)HitType.Block); + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnBlock, "onBlock", attacker, this, skill, action, actionContainer); + } + + //Called when this character parries attacker's action + public void OnParry(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + SetProc((ushort)HitType.Parry); + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnParry, "onParry", attacker, this, skill, action, actionContainer); + } + + //Called when this character misses + public void OnMiss(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + SetProc((ushort)HitType.Miss); + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnMiss, "onMiss", this, defender, skill, action, actionContainer); + } + + public void OnHit(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHit, "onHit", this, defender, skill, action, actionContainer); + } + + public void OnCrit(Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCrit, "onCrit", this, defender, skill, action, actionContainer); + } + + //The order of messages that appears after using a command is: + + //1. Cast start messages. (ie "You begin casting... ") + //2. Messages from buffs that activate before the command actually starts, like Power Surge or Presence of Mind. (This may be wrong and these could be the same as 4.) + //3. If the command is a multi-hit command, this is where the "You use [command] on [target]" message goes + + //Then, for each hit: + //4. Buffs that activate before a command hits, like Blindside + //5. The hit itself. For single hit commands this message is "Your [command] hits [target] for x damage" for multi hits it's "[Target] takes x points of damage" + //6. Stoneskin falling off + //6. Buffs that activate after a command hits, like Aegis Boon and Divine Veil + + //After all hits + //7. If it's a multi-hit command there's a "{numhits]fold attack..." message or if all hits miss an "All attacks missed" message + //8. Buffs that fall off after the skill ends, like Excruciate + + //For every target defeated: + //8. Defeat message + //9. EXP message + //10. EXP chain message + + + //folder is probably temporary until move to cached scripts is complete + public void DoBattleCommand(BattleCommand command, string folder) + { + //List actions = new List(); + CommandResultContainer actions = new CommandResultContainer(); + + var targets = command.targetFind.GetTargets(); + bool hitTarget = false; + + if (targets.Count > 0) + { + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandStart, "onCommandStart", this, command, actions); + + foreach (var chara in targets) + { + ushort hitCount = 0; + ushort totalDamage = 0; + for (int hitNum = 1; hitNum <= command.numHits; hitNum++) + { + var action = new CommandResult(chara.actorId, command, (byte)GetHitDirection(chara), (byte) hitNum); + + //uncached script + lua.LuaEngine.CallLuaBattleCommandFunction(this, command, folder, "onSkillFinish", this, chara, command, action, actions); + //cached script + //skill.CallLuaFunction(owner, "onSkillFinish", this, chara, command, action, actions); + if (action.ActionLanded()) + { + hitTarget = true; + hitCount++; + totalDamage += action.amount; + } + } + + if (command.numHits > 1) + { + //30442: [hitCount]fold Attack! [chara] takes a total of totalDamage points of damage. + //30450: All attacks miss! + ushort textId = (ushort) (hitTarget ? 30442 : 30450); + actions.AddAction(new CommandResult(chara.actorId, textId, 0, totalDamage, (byte)hitCount)); + } + } + + statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnCommandFinish, "onCommandFinish", this, command, actions); + } + else + { + actions.AddAction(new CommandResult(actorId, 30202, 0)); + } + + //Now that we know if we hit the target we can check if the combo continues + if (this is Player) + { + if (command.isCombo && hitTarget) + ((Player)this).SetCombos(command.comboNextCommandId); + //Only reset combo if the command is a spell or weaponskill, since abilities can be used between combo skills + else if (command.commandType == CommandType.Spell || command.commandType == CommandType.WeaponSkill) + ((Player)this).SetCombos(); + } + + DelMP(command.CalculateMpCost(this)); + DelTP(command.CalculateTpCost(this)); + + actions.CombineLists(); + DoBattleAction(command.id, command.battleAnimation, actions.GetList()); + } + + public List GetPartyMembersInRange(uint range) + { + TargetFind targetFind = new TargetFind(this); + targetFind.SetAOEType(ValidTarget.Party, TargetFindAOEType.Circle, TargetFindAOETarget.Self, range, 0, 10, 0, 0); + targetFind.FindWithinArea(this, ValidTarget.Party, TargetFindAOETarget.Self); + return targetFind.GetTargets(); + } + } +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/chara/Modifier.cs b/FFXIVClassic Map Server/actors/chara/Modifier.cs new file mode 100644 index 00000000..97be6395 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/Modifier.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.chara +{ + //These will need to be redone at some point. remember to update tables in db. + //Consider using text_paramname sheet. that matches up with the stats on armor, but some things will need special handling + //Also, 0-35 should probably match with up BattleTemp + enum Modifier : UInt32 + { + //These line up with ParamNames starting at 15001 and appear on gear + //Health + Hp = 0, //Max HP + Mp = 1, //Max MP + Tp = 2, //Max TP + + //Main stats + Strength = 3, + Vitality = 4, + Dexterity = 5, + Intelligence = 6, + Mind = 7, + Piety = 8, + + //Elemental Resistances + FireResistance = 9, //Lowers Fire damage taken + IceResistance = 10, //Lowers Ice damage taken + WindResistance = 11, //Lowers Wind damage taken + EarthResistance = 12, //Lowers Earth damage taken + LightningResistance = 13, //Lowers Lightning damage taken + WaterResistance = 14, //Lowers Water damage taken + + //Physical Secondary stats + Accuracy = 15, //Increases chance to hit with physical attacks + Evasion = 16, //Decreases chance to be hit by physical attacks + Attack = 17, //Increases damage done with physical attacks + Defense = 18, //Decreases damage taken from physical attacks + + //Physical crit stats + CriticalHitRating = 19, //Increases chance to crit with physical attacks + CriticalHitEvasion = 20, //Decreases chance to be crit by physical attacks + CriticalHitAttackPower = 21, //Increases damage done by critical physical attacks + CriticalHitResilience = 22, //Decreases damage taken from critical physical attacks + + //Magic secondary stats + AttackMagicPotency = 23, //Increases damage done with magical attacks + HealingMagicPotency = 24, //Increases healing done with magic healing + EnhancementMagicPotency = 25, //Increases effect of enhancement magic + EnfeeblingMagicPotency = 26, //Increases effect of enfeebling magic + MagicAccuracy = 27, //Decreases chance for magic to be evaded + MagicEvasion = 28, //Increases chance to evade magic + + //Crafting stats + Craftsmanship = 29, + MagicCraftsmanship = 30, + Control = 31, + Gathering = 32, + Output = 33, + Perception = 34, + + //Magic crit stats + MagicCriticalHitRating = 35, //Increases chance to crit with magical attacks + MagicCriticalHitEvasion = 36, //Decreases chance to be crit by magical attacks + MagicCriticalHitPotency = 37, //Increases damage done by critical magical attacks + MagicCriticalHitResilience = 38, //Decreases damage taken from critical magical attacks + + //Blocking stats + Parry = 39, //Increases chance to parry + BlockRate = 40, //Increases chance to block + Block = 41, //Reduces damage taken from blocked attacks + + //Elemental Potencies + FireMagicPotency = 42, //Increases damage done by Fire Magic + IceMagicPotency = 43, //Increases damage done by Ice Magic + WindMagicPotency = 44, //Increases damage done by Wind Magic + EarthMagicPotency = 45, //Increases damage done by Earth Magic + LightningMagicPotency = 46, //Increases damage done by Lightning Magic + WaterMagicPotency = 47, //Increases damage done by Water Magic + + //Miscellaneous + Regen = 48, //Restores health over time + Refresh = 49, //Restores MP over time + StoreTp = 50, //Increases TP gained by auto attacks and damaging abiltiies + Enmity = 51, //Increases enmity gained from actions + Spikes = 52, //Deals damage or status to attacker when hit + Haste = 53, //Increases attack speed + //54 and 55 didn't have names and seem to be unused + ReducedDurabilityLoss = 56, //Reduces durability loss + IncreasedSpiritbondGain = 57, //Increases rate of spiritbonding + Damage = 58, //Increases damage of auto attacks + Delay = 59, //Increases rate of auto attacks + Fastcast = 60, //Increases speed of casts + MovementSpeed = 61, //Increases movement speed + Exp = 62, //Increases experience gained + RestingHp = 63, //? + RestingMp = 64, //? + + //Attack property resistances + SlashingResistance = 65, //Reduces damage taken by slashing attacks + PiercingResistance = 66, //Reduces damage taken by piercing attacks + BluntResistance = 67, //Reduces damage taken by blunt attacks + ProjectileResistance = 68, //Reduces damage taken by projectile attacks + SonicResistance = 69, //Reduces damage taken by sonic attacks + BreathResistance = 70, //Reduces damage taken by breath attacks + PhysicalResistance = 71, //Reduces damage taken by physical attacks + MagicResistance = 72, //Reduces damage taken by magic attacks + + //Status resistances + SlowResistance = 73, //Reduces chance to be inflicted with slow by status magic + PetrificationResistance = 74, //Reduces chance to be inflicted with petrification by status magic + ParalysisResistance = 75, //Reduces chance to be inflicted with paralysis by status magic + SilenceResistance = 76, //Reduces chance to be inflicted with silence by status magic + BlindResistance = 77, //Reduces chance to be inflicted with blind by status magic + PoisonResistance = 78, //Reduces chance to be inflicted with poison by status magic + StunResistance = 79, //Reduces chance to be inflicted with stun by status magic + SleepResistance = 80, //Reduces chance to be inflicted with sleep by status magic + BindResistance = 81, //Reduces chance to be inflicted with bind by status magic + HeavyResistance = 82, //Reduces chance to be inflicted with heavy by status magic + DoomResistance = 83, //Reduces chance to be inflicted with doom by status magic + + //84-101 didn't have names and seem to be unused + //Miscellaneous + ConserveMp = 101, //Chance to reduce mp used by actions + SpellInterruptResistance = 102, //Reduces chance to be interrupted by damage while casting + DoubleDownOdds = 103, //Increases double down odds + HqDiscoveryRate = 104, + + + //Non-gear mods + None = 105, + NAMEPLATE_SHOWN = 106, + TARGETABLE = 107, + NAMEPLATE_SHOWN2 = 108, + + HpPercent = 109, + MpPercent = 110, + TpPercent = 111, + + AttackRange = 112, //How far away in yalms this character can attack from (probably won't need this when auto attack skills are done) + + Raise = 113, + MinimumHpLock = 114, //Stops HP from falling below this value + MinimumMpLock = 115, //Stops MP from falling below this value + MinimumTpLock = 116, //Stops TP from falling below this value + AttackType = 117, //Attack property of auto attacks (might not need this when auto attack skills are done, unsure) + CanBlock = 118, //Whether the character can block attacks. (For players this is only true when they have a shield) + HitCount = 119, //Amount of hits in an auto attack. Usually 1, 2 for h2h, 3 with spinning heel + + //Flat percent increases to these rates. Might not need these? + RawEvadeRate = 120, + RawParryRate = 121, + RawBlockRate = 122, + RawResistRate = 123, + RawHitRate = 124, + RawCritRate = 125, + + DamageTakenDown = 126, //Percent damage taken down + Regain = 127, //TP regen, should be -90 out of combat, Invigorate sets to 100+ depending on traits + RegenDown = 128, //Damage over time effects. Separate from normal Regen because of how they are displayed in game + Stoneskin = 129, //Nullifies damage + KnockbackImmune = 130, //Immune to knockback effects when above 0 + Stealth = 131, //Not visisble when above 0 + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ModifierList.cs b/FFXIVClassic Map Server/actors/chara/ModifierList.cs new file mode 100644 index 00000000..15b188b6 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ModifierList.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FFXIVClassic_Map_Server.actors.chara.npc; + +namespace FFXIVClassic_Map_Server.actors.chara +{ + class ModifierListEntry + { + public uint id; + public Int64 value; + + public ModifierListEntry(uint id, Int64 value) + { + this.id = id; + this.value = value; + } + } + + class ModifierList + { + public Dictionary modList; + public Dictionary mobModList; + + public ModifierList(uint id) + { + modList = new Dictionary(); + mobModList = new Dictionary(); + } + + public void AddModifier(uint id, Int64 val, bool isMobMod) + { + var list = isMobMod ? mobModList : modList; + list.Add(id, new ModifierListEntry(id, val)); + } + + public void SetModifier(uint id, Int64 val, bool isMobMod) + { + var list = isMobMod ? mobModList : modList; + if (list.ContainsKey(id)) + list[id].value = val; + else + list.Add(id, new ModifierListEntry(id, val)); + } + + public Int64 GetModifier(uint id, bool isMobMod) + { + ModifierListEntry retVal; + var list = isMobMod ? mobModList : modList; + if (!list.TryGetValue(id, out retVal)) + return 0; + + return retVal.value; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/SubState.cs b/FFXIVClassic Map Server/actors/chara/SubState.cs new file mode 100644 index 00000000..e7e3d8c7 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/SubState.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.chara +{ + class SubState + { + public byte breakage = 0; + public byte chantId = 0; + public byte guard = 0; + public byte waste = 0; + public byte mode = 0; + public ushort motionPack = 0; + + public void toggleBreak(int index, bool toggle) + { + if (index > 7 || index < 0) + return; + + if (toggle) + breakage = (byte)(breakage | (1 << index)); + else + breakage = (byte)(breakage & ~(1 << index)); + } + + public void setChant(byte chant) { + chantId = chant; + } + + public void setGuard(byte guard) + { + if (guard >= 0 && guard <= 3) + this.guard = guard; + } + + public void setWaste(byte waste) + { + if (waste >= 0 && waste <= 3) + this.waste = waste; + } + + public void setMode(byte bitfield) + { + mode = bitfield; + } + + public void setMotionPack(ushort mp) + { + motionPack = mp; + } + + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs new file mode 100644 index 00000000..38c636a1 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs @@ -0,0 +1,382 @@ +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.actors.chara.ai.state; +using FFXIVClassic_Map_Server.actors.chara.ai.controllers; +using FFXIVClassic_Map_Server.packets.send.actor; + +// port of ai code in dsp by kjLotus (https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai) +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + class AIContainer + { + private Character owner; + private Controller controller; + private Stack states; + private DateTime latestUpdate; + private DateTime prevUpdate; + public readonly PathFind pathFind; + private TargetFind targetFind; + private ActionQueue actionQueue; + private DateTime lastActionTime; + + public AIContainer(Character actor, Controller controller, PathFind pathFind, TargetFind targetFind) + { + this.owner = actor; + this.states = new Stack(); + this.controller = controller; + this.pathFind = pathFind; + this.targetFind = targetFind; + latestUpdate = DateTime.Now; + prevUpdate = latestUpdate; + actionQueue = new ActionQueue(owner); + } + + public void UpdateLastActionTime(uint delay = 0) + { + lastActionTime = DateTime.Now.AddSeconds(delay); + } + + public DateTime GetLastActionTime() + { + return lastActionTime; + } + + public void Update(DateTime tick) + { + prevUpdate = latestUpdate; + latestUpdate = tick; + + // todo: trigger listeners + + if (controller == null && pathFind != null) + { + 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() + { + while (states.Count > 0 && states.Peek().IsCompleted()) + { + states.Peek().Cleanup(); + states.Pop(); + } + } + + public void InterruptStates() + { + while (states.Count > 0 && states.Peek().CanInterrupt()) + { + states.Peek().SetInterrupted(true); + states.Peek().Cleanup(); + states.Pop(); + } + } + + public void InternalUseItem(Character target, uint slot, uint itemId) + { + // todo: can allies use items? + if (owner is Player) + { + if (CanChangeState()) + { + ChangeState(new ItemState((Player)owner, target, (ushort)slot, itemId)); + } + else + { + // You cannot use that item now. + ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32544, 0x20, itemId); + } + } + } + + public void ClearStates() + { + while (states.Count > 0) + { + states.Peek().Cleanup(); + states.Pop(); + } + } + + public void ChangeController(Controller controller) + { + this.controller = controller; + } + + public T GetController() where T : Controller + { + return controller as T; + } + + public TargetFind GetTargetFind() + { + return targetFind; + } + + public bool CanFollowPath() + { + return pathFind != null && (GetCurrentState() == null || GetCurrentState().CanChangeState()); + } + + public bool CanChangeState() + { + return GetCurrentState() == null || states.Peek().CanChangeState(); + } + + public void ChangeTarget(Character target) + { + if (controller != null) + { + controller.ChangeTarget(target); + } + } + + public void ChangeState(State state) + { + if (CanChangeState()) + { + if (states.Count <= 10) + { + CheckCompletedStates(); + states.Push(state); + } + else + { + throw new Exception("shit"); + } + } + } + + public void ForceChangeState(State state) + { + if (states.Count <= 10) + { + CheckCompletedStates(); + states.Push(state); + } + else + { + throw new Exception("force shit"); + } + } + + public bool IsCurrentState() where T : State + { + return GetCurrentState() is T; + } + + public State GetCurrentState() + { + return states.Count > 0 ? states.Peek() : null; + } + + public DateTime GetLatestUpdate() + { + return latestUpdate; + } + + public void Reset() + { + // todo: reset cooldowns and stuff here too? + targetFind?.Reset(); + pathFind?.Clear(); + ClearStates(); + InternalDisengage(); + } + + public bool IsSpawned() + { + return !IsDead(); + } + + public bool IsEngaged() + { + return owner.currentMainState == SetActorStatePacket.MAIN_STATE_ACTIVE; + } + + public bool IsDead() + { + return owner.currentMainState == SetActorStatePacket.MAIN_STATE_DEAD || + owner.currentMainState == SetActorStatePacket.MAIN_STATE_DEAD2; + } + + public bool IsRoaming() + { + // todo: check mounted? + return owner.currentMainState == SetActorStatePacket.MAIN_STATE_PASSIVE; + } + + public void Engage(Character target) + { + if (controller != null) + controller.Engage(target); + else + InternalEngage(target); + } + + public void Disengage() + { + if (controller != null) + controller.Disengage(); + else + InternalDisengage(); + } + + public void Ability(Character target, uint abilityId) + { + if (controller != null) + controller.Ability(target, abilityId); + else + InternalAbility(target, abilityId); + } + + public void Cast(Character target, uint spellId) + { + if (controller != null) + controller.Cast(target, spellId); + else + InternalCast(target, spellId); + } + + public void WeaponSkill(Character target, uint weaponSkillId) + { + if (controller != null) + controller.WeaponSkill(target, weaponSkillId); + else + InternalWeaponSkill(target, weaponSkillId); + } + + public void MobSkill(Character target, uint mobSkillId) + { + if (controller != null) + controller.MonsterSkill(target, mobSkillId); + else + InternalMobSkill(target, mobSkillId); + } + + public void UseItem(Character target, uint slot, uint itemId) + { + if (controller != null) + controller.UseItem(target, slot, itemId); + } + + public void InternalChangeTarget(Character target) + { + // targets are changed in the controller + if (IsEngaged() || target == null) + { + + } + else + { + Engage(target); + } + } + + public bool InternalEngage(Character target) + { + if (IsEngaged()) + { + if (this.owner.target != target) + { + ChangeTarget(target); + return true; + } + return false; + } + + if (CanChangeState() || (GetCurrentState() != null && GetCurrentState().IsCompleted())) + { + ForceChangeState(new AttackState(owner, target)); + return true; + } + return false; + } + + public void InternalDisengage() + { + pathFind?.Clear(); + GetTargetFind()?.Reset(); + + owner.updateFlags |= ActorUpdateFlags.HpTpMp; + ChangeTarget(null); + + if (owner.currentMainState == SetActorStatePacket.MAIN_STATE_ACTIVE) + owner.ChangeState(SetActorStatePacket.MAIN_STATE_PASSIVE); + + ClearStates(); + } + + public void InternalAbility(Character target, uint abilityId) + { + if (CanChangeState()) + { + ChangeState(new AbilityState(owner, target, (ushort)abilityId)); + } + } + + public void InternalCast(Character target, uint spellId) + { + if (CanChangeState()) + { + ChangeState(new MagicState(owner, target, (ushort)spellId)); + } + } + + public void InternalWeaponSkill(Character target, uint weaponSkillId) + { + if (CanChangeState()) + { + ChangeState(new WeaponSkillState(owner, target, (ushort)weaponSkillId)); + } + } + + public void InternalMobSkill(Character target, uint mobSkillId) + { + if (CanChangeState()) + { + + } + } + + public void InternalDie(DateTime tick, uint fadeoutTimerSeconds) + { + pathFind?.Clear(); + ClearStates(); + ForceChangeState(new DeathState(owner, tick, fadeoutTimerSeconds)); + } + + public void InternalDespawn(DateTime tick, uint respawnTimerSeconds) + { + ClearStates(); + Disengage(); + ForceChangeState(new DespawnState(owner, respawnTimerSeconds)); + } + + public void InternalRaise(Character target) + { + // todo: place at target + // ForceChangeState(new RaiseState(target)); + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs b/FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs new file mode 100644 index 00000000..7f96bd14 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs @@ -0,0 +1,391 @@ +using FFXIVClassic_Map_Server.Actors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FFXIVClassic_Map_Server.actors.chara.player; +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.actors.chara.ai.utils; +using MoonSharp.Interpreter; + +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + + public enum BattleCommandRequirements : ushort + { + None, + DiscipleOfWar = 0x01, + DiscipeOfMagic = 0x02, + HandToHand = 0x04, + Sword = 0x08, + Shield = 0x10, + Axe = 0x20, + Archery = 0x40, + Polearm = 0x80, + Thaumaturgy = 0x100, + Conjury = 0x200 + } + + public enum BattleCommandPositionBonus : byte + { + None, + Front = 0x01, + Rear = 0x02, + Flank = 0x04 + } + + public enum BattleCommandProcRequirement : byte + { + None, + Miss, + Evade, + Parry, + Block + } + + public enum BattleCommandValidUser : byte + { + All, + Player, + Monster + } + + public enum BattleCommandCastType : ushort + { + None, + Weaponskill = 1, + Weaponskill2 = 2, + BlackMagic = 3, + WhiteMagic = 4, + SongMagic = 8 + } + + + //What type of command it is + [Flags] + public enum CommandType : ushort + { + //Type of action + None = 0, + AutoAttack = 1, + WeaponSkill = 2, + Ability =3, + Spell = 4 + } + + public enum KnockbackType : ushort + { + None = 0, + Level1 = 1, + Level2 = 2, + Level3 = 3, + Level4 = 4, + Level5 = 5, + Clockwise1 = 6, + Clockwise2 = 7, + CounterClockwise1 = 8, + CounterClockwise2 = 9, + DrawIn = 10 + } + + class BattleCommand + { + public ushort id; + public string name; + public byte job; + public byte level; + public BattleCommandRequirements requirements; + public ValidTarget mainTarget; //what the skill has to be used on. ie self for flare, enemy for ring of talons even though both are self-centere aoe + public ValidTarget validTarget; //what type of character the skill can hit + public TargetFindAOEType aoeType; //shape of aoe + public TargetFindAOETarget aoeTarget; //where the center of the aoe is (target/self) + public byte numHits; //amount of hits in the skill + public BattleCommandPositionBonus positionBonus; //bonus for front/flank/rear + public BattleCommandProcRequirement procRequirement;//if the skill requires a block/parry/evade before using + public float range; //maximum distance to target to be able to use this skill + public float minRange; //Minimum distance to target to be able to use this skill + + public uint statusId; //id of statuseffect that the skill might inflict + public uint statusDuration; //duration of statuseffect in milliseconds + public float statusChance; //percent chance of status landing, 0-1.0. Usually 1.0 for buffs + public byte castType; //casting animation, 2 for blm, 3 for whm, 8 for brd + public uint castTimeMs; //cast time in milliseconds + public uint recastTimeMs; //recast time in milliseconds + public uint maxRecastTimeSeconds; //maximum recast time in seconds + public short mpCost; //short in case these casts can have negative cost + public short tpCost; //short because there are certain cases where we want weaponskills to have negative costs (such as Feint) + public byte animationType; + public ushort effectAnimation; + public ushort modelAnimation; + public ushort animationDurationSeconds; + public uint battleAnimation; + public ushort worldMasterTextId; + public float aoeRange; //Radius for circle and cone aoes, length for box aoes + public float aoeMinRange; //Minimum range of aoe effect for things like Lunar Dynamo or Arrow Helix + public float aoeConeAngle; //Angle of aoe cones + public float aoeRotateAngle; //Amount aoes are rotated about the target position (usually the user's position) + public float rangeHeight; //Total height a skill can be used against target above or below user + public float rangeWidth; //Width of box aoes + public int[] comboNextCommandId = new int[2]; //next two skills in a combo + public short comboStep; //Where in a combo string this skill is + public CommandType commandType; + public ActionProperty actionProperty; + public ActionType actionType; + + + public byte statusTier; //tier of status to put on target + public double statusMagnitude = 0; //magnitude of status to put on target + public ushort basePotency; //damage variable + public float enmityModifier; //multiples by damage done to get final enmity + public float accuracyModifier; //modifies accuracy + public float bonusCritRate; //extra crit rate + public bool isCombo; + public bool comboEffectAdded = false; //If the combo effect is added to multiple hiteffects it plays multiple times, so this keeps track of that + public bool isRanged = false; + + public bool actionCrit; //Whether any actions were critical hits, used for Excruciate + + public lua.LuaScript script; //cached script + + public TargetFind targetFind; + public BattleCommandValidUser validUser; + + public BattleCommand(ushort id, string name) + { + this.id = id; + this.name = name; + this.range = 0; + this.enmityModifier = 1; + this.accuracyModifier = 0; + this.statusTier = 1; + this.statusChance = 50; + this.recastTimeMs = (uint) maxRecastTimeSeconds * 1000; + this.isCombo = false; + } + + public BattleCommand Clone() + { + return (BattleCommand)MemberwiseClone(); + } + + public int CallLuaFunction(Character chara, string functionName, params object[] args) + { + if (script != null && !script.Globals.Get(functionName).IsNil()) + { + DynValue res = new DynValue(); + res = script.Call(script.Globals.Get(functionName), args); + if (res != null) + return (int)res.Number; + } + + return -1; + } + + public bool IsSpell() + { + return mpCost != 0 || castTimeMs != 0; + } + + public bool IsInstantCast() + { + return castTimeMs == 0; + } + + //Checks whether the skill can be used on the given targets, uses error to return specific text ids for errors + public bool IsValidMainTarget(Character user, Character target, CommandResult error = null) + { + targetFind = new TargetFind(user, target); + + if (aoeType == TargetFindAOEType.Box) + { + targetFind.SetAOEBox(validTarget, aoeTarget, aoeRange, rangeWidth, aoeRotateAngle); + } + else + { + targetFind.SetAOEType(validTarget, aoeType, aoeTarget, aoeRange, aoeMinRange, rangeHeight, aoeRotateAngle, aoeConeAngle); + } + + /* + worldMasterTextId + 32511 Target does not exist + 32512 cannot be performed on a KO'd target. + 32513 can only be performed on a KO'd target. + 32514 cannot be performed on yourself. + 32515 can only be performed on yourself. + 32516 cannot be performed on a friendly target. + 32517 can only be performed on a friendly target. + 32518 cannot be performed on an enemy. + 32519 can only be performed on an enemy. + 32547 That command cannot be performed on the current target. + 32548 That command cannot be performed on a party member + */ + if (target == null) + { + error?.SetTextId(32511); + return false; + } + + //This skill can't be used on a corpse and target is dead + if ((mainTarget & ValidTarget.Corpse) == 0 && target.IsDead()) + { + error?.SetTextId(32512); + return false; + } + + //This skill must be used on a corpse and target is alive + if ((mainTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive()) + { + error?.SetTextId(32513); + return false; + } + + //This skill can't be used on self and target is self + if ((mainTarget & ValidTarget.Self) == 0 && target == user) + { + error?.SetTextId(32514); + return false; + } + + //This skill must be used on self and target isn't self + if ((mainTarget & ValidTarget.SelfOnly) != 0 && target != user) + { + error?.SetTextId(32515); + return false; + } + + //This skill can't be used on an ally and target is an ally + if ((mainTarget & ValidTarget.Ally) == 0 && target.allegiance == user.allegiance) + { + error?.SetTextId(32516); + return false; + } + + //This skill must be used on an ally and target is not an ally + if ((mainTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != user.allegiance) + { + error?.SetTextId(32517); + return false; + } + + //This skill can't be used on an enemu and target is an enemy + if ((mainTarget & ValidTarget.Enemy) == 0 && target.allegiance != user.allegiance) + { + error?.SetTextId(32518); + return false; + } + + //This skill must be used on an enemy and target is an ally + if ((mainTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == user.allegiance) + { + error?.SetTextId(32519); + return false; + } + + //This skill can't be used on party members and target is a party member + if ((mainTarget & ValidTarget.Party) == 0 && target.currentParty == user.currentParty) + { + error?.SetTextId(32548); + return false; + } + + //This skill must be used on party members and target is not a party member + if ((mainTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != user.currentParty) + { + error?.SetTextId(32547); + return false; + } + + //This skill can't be used on NPCs and target is an npc + if ((mainTarget & ValidTarget.NPC) == 0 && target.isStatic) + { + error?.SetTextId(32547); + return false; + } + + //This skill must be used on NPCs and target is not an npc + if ((mainTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic) + { + error?.SetTextId(32547); + return false; + } + + // todo: why is player always zoning? + // cant target if zoning + if (target is Player && ((Player)target).playerSession.isUpdatesLocked) + { + user.aiContainer.ChangeTarget(null); + return false; + } + + if (target.zone != user.zone) + return false; + + return true; + } + + public ushort CalculateMpCost(Character user) + { + // todo: use precalculated costs instead + var level = user.GetLevel(); + ushort cost = 0; + if (level <= 10) + cost = (ushort)(100 + level * 10); + else if (level <= 20) + cost = (ushort)(200 + (level - 10) * 20); + else if (level <= 30) + cost = (ushort)(400 + (level - 20) * 40); + else if (level <= 40) + cost = (ushort)(800 + (level - 30) * 70); + else if (level <= 50) + cost = (ushort)(1500 + (level - 40) * 130); + else if (level <= 60) + cost = (ushort)(2800 + (level - 50) * 200); + else if (level <= 70) + cost = (ushort)(4800 + (level - 60) * 320); + else + cost = (ushort)(8000 + (level - 70) * 500); + + //scale the mpcost by level + cost = (ushort)Math.Ceiling((cost * mpCost * 0.001)); + + //if user is player, check if spell is a part of combo + if (user is Player) + { + var player = user as Player; + if (player.playerWork.comboNextCommandId[0] == id || player.playerWork.comboNextCommandId[1] == id) + cost = (ushort)Math.Ceiling(cost * (1 - player.playerWork.comboCostBonusRate)); + } + + return mpCost != 0 ? cost : (ushort)0; + } + + //Calculate TP cost taking into considerating the combo bonus rate for players + //Should this set tpCost or should it be called like CalculateMp where it gets calculated each time? + //Might cause issues with the delay between starting and finishing a WS + public short CalculateTpCost(Character user) + { + short tp = tpCost; + //Calculate tp cost + if (user is Player) + { + var player = user as Player; + if (player.playerWork.comboNextCommandId[0] == id || player.playerWork.comboNextCommandId[1] == id) + tp = (short)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate)); + } + + return tp; + } + + public List GetTargets() + { + return targetFind?.GetTargets(); + } + + public ushort GetCommandType() + { + return (ushort) commandType; + } + } +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/chara/ai/BattleTrait.cs b/FFXIVClassic Map Server/actors/chara/ai/BattleTrait.cs new file mode 100644 index 00000000..f8f41b48 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/BattleTrait.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + class BattleTrait + { + public ushort id; + public string name; + public byte job; + public byte level; + public uint modifier; + public int bonus; + + public BattleTrait(ushort id, string name, byte job, byte level, uint modifier, int bonus) + { + this.id = id; + this.name = name; + this.job = job; + this.level = level; + this.modifier = modifier; + this.bonus = bonus; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs new file mode 100644 index 00000000..47086f68 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs @@ -0,0 +1,91 @@ +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 +{ + // todo: actually implement enmity properly + class HateEntry + { + public Character actor; + public uint cumulativeEnmity; + public uint volatileEnmity; + public bool isActive; + + public HateEntry(Character actor, uint cumulativeEnmity = 0, uint volatileEnmity = 0, bool isActive = false) + { + this.actor = actor; + this.cumulativeEnmity = cumulativeEnmity; + this.volatileEnmity = volatileEnmity; + this.isActive = isActive; + } + } + + class HateContainer + { + private Dictionary hateList; + private Character owner; + + public HateContainer(Character owner) + { + this.owner = owner; + this.hateList = new Dictionary(); + } + + public void AddBaseHate(Character target) + { + if (!HasHateForTarget(target)) + hateList.Add(target, new HateEntry(target, 1, 0, true)); + } + + public void UpdateHate(Character target, int damage) + { + AddBaseHate(target); + //hateList[target].volatileEnmity += (uint)damage; + hateList[target].cumulativeEnmity += (uint)damage; + } + + public void ClearHate(Character target = null) + { + if (target != null) + hateList.Remove(target); + else + hateList.Clear(); + } + + private void UpdateHate(HateEntry entry) + { + + } + + public Dictionary GetHateList() + { + // todo: return unmodifiable collection? + return hateList; + } + + public bool HasHateForTarget(Character target) + { + return hateList.ContainsKey(target); + } + + public Character GetMostHatedTarget() + { + uint enmity = 0; + Character target = null; + + foreach(var entry in hateList.Values) + { + if (entry.cumulativeEnmity > enmity && entry.isActive) + { + enmity = entry.cumulativeEnmity; + target = entry.actor; + } + } + return target; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs b/FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs new file mode 100644 index 00000000..410f8a23 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs @@ -0,0 +1,706 @@ +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.lua; +using FFXIVClassic_Map_Server.packets.send.actor; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MoonSharp.Interpreter; +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + enum StatusEffectId : uint + { + RageofHalone = 221021, + + Quick = 223001, + Haste = 223002, + Slow = 223003, + Petrification = 223004, + Paralysis = 223005, + Silence = 223006, + Blind = 223007, + Mute = 223008, + Slowcast = 223009, + Glare = 223010, + Poison = 223011, + Transfixion = 223012, + Pacification = 223013, + Amnesia = 223014, + Stun = 223015, + Daze = 223016, + ExposedFront = 223017, + ExposedRight = 223018, + ExposedRear = 223019, + ExposedLeft = 223020, + Incapacitation = 223021, + Incapacitation2 = 223022, + Incapacitation3 = 223023, + Incapacitation4 = 223024, + Incapacitation5 = 223025, + Incapacitation6 = 223026, + Incapacitation7 = 223027, + Incapacitation8 = 223028, + HPBoost = 223029, + HPPenalty = 223030, + MPBoost = 223031, + MPPenalty = 223032, + AttackUp = 223033, + AttackDown = 223034, + AccuracyUp = 223035, + AccuracyDown = 223036, + DefenseUp = 223037, + DefenseDown = 223038, + EvasionUp = 223039, + EvasionDown = 223040, + MagicPotencyUp = 223041, + MagicPotencyDown = 223042, + MagicAccuracyUp = 223043, + MagicAccuracyDown = 223044, + MagicDefenseUp = 223045, + MagicDefenseDown = 223046, + MagicResistanceUp = 223047, + MagicResistanceDown = 223048, + CombatFinesse = 223049, + CombatHindrance = 223050, + MagicFinesse = 223051, + MagicHindrance = 223052, + CombatResilience = 223053, + CombatVulnerability = 223054, + MagicVulnerability = 223055, + MagicResilience = 223056, + Inhibited = 223057, + AegisBoon = 223058, + Deflection = 223059, + Outmaneuver = 223060, + Provoked = 223061, + Sentinel = 223062, + Cover = 223063, + Rampart = 223064, + StillPrecision = 223065, + Cadence = 223066, + DiscerningEye = 223067, + TemperedWill = 223068, + Obsess = 223069, + Ambidexterity = 223070, + BattleCalm = 223071, + MasterofArms = 223072, + Taunted = 223073, + Blindside = 223074, + Featherfoot = 223075, + PresenceofMind = 223076, + CoeurlStep = 223077, + EnduringMarch = 223078, + MurderousIntent = 223079, + Entrench = 223080, + Bloodbath = 223081, + Retaliation = 223082, + Foresight = 223083, + Defender = 223084, + Rampage = 223085, //old effect + Enraged = 223086, + Warmonger = 223087, + Disorientx1 = 223088, + Disorientx2 = 223089, + Disorientx3 = 223090, + KeenFlurry = 223091, + ComradeinArms = 223092, + Ferocity = 223093, + Invigorate = 223094, + LineofFire = 223095, + Jump = 223096, + Collusion = 223097, + Diversion = 223098, + SpeedSurge = 223099, + LifeSurge = 223100, + SpeedSap = 223101, + LifeSap = 223102, + Farshot = 223103, + QuellingStrike = 223104, + RagingStrike = 223105, //old effect + HawksEye = 223106, + SubtleRelease = 223107, + Decoy = 223108, //Untraited + Profundity = 223109, + TranceChant = 223110, + RoamingSoul = 223111, + Purge = 223112, + Spiritsong = 223113, + Resonance = 223114, //Old Resonance? Both have the same icons and description + Soughspeak = 223115, + PresenceofMind2 = 223116, + SanguineRite = 223117, //old effect + PunishingBarbs = 223118, + DarkSeal = 223119, //old effect + Emulate = 223120, + ParadigmShift = 223121, + ConcussiveBlowx1 = 223123, + ConcussiveBlowx2 = 223124, + ConcussiveBlowx3 = 223125, + SkullSunder = 223126, + Bloodletter = 223127, //comboed effect + Levinbolt = 223128, + Protect = 223129, //untraited protect + Shell = 223130, //old shell + Reraise = 223131, + ShockSpikes = 223132, + Stoneskin = 223133, + Scourge = 223134, + Bio = 223135, + Dia = 223136, + Banish = 223137, + StygianSpikes = 223138, + ATKAbsorbed = 223139, + DEFAbsorbed = 223140, + ACCAbsorbed = 223141, + EVAAbsorbed = 223142, + AbsorbATK = 223143, + AbsorbDEF = 223144, + AbsorbACC = 223145, + AbsorbEVA = 223146, + SoulWard = 223147, + Burn = 223148, + Frost = 223149, + Shock = 223150, + Drown = 223151, + Choke = 223152, + Rasp = 223153, + Flare = 223154, + Freeze = 223155, + Burst = 223156, + Flood = 223157, + Tornado = 223158, + Quake = 223159, + Berserk = 223160, + RegimenofRuin = 223161, + RegimenofTrauma = 223162, + RegimenofDespair = 223163, + RegimenofConstraint = 223164, + Weakness = 223165, + Scavenge = 223166, + Fastcast = 223167, + MidnightHowl = 223168, + Outlast = 223169, + Steadfast = 223170, + DoubleNock = 223171, + TripleNock = 223172, + Covered = 223173, + PerfectDodge = 223174, + ExpertMining = 223175, + ExpertLogging = 223176, + ExpertHarvesting = 223177, + ExpertFishing = 223178, + ExpertSpearfishing = 223179, + Regen = 223180, + Refresh = 223181, + Regain = 223182, + TPBleed = 223183, + Empowered = 223184, + Imperiled = 223185, + Adept = 223186, + Inept = 223187, + Quick2 = 223188, + Quick3 = 223189, + WristFlick = 223190, + Glossolalia = 223191, + SonorousBlast = 223192, + Comradery = 223193, + StrengthinNumbers = 223194, + + BrinkofDeath = 223197, + CraftersGrace = 223198, + GatherersGrace = 223199, + Rebirth = 223200, + Stealth = 223201, + StealthII = 223202, + StealthIII = 223203, + StealthIV = 223204, + Combo = 223205, + GoringBlade = 223206, + Berserk2 = 223207, //new effect + Rampage2 = 223208, //new effect + FistsofFire = 223209, + FistsofEarth = 223210, + FistsofWind = 223211, + PowerSurgeI = 223212, + PowerSurgeII = 223213, + PowerSurgeIII = 223214, + LifeSurgeI = 223215, + LifeSurgeII = 223216, + LifeSurgeIII = 223217, + DreadSpike = 223218, + BloodforBlood = 223219, + Barrage = 223220, + RagingStrike2 = 223221, + + Swiftsong = 223224, + SacredPrism = 223225, + ShroudofSaints = 223226, + ClericStance = 223227, + BlissfulMind = 223228, + DarkSeal2 = 223229, //new effect + Resonance2 = 223230, + Excruciate = 223231, + Necrogenesis = 223232, + Parsimony = 223233, + SanguineRite2 = 223234, //untraited effect + Aero = 223235, + Outmaneuver2 = 223236, + Blindside2 = 223237, + Decoy2 = 223238, //Traited + Protect2 = 223239, //Traited + SanguineRite3 = 223240, //Traited + Bloodletter2 = 223241, //uncomboed effect + FullyBlissfulMind = 223242, + MagicEvasionDown = 223243, + HundredFists = 223244, + SpinningHeel = 223245, + DivineVeil = 223248, + HallowedGround = 223249, + Vengeance = 223250, + Antagonize = 223251, + MightyStrikes = 223252, + BattleVoice = 223253, + BalladofMagi = 223254, + PaeonofWar = 223255, + MinuetofRigor = 223256, + GoldLung = 223258, + Goldbile = 223259, + AurumVeil = 223260, + AurumVeilII = 223261, + Flare2 = 223262, + Resting = 223263, + DivineRegen = 223264, + DefenseAndEvasionUp = 223265, + MagicDefenseAndEvasionUp = 223266, + AttackUp2 = 223267, + MagicPotencyUp2 = 223268, + DefenseAndEvasionDown = 223269, + MagicDefenseAndEvasionDown = 223270, + Poison2 = 223271, + DeepBurn = 223272, + LunarCurtain = 223273, + DefenseUp2 = 223274, + AttackDown2 = 223275, + Sanction = 223992, + IntactPodlingToting = 223993, + RedRidingHooded = 223994, + Medicated = 223998, + WellFed = 223999, + + Sleep = 228001, + Bind = 228011, + Fixation = 228012, + Bind2 = 228013, + Heavy = 228021, + Charm = 228031, + Flee = 228041, + Doom = 228051, + SynthesisSupport = 230001, + WoodyardAccess = 230002, + SmithsForgeAccess = 230003, + ArmorersForgeAccess = 230004, + GemmaryAccess = 230005, + TanneryAccess = 230006, + ClothshopAccess = 230007, + LaboratoryAccess = 230008, + CookeryAccess = 230009, + MinersSupport = 230010, + BotanistsSupport = 230011, + FishersSupport = 230012, + GearChange = 230013, + GearDamage = 230014, + HeavyGearDamage = 230015, + Lamed = 230016, + Lamed2 = 230017, + Lamed3 = 230018, + Poison3 = 231002, + Envenom = 231003, + Berserk4 = 231004, + GuardiansAspect = 253002, + + + // custom effects here + // status for having procs fall off + EvadeProc = 253003, + BlockProc = 253004, + ParryProc = 253005, + MissProc = 253006, + EXPChain = 253007 + } + + [Flags] + enum StatusEffectFlags : uint + { + None = 0, + + //Loss flags - Do we need loseonattacking/caststart? Could just be done with activate flags + LoseOnDeath = 1 << 0, // effects removed on death + LoseOnZoning = 1 << 1, // effects removed on zoning + LoseOnEsuna = 1 << 2, // effects which can be removed with esuna (debuffs) + LoseOnDispel = 1 << 3, // some buffs which player might be able to dispel from mob + LoseOnLogout = 1 << 4, // effects removed on logging out + LoseOnAttacking = 1 << 5, // effects removed when owner attacks another entity + LoseOnCastStart = 1 << 6, // effects removed when owner starts casting + LoseOnAggro = 1 << 7, // effects removed when owner gains enmity (swiftsong) + LoseOnClassChange = 1 << 8, //Effect falls off whhen changing class + + //Activate flags + ActivateOnCastStart = 1 << 9, //Activates when a cast starts. + ActivateOnCommandStart = 1 << 10, //Activates when a command is used, before iterating over targets. Used for things like power surge, excruciate. + ActivateOnCommandFinish = 1 << 11, //Activates when the command is finished, after all targets have been iterated over. Used for things like Excruciate and Resonance falling off. + ActivateOnPreactionTarget = 1 << 12, //Activates after initial rates are calculated for an action against owner + ActivateOnPreactionCaster = 1 << 13, //Activates after initial rates are calculated for an action by owner + ActivateOnDamageTaken = 1 << 14, + ActivateOnHealed = 1 << 15, + + //Should these be rolled into DamageTaken? + ActivateOnMiss = 1 << 16, //Activates when owner misses + ActivateOnEvade = 1 << 17, //Activates when owner evades + ActivateOnParry = 1 << 18, //Activates when owner parries + ActivateOnBlock = 1 << 19, //Activates when owner evades + ActivateOnHit = 1 << 20, //Activates when owner hits + ActivateOnCrit = 1 << 21, //Activates when owner crits + + //Prevent flags. Sleep/stun/petrify/etc combine these + PreventSpell = 1 << 22, // effects which prevent using spells, such as silence + PreventWeaponSkill = 1 << 23, // effects which prevent using weaponskills, such as pacification + PreventAbility = 1 << 24, // effects which prevent using abilities, such as amnesia + PreventAttack = 1 << 25, // effects which prevent basic attacks + PreventMovement = 1 << 26, // effects which prevent movement such as bind, still allows turning in place + PreventTurn = 1 << 27, // effects which prevent turning, such as stun + PreventUntarget = 1 << 28, // effects which prevent changing targets, such as fixation + Stance = 1 << 29 // effects that do not have a timer + } + + enum StatusEffectOverwrite : byte + { + None, + Always, + GreaterOrEqualTo, + GreaterOnly, + } + + class StatusEffect + { + // todo: probably use get;set; + private Character owner; + private Character source; + private StatusEffectId id; + private string name; // name of this effect + private DateTime startTime; // when was this effect added + private DateTime endTime; // when this status falls off + private DateTime lastTick; // when did this effect last tick + private uint duration; // how long should this effect last in seconds + private uint tickMs; // how often should this effect proc + private double magnitude; // a value specified by scripter which is guaranteed to be used by all effects + private byte tier; // same effect with higher tier overwrites this + private double extra; // optional value + private StatusEffectFlags flags; // death/erase/dispel etc + private StatusEffectOverwrite overwrite; // how to handle adding an effect with same id (see StatusEfectOverwrite) + private bool silentOnGain = false; //Whether a message is sent when the status is gained + private bool silentOnLoss = false; //Whether a message is sent when the status is lost + private bool hidden = false; //Whether this status is shown. Used for things that aren't really status effects like exp chains and procs + private ushort statusGainTextId; //The text id used when the status is gained + private ushort statusLossTextId; //The text id used when the status effect falls off when its time runs out + public LuaScript script; + + HitEffect animationEffect; + + public StatusEffect(Character owner, uint id, double magnitude, uint tickMs, uint duration, byte tier = 0) + { + this.owner = owner; + this.source = owner; + this.id = (StatusEffectId)id; + this.magnitude = magnitude; + this.tickMs = tickMs; + this.duration = duration; + this.tier = tier; + + this.startTime = DateTime.Now; + this.lastTick = startTime; + } + + public StatusEffect(Character owner, StatusEffect effect) + { + this.owner = owner; + this.source = owner; + this.id = effect.id; + this.magnitude = effect.magnitude; + this.tickMs = effect.tickMs; + this.duration = effect.duration; + this.tier = effect.tier; + this.startTime = effect.startTime; + this.lastTick = effect.lastTick; + + this.name = effect.name; + this.flags = effect.flags; + this.overwrite = effect.overwrite; + this.statusGainTextId = effect.statusGainTextId; + this.statusLossTextId = effect.statusLossTextId; + this.extra = effect.extra; + this.script = effect.script; + this.silentOnGain = effect.silentOnGain; + this.silentOnLoss = effect.silentOnLoss; + this.hidden = effect.hidden; + } + + public StatusEffect(uint id, string name, uint flags, uint overwrite, uint tickMs, bool hidden, bool silentOnGain, bool silentOnLoss) + { + this.id = (StatusEffectId)id; + this.name = name; + this.flags = (StatusEffectFlags)flags; + this.overwrite = (StatusEffectOverwrite)overwrite; + this.tickMs = tickMs; + this.hidden = hidden; + this.silentOnGain = silentOnGain; + this.silentOnLoss = silentOnLoss; + } + + // return true when duration has elapsed + public bool Update(DateTime tick, CommandResultContainer resultContainer = null) + { + if (tickMs != 0 && (tick - lastTick).TotalMilliseconds >= tickMs) + { + lastTick = tick; + if (LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this, resultContainer) > 0) + return true; + } + + if (duration >= 0 && tick >= endTime) + { + return true; + } + return false; + } + + public int CallLuaFunction(Character chara, string functionName, params object[] args) + { + + DynValue res = new DynValue(); + + return lua.LuaEngine.CallLuaStatusEffectFunction(chara, this, functionName, args); + if (!script.Globals.Get(functionName).IsNil()) + { + res = script.Call(script.Globals.Get(functionName), args); + if (res != null) + return (int)res.Number; + } + + } + + public Character GetOwner() + { + return owner; + } + + public Character GetSource() + { + return source ?? owner; + } + + public uint GetStatusEffectId() + { + return (uint)id; + } + + public ushort GetStatusId() + { + return (ushort)(id - 200000); + } + + public DateTime GetStartTime() + { + return startTime; + } + + public DateTime GetEndTime() + { + return endTime; + } + + public string GetName() + { + return name; + } + + public uint GetDuration() + { + return duration; + } + + public uint GetTickMs() + { + return tickMs; + } + + public double GetMagnitude() + { + return magnitude; + } + + public byte GetTier() + { + return tier; + } + + public double GetExtra() + { + return extra; + } + + public uint GetFlags() + { + return (uint)flags; + } + + public byte GetOverwritable() + { + return (byte)overwrite; + } + + public bool GetSilentOnGain() + { + return silentOnGain; + } + + public bool GetSilentOnLoss() + { + return silentOnLoss; + } + + public bool GetHidden() + { + return hidden; + } + + public ushort GetStatusGainTextId() + { + return 30328; + return statusGainTextId; + } + + public ushort GetStatusLossTextId() + { + return statusLossTextId; + } + + public void SetStartTime(DateTime time) + { + this.startTime = time; + this.lastTick = time; + } + + public void SetEndTime(DateTime time) + { + //If it's a stance, just set endtime to highest number possible for XIV + if ((flags & StatusEffectFlags.Stance) != 0) + { + endTime = Utils.UnixTimeStampToDateTime(4294967295); + } + else + { + endTime = time; + } + } + + //Refresh the status, updating the end time based on the duration of the status and broadcasts the new time + public void RefreshTime() + { + endTime = DateTime.Now.AddSeconds(GetDuration()); + int index = Array.IndexOf(owner.charaWork.status, GetStatusId()); + + if (index >= 0) + owner.statusEffects.SetTimeAtIndex(index, (uint) Utils.UnixTimeStampUTC(endTime)); + } + + public void SetOwner(Character owner) + { + this.owner = owner; + } + + public void SetSource(Character source) + { + this.source = source; + } + + public void SetName(string name) + { + this.name = name; + } + + public void SetMagnitude(double magnitude) + { + this.magnitude = magnitude; + } + + public void SetDuration(uint duration) + { + this.duration = duration; + } + + public void SetTickMs(uint tickMs) + { + this.tickMs = tickMs; + } + + public void SetTier(byte tier) + { + this.tier = tier; + } + + public void SetExtra(double val) + { + this.extra = val; + } + + public void SetFlags(uint flags) + { + this.flags = (StatusEffectFlags)flags; + } + + public void SetOverwritable(byte overwrite) + { + this.overwrite = (StatusEffectOverwrite)overwrite; + } + + public void SetSilentOnGain(bool silent) + { + this.silentOnGain = silent; + } + + public void SetSilentOnLoss(bool silent) + { + this.silentOnLoss = silent; + } + + public void SetHidden(bool hidden) + { + this.hidden = hidden; + } + + public void SetStatusGainTextId(ushort textId) + { + this.statusGainTextId = textId; + } + + public void SetStatusLossTextId(ushort textId) + { + this.statusLossTextId = textId; + } + + + public void SetAnimation(uint hitEffect) + { + animationEffect = (HitEffect)hitEffect; + } + + public uint GetAnimation() + { + return (uint)animationEffect; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs b/FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs new file mode 100644 index 00000000..a4f35405 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs @@ -0,0 +1,414 @@ +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.lua; +using FFXIVClassic_Map_Server.actors.area; +using FFXIVClassic_Map_Server.packets.send; +using FFXIVClassic_Map_Server.packets.send.actor; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using System.Collections.ObjectModel; +using FFXIVClassic_Map_Server.utils; + +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + class StatusEffectContainer + { + private Character owner; + private readonly Dictionary effects; + public static readonly int MAX_EFFECTS = 20; + private bool sendUpdate = false; + private DateTime lastTick;// Do all effects tick at the same time like regen? + private List statusSubpackets; + private ActorPropertyPacketUtil statusTimerPropPacketUtil; + + public StatusEffectContainer(Character owner) + { + this.owner = owner; + this.effects = new Dictionary(); + statusSubpackets = new List(); + statusTimerPropPacketUtil = new ActorPropertyPacketUtil("charawork/Status", owner); + } + + public void Update(DateTime tick) + { + CommandResultContainer resultContainer = new CommandResultContainer(); + + //Regen/Refresh/Regain effects tick every 3 seconds + if ((DateTime.Now - lastTick).Seconds >= 3) + { + RegenTick(tick, resultContainer); + lastTick = DateTime.Now; + } + + // list of effects to remove + var removeEffects = new List(); + var effectsList = effects.Values; + for (int i = effectsList.Count - 1; i >= 0; i--) + { + // effect's update function returns true if effect has completed + if (effectsList.ElementAt(i).Update(tick, resultContainer)) + removeEffects.Add(effectsList.ElementAt(i)); + } + + // remove effects from this list + foreach (var effect in removeEffects) + { + RemoveStatusEffect(effect); + } + + resultContainer.CombineLists(); + + if (resultContainer.GetList().Count > 0) + { + owner.DoBattleAction(0, 0x7c000062, resultContainer.GetList()); + } + } + + //regen/refresh/regain + public void RegenTick(DateTime tick, CommandResultContainer resultContainer) + { + ushort dotTick = (ushort) owner.GetMod(Modifier.RegenDown); + ushort regenTick = (ushort) owner.GetMod(Modifier.Regen); + ushort refreshtick = (ushort) owner.GetMod(Modifier.Refresh); + short regainTick = (short) owner.GetMod(Modifier.Regain); + + //DoTs tick before regen and the full dot damage is displayed, even if some or all of it is nullified by regen. Only effects like stoneskin actually alter the number shown + if (dotTick > 0) + { + //Unsure why 10105 is the textId used + //Also unsure why magicshield is used + CommandResult action = new CommandResult(owner.actorId, 10105, (uint)(HitEffect.MagicEffectType | HitEffect.MagicShield | HitEffect.NoResist), dotTick); + utils.BattleUtils.HandleStoneskin(owner, action); + // todo: figure out how to make red numbers appear for enemies getting hurt by dots + resultContainer.AddAction(action); + owner.DelHP(action.amount, resultContainer); + } + + //DoTs are the only effect to show numbers, so that doesnt need to be handled for these + if (regenTick != 0) + owner.AddHP(regenTick); + + if (refreshtick != 0) + owner.AddMP(refreshtick); + + if (regainTick != 0) + owner.AddTP(regainTick); + } + + public bool HasStatusEffect(uint id) + { + return effects.ContainsKey(id); + } + + public bool HasStatusEffect(StatusEffectId id) + { + return effects.ContainsKey((uint)id); + } + + public bool AddStatusEffect(uint id, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328) + { + var se = Server.GetWorldManager().GetStatusEffect(id); + + return AddStatusEffect(se, owner, actionContainer, worldmasterTextId); + } + + public bool AddStatusEffect(uint id, byte tier, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328) + { + var se = Server.GetWorldManager().GetStatusEffect(id); + + se.SetTier(tier); + + return AddStatusEffect(se, owner, actionContainer, worldmasterTextId); + } + + public bool AddStatusEffect(uint id, byte tier, double magnitude, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328) + { + var se = Server.GetWorldManager().GetStatusEffect(id); + + se.SetMagnitude(magnitude); + se.SetTier(tier); + + return AddStatusEffect(se, owner, actionContainer, worldmasterTextId); + } + + public bool AddStatusEffect(uint id, byte tier, double magnitude, uint duration, int tickMs, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328) + { + var se = Server.GetWorldManager().GetStatusEffect(id); + if (se != null) + { + se.SetDuration(duration); + se.SetOwner(owner); + } + return AddStatusEffect(se ?? new StatusEffect(this.owner, id, magnitude, 3000, duration, tier), owner, actionContainer, worldmasterTextId); + } + + public bool AddStatusEffect(StatusEffect newEffect, Character source, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30328) + { + /* + worldMasterTextId + 32001 [@2B([@IF($E4($EB(1),$EB(2)),you,[@IF($E9(7),[@SHEETEN(xtx/displayName,2,$E9(7),1,1)],$EB(2))])])] [@IF($E4($EB(1),$EB(2)),resist,resists)] the effect of [@SHEET(xtx/status,$E8(11),3)]. + 32002 [@SHEET(xtx/status,$E8(11),3)] fails to take effect. + */ + + var effect = GetStatusEffectById(newEffect.GetStatusEffectId()); + + bool canOverwrite = false; + if (effect != null) + { + var overwritable = effect.GetOverwritable(); + canOverwrite = (overwritable == (uint)StatusEffectOverwrite.Always) || + (overwritable == (uint)StatusEffectOverwrite.GreaterOnly && (effect.GetDuration() < newEffect.GetDuration() || effect.GetMagnitude() < newEffect.GetMagnitude())) || + (overwritable == (uint)StatusEffectOverwrite.GreaterOrEqualTo && (effect.GetDuration() <= newEffect.GetDuration() || effect.GetMagnitude() <= newEffect.GetMagnitude())); + } + + if (canOverwrite || effect == null) + { + // send packet to client with effect added message + if (newEffect != null && !newEffect.GetSilentOnGain()) + { + if (actionContainer != null) + actionContainer.AddAction(new CommandResult(owner.actorId, worldmasterTextId, newEffect.GetStatusEffectId() | (uint)HitEffect.StatusEffectType)); + } + + // wont send a message about losing effect here + if (canOverwrite) + effects.Remove(newEffect.GetStatusEffectId()); + + newEffect.SetStartTime(DateTime.Now); + newEffect.SetEndTime(DateTime.Now.AddSeconds(newEffect.GetDuration())); + newEffect.SetOwner(owner); + + if (effects.Count < MAX_EFFECTS) + { + newEffect.CallLuaFunction(this.owner, "onGain", this.owner, newEffect, actionContainer); + + effects.Add(newEffect.GetStatusEffectId(), newEffect); + + if (!newEffect.GetHidden()) + { + int index = 0; + + //If effect is already in the list of statuses, get that index, otherwise find the first open index + if (owner.charaWork.status.Contains(newEffect.GetStatusId())) + index = Array.IndexOf(owner.charaWork.status, newEffect.GetStatusId()); + else + index = Array.IndexOf(owner.charaWork.status, (ushort) 0); + + SetStatusAtIndex(index, newEffect.GetStatusId()); + //Stance statuses need their time set to an extremely high number so their icon doesn't flash + //Adding getduration with them doesn't work because it overflows + uint time = (newEffect.GetFlags() & (uint) StatusEffectFlags.Stance) == 0 ? Utils.UnixTimeStampUTC(newEffect.GetEndTime()) : 0xFFFFFFFF; + SetTimeAtIndex(index, time); + } + owner.RecalculateStats(); + } + return true; + } + return false; + } + + //playEffect determines whether the effect of the animation that's going to play with actionContainer is going to play on owner + //Generally, for abilities removing an effect, this is true and for effects removing themselves it's false. + public bool RemoveStatusEffect(StatusEffect effect, CommandResultContainer actionContainer = null, ushort worldmasterTextId = 30331, bool playEffect = true) + { + bool removedEffect = false; + if (effect != null && effects.ContainsKey(effect.GetStatusEffectId())) + { + // send packet to client with effect remove message + if (!effect.GetSilentOnLoss()) + { + //Only send a message if we're using an actioncontainer and the effect normally sends a message when it's lost + if (actionContainer != null) + actionContainer.AddAction(new CommandResult(owner.actorId, worldmasterTextId, effect.GetStatusEffectId() | (playEffect ? 0 : (uint)HitEffect.StatusLossType))); + } + + //hidden effects not in charawork + var index = Array.IndexOf(owner.charaWork.status, effect.GetStatusId()); + if (!effect.GetHidden() && index != -1) + { + SetStatusAtIndex(index, 0); + SetTimeAtIndex(index, 0); + } + + // function onLose(actor, effect) + effects.Remove(effect.GetStatusEffectId()); + effect.CallLuaFunction(owner, "onLose", owner, effect, actionContainer); + owner.RecalculateStats(); + removedEffect = true; + } + + return removedEffect; + } + + public bool RemoveStatusEffect(uint effectId, CommandResultContainer resultContainer = null, ushort worldmasterTextId = 30331, bool playEffect = true) + { + return RemoveStatusEffect(GetStatusEffectById(effectId), resultContainer, worldmasterTextId, playEffect); + } + + public StatusEffect CopyEffect(StatusEffect effect) + { + var newEffect = new StatusEffect(owner, effect); + newEffect.SetOwner(owner); + // todo: should source be copied too? + return AddStatusEffect(newEffect, effect.GetSource()) ? newEffect : null; + } + + public bool RemoveStatusEffectsByFlags(uint flags, CommandResultContainer resultContainer = null) + { + // build list of effects to remove + var removeEffects = GetStatusEffectsByFlag(flags); + + // remove effects from main list + foreach (var effect in removeEffects) + RemoveStatusEffect(effect, resultContainer, effect.GetStatusLossTextId(), true); + + // removed an effect with one of these flags + return removeEffects.Count > 0; + } + + public StatusEffect GetStatusEffectById(uint id, byte tier = 0xFF) + { + StatusEffect effect; + + if (effects.TryGetValue(id, out effect) && effect.GetStatusEffectId() == id && (tier != 0xFF ? effect.GetTier() == tier : true)) + return effect; + + return null; + } + + public List GetStatusEffectsByFlag(uint flag) + { + var list = new List(); + foreach (var effect in effects.Values) + if ((effect.GetFlags() & flag) != 0) + list.Add(effect); + + return list; + } + + public StatusEffect GetRandomEffectByFlag(uint flag) + { + var list = GetStatusEffectsByFlag(flag); + + if (list.Count > 0) + return list[Program.Random.Next(list.Count)]; + + return null; + } + + // todo: why the fuck cant c# convert enums/ + public bool HasStatusEffectsByFlag(StatusEffectFlags flags) + { + return HasStatusEffectsByFlag((uint)flags); + } + + public bool HasStatusEffectsByFlag(uint flag) + { + foreach (var effect in effects.Values) + { + if ((effect.GetFlags() & flag) != 0) + return true; + } + return false; + } + + public IEnumerable GetStatusEffects() + { + return effects.Values; + } + + void SaveStatusEffectsToDatabase(StatusEffectFlags removeEffectFlags = StatusEffectFlags.None) + { + if (owner is Player) + { + Database.SavePlayerStatusEffects((Player)owner); + } + } + + public void CallLuaFunctionByFlag(uint flag, string function, params object[] args) + { + var effects = GetStatusEffectsByFlag(flag); + + object[] argsWithEffect = new object[args.Length + 1]; + + for (int i = 0; i < args.Length; i++) + argsWithEffect[i + 1] = args[i]; + + foreach (var effect in effects) + { + argsWithEffect[0] = effect; + effect.CallLuaFunction(owner, function, argsWithEffect); + } + } + + //Sets the status id at an index. + //Changing a status to another doesn't seem to work. If updating an index that already has an effect, set it to 0 first then to the correct status + public void SetStatusAtIndex(int index, ushort statusId) + { + owner.charaWork.status[index] = statusId; + + statusSubpackets.Add(SetActorStatusPacket.BuildPacket(owner.actorId, (ushort)index, statusId)); + owner.updateFlags |= ActorUpdateFlags.Status; + } + + public void SetTimeAtIndex(int index, uint time) + { + owner.charaWork.statusShownTime[index] = time; + statusTimerPropPacketUtil.AddProperty($"charaWork.statusShownTime[{index}]"); + owner.updateFlags |= ActorUpdateFlags.StatusTime; + } + + public List GetStatusPackets() + { + return statusSubpackets; + } + + public List GetStatusTimerPackets() + { + return statusTimerPropPacketUtil.Done(); + } + + public void ResetPropPacketUtil() + { + statusTimerPropPacketUtil = new ActorPropertyPacketUtil("charaWork/status", owner); + } + + //Overwrites effectToBeReplaced with a new status effect + //Returns the message of the new effect being added + //Doing this instead of simply calling remove then add so that the new effect is in the same slot as the old one + //There should be a better way to do this + //Currently causes the icons to blink whenb eing rpelaced + public CommandResult ReplaceEffect(StatusEffect effectToBeReplaced, uint newEffectId, byte tier, double magnitude, uint duration) + { + StatusEffect newEffect = Server.GetWorldManager().GetStatusEffect(newEffectId); + newEffect.SetTier(tier); + newEffect.SetMagnitude(magnitude); + newEffect.SetDuration(duration); + newEffect.SetOwner(effectToBeReplaced.GetOwner()); + effectToBeReplaced.CallLuaFunction(owner, "onLose", owner, effectToBeReplaced); + newEffect.CallLuaFunction(owner, "onGain", owner, newEffect); + effects.Remove(effectToBeReplaced.GetStatusEffectId()); + + newEffect.SetStartTime(DateTime.Now); + newEffect.SetEndTime(DateTime.Now.AddSeconds(newEffect.GetDuration())); + uint time = (newEffect.GetFlags() & (uint)StatusEffectFlags.Stance) == 0 ? Utils.UnixTimeStampUTC(newEffect.GetEndTime()) : 0xFFFFFFFF; + int index = Array.IndexOf(owner.charaWork.status, effectToBeReplaced.GetStatusId()); + + //owner.charaWork.status[index] = newEffect.GetStatusId(); + owner.charaWork.statusShownTime[index] = time; + effects[newEffectId] = newEffect; + + SetStatusAtIndex(index, 0); + + //charawork/status + SetStatusAtIndex(index, (ushort) (newEffectId - 200000)); + SetTimeAtIndex(index, time); + + return new CommandResult(owner.actorId, 30330, (uint) HitEffect.StatusEffectType | newEffectId); + } + } +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/AllyController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/AllyController.cs new file mode 100644 index 00000000..4098e2bb --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/AllyController.cs @@ -0,0 +1,81 @@ +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.actors.chara.npc; +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers +{ + // todo: this is probably not needed, can do everything in their script + class AllyController : BattleNpcController + { + protected new Ally owner; + public AllyController(Ally owner) : + base(owner) + { + this.owner = owner; + } + + protected List GetContentGroupCharas() + { + List contentGroupCharas = null; + + if (owner.currentContentGroup != null) + { + contentGroupCharas = new List(owner.currentContentGroup.GetMemberCount()); + foreach (var charaId in owner.currentContentGroup.GetMembers()) + { + var chara = owner.zone.FindActorInArea(charaId); + + if (chara != null) + contentGroupCharas.Add(chara); + } + } + + return contentGroupCharas; + } + + //Iterate over players in the group and if they are fighting, assist them + protected override void TryAggro(DateTime tick) + { + //lua.LuaEngine.CallLuaBattleFunction(owner, "tryAggro", owner, GetContentGroupCharas()); + + foreach(Character chara in GetContentGroupCharas()) + { + if(chara.IsPlayer()) + { + if(owner.aiContainer.GetTargetFind().CanTarget((Character) chara.target) && chara.target is BattleNpc && ((BattleNpc)chara.target).hateContainer.HasHateForTarget(chara)) + { + owner.Engage(chara.target.actorId); + owner.hateContainer.AddBaseHate((Character) chara.target); + break; + } + } + } + //base.TryAggro(tick); + } + + // server really likes to hang whenever scripts iterate area's actorlist + protected override void DoCombatTick(DateTime tick, List contentGroupCharas = null) + { + if (contentGroupCharas == null) + { + contentGroupCharas = GetContentGroupCharas(); + } + + base.DoCombatTick(tick, contentGroupCharas); + } + + protected override void DoRoamTick(DateTime tick, List contentGroupCharas = null) + { + if (contentGroupCharas == null) + { + contentGroupCharas = GetContentGroupCharas(); + } + base.DoRoamTick(tick, contentGroupCharas); + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs new file mode 100644 index 00000000..7ca4df4a --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/BattleNpcController.cs @@ -0,0 +1,409 @@ +using System; +using System.Collections.Generic; +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.packets.send.actor; +using FFXIVClassic_Map_Server.actors.area; +using FFXIVClassic_Map_Server.utils; +using FFXIVClassic_Map_Server.actors.chara.ai.state; +using FFXIVClassic_Map_Server.actors.chara.npc; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers +{ + class BattleNpcController : Controller + { + protected DateTime lastActionTime; + protected DateTime lastSpellCastTime; + protected DateTime lastSkillTime; + protected DateTime lastSpecialSkillTime; // todo: i dont think monsters have "2hr" cooldowns like ffxi + protected DateTime deaggroTime; + protected DateTime neutralTime; + protected DateTime waitTime; + + private bool firstSpell = true; + protected DateTime lastRoamUpdate; + protected DateTime battleStartTime; + + protected new BattleNpc owner; + public BattleNpcController(BattleNpc owner) : + base(owner) + { + this.owner = owner; + this.lastUpdate = DateTime.Now; + this.waitTime = lastUpdate.AddSeconds(5); + } + + public override void Update(DateTime tick) + { + lastUpdate = tick; + if (!owner.IsDead()) + { + // todo: handle aggro/deaggro and other shit here + if (!owner.aiContainer.IsEngaged()) + { + TryAggro(tick); + } + + if (owner.aiContainer.IsEngaged()) + { + DoCombatTick(tick); + } + //Only move if owner isn't dead and is either too far away from their spawn point or is meant to roam and isn't in combat + else if (!owner.IsDead() && (owner.isMovingToSpawn || owner.GetMobMod((uint)MobModifier.Roams) > 0)) + { + DoRoamTick(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; + } + + //If the owner isn't moving to spawn, iterate over nearby enemies and + //aggro the first one that is within 10 levels and can be detected, then engage + protected virtual void TryAggro(DateTime tick) + { + if (tick >= neutralTime && !owner.isMovingToSpawn) + { + if (!owner.neutral && owner.IsAlive()) + { + foreach (var chara in owner.zone.GetActorsAroundActor(owner, 50)) + { + if (chara.allegiance == owner.allegiance) + continue; + + if (owner.aiContainer.pathFind.AtPoint() && owner.detectionType != DetectionType.None) + { + uint levelDifference = (uint)Math.Abs(owner.GetLevel() - chara.GetLevel()); + + if (levelDifference <= 10 || (owner.detectionType & DetectionType.IgnoreLevelDifference) != 0 && CanAggroTarget(chara)) + { + owner.hateContainer.AddBaseHate(chara); + break; + } + } + } + } + } + + if (owner.hateContainer.GetHateList().Count > 0) + { + Engage(owner.hateContainer.GetMostHatedTarget()); + } + } + + public override bool Engage(Character target) + { + var canEngage = this.owner.aiContainer.InternalEngage(target); + if (canEngage) + { + //owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); + + // reset casting + firstSpell = true; + // todo: find a better place to put this? + if (owner.GetState() != SetActorStatePacket.MAIN_STATE_ACTIVE) + owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); + + lastActionTime = DateTime.Now; + battleStartTime = lastActionTime; + // todo: adjust cooldowns with modifiers + } + return canEngage; + } + + protected bool TryEngage(Character target) + { + // todo: + return true; + } + + public override void Disengage() + { + var target = owner.target; + base.Disengage(); + // todo: + lastActionTime = lastUpdate.AddSeconds(5); + owner.isMovingToSpawn = true; + owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None); + owner.aiContainer.pathFind.PreparePath(owner.spawnX, owner.spawnY, owner.spawnZ, 1.5f, 10); + neutralTime = lastActionTime; + owner.hateContainer.ClearHate(); + lua.LuaEngine.CallLuaBattleFunction(owner, "onDisengage", owner, target, Utils.UnixTimeStampUTC(lastUpdate)); + } + + public override void Cast(Character target, uint spellId) + { + // todo: + if(owner.aiContainer.CanChangeState()) + owner.aiContainer.InternalCast(target, spellId); + } + + public override void Ability(Character target, uint abilityId) + { + // todo: + if (owner.aiContainer.CanChangeState()) + owner.aiContainer.InternalAbility(target, abilityId); + } + + public override void RangedAttack(Character target) + { + // todo: + } + + public override void MonsterSkill(Character target, uint mobSkillId) + { + // todo: + } + + protected virtual void DoRoamTick(DateTime tick, List contentGroupCharas = null) + { + if (tick >= waitTime) + { + neutralTime = tick.AddSeconds(5); + if (owner.aiContainer.pathFind.IsFollowingPath()) + { + owner.aiContainer.pathFind.FollowPath(); + lastActionTime = tick.AddSeconds(-5); + } + else + { + if (tick >= lastActionTime) + { + + } + } + waitTime = tick.AddSeconds(owner.GetMobMod((uint) MobModifier.RoamDelay)); + owner.OnRoam(tick); + + if (CanMoveForward(0.0f) && !owner.aiContainer.pathFind.IsFollowingPath()) + { + // will move on next tick + owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None); + owner.aiContainer.pathFind.PathInRange(owner.spawnX, owner.spawnY, owner.spawnZ, 1.5f, 50.0f); + } + //lua.LuaEngine.CallLuaBattleFunction(owner, "onRoam", owner, contentGroupCharas); + } + + if (owner.aiContainer.pathFind.IsFollowingPath() && owner.aiContainer.CanFollowPath()) + { + owner.aiContainer.pathFind.FollowPath(); + } + } + + protected virtual void DoCombatTick(DateTime tick, List contentGroupCharas = null) + { + HandleHate(); + // todo: magic/attack/ws cooldowns etc + if (TryDeaggro()) + { + Disengage(); + return; + } + owner.SetMod((uint)Modifier.MovementSpeed, 5); + if ((tick - lastCombatTickScript).TotalSeconds > 3) + { + Move(); + //if (owner.aiContainer.CanChangeState()) + //owner.aiContainer.WeaponSkill(owner.zone.FindActorInArea(owner.target.actorId), 27155); + //lua.LuaEngine.CallLuaBattleFunction(owner, "onCombatTick", owner, owner.target, Utils.UnixTimeStampUTC(tick), contentGroupCharas); + lastCombatTickScript = tick; + } + } + + protected virtual void Move() + { + if (!owner.aiContainer.CanFollowPath()) + { + return; + } + + if (owner.aiContainer.pathFind.IsFollowingScriptedPath()) + { + owner.aiContainer.pathFind.FollowPath(); + return; + } + + var vecToTarget = owner.target.GetPosAsVector3() - owner.GetPosAsVector3(); + vecToTarget /= vecToTarget.Length(); + vecToTarget = (Utils.Distance(owner.GetPosAsVector3(), owner.target.GetPosAsVector3()) - owner.GetAttackRange() + 0.2f) * vecToTarget; + + var targetPos = vecToTarget + owner.GetPosAsVector3(); + var distance = Utils.Distance(owner.GetPosAsVector3(), owner.target.GetPosAsVector3()); + if (distance > owner.GetAttackRange() - 0.2f) + { + if (CanMoveForward(distance)) + { + if (!owner.aiContainer.pathFind.IsFollowingPath() && distance > 3) + { + // pathfind if too far otherwise jump to target + owner.aiContainer.pathFind.SetPathFlags(PathFindFlags.None); + owner.aiContainer.pathFind.PreparePath(targetPos, 1.5f, 5); + } + owner.aiContainer.pathFind.FollowPath(); + if (!owner.aiContainer.pathFind.IsFollowingPath()) + { + if (owner.target is Player) + { + foreach (var chara in owner.zone.GetActorsAroundActor(owner, 1)) + { + if (chara == owner) + continue; + + float mobDistance = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, chara.positionX, chara.positionY, chara.positionZ); + if (mobDistance < 0.50f && (chara.updateFlags & ActorUpdateFlags.Position) == 0) + { + owner.aiContainer.pathFind.PathInRange(targetPos, 1.3f, chara.GetAttackRange()); + break; + } + } + } + FaceTarget(); + } + } + } + else + { + FaceTarget(); + } + lastRoamUpdate = DateTime.Now; + } + + protected void FaceTarget() + { + // todo: check if stunned etc + if (owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventTurn) ) + { + } + else + { + owner.LookAt(owner.target); + } + } + + protected bool CanMoveForward(float distance) + { + // todo: check spawn leash and stuff + if (!owner.IsCloseToSpawn()) + { + return false; + } + if (owner.GetSpeed() == 0) + { + return false; + } + return true; + } + + public virtual bool CanAggroTarget(Character target) + { + if (owner.neutral || owner.detectionType == DetectionType.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 virtual bool CanDetectTarget(Character target, bool forceSight = false) + { + if (owner.IsDead()) + return false; + + // todo: this should probably be changed to only allow detection at end of path? + if (owner.aiContainer.pathFind.IsFollowingScriptedPath() || owner.aiContainer.pathFind.IsFollowingPath() && !owner.aiContainer.pathFind.AtPoint()) + { + return 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.detectionType & DetectionType.Sight) != 0; + bool hasSneak = false; + bool hasInvisible = false; + bool isFacing = owner.IsFacing(target); + + // use the mobmod sight range before defaulting to 20 yalms + if (detectSight && !hasInvisible && isFacing && distance < owner.GetMobMod((uint)MobModifier.SightRange)) + return CanSeePoint(target.positionX, target.positionY, target.positionZ); + + // todo: check line of sight and aggroTypes + if (distance > 20) + { + return false; + } + + // todo: seems ffxiv doesnt even differentiate between sneak/invis? + { + hasSneak = target.GetMod(Modifier.Stealth) > 0; + hasInvisible = hasSneak; + } + + + + if ((owner.detectionType & DetectionType.Sound) != 0 && !hasSneak && distance < owner.GetMobMod((uint)MobModifier.SoundRange)) + return CanSeePoint(target.positionX, target.positionY, target.positionZ); + + if ((owner.detectionType & DetectionType.Magic) != 0 && target.aiContainer.IsCurrentState()) + return CanSeePoint(target.positionX, target.positionY, target.positionZ); + + if ((owner.detectionType & DetectionType.LowHp) != 0 && target.GetHPP() < 75) + return CanSeePoint(target.positionX, target.positionY, target.positionZ); + + return false; + } + + public virtual bool CanSeePoint(float x, float y, float z) + { + return NavmeshUtils.CanSee((Zone)owner.zone, owner.positionX, owner.positionY, owner.positionZ, x, y, z); + } + + protected virtual void HandleHate() + { + ChangeTarget(owner.hateContainer.GetMostHatedTarget()); + } + + public override void ChangeTarget(Character target) + { + if (target != owner.target) + { + owner.target = target; + owner.currentLockedTarget = target?.actorId ?? Actor.INVALID_ACTORID; + owner.currentTarget = target?.actorId ?? Actor.INVALID_ACTORID; + + foreach (var player in owner.zone.GetActorsAroundActor(owner, 50)) + player.QueuePacket(owner.GetHateTypePacket(player)); + + base.ChangeTarget(target); + } + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/Controller.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/Controller.cs new file mode 100644 index 00000000..ab194953 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/Controller.cs @@ -0,0 +1,80 @@ +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.controllers +{ + abstract class Controller + { + protected Character owner; + + protected DateTime lastCombatTickScript; + protected DateTime lastUpdate; + public bool canUpdate = true; + protected bool autoAttackEnabled = true; + protected bool castingEnabled = true; + protected bool weaponSkillEnabled = true; + protected PathFind pathFind; + protected TargetFind targetFind; + + public Controller(Character owner) + { + this.owner = owner; + } + + public abstract void Update(DateTime tick); + public abstract bool Engage(Character target); + public abstract void Cast(Character target, uint spellId); + public virtual void WeaponSkill(Character target, uint weaponSkillId) { } + public virtual void MonsterSkill(Character target, uint mobSkillId) { } + public virtual void UseItem(Character target, uint slot, uint itemId) { } + public abstract void Ability(Character target, uint abilityId); + public abstract void RangedAttack(Character target); + public virtual void Spawn() { } + public virtual void Despawn() { } + + + public virtual void Disengage() + { + owner.aiContainer.InternalDisengage(); + } + + public virtual void ChangeTarget(Character target) + { + owner.aiContainer.InternalChangeTarget(target); + } + + public bool IsAutoAttackEnabled() + { + return autoAttackEnabled; + } + + public void SetAutoAttackEnabled(bool isEnabled) + { + autoAttackEnabled = isEnabled; + } + + public bool IsCastingEnabled() + { + return castingEnabled; + } + + public void SetCastingEnabled(bool isEnabled) + { + castingEnabled = isEnabled; + } + + public bool IsWeaponSkillEnabled() + { + return weaponSkillEnabled; + } + + public void SetWeaponSkillEnabled(bool isEnabled) + { + weaponSkillEnabled = isEnabled; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/PetController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/PetController.cs new file mode 100644 index 00000000..bfcef0ac --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/PetController.cs @@ -0,0 +1,72 @@ +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.controllers +{ + class PetController : Controller + { + private Character petMaster; + + public PetController(Character owner) : + base(owner) + { + this.lastUpdate = Program.Tick; + } + + public override void Update(DateTime tick) + { + // todo: handle pet stuff on tick + } + + public override void ChangeTarget(Character target) + { + base.ChangeTarget(target); + } + + public override bool Engage(Character target) + { + // todo: check distance, last swing time, status effects + return true; + } + + public override void Disengage() + { + // todo: + return; + } + + public override void Cast(Character target, uint spellId) + { + + } + + public override void Ability(Character target, uint abilityId) + { + + } + + public override void RangedAttack(Character target) + { + + } + + public Character GetPetMaster() + { + return petMaster; + } + + public void SetPetMaster(Character master) + { + petMaster = master; + + if (master is Player) + owner.allegiance = CharacterTargetingAllegiance.Player; + else + owner.allegiance = CharacterTargetingAllegiance.BattleNpcs; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs b/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs new file mode 100644 index 00000000..1505d453 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/controllers/PlayerController.cs @@ -0,0 +1,93 @@ +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; +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.controllers +{ + class PlayerController : Controller + { + private new Player owner; + public PlayerController(Player owner) : + base(owner) + { + this.owner = owner; + this.lastUpdate = DateTime.Now; + } + + public override void Update(DateTime tick) + { + /* + if (owner.newMainState != owner.currentMainState) + { + if (owner.newMainState == SetActorStatePacket.MAIN_STATE_ACTIVE) + { + owner.Engage(); + } + else + { + owner.Disengage(); + } + owner.currentMainState = (ushort)owner.newMainState; + }*/ + } + + public override void ChangeTarget(Character target) + { + owner.target = target; + base.ChangeTarget(target); + } + + public override bool Engage(Character target) + { + var canEngage = this.owner.aiContainer.InternalEngage(target); + if (canEngage) + { + if (owner.statusEffects.HasStatusEffect(StatusEffectId.Sleep)) + { + // That command cannot be performed. + owner.SendGameMessage(Server.GetWorldManager().GetActor(), 32553, 0x20); + return false; + } + // todo: adjust cooldowns with modifiers + } + return canEngage; + } + + public override void Disengage() + { + // todo: + base.Disengage(); + return; + } + + public override void Cast(Character target, uint spellId) + { + owner.aiContainer.InternalCast(target, spellId); + } + + public override void WeaponSkill(Character target, uint weaponSkillId) + { + owner.aiContainer.InternalWeaponSkill(target, weaponSkillId); + } + + public override void Ability(Character target, uint abilityId) + { + owner.aiContainer.InternalAbility(target, abilityId); + } + + public override void RangedAttack(Character target) + { + + } + + public override void UseItem(Character target, uint slot, uint itemId) + { + owner.aiContainer.InternalUseItem(target, slot, itemId); + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/helpers/ActionQueue.cs b/FFXIVClassic Map Server/actors/chara/ai/helpers/ActionQueue.cs new file mode 100644 index 00000000..87112724 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/helpers/ActionQueue.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FFXIVClassic_Map_Server.Actors; +using MoonSharp; +using MoonSharp.Interpreter; +using FFXIVClassic_Map_Server.lua; + +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + class Action + { + public DateTime startTime; + public uint durationMs; + public bool checkState; + // todo: lua function + LuaScript script; + } + + class ActionQueue + { + private Character owner; + private Queue actionQueue; + private Queue timerQueue; + + public bool IsEmpty { get { return actionQueue.Count > 0 || timerQueue.Count > 0; } } + + public ActionQueue(Character owner) + { + this.owner = owner; + actionQueue = new Queue(); + timerQueue = new Queue(); + } + + public void PushAction(Action action) + { + + } + + public void Update(DateTime tick) + { + + } + + public void HandleAction(Action action) + { + + } + + public void CheckAction(DateTime tick) + { + + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/helpers/PathFind.cs b/FFXIVClassic Map Server/actors/chara/ai/helpers/PathFind.cs new file mode 100644 index 00000000..399bb9b5 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/helpers/PathFind.cs @@ -0,0 +1,211 @@ +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; +using FFXIVClassic_Map_Server.utils; +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.area; +using FFXIVClassic_Map_Server.packets.send.actor; + +// port of https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai/helpers/pathfind.h + +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + // todo: check for obstacles, los, etc + public enum PathFindFlags + { + None, + Scripted = 0x01, + IgnoreNav = 0x02, + } + class PathFind + { + private Character owner; + private List path; + private bool canFollowPath; + float distanceFromPoint; + + private PathFindFlags pathFlags; + + public PathFind(Character owner) + { + this.owner = owner; + } + + public void PreparePath(Vector3 dest, float stepSize = 1.25f, int maxPath = 40, float polyRadius = 0.0f) + { + PreparePath(dest.X, dest.Y, dest.Z, stepSize, maxPath, polyRadius); + } + + public void PreparePath(float x, float y, float z, float stepSize = 1.25f, int maxPath = 40, float polyRadius = 0.0f) + { + var pos = new Vector3(owner.positionX, owner.positionY, owner.positionZ); + var dest = new Vector3(x, y, z); + + Zone zone; + if (owner.GetZone() is PrivateArea || owner.GetZone() is PrivateAreaContent) + zone = (Zone)((PrivateArea)owner.GetZone()).GetParentZone(); + else + zone = (Zone)owner.GetZone(); + + var sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + + if ((pathFlags & PathFindFlags.IgnoreNav) != 0) + path = new List(1) { new Vector3(x, y, z) }; + else + path = NavmeshUtils.GetPath(zone, pos, dest, stepSize, maxPath, polyRadius); + + if (path != null) + { + if (owner.oldPositionX == 0.0f && owner.oldPositionY == 0.0f && owner.oldPositionZ == 0.0f) + { + owner.oldPositionX = owner.positionX; + owner.oldPositionY = owner.positionY; + owner.oldPositionZ = owner.positionZ; + } + + // todo: something went wrong + if (path.Count == 0) + { + owner.positionX = owner.oldPositionX; + owner.positionY = owner.oldPositionY; + owner.positionZ = owner.oldPositionZ; + } + + sw.Stop(); + zone.pathCalls++; + zone.pathCallTime += sw.ElapsedMilliseconds; + + //if (path.Count == 1) + // Program.Log.Info($"mypos: {owner.positionX} {owner.positionY} {owner.positionZ} | targetPos: {x} {y} {z} | step {stepSize} | maxPath {maxPath} | polyRadius {polyRadius}"); + + //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); + // todo: this is dumb.. + distanceFromPoint = owner.GetAttackRange(); + 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]; + + StepTo(point); + + if (AtPoint(point)) + { + path.Remove(point); + owner.OnPath(point); + //Program.Log.Error($"{owner.actorName} arrived at point {point.X} {point.Y} {point.Z}"); + } + + if (path.Count == 0 && owner.target != null) + owner.LookAt(owner.target); + } + } + + public bool AtPoint(Vector3 point = null) + { + if (point == null && path != null && path.Count > 0) + { + point = path[path.Count - 1]; + } + else + { + distanceFromPoint = 0; + return true; + } + + if (distanceFromPoint == 0) + return owner.positionX == point.X && owner.positionZ == point.Z; + else + return Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, point.X, point.Y, point.Z) <= (distanceFromPoint + 4.5f); + } + + public void StepTo(Vector3 point, bool run = false) + { + float speed = GetSpeed(); + + float stepDistance = speed / 3; + float distanceTo = Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, point.X, point.Y, point.Z); + + owner.LookAt(point); + + if (distanceTo <= distanceFromPoint + stepDistance) + { + if (distanceFromPoint <= owner.GetAttackRange()) + { + owner.QueuePositionUpdate(point); + } + else + { + float x = owner.positionX - (float)Math.Cos(owner.rotation + (float)(Math.PI / 2)) * (distanceTo - distanceFromPoint); + float z = owner.positionZ + (float)Math.Sin(owner.rotation + (float)(Math.PI / 2)) * (distanceTo - distanceFromPoint); + + owner.QueuePositionUpdate(x, owner.positionY, z); + } + } + else + { + float x = owner.positionX - (float)Math.Cos(owner.rotation + (float)(Math.PI / 2)) * (distanceTo - distanceFromPoint); + float z = owner.positionZ + (float)Math.Sin(owner.rotation + (float)(Math.PI / 2)) * (distanceTo - distanceFromPoint); + + owner.QueuePositionUpdate(x, owner.positionY, z); + } + } + + public void Clear() + { + path?.Clear(); + pathFlags = PathFindFlags.None; + distanceFromPoint = 0.0f; + } + + private float GetSpeed() + { + float baseSpeed = owner.GetSpeed(); + + if (!(owner is Player)) + { + if (owner is BattleNpc) + { + //owner.ChangeSpeed(0.0f, SetActorSpeedPacket.DEFAULT_WALK - 2.0f, SetActorSpeedPacket.DEFAULT_RUN - 2.0f, SetActorSpeedPacket.DEFAULT_ACTIVE - 2.0f); + } + // baseSpeed += ConfigConstants.NPC_SPEED_MOD; + } + return baseSpeed; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs b/FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs new file mode 100644 index 00000000..d2c94e00 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs @@ -0,0 +1,492 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara.ai.controllers; +using FFXIVClassic_Map_Server.actors.group; +using FFXIVClassic_Map_Server.packets.send.actor; + +// port of dsp's ai code https://github.com/DarkstarProject/darkstar/blob/master/src/map/ai/ + +namespace FFXIVClassic_Map_Server.actors.chara.ai +{ + [Flags] + public enum ValidTarget : ushort + { + None = 0x00, + Self = 0x01, //Can be used on self (if this flag isn't set and target is self, return false) + SelfOnly = 0x02, //Must be used on self (if this flag is set and target isn't self, return false) + Party = 0x4, //Can be used on party members + PartyOnly = 0x8, //Must be used on party members + Ally = 0x10, //Can be used on allies + AllyOnly = 0x20, //Must be used on allies + NPC = 0x40, //Can be used on static NPCs + NPCOnly = 0x80, //Must be used on static NPCs + Enemy = 0x100, //Can be used on enemies + EnemyOnly = 0x200, //Must be used on enemies + Object = 0x400, //Can be used on objects + ObjectOnly = 0x800, //Must be used on objects + Corpse = 0x1000, //Can be used on corpses + CorpseOnly = 0x2000, //Must be used on corpses + + //These are only used for ValidTarget, not MainTarget + MainTargetParty = 0x4000, //Can be used on main target's party (This will basically always be true.) + MainTargetPartyOnly = 0x8000, //Must be used on main target's party (This is for Protect basically.) + } + + /// Targeting from/to different entity types + enum TargetFindCharacterType : byte + { + None, + /// Player can target all s in party + PlayerToPlayer, + /// Player can target all s (excluding player owned s) + PlayerToBattleNpc, + /// BattleNpc can target other s + BattleNpcToBattleNpc, + /// BattleNpc can target s and their s + BattleNpcToPlayer, + } + + /// Type of AOE region to create + enum TargetFindAOEType : byte + { + None, + /// Really a cylinder, uses maxDistance parameter in SetAOEType + Circle, + /// Create a cone with param in radians + Cone, + /// Box using self/target coords and + Box + } + + /// Set AOE around self or target + enum TargetFindAOETarget : byte + { + /// Set AOE's origin at target's position + Target, + /// Set AOE's origin to own position. + Self + } + + /// Target finding helper class + class TargetFind + { + private Character owner; + private Character mainTarget; //This is the target that the skill is being used on + private Character masterTarget; //If mainTarget is a pet, this is the owner + private TargetFindCharacterType findType; + private ValidTarget validTarget; + private TargetFindAOETarget aoeTarget; + private TargetFindAOEType aoeType; + private Vector3 aoeTargetPosition; //This is the center of circle of cone AOEs and the position where line aoes come out. If we have mainTarget this might not be needed? + private float aoeTargetRotation; //This is the direction the aoe target is facing + private float maxDistance; //Radius for circle and cone AOEs, length for line AOEs + private float minDistance; //Minimum distance to that target must be to be able to be hit + private float width; //Width of line AOEs + private float height; //All AoEs are boxes or cylinders. Height is usually 10y regardless of maxDistance, but some commands have different values. Height is total height, so targets can be at most half this distance away on Y axis + private float aoeRotateAngle; //This is the angle that cones and line aoes are rotated about aoeTargetPosition for skills that come out of a side other than the front + private float coneAngle; //The angle of the cone itself in Pi Radians + private float param; + private List targets; + + public TargetFind(Character owner, Character mainTarget = null) + { + Reset(); + this.owner = owner; + this.mainTarget = mainTarget == null ? owner : mainTarget; + } + + public void Reset() + { + this.mainTarget = owner; + this.findType = TargetFindCharacterType.None; + this.validTarget = ValidTarget.Enemy; + this.aoeType = TargetFindAOEType.None; + this.aoeTarget = TargetFindAOETarget.Target; + this.aoeTargetPosition = null; + this.aoeTargetRotation = 0; + this.maxDistance = 0.0f; + this.minDistance = 0.0f; + this.width = 0.0f; + this.height = 0.0f; + this.aoeRotateAngle = 0.0f; + this.coneAngle = 0.0f; + this.param = 0.0f; + this.targets = new List(); + } + + public List GetTargets() where T : Character + { + return new List(targets.OfType()); + } + + public List GetTargets() + { + return targets; + } + + /// + /// Call this before + /// + /// + /// - radius of circle + /// - height of cone + /// - width of box / 2 (todo: set box length not just between user and target) + /// + /// param in degrees of cone (todo: probably use radians and forget converting at runtime) + public void SetAOEType(ValidTarget validTarget, TargetFindAOEType aoeType, TargetFindAOETarget aoeTarget, float maxDistance, float minDistance, float height, float aoeRotate, float coneAngle, float param = 0.0f) + { + this.validTarget = validTarget; + this.aoeType = aoeType; + this.maxDistance = maxDistance; + this.minDistance = minDistance; + this.param = param; + this.height = height; + this.aoeRotateAngle = aoeRotate; + this.coneAngle = coneAngle; + } + + /// + /// Call this to prepare Box AOE + /// + /// + /// + /// + /// + public void SetAOEBox(ValidTarget validTarget, TargetFindAOETarget aoeTarget, float length, float width, float aoeRotateAngle) + { + this.validTarget = validTarget; + this.aoeType = TargetFindAOEType.Box; + this.aoeTarget = aoeTarget; + this.aoeRotateAngle = aoeRotateAngle; + float x = owner.positionX - (float)Math.Cos(owner.rotation + (float)(Math.PI / 2)) * (length); + float z = owner.positionZ + (float)Math.Sin(owner.rotation + (float)(Math.PI / 2)) * (length); + this.maxDistance = length; + this.width = width; + } + + /// + /// Find and try to add a single target to target list + /// + public void FindTarget(Character target, ValidTarget flags) + { + validTarget = flags; + // todo: maybe this should only be set if successfully added? + AddTarget(target, false); + } + + /// + /// Call SetAOEType before calling this + /// Find targets within area set by + /// + public void FindWithinArea(Character target, ValidTarget flags, TargetFindAOETarget aoeTarget) + { + targets.Clear(); + validTarget = flags; + // are we creating aoe circles around target or self + if (aoeTarget == TargetFindAOETarget.Self) + { + this.aoeTargetPosition = owner.GetPosAsVector3(); + this.aoeTargetRotation = owner.rotation + (float) (aoeRotateAngle * Math.PI); + } + else + { + this.aoeTargetPosition = target.GetPosAsVector3(); + this.aoeTargetRotation = target.rotation + (float) (aoeRotateAngle * Math.PI); + } + + masterTarget = TryGetMasterTarget(target) ?? target; + + // todo: this is stupid + bool withPet = (flags & ValidTarget.Ally) != 0 || masterTarget.allegiance != owner.allegiance; + + if (masterTarget != null && CanTarget(masterTarget)) + targets.Add(masterTarget); + + if (aoeType != TargetFindAOEType.None) + { + AddAllInRange(target, withPet); + } + + //if (targets.Count > 8) + //targets.RemoveRange(8, targets.Count - 8); + } + + /// + /// Find targets within a box using owner's coordinates and target's coordinates as length + /// with corners being `maxDistance` yalms to either side of self and target + /// + private bool IsWithinBox(Character target, bool withPet) + { + Vector3 vec = target.GetPosAsVector3() - aoeTargetPosition; + Vector3 relativePos = new Vector3(); + + //Get target's position relative to owner's position where owner's front is facing positive z axis + relativePos.X = (float)(vec.X * Math.Cos(aoeTargetRotation) - vec.Z * Math.Sin(aoeTargetRotation)); + relativePos.Z = (float)(vec.X * Math.Sin(aoeTargetRotation) + vec.Z * Math.Cos(aoeTargetRotation)); + + float halfHeight = height / 2; + float halfWidth = width / 2; + + Vector3 closeBottomLeft = new Vector3(-halfWidth, -halfHeight, minDistance); + Vector3 farTopRight = new Vector3(halfWidth, halfHeight, maxDistance); + + return relativePos.IsWithinBox(closeBottomLeft, farTopRight); + } + + private bool IsWithinCone(Character target, bool withPet) + { + double distance = Utils.XZDistance(aoeTargetPosition, target.GetPosAsVector3()); + + //Make sure target is within the correct range first + if (!IsWithinCircle(target)) + return false; + + //This might not be 100% right or the most optimal way to do this + //Get between taget's position and our position + return target.GetPosAsVector3().IsWithinCone(aoeTargetPosition, aoeTargetRotation, coneAngle); + } + + private void AddTarget(Character target, bool withPet) + { + if (CanTarget(target)) + { + // todo: add pets too + targets.Add(target); + } + } + + private void AddAllInParty(Character target, bool withPet) + { + var party = target.currentParty as Party; + if (party != null) + { + foreach (var actorId in party.members) + { + AddTarget(owner.zone.FindActorInArea(actorId), withPet); + } + } + } + + private void AddAllInAlliance(Character target, bool withPet) + { + // todo: + AddAllInParty(target, withPet); + } + + private void AddAllBattleNpcs(Character target, bool withPet) + { + int dist = (int)maxDistance; + var actors = owner.zone.GetActorsAroundActor(target, dist); + + foreach (BattleNpc actor in actors) + { + AddTarget(actor, false); + } + } + + private void AddAllInZone(Character target, bool withPet) + { + var actors = owner.zone.GetAllActors(); + foreach (Character actor in actors) + { + AddTarget(actor, withPet); + } + } + + private void AddAllInRange(Character target, bool withPet) + { + int dist = (int)maxDistance; + var actors = owner.zone.GetActorsAroundActor(target, dist); + + foreach (Character actor in actors) + { + AddTarget(actor, false); + } + + } + + private void AddAllInHateList() + { + if (!(owner is BattleNpc)) + { + Program.Log.Error($"TargetFind.AddAllInHateList() owner [{owner.actorId}] {owner.customDisplayName} {owner.actorName} is not a BattleNpc"); + } + else + { + foreach (var hateEntry in ((BattleNpc)owner).hateContainer.GetHateList()) + { + AddTarget(hateEntry.Value.actor, false); + } + } + } + + public bool CanTarget(Character target, bool withPet = false, bool retarget = false, bool ignoreAOE = false) + { + // already targeted, dont target again + if (target == null || !retarget && targets.Contains(target)) + return false; + + if (target == null) + return false; + + //This skill can't be used on a corpse and target is dead + if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead()) + return false; + + //This skill must be used on a corpse and target is alive + if ((validTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive()) + return false; + + //This skill can't be used on self and target is self + if ((validTarget & ValidTarget.Self) == 0 && target == owner) + return false; + + //This skill must be used on self and target isn't self + if ((validTarget & ValidTarget.SelfOnly) != 0 && target != owner) + return false; + + //This skill can't be used on an ally and target is an ally + if ((validTarget & ValidTarget.Ally) == 0 && target.allegiance == owner.allegiance) + return false; + + //This skill must be used on an ally and target is not an ally + if ((validTarget & ValidTarget.AllyOnly) != 0 && target.allegiance != owner.allegiance) + return false; + + //This skill can't be used on an enemu and target is an enemy + if ((validTarget & ValidTarget.Enemy) == 0 && target.allegiance != owner.allegiance) + return false; + + //This skill must be used on an enemy and target is an ally + if ((validTarget & ValidTarget.EnemyOnly) != 0 && target.allegiance == owner.allegiance) + return false; + + //This skill can't be used on party members and target is a party member + if ((validTarget & ValidTarget.Party) == 0 && target.currentParty == owner.currentParty) + return false; + + //This skill must be used on party members and target is not a party member + if ((validTarget & ValidTarget.PartyOnly) != 0 && target.currentParty != owner.currentParty) + return false; + + //This skill can't be used on NPCs and target is an npc + if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic) + return false; + + //This skill must be used on NPCs and target is not an npc + if ((validTarget & ValidTarget.NPCOnly) != 0 && !target.isStatic) + return false; + + // todo: why is player always zoning? + // cant target if zoning + if (target is Player && ((Player)target).playerSession.isUpdatesLocked) + { + owner.aiContainer.ChangeTarget(null); + return false; + } + + if (/*target.isZoning || owner.isZoning || */target.zone != owner.zone) + return false; + + if (validTarget == ValidTarget.Self && aoeType == TargetFindAOEType.None && owner != target) + return false; + + //This skill can't be used on main target's party members and target is a party member of main target + if ((validTarget & ValidTarget.MainTargetParty) == 0 && target.currentParty == mainTarget.currentParty) + return false; + + //This skill must be used on main target's party members and target is not a party member of main target + if ((validTarget & ValidTarget.MainTargetPartyOnly) != 0 && target.currentParty != mainTarget.currentParty) + return false; + + + // this is fuckin retarded, think of a better way l8r + if (!ignoreAOE) + { + // hit everything within zone or within aoe region + if (param == -1.0f || aoeType == TargetFindAOEType.Circle && !IsWithinCircle(target)) + return false; + + if (aoeType == TargetFindAOEType.Cone && !IsWithinCone(target, withPet)) + return false; + + if (aoeType == TargetFindAOEType.Box && !IsWithinBox(target, withPet)) + return false; + + if (aoeType == TargetFindAOEType.None && targets.Count != 0) + return false; + } + return true; + } + + private bool IsWithinCircle(Character target) + { + //Check if XZ is in circle and that y difference isn't larger than half height + return target.GetPosAsVector3().IsWithinCircle(aoeTargetPosition, maxDistance, minDistance) && Math.Abs(owner.positionY - target.positionY) <= (height / 2); + } + + private bool IsPlayer(Character target) + { + if (target is Player) + return true; + + // treat player owned pets as players too + return TryGetMasterTarget(target) is Player; + } + + private Character TryGetMasterTarget(Character target) + { + // if character is a player owned pet, treat as a player + if (target.aiContainer != null) + { + var controller = target.aiContainer.GetController(); + if (controller != null) + return controller.GetPetMaster(); + } + return null; + } + + private bool IsBattleNpcOwner(Character target) + { + // i know i copied this from dsp but what even + if (!(owner is Player) || target is Player) + return true; + + // todo: check hate list + if (owner is BattleNpc && ((BattleNpc)owner).hateContainer.GetMostHatedTarget() != target) + { + return false; + } + return false; + } + + public Character GetValidTarget(Character target, ValidTarget findFlags) + { + if (target == null || target is Player && ((Player)target).playerSession.isUpdatesLocked) + return null; + + if ((findFlags & ValidTarget.Ally) != 0) + { + return owner.pet; + } + + // todo: this is beyond retarded + var oldFlags = this.validTarget; + this.validTarget = findFlags; + if (CanTarget(target, false, true)) + { + this.validTarget = oldFlags; + return target; + } + this.validTarget = oldFlags; + + return null; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/AbilityState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/AbilityState.cs new file mode 100644 index 00000000..955fe22b --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/AbilityState.cs @@ -0,0 +1,154 @@ +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; +using FFXIVClassic_Map_Server.packets.send; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.state +{ + class AbilityState : State + { + + private BattleCommand skill; + + public AbilityState(Character owner, Character target, ushort skillId) : + base(owner, target) + { + this.startTime = DateTime.Now; + this.skill = Server.GetWorldManager().GetBattleCommand(skillId); + var returnCode = skill.CallLuaFunction(owner, "onAbilityPrepare", owner, target, skill); + + this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target; + + errorResult = new CommandResult(owner.actorId, 32553, 0); + if (returnCode == 0) + { + OnStart(); + } + else + { + errorResult = null; + interrupt = true; + } + } + + public override void OnStart() + { + var returnCode = skill.CallLuaFunction(owner, "onAbilityStart", owner, target, skill); + + if (returnCode != 0) + { + interrupt = true; + errorResult = new CommandResult(owner.actorId, (ushort)(returnCode == -1 ? 32558 : returnCode), 0); + } + else + { + if (!skill.IsInstantCast()) + { + float castTime = skill.castTimeMs; + + // command casting duration + if (owner is Player) + { + // todo: modify spellSpeed based on modifiers and stuff + ((Player)owner).SendStartCastbar(skill.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(castTime))); + } + owner.GetSubState().chantId = 0xf0; + owner.SubstateModified(); + //You ready [skill] (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD) + owner.DoBattleAction(skill.id, (uint)0x6F000000 | skill.castType, new CommandResult(target.actorId, 30126, 1, 0, 1)); + } + } + } + + public override bool Update(DateTime tick) + { + if (skill != null) + { + TryInterrupt(); + + if (interrupt) + { + OnInterrupt(); + return true; + } + + // todo: check weapon delay/haste etc and use that + var actualCastTime = skill.castTimeMs; + + if ((tick - startTime).TotalMilliseconds >= skill.castTimeMs) + { + OnComplete(); + return true; + } + return false; + } + return true; + } + + public override void OnInterrupt() + { + // todo: send paralyzed/sleep message etc. + if (errorResult != null) + { + owner.DoBattleAction(skill.id, errorResult.animation, errorResult); + errorResult = null; + } + } + + public override void OnComplete() + { + owner.LookAt(target); + bool hitTarget = false; + + skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget); + isCompleted = true; + + owner.DoBattleCommand(skill, "ability"); + } + + public override void TryInterrupt() + { + if (interrupt) + return; + + if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAbility)) + { + // todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack + var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAbility); + uint effectId = 0; + if (list.Count > 0) + { + // todo: actually check proc rate/random chance of whatever effect + effectId = list[0].GetStatusEffectId(); + } + interrupt = true; + return; + } + + interrupt = !CanUse(); + } + + private bool CanUse() + { + return skill.IsValidMainTarget(owner, target); + } + + public BattleCommand GetWeaponSkill() + { + return skill; + } + + public override void Cleanup() + { + owner.GetSubState().chantId = 0x0; + owner.SubstateModified(); + owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds); + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs new file mode 100644 index 00000000..febcdcdc --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs @@ -0,0 +1,215 @@ +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 AttackState : State + { + private DateTime attackTime; + + public AttackState(Character owner, Character target) : + base(owner, target) + { + this.canInterrupt = false; + this.startTime = DateTime.Now; + + owner.ChangeState(SetActorStatePacket.MAIN_STATE_ACTIVE); + ChangeTarget(target); + attackTime = startTime; + owner.aiContainer.pathFind?.Clear(); + } + + public override void OnStart() + { + + } + + public override bool Update(DateTime tick) + { + if ((target == null || owner.target != target || owner.target?.actorId != owner.currentLockedTarget) && owner.isAutoAttackEnabled) + owner.aiContainer.ChangeTarget(target = owner.zone.FindActorInArea(owner.currentTarget)); + + if (target == null || target.IsDead()) + { + if (owner.IsMonster() || owner.IsAlly()) + target = ((BattleNpc)owner).hateContainer.GetMostHatedTarget(); + } + else + { + if (IsAttackReady()) + { + if (CanAttack()) + { + TryInterrupt(); + + // todo: check weapon delay/haste etc and use that + if (!interrupt) + { + OnComplete(); + } + else + { + + } + SetInterrupted(false); + } + else + { + // todo: handle interrupt/paralyze etc + } + attackTime = DateTime.Now.AddMilliseconds(owner.GetAttackDelayMs()); + } + } + return false; + } + + public override void OnInterrupt() + { + // todo: send paralyzed/sleep message etc. + if (errorResult != null) + { + owner.zone.BroadcastPacketAroundActor(owner, CommandResultX01Packet.BuildPacket(errorResult.targetId, errorResult.animation, 0x765D, errorResult)); + errorResult = null; + } + } + + public override void OnComplete() + { + //BattleAction action = new BattleAction(target.actorId, 0x765D, (uint) HitEffect.Hit, 0, (byte) HitDirection.None); + errorResult = null; + + // todo: implement auto attack damage bonus in Character.OnAttack + /* + ≪Auto-attack Damage Bonus≫ + Class Bonus 1 Bonus 2 + Pugilist Intelligence Strength + Gladiator Mind Strength + Marauder Vitality Strength + Archer Dexterity Piety + Lancer Piety Strength + Conjurer Mind Piety + Thaumaturge Mind Piety + * The above damage bonus also applies to “Shot” attacks by archers. + */ + // handle paralyze/intimidate/sleep/whatever in Character.OnAttack + + + // todo: Change this to use a BattleCommand like the other states + + //List actions = new List(); + CommandResultContainer actions = new CommandResultContainer(); + + //This is all temporary until the skill sheet is finishd and the different auto attacks are added to the database + //Some mobs have multiple unique auto attacks that they switch between as well as ranged auto attacks, so we'll need a way to handle that + //For now, just use a temporary hardcoded BattleCommand that's the same for everyone. + BattleCommand attackCommand = new BattleCommand(22104, "Attack"); + attackCommand.range = 5; + attackCommand.rangeHeight = 10; + attackCommand.worldMasterTextId = 0x765D; + attackCommand.mainTarget = (ValidTarget)768; + attackCommand.validTarget = (ValidTarget)17152; + attackCommand.commandType = CommandType.AutoAttack; + attackCommand.numHits = (byte)owner.GetMod(Modifier.HitCount); + attackCommand.basePotency = 100; + ActionProperty property = (owner.GetMod(Modifier.AttackType) != 0) ? (ActionProperty)owner.GetMod(Modifier.AttackType) : ActionProperty.Slashing; + attackCommand.actionProperty = property; + attackCommand.actionType = ActionType.Physical; + + uint anim = (17 << 24 | 1 << 12); + + if (owner is Player) + anim = (25 << 24 | 1 << 12); + + attackCommand.battleAnimation = anim; + + if (owner.CanUse(target, attackCommand)) + { + attackCommand.targetFind.FindWithinArea(target, attackCommand.validTarget, attackCommand.aoeTarget); + owner.DoBattleCommand(attackCommand, "autoattack"); + } + } + + public override void TryInterrupt() + { + if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack)) + { + // todo: sometimes paralyze can let you attack, calculate proc rate + var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventAttack); + uint statusId = 0; + if (list.Count > 0) + { + statusId = list[0].GetStatusId(); + } + interrupt = true; + return; + } + + interrupt = !CanAttack(); + } + + private bool IsAttackReady() + { + // todo: this enforced delay should really be changed if it's not retail.. + return Program.Tick >= attackTime && Program.Tick >= owner.aiContainer.GetLastActionTime(); + } + + private bool CanAttack() + { + if (!owner.isAutoAttackEnabled || target.allegiance == owner.allegiance) + { + return false; + } + + if (target == null) + { + return false; + } + + if (!owner.IsFacing(target)) + { + return false; + } + // todo: shouldnt need to check if owner is dead since all states would be cleared + if (owner.IsDead() || target.IsDead()) + { + if (owner.IsMonster() || owner.IsAlly()) + ((BattleNpc)owner).hateContainer.ClearHate(target); + + owner.aiContainer.ChangeTarget(null); + return false; + } + else if (!owner.IsValidTarget(target, ValidTarget.Enemy) || !owner.aiContainer.GetTargetFind().CanTarget(target, false, true)) + { + return false; + } + // todo: use a mod for melee range + else if (Utils.Distance(owner.positionX, owner.positionY, owner.positionZ, target.positionX, target.positionY, target.positionZ) > owner.GetAttackRange()) + { + if (owner is Player) + { + //The target is too far away + ((Player)owner).SendGameMessage(Server.GetWorldManager().GetActor(), 32537, 0x20); + } + return false; + } + return true; + } + + public override void Cleanup() + { + if (owner.IsDead()) + owner.Disengage(); + } + + public override bool CanChangeState() + { + return true; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/DeathState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/DeathState.cs new file mode 100644 index 00000000..a2d33ae3 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/DeathState.cs @@ -0,0 +1,46 @@ +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.Disengage(); + owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD); + owner.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnDeath); + //var deathStatePacket = SetActorStatePacket.BuildPacket(owner.actorId, SetActorStatePacket.MAIN_STATE_DEAD2, owner.currentSubState); + //owner.zone.BroadcastPacketAroundActor(owner, deathStatePacket); + canInterrupt = false; + startTime = tick; + despawnTime = startTime.AddSeconds(timeToFadeOut); + } + + public override bool Update(DateTime tick) + { + // todo: set a flag on chara for accept raise, play animation and spawn + if (owner.GetMod((uint)Modifier.Raise) > 0) + { + owner.Spawn(tick); + return true; + } + + // todo: handle raise etc + if (tick >= despawnTime) + { + // todo: for players, return them to homepoint + owner.Despawn(tick); + return true; + } + return false; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/DespawnState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/DespawnState.cs new file mode 100644 index 00000000..4ba940b2 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/DespawnState.cs @@ -0,0 +1,34 @@ +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 DespawnState : State + { + private DateTime respawnTime; + public DespawnState(Character owner, uint respawnTimeSeconds) : + base(owner, null) + { + startTime = Program.Tick; + respawnTime = startTime.AddSeconds(respawnTimeSeconds); + owner.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD2); + owner.OnDespawn(); + } + + public override bool Update(DateTime tick) + { + if (tick >= respawnTime) + { + owner.ResetTempVars(); + owner.Spawn(tick); + return true; + } + return false; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/InactiveState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/InactiveState.cs new file mode 100644 index 00000000..735d7765 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/InactiveState.cs @@ -0,0 +1,42 @@ +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.state +{ + class InactiveState : State + { + private DateTime endTime; + private uint durationMs; + public InactiveState(Character owner, uint durationMs, bool canChangeState) : + base(owner, null) + { + if (!canChangeState) + owner.aiContainer.InterruptStates(); + this.durationMs = durationMs; + endTime = DateTime.Now.AddMilliseconds(durationMs); + } + + public override bool Update(DateTime tick) + { + if (durationMs == 0) + { + if (owner.IsDead()) + return true; + + if (!owner.statusEffects.HasStatusEffectsByFlag(StatusEffectFlags.PreventMovement)) + return true; + } + + if (durationMs != 0 && tick > endTime) + { + return true; + } + + return false; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/ItemState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/ItemState.cs new file mode 100644 index 00000000..dad3d6b8 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/ItemState.cs @@ -0,0 +1,23 @@ +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.actors.chara.player; +using FFXIVClassic_Map_Server.dataobjects; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.state +{ + class ItemState : State + { + ItemData item; + new Player owner; + public ItemState(Player owner, Character target, ushort slot, uint itemId) : + base(owner, target) + { + this.owner = owner; + this.target = target; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs new file mode 100644 index 00000000..2162dc9c --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs @@ -0,0 +1,202 @@ +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; +using FFXIVClassic_Map_Server.packets.send; +using FFXIVClassic_Map_Server.utils; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.state +{ + class MagicState : State + { + + private BattleCommand spell; + private Vector3 startPos; + + public MagicState(Character owner, Character target, ushort spellId) : + base(owner, target) + { + this.startPos = owner.GetPosAsVector3(); + this.startTime = DateTime.Now; + this.spell = Server.GetWorldManager().GetBattleCommand(spellId); + var returnCode = spell.CallLuaFunction(owner, "onMagicPrepare", owner, target, spell); + + //Modify spell based on status effects. Need to do it here because they can modify cast times + List effects = owner.statusEffects.GetStatusEffectsByFlag((uint)(StatusEffectFlags.ActivateOnCastStart)); + + //modify skill based on status effects + //Do this here to allow buffs like Resonance to increase range before checking CanCast() + foreach (var effect in effects) + lua.LuaEngine.CallLuaStatusEffectFunction(owner, effect, "onMagicCast", owner, effect, spell); + + this.target = (spell.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target; + + errorResult = new CommandResult(owner.actorId, 32553, 0); + if (returnCode == 0 && owner.CanUse(this.target, spell, errorResult)) + { + OnStart(); + } + else + { + interrupt = true; + } + } + + public override void OnStart() + { + var returnCode = spell.CallLuaFunction(owner, "onMagicStart", owner, target, spell); + + if (returnCode != 0) + { + interrupt = true; + errorResult = new CommandResult(target.actorId, (ushort)(returnCode == -1 ? 32553 : returnCode), 0, 0, 0, 1); + } + else + { + // todo: check within attack range + float[] baseCastDuration = { 1.0f, 0.25f }; + + //There are no positional spells, so just check onCombo, need to check first because certain spells change aoe type/accuracy + //If owner is a player and the spell being used is part of the current combo + if (owner is Player && ((Player)owner).GetClass() == spell.job) + { + Player p = (Player)owner; + if (spell.comboStep == 1 || ((p.playerWork.comboNextCommandId[0] == spell.id || p.playerWork.comboNextCommandId[1] == spell.id))) + { + spell.CallLuaFunction(owner, "onCombo", owner, target, spell); + spell.isCombo = true; + } + } + + //Check combo stuff here because combos can impact spell cast times + + float spellSpeed = spell.castTimeMs; + + if (!spell.IsInstantCast()) + { + // command casting duration + if (owner is Player) + { + // todo: modify spellSpeed based on modifiers and stuff + ((Player)owner).SendStartCastbar(spell.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(spellSpeed))); + } + owner.GetSubState().chantId = 0xf0; + owner.SubstateModified(); + owner.DoBattleAction(spell.id, (uint) 0x6F000000 | spell.castType, new CommandResult(target.actorId, 30128, 1, 0, 1)); //You begin casting (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD) + } + } + } + + public override bool Update(DateTime tick) + { + if (spell != null) + { + TryInterrupt(); + + if (interrupt) + { + OnInterrupt(); + return true; + } + + // todo: check weapon delay/haste etc and use that + var actualCastTime = spell.castTimeMs; + + if ((tick - startTime).TotalMilliseconds >= spell.castTimeMs) + { + OnComplete(); + return true; + } + return false; + } + return true; + } + + public override void OnInterrupt() + { + // todo: send paralyzed/sleep message etc. + if (errorResult != null) + { + owner.GetSubState().chantId = 0x0; + owner.SubstateModified(); + owner.DoBattleAction(spell.id, errorResult.animation, errorResult); + errorResult = null; + } + } + + public override void OnComplete() + { + //How do combos/hitdirs work for aoe abilities or does that not matter for aoe? + HitDirection hitDir = owner.GetHitDirection(target); + bool hitTarget = false; + + spell.targetFind.FindWithinArea(target, spell.validTarget, spell.aoeTarget); + isCompleted = true; + var targets = spell.targetFind.GetTargets(); + + owner.DoBattleCommand(spell, "magic"); + } + + public override void TryInterrupt() + { + if (interrupt) + return; + + if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventSpell)) + { + // todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack + var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventSpell); + uint effectId = 0; + if (list.Count > 0) + { + // todo: actually check proc rate/random chance of whatever effect + effectId = list[0].GetStatusEffectId(); + } + interrupt = true; + return; + } + + if (HasMoved()) + { + errorResult = new CommandResult(owner.actorId, 30211, 0); + errorResult.animation = 0x7F000002; + interrupt = true; + return; + } + + interrupt = !CanCast(); + } + + private bool CanCast() + { + return owner.CanUse(target, spell); + } + + private bool HasMoved() + { + return (owner.GetPosAsVector3() != startPos); + } + + public override void Cleanup() + { + owner.GetSubState().chantId = 0x0; + owner.SubstateModified(); + + if (owner is Player) + { + ((Player)owner).SendEndCastbar(); + } + owner.aiContainer.UpdateLastActionTime(spell.animationDurationSeconds); + } + + public BattleCommand GetSpell() + { + return spell; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/State.cs b/FFXIVClassic Map Server/actors/chara/ai/state/State.cs new file mode 100644 index 00000000..cebd3227 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/State.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.packets.send.actor.battle; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.state +{ + class State + { + protected Character owner; + protected Character target; + + protected bool canInterrupt; + protected bool interrupt = false; + + protected DateTime startTime; + + protected CommandResult errorResult; + + protected bool isCompleted; + + public State(Character owner, Character target) + { + this.owner = owner; + this.target = target; + this.canInterrupt = true; + this.interrupt = false; + } + + public virtual bool Update(DateTime tick) { return true; } + public virtual void OnStart() { } + public virtual void OnInterrupt() { } + public virtual void OnComplete() { isCompleted = true; } + public virtual bool CanChangeState() { return false; } + public virtual void TryInterrupt() { } + + public virtual void Cleanup() { } + + public bool CanInterrupt() + { + return canInterrupt; + } + + public void SetInterrupted(bool interrupt) + { + this.interrupt = interrupt; + } + + public bool IsCompleted() + { + return isCompleted; + } + + public void ChangeTarget(Character target) + { + this.target = target; + } + + public Character GetTarget() + { + return target; + } + + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/state/WeaponSkillState.cs b/FFXIVClassic Map Server/actors/chara/ai/state/WeaponSkillState.cs new file mode 100644 index 00000000..3b0da761 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/state/WeaponSkillState.cs @@ -0,0 +1,184 @@ +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; +using FFXIVClassic_Map_Server.packets.send; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.state +{ + class WeaponSkillState : State + { + + private BattleCommand skill; + private HitDirection hitDirection; + public WeaponSkillState(Character owner, Character target, ushort skillId) : + base(owner, target) + { + this.startTime = DateTime.Now; + this.skill = Server.GetWorldManager().GetBattleCommand(skillId); + + var returnCode = skill.CallLuaFunction(owner, "onSkillPrepare", owner, target, skill); + + this.target = (skill.mainTarget & ValidTarget.SelfOnly) != 0 ? owner : target; + + errorResult = new CommandResult(owner.actorId, 32553, 0); + if (returnCode == 0 && owner.CanUse(this.target, skill, errorResult)) + { + OnStart(); + } + else + { + errorResult = null; + interrupt = true; + } + } + + public override void OnStart() + { + var returnCode = skill.CallLuaFunction(owner, "onSkillStart", owner, target, skill); + + if (returnCode != 0) + { + interrupt = true; + errorResult = new CommandResult(owner.actorId, (ushort)(returnCode == -1 ? 32558 : returnCode), 0); + } + else + { + hitDirection = owner.GetHitDirection(target); + + //Do positionals and combo effects first because these can influence accuracy and amount of targets/numhits, which influence the rest of the steps + //If there is no positon required or if the position bonus should be activated + if ((skill.positionBonus & utils.BattleUtils.ConvertHitDirToPosition(hitDirection)) == skill.positionBonus) + { + //If there is a position bonus + if (skill.positionBonus != BattleCommandPositionBonus.None) + skill.CallLuaFunction(owner, "weaponskill", "onPositional", owner, target, skill); + + //Combo stuff + if (owner is Player) + { + Player p = (Player)owner; + //If skill is part of owner's class/job, it can be used in a combo + if (skill.job == p.GetClass() || skill.job == p.GetCurrentClassOrJob()) + { + //If owner is a player and the skill being used is part of the current combo + if (p.playerWork.comboNextCommandId[0] == skill.id || p.playerWork.comboNextCommandId[1] == skill.id) + { + skill.CallLuaFunction(owner, "onCombo", owner, target, skill); + skill.isCombo = true; + } + //or if this just the start of a combo + else if (skill.comboStep == 1) + skill.isCombo = true; + } + } + + if (!skill.IsInstantCast()) + { + float castTime = skill.castTimeMs; + + // command casting duration + if (owner is Player) + { + // todo: modify spellSpeed based on modifiers and stuff + ((Player)owner).SendStartCastbar(skill.id, Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(castTime))); + } + owner.GetSubState().chantId = 0xf0; + owner.SubstateModified(); + //You ready [skill] (6F000002: BLM, 6F000003: WHM, 0x6F000008: BRD) + owner.DoBattleAction(skill.id, (uint)0x6F000000 | skill.castType, new CommandResult(target.actorId, 30126, 1, 0, 1)); + } + } + } + } + + public override bool Update(DateTime tick) + { + if (skill != null) + { + TryInterrupt(); + + if (interrupt) + { + OnInterrupt(); + return true; + } + + // todo: check weapon delay/haste etc and use that + var actualCastTime = skill.castTimeMs; + + if ((tick - startTime).TotalMilliseconds >= skill.castTimeMs) + { + OnComplete(); + return true; + } + return false; + } + return true; + } + + public override void OnInterrupt() + { + // todo: send paralyzed/sleep message etc. + if (errorResult != null) + { + owner.DoBattleAction(skill.id, errorResult.animation, errorResult); + errorResult = null; + } + } + + public override void OnComplete() + { + owner.LookAt(target); + skill.targetFind.FindWithinArea(target, skill.validTarget, skill.aoeTarget); + isCompleted = true; + + owner.DoBattleCommand(skill, "weaponskill"); + owner.statusEffects.RemoveStatusEffectsByFlags((uint) StatusEffectFlags.LoseOnAttacking); + + lua.LuaEngine.GetInstance().OnSignal("weaponskillUsed"); + } + + public override void TryInterrupt() + { + if (interrupt) + return; + + if (owner.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.PreventWeaponSkill)) + { + // todo: sometimes paralyze can let you attack, get random percentage of actually letting you attack + var list = owner.statusEffects.GetStatusEffectsByFlag((uint)StatusEffectFlags.PreventWeaponSkill); + uint effectId = 0; + if (list.Count > 0) + { + // todo: actually check proc rate/random chance of whatever effect + effectId = list[0].GetStatusEffectId(); + } + interrupt = true; + return; + } + + interrupt = !CanUse(); + } + + private bool CanUse() + { + return owner.CanUse(target, skill); + } + + public BattleCommand GetWeaponSkill() + { + return skill; + } + + public override void Cleanup() + { + owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds); + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/ai/utils/AttackUtils.cs b/FFXIVClassic Map Server/actors/chara/ai/utils/AttackUtils.cs new file mode 100644 index 00000000..cdd7865d --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/utils/AttackUtils.cs @@ -0,0 +1,24 @@ +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 AttackUtils + { + public static int CalculateDamage(Character attacker, Character defender) + { + int dmg = CalculateBaseDamage(attacker, defender); + + return dmg; + } + + public static int CalculateBaseDamage(Character attacker, Character defender) + { + // todo: actually calculate damage + return Program.Random.Next(10) * 10; + } + } +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/chara/ai/utils/BattleUtils.cs b/FFXIVClassic Map Server/actors/chara/ai/utils/BattleUtils.cs new file mode 100644 index 00000000..9fddd194 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/ai/utils/BattleUtils.cs @@ -0,0 +1,907 @@ +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; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.actors.chara.player; +using FFXIVClassic_Map_Server.actors.chara.npc; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.actors.chara.ai.utils +{ + static class BattleUtils + { + + public static Dictionary PhysicalHitTypeTextIds = new Dictionary() + { + { HitType.Miss, 30311 }, + { HitType.Evade, 30310 }, + { HitType.Parry, 30308 }, + { HitType.Block, 30306 }, + { HitType.Hit, 30301 }, + { HitType.Crit, 30302 } + }; + + public static Dictionary MagicalHitTypeTextIds = new Dictionary() + { + { HitType.SingleResist,30318 }, + { HitType.DoubleResist,30317 }, + { HitType.TripleResist, 30316 },//Triple Resists seem to use the same text ID as full resists + { HitType.FullResist,30316 }, + { HitType.Hit, 30319 }, + { HitType.Crit, 30392 } //Unsure why crit is separated from the rest of the ids + }; + + public static Dictionary MultiHitTypeTextIds = new Dictionary() + { + { HitType.Miss, 30449 }, //The attack misses. + { HitType.Evade, 0 }, //Evades were removed before multi hit skills got their own messages, so this doesnt exist + { HitType.Parry, 30448 }, //[Target] parries, taking x points of damage. + { HitType.Block, 30447 }, //[Target] blocks, taking x points of damage. + { HitType.Hit, 30443 }, //[Target] tales x points of damage + { HitType.Crit, 30444 } //Critical! [Target] takes x points of damage. + }; + + public static Dictionary HitTypeEffectsPhysical = new Dictionary() + { + { HitType.Miss, 0 }, + { HitType.Evade, HitEffect.Evade }, + { HitType.Parry, HitEffect.Parry }, + { HitType.Block, HitEffect.Block }, + { HitType.Hit, HitEffect.Hit }, + { HitType.Crit, HitEffect.Crit | HitEffect.CriticalHit } + }; + + //Magic attacks can't miss, be blocked, or parried. Resists are technically evades + public static Dictionary HitTypeEffectsMagical = new Dictionary() + { + { HitType.SingleResist, HitEffect.WeakResist }, + { HitType.DoubleResist, HitEffect.WeakResist }, + { HitType.TripleResist, HitEffect.WeakResist }, + { HitType.FullResist, HitEffect.FullResist }, + { HitType.Hit, HitEffect.NoResist }, + { HitType.Crit, HitEffect.Crit } + }; + + public static Dictionary KnockbackEffects = new Dictionary() + { + { KnockbackType.None, 0 }, + { KnockbackType.Level1, HitEffect.KnockbackLv1 }, + { KnockbackType.Level2, HitEffect.KnockbackLv2 }, + { KnockbackType.Level3, HitEffect.KnockbackLv3 }, + { KnockbackType.Level4, HitEffect.KnockbackLv4 }, + { KnockbackType.Level5, HitEffect.KnockbackLv5 }, + { KnockbackType.Clockwise1, HitEffect.KnockbackClockwiseLv1 }, + { KnockbackType.Clockwise2, HitEffect.KnockbackClockwiseLv2 }, + { KnockbackType.CounterClockwise1, HitEffect.KnockbackCounterClockwiseLv1 }, + { KnockbackType.CounterClockwise2, HitEffect.KnockbackCounterClockwiseLv2 }, + { KnockbackType.DrawIn, HitEffect.DrawIn } + }; + + public static Dictionary ClassExperienceTextIds = new Dictionary() + { + { 2, 33934 }, //Pugilist + { 3, 33935 }, //Gladiator + { 4, 33936 }, //Marauder + { 7, 33937 }, //Archer + { 8, 33938 }, //Lancer + { 10, 33939 }, //Sentinel, this doesn't exist anymore but it's still in the files so may as well put it here just in case + { 22, 33940 }, //Thaumaturge + { 23, 33941 }, //Conjurer + { 29, 33945 }, //Carpenter, for some reason there's a a few different messages between 33941 and 33945 + { 30, 33946 }, //Blacksmith + { 31, 33947 }, //Armorer + { 32, 33948 }, //Goldsmith + { 33, 33949 }, //Leatherworker + { 34, 33950 }, //Weaver + { 35, 33951 }, //Alchemist + { 36, 33952 }, //Culinarian + { 39, 33953 }, //Miner + { 40, 33954 }, //Botanist + { 41, 33955 } //Fisher + }; + + //Most of these numbers I'm fairly certain are correct. The repeated numbers at levels 23 and 48 I'm less sure about but they do match some weird spots in the EXP graph + + public static ushort[] BASEEXP = {150, 150, 150, 150, 150, 150, 150, 150, 150, 150, //Level <= 10 + 150, 150, 150, 150, 150, 150, 150, 150, 160, 170, //Level <= 20 + 180, 190, 190, 200, 210, 220, 230, 240, 250, 260, //Level <= 30 + 270, 280, 290, 300, 310, 320, 330, 340, 350, 360, //Level <= 40 + 370, 380, 380, 390, 400, 410, 420, 430, 430, 440}; //Level <= 50 + + public static bool TryAttack(Character attacker, Character defender, CommandResult action, ref CommandResult error) + { + // todo: get hit rate, hit count, set hit effect + //action.effectId |= (uint)(HitEffect.RecoilLv2 | HitEffect.Hit | HitEffect.HitVisual1); + return true; + } + + private static double CalculateDlvlModifier(short dlvl) + { + //this is just a really, really simplified version of the graph from http://kanican.livejournal.com/55915.html + //actual formula is definitely more complicated + //I'm going to assum these formulas are linear, and they're clamped so the modifier never goes below 0. + double modifier = 0; + + + if (dlvl >= 0) + modifier = (.35 * dlvl) + .225; + else + modifier = (.01 * dlvl) + .25; + + return modifier.Clamp(0, 1); + } + + //Damage calculations + //Calculate damage of action + //We could probably just do this when determining the action's hit type + public static void CalculatePhysicalDamageTaken(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + short dlvl = (short)(defender.GetLevel() - attacker.GetLevel()); + + // todo: physical resistances + + //dlvl, Defense, and Vitality all effect how much damage is taken after hittype takes effect + //player attacks cannot do more than 9999 damage. + //VIT is turned into Defense at a 3:2 ratio in calculatestats, so don't need to do that here + double damageTakenPercent = 1 - (defender.GetMod(Modifier.DamageTakenDown) / 100.0); + action.amount = (ushort)(action.amount - CalculateDlvlModifier(dlvl) * (defender.GetMod((uint)Modifier.Defense))).Clamp(0, 9999); + action.amount = (ushort)(action.amount * damageTakenPercent).Clamp(0, 9999); + } + + + public static void CalculateSpellDamageTaken(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + short dlvl = (short)(defender.GetLevel() - attacker.GetLevel()); + + // todo: elemental resistances + //Patch 1.19: + //Magic Defense has been abolished and no longer appears in equipment attributes. + //The effect of elemental attributes has been changed to that of reducing damage from element-based attacks. + + //http://kanican.livejournal.com/55370.html: + //elemental resistance stats are not actually related to resists (except for status effects), instead they impact damage taken + + + //dlvl, Defense, and Vitality all effect how much damage is taken after hittype takes effect + //player attacks cannot do more than 9999 damage. + double damageTakenPercent = 1 - (defender.GetMod(Modifier.DamageTakenDown) / 100.0); + action.amount = (ushort)(action.amount - CalculateDlvlModifier(dlvl) * (defender.GetMod((uint)Modifier.Defense) + 0.67 * defender.GetMod((uint)Modifier.Vitality))).Clamp(0, 9999); + action.amount = (ushort)(action.amount * damageTakenPercent).Clamp(0, 9999); + } + + + public static void CalculateBlockDamage(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + double percentBlocked; + + //Aegis boon forces a full block + if (defender.statusEffects.HasStatusEffect(StatusEffectId.AegisBoon)) + percentBlocked = 1.0; + else + { + //Is this a case where VIT gives Block? + percentBlocked = defender.GetMod((uint)Modifier.Block) * 0.002;//Every point of Block adds .2% to how much is blocked + percentBlocked += defender.GetMod((uint)Modifier.Vitality) * 0.001;//Every point of vitality adds .1% to how much is blocked + } + + action.amountMitigated = (ushort)(action.amount * percentBlocked); + action.amount = (ushort)(action.amount * (1.0 - percentBlocked)); + } + + //don't know exact crit bonus formula + public static void CalculateCritDamage(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + short dlvl = (short)(defender.GetLevel() - attacker.GetLevel()); + double bonus = (.04 * (dlvl * dlvl)) - 2 * dlvl; + bonus += 1.20; + double potencyModifier = (-.075 * dlvl) + 1.73; + + // + potency bonus + //bonus += attacker.GetMod((uint) Modifier.CriticalPotency) * potencyModifier; + // - Crit resilience + //bonus -= attacker.GetMod((uint)Modifier.CriticalResilience) * potencyModifier; + + //need to add something for bonus potency as a part of skill (ie thundara, which breaks the cap) + action.amount = (ushort)(action.amount * bonus.Clamp(1.15, 1.75));//min bonus of 115, max bonus of 175 + } + + public static void CalculateParryDamage(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + double percentParry = 0.75; + + action.amountMitigated = (ushort)(action.amount * (1 - percentParry)); + action.amount = (ushort)(action.amount * percentParry); + } + + //There are 3 or 4 tiers of resist that are flat 25% decreases in damage. + //It's possible we could just calculate the damage at the same time as we determine the hit type (the same goes for the rest of the hit types) + //Or we could have HitTypes for DoubleResist, TripleResist, and FullResist that get used here. + public static void CalculateResistDamage(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + //Every tier of resist is a 25% reduction in damage. ie SingleResist is 25% damage taken down, Double is 50% damage taken down, etc + double percentResist = 0.25 * (action.hitType - HitType.SingleResist + 1); + + action.amountMitigated = (ushort)(action.amount * (1 - percentResist)); + action.amount = (ushort)(action.amount * percentResist); + } + + //It's weird that stoneskin is handled in C# and all other buffs are in scripts right now + //But it's because stoneskin acts like both a preaction and postaction buff in that it falls off after damage is dealt but impacts how much damage is dealt + public static void HandleStoneskin(Character defender, CommandResult action) + { + var mitigation = Math.Min(action.amount, defender.GetMod(Modifier.Stoneskin)); + + action.amount = (ushort) (action.amount - mitigation).Clamp(0, 9999); + defender.SubtractMod((uint)Modifier.Stoneskin, mitigation); + } + + public static void DamageTarget(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer= null) + { + if (defender != null) + { + + //Bugfix, mobs that instantly died were insta disappearing due to lastAttacker == null. + if (defender is BattleNpc) + { + var bnpc = defender as BattleNpc; + if (bnpc.lastAttacker == null) + bnpc.lastAttacker = attacker; + } + + defender.DelHP((short)action.amount, actionContainer); + attacker.OnDamageDealt(defender, skill, action, actionContainer); + defender.OnDamageTaken(attacker, skill, action, actionContainer); + + // todo: other stuff too + if (defender is BattleNpc) + { + var bnpc = defender as BattleNpc; + if (!bnpc.hateContainer.HasHateForTarget(attacker)) + { + bnpc.hateContainer.AddBaseHate(attacker); + } + bnpc.hateContainer.UpdateHate(attacker, action.enmity); + bnpc.lastAttacker = attacker; + } + } + } + + public static void HealTarget(Character caster, Character target, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + if (target != null) + { + target.AddHP(action.amount, actionContainer); + + target.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, skill, action, actionContainer); + } + } + + + #region Rate Functions + + //How is accuracy actually calculated? + public static double GetHitRate(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + double hitRate = 80.0; + + //Add raw hit rate buffs, subtract raw evade buffs, take into account skill's accuracy modifier. + double hitBuff = attacker.GetMod(Modifier.RawHitRate); + double evadeBuff = defender.GetMod(Modifier.RawEvadeRate); + float modifier = skill != null ? skill.accuracyModifier : 0; + hitRate += (hitBuff + modifier).Clamp(0, 100.0); + hitRate -= evadeBuff; + return hitRate.Clamp(0, 100.0); + } + + //Whats the parry formula? + public static double GetParryRate(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + //Can't parry with shield, can't parry rear attacks + if (defender.GetMod((uint)Modifier.CanBlock) != 0 || action.param == (byte) HitDirection.Rear) + return 0; + + double parryRate = 10.0; + + parryRate += defender.GetMod(Modifier.Parry) * 0.1;//.1% rate for every point of Parry + + return parryRate + (defender.GetMod(Modifier.RawParryRate)); + } + + public static double GetCritRate(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + if (action.actionType == ActionType.Status) + return 0.0; + + //using 10.0 for now since gear isn't working + double critRate = 10.0;// 0.16 * attacker.GetMod((uint)Modifier.CritRating);//Crit rating adds .16% per point + + //Add additional crit rate from skill + //Should this be a raw percent or a flat crit raitng? the wording on skills/buffs isn't clear. + critRate += 0.16 * (skill != null ? skill.bonusCritRate : 0); + + return critRate + attacker.GetMod(Modifier.RawCritRate); + } + + //http://kanican.livejournal.com/55370.html + // todo: figure that out + public static double GetResistRate(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + // todo: add elemental stuff + //Can only resist spells? + if (action.commandType != CommandType.Spell && action.actionProperty <= ActionProperty.Projectile) + return 0.0; + + return 15.0 + defender.GetMod(Modifier.RawResistRate); + } + + //Block Rate follows 4 simple rules: + //(1) Every point in DEX gives +0.1% rate + //(2) Every point in "Block Rate" gives +0.2% rate + //(3) True block proc rate is capped at 75%. No clue on a possible floor. + //(4) The baseline rate is based on dLVL only(mob stats play no role). The baseline rate is summarized in this raw data sheet: https://imgbox.com/aasLyaJz + public static double GetBlockRate(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + //Shields are required to block and can't block from rear. + if (defender.GetMod((uint)Modifier.CanBlock) == 0 || action.param == (byte)HitDirection.Rear) + return 0; + + short dlvl = (short)(defender.GetLevel() - attacker.GetLevel()); + double blockRate = (2.5 * dlvl) + 5; // Base block rate + + //Is this one of those thing where DEX gives block rate and this would be taking DEX into account twice? + blockRate += defender.GetMod((uint)Modifier.Dexterity) * 0.1;// .1% for every dex + blockRate += defender.GetMod((uint)Modifier.BlockRate) * 0.2;// .2% for every block rate + + return Math.Min(blockRate, 25.0) + defender.GetMod((uint)Modifier.RawBlockRate); + } + + #endregion + + public static bool TryCrit(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + if ((Program.Random.NextDouble() * 100) <= action.critRate) + { + action.hitType = HitType.Crit; + CalculateCritDamage(attacker, defender, skill, action); + + if(skill != null) + skill.actionCrit = true; + + return true; + } + + return false; + } + + //This probably isn't totally correct but it's close enough for now. + //Full Resists seem to be calculated in a different way because the resist rates don't seem to line up with kanikan's testing (their tests didn't show any full resists) + //Non-spells with elemental damage can be resisted, it just doesnt say in the chat that they were. As far as I can tell, all mob-specific attacks are considered not to be spells + public static bool TryResist(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + //The rate degrades for each check. Meaning with 100% resist, the attack will always be resisted, but it won't necessarily be a triple or full resist + //Rates beyond 100 still increase the chance for higher resist tiers though + double rate = action.resistRate; + + int i = -1; + + while ((Program.Random.NextDouble() * 100) <= rate && i < 4) + { + rate /= 2; + i++; + } + + if (i != -1) + { + action.hitType = (HitType) ((int) HitType.SingleResist + i); + CalculateResistDamage(attacker, defender, skill, action); + return true; + } + + return false; + } + + public static bool TryBlock(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + if ((Program.Random.NextDouble() * 100) <= action.blockRate) + { + action.hitType = HitType.Block; + CalculateBlockDamage(attacker, defender, skill, action); + return true; + } + + return false; + } + + public static bool TryParry(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + if ((Program.Random.NextDouble() * 100) <= action.parryRate) + { + action.hitType = HitType.Parry; + CalculateParryDamage(attacker, defender, skill, action); + return true; + } + + return false; + } + + //TryMiss instead of tryHit because hits are the default and don't change damage + public static bool TryMiss(Character attacker, Character defender, BattleCommand skill, CommandResult action) + { + if ((Program.Random.NextDouble() * 100) >= GetHitRate(attacker, defender, skill, action)) + { + action.hitType = (ushort)HitType.Miss; + //On misses, the entire amount is considered mitigated + action.amountMitigated = action.amount; + action.amount = 0; + return true; + } + return false; + } + + /* + * Hit Effecthelpers. Different types of hit effects hits use some flags for different things, so they're split into physical, magical, heal, and status + */ + public static void DoAction(Character caster, Character target, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + switch (action.actionType) + { + case (ActionType.Physical): + FinishActionPhysical(caster, target, skill, action, actionContainer); + break; + case (ActionType.Magic): + FinishActionSpell(caster, target, skill, action, actionContainer); + break; + case (ActionType.Heal): + FinishActionHeal(caster, target, skill, action, actionContainer); + break; + case (ActionType.Status): + FinishActionStatus(caster, target, skill, action, actionContainer); + break; + default: + actionContainer.AddAction(action); + break; + } + } + + //Determine the hit type, set the hit effect, modify damage based on stoneskin and hit type, hit target + public static void FinishActionPhysical(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + //Figure out the hit type and change damage depending on hit type + if (!TryMiss(attacker, defender, skill, action)) + { + //Handle Stoneskin here because it seems like stoneskin mitigates damage done before taking into consideration crit/block/parry damage reductions. + //This is based on the fact that a 0 damage attack due to stoneskin will heal for 0 with Aegis Boon, meaning Aegis Boon didn't mitigate any damage + HandleStoneskin(defender, action); + + //Crits can't be blocked (is this true for Aegis Boon and Divine Veil?) or parried so they are checked first. + if (!TryCrit(attacker, defender, skill, action)) + //Block and parry order don't really matter because if you can block you can't parry and vice versa + if (!TryBlock(attacker, defender, skill, action)) + if(!TryParry(attacker, defender, skill, action)) + //Finally if it's none of these, the attack was a hit + action.hitType = HitType.Hit; + } + + //Actions have different text ids depending on whether they're a part of a multi-hit ws or not. + Dictionary textIds = PhysicalHitTypeTextIds; + + //If this is the first hit of a multi hit command, add the "You use [command] on [target]" action + //Needs to be done here because certain buff messages appear before it. + if (skill != null && skill.numHits > 1) + { + if (action.hitNum == 1) + actionContainer?.AddAction(new CommandResult(attacker.actorId, 30441, 0)); + + textIds = MultiHitTypeTextIds; + } + + //Set the correct textId + action.worldMasterTextId = textIds[action.hitType]; + + //Set the hit effect + SetHitEffectPhysical(attacker, defender, skill, action, actionContainer); + + //Modify damage based on defender's stats + CalculatePhysicalDamageTaken(attacker, defender, skill, action); + + actionContainer.AddAction(action); + action.enmity = (ushort) (action.enmity * (skill != null ? skill.enmityModifier : 1)); + + //Damage the target + DamageTarget(attacker, defender, skill, action, actionContainer); + } + + public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + //I'm assuming that like physical attacks stoneskin is taken into account before mitigation + HandleStoneskin(defender, action); + + //Determine the hit type of the action + //Spells don't seem to be able to miss, instead magic acc/eva is used for resists (which are generally called evades in game) + //Unlike blocks and parries, crits do not go through resists. + if (!TryResist(attacker, defender, skill, action)) + { + if (!TryCrit(attacker, defender, skill, action)) + action.hitType = HitType.Hit; + } + + //There are no multi-hit spells, so we don't need to take that into account + action.worldMasterTextId = MagicalHitTypeTextIds[action.hitType]; + + //Set the hit effect + SetHitEffectSpell(attacker, defender, skill, action); + + HandleStoneskin(defender, action); + + CalculateSpellDamageTaken(attacker, defender, skill, action); + + actionContainer.AddAction(action); + + DamageTarget(attacker, defender, skill, action, actionContainer); + } + + public static void FinishActionHeal(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + //Set the hit effect + SetHitEffectHeal(attacker, defender, skill, action); + + actionContainer.AddAction(action); + + HealTarget(attacker, defender, skill, action, actionContainer); + } + + public static void FinishActionStatus(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + //Set the hit effect + SetHitEffectStatus(attacker, defender, skill, action); + + TryStatus(attacker, defender, skill, action, actionContainer, false); + + actionContainer.AddAction(action); + } + + public static void SetHitEffectPhysical(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer) + { + var hitEffect = HitEffect.HitEffectType; + HitType hitType = action.hitType; + + //Don't know what recoil is actually based on, just guessing + //Crit is 2 and 3 together + if (hitType == HitType.Crit) + hitEffect |= HitEffect.CriticalHit; + else + { + //It's not clear what recoil level is based on for physical attacks + double percentDealt = (100.0 * (action.amount / defender.GetMaxHP())); + if (percentDealt > 5.0) + hitEffect |= HitEffect.RecoilLv2; + else if (percentDealt > 10) + hitEffect |= HitEffect.RecoilLv3; + } + + hitEffect |= HitTypeEffectsPhysical[hitType]; + + //For combos that land, add the combo effect + if (skill != null && skill.isCombo && action.ActionLanded() && !skill.comboEffectAdded) + { + hitEffect |= (HitEffect)(skill.comboStep << 15); + skill.comboEffectAdded = true; + } + + //if attack hit the target, take into account protective status effects + if (hitType >= HitType.Parry) + { + //Protect / Shell only show on physical/ magical attacks respectively. + if (defender.statusEffects.HasStatusEffect(StatusEffectId.Protect) || defender.statusEffects.HasStatusEffect(StatusEffectId.Protect2)) + if (action != null) + hitEffect |= HitEffect.Protect; + + if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin)) + if (action != null) + hitEffect |= HitEffect.Stoneskin; + } + + action.effectId = (uint)hitEffect; + } + + public static void SetHitEffectSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + var hitEffect = HitEffect.MagicEffectType; + HitType hitType = action.hitType; + + + hitEffect |= HitTypeEffectsMagical[hitType]; + + if (skill != null && skill.isCombo && !skill.comboEffectAdded) + { + hitEffect |= (HitEffect)(skill.comboStep << 15); + skill.comboEffectAdded = true; + } + + //if attack hit the target, take into account protective status effects + if (action.ActionLanded()) + { + //Protect / Shell only show on physical/ magical attacks respectively. + //The magic hit effect category only has a flag for shell (and another shield effect that seems unused) + //Even though traited protect gives magic defense, the shell effect doesn't play on attacks + //This also means stoneskin doesnt show, but it does reduce damage + if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell)) + if (action != null) + hitEffect |= HitEffect.MagicShell; + } + action.effectId = (uint)hitEffect; + } + + + public static void SetHitEffectHeal(Character caster, Character receiver, BattleCommand skill, CommandResult action) + { + var hitEffect = HitEffect.MagicEffectType | HitEffect.Heal; + //Heals use recoil levels in some way as well. Possibly for very low health clutch heals or based on percentage of current health healed (not max health). + // todo: figure recoil levels out for heals + hitEffect |= HitEffect.RecoilLv3; + //do heals crit? + + action.effectId = (uint)hitEffect; + } + + public static void SetHitEffectStatus(Character caster, Character receiver, BattleCommand skill, CommandResult action) + { + var hitEffect = (uint)HitEffect.StatusEffectType | skill.statusId; + action.effectId = hitEffect; + + action.hitType = HitType.Hit; + } + + public static uint CalculateSpellCost(Character caster, Character target, BattleCommand spell) + { + var scaledCost = spell.CalculateMpCost(caster); + + // todo: calculate cost for mob/player + if (caster is BattleNpc) + { + + } + else + { + + } + return scaledCost; + } + + + //IsAdditional is needed because additional actions may be required for some actions' effects + //For instance, Goring Blade's bleed effect requires another action so the first action can still show damage numbers + //Sentinel doesn't require an additional action because it doesn't need to show those numbers + //this is stupid + public static void TryStatus(Character caster, Character target, BattleCommand skill, CommandResult action, CommandResultContainer results, bool isAdditional = true) + { + double rand = Program.Random.NextDouble(); + + //Statuses only land for non-resisted attacks and attacks that hit + if (skill != null && skill.statusId != 0 && (action.ActionLanded()) && rand < skill.statusChance) + { + StatusEffect effect = Server.GetWorldManager().GetStatusEffect(skill.statusId); + //Because combos might change duration or tier + if (effect != null) + { + effect.SetDuration(skill.statusDuration); + effect.SetTier(skill.statusTier); + effect.SetMagnitude(skill.statusMagnitude); + effect.SetOwner(target); + effect.SetSource(caster); + + if (target.statusEffects.AddStatusEffect(effect, caster)) + { + //If we need an extra action to show the status text + if (isAdditional) + results.AddAction(target.actorId, 30328, skill.statusId | (uint) HitEffect.StatusEffectType); + } + else + action.worldMasterTextId = 32002;//Is this right? + } + else + { + //until all effects are scripted and added to db just doing this + if (target.statusEffects.AddStatusEffect(skill.statusId, skill.statusTier, skill.statusMagnitude, skill.statusDuration, 3000)) + { + //If we need an extra action to show the status text + if (isAdditional) + results.AddAction(target.actorId, 30328, skill.statusId | (uint) HitEffect.StatusEffectType); + } + else + action.worldMasterTextId = 32002;//Is this right? + } + } + } + + //Convert a HitDirection to a BattleCommandPositionBonus. Basically just combining left/right into flank + public static BattleCommandPositionBonus ConvertHitDirToPosition(HitDirection hitDir) + { + BattleCommandPositionBonus position = BattleCommandPositionBonus.None; + + switch (hitDir) + { + case (HitDirection.Front): + position = BattleCommandPositionBonus.Front; + break; + case (HitDirection.Right): + case (HitDirection.Left): + position = BattleCommandPositionBonus.Flank; + break; + case (HitDirection.Rear): + position = BattleCommandPositionBonus.Rear; + break; + } + return position; + } + + + #region experience helpers + //See 1.19 patch notes for exp info. + public static ushort GetBaseEXP(Player player, BattleNpc mob) + { + //The way EXP seems to work for most enemies is that it gets the lower character's level, gets the base exp for that level, then uses dlvl to modify that exp + //Less than -19 dlvl gives 0 exp and no message is sent. + //This equation doesn't seem to work for certain bosses or NMs. + //Some enemies might give less EXP? Unsure on this. It seems like there might have been a change in base exp amounts after 1.19 + + //Example: + //Level 50 in a party kills a level 45 enemy + //Base exp is 400, as that's the base EXP for level 45 + //That's multiplied by the dlvl modifier for -5, which is 0.5625, which gives 225 + //That's then multiplied by the party modifier, which seems to be 0.667 regardless of party size, which gives 150 + //150 is then modified by bonus experience from food, rested exp, links, and chains + + int dlvl = mob.GetLevel() - player.GetLevel(); + if (dlvl <= -20) + return 0; + + int baseLevel = Math.Min(player.GetLevel(), mob.GetLevel()); + ushort baseEXP = BASEEXP[baseLevel - 1]; + + double dlvlModifier = 1.0; + + //There's 2 functions depending on if the dlvl is positive or negative. + if (dlvl >= 0) + //I'm not sure if this caps out at some point. This is correct up to at least +9 dlvl though. + dlvlModifier += 0.2 * dlvl; + else + //0.1x + 0.0025x^2 + dlvlModifier += 0.1 * dlvl + 0.0025 * (dlvl * dlvl); + + //The party modifier isn't clear yet. It seems like it might just be 0.667 for any number of members in a group, but the 1.19 notes say it's variable + //There also seem to be some cases where it simply doesn't apply but it isn't obvious if that's correct or when it applies if it is correct + double partyModifier = player.currentParty.GetMemberCount() == 1 ? 1.0 : 0.667; + + baseEXP = (ushort) (baseEXP * dlvlModifier * partyModifier); + + return baseEXP; + } + + //Gets the EXP bonus when enemies link + public static byte GetLinkBonus(ushort linkCount) + { + byte bonus = 0; + + switch (linkCount) + { + case (0): + break; + case (1): + bonus = 25; + break; + case (2): + bonus = 50; + break; + case (3): + bonus = 75; + break; + case (4): + default: + bonus = 100; + break; + } + + return bonus; + } + + //Gets EXP chain bonus for Attacker fighting Defender + //Official text on EXP Chains: An EXP Chain occurs when players consecutively defeat enemies of equal or higher level than themselves within a specific amount of time. + //Assuming this means that there is no bonus for enemies below player's level and EXP chains are specific to the person, not party + public static byte GetChainBonus(ushort tier) + { + byte bonus = 0; + + switch (tier) + { + case (0): + break; + case (1): + bonus = 20; + break; + case (2): + bonus = 25; + break; + case (3): + bonus = 30; + break; + case (4): + bonus = 40; + break; + default: + bonus = 50; + break; + } + return bonus; + } + + public static byte GetChainTimeLimit(ushort tier) + { + byte timeLimit = 0; + + switch (tier) + { + case (0): + timeLimit = 100; + break; + case (1): + timeLimit = 80; + break; + case (2): + timeLimit = 60; + break; + case (3): + timeLimit = 20; + break; + default: + timeLimit = 10; + break; + } + + return timeLimit; + } + + //Calculates bonus EXP for Links and Chains + public static void AddBattleBonusEXP(Player attacker, BattleNpc defender, CommandResultContainer actionContainer) + { + ushort baseExp = GetBaseEXP(attacker, defender); + + //Only bother calculating the rest if there's actually exp to be gained. + //0 exp sends no message + if (baseExp > 0) + { + int totalBonus = 0;//GetMod(Modifier.bonusEXP) + + var linkCount = defender.GetMobMod(MobModifier.LinkCount); + totalBonus += GetLinkBonus((byte)Math.Min(linkCount, 255)); + + StatusEffect effect = attacker.statusEffects.GetStatusEffectById((uint)StatusEffectId.EXPChain); + ushort expChainNumber = 0; + uint timeLimit = 100; + if (effect != null) + { + expChainNumber = effect.GetTier(); + timeLimit = (uint)(GetChainTimeLimit(expChainNumber)); + actionContainer?.AddEXPAction(new CommandResult(attacker.actorId, 33919, 0, expChainNumber, (byte)timeLimit)); + } + + totalBonus += GetChainBonus(expChainNumber); + + StatusEffect newChain = Server.GetWorldManager().GetStatusEffect((uint)StatusEffectId.EXPChain); + newChain.SetDuration(timeLimit); + newChain.SetTier((byte)(Math.Min(expChainNumber + 1, 255))); + attacker.statusEffects.AddStatusEffect(newChain, attacker); + + actionContainer?.AddEXPActions(attacker.AddExp(baseExp, (byte)attacker.GetClass(), (byte)(totalBonus.Min(255)))); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/chara/npc/Ally.cs b/FFXIVClassic Map Server/actors/chara/npc/Ally.cs new file mode 100644 index 00000000..d8924544 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/npc/Ally.cs @@ -0,0 +1,25 @@ +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.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara.ai.controllers; + +namespace FFXIVClassic_Map_Server.actors.chara.npc +{ + class Ally : BattleNpc + { + // todo: ally class is probably not necessary + public Ally(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, + ushort actorState, uint animationId, string customDisplayName) + : base(actorNumber, actorClass, uniqueId, spawnedArea, posX, posY, posZ, rot, actorState, animationId, customDisplayName) + { + aiContainer = new AIContainer(this, new AllyController(this), new PathFind(this), new TargetFind(this)); + this.allegiance = CharacterTargetingAllegiance.Player; + this.isAutoAttackEnabled = true; + this.isMovingToSpawn = false; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs new file mode 100644 index 00000000..156a6109 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs @@ -0,0 +1,453 @@ +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.actors.chara.npc; +using FFXIVClassic_Map_Server.actors; +using FFXIVClassic_Map_Server.actors.chara; +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara.ai.controllers; +using FFXIVClassic_Map_Server.packets.send.actor; +using FFXIVClassic_Map_Server.actors.chara.ai.state; +using FFXIVClassic_Map_Server.utils; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.actors.chara.ai.utils; +using FFXIVClassic_Map_Server.actors.group; +using FFXIVClassic_Map_Server.packets.send; +using FFXIVClassic_Map_Server.Actors.Chara; + +namespace FFXIVClassic_Map_Server.Actors +{ + [Flags] + enum DetectionType + { + None = 0x00, + Sight = 0x01, + Scent = 0x02, + Sound = 0x04, + LowHp = 0x08, + IgnoreLevelDifference = 0x10, + Magic = 0x20, + } + + enum KindredType + { + Unknown = 0, + Beast = 1, + Plantoid = 2, + Aquan = 3, + Spoken = 4, + Reptilian = 5, + Insect = 6, + Avian = 7, + Undead = 8, + Cursed = 9, + Voidsent = 10, + } + + class BattleNpc : Npc + { + public HateContainer hateContainer; + public DetectionType detectionType; + public KindredType kindredType; + public bool neutral; + protected uint despawnTime; + protected uint respawnTime; + protected uint spawnDistance; + protected uint bnpcId; + public Character lastAttacker; + + public uint spellListId, skillListId, dropListId; + public Dictionary skillList = new Dictionary(); + public Dictionary spellList = new Dictionary(); + + public uint poolId, genusId; + public ModifierList poolMods; + public ModifierList genusMods; + public ModifierList spawnMods; + + protected Dictionary mobModifiers = new Dictionary(); + + public BattleNpc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, + ushort actorState, uint animationId, string customDisplayName) + : base(actorNumber, actorClass, uniqueId, spawnedArea, posX, posY, posZ, rot, actorState, animationId, customDisplayName) + { + this.aiContainer = new AIContainer(this, new BattleNpcController(this), new PathFind(this), new TargetFind(this)); + + //this.currentSubState = SetActorStatePacket.SUB_STATE_MONSTER; + //this.currentMainState = SetActorStatePacket.MAIN_STATE_ACTIVE; + + //charaWork.property[2] = 1; + //npcWork.hateType = 1; + + this.hateContainer = new HateContainer(this); + this.allegiance = CharacterTargetingAllegiance.BattleNpcs; + + spawnX = posX; + spawnY = posY; + spawnZ = posZ; + + despawnTime = 10; + CalculateBaseStats(); + } + + public override List GetSpawnPackets(Player player, ushort spawnType) + { + List subpackets = new List(); + if (IsAlive()) + { + subpackets.Add(CreateAddActorPacket()); + subpackets.AddRange(GetEventConditionPackets()); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(0x0)); + + subpackets.Add(CreateAppearancePacket()); + + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateSubStatePacket()); + subpackets.Add(CreateInitStatusPacket()); + subpackets.Add(CreateSetActorIconPacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket(player)); + subpackets.Add(GetHateTypePacket(player)); + } + return subpackets; + } + + //This might need more work + //I think there migh be something that ties mobs to parties + //and the client checks if any mobs are tied to the current party + //and bases the color on that. Adding mob to party obviously doesn't work + //Based on depictionjudge script: + //HATE_TYPE_NONE is for passive + //HATE_TYPE_ENGAGED is for aggroed mobs + //HATE_TYPE_ENGAGED_PARTY is for claimed mobs, client uses occupancy group to determine if mob is claimed by player's party + //for now i'm just going to assume that occupancygroup will be BattleNpc's currentparties when they're in combat, + //so if party isn't null, they're claimed. + public SubPacket GetHateTypePacket(Player player) + { + npcWork.hateType = NpcWork.HATE_TYPE_NONE; + if (player != null) + { + if (aiContainer.IsEngaged()) + { + npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED; + + if (this.currentParty != null) + { + npcWork.hateType = NpcWork.HATE_TYPE_ENGAGED_PARTY; + } + } + } + npcWork.hateType = 3; + var propPacketUtil = new ActorPropertyPacketUtil("npcWork/hate", this); + propPacketUtil.AddProperty("npcWork.hateType"); + return propPacketUtil.Done()[0]; + } + + public uint GetDetectionType() + { + return (uint)detectionType; + } + + public void SetDetectionType(uint detectionType) + { + this.detectionType = (DetectionType)detectionType; + } + + public override void Update(DateTime tick) + { + this.aiContainer.Update(tick); + this.statusEffects.Update(tick); + } + + public override void PostUpdate(DateTime tick, List packets = null) + { + // todo: should probably add another flag for battleTemp since all this uses reflection + packets = new List(); + if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0) + { + var propPacketUtil = new ActorPropertyPacketUtil("charaWork/stateAtQuicklyForAll", this); + propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkill[0]"); + propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkillLevel"); + + propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[0]"); + propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[1]"); + packets.AddRange(propPacketUtil.Done()); + } + base.PostUpdate(tick, packets); + } + + public override bool CanAttack() + { + // todo: + return true; + } + + public override bool CanUse(Character target, BattleCommand spell, CommandResult error = null) + { + // todo: + if (target == null) + { + // Target does not exist. + return false; + } + if (Utils.Distance(positionX, positionY, positionZ, target.positionX, target.positionY, target.positionZ) > spell.range) + { + // The target is out of range. + return false; + } + if (!IsValidTarget(target, spell.mainTarget) || !spell.IsValidMainTarget(this, target)) + { + // error packet is set in IsValidTarget + return false; + } + return true; + } + + public uint GetDespawnTime() + { + return despawnTime; + } + + public void SetDespawnTime(uint seconds) + { + despawnTime = seconds; + } + + public uint GetRespawnTime() + { + return respawnTime; + } + + public void SetRespawnTime(uint seconds) + { + respawnTime = seconds; + } + + /// // todo: create an action object? + public bool OnAttack(AttackState state) + { + return false; + } + + public override void Spawn(DateTime tick) + { + if (respawnTime > 0) + { + ForceRespawn(); + } + } + + public void ForceRespawn() + { + base.Spawn(Program.Tick); + + this.isMovingToSpawn = false; + this.hateContainer.ClearHate(); + zone.BroadcastPacketsAroundActor(this, GetSpawnPackets(null, 0x01)); + zone.BroadcastPacketsAroundActor(this, GetInitPackets()); + charaWork.parameterSave.hp = charaWork.parameterSave.hpMax; + charaWork.parameterSave.hp = (short[])charaWork.parameterSave.hpMax.Clone(); + RecalculateStats(); + + OnSpawn(); + updateFlags |= ActorUpdateFlags.AllNpc; + } + + public override void Die(DateTime tick, CommandResultContainer actionContainer = null) + { + if (IsAlive()) + { + // todo: does retail + if (lastAttacker is Pet && lastAttacker.aiContainer.GetController() != null && lastAttacker.aiContainer.GetController().GetPetMaster() is Player) + { + lastAttacker = lastAttacker.aiContainer.GetController().GetPetMaster(); + } + + if (lastAttacker is Player) + { + //I think this is, or should be odne in DoBattleAction. Packet capture had the message in the same packet as an attack + // defeat/defeats + if (actionContainer != null) + actionContainer.AddEXPAction(new CommandResult(actorId, 30108, 0)); + + if (lastAttacker.currentParty != null && lastAttacker.currentParty is Party) + { + foreach (var memberId in ((Party)lastAttacker.currentParty).members) + { + var partyMember = zone.FindActorInArea(memberId); + // onDeath(monster, player, killer) + lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, partyMember, lastAttacker); + + // todo: add actual experience calculation and exp bonus values. + if (partyMember is Player) + BattleUtils.AddBattleBonusEXP((Player)partyMember, this, actionContainer); + } + } + else + { + // onDeath(monster, player, killer) + lua.LuaEngine.CallLuaBattleFunction(this, "onDeath", this, lastAttacker, lastAttacker); + //((Player)lastAttacker).QueuePacket(BattleActionX01Packet.BuildPacket(lastAttacker.actorId, 0, 0, new BattleAction(actorId, 30108, 0))); + } + } + + if (positionUpdates != null) + positionUpdates.Clear(); + + aiContainer.InternalDie(tick, despawnTime); + //this.ResetMoveSpeeds(); + // todo: reset cooldowns + + lua.LuaEngine.GetInstance().OnSignal("mobkill"); + } + else + { + var err = String.Format("[{0}][{1}] {2} {3} {4} {5} tried to die ded", actorId, GetUniqueId(), positionX, positionY, positionZ, GetZone().GetName()); + Program.Log.Error(err); + //throw new Exception(err); + } + } + + public override void Despawn(DateTime tick) + { + // todo: probably didnt need to make a new state... + aiContainer.InternalDespawn(tick, respawnTime); + lua.LuaEngine.CallLuaBattleFunction(this, "onDespawn", this); + this.isAtSpawn = true; + } + + public void OnRoam(DateTime tick) + { + // leash back to spawn + if (!IsCloseToSpawn()) + { + if (!isMovingToSpawn) + { + aiContainer.Reset(); + isMovingToSpawn = true; + } + else + { + if (target == null && !aiContainer.pathFind.IsFollowingPath()) + aiContainer.pathFind.PathInRange(spawnX, spawnY, spawnZ, 1.5f, 15.0f); + } + } + else + { + // recover hp + if (GetHPP() < 100) + { + AddHP(GetMaxHP() / 10); + } + else + { + this.isMovingToSpawn = false; + } + } + } + + public bool IsCloseToSpawn() + { + return this.isAtSpawn = Utils.DistanceSquared(positionX, positionY, positionZ, spawnX, spawnY, spawnZ) <= 2500.0f; + } + + public override void OnAttack(State state, CommandResult action, ref CommandResult error) + { + base.OnAttack(state, action, ref error); + // todo: move this somewhere else prolly and change based on model/appearance (so maybe in Character.cs instead) + action.animation = 0x11001000; // (temporary) wolf anim + + if (GetMobMod((uint)MobModifier.AttackScript) != 0) + lua.LuaEngine.CallLuaBattleFunction(this, "onAttack", this, state.GetTarget(), action.amount); + } + + public override void OnCast(State state, CommandResult[] actions, BattleCommand spell, ref CommandResult[] errors) + { + base.OnCast(state, actions, spell, ref errors); + + if (GetMobMod((uint)MobModifier.SpellScript) != 0) + foreach (var action in actions) + lua.LuaEngine.CallLuaBattleFunction(this, "onCast", this, zone.FindActorInArea(action.targetId), ((MagicState)state).GetSpell(), action); + } + + public override void OnAbility(State state, CommandResult[] actions, BattleCommand ability, ref CommandResult[] errors) + { + base.OnAbility(state, actions, ability, ref errors); + + /* + if (GetMobMod((uint)MobModifier.AbilityScript) != 0) + foreach (var action in actions) + lua.LuaEngine.CallLuaBattleFunction(this, "onAbility", this, zone.FindActorInArea(action.targetId), ((AbilityState)state).GetAbility(), action); + */ + } + + public override void OnWeaponSkill(State state, CommandResult[] actions, BattleCommand skill, ref CommandResult[] errors) + { + base.OnWeaponSkill(state, actions, skill, ref errors); + + if (GetMobMod((uint)MobModifier.WeaponSkillScript) != 0) + foreach (var action in actions) + lua.LuaEngine.CallLuaBattleFunction(this, "onWeaponSkill", this, zone.FindActorInArea(action.targetId), ((WeaponSkillState)state).GetWeaponSkill(), action); + } + + public override void OnSpawn() + { + base.OnSpawn(); + lua.LuaEngine.CallLuaBattleFunction(this, "onSpawn", this); + } + + public override void OnDeath() + { + base.OnDeath(); + } + + public override void OnDespawn() + { + base.OnDespawn(); + } + + public uint GetBattleNpcId() + { + return bnpcId; + } + + public void SetBattleNpcId(uint id) + { + this.bnpcId = id; + } + + public Int64 GetMobMod(MobModifier mobMod) + { + return GetMobMod((uint)mobMod); + } + + public Int64 GetMobMod(uint mobModId) + { + Int64 res; + if (mobModifiers.TryGetValue((MobModifier)mobModId, out res)) + return res; + return 0; + } + + public void SetMobMod(uint mobModId, Int64 val) + { + if (mobModifiers.ContainsKey((MobModifier)mobModId)) + mobModifiers[(MobModifier)mobModId] = val; + else + mobModifiers.Add((MobModifier)mobModId, val); + } + + public override void OnDamageTaken(Character attacker, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null) + { + if (GetMobMod((uint)MobModifier.DefendScript) != 0) + lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount); + base.OnDamageTaken(attacker, skill, action, actionContainer); + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs b/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs new file mode 100644 index 00000000..1e525cb0 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.chara.npc +{ + enum MobModifier + { + None = 0, + SpawnLeash = 1, // how far can i move before i deaggro target + SightRange = 2, // how close does target need to be for me to detect by sight + SoundRange = 3, // how close does target need to be for me to detect by sound + BuffChance = 4, + HealChance = 5, + SkillUseChance = 6, + LinkRadius = 7, + MagicDelay = 8, + SpecialDelay = 9, + ExpBonus = 10, // + IgnoreSpawnLeash = 11, // pursue target forever + DrawIn = 12, // do i suck people in around me + HpScale = 13, // + Assist = 14, // gotta call the bois + NoMove = 15, // cant move + ShareTarget = 16, // use this actor's id as target id + AttackScript = 17, // call my script's onAttack whenever i attack + DefendScript = 18, // call my script's onDamageTaken whenever i take damage + SpellScript = 19, // call my script's onSpellCast whenever i finish casting + WeaponSkillScript = 20, // call my script's onWeaponSkill whenever i finish using a weaponskill + AbilityScript = 21, // call my script's onAbility whenever i finish using an ability + CallForHelp = 22, // actor with this id outside of target's party with this can attack me + FreeForAll = 23, // any actor can attack me + Roams = 24, // Do I walk around? + RoamDelay = 25, // What is the delay between roam ticks + Linked = 26, // Did I get aggroed via linking? + LinkCount = 27 // How many BattleNPCs got linked with me + } +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs index 761ef80c..a351e73b 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/Npc.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/Npc.cs @@ -17,16 +17,31 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using FFXIVClassic_Map_Server.actors.chara.ai; namespace FFXIVClassic_Map_Server.Actors { + [Flags] + enum NpcSpawnType : ushort + { + Normal = 0x00, + Scripted = 0x01, + Nighttime = 0x02, + Evening = 0x04, + Daytime = 0x08, + Weather = 0x10, + } + class Npc : Character { private uint actorClassId; private string uniqueIdentifier; - private uint regionId, layoutId; + + private bool isMapObj = false; + private uint layout, instance; public NpcWork npcWork = new NpcWork(); + public NpcSpawnType npcSpawnType; public Npc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, ushort actorState, uint animationId, string customDisplayName) : base((4 << 28 | spawnedArea.actorId << 19 | (uint)actorNumber)) @@ -48,31 +63,49 @@ namespace FFXIVClassic_Map_Server.Actors this.actorClassId = actorClass.actorClassId; + this.currentSubState.motionPack = (ushort) animationId; + LoadNpcAppearance(actorClass.actorClassId); - this.classPath = actorClass.classPath; - className = classPath.Substring(classPath.LastIndexOf("/")+1); + className = actorClass.classPath.Substring(actorClass.classPath.LastIndexOf("/") + 1); + this.classPath = String.Format("{0}/{1}", actorClass.classPath.Substring(0, actorClass.classPath.LastIndexOf('/')).ToLower(), className); charaWork.battleSave.potencial = 1.0f; - charaWork.parameterSave.state_mainSkill[0] = 3; - charaWork.parameterSave.state_mainSkill[2] = 3; - charaWork.parameterSave.state_mainSkillLevel = 2; + // todo: these really need to be read from db etc + { + charaWork.parameterSave.state_mainSkill[0] = 3; + charaWork.parameterSave.state_mainSkill[2] = 3; + charaWork.parameterSave.state_mainSkillLevel = 1; - charaWork.parameterSave.hp[0] = 500; - charaWork.parameterSave.hpMax[0] = 500; - - for (int i = 0; i < 32; i++ ) - charaWork.property[i] = (byte)(((int)actorClass.propertyFlags >> i) & 1); + charaWork.parameterSave.hp[0] = 80; + charaWork.parameterSave.hpMax[0] = 80; + } + for (int i = 0; i < 32; i++ ) + charaWork.property[i] = (byte)(((int)actorClass.propertyFlags >> i) & 1); npcWork.pushCommand = actorClass.pushCommand; npcWork.pushCommandSub = actorClass.pushCommandSub; npcWork.pushCommandPriority = actorClass.pushCommandPriority; - GenerateActorName((int)actorNumber); + if (actorClassId == 1080078 || actorClassId == 1080079 || actorClassId == 1080080 || (actorClassId >= 1080123 && actorClassId <= 1080135) || (actorClassId >= 5000001 && actorClassId <= 5000090) || (actorClassId >= 5900001 && actorClassId <= 5900038)) + { + isMapObj = true; + List lParams = LuaEngine.GetInstance().CallLuaFunctionForReturn(null, this, "init", false); + if (lParams == null || lParams.Count < 6) + isMapObj = false; + else + { + layout = (uint)(Int32)lParams[4].value; + instance = (uint)(Int32)lParams[5].value; + isStatic = true; + } + } + GenerateActorName((int)actorNumber); + this.aiContainer = new AIContainer(this, null, new PathFind(this), new TargetFind(this)); } - public Npc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, uint region, uint layout) + public Npc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, uint layout, uint instance) : base((4 << 28 | spawnedArea.actorId << 19 | (uint)actorNumber)) { this.positionX = posX; @@ -103,58 +136,42 @@ namespace FFXIVClassic_Map_Server.Actors npcWork.pushCommandSub = actorClass.pushCommandSub; npcWork.pushCommandPriority = actorClass.pushCommandPriority; - this.regionId = region; - this.layoutId = layout; + this.isMapObj = true; + this.layout = layout; + this.instance = instance; GenerateActorName((int)actorNumber); + this.aiContainer = new AIContainer(this, null, new PathFind(this), new TargetFind(null)); } - public SubPacket CreateAddActorPacket(uint playerActorId) + public SubPacket CreateAddActorPacket() { - return AddActorPacket.BuildPacket(actorId, playerActorId, 8); + return AddActorPacket.BuildPacket(actorId, 8); } - int val = 0x0b00; // actorClassId, [], [], numBattleCommon, [battleCommon], numEventCommon, [eventCommon], args for either initForBattle/initForEvent - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket(Player player) { List lParams; - - Player player = Server.GetWorldManager().GetPCInWorld(playerActorId); + lParams = LuaEngine.GetInstance().CallLuaFunctionForReturn(player, this, "init", false); - if (uniqueIdentifier.Equals("1")) - { - lParams[5].value = val; - val++; - player.SendMessage(0x20, "", String.Format("ID is now: 0x{0:X}", val)); - } - if (lParams != null && lParams.Count >= 3 && lParams[2].typeID == 0 && (int)lParams[2].value == 0) isStatic = true; else { - // charaWork.property[2] = 1; - // npcWork.hateType = 1; + //charaWork.property[2] = 1; + //npcWork.hateType = 1; } - if (regionId != 0 && layoutId != 0) - { - string classPathFake = "/Chara/Npc/MapObj/MapObjStandard"; - string classNameFake = "MapObjStandard"; - lParams = LuaUtils.CreateLuaParamList(classPathFake, false, false, false, false, false, actorClassId, false, false, 0, 0, regionId, layoutId); - isStatic = true; - //ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, classNameFake, lParams).DebugPrintSubPacket(); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, classNameFake, lParams); - } - else if (lParams == null) + if (lParams == null) { string classPathFake = "/Chara/Npc/Populace/PopulaceStandard"; string classNameFake = "PopulaceStandard"; lParams = LuaUtils.CreateLuaParamList(classPathFake, false, false, false, false, false, 0xF47F6, false, false, 0, 0); isStatic = true; - //ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, classNameFake, lParams).DebugPrintSubPacket(); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, classNameFake, lParams); + //ActorInstantiatePacket.BuildPacket(actorId, actorName, classNameFake, lParams).DebugPrintSubPacket(); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, classNameFake, lParams); } else { @@ -167,95 +184,37 @@ namespace FFXIVClassic_Map_Server.Actors lParams.Insert(6, new LuaParam(0, (int)actorClassId)); } - //ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, lParams).DebugPrintSubPacket(); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, lParams); + //ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams).DebugPrintSubPacket(); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams); } - - public override BasePacket GetSpawnPackets(uint playerActorId, ushort spawnType) + + public override List GetSpawnPackets(Player player, ushort spawnType) { List subpackets = new List(); - subpackets.Add(CreateAddActorPacket(playerActorId)); - subpackets.AddRange(GetEventConditionPackets(playerActorId)); - subpackets.Add(CreateSpeedPacket(playerActorId)); - subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x0)); + subpackets.Add(CreateAddActorPacket()); + subpackets.AddRange(GetEventConditionPackets()); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(0x0)); - if (regionId != 0 && layoutId != 0) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, layoutId, regionId)); - } - else if (uniqueIdentifier.Equals("door1")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, 0xB0D, 0x1af)); - } - else if (uniqueIdentifier.Equals("door2")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, 0xB09, 0x1af)); - } - else if (uniqueIdentifier.Equals("closed_gridania_gate")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, 0xB79, 0x141)); - } - else if (uniqueIdentifier.Equals("uldah_mapshipport_1")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, 0xdc5, 0x1af)); - subpackets[subpackets.Count - 1].DebugPrintSubPacket(); - subpackets.Add(_0xD9Packet.BuildPacket(actorId, playerActorId, "end0")); - subpackets[subpackets.Count - 1].DebugPrintSubPacket(); - } - else if (uniqueIdentifier.Equals("uldah_mapshipport_2")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, 0x2, 0x1eb)); - subpackets[subpackets.Count - 1].DebugPrintSubPacket(); - subpackets.Add(_0xD9Packet.BuildPacket(actorId, playerActorId, "end0")); - subpackets[subpackets.Count - 1].DebugPrintSubPacket(); - } - else if (uniqueIdentifier.Equals("gridania_shipport")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId,playerActorId, 0xcde, 0x141)); - subpackets.Add(_0xD9Packet.BuildPacket(actorId,playerActorId, "end0")); - } - else if (uniqueIdentifier.Equals("gridania_shipport2")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, 0x02, 0x187)); - subpackets.Add(_0xD9Packet.BuildPacket(actorId, playerActorId, "end0")); - } - else if (uniqueIdentifier.Equals("limsa_shipport")) - { - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, 0x1c8, 0xc4)); - subpackets.Add(_0xD9Packet.BuildPacket(actorId, playerActorId, "spin")); - } - else if (actorClassId == 5900013) - { - uint id = 201; - uint id2 = 0x1415; - string val = "fdin"; - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, id, id2)); - subpackets.Add(_0xD9Packet.BuildPacket(actorId, playerActorId, val)); - } - else if (actorClassId == 5900014) - { - uint id = 201; - uint id2 = 0x1415; - string val = "fdot"; - subpackets.Add(_0xD8Packet.BuildPacket(actorId, playerActorId, id, id2)); - subpackets.Add(_0xD9Packet.BuildPacket(actorId, playerActorId, val)); - } + if (isMapObj) + subpackets.Add(SetActorBGPropertiesPacket.BuildPacket(actorId, instance, layout)); else - subpackets.Add(CreateAppearancePacket(playerActorId)); - subpackets.Add(CreateNamePacket(playerActorId)); - subpackets.Add(CreateStatePacket(playerActorId)); - subpackets.Add(CreateIdleAnimationPacket(playerActorId)); - subpackets.Add(CreateInitStatusPacket(playerActorId)); - subpackets.Add(CreateSetActorIconPacket(playerActorId)); - subpackets.Add(CreateIsZoneingPacket(playerActorId)); - subpackets.Add(CreateScriptBindPacket(playerActorId)); + subpackets.Add(CreateAppearancePacket()); - return BasePacket.CreatePacket(subpackets, true, false); + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateSubStatePacket()); + subpackets.Add(CreateInitStatusPacket()); + subpackets.Add(CreateSetActorIconPacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket(player)); + + return subpackets; } - public override BasePacket GetInitPackets(uint playerActorId) + public override List GetInitPackets() { - ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("/_init", this, playerActorId); + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("/_init", this); //Potential propPacketUtil.AddProperty("charaWork.battleSave.potencial"); @@ -288,7 +247,7 @@ namespace FFXIVClassic_Map_Server.Actors //Status Times for (int i = 0; i < charaWork.statusShownTime.Length; i++) { - if (charaWork.statusShownTime[i] != 0xFFFFFFFF) + if (charaWork.statusShownTime[i] != 0) propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i)); } @@ -309,7 +268,7 @@ namespace FFXIVClassic_Map_Server.Actors propPacketUtil.AddProperty("npcWork.pushCommandPriority"); } - return BasePacket.CreatePacket(propPacketUtil.Done(), true, false); + return propPacketUtil.Done(); } public string GetUniqueId() @@ -325,7 +284,7 @@ namespace FFXIVClassic_Map_Server.Actors public void ChangeNpcAppearance(uint id) { LoadNpcAppearance(id); - zone.BroadcastPacketAroundActor(this, CreateAppearancePacket(actorId)); + zone.BroadcastPacketAroundActor(this, CreateAppearancePacket()); } public void LoadNpcAppearance(uint id) @@ -443,21 +402,54 @@ namespace FFXIVClassic_Map_Server.Actors public void PlayMapObjAnimation(Player player, string animationName) { - player.QueuePacket(PlayBGAnimation.BuildPacket(actorId, player.actorId, animationName)); + player.QueuePacket(PlayBGAnimation.BuildPacket(actorId, animationName)); } - public void Update(double deltaTime) + public void Despawn() { - LuaEngine.GetInstance().CallLuaFunction(null, this, "onUpdate", true, deltaTime); + zone.DespawnActor(this); } + public override void Update(DateTime tick) + { + // todo: can normal npcs have status effects? + aiContainer.Update(tick); + } + + public override void PostUpdate(DateTime tick, List packets = null) + { + packets = packets ?? new List(); + + if ((updateFlags & ActorUpdateFlags.Work) != 0) + { + + } + base.PostUpdate(tick, packets); + } + + public override void OnSpawn() + { + base.OnSpawn(); + } + + public override void OnDeath() + { + base.OnDeath(); + } + + public override void OnDespawn() + { + zone.BroadcastPacketAroundActor(this, RemoveActorPacket.BuildPacket(this.actorId)); + QueuePositionUpdate(spawnX, spawnY, spawnZ); + LuaEngine.CallLuaBattleFunction(this, "onDespawn", this); + } //A party member list packet came, set the party - /* public void SetParty(MonsterPartyGroup group) - { - if (group is MonsterPartyGroup) - currentParty = group; - } - */ + /* public void SetParty(MonsterPartyGroup group) + { + if (group is MonsterPartyGroup) + currentParty = group; + } + */ } } diff --git a/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs b/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs index ca21d437..7827f7f5 100644 --- a/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs +++ b/FFXIVClassic Map Server/actors/chara/npc/NpcWork.cs @@ -2,6 +2,10 @@ { class NpcWork { + public static byte HATE_TYPE_NONE = 0; + public static byte HATE_TYPE_ENGAGED = 2; + public static byte HATE_TYPE_ENGAGED_PARTY = 3; + public ushort pushCommand; public int pushCommandSub; public byte pushCommandPriority; diff --git a/FFXIVClassic Map Server/actors/chara/npc/Pet.cs b/FFXIVClassic Map Server/actors/chara/npc/Pet.cs new file mode 100644 index 00000000..3068d023 --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/npc/Pet.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara.ai.controllers; +using FFXIVClassic_Map_Server.actors.chara.npc; +using FFXIVClassic_Map_Server.packets.send.actor; + +namespace FFXIVClassic_Map_Server.Actors +{ + class Pet : BattleNpc + { + public Pet(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, + ushort actorState, uint animationId, string customDisplayName) + : base(actorNumber, actorClass, uniqueId, spawnedArea, posX, posY, posZ, rot, actorState, animationId, customDisplayName) + { + this.aiContainer = new AIContainer(this, new PetController(this), new PathFind(this), new TargetFind(this)); + this.hateContainer = new HateContainer(this); + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/npc/Retainer.cs b/FFXIVClassic Map Server/actors/chara/npc/Retainer.cs new file mode 100644 index 00000000..ec82890e --- /dev/null +++ b/FFXIVClassic Map Server/actors/chara/npc/Retainer.cs @@ -0,0 +1,61 @@ +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.chara.player; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.packets.send.actor.inventory; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.chara.npc +{ + class Retainer : Npc + { + public const int MAXSIZE_INVENTORY_NORMAL = 150; + public const int MAXSIZE_INVENTORY_CURRANCY = 320; + public const int MAXSIZE_INVENTORY_BAZAAR = 10; + + private uint retainerId; + private Player ownerPlayer; + private Dictionary inventories = new Dictionary(); + + public Retainer(uint retainerId, ActorClass actorClass, Player player, float posX, float posY, float posZ, float rot) + : base(0, actorClass, "myretainer", player.GetZone(), posX, posY, posZ, rot, 0, 0, null) + { + this.retainerId = retainerId; + this.ownerPlayer = player; + this.actorName = String.Format("_rtnre{0:x7}", actorId); + + inventories[Inventory.NORMAL] = new Inventory(this, MAXSIZE_INVENTORY_NORMAL, Inventory.NORMAL); + inventories[Inventory.CURRENCY_CRYSTALS] = new Inventory(this, MAXSIZE_INVENTORY_CURRANCY, Inventory.CURRENCY_CRYSTALS); + inventories[Inventory.BAZAAR] = new Inventory(this, MAXSIZE_INVENTORY_BAZAAR, Inventory.BAZAAR); + + inventories[Inventory.NORMAL].InitList(Database.GetInventory(this, Inventory.NORMAL)); + inventories[Inventory.CURRENCY_CRYSTALS].InitList(Database.GetInventory(this, Inventory.CURRENCY_CRYSTALS)); + inventories[Inventory.BAZAAR].InitList(Database.GetInventory(this, Inventory.BAZAAR)); + } + + public Inventory GetInventory(ushort type) + { + if (inventories.ContainsKey(type)) + return inventories[type]; + else + return null; + } + + public void SendFullRetainerInventory(Player player) + { + player.QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId)); + inventories[Inventory.NORMAL].SendFullInventory(player); + inventories[Inventory.CURRENCY_CRYSTALS].SendFullInventory(player); + inventories[Inventory.BAZAAR].SendFullInventory(player); + player.QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); + } + + public uint getRetainerId() + { + return retainerId; + } + } +} diff --git a/FFXIVClassic Map Server/actors/chara/player/Equipment.cs b/FFXIVClassic Map Server/actors/chara/player/Equipment.cs index 3092ba39..87ad1210 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Equipment.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Equipment.cs @@ -63,24 +63,24 @@ namespace FFXIVClassic_Map_Server.actors.chara.player } } - toPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, toPlayer.actorId, 0x23, Inventory.EQUIPMENT_OTHERPLAYER)); + toPlayer.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, 0x23, Inventory.EQUIPMENT_OTHERPLAYER)); int currentIndex = 0; while (true) { if (items.Count - currentIndex >= 16) - toPlayer.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, toPlayer.actorId, items, ref currentIndex)); + toPlayer.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, items, ref currentIndex)); else if (items.Count - currentIndex > 1) - toPlayer.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, toPlayer.actorId, items, ref currentIndex)); + toPlayer.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, items, ref currentIndex)); else if (items.Count - currentIndex == 1) { - toPlayer.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, toPlayer.actorId, items[currentIndex])); + toPlayer.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, items[currentIndex])); currentIndex++; } else break; } - toPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId, toPlayer.actorId)); + toPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); } public void SendFullEquipment(bool DoClear) @@ -106,13 +106,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.player for (int i = 0; i < slots.Length; i++) { - InventoryItem item = normalInventory.GetItemBySlot(itemSlots[i]); + InventoryItem item = normalInventory.GetItemAtSlot(itemSlots[i]); if (item == null) continue; Database.EquipItem(owner, slots[i], item.uniqueId); - list[slots[i]] = normalInventory.GetItemBySlot(itemSlots[i]); + list[slots[i]] = normalInventory.GetItemAtSlot(itemSlots[i]); } owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); @@ -133,7 +133,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player public void Equip(ushort slot, ushort invSlot) { - InventoryItem item = normalInventory.GetItemBySlot(invSlot); + InventoryItem item = normalInventory.GetItemAtSlot(invSlot); if (item == null) return; @@ -152,9 +152,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.player owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); if (list[slot] != null) - normalInventory.RefreshItem(list[slot], item); + normalInventory.RefreshItem(owner, list[slot], item); else - normalInventory.RefreshItem(item); + normalInventory.RefreshItem(owner, item); owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); SendEquipmentPackets(slot, item); @@ -163,6 +163,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); list[slot] = item; + owner.CalculateBaseStats();// RecalculateStats(); } public void ToggleDBWrite(bool flag) @@ -180,7 +181,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - normalInventory.RefreshItem(list[slot]); + normalInventory.RefreshItem(owner, list[slot]); owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); SendEquipmentPackets(slot, null); @@ -189,6 +190,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); list[slot] = null; + owner.RecalculateStats(); } private void SendEquipmentPackets(ushort equipSlot, InventoryItem item) diff --git a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs b/FFXIVClassic Map Server/actors/chara/player/Inventory.cs index 4b2d786b..a0d741d9 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Inventory.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Inventory.cs @@ -1,501 +1,625 @@ - -using FFXIVClassic.Common; -using FFXIVClassic_Map_Server.Actors; -using FFXIVClassic_Map_Server.dataobjects; -using FFXIVClassic_Map_Server.packets.send.actor.inventory; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace FFXIVClassic_Map_Server.actors.chara.player -{ - class Inventory - { - public const ushort NORMAL = 0x0000; //Max 0xC8 - public const ushort LOOT = 0x0004; //Max 0xA - public const ushort MELDREQUEST = 0x0005; //Max 0x04 - public const ushort BAZAAR = 0x0007; //Max 0x0A - public const ushort CURRENCY = 0x0063; //Max 0x140 - public const ushort KEYITEMS = 0x0064; //Max 0x500 - public const ushort EQUIPMENT = 0x00FE; //Max 0x23 - public const ushort EQUIPMENT_OTHERPLAYER = 0x00F9; //Max 0x23 - - private Player owner; - private ushort inventoryCapacity; - private ushort inventoryCode; - private List list; - - public Inventory(Player ownerPlayer, ushort capacity, ushort code) - { - owner = ownerPlayer; - inventoryCapacity = capacity; - inventoryCode = code; - } - - #region Inventory Management - public void InitList(List itemsFromDB) - { - list = itemsFromDB; - } - - public InventoryItem GetItemBySlot(ushort slot) - { - if (slot < list.Count) - return list[slot]; - else - return null; - } - - public InventoryItem GetItemById(ulong itemId) - { - foreach (InventoryItem item in list) - { - if (item.uniqueId == itemId) - return item; - } - return null; - } - - public void RefreshItem(InventoryItem item) - { - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - SendInventoryPackets(item); - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - public void RefreshItem(params InventoryItem[] items) - { - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - SendInventoryPackets(items.ToList()); - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - public void RefreshItem(List items) - { - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - SendInventoryPackets(items); - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - public void AddItem(uint itemId) - { - AddItem(itemId, 1, 1); - } - - public void AddItem(uint itemId, int quantity) - { - AddItem(itemId, quantity, 1); - } - - public bool AddItem(uint itemId, int quantity, byte quality) - { - if (!IsSpaceForAdd(itemId, quantity)) - return false; - - Item gItem = Server.GetItemGamedata(itemId); - List slotsToUpdate = new List(); - List addItemPackets = new List(); - +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.chara.npc; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic_Map_Server.packets.send.actor.inventory; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace FFXIVClassic_Map_Server.actors.chara.player +{ + class Inventory + { + public const ushort NORMAL = 0; //Max 0xC8 + public const ushort TRADE = 1; //Max 0x96 + public const ushort LOOT = 4; //Max 0xA + public const ushort MELDREQUEST = 5; //Max 0x04 + public const ushort BAZAAR = 7; //Max 0x0A + public const ushort CURRENCY_CRYSTALS = 99; //Max 0x140 + public const ushort KEYITEMS = 100; //Max 0x500 + public const ushort EQUIPMENT = 0x00FE; //Max 0x23 + public const ushort EQUIPMENT_OTHERPLAYER = 0x00F9; //Max 0x23 + + public enum INV_ERROR { + SUCCESS = 0, + INVENTORY_FULL, + ALREADY_HAS_UNIQUE, + SYSTEM_ERROR + }; + + private Character owner; + private ushort inventoryCapacity; + private ushort inventoryCode; + private bool isTemporary; + private InventoryItem[] list; + private bool[] isDirty; + + private int endOfListIndex = 0; + + public Inventory(Character ownerPlayer, ushort capacity, ushort code, bool temporary = false) + { + owner = ownerPlayer; + inventoryCapacity = capacity; + inventoryCode = code; + isTemporary = temporary; + list = new InventoryItem[capacity]; + isDirty = new bool[capacity]; + } + + #region Inventory Management + public void InitList(List itemsFromDB) + { + int i = 0; + foreach (InventoryItem item in itemsFromDB) + list[i++] = item; + endOfListIndex = i; + } + + public InventoryItem GetItemAtSlot(ushort slot) + { + if (slot < list.Length) + return list[slot]; + else + return null; + } + + public InventoryItem GetItemByUniqueId(ulong uniqueItemId) + { + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.uniqueId == uniqueItemId) + return item; + } + return null; + } + + public InventoryItem GetItemByCatelogId(ulong catelogId) + { + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == catelogId) + return item; + } + return null; + } + + + public int GetItemQuantity(uint itemId) + { + return GetItemQuantity(itemId, 1); + } + + public int GetItemQuantity(uint itemId, uint quality) + { + int count = 0; + + for (int i = endOfListIndex - 1; i >= 0; i--) + { + InventoryItem item = list[i]; + + if (item.itemId == itemId && item.quality == quality) + count += item.quantity; + + } + + return count; + } + + + public int AddItem(uint itemId) + { + return AddItem(itemId, 1, 1); + } + + public void AddItem(uint[] itemId) + { + for (int i = 0; i < itemId.Length; i++) + AddItem(itemId[i]); + } + + public int AddItem(uint itemId, int quantity) + { + return AddItem(itemId, quantity, 1); + } + + public int AddItem(uint itemId, int quantity, byte quality) + { + if (!IsSpaceForAdd(itemId, quantity, quality)) + return (int)INV_ERROR.INVENTORY_FULL; + + ItemData gItem = Server.GetItemGamedata(itemId); + if (gItem == null) { Program.Log.Error("Inventory.AddItem: unable to find item %u", itemId); - return false; - } - - //Check if item id exists - int quantityCount = quantity; - for (int i = 0; i < list.Count; i++) - { - InventoryItem item = list[i]; - if (item.itemId == itemId && item.quantity < gItem.maxStack) - { - slotsToUpdate.Add(item.slot); - int oldQuantity = item.quantity; - item.quantity = Math.Min(item.quantity + quantityCount, gItem.maxStack); - quantityCount -= (gItem.maxStack - oldQuantity); - if (quantityCount <= 0) - break; - } - } - - //If it's unique, abort - //if (quantityCount > 0 && storedItem.isUnique) - // return ITEMERROR_UNIQUE; - - //If Inventory is full - //if (quantityCount > 0 && isInventoryFull()) - // return ITEMERROR_FULL; - - //Update lists and db - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - - //These had their quantities Changed - foreach (ushort slot in slotsToUpdate) - { - Database.SetQuantity(owner, slot, inventoryCode, list[slot].quantity); - - if (inventoryCode != CURRENCY && inventoryCode != KEYITEMS) - SendInventoryPackets(list[slot]); - } - - //New item that spilled over - while (quantityCount > 0) - { - InventoryItem addedItem = Database.AddItem(owner, itemId, Math.Min(quantityCount, gItem.maxStack), quality, gItem.isExclusive ? (byte)0x3 : (byte)0x0, gItem.durability, inventoryCode); - - - list.Add(addedItem); - - if (inventoryCode != CURRENCY && inventoryCode != KEYITEMS) - SendInventoryPackets(addedItem); - - quantityCount -= gItem.maxStack; - } - - if (inventoryCode == CURRENCY || inventoryCode == KEYITEMS) - SendFullInventory(); - - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - return true; - } - - public void AddItem(uint[] itemId) - { - if (!IsSpaceForAdd(itemId[0], itemId.Length)) - return; - - //Update lists and db - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - - int startPos = list.Count; - - //New item that spilled over - for (int i = 0; i < itemId.Length; i++) - { - Item gItem = Server.GetItemGamedata(itemId[i]); - InventoryItem addedItem = Database.AddItem(owner, itemId[i], 1, (byte)1, gItem.isExclusive ? (byte)0x3 : (byte)0x0, gItem.durability, inventoryCode); - list.Add(addedItem); - } - - SendInventoryPackets(startPos); - - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - } - - public void RemoveItem(uint itemId, int quantity) - { - if (!HasItem(itemId, quantity)) - return; - - List slotsToUpdate = new List(); - List itemsToRemove = new List(); - List slotsToRemove = new List(); - List AddItemPackets = new List(); - - //Remove as we go along - int quantityCount = quantity; - ushort lowestSlot = 0; - for (int i = list.Count - 1; i >= 0; i--) - { - InventoryItem item = list[i]; - if (item.itemId == itemId) - { - int oldQuantity = item.quantity; - //Stack nomnomed - if (item.quantity - quantityCount <= 0) - { - itemsToRemove.Add(item); - slotsToRemove.Add(item.slot); - } - else - { - slotsToUpdate.Add(item.slot); - item.quantity -= quantityCount; //Stack reduced - } - - quantityCount -= oldQuantity; - lowestSlot = item.slot; - - if (quantityCount <= 0) - break; - } - } - - for (int i = 0; i < slotsToUpdate.Count; i++) - { - Database.SetQuantity(owner, slotsToUpdate[i], inventoryCode, list[slotsToUpdate[i]].quantity); - } - - int oldListSize = list.Count; - for (int i = 0; i < itemsToRemove.Count; i++) - { - Database.RemoveItem(owner, itemsToRemove[i].uniqueId, inventoryCode); - list.Remove(itemsToRemove[i]); - } - - //Realign slots - for (int i = lowestSlot; i < list.Count; i++) - list[i].slot = (ushort)i; - - //Added tail end items that need to be cleared for slot realignment - for (int i = oldListSize-1; i >= oldListSize - itemsToRemove.Count; i--) - { - if (!slotsToRemove.Contains((ushort)i)) - slotsToRemove.Add((ushort)i); - } - - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - - SendInventoryPackets(lowestSlot); - SendInventoryRemovePackets(slotsToRemove); - - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - - if (inventoryCode == NORMAL) - owner.GetEquipment().SendFullEquipment(false); - - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - } - - public void RemoveItem(ulong itemDBId) - { - ushort slot = 0; - InventoryItem toDelete = null; - foreach (InventoryItem item in list) - { - if (item.uniqueId == itemDBId) - { - toDelete = item; - break; - } - slot++; - } - - if (toDelete == null) - return; - - int oldListSize = list.Count; - list.RemoveAt(slot); - Database.RemoveItem(owner, itemDBId, inventoryCode); - - //Realign slots - for (int i = slot; i < list.Count; i++) - list[i].slot = (ushort)i; - - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - - SendInventoryPackets(slot); - SendInventoryRemovePackets(slot); - if (slot != oldListSize - 1) - SendInventoryRemovePackets((ushort)(oldListSize - 1)); - - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - - if (inventoryCode == NORMAL) - owner.GetEquipment().SendFullEquipment(false); - - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - - } - - public void RemoveItem(ushort slot) - { - if (slot >= list.Count) - return; - - int oldListSize = list.Count; - list.RemoveAt((int)slot); - Database.RemoveItem(owner, slot, inventoryCode); - - //Realign slots - for (int i = slot; i < list.Count; i++) - list[i].slot = (ushort)i; - - owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - - SendInventoryPackets(slot); - SendInventoryRemovePackets(slot); - if (slot != oldListSize - 1) - SendInventoryRemovePackets((ushort)(oldListSize - 1)); - - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - - if (inventoryCode == NORMAL) - owner.GetEquipment().SendFullEquipment(false); - - owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); - } - - public void ChangeDurability(uint slot, uint durabilityChange) - { - - } - - public void ChangeSpiritBind(uint slot, uint spiritBindChange) - { - - } - - public void ChangeMateria(uint slot, byte materiaSlot, byte materiaId) - { - - } - #endregion - - #region Packet Functions - public void SendFullInventory() - { - owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); - SendInventoryPackets(0); - owner.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); - } - - private void SendInventoryPackets(InventoryItem item) - { - owner.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, item)); - } - - private void SendInventoryPackets(List items) - { - int currentIndex = 0; - - while (true) - { - if (items.Count - currentIndex >= 64) - owner.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex >= 32) - owner.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex >= 16) - owner.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex > 1) - owner.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, items, ref currentIndex)); - else if (items.Count - currentIndex == 1) - { - owner.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, items[currentIndex])); - currentIndex++; - } - else - break; - } - - } - - private void SendInventoryPackets(int startOffset) - { - int currentIndex = startOffset; - - while (true) - { - if (list.Count - currentIndex >= 64) - owner.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, list, ref currentIndex)); - else if (list.Count - currentIndex >= 32) - owner.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, list, ref currentIndex)); - else if (list.Count - currentIndex >= 16) - owner.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, list, ref currentIndex)); - else if (list.Count - currentIndex > 1) - owner.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, list, ref currentIndex)); - else if (list.Count - currentIndex == 1) - { - owner.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, list[currentIndex])); - currentIndex++; - } - else - break; - } - - } - - private void SendInventoryRemovePackets(ushort index) - { - owner.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, index)); - } - - private void SendInventoryRemovePackets(List indexes) - { - int currentIndex = 0; - - while (true) - { - if (indexes.Count - currentIndex >= 64) - owner.QueuePacket(InventoryRemoveX64Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex >= 32) - owner.QueuePacket(InventoryRemoveX32Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex >= 16) - owner.QueuePacket(InventoryRemoveX16Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex > 1) - owner.QueuePacket(InventoryRemoveX08Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); - else if (indexes.Count - currentIndex == 1) - { - owner.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, indexes[currentIndex])); - currentIndex++; - } - else - break; - } - - } - - #endregion - - #region Inventory Utils - - public bool IsFull() - { - return list.Count >= inventoryCapacity; - } - - public bool IsSpaceForAdd(uint itemId, int quantity) - { - int quantityCount = quantity; - for (int i = 0; i < list.Count; i++) - { - InventoryItem item = list[i]; - Item gItem = Server.GetItemGamedata(item.itemId); - if (item.itemId == itemId && item.quantity < gItem.maxStack) - { - quantityCount -= (gItem.maxStack - item.quantity); - if (quantityCount <= 0) - break; - } - } - - return quantityCount <= 0 || (quantityCount > 0 && !IsFull()); - } - - public bool HasItem(uint itemId) - { - return HasItem(itemId, 1); - } - - public bool HasItem(uint itemId, int minQuantity) - { - int count = 0; - - foreach (InventoryItem item in list) - { - if (item.itemId == itemId) - count += item.quantity; - - if (count >= minQuantity) - return true; - } - - return false; - } - - public int GetNextEmptySlot() - { - return list.Count == 0 ? 0 : list.Count(); - } - - #endregion - - } -} + return (int)INV_ERROR.SYSTEM_ERROR; + } + + //Check if item id exists + int quantityCount = quantity; + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == itemId && item.quality == quality && item.quantity < gItem.maxStack) + { + int oldQuantity = item.quantity; + item.quantity = Math.Min(item.quantity + quantityCount, gItem.maxStack); + isDirty[i] = true; + quantityCount -= (gItem.maxStack - oldQuantity); + + DoDatabaseQuantity(item.uniqueId, item.quantity); + + if (quantityCount <= 0) + break; + } + } + + //If it's unique, abort + if (HasItem(itemId) && gItem.isRare) + return (int)INV_ERROR.ALREADY_HAS_UNIQUE; + + //New item that spilled over + while (quantityCount > 0) + { + InventoryItem addedItem = Database.CreateItem(itemId, Math.Min(quantityCount, gItem.maxStack), quality, gItem.isExclusive ? (byte)0x3 : (byte)0x0, gItem.durability); + addedItem.slot = (ushort)endOfListIndex; + isDirty[endOfListIndex] = true; + list[endOfListIndex++] = addedItem; + quantityCount -= gItem.maxStack; + + DoDatabaseAdd(addedItem); + } + + SendUpdatePackets(); + + return (int)INV_ERROR.SUCCESS; + } + + public void RemoveItem(uint itemId) + { + RemoveItem(itemId, 1); + } + + public void RemoveItem(uint itemId, int quantity) + { + RemoveItem(itemId, quantity, 1); + } + + public void RemoveItem(uint itemId, int quantity, int quality) + { + if (!HasItem(itemId, quantity, quality)) + return; + + List slotsToUpdate = new List(); + List itemsToRemove = new List(); + List slotsToRemove = new List(); + List AddItemPackets = new List(); + + //Remove as we go along + int quantityCount = quantity; + ushort lowestSlot = 0; + for (int i = endOfListIndex - 1; i >= 0; i--) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == itemId && item.quality == quality) + { + int oldQuantity = item.quantity; + //Stack nomnomed + if (item.quantity - quantityCount <= 0) + { + DoDatabaseRemove(list[i].uniqueId); + list[i] = null; + } + //Stack reduced + else + { + item.quantity -= quantityCount; + DoDatabaseQuantity(list[i].uniqueId, list[i].quantity); + } + + isDirty[i] = true; + + quantityCount -= oldQuantity; + lowestSlot = item.slot; + + if (quantityCount <= 0) + break; + } + } + + DoRealign(); + SendUpdatePackets(); + } + + public void RemoveItemByUniqueId(ulong itemDBId) + { + ushort slot = 0; + InventoryItem toDelete = null; + for (int i = endOfListIndex - 1; i >= 0; i--) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.uniqueId == itemDBId) + { + toDelete = item; + break; + } + slot++; + } + + if (toDelete == null) + return; + + DoDatabaseRemove(toDelete.uniqueId); + + list[slot] = null; + isDirty[slot] = true; + + DoRealign(); + SendUpdatePackets(); + } + + public void RemoveItemAtSlot(ushort slot) + { + if (slot >= endOfListIndex) + return; + + DoDatabaseRemove(list[slot].uniqueId); + + list[slot] = null; + isDirty[slot] = true; + + DoRealign(); + SendUpdatePackets(); + } + + public void RemoveItemAtSlot(ushort slot, int quantity) + { + if (slot >= endOfListIndex) + return; + + if (list[slot] != null) + { + list[slot].quantity -= quantity; + + if (list[slot].quantity <= 0) + { + DoDatabaseRemove(list[slot].uniqueId); + + list[slot] = null; + DoRealign(); + } + else + DoDatabaseQuantity(list[slot].uniqueId, list[slot].quantity); + + isDirty[slot] = true; + SendUpdatePackets(); + } + } + + public void ChangeDurability(uint slot, uint durabilityChange) + { + isDirty[slot] = true; + } + + public void ChangeSpiritBind(uint slot, uint spiritBindChange) + { + isDirty[slot] = true; + } + + public void ChangeMateria(uint slot, byte materiaSlot, byte materiaId) + { + isDirty[slot] = true; + } + #endregion + + #region Packet Functions + public void SendFullInventory(Player player) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + SendInventoryPackets(player, 0); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + private void SendInventoryPackets(Player player, InventoryItem item) + { + player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, item)); + } + + private void SendInventoryPackets(Player player, List items) + { + int currentIndex = 0; + + while (true) + { + if (items.Count - currentIndex >= 64) + player.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex >= 32) + player.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex >= 16) + player.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex > 1) + player.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, items, ref currentIndex)); + else if (items.Count - currentIndex == 1) + { + player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, items[currentIndex])); + currentIndex++; + } + else + break; + } + + } + + private void SendInventoryPackets(Player player, int startOffset) + { + int currentIndex = startOffset; + + List lst = new List(); + for (int i = 0; i < endOfListIndex; i++) + lst.Add(list[i]); + + while (true) + { + if (endOfListIndex - currentIndex >= 64) + player.QueuePacket(InventoryListX64Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex >= 32) + player.QueuePacket(InventoryListX32Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex >= 16) + player.QueuePacket(InventoryListX16Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex > 1) + player.QueuePacket(InventoryListX08Packet.BuildPacket(owner.actorId, lst, ref currentIndex)); + else if (endOfListIndex - currentIndex == 1) + { + player.QueuePacket(InventoryListX01Packet.BuildPacket(owner.actorId, list[currentIndex])); + currentIndex++; + } + else + break; + } + + } + + private void SendInventoryRemovePackets(Player player, ushort index) + { + player.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, index)); + } + + private void SendInventoryRemovePackets(Player player, List indexes) + { + int currentIndex = 0; + + while (true) + { + if (indexes.Count - currentIndex >= 64) + player.QueuePacket(InventoryRemoveX64Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex >= 32) + player.QueuePacket(InventoryRemoveX32Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex >= 16) + player.QueuePacket(InventoryRemoveX16Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex > 1) + player.QueuePacket(InventoryRemoveX08Packet.BuildPacket(owner.actorId, indexes, ref currentIndex)); + else if (indexes.Count - currentIndex == 1) + { + player.QueuePacket(InventoryRemoveX01Packet.BuildPacket(owner.actorId, indexes[currentIndex])); + currentIndex++; + } + else + break; + } + + } + + public void RefreshItem(Player player, InventoryItem item) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + SendInventoryPackets(player, item); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + public void RefreshItem(Player player, params InventoryItem[] items) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + SendInventoryPackets(player, items.ToList()); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + public void RefreshItem(Player player, List items) + { + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + SendInventoryPackets(player, items); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + } + + #endregion + + #region Automatic Client and DB Updating + + private void DoDatabaseAdd(InventoryItem addedItem) + { + if (isTemporary) + return; + + if (owner is Player) + Database.AddItem((Player)owner, addedItem, inventoryCode); + else if (owner is Retainer) + Database.AddItem((Retainer)owner, addedItem, inventoryCode); + } + + private void DoDatabaseQuantity(ulong itemDBId, int quantity) + { + if (isTemporary) + return; + + if (owner is Player) + Database.SetQuantity((Player)owner, itemDBId, inventoryCode); + else if (owner is Retainer) + Database.SetQuantity((Retainer)owner, itemDBId, inventoryCode); + } + + private void DoDatabaseRemove(ulong itemDBId) + { + if (isTemporary) + return; + + if (owner is Player) + Database.RemoveItem((Player)owner, itemDBId); + else if (owner is Retainer) + Database.RemoveItem((Retainer)owner, itemDBId); + } + + private void SendUpdatePackets() + { + if (owner is Player) + { + SendUpdatePackets((Player)owner, true); + } + } + + public void SendUpdatePackets(Player player, bool doneImmediate = false) + { + List items = new List(); + List slotsToRemove = new List(); + + for (int i = 0; i < list.Length; i++) + { + if (i == endOfListIndex) + break; + if (isDirty[i]) + items.Add(list[i]); + } + + for (int i = endOfListIndex; i < list.Length; i++) + { + if (isDirty[i]) + slotsToRemove.Add((ushort)i); + } + + if (doneImmediate) + DoneSendUpdate(); + + player.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); + player.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); + //Send Updated Slots + SendInventoryPackets(player, items); + //Send Remove packets for tail end + SendInventoryRemovePackets(player, slotsToRemove); + player.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId)); + player.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); + } + + public void DoneSendUpdate() + { + Array.Clear(isDirty, 0, isDirty.Length); + } + + #endregion + + #region Inventory Utils + + public bool IsFull() + { + return endOfListIndex >= inventoryCapacity; + } + + public bool IsSpaceForAdd(uint itemId, int quantity, int quality) + { + int quantityCount = quantity; + for (int i = 0; i < endOfListIndex; i++) + { + InventoryItem item = list[i]; + ItemData gItem = Server.GetItemGamedata(item.itemId); + if (item.itemId == itemId && item.quality == quality && item.quantity < gItem.maxStack) + { + quantityCount -= (gItem.maxStack - item.quantity); + if (quantityCount <= 0) + break; + } + } + + return quantityCount <= 0 || (quantityCount > 0 && !IsFull()); + } + + public bool HasItem(uint itemId) + { + return HasItem(itemId, 1); + } + + public bool HasItem(uint itemId, int minQuantity) + { + return HasItem(itemId, minQuantity, 1); + } + + public bool HasItem(uint itemId, int minQuantity, int quality) + { + int count = 0; + + for (int i = endOfListIndex - 1; i >= 0; i--) + { + InventoryItem item = list[i]; + + Debug.Assert(item != null, "Item slot was null!!!"); + + if (item.itemId == itemId && item.quality == quality) + count += item.quantity; + + if (count >= minQuantity) + return true; + } + + return false; + } + + public int GetNextEmptySlot() + { + return endOfListIndex; + } + + private void DoRealign() + { + int lastNullSlot = -1; + + for (int i = 0; i < endOfListIndex; i++) + { + if (list[i] == null && lastNullSlot == -1) + { + lastNullSlot = i; + continue; + } + else if (list[i] != null && lastNullSlot != -1) + { + list[lastNullSlot] = list[i]; + list[lastNullSlot].slot = (ushort)lastNullSlot; + list[i] = null; + isDirty[lastNullSlot] = true; + isDirty[i] = true; + lastNullSlot++; + } + } + + if (lastNullSlot != -1) + endOfListIndex = lastNullSlot; + } + + #endregion + + } +} diff --git a/FFXIVClassic Map Server/actors/chara/player/Player.cs b/FFXIVClassic Map Server/actors/chara/player/Player.cs index a42d2c91..dbdc50e4 100644 --- a/FFXIVClassic Map Server/actors/chara/player/Player.cs +++ b/FFXIVClassic Map Server/actors/chara/player/Player.cs @@ -1,49 +1,33 @@ using FFXIVClassic.Common; - -using FFXIVClassic_Map_Server.actors.chara.player; -using FFXIVClassic_Map_Server.actors.director; -using FFXIVClassic_Map_Server.dataobjects; -using FFXIVClassic_Map_Server.dataobjects.chara; -using FFXIVClassic_Map_Server.lua; -using FFXIVClassic_Map_Server.packets.send; -using FFXIVClassic_Map_Server.packets.send.actor; -using FFXIVClassic_Map_Server.packets.send.events; -using FFXIVClassic_Map_Server.packets.send.player; -using FFXIVClassic_Map_Server.utils; using System; using System.Collections.Generic; using MoonSharp.Interpreter; -using FFXIVClassic_Map_Server.packets.receive.events; -using FFXIVClassic_Map_Server.packets.send.actor.inventory; -using FFXIVClassic_Map_Server.actors.group; -using FFXIVClassic_Map_Server.packets.send.group; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic_Map_Server.dataobjects.chara; +using FFXIVClassic_Map_Server.lua; using FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group; +using FFXIVClassic_Map_Server.utils; +using FFXIVClassic_Map_Server.actors.group; +using FFXIVClassic_Map_Server.actors.chara.player; +using FFXIVClassic_Map_Server.actors.director; +using FFXIVClassic_Map_Server.actors.chara.npc; +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara.ai.controllers; +using FFXIVClassic_Map_Server.actors.chara.ai.utils; +using FFXIVClassic_Map_Server.actors.chara.ai.state; +using FFXIVClassic_Map_Server.actors.chara; +using FFXIVClassic_Map_Server.packets.send; +using FFXIVClassic_Map_Server.packets.send.actor; +using FFXIVClassic_Map_Server.packets.send.events; +using FFXIVClassic_Map_Server.packets.send.actor.inventory; +using FFXIVClassic_Map_Server.packets.send.player; +using FFXIVClassic_Map_Server.packets.send.actor.battle; +using FFXIVClassic_Map_Server.packets.receive.events; namespace FFXIVClassic_Map_Server.Actors { class Player : Character { - public const int CLASSID_PUG = 2; - public const int CLASSID_GLA = 3; - public const int CLASSID_MRD = 4; - public const int CLASSID_ARC = 7; - public const int CLASSID_LNC = 8; - public const int CLASSID_THM = 22; - public const int CLASSID_CNJ = 23; - - public const int CLASSID_CRP = 29; - public const int CLASSID_BSM = 30; - public const int CLASSID_ARM = 31; - public const int CLASSID_GSM = 32; - public const int CLASSID_LTW = 33; - public const int CLASSID_WVR = 34; - public const int CLASSID_ALC = 35; - public const int CLASSID_CUL = 36; - - public const int CLASSID_MIN = 39; - public const int CLASSID_BTN = 40; - public const int CLASSID_FSH = 41; - public const int MAXSIZE_INVENTORY_NORMAL = 200; public const int MAXSIZE_INVENTORY_CURRANCY = 320; public const int MAXSIZE_INVENTORY_KEYITEMS = 500; @@ -94,7 +78,6 @@ namespace FFXIVClassic_Map_Server.Actors public uint destinationZone; public ushort destinationSpawnType; public uint[] timers = new uint[20]; - public ushort currentJob; public uint currentTitle; public uint playTime; public uint lastPlayTimeUpdate; @@ -126,12 +109,17 @@ namespace FFXIVClassic_Map_Server.Actors //Quest Actors (MUST MATCH playerWork.questScenario/questGuildleve) public Quest[] questScenario = new Quest[16]; - public Quest[] questGuildleve = new Quest[8]; + public uint[] questGuildleve = new uint[8]; //Aetheryte public uint homepoint = 0; public byte homepointInn = 0; + //Retainer + RetainerMeetingRelationGroup retainerMeetingGroup = null; + public Retainer currentSpawnedRetainer = null; + public bool sentRetainerSpawn = false; + private List ownedDirectors = new List(); private Director loginInitDirector = null; @@ -144,11 +132,15 @@ namespace FFXIVClassic_Map_Server.Actors playerSession = cp; actorName = String.Format("_pc{0:00000000}", actorID); className = "Player"; - currentSubState = SetActorStatePacket.SUB_STATE_PLAYER; + + moveSpeeds[0] = SetActorSpeedPacket.DEFAULT_STOP; + moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK; + moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN; + moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE; inventories[Inventory.NORMAL] = new Inventory(this, MAXSIZE_INVENTORY_NORMAL, Inventory.NORMAL); inventories[Inventory.KEYITEMS] = new Inventory(this, MAXSIZE_INVENTORY_KEYITEMS, Inventory.KEYITEMS); - inventories[Inventory.CURRENCY] = new Inventory(this, MAXSIZE_INVENTORY_CURRANCY, Inventory.CURRENCY); + inventories[Inventory.CURRENCY_CRYSTALS] = new Inventory(this, MAXSIZE_INVENTORY_CURRANCY, Inventory.CURRENCY_CRYSTALS); inventories[Inventory.MELDREQUEST] = new Inventory(this, MAXSIZE_INVENTORY_MELDREQUEST, Inventory.MELDREQUEST); inventories[Inventory.BAZAAR] = new Inventory(this, MAXSIZE_INVENTORY_BAZAAR, Inventory.BAZAAR); inventories[Inventory.LOOT] = new Inventory(this, MAXSIZE_INVENTORY_LOOT, Inventory.LOOT); @@ -204,11 +196,7 @@ namespace FFXIVClassic_Map_Server.Actors charaWork.command[12] = 0xA0F00000 | 22012; charaWork.command[13] = 0xA0F00000 | 22013; charaWork.command[14] = 0xA0F00000 | 29497; - charaWork.command[15] = 0xA0F00000 | 22015; - - charaWork.command[32] = 0xA0F00000 | 27191; - charaWork.command[33] = 0xA0F00000 | 22302; - charaWork.command[34] = 0xA0F00000 | 28466; + charaWork.command[15] = 0xA0F00000 | 22015; charaWork.commandAcquired[27150 - 26000] = true; @@ -226,35 +214,37 @@ namespace FFXIVClassic_Map_Server.Actors charaWork.eventSave.bazaarTax = 5; charaWork.battleSave.potencial = 6.6f; + charaWork.battleSave.negotiationFlag[0] = true; + charaWork.commandCategory[0] = 1; charaWork.commandCategory[1] = 1; - charaWork.commandCategory[32] = 1; - charaWork.commandCategory[33] = 1; - charaWork.commandCategory[34] = 1; charaWork.parameterSave.commandSlot_compatibility[0] = true; charaWork.parameterSave.commandSlot_compatibility[1] = true; - charaWork.parameterSave.commandSlot_compatibility[32] = true; charaWork.commandBorder = 0x20; - charaWork.parameterTemp.tp = 3000; + charaWork.parameterTemp.tp = 0; Database.LoadPlayerCharacter(this); lastPlayTimeUpdate = Utils.UnixTimeStampUTC(); + + this.aiContainer = new AIContainer(this, new PlayerController(this), null, new TargetFind(this)); + allegiance = CharacterTargetingAllegiance.Player; + CalculateBaseStats(); } - public List Create0x132Packets(uint playerActorId) + public List Create0x132Packets() { List packets = new List(); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0xB, "commandForced")); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0xA, "commandDefault")); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0x6, "commandWeak")); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0x4, "commandContent")); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0x6, "commandJudgeMode")); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0x100, "commandRequest")); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0x100, "widgetCreate")); - packets.Add(_0x132Packet.BuildPacket(playerActorId, 0x100, "macroRequest")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0xB, "commandForced")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0xA, "commandDefault")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0x6, "commandWeak")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0x4, "commandContent")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0x6, "commandJudgeMode")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0x100, "commandRequest")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0x100, "widgetCreate")); + packets.Add(_0x132Packet.BuildPacket(actorId, 0x100, "macroRequest")); return packets; } @@ -269,10 +259,10 @@ namespace FFXIVClassic_Map_Server.Actors * Timer Array - 20 Number */ - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket(Player requestPlayer) { List lParams; - if (IsMyPlayer(playerActorId)) + if (IsMyPlayer(requestPlayer.actorId)) { if (loginInitDirector != null) lParams = LuaUtils.CreateLuaParamList("/Chara/Player/Player_work", false, false, true, loginInitDirector, true, 0, false, timers, true); @@ -282,69 +272,76 @@ namespace FFXIVClassic_Map_Server.Actors else lParams = LuaUtils.CreateLuaParamList("/Chara/Player/Player_work", false, false, false, false, false, true); - ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, lParams).DebugPrintSubPacket(); + ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams).DebugPrintSubPacket(); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, lParams); + + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams); } - public override BasePacket GetSpawnPackets(uint playerActorId, ushort spawnType) + public override List GetSpawnPackets(Player requestPlayer, ushort spawnType) { List subpackets = new List(); - subpackets.Add(CreateAddActorPacket(playerActorId, 8)); - if (IsMyPlayer(playerActorId)) - subpackets.AddRange(Create0x132Packets(playerActorId)); - subpackets.Add(CreateSpeedPacket(playerActorId)); - subpackets.Add(CreateSpawnPositonPacket(playerActorId, spawnType)); - subpackets.Add(CreateAppearancePacket(playerActorId)); - subpackets.Add(CreateNamePacket(playerActorId)); - subpackets.Add(_0xFPacket.BuildPacket(playerActorId, playerActorId)); - subpackets.Add(CreateStatePacket(playerActorId)); - subpackets.Add(CreateIdleAnimationPacket(playerActorId)); - subpackets.Add(CreateInitStatusPacket(playerActorId)); - subpackets.Add(CreateSetActorIconPacket(playerActorId)); - subpackets.Add(CreateIsZoneingPacket(playerActorId)); - subpackets.AddRange(CreatePlayerRelatedPackets(playerActorId)); - subpackets.Add(CreateScriptBindPacket(playerActorId)); - return BasePacket.CreatePacket(subpackets, true, false); + subpackets.Add(CreateAddActorPacket(8)); + if (IsMyPlayer(requestPlayer.actorId)) + subpackets.AddRange(Create0x132Packets()); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(this, spawnType)); + subpackets.Add(CreateAppearancePacket()); + subpackets.Add(CreateNamePacket()); + subpackets.Add(_0xFPacket.BuildPacket(actorId)); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateSubStatePacket()); + subpackets.Add(CreateInitStatusPacket()); + subpackets.Add(CreateSetActorIconPacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.AddRange(CreatePlayerRelatedPackets(requestPlayer.actorId)); + subpackets.Add(CreateScriptBindPacket(requestPlayer)); + return subpackets; } - public List CreatePlayerRelatedPackets(uint playerActorId) + public List CreatePlayerRelatedPackets(uint requestingPlayerActorId) { List subpackets = new List(); if (gcCurrent != 0) - subpackets.Add(SetGrandCompanyPacket.BuildPacket(actorId, playerActorId, gcCurrent, gcRankLimsa, gcRankGridania, gcRankUldah)); + subpackets.Add(SetGrandCompanyPacket.BuildPacket(actorId, gcCurrent, gcRankLimsa, gcRankGridania, gcRankUldah)); if (currentTitle != 0) - subpackets.Add(SetPlayerTitlePacket.BuildPacket(actorId, playerActorId, currentTitle)); + subpackets.Add(SetPlayerTitlePacket.BuildPacket(actorId, currentTitle)); if (currentJob != 0) - subpackets.Add(SetCurrentJobPacket.BuildPacket(actorId, playerActorId, currentJob)); + subpackets.Add(SetCurrentJobPacket.BuildPacket(actorId, currentJob)); - if (IsMyPlayer(playerActorId)) + if (IsMyPlayer(requestingPlayerActorId)) { - subpackets.Add(_0x196Packet.BuildPacket(playerActorId, playerActorId)); + subpackets.Add(SetSpecialEventWorkPacket.BuildPacket(actorId)); if (hasChocobo && chocoboName != null && !chocoboName.Equals("")) { - subpackets.Add(SetChocoboNamePacket.BuildPacket(actorId, playerActorId, chocoboName)); - subpackets.Add(SetHasChocoboPacket.BuildPacket(playerActorId, hasChocobo)); + subpackets.Add(SetChocoboNamePacket.BuildPacket(actorId, chocoboName)); + subpackets.Add(SetHasChocoboPacket.BuildPacket(actorId, hasChocobo)); } if (hasGoobbue) - subpackets.Add(SetHasGoobbuePacket.BuildPacket(playerActorId, hasGoobbue)); + subpackets.Add(SetHasGoobbuePacket.BuildPacket(actorId, hasGoobbue)); + + subpackets.Add(SetAchievementPointsPacket.BuildPacket(actorId, achievementPoints)); - subpackets.Add(SetAchievementPointsPacket.BuildPacket(playerActorId, achievementPoints)); subpackets.Add(Database.GetLatestAchievements(this)); subpackets.Add(Database.GetAchievementsPacket(this)); } + if (mountState == 1) + subpackets.Add(SetCurrentMountChocoboPacket.BuildPacket(actorId, chocoboAppearance)); + else if (mountState == 2) + subpackets.Add(SetCurrentMountGoobbuePacket.BuildPacket(actorId, 1)); + return subpackets; } - public override BasePacket GetInitPackets(uint playerActorId) + public override List GetInitPackets() { - ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("/_init", this, playerActorId); + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("/_init", this); propPacketUtil.AddProperty("charaWork.eventSave.bazaarTax"); propPacketUtil.AddProperty("charaWork.battleSave.potencial"); @@ -368,7 +365,7 @@ namespace FFXIVClassic_Map_Server.Actors //Status Times for (int i = 0; i < charaWork.statusShownTime.Length; i++) { - if (charaWork.statusShownTime[i] != 0xFFFFFFFF) + if (charaWork.statusShownTime[i] != 0) propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i)); } @@ -381,19 +378,28 @@ namespace FFXIVClassic_Map_Server.Actors propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[0]"); propPacketUtil.AddProperty("charaWork.battleTemp.castGauge_speed[1]"); - + //Battle Save Skillpoint - + propPacketUtil.AddProperty(String.Format("charaWork.battleSave.skillPoint[{0}]", charaWork.parameterSave.state_mainSkill[0] - 1)); + //Commands propPacketUtil.AddProperty("charaWork.commandBorder"); - - + + propPacketUtil.AddProperty("charaWork.battleSave.negotiationFlag[0]"); + for (int i = 0; i < charaWork.command.Length; i++) { if (charaWork.command[i] != 0) + { propPacketUtil.AddProperty(String.Format("charaWork.command[{0}]", i)); + //Recast Timers + if(i >= charaWork.commandBorder) + { + propPacketUtil.AddProperty(String.Format("charaWork.parameterTemp.maxCommandRecastTime[{0}]", i - charaWork.commandBorder)); + propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_recastTime[{0}]", i - charaWork.commandBorder)); + } + } } - for (int i = 0; i < charaWork.commandCategory.Length; i++) { @@ -407,7 +413,6 @@ namespace FFXIVClassic_Map_Server.Actors if (charaWork.commandAcquired[i] != false) propPacketUtil.AddProperty(String.Format("charaWork.commandAcquired[{0}]", i)); } - for (int i = 0; i < charaWork.additionalCommandAcquired.Length; i++) { @@ -422,13 +427,11 @@ namespace FFXIVClassic_Map_Server.Actors propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_compatibility[{0}]", i)); } - /* - for (int i = 0; i < charaWork.parameterSave.commandSlot_recastTime.Length; i++) - { - if (charaWork.parameterSave.commandSlot_recastTime[i] != 0) - propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_recastTime[{0}]", i)); - } - */ + for (int i = 0; i < charaWork.parameterSave.commandSlot_recastTime.Length; i++) + { + if (charaWork.parameterSave.commandSlot_recastTime[i] != 0) + propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_recastTime[{0}]", i)); + } //System propPacketUtil.AddProperty("charaWork.parameterTemp.forceControl_float_forClientSelf[0]"); @@ -489,7 +492,7 @@ namespace FFXIVClassic_Map_Server.Actors propPacketUtil.AddProperty("playerWork.birthdayDay"); propPacketUtil.AddProperty("playerWork.initialTown"); - return BasePacket.CreatePacket(propPacketUtil.Done(), true, false); + return propPacketUtil.Done(); } public void SendSeamlessZoneInPackets() @@ -500,67 +503,69 @@ namespace FFXIVClassic_Map_Server.Actors public void SendZoneInPackets(WorldManager world, ushort spawnType) { - QueuePacket(SetActorIsZoningPacket.BuildPacket(actorId, actorId, false)); - QueuePacket(_0x10Packet.BuildPacket(actorId, 0xFF)); + QueuePacket(SetActorIsZoningPacket.BuildPacket(actorId, false)); + QueuePacket(SetDalamudPacket.BuildPacket(actorId, 0)); QueuePacket(SetMusicPacket.BuildPacket(actorId, zone.bgmDay, 0x01)); QueuePacket(SetWeatherPacket.BuildPacket(actorId, SetWeatherPacket.WEATHER_CLEAR, 1)); - + QueuePacket(SetMapPacket.BuildPacket(actorId, zone.regionId, zone.actorId)); - QueuePacket(GetSpawnPackets(actorId, spawnType)); + QueuePackets(GetSpawnPackets(this, spawnType)); //GetSpawnPackets(actorId, spawnType).DebugPrintPacket(); #region Inventory & Equipment QueuePacket(InventoryBeginChangePacket.BuildPacket(actorId)); - inventories[Inventory.NORMAL].SendFullInventory(); - inventories[Inventory.CURRENCY].SendFullInventory(); - inventories[Inventory.KEYITEMS].SendFullInventory(); - inventories[Inventory.BAZAAR].SendFullInventory(); - inventories[Inventory.MELDREQUEST].SendFullInventory(); - inventories[Inventory.LOOT].SendFullInventory(); + inventories[Inventory.NORMAL].SendFullInventory(this); + inventories[Inventory.CURRENCY_CRYSTALS].SendFullInventory(this); + inventories[Inventory.KEYITEMS].SendFullInventory(this); + inventories[Inventory.BAZAAR].SendFullInventory(this); + inventories[Inventory.MELDREQUEST].SendFullInventory(this); + inventories[Inventory.LOOT].SendFullInventory(this); equipment.SendFullEquipment(false); - playerSession.QueuePacket(InventoryEndChangePacket.BuildPacket(actorId), true, false); + playerSession.QueuePacket(InventoryEndChangePacket.BuildPacket(actorId)); #endregion - playerSession.QueuePacket(GetInitPackets(actorId)); + playerSession.QueuePacket(GetInitPackets()); - BasePacket areaMasterSpawn = zone.GetSpawnPackets(actorId); - BasePacket debugSpawn = world.GetDebugActor().GetSpawnPackets(actorId); - BasePacket worldMasterSpawn = world.GetActor().GetSpawnPackets(actorId); + List areaMasterSpawn = zone.GetSpawnPackets(); + List debugSpawn = world.GetDebugActor().GetSpawnPackets(); + List worldMasterSpawn = world.GetActor().GetSpawnPackets(); playerSession.QueuePacket(areaMasterSpawn); - playerSession.QueuePacket(debugSpawn); + playerSession.QueuePacket(debugSpawn); playerSession.QueuePacket(worldMasterSpawn); + //Inn Packets (Dream, Cutscenes, Armoire) + if (zone.isInn) { SetCutsceneBookPacket cutsceneBookPacket = new SetCutsceneBookPacket(); for (int i = 0; i < 2048; i++) cutsceneBookPacket.cutsceneFlags[i] = true; - SubPacket packet = cutsceneBookPacket.BuildPacket(actorId, "", 11, 1, 1); packet.DebugPrintSubPacket(); QueuePacket(packet); + QueuePacket(SetPlayerItemStoragePacket.BuildPacket(actorId)); } if (zone.GetWeatherDirector() != null) { - BasePacket weatherDirectorSpawn = zone.GetWeatherDirector().GetSpawnPackets(actorId); - playerSession.QueuePacket(weatherDirectorSpawn); + playerSession.QueuePacket(zone.GetWeatherDirector().GetSpawnPackets()); } foreach (Director director in ownedDirectors) { - director.GetSpawnPackets(actorId).DebugPrintPacket(); - QueuePacket(director.GetSpawnPackets(actorId)); - QueuePacket(director.GetInitPackets(actorId)); + QueuePackets(director.GetSpawnPackets()); + QueuePackets(director.GetInitPackets()); } if (currentContentGroup != null) currentContentGroup.SendGroupPackets(playerSession); + if (currentParty != null) + currentParty.SendGroupPackets(playerSession); } private void SendRemoveInventoryPackets(List slots) @@ -590,20 +595,15 @@ namespace FFXIVClassic_Map_Server.Actors return actorId == otherActorId; } - public void QueuePacket(BasePacket packet) + public void QueuePacket(SubPacket packet) + { playerSession.QueuePacket(packet); } - public void QueuePacket(SubPacket packet) - { - playerSession.QueuePacket(packet, true, false); - } - public void QueuePackets(List packets) { - foreach (SubPacket subpacket in packets) - playerSession.QueuePacket(subpacket, true, false); + playerSession.QueuePacket(packets); } public void SendPacket(string path) @@ -613,23 +613,31 @@ namespace FFXIVClassic_Map_Server.Actors BasePacket packet = new BasePacket(path); packet.ReplaceActorID(actorId); - QueuePacket(packet); + var packets = packet.GetSubpackets(); + QueuePackets(packets); } catch (Exception e) { this.SendMessage(SendMessagePacket.MESSAGE_TYPE_SYSTEM_ERROR, "[SendPacket]", "Unable to send packet."); + this.SendMessage(SendMessagePacket.MESSAGE_TYPE_SYSTEM_ERROR, "[SendPacket]", e.Message); } } public void BroadcastPacket(SubPacket packet, bool sendToSelf) { + if (sendToSelf) + { + SubPacket clonedPacket = new SubPacket(packet, actorId); + QueuePacket(clonedPacket); + } + foreach (Actor a in playerSession.actorInstanceList) { if (a is Player) { Player p = (Player)a; - if (p.Equals(this) && !sendToSelf) + if (p.Equals(this)) continue; SubPacket clonedPacket = new SubPacket(packet, a.actorId); @@ -649,14 +657,14 @@ namespace FFXIVClassic_Map_Server.Actors { if (flag) { - BroadcastPacket(SetActorIconPacket.BuildPacket(actorId, actorId, SetActorIconPacket.DISCONNECTING), true); + BroadcastPacket(SetActorIconPacket.BuildPacket(actorId, SetActorIconPacket.DISCONNECTING), true); } else { if (isGM) - BroadcastPacket(SetActorIconPacket.BuildPacket(actorId, actorId, SetActorIconPacket.ISGM), true); + BroadcastPacket(SetActorIconPacket.BuildPacket(actorId, SetActorIconPacket.ISGM), true); else - BroadcastPacket(SetActorIconPacket.BuildPacket(actorId, actorId, 0), true); + BroadcastPacket(SetActorIconPacket.BuildPacket(actorId, 0), true); } } @@ -677,6 +685,7 @@ namespace FFXIVClassic_Map_Server.Actors //Save Player Database.SavePlayerPlayTime(this); Database.SavePlayerPosition(this); + Database.SavePlayerStatusEffects(this); } public void CleanupAndSave(uint destinationZone, ushort spawnType, float destinationX, float destinationY, float destinationZ, float destinationRot) @@ -697,9 +706,12 @@ namespace FFXIVClassic_Map_Server.Actors this.positionZ = destinationZ; this.rotation = destinationRot; + this.statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnZoning); + //Save Player Database.SavePlayerPlayTime(this); - Database.SavePlayerPosition(this); + Database.SavePlayerPosition(this); + Database.SavePlayerStatusEffects(this); } public Area GetZone() @@ -709,18 +721,21 @@ namespace FFXIVClassic_Map_Server.Actors public void SendMessage(uint logType, string sender, string message) { - QueuePacket(SendMessagePacket.BuildPacket(actorId, actorId, logType, sender, message)); + QueuePacket(SendMessagePacket.BuildPacket(actorId, logType, sender, message)); } public void Logout() { + // todo: really this should be in CleanupAndSave but we might want logout/disconnect handled separately for some effects QueuePacket(LogoutPacket.BuildPacket(actorId)); + statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnLogout); CleanupAndSave(); } public void QuitGame() { QueuePacket(QuitPacket.BuildPacket(actorId)); + statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnLogout); CleanupAndSave(); } @@ -746,19 +761,18 @@ namespace FFXIVClassic_Map_Server.Actors QueuePacket(SetMusicPacket.BuildPacket(actorId, musicId, 1)); } - public void SendChocoboAppearance() + public void SendMountAppearance() { - BroadcastPacket(SetCurrentMountChocoboPacket.BuildPacket(actorId, chocoboAppearance), true); - } - - public void SendGoobbueAppearance() - { - BroadcastPacket(SetCurrentMountGoobbuePacket.BuildPacket(actorId, 1), true); + if (mountState == 1) + BroadcastPacket(SetCurrentMountChocoboPacket.BuildPacket(actorId, chocoboAppearance), true); + else if (mountState == 2) + BroadcastPacket(SetCurrentMountGoobbuePacket.BuildPacket(actorId, 1), true); } public void SetMountState(byte mountState) { this.mountState = mountState; + SendMountAppearance(); } public byte GetMountState() @@ -766,43 +780,43 @@ namespace FFXIVClassic_Map_Server.Actors return mountState; } - public void DoEmote(uint emoteId) + public void DoEmote(uint targettedActor, uint animId, uint descId) { - BroadcastPacket(ActorDoEmotePacket.BuildPacket(actorId, actorId, currentTarget, emoteId), true); + BroadcastPacket(ActorDoEmotePacket.BuildPacket(actorId, targettedActor, animId, descId), true); } public void SendGameMessage(Actor sourceActor, Actor textIdOwner, ushort textId, byte log, params object[] msgParams) { if (msgParams == null || msgParams.Length == 0) { - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, sourceActor.actorId, textIdOwner.actorId, textId, log)); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, sourceActor.actorId, textIdOwner.actorId, textId, log)); } else - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, sourceActor.actorId, textIdOwner.actorId, textId, log, LuaUtils.CreateLuaParamList(msgParams))); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, sourceActor.actorId, textIdOwner.actorId, textId, log, LuaUtils.CreateLuaParamList(msgParams))); } public void SendGameMessage(Actor textIdOwner, ushort textId, byte log, params object[] msgParams) { if (msgParams == null || msgParams.Length == 0) - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, textIdOwner.actorId, textId, log)); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, log)); else - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, textIdOwner.actorId, textId, log, LuaUtils.CreateLuaParamList(msgParams))); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, log, LuaUtils.CreateLuaParamList(msgParams))); } - public void SendGameMessage(Actor textIdOwner, ushort textId, byte log, string customSender, params object[] msgParams) + public void SendGameMessageCustomSender(Actor textIdOwner, ushort textId, byte log, string customSender, params object[] msgParams) { if (msgParams == null || msgParams.Length == 0) - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, textIdOwner.actorId, textId, customSender, log)); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, customSender, log)); else - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, textIdOwner.actorId, textId, customSender, log, LuaUtils.CreateLuaParamList(msgParams))); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, customSender, log, LuaUtils.CreateLuaParamList(msgParams))); } - public void SendGameMessage(Actor textIdOwner, ushort textId, byte log, uint displayId, params object[] msgParams) + public void SendGameMessageDisplayIDSender(Actor textIdOwner, ushort textId, byte log, uint displayId, params object[] msgParams) { if (msgParams == null || msgParams.Length == 0) - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, textIdOwner.actorId, textId, displayId, log)); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, displayId, log)); else - QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, actorId, textIdOwner.actorId, textId, displayId, log, LuaUtils.CreateLuaParamList(msgParams))); + QueuePacket(GameMessagePacket.BuildPacket(Server.GetWorldManager().GetActor().actorId, textIdOwner.actorId, textId, displayId, log, LuaUtils.CreateLuaParamList(msgParams))); } public void BroadcastWorldMessage(ushort worldMasterId, params object[] msgParams) @@ -837,7 +851,7 @@ namespace FFXIVClassic_Map_Server.Actors public void SendAppearance() { - BroadcastPacket(CreateAppearancePacket(actorId), true); + BroadcastPacket(CreateAppearancePacket(), true); } public void SendCharaExpInfo() @@ -869,7 +883,7 @@ namespace FFXIVClassic_Map_Server.Actors charaInfo1.AddTarget(); - QueuePacket(charaInfo1.BuildPacket(actorId, actorId)); + QueuePacket(charaInfo1.BuildPacket(actorId)); } else if (lastStep == 1) { @@ -900,7 +914,7 @@ namespace FFXIVClassic_Map_Server.Actors charaInfo1.AddTarget(); - QueuePacket(charaInfo1.BuildPacket(actorId, actorId)); + QueuePacket(charaInfo1.BuildPacket(actorId)); } } @@ -935,11 +949,11 @@ namespace FFXIVClassic_Map_Server.Actors //Calculate hp/mp //Get Potenciel ?????? - + //Set HP/MP/TP PARAMS //Set mainskill and level - + //Set Parameters //Set current EXP @@ -950,19 +964,42 @@ namespace FFXIVClassic_Map_Server.Actors //Check if bonus point available... set - //Set rested EXP + //Remove buffs that fall off when changing class + CommandResultContainer resultContainer = new CommandResultContainer(); + statusEffects.RemoveStatusEffectsByFlags((uint)StatusEffectFlags.LoseOnClassChange, resultContainer); + resultContainer.CombineLists(); + DoBattleAction(0, 0x7c000062, resultContainer.GetList()); + //Set rested EXP charaWork.parameterSave.state_mainSkill[0] = classId; charaWork.parameterSave.state_mainSkillLevel = charaWork.battleSave.skillLevel[classId-1]; - playerWork.restBonusExpRate = 0.0f; + for(int i = charaWork.commandBorder; i < charaWork.command.Length; i++) + { + charaWork.command[i] = 0; + charaWork.commandCategory[i] = 0; + } - ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("charaWork/stateForAll", this, actorId); + ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("charaWork/stateForAll", this); propertyBuilder.AddProperty("charaWork.parameterSave.state_mainSkill[0]"); propertyBuilder.AddProperty("charaWork.parameterSave.state_mainSkillLevel"); propertyBuilder.NewTarget("playerWork/expBonus"); propertyBuilder.AddProperty("playerWork.restBonusExpRate"); + propertyBuilder.NewTarget("charaWork/battleStateForSelf"); + propertyBuilder.AddProperty(String.Format("charaWork.battleSave.skillPoint[{0}]", classId - 1)); + Database.LoadHotbar(this); + + var time = Utils.UnixTimeStampUTC(); + for(int i = charaWork.commandBorder; i < charaWork.command.Length; i++) + { + if(charaWork.command[i] != 0) + { + charaWork.parameterSave.commandSlot_recastTime[i - charaWork.commandBorder] = time + charaWork.parameterTemp.maxCommandRecastTime[i - charaWork.commandBorder]; + } + } + + UpdateHotbar(); List packets = propertyBuilder.Done(); @@ -970,6 +1007,7 @@ namespace FFXIVClassic_Map_Server.Actors BroadcastPacket(packet, true); Database.SavePlayerCurrentClass(this); + RecalculateStats(); } public void GraphicChange(int slot, InventoryItem invItem) @@ -978,7 +1016,8 @@ namespace FFXIVClassic_Map_Server.Actors appearanceIds[slot] = 0; else { - Item item = Server.GetItemGamedata(invItem.itemId); + ItemData item = Server.GetItemGamedata(invItem.itemId); + if (item is EquipmentItem) { EquipmentItem eqItem = (EquipmentItem)item; @@ -997,12 +1036,24 @@ namespace FFXIVClassic_Map_Server.Actors appearanceIds[slot] = graphicId; } + + //Handle offhand + if (slot == MAINHAND && item is WeaponItem) + { + WeaponItem wpItem = (WeaponItem)item; + + uint graphicId = + (wpItem.graphicsOffhandWeaponId & 0x3FF) << 20 | + (wpItem.graphicsOffhandEquipmentId & 0x3FF) << 10 | + (wpItem.graphicsOffhandVariantId & 0x3FF); + + appearanceIds[SetActorAppearancePacket.OFFHAND] = graphicId; + } } Database.SavePlayerAppearance(this); - - BroadcastPacket(CreateAppearancePacket(actorId), true); - } + BroadcastPacket(CreateAppearancePacket(), true); + } public Inventory GetInventory(ushort type) { @@ -1012,6 +1063,14 @@ namespace FFXIVClassic_Map_Server.Actors return null; } + public int GetCurrentGil() + { + if (GetInventory(Inventory.CURRENCY_CRYSTALS).HasItem(1000001)) + return GetInventory(Inventory.CURRENCY_CRYSTALS).GetItemByCatelogId(1000001).quantity; + else + return 0; + } + public Actor GetActorInInstance(uint actorId) { foreach (Actor a in playerSession.actorInstanceList) @@ -1084,6 +1143,17 @@ namespace FFXIVClassic_Map_Server.Actors return -1; } + public int GetFreeGuildleveSlot() + { + for (int i = 0; i < work.guildleveId.Length; i++) + { + if (work.guildleveId[i] == 0) + return i; + } + + return -1; + } + //For Lua calls, cause MoonSharp goes retard with uint public void AddQuest(int id, bool isSilent = false) { @@ -1111,6 +1181,52 @@ namespace FFXIVClassic_Map_Server.Actors } //For Lua calls, cause MoonSharp goes retard with uint + public void AddGuildleve(uint id) + { + int freeSlot = GetFreeGuildleveSlot(); + + if (freeSlot == -1) + return; + + work.guildleveId[freeSlot] = (ushort)id; + Database.SaveGuildleve(this, id, freeSlot); + SendGuildleveClientUpdate(freeSlot); + } + + public void MarkGuildleve(uint id, bool abandoned, bool completed) + { + if (HasGuildleve(id)) + { + for (int i = 0; i < work.guildleveId.Length; i++) + { + if (work.guildleveId[i] == id) + { + work.guildleveChecked[i] = completed; + work.guildleveDone[i] = abandoned; + Database.MarkGuildleve(this, id, abandoned, completed); + SendGuildleveMarkClientUpdate(i); + } + } + } + } + + public void RemoveGuildleve(uint id) + { + if (HasGuildleve(id)) + { + for (int i = 0; i < work.guildleveId.Length; i++) + { + if (work.guildleveId[i] == id) + { + Database.RemoveGuildleve(this, id); + work.guildleveId[i] = 0; + SendGuildleveClientUpdate(i); + break; + } + } + } + } + public void AddQuest(uint id, bool isSilent = false) { Actor actor = Server.GetStaticActors((0xA0F00000 | id)); @@ -1284,6 +1400,17 @@ namespace FFXIVClassic_Map_Server.Actors return false; } + public bool HasGuildleve(uint id) + { + for (int i = 0; i < work.guildleveId.Length; i++) + { + if (work.guildleveId[i] == id) + return true; + } + + return false; + } + public int GetQuestSlot(uint id) { for (int i = 0; i < questScenario.Length; i++) @@ -1330,7 +1457,7 @@ namespace FFXIVClassic_Map_Server.Actors Database.SaveNpcLS(this, npcLSId, isCalling, isExtra); - ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("playerWork/npcLinkshellChat", this, actorId); + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("playerWork/npcLinkshellChat", this); propPacketUtil.AddProperty(String.Format("playerWork.npcLinkshellChatExtra[{0}]", npcLSId)); propPacketUtil.AddProperty(String.Format("playerWork.npcLinkshellChatCalling[{0}]", npcLSId)); QueuePackets(propPacketUtil.Done()); @@ -1338,42 +1465,86 @@ namespace FFXIVClassic_Map_Server.Actors private void SendQuestClientUpdate(int slot) { - ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("playerWork/journal", this, actorId); + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("playerWork/journal", this); propPacketUtil.AddProperty(String.Format("playerWork.questScenario[{0}]", slot)); QueuePackets(propPacketUtil.Done()); } + private void SendGuildleveClientUpdate(int slot) + { + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("work/guildleve", this); + propPacketUtil.AddProperty(String.Format("work.guildleveId[{0}]", slot)); + QueuePackets(propPacketUtil.Done()); + } + + private void SendGuildleveMarkClientUpdate(int slot) + { + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("work/guildleve", this); + propPacketUtil.AddProperty(String.Format("work.guildleveDone[{0}]", slot)); + propPacketUtil.AddProperty(String.Format("work.guildleveChecked[{0}]", slot)); + QueuePackets(propPacketUtil.Done()); + } + + public void SendStartCastbar(uint commandId, uint endTime) + { + playerWork.castCommandClient = commandId; + playerWork.castEndClient = endTime; + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("playerWork/castState", this); + propPacketUtil.AddProperty("playerWork.castEndClient"); + propPacketUtil.AddProperty("playerWork.castCommandClient"); + QueuePackets(propPacketUtil.Done()); + } + + public void SendEndCastbar() + { + playerWork.castCommandClient = 0; + playerWork.castEndClient = 0; + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("playerWork/castState", this); + propPacketUtil.AddProperty("playerWork.castCommandClient"); + QueuePackets(propPacketUtil.Done()); + } + public void SetLoginDirector(Director director) { if (ownedDirectors.Contains(director)) loginInitDirector = director; } - public void AddDirector(Director director) + public void AddDirector(Director director, bool spawnImmediatly = false) { if (!ownedDirectors.Contains(director)) { ownedDirectors.Add(director); - director.AddChild(this); - //QueuePacket(director.GetSetEventStatusPackets(actorId)); + director.AddMember(this); } } public void SendDirectorPackets(Director director) { - director.GetSpawnPackets(actorId).DebugPrintPacket(); - QueuePacket(director.GetSpawnPackets(actorId)); - QueuePacket(director.GetInitPackets(actorId)); + QueuePackets(director.GetSpawnPackets()); + QueuePackets(director.GetInitPackets()); } public void RemoveDirector(Director director) { - if (!ownedDirectors.Contains(director)) + if (ownedDirectors.Contains(director)) { + QueuePacket(RemoveActorPacket.BuildPacket(director.actorId)); ownedDirectors.Remove(director); - director.RemoveChild(this); + director.RemoveMember(this); } } + + public GuildleveDirector GetGuildleveDirector() + { + foreach (Director d in ownedDirectors) + { + if (d is GuildleveDirector) + return (GuildleveDirector)d; + } + + return null; + } public Director GetDirector(string directorName) { @@ -1405,15 +1576,21 @@ namespace FFXIVClassic_Map_Server.Actors else return; - QueuePacket(InventoryBeginChangePacket.BuildPacket(toBeExamined.actorId, actorId)); + QueuePacket(InventoryBeginChangePacket.BuildPacket(toBeExamined.actorId)); toBeExamined.GetEquipment().SendCheckEquipmentToPlayer(this); - QueuePacket(InventoryEndChangePacket.BuildPacket(toBeExamined.actorId, actorId)); + QueuePacket(InventoryEndChangePacket.BuildPacket(toBeExamined.actorId)); + } + + public void SendMyTradeToPlayer(Player player) + { + Inventory tradeInventory = new Inventory(this, 4, Inventory.TRADE); + tradeInventory.SendFullInventory(player); } public void SendDataPacket(params object[] parameters) { List lParams = LuaUtils.CreateLuaParamList(parameters); - SubPacket spacket = InfoRequestResponsePacket.BuildPacket(actorId, actorId, lParams); + SubPacket spacket = GenericDataPacket.BuildPacket(actorId, lParams); spacket.DebugPrintSubPacket(); QueuePacket(spacket); } @@ -1441,7 +1618,7 @@ namespace FFXIVClassic_Map_Server.Actors public void SetEventStatus(Actor actor, string conditionName, bool enabled, byte unknown) { - QueuePacket(packets.send.actor.events.SetEventStatus.BuildPacket(actorId, actor.actorId, enabled, unknown, conditionName)); + QueuePacket(packets.send.actor.events.SetEventStatus.BuildPacket(actor.actorId, enabled, unknown, conditionName)); } public void RunEventFunction(string functionName, params object[] parameters) @@ -1462,7 +1639,12 @@ namespace FFXIVClassic_Map_Server.Actors currentEventName = ""; currentEventRunning = null; } - + + public void BroadcastCountdown(byte countdownLength, ulong syncTime) + { + BroadcastPacket(StartCountdownPacket.BuildPacket(actorId, countdownLength, syncTime, "Go!"), true); + } + public void SendInstanceUpdate() { //Server.GetWorldManager().SeamlessCheck(this); @@ -1470,7 +1652,8 @@ namespace FFXIVClassic_Map_Server.Actors //Update Instance List aroundMe = new List(); - aroundMe.AddRange(zone.GetActorsAroundActor(this, 50)); + if (zone != null) + aroundMe.AddRange(zone.GetActorsAroundActor(this, 50)); if (zone2 != null) aroundMe.AddRange(zone2.GetActorsAroundActor(this, 50)); playerSession.UpdateInstance(aroundMe); @@ -1531,7 +1714,7 @@ namespace FFXIVClassic_Map_Server.Actors //A party member list packet came, set the party public void SetParty(Party group) { - if (group is Party) + if (group is Party && currentParty != group) { RemoveFromCurrentPartyAndCleanup(); currentParty = group; @@ -1558,14 +1741,807 @@ namespace FFXIVClassic_Map_Server.Actors //currentParty.members.Remove(this); if (partyGroup.members.Count == 0) Server.GetWorldManager().NoMembersInParty((Party)currentParty); + currentParty = null; } - - - public void Update(double delta) + public void IssueChocobo(byte appearanceId, string nameResponse) { - LuaEngine.GetInstance().CallLuaFunction(this, this, "OnUpdate", true, delta); + Database.IssuePlayerChocobo(this, appearanceId, nameResponse); + hasChocobo = true; + chocoboAppearance = appearanceId; + chocoboName = nameResponse; + } + + public void ChangeChocoboAppearance(byte appearanceId) + { + Database.ChangePlayerChocoboAppearance(this, appearanceId); + chocoboAppearance = appearanceId; + } + + public Retainer SpawnMyRetainer(Npc bell, int retainerIndex) + { + Retainer retainer = Database.LoadRetainer(this, retainerIndex); + + float distance = (float)Math.Sqrt(((positionX - bell.positionX) * (positionX - bell.positionX)) + ((positionZ - bell.positionZ) * (positionZ - bell.positionZ))); + float posX = bell.positionX - ((-1.0f * (bell.positionX - positionX)) / distance); + float posZ = bell.positionZ - ((-1.0f * (bell.positionZ - positionZ)) / distance); + + retainer.positionX = posX; + retainer.positionY = positionY; + retainer.positionZ = posZ; + retainer.rotation = (float)Math.Atan2(positionX - posX, positionZ - posZ); + + retainerMeetingGroup = new RetainerMeetingRelationGroup(5555, this, retainer); + retainerMeetingGroup.SendGroupPackets(playerSession); + + currentSpawnedRetainer = retainer; + sentRetainerSpawn = false; + + return retainer; + } + + public void DespawnMyRetainer() + { + if (currentSpawnedRetainer != null) + { + currentSpawnedRetainer = null; + retainerMeetingGroup.SendDeletePacket(playerSession); + retainerMeetingGroup = null; + } + } + + public override void Update(DateTime tick) + { + aiContainer.Update(tick); + statusEffects.Update(tick); + } + + public override void PostUpdate(DateTime tick, List packets = null) + { + // todo: is this correct? + if (this.playerSession.isUpdatesLocked) + return; + + // todo: should probably add another flag for battleTemp since all this uses reflection + packets = new List(); + + // we only want the latest update for the player + if ((updateFlags & ActorUpdateFlags.Position) != 0) + { + if (positionUpdates.Count > 1) + positionUpdates.RemoveRange(1, positionUpdates.Count - 1); + } + + if ((updateFlags & ActorUpdateFlags.HpTpMp) != 0) + { + var propPacketUtil = new ActorPropertyPacketUtil("charaWork/stateAtQuicklyForAll", this); + + // todo: should this be using job as index? + propPacketUtil.AddProperty("charaWork.parameterSave.hp[0]"); + propPacketUtil.AddProperty("charaWork.parameterSave.hpMax[0]"); + propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkill[0]"); + propPacketUtil.AddProperty("charaWork.parameterSave.state_mainSkillLevel"); + + packets.AddRange(propPacketUtil.Done()); + } + + + if ((updateFlags & ActorUpdateFlags.Stats) != 0) + { + var propPacketUtil = new ActorPropertyPacketUtil("charaWork/battleParameter", this); + + for (uint i = 0; i < 35; i++) + { + if (GetMod(i) != charaWork.battleTemp.generalParameter[i]) + { + charaWork.battleTemp.generalParameter[i] = (short)GetMod(i); + propPacketUtil.AddProperty(String.Format("charaWork.battleTemp.generalParameter[{0}]", i)); + } + } + + QueuePackets(propPacketUtil.Done()); + + } + + + base.PostUpdate(tick, packets); + } + + public override void Die(DateTime tick, CommandResultContainer actionContainer = null) + { + // todo: death timer + aiContainer.InternalDie(tick, 60); + } + + //Update commands and recast timers for the entire hotbar + public void UpdateHotbar() + { + List slotsToUpdate = new List(); + for (ushort i = charaWork.commandBorder; i < charaWork.commandBorder + 30; i++) + { + slotsToUpdate.Add(i); + } + UpdateHotbar(slotsToUpdate); + } + + //Updates the hotbar and recast timers for only certain hotbar slots + public void UpdateHotbar(List slotsToUpdate) + { + UpdateHotbarCommands(slotsToUpdate); + UpdateRecastTimers(slotsToUpdate); + } + + //Update command ids for the passed in hotbar slots + public void UpdateHotbarCommands(List slotsToUpdate) + { + ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/command", this); + foreach (ushort slot in slotsToUpdate) + { + propPacketUtil.AddProperty(String.Format("charaWork.command[{0}]", slot)); + propPacketUtil.AddProperty(String.Format("charaWork.commandCategory[{0}]", slot)); + } + + propPacketUtil.NewTarget("charaWork/commandDetailForSelf"); + //Enable or disable slots based on whether there is an ability in that slot + foreach (ushort slot in slotsToUpdate) + { + charaWork.parameterSave.commandSlot_compatibility[slot - charaWork.commandBorder] = charaWork.command[slot] != 0; + propPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_compatibility[{0}]", slot - charaWork.commandBorder)); + } + + QueuePackets(propPacketUtil.Done()); + //QueuePackets(compatibiltyUtil.Done()); + } + + //Update recast timers for the passed in hotbar slots + public void UpdateRecastTimers(List slotsToUpdate) + { + ActorPropertyPacketUtil recastPacketUtil = new ActorPropertyPacketUtil("charaWork/commandDetailForSelf", this); + + foreach (ushort slot in slotsToUpdate) + { + recastPacketUtil.AddProperty(String.Format("charaWork.parameterTemp.maxCommandRecastTime[{0}]", slot - charaWork.commandBorder)); + recastPacketUtil.AddProperty(String.Format("charaWork.parameterSave.commandSlot_recastTime[{0}]", slot - charaWork.commandBorder)); + } + + QueuePackets(recastPacketUtil.Done()); + } + + //Find the first open slot in classId's hotbar and equip an ability there. + public void EquipAbilityInFirstOpenSlot(byte classId, uint commandId, bool printMessage = true) + { + //Find first open slot on class's hotbar slot, then call EquipAbility with that slot. + ushort hotbarSlot = 0; + + //If the class we're equipping for is the current class, we can just look at charawork.command + if(classId == charaWork.parameterSave.state_mainSkill[0]) + hotbarSlot = FindFirstCommandSlotById(0); + //Otherwise, we need to check the database. + else + hotbarSlot = (ushort) (Database.FindFirstCommandSlot(this, classId) + charaWork.commandBorder); + + EquipAbility(classId, commandId, hotbarSlot, printMessage); + } + + //Add commandId to classId's hotbar at hotbarSlot. + //If classId is not the current class, do it in the database + //hotbarSlot starts at 32 + public void EquipAbility(byte classId, uint commandId, ushort hotbarSlot, bool printMessage = true) + { + var ability = Server.GetWorldManager().GetBattleCommand(commandId); + uint trueCommandId = 0xA0F00000 + commandId; + ushort lowHotbarSlot = (ushort)(hotbarSlot - charaWork.commandBorder); + ushort maxRecastTime = (ushort)(ability != null ? ability.maxRecastTimeSeconds : 5); + uint recastEnd = Utils.UnixTimeStampUTC() + maxRecastTime; + List slotsToUpdate = new List(); + + Database.EquipAbility(this, classId, (ushort) (hotbarSlot - charaWork.commandBorder), commandId, recastEnd); + //If the class we're equipping for is the current class (need to find out if state_mainSkill is supposed to change when you're a job) + //then equip the ability in charawork.commands and save in databse, otherwise just save in database + if (classId == GetCurrentClassOrJob()) + { + charaWork.command[hotbarSlot] = trueCommandId; + charaWork.commandCategory[hotbarSlot] = 1; + charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot] = maxRecastTime; + charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot] = recastEnd; + + slotsToUpdate.Add(hotbarSlot); + UpdateHotbar(slotsToUpdate); + } + + + if(printMessage) + SendGameMessage(Server.GetWorldManager().GetActor(), 30603, 0x20, 0, commandId); + } + + //Doesn't take a classId because the only way to swap abilities is through the ability equip widget oe /eaction, which only apply to current class + //hotbarSlot 1 and 2 are 32-indexed. + public void SwapAbilities(ushort hotbarSlot1, ushort hotbarSlot2) + { + //0 indexed hotbar slots for saving to database and recast timers + uint lowHotbarSlot1 = (ushort)(hotbarSlot1 - charaWork.commandBorder); + uint lowHotbarSlot2 = (ushort)(hotbarSlot2 - charaWork.commandBorder); + + //Store information about first command + uint commandId = charaWork.command[hotbarSlot1]; + uint recastEnd = charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1]; + ushort recastMax = charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot1]; + + //Move second command's info to first hotbar slot + charaWork.command[hotbarSlot1] = charaWork.command[hotbarSlot2]; + charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot1] = charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot2]; + charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1] = charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2]; + + //Move first command's info to second slot + charaWork.command[hotbarSlot2] = commandId; + charaWork.parameterTemp.maxCommandRecastTime[lowHotbarSlot2] = recastMax; + charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2] = recastEnd; + + //Save changes to both slots + Database.EquipAbility(this, GetCurrentClassOrJob(), (ushort)(lowHotbarSlot1), 0xA0F00000 ^ charaWork.command[hotbarSlot1], charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot1]); + Database.EquipAbility(this, GetCurrentClassOrJob(), (ushort)(lowHotbarSlot2), 0xA0F00000 ^ charaWork.command[hotbarSlot2], charaWork.parameterSave.commandSlot_recastTime[lowHotbarSlot2]); + + //Update slots on client + List slotsToUpdate = new List(); + slotsToUpdate.Add(hotbarSlot1); + slotsToUpdate.Add(hotbarSlot2); + UpdateHotbar(slotsToUpdate); + } + + public void UnequipAbility(ushort hotbarSlot, bool printMessage = true) + { + List slotsToUpdate = new List(); + ushort trueHotbarSlot = (ushort)(hotbarSlot + charaWork.commandBorder - 1); + uint commandId = charaWork.command[trueHotbarSlot]; + Database.UnequipAbility(this, (ushort)(trueHotbarSlot - charaWork.commandBorder)); + charaWork.command[trueHotbarSlot] = 0; + slotsToUpdate.Add(trueHotbarSlot); + + if(printMessage) + SendGameMessage(Server.GetWorldManager().GetActor(), 30604, 0x20, 0, 0xA0F00000 ^ commandId); + + UpdateHotbar(slotsToUpdate); + } + + //Finds the first hotbar slot with a given commandId. + //If the returned value is outside the hotbar, it indicates it wasn't found. + public ushort FindFirstCommandSlotById(uint commandId) + { + if(commandId != 0) + commandId |= 0xA0F00000; + + ushort firstSlot = (ushort)(charaWork.commandBorder + 30); + + for (ushort i = charaWork.commandBorder; i < charaWork.commandBorder + 30; i++) + { + if (charaWork.command[i] == commandId) + { + firstSlot = i; + break; + } + } + + return firstSlot; + } + + private void UpdateHotbarTimer(uint commandId, uint recastTimeMs) + { + ushort slot = FindFirstCommandSlotById(commandId); + charaWork.parameterSave.commandSlot_recastTime[slot - charaWork.commandBorder] = Utils.UnixTimeStampUTC(DateTime.Now.AddMilliseconds(recastTimeMs)); + var slots = new List(); + slots.Add(slot); + UpdateRecastTimers(slots); + } + + private uint GetHotbarTimer(uint commandId) + { + ushort slot = FindFirstCommandSlotById(commandId); + return charaWork.parameterSave.commandSlot_recastTime[slot - charaWork.commandBorder]; + } + + public override void Cast(uint spellId, uint targetId = 0) + { + if (aiContainer.CanChangeState()) + aiContainer.Cast(zone.FindActorInArea(targetId == 0 ? currentTarget : targetId), spellId); + else if (aiContainer.IsCurrentState()) + // You are already casting. + SendGameMessage(Server.GetWorldManager().GetActor(), 32536, 0x20); + else + // Please wait a moment and try again. + SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20); + } + + public override void Ability(uint abilityId, uint targetId = 0) + { + if (aiContainer.CanChangeState()) + aiContainer.Ability(zone.FindActorInArea(targetId == 0 ? currentTarget : targetId), abilityId); + else + // Please wait a moment and try again. + SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20); + } + + public override void WeaponSkill(uint skillId, uint targetId = 0) + { + if (aiContainer.CanChangeState()) + aiContainer.WeaponSkill(zone.FindActorInArea(targetId == 0 ? currentTarget : targetId), skillId); + else + // Please wait a moment and try again. + SendGameMessage(Server.GetWorldManager().GetActor(), 32535, 0x20); + } + + public override bool IsValidTarget(Character target, ValidTarget validTarget) + { + if (target == null) + { + // Target does not exist. + SendGameMessage(Server.GetWorldManager().GetActor(), 32511, 0x20); + return false; + } + + if (target.isMovingToSpawn) + { + // That command cannot be performed on the current target. + SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20); + return false; + } + + // enemy only + if ((validTarget & ValidTarget.Enemy) != 0) + { + // todo: this seems ambiguous + if (target.isStatic) + { + // That command cannot be performed on the current target. + SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20); + return false; + } + if (currentParty != null && target.currentParty == currentParty) + { + // That command cannot be performed on a party member. + SendGameMessage(Server.GetWorldManager().GetActor(), 32548, 0x20); + return false; + } + // todo: pvp? + if (target.allegiance == allegiance) + { + // That command cannot be performed on an ally. + SendGameMessage(Server.GetWorldManager().GetActor(), 32549, 0x20); + return false; + } + + bool partyEngaged = false; + // todo: replace with confrontation status effect? (see how dsp does it) + if (target.aiContainer.IsEngaged()) + { + if (currentParty != null) + { + if (target is BattleNpc) + { + var helpingActorId = ((BattleNpc)target).GetMobMod((uint)MobModifier.CallForHelp); + partyEngaged = this.actorId == helpingActorId || (((BattleNpc)target).GetMobMod((uint)MobModifier.FreeForAll) != 0); + } + + if (!partyEngaged) + { + foreach (var memberId in ((Party)currentParty).members) + { + if (memberId == target.currentLockedTarget) + { + partyEngaged = true; + break; + } + } + } + } + else if (target.currentLockedTarget == actorId) + { + partyEngaged = true; + } + } + else + { + partyEngaged = true; + } + + if (!partyEngaged) + { + // That target is already engaged. + SendGameMessage(Server.GetWorldManager().GetActor(), 32520, 0x20); + return false; + } + } + + if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != allegiance) + { + // That command cannot be performed on the current target. + SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20); + return false; + } + + // todo: isStatic seems ambiguous? + if ((validTarget & ValidTarget.NPC) != 0 && target.isStatic) + return true; + + // todo: why is player always zoning? + // cant target if zoning + if (target is Player && ((Player)target).playerSession.isUpdatesLocked) + { + // That command cannot be performed on the current target. + SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20); + return false; + } + + return true; + } + + //Do we need separate functions? they check the same things + public override bool CanUse(Character target, BattleCommand skill, CommandResult error = null) + { + if (!skill.IsValidMainTarget(this, target, error) || !IsValidTarget(target, skill.mainTarget)) + { + // error packet is set in IsValidTarget + return false; + } + + //Might want to do these with a BattleAction instead to be consistent with the rest of command stuff + if (GetHotbarTimer(skill.id) > Utils.UnixTimeStampUTC()) + { + // todo: this needs confirming + // Please wait a moment and try again. + error?.SetTextId(32535); + return false; + } + + if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) > skill.range) + { + // The target is too far away. + error?.SetTextId(32539); + return false; + } + + if (Utils.XZDistance(positionX, positionZ, target.positionX, target.positionZ) < skill.minRange) + { + // The target is too close. + error?.SetTextId(32538); + return false; + } + + if (target.positionY - positionY > (skill.rangeHeight / 2)) + { + // The target is too far above you. + error?.SetTextId(32540); + return false; + } + + if (positionY - target.positionY > (skill.rangeHeight / 2)) + { + // The target is too far below you. + error?.SetTextId(32541); + return false; + } + + if (skill.CalculateMpCost(this) > GetMP()) + { + // You do not have enough MP. + error?.SetTextId(32545); + return false; + } + + if (skill.CalculateTpCost(this) > GetTP()) + { + // You do not have enough TP. + error?.SetTextId(32546); + return false; + } + + //Proc requirement + if (skill.procRequirement != BattleCommandProcRequirement.None && !charaWork.battleTemp.timingCommandFlag[(int)skill.procRequirement - 1]) + { + //Conditions for use are not met + error?.SetTextId(32556); + return false; + } + + + return true; + } + + public override void OnAttack(State state, CommandResult action, ref CommandResult error) + { + var target = state.GetTarget(); + + base.OnAttack(state, action, ref error); + + // todo: switch based on main weap (also probably move this anim assignment somewhere else) + action.animation = 0x19001000; + if (error == null) + { + // melee attack animation + //action.animation = 0x19001000; + } + if (target is BattleNpc) + { + ((BattleNpc)target).hateContainer.UpdateHate(this, action.enmity); + } + + LuaEngine.GetInstance().OnSignal("playerAttack"); + } + + public override void OnCast(State state, CommandResult[] actions, BattleCommand spell, ref CommandResult[] errors) + { + // todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap) + base.OnCast(state, actions, spell, ref errors); + // todo: should just make a thing that updates the one slot cause this is dumb as hell + UpdateHotbarTimer(spell.id, spell.recastTimeMs); + //LuaEngine.GetInstance().OnSignal("spellUse"); + } + + public override void OnWeaponSkill(State state, CommandResult[] actions, BattleCommand skill, ref CommandResult[] errors) + { + // todo: update hotbar timers to skill's recast time (also needs to be done on class change or equip crap) + base.OnWeaponSkill(state, actions, skill, ref errors); + + // todo: should just make a thing that updates the one slot cause this is dumb as hell + UpdateHotbarTimer(skill.id, skill.recastTimeMs); + // todo: this really shouldnt be called on each ws? + lua.LuaEngine.CallLuaBattleFunction(this, "onWeaponSkill", this, state.GetTarget(), skill); + LuaEngine.GetInstance().OnSignal("weaponskillUse"); + } + + public override void OnAbility(State state, CommandResult[] actions, BattleCommand ability, ref CommandResult[] errors) + { + base.OnAbility(state, actions, ability, ref errors); + UpdateHotbarTimer(ability.id, ability.recastTimeMs); + LuaEngine.GetInstance().OnSignal("abilityUse"); + } + + //Handles exp being added, does not handle figuring out exp bonus from buffs or skill/link chains or any of that + //Returns CommandResults that can be sent to display the EXP gained number and level ups + //exp should be a ushort single the exp graphic overflows after ~65k + public List AddExp(int exp, byte classId, byte bonusPercent = 0) + { + List actionList = new List(); + exp += (int) Math.Ceiling((exp * bonusPercent / 100.0f)); + + //You earn [exp] (+[bonusPercent]%) experience points. + //In non-english languages there are unique messages for each language, hence the use of ClassExperienceTextIds + actionList.Add(new CommandResult(actorId, BattleUtils.ClassExperienceTextIds[classId], 0, (ushort)exp, bonusPercent)); + + bool leveled = false; + int diff = MAXEXP[GetLevel() - 1] - charaWork.battleSave.skillPoint[classId - 1]; + //While there is enough experience to level up, keep leveling up, unlocking skills and removing experience from exp until we don't have enough to level up + while (exp >= diff && GetLevel() < charaWork.battleSave.skillLevelCap[classId]) + { + //Level up + LevelUp(classId); + leveled = true; + //Reduce exp based on how much exp is needed to level + exp -= diff; + diff = MAXEXP[GetLevel() - 1]; + } + + if(leveled) + { + //Set exp to current class to 0 so that exp is added correctly + charaWork.battleSave.skillPoint[classId - 1] = 0; + //send new level + ActorPropertyPacketUtil expPropertyPacket2 = new ActorPropertyPacketUtil("charaWork/exp", this); + ActorPropertyPacketUtil expPropertyPacket3 = new ActorPropertyPacketUtil("charaWork/stateForAll", this); + expPropertyPacket2.AddProperty(String.Format("charaWork.battleSave.skillLevel[{0}]", classId - 1)); + expPropertyPacket2.AddProperty("charaWork.parameterSave.state_mainSkillLevel"); + QueuePackets(expPropertyPacket2.Done()); + QueuePackets(expPropertyPacket3.Done()); + //play levelup animation (do this outside LevelUp so that it only plays once if multiple levels are earned + //also i dunno how to do this + + Database.SetLevel(this, classId, GetLevel()); + Database.SavePlayerCurrentClass(this); + } + //Cap experience for level 50 + charaWork.battleSave.skillPoint[classId - 1] = Math.Min(charaWork.battleSave.skillPoint[classId - 1] + exp, MAXEXP[GetLevel() - 1]); + + ActorPropertyPacketUtil expPropertyPacket = new ActorPropertyPacketUtil("charaWork/battleStateForSelf", this); + expPropertyPacket.AddProperty(String.Format("charaWork.battleSave.skillPoint[{0}]", classId - 1)); + + QueuePackets(expPropertyPacket.Done()); + Database.SetExp(this, classId, charaWork.battleSave.skillPoint[classId - 1]); + + return actionList; + } + + //Increaess level of current class and equips new abilities earned at that level + public void LevelUp(byte classId, List actionList = null) + { + if (charaWork.battleSave.skillLevel[classId - 1] < charaWork.battleSave.skillLevelCap[classId]) + { + //Increase level + charaWork.battleSave.skillLevel[classId - 1]++; + charaWork.parameterSave.state_mainSkillLevel++; + + //33909: You gain level [level] + if (actionList != null) + actionList.Add(new CommandResult(actorId, 33909, 0, (ushort) charaWork.battleSave.skillLevel[classId - 1])); + + //If there's any abilites that unlocks at this level, equip them. + List commandIds = Server.GetWorldManager().GetBattleCommandIdByLevel(classId, GetLevel()); + foreach(uint commandId in commandIds) + { + EquipAbilityInFirstOpenSlot(classId, commandId, false); + byte jobId = ConvertClassIdToJobId(classId); + if (jobId != classId) + EquipAbilityInFirstOpenSlot(jobId, commandId, false); + + //33926: You learn [command]. + if (actionList != null) + { + if(classId == GetCurrentClassOrJob() || jobId == GetCurrentClassOrJob()) + actionList.Add(new CommandResult(actorId, 33926, commandId)); + } + } + } + } + + public static byte ConvertClassIdToJobId(byte classId) + { + byte jobId = classId; + + switch(classId) + { + case CLASSID_PUG: + case CLASSID_GLA: + case CLASSID_MRD: + jobId += 13; + break; + case CLASSID_ARC: + case CLASSID_LNC: + jobId += 11; + break; + case CLASSID_THM: + case CLASSID_CNJ: + jobId += 4; + break; + } + + return jobId; + } + + public void SetCurrentJob(byte jobId) + { + currentJob = jobId; + BroadcastPacket(SetCurrentJobPacket.BuildPacket(actorId, jobId), true); + Database.LoadHotbar(this); + SendCharaExpInfo(); + } + + //Gets the id of the player's current job. If they aren't a job, gets the id of their class + public byte GetCurrentClassOrJob() + { + if (currentJob != 0) + return (byte) currentJob; + return charaWork.parameterSave.state_mainSkill[0]; + } + + public void hpstuff(uint hp) + { + SetMaxHP(hp); + SetHP(hp); + mpMaxBase = (ushort)hp; + charaWork.parameterSave.mpMax = (short)hp; + charaWork.parameterSave.mp = (short)hp; + AddTP(3000); + updateFlags |= ActorUpdateFlags.HpTpMp; + } + + public void SetCombos(int comboId1 = 0, int comboId2 = 0) + { + SetCombos(new int[] { comboId1, comboId2 }); + } + + public void SetCombos(int[] comboIds) + { + Array.Copy(comboIds, playerWork.comboNextCommandId, 2); + + //If we're starting or continuing a combo chain, add the status effect and combo cost bonus + if (comboIds[0] != 0) + { + StatusEffect comboEffect = new StatusEffect(this, Server.GetWorldManager().GetStatusEffect((uint) StatusEffectId.Combo)); + comboEffect.SetDuration(13); + comboEffect.SetOverwritable(1); + statusEffects.AddStatusEffect(comboEffect, this); + playerWork.comboCostBonusRate = 1; + } + //Otherwise we're ending a combo, remove the status + else + { + statusEffects.RemoveStatusEffect(statusEffects.GetStatusEffectById((uint) StatusEffectId.Combo)); + playerWork.comboCostBonusRate = 0; + } + + ActorPropertyPacketUtil comboPropertyPacket = new ActorPropertyPacketUtil("playerWork/combo", this); + comboPropertyPacket.AddProperty("playerWork.comboCostBonusRate"); + comboPropertyPacket.AddProperty("playerWork.comboNextCommandId[0]"); + comboPropertyPacket.AddProperty("playerWork.comboNextCommandId[1]"); + QueuePackets(comboPropertyPacket.Done()); + } + + public override void CalculateBaseStats() + { + base.CalculateBaseStats(); + //Add weapon property mod + var equip = GetEquipment(); + var mainHandItem = equip.GetItemAtSlot(Equipment.SLOT_MAINHAND); + var damageAttribute = 0; + var attackDelay = 3000; + var hitCount = 1; + + if (mainHandItem != null) + { + var mainHandWeapon = (Server.GetItemGamedata(mainHandItem.itemId) as WeaponItem); + damageAttribute = mainHandWeapon.damageAttributeType1; + attackDelay = (int) (mainHandWeapon.damageInterval * 1000); + hitCount = mainHandWeapon.frequency; + } + + var hasShield = equip.GetItemAtSlot(Equipment.SLOT_OFFHAND) != null ? 1 : 0; + SetMod((uint)Modifier.CanBlock, hasShield); + + SetMod((uint)Modifier.AttackType, damageAttribute); + SetMod((uint)Modifier.Delay, attackDelay); + SetMod((uint)Modifier.HitCount, hitCount); + + //These stats all correlate in a 3:2 fashion + //It seems these stats don't actually increase their respective stats. The magic stats do, however + AddMod((uint)Modifier.Attack, (long)(GetMod(Modifier.Strength) * 0.667)); + AddMod((uint)Modifier.Accuracy, (long)(GetMod(Modifier.Dexterity) * 0.667)); + AddMod((uint)Modifier.Defense, (long)(GetMod(Modifier.Vitality) * 0.667)); + + //These stats correlate in a 4:1 fashion. (Unsure if MND is accurate but it would make sense for it to be) + AddMod((uint)Modifier.AttackMagicPotency, (long)((float)GetMod(Modifier.Intelligence) * 0.25)); + + AddMod((uint)Modifier.MagicAccuracy, (long)((float)GetMod(Modifier.Mind) * 0.25)); + AddMod((uint)Modifier.HealingMagicPotency, (long)((float)GetMod(Modifier.Mind) * 0.25)); + + AddMod((uint)Modifier.MagicEvasion, (long)((float)GetMod(Modifier.Piety) * 0.25)); + AddMod((uint)Modifier.EnfeeblingMagicPotency, (long)((float)GetMod(Modifier.Piety) * 0.25)); + + //VIT correlates to HP in a 1:1 fashion + AddMod((uint)Modifier.Hp, (long)((float)Modifier.Vitality)); + + CalculateTraitMods(); + } + + public bool HasTrait(ushort id) + { + BattleTrait trait = Server.GetWorldManager().GetBattleTrait(id); + + return HasTrait(trait); + } + + public bool HasTrait(BattleTrait trait) + { + return (trait != null) && (trait.job == GetClass()) && (trait.level <= GetLevel()); + } + + public void CalculateTraitMods() + { + var traitIds = Server.GetWorldManager().GetAllBattleTraitIdsForClass((byte) GetClass()); + + foreach(var traitId in traitIds) + { + var trait = Server.GetWorldManager().GetBattleTrait(traitId); + if(HasTrait(trait)) + { + AddMod(trait.modifier, trait.bonus); + } + } + } + + public bool HasItemEquippedInSlot(uint itemId, ushort slot) + { + var equippedItem = equipment.GetItemAtSlot(slot); + + return equippedItem != null && equippedItem.itemId == itemId; } } diff --git a/FFXIVClassic Map Server/actors/chara/player/PlayerWork.cs b/FFXIVClassic Map Server/actors/chara/player/PlayerWork.cs index b0b358cd..a4206abf 100644 --- a/FFXIVClassic Map Server/actors/chara/player/PlayerWork.cs +++ b/FFXIVClassic Map Server/actors/chara/player/PlayerWork.cs @@ -18,8 +18,8 @@ public bool isContentsCommand; - public int castCommandClient; - public int castEndClient; + public uint castCommandClient; + public uint castEndClient; public int[] comboNextCommandId = new int[2]; public float comboCostBonusRate; diff --git a/FFXIVClassic Map Server/actors/debug/Debug.cs b/FFXIVClassic Map Server/actors/debug/Debug.cs index ddea59d9..49b62173 100644 --- a/FFXIVClassic Map Server/actors/debug/Debug.cs +++ b/FFXIVClassic Map Server/actors/debug/Debug.cs @@ -18,24 +18,24 @@ namespace FFXIVClassic_Map_Server.Actors this.className = "Debug"; } - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket() { List lParams; lParams = LuaUtils.CreateLuaParamList("/System/Debug.prog", false, false, false, false, true, 0xC51F, true, true); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, lParams); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams); } - public override BasePacket GetSpawnPackets(uint playerActorId) + public override List GetSpawnPackets() { List subpackets = new List(); - subpackets.Add(CreateAddActorPacket(playerActorId, 0)); - subpackets.Add(CreateSpeedPacket(playerActorId)); - subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x1)); - subpackets.Add(CreateNamePacket(playerActorId)); - subpackets.Add(CreateStatePacket(playerActorId)); - subpackets.Add(CreateIsZoneingPacket(playerActorId)); - subpackets.Add(CreateScriptBindPacket(playerActorId)); - return BasePacket.CreatePacket(subpackets, true, false); + subpackets.Add(CreateAddActorPacket(0)); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(0x1)); + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket()); + return subpackets; } } diff --git a/FFXIVClassic Map Server/actors/director/Director.cs b/FFXIVClassic Map Server/actors/director/Director.cs index 0d26358b..db46a3cd 100644 --- a/FFXIVClassic Map Server/actors/director/Director.cs +++ b/FFXIVClassic Map Server/actors/director/Director.cs @@ -1,6 +1,7 @@  using FFXIVClassic.Common; using FFXIVClassic_Map_Server.actors.area; +using FFXIVClassic_Map_Server.actors.group; using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.lua; using FFXIVClassic_Map_Server.packets.send.actor; @@ -15,25 +16,36 @@ namespace FFXIVClassic_Map_Server.actors.director { private uint directorId; private string directorScriptPath; - private List childrenOwners = new List(); + private List members = new List(); + protected ContentGroup contentGroup; private bool isCreated = false; + private bool isDeleted = false; + private bool isDeleting = false; - public Director(uint id, Area zone, string directorPath) + private Script directorScript; + private Coroutine currentCoroutine; + + public Director(uint id, Area zone, string directorPath, bool hasContentGroup, params object[] args) : base((6 << 28 | zone.actorId << 19 | (uint)id)) { directorId = id; this.zone = zone; - directorScriptPath = directorPath; - DoActorInit(directorScriptPath); - GenerateActorName((int)id); + this.zoneId = zone.actorId; + directorScriptPath = directorPath; + + LoadLuaScript(); + + if (hasContentGroup) + contentGroup = Server.GetWorldManager().CreateContentGroup(this, GetMembers()); eventConditions = new EventList(); eventConditions.noticeEventConditions = new List(); eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("noticeEvent", 0xE,0x0)); - eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("noticeRequest",0x0,0x1)); + eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("noticeRequest", 0x0, 0x1)); + eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("reqForChild", 0x0, 0x1)); } - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket() { List actualLParams = new List(); actualLParams.Insert(0, new LuaParam(2, classPath)); @@ -42,30 +54,35 @@ namespace FFXIVClassic_Map_Server.actors.director actualLParams.Insert(3, new LuaParam(4, 4)); actualLParams.Insert(4, new LuaParam(4, 4)); actualLParams.Insert(5, new LuaParam(4, 4)); - actualLParams.Insert(6, new LuaParam(0, (int)0x13883)); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, actualLParams); + List lparams = LuaEngine.GetInstance().CallLuaFunctionForReturn(null, this, "init", false); + for (int i = 1; i < lparams.Count; i++) + actualLParams.Add(lparams[i]); + + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, actualLParams); } - public override BasePacket GetSpawnPackets(uint playerActorId, ushort spawnType) + public override List GetSpawnPackets(ushort spawnType = 1) { List subpackets = new List(); - subpackets.Add(CreateAddActorPacket(playerActorId, 0)); - subpackets.AddRange(GetEventConditionPackets(playerActorId)); - subpackets.Add(CreateSpeedPacket(playerActorId)); - subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0)); - subpackets.Add(CreateNamePacket(playerActorId)); - subpackets.Add(CreateStatePacket(playerActorId)); - subpackets.Add(CreateIsZoneingPacket(playerActorId)); - subpackets.Add(CreateScriptBindPacket(playerActorId)); - return BasePacket.CreatePacket(subpackets, true, false); - } + subpackets.Add(CreateAddActorPacket(0)); + subpackets.AddRange(GetEventConditionPackets()); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(0)); + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket()); + return subpackets; + } - public override BasePacket GetInitPackets(uint playerActorId) + public override List GetInitPackets() { + List subpackets = new List(); SetActorPropetyPacket initProperties = new SetActorPropetyPacket("/_init"); initProperties.AddTarget(); - return BasePacket.CreatePacket(initProperties.BuildPacket(playerActorId, actorId), true, false); + subpackets.Add(initProperties.BuildPacket(actorId)); + return subpackets; } public void OnTalkEvent(Player player, Npc npc) @@ -76,38 +93,105 @@ namespace FFXIVClassic_Map_Server.actors.director public void OnCommandEvent(Player player, Command command) { LuaEngine.GetInstance().CallLuaFunction(player, this, "onCommandEvent", false, command); - } + } - public void DoActorInit(string directorPath) + public void StartDirector(bool spawnImmediate, params object[] args) { - List lparams = LuaEngine.GetInstance().CallLuaFunctionForReturn(null, this, "init", false); + object[] args2 = new object[args.Length + 1]; + args2[0] = this; + Array.Copy(args, 0, args2, 1, args.Length); + + List lparams = CallLuaScript("init", args2); - if (lparams.Count == 1 && lparams[0].value is string) + if (lparams != null && lparams.Count >= 1 && lparams[0].value is string) { classPath = (string)lparams[0].value; className = classPath.Substring(classPath.LastIndexOf("/") + 1); + GenerateActorName((int)directorId); isCreated = true; } + + if (isCreated && spawnImmediate) + { + if (contentGroup != null) + contentGroup.Start(); + + foreach (Player p in GetPlayerMembers()) + { + p.QueuePackets(GetSpawnPackets()); + p.QueuePackets(GetInitPackets()); + } + } + + if (this is GuildleveDirector) + { + ((GuildleveDirector)this).LoadGuildleve(); + } + + CallLuaScript("main", this, contentGroup); + } + + public void StartContentGroup() + { + if (contentGroup != null) + contentGroup.Start(); + } + + public void EndDirector() + { + isDeleting = true; + + if (contentGroup != null) + contentGroup.DeleteGroup(); + + if (this is GuildleveDirector) + ((GuildleveDirector)this).EndGuildleveDirector(); + + List players = GetPlayerMembers(); + foreach (Actor player in players) + ((Player)player).RemoveDirector(this); + members.Clear(); + isDeleted = true; + Server.GetWorldManager().GetZone(zoneId).DeleteDirector(actorId); + } + + public void AddMember(Actor actor) + { + if (!members.Contains(actor)) + { + members.Add(actor); + + if (actor is Player) + ((Player)actor).AddDirector(this); + + if (contentGroup != null) + contentGroup.AddMember(actor); + } } - public void AddChild(Actor actor) + public void RemoveMember(Actor actor) { - if (!childrenOwners.Contains(actor)) - childrenOwners.Add(actor); + if (members.Contains(actor)) + members.Remove(actor); + if (contentGroup != null) + contentGroup.RemoveMember(actor.actorId); + if (GetPlayerMembers().Count == 0 && !isDeleting) + EndDirector(); } - public void RemoveChild(Actor actor) + public List GetMembers() { - if (childrenOwners.Contains(actor)) - childrenOwners.Remove(actor); - if (childrenOwners.Count == 0) - Server.GetWorldManager().GetZone(zoneId).DeleteDirector(actorId); + return members; } - public void RemoveChildren() + public List GetPlayerMembers() { - childrenOwners.Clear(); - Server.GetWorldManager().GetZone(zoneId).DeleteDirector(actorId); + return members.FindAll(s => s is Player); + } + + public List GetNpcMembers() + { + return members.FindAll(s => s is Npc); } public bool IsCreated() @@ -115,6 +199,21 @@ namespace FFXIVClassic_Map_Server.actors.director return isCreated; } + public bool IsDeleted() + { + return isDeleted; + } + + public bool HasContentGroup() + { + return contentGroup != null; + } + + public ContentGroup GetContentGroup() + { + return contentGroup; + } + public void GenerateActorName(int actorNumber) { //Format Class Name @@ -142,7 +241,7 @@ namespace FFXIVClassic_Map_Server.actors.director { className = className.Substring(0, 20 - zoneName.Length); } - catch (ArgumentOutOfRangeException e) + catch (ArgumentOutOfRangeException) { } //Convert actor number to base 63 @@ -162,5 +261,62 @@ namespace FFXIVClassic_Map_Server.actors.director return directorScriptPath; } + private void LoadLuaScript() + { + string luaPath = String.Format(LuaEngine.FILEPATH_DIRECTORS, GetScriptPath()); + directorScript = LuaEngine.LoadScript(luaPath); + if (directorScript == null) + Program.Log.Error("Could not find script for director {0}.", GetName()); + } + + private List CallLuaScript(string funcName, params object[] args) + { + if (directorScript != null) + { + directorScript = LuaEngine.LoadScript(String.Format(LuaEngine.FILEPATH_DIRECTORS, directorScriptPath)); + if (!directorScript.Globals.Get(funcName).IsNil()) + { + DynValue result = directorScript.Call(directorScript.Globals[funcName], args); + List lparams = LuaUtils.CreateLuaParamList(result); + return lparams; + } + else + Program.Log.Error("Could not find script for director {0}.", GetName()); + } + return null; + } + + private List StartCoroutine(string funcName, params object[] args) + { + if (directorScript != null) + { + if (!directorScript.Globals.Get(funcName).IsNil()) + { + currentCoroutine = directorScript.CreateCoroutine(directorScript.Globals[funcName]).Coroutine; + DynValue value = currentCoroutine.Resume(args); + LuaEngine.GetInstance().ResolveResume(null, currentCoroutine, value); + } + else + Program.Log.Error("Could not find script for director {0}.", GetName()); + } + return null; + } + + public void OnEventStart(Player player, object[] args) + { + object[] args2 = new object[args.Length + (player == null ? 1 : 2)]; + Array.Copy(args, 0, args2, (player == null ? 1 : 2), args.Length); + if (player != null) + { + args2[0] = player; + args2[1] = this; + } + else + args2[0] = this; + + Coroutine coroutine = directorScript.CreateCoroutine(directorScript.Globals["onEventStarted"]).Coroutine; + DynValue value = coroutine.Resume(args2); + LuaEngine.GetInstance().ResolveResume(player, coroutine, value); + } } -} +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/actors/director/GuildleveDirector.cs b/FFXIVClassic Map Server/actors/director/GuildleveDirector.cs new file mode 100644 index 00000000..0bfa49a4 --- /dev/null +++ b/FFXIVClassic Map Server/actors/director/GuildleveDirector.cs @@ -0,0 +1,256 @@ +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.area; +using FFXIVClassic_Map_Server.actors.director.Work; +using FFXIVClassic_Map_Server.actors.group; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic_Map_Server.utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.director +{ + class GuildleveDirector : Director + { + public uint guildleveId; + public Player guildleveOwner; + public byte selectedDifficulty; + + public GuildleveData guildleveData; + public GuildleveWork guildleveWork = new GuildleveWork(); + + public bool isEnded = false; + public uint completionTime = 0; + + public GuildleveDirector(uint id, Area zone, string directorPath, uint guildleveId, byte selectedDifficulty, Player guildleveOwner, params object[] args) + : base(id, zone, directorPath, true, args) + { + this.guildleveId = guildleveId; + this.selectedDifficulty = selectedDifficulty; + this.guildleveData = Server.GetGuildleveGamedata(guildleveId); + this.guildleveOwner = guildleveOwner; + + guildleveWork.aimNum[0] = guildleveData.aimNum[0]; + guildleveWork.aimNum[1] = guildleveData.aimNum[1]; + guildleveWork.aimNum[2] = guildleveData.aimNum[2]; + guildleveWork.aimNum[3] = guildleveData.aimNum[3]; + + if (guildleveWork.aimNum[0] != 0) + guildleveWork.uiState[0] = 1; + if (guildleveWork.aimNum[1] != 0) + guildleveWork.uiState[1] = 1; + if (guildleveWork.aimNum[2] != 0) + guildleveWork.uiState[2] = 1; + if (guildleveWork.aimNum[3] != 0) + guildleveWork.uiState[3] = 1; + + guildleveWork.aimNumNow[0] = guildleveWork.aimNumNow[1] = guildleveWork.aimNumNow[2] = guildleveWork.aimNumNow[3] = 0; + } + + public void LoadGuildleve() + { + + } + + public void StartGuildleve() + { + foreach (Actor p in GetPlayerMembers()) + { + Player player = (Player) p; + + //Set music + if (guildleveData.location == 1) + player.ChangeMusic(22); + else if (guildleveData.location == 2) + player.ChangeMusic(14); + else if (guildleveData.location == 3) + player.ChangeMusic(26); + else if (guildleveData.location == 4) + player.ChangeMusic(16); + + //Show Start Messages + player.SendGameMessage(Server.GetWorldManager().GetActor(), 50022, 0x20, guildleveId, selectedDifficulty); + player.SendDataPacket("attention", Server.GetWorldManager().GetActor(), "", 50022, guildleveId, selectedDifficulty); + player.SendGameMessage(Server.GetWorldManager().GetActor(), 50026, 0x20, (object)(int)guildleveData.timeLimit); + } + + guildleveWork.startTime = Utils.UnixTimeStampUTC(); + ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("guildleveWork/start", this); + propertyBuilder.AddProperty("guildleveWork.startTime"); + SendPacketsToPlayers(propertyBuilder.Done()); + } + + public void EndGuildleve(bool wasCompleted) + { + if (isEnded) + return; + isEnded = true; + + completionTime = Utils.UnixTimeStampUTC() - guildleveWork.startTime; + + if (wasCompleted) + { + foreach (Actor a in GetPlayerMembers()) + { + Player player = (Player)a; + player.MarkGuildleve(guildleveId, true, true); + player.PlayAnimation(0x02000002, true); + player.ChangeMusic(81); + player.SendGameMessage(Server.GetWorldManager().GetActor(), 50023, 0x20, (object)(int)guildleveId); + player.SendDataPacket("attention", Server.GetWorldManager().GetActor(), "", 50023, (object)(int)guildleveId); + } + } + + foreach (Actor a in GetNpcMembers()) + { + Npc npc = (Npc)a; + npc.Despawn(); + RemoveMember(a); + } + + guildleveWork.startTime = 0; + guildleveWork.signal = -1; + ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("guildleveWork/signal", this); + propertyBuilder.AddProperty("guildleveWork.signal"); + propertyBuilder.NewTarget("guildleveWork/start"); + propertyBuilder.AddProperty("guildleveWork.startTime"); + SendPacketsToPlayers(propertyBuilder.Done()); + + if (wasCompleted) + { + Npc aetheryteNode = zone.SpawnActor(1200040, String.Format("{0}:warpExit", guildleveOwner.actorName), guildleveOwner.positionX, guildleveOwner.positionY, guildleveOwner.positionZ); + AddMember(aetheryteNode); + + foreach (Actor a in GetPlayerMembers()) + { + Player player = (Player)a; + player.SendGameMessage(Server.GetWorldManager().GetActor(), 50029, 0x20); + player.SendGameMessage(Server.GetWorldManager().GetActor(), 50032, 0x20); + } + } + } + + public void AbandonGuildleve() + { + foreach (Actor p in GetPlayerMembers()) + { + Player player = (Player)p; + player.SendGameMessage(Server.GetWorldManager().GetActor(), 50147, 0x20, (object)guildleveId); + player.MarkGuildleve(guildleveId, true, false); + } + + EndGuildleve(false); + EndDirector(); + } + + //Delete ContentGroup, change music back + public void EndGuildleveDirector() + { + foreach (Actor p in GetPlayerMembers()) + { + Player player = (Player)p; + player.ChangeMusic(player.GetZone().bgmDay); + } + } + + public void SyncAllInfo() + { + ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("guildleveWork/infoVariable", this); + + if (guildleveWork.aimNum[0] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNum[0]"); + if (guildleveWork.aimNum[1] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNum[1]"); + if (guildleveWork.aimNum[2] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNum[2]"); + if (guildleveWork.aimNum[3] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNum[3]"); + + if (guildleveWork.aimNumNow[0] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNumNow[0]"); + if (guildleveWork.aimNumNow[1] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNumNow[1]"); + if (guildleveWork.aimNumNow[2] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNumNow[2]"); + if (guildleveWork.aimNumNow[3] != 0) + propertyBuilder.AddProperty("guildleveWork.aimNumNow[3]"); + + if (guildleveWork.uiState[0] != 0) + propertyBuilder.AddProperty("guildleveWork.uiState[0]"); + if (guildleveWork.uiState[1] != 0) + propertyBuilder.AddProperty("guildleveWork.uiState[1]"); + if (guildleveWork.uiState[2] != 0) + propertyBuilder.AddProperty("guildleveWork.uiState[2]"); + if (guildleveWork.uiState[3] != 0) + propertyBuilder.AddProperty("guildleveWork.uiState[3]"); + + SendPacketsToPlayers(propertyBuilder.Done()); + } + + public void UpdateAimNumNow(int index, sbyte value) + { + guildleveWork.aimNumNow[index] = value; + ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("guildleveWork/infoVariable", this); + propertyBuilder.AddProperty(String.Format("guildleveWork.aimNumNow[{0}]", index)); + SendPacketsToPlayers(propertyBuilder.Done()); + } + + public void UpdateUiState(int index, sbyte value) + { + guildleveWork.uiState[index] = value; + ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("guildleveWork/infoVariable", this); + propertyBuilder.AddProperty(String.Format("guildleveWork.uiState[{0}]", index)); + SendPacketsToPlayers(propertyBuilder.Done()); + } + + public void UpdateMarkers(int markerIndex, float x, float y, float z) + { + guildleveWork.markerX[markerIndex] = x; + guildleveWork.markerY[markerIndex] = y; + guildleveWork.markerZ[markerIndex] = z; + ActorPropertyPacketUtil propertyBuilder = new ActorPropertyPacketUtil("guildleveWork/marker", this); + propertyBuilder.AddProperty(String.Format("guildleveWork.markerX[{0}]", markerIndex)); + propertyBuilder.AddProperty(String.Format("guildleveWork.markerY[{0}]", markerIndex)); + propertyBuilder.AddProperty(String.Format("guildleveWork.markerZ[{0}]", markerIndex)); + SendPacketsToPlayers(propertyBuilder.Done()); + } + + public void SendPacketsToPlayers(List packets) + { + List players = GetPlayerMembers(); + foreach (Actor p in players) + { + ((Player)p).QueuePackets(packets); + } + } + + public static uint GlBorderIconIDToAnimID(uint iconId) + { + return iconId - 20000; + } + + public static uint GlPlateIconIDToAnimID(uint iconId) + { + return iconId - 20020; + } + + public static uint GetGLStartAnimationFromSheet(uint border, uint plate, bool isBoost) + { + return GetGLStartAnimation(GlBorderIconIDToAnimID(border), GlPlateIconIDToAnimID(plate), isBoost); + } + + public static uint GetGLStartAnimation(uint border, uint plate, bool isBoost) + { + uint borderBits = border; + uint plateBits = plate << 7; + + uint boostBits = isBoost ? (uint)0x8000 : (uint) 0; + + return 0x0B000000 | boostBits | plateBits | borderBits; + } + + } +} diff --git a/FFXIVClassic Map Server/actors/director/Work/GuildleveWork.cs b/FFXIVClassic Map Server/actors/director/Work/GuildleveWork.cs new file mode 100644 index 00000000..105a427d --- /dev/null +++ b/FFXIVClassic Map Server/actors/director/Work/GuildleveWork.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.director.Work +{ + + class GuildleveWork + { + public uint startTime = 0; + public sbyte[] aimNum = new sbyte[4]; + public sbyte[] aimNumNow = new sbyte[4]; + public sbyte[] uiState = new sbyte[4]; + public float[] markerX = new float[3]; + public float[] markerY = new float[3]; + public float[] markerZ = new float[3]; + public sbyte signal; + } + +} diff --git a/FFXIVClassic Map Server/actors/group/ContentGroup.cs b/FFXIVClassic Map Server/actors/group/ContentGroup.cs index 2cabe0f9..ca15fcbd 100644 --- a/FFXIVClassic Map Server/actors/group/ContentGroup.cs +++ b/FFXIVClassic Map Server/actors/group/ContentGroup.cs @@ -19,37 +19,53 @@ namespace FFXIVClassic_Map_Server.actors.group public ContentGroupWork contentGroupWork = new ContentGroupWork(); private Director director; private List members = new List(); + private bool isStarted = false; public ContentGroup(ulong groupIndex, Director director, uint[] initialMembers) : base(groupIndex) { if (initialMembers != null) { for (int i = 0; i < initialMembers.Length; i++) + { + Session s = Server.GetServer().GetSession(initialMembers[i]); + if (s != null) + s.GetActor().SetCurrentContentGroup(this); + members.Add(initialMembers[i]); + } } this.director = director; contentGroupWork._globalTemp.director = (ulong)director.actorId << 32; } + public void Start() + { + isStarted = true; + + SendGroupPacketsAll(members); + } + public void AddMember(Actor actor) { if (actor == null) return; + + if(!members.Contains(actor.actorId)) + members.Add(actor.actorId); - members.Add(actor.actorId); - if (actor is Character) - { + if (actor is Character) ((Character)actor).SetCurrentContentGroup(this); - SendCurrentContentSync(actor); - } - SendGroupPacketsAll(members); + + if (isStarted) + SendGroupPacketsAll(members); } public void RemoveMember(uint memberId) { members.Remove(memberId); - SendGroupPacketsAll(members); + if (isStarted) + SendGroupPacketsAll(members); CheckDestroy(); } @@ -77,9 +93,9 @@ namespace FFXIVClassic_Map_Server.actors.group groupWork.addByte(Utils.MurmurHash2("contentGroupWork.property[0]", 0), 1); groupWork.setTarget("/_init"); - SubPacket test = groupWork.buildPacket(session.id, session.id); + SubPacket test = groupWork.buildPacket(session.id); test.DebugPrintSubPacket(); - session.QueuePacket(test, true, false); + session.QueuePacket(test); } public override void SendGroupPackets(Session session) @@ -87,41 +103,26 @@ namespace FFXIVClassic_Map_Server.actors.group ulong time = Utils.MilisUnixTimeStampUTC(); List members = BuildMemberList(session.id); - session.QueuePacket(GroupHeaderPacket.buildPacket(session.id, session.GetActor().zoneId, time, this), true, false); - session.QueuePacket(GroupMembersBeginPacket.buildPacket(session.id, session.GetActor().zoneId, time, this), true, false); + session.QueuePacket(GroupHeaderPacket.buildPacket(session.id, session.GetActor().zoneId, time, this)); + session.QueuePacket(GroupMembersBeginPacket.buildPacket(session.id, session.GetActor().zoneId, time, this)); int currentIndex = 0; while (true) { if (GetMemberCount() - currentIndex >= 64) - session.QueuePacket(ContentMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); + session.QueuePacket(ContentMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); else if (GetMemberCount() - currentIndex >= 32) - session.QueuePacket(ContentMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); + session.QueuePacket(ContentMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); else if (GetMemberCount() - currentIndex >= 16) - session.QueuePacket(ContentMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); + session.QueuePacket(ContentMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); else if (GetMemberCount() - currentIndex > 0) - session.QueuePacket(ContentMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); + session.QueuePacket(ContentMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); else break; } - session.QueuePacket(GroupMembersEndPacket.buildPacket(session.id, session.GetActor().zoneId, time, this), true, false); - - } - - public void SendCurrentContentSync(Actor currentContentChanged) - { - foreach (uint memberId in members) - { - Session session = Server.GetServer().GetSession(memberId); - if (session != null) - { - ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", currentContentChanged, session.id); - propPacketUtil.AddProperty("charaWork.currentContentGroup"); - session.GetActor().QueuePackets(propPacketUtil.Done()); - } - } + session.QueuePacket(GroupMembersEndPacket.buildPacket(session.id, session.GetActor().zoneId, time, this)); } public override uint GetTypeId() @@ -135,12 +136,23 @@ namespace FFXIVClassic_Map_Server.actors.group SendGroupPacketsAll(members); } - public void DeleteAll() + public void DeleteGroup() { SendDeletePackets(members); + for (int i = 0; i < members.Count; i++) + { + Session s = Server.GetServer().GetSession(members[i]); + if (s != null) + s.GetActor().SetCurrentContentGroup(null); + Actor a = director.GetZone().FindActorInArea(members[i]); + if (a is Npc) + ((Npc)a).Despawn(); + members.Remove(members[i]); + i--; + } + Server.GetWorldManager().DeleteContentGroup(groupIndex); } - public void CheckDestroy() { bool foundSession = false; @@ -155,8 +167,12 @@ namespace FFXIVClassic_Map_Server.actors.group } if (!foundSession) - Server.GetWorldManager().DeleteContentGroup(groupIndex); + DeleteGroup(); } + public List GetMembers() + { + return members; + } } } diff --git a/FFXIVClassic Map Server/actors/group/GLContentGroup.cs b/FFXIVClassic Map Server/actors/group/GLContentGroup.cs new file mode 100644 index 00000000..ada138f3 --- /dev/null +++ b/FFXIVClassic Map Server/actors/group/GLContentGroup.cs @@ -0,0 +1,29 @@ +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.director; +using FFXIVClassic_Map_Server.actors.group.Work; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic_Map_Server.packets.send.group; +using FFXIVClassic_Map_Server.packets.send.groups; +using FFXIVClassic_Map_Server.utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.group +{ + class GLContentGroup : ContentGroup + { + public GLContentGroup(ulong groupIndex, Director director, uint[] initialMembers) + : base(groupIndex, director, initialMembers) + { + } + + public override uint GetTypeId() + { + return Group.ContentGroup_GuildleveGroup; + } + } +} diff --git a/FFXIVClassic Map Server/actors/group/Group.cs b/FFXIVClassic Map Server/actors/group/Group.cs index d2d6bdec..e5f1e164 100644 --- a/FFXIVClassic Map Server/actors/group/Group.cs +++ b/FFXIVClassic Map Server/actors/group/Group.cs @@ -120,33 +120,34 @@ namespace FFXIVClassic_Map_Server.actors.group ulong time = Utils.MilisUnixTimeStampUTC(); List members = BuildMemberList(session.id); - session.QueuePacket(GroupHeaderPacket.buildPacket(session.id, session.GetActor().zoneId, time, this), true, false); - session.QueuePacket(GroupMembersBeginPacket.buildPacket(session.id, session.GetActor().zoneId, time, this), true, false); + session.QueuePacket(GroupHeaderPacket.buildPacket(session.id, session.GetActor().zoneId, time, this)); + session.QueuePacket(GroupMembersBeginPacket.buildPacket(session.id, session.GetActor().zoneId, time, this)); int currentIndex = 0; while (true) { - if (GetMemberCount() - currentIndex >= 64) - session.QueuePacket(GroupMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); - else if (GetMemberCount() - currentIndex >= 32) - session.QueuePacket(GroupMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); - else if (GetMemberCount() - currentIndex >= 16) - session.QueuePacket(GroupMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); - else if (GetMemberCount() - currentIndex > 0) - session.QueuePacket(GroupMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); + int memberCount = Math.Min(GetMemberCount(), members.Count); + if (memberCount - currentIndex >= 64) + session.QueuePacket(GroupMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); + else if (memberCount - currentIndex >= 32) + session.QueuePacket(GroupMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); + else if (memberCount - currentIndex >= 16) + session.QueuePacket(GroupMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); + else if (memberCount - currentIndex > 0) + session.QueuePacket(GroupMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex)); else break; } - session.QueuePacket(GroupMembersEndPacket.buildPacket(session.id, session.GetActor().zoneId, time, this), true, false); + session.QueuePacket(GroupMembersEndPacket.buildPacket(session.id, session.GetActor().zoneId, time, this)); } public void SendDeletePacket(Session session) { if (session != null) - session.QueuePacket(DeleteGroupPacket.buildPacket(session.id, this), true, false); + session.QueuePacket(DeleteGroupPacket.buildPacket(session.id, this)); } public virtual void SendInitWorkValues(Session session) diff --git a/FFXIVClassic Map Server/actors/group/MonsterParty.cs b/FFXIVClassic Map Server/actors/group/MonsterParty.cs index e6ae3dae..7c0b344f 100644 --- a/FFXIVClassic Map Server/actors/group/MonsterParty.cs +++ b/FFXIVClassic Map Server/actors/group/MonsterParty.cs @@ -17,8 +17,9 @@ namespace FFXIVClassic_Map_Server.actors.group public MonsterParty(ulong groupIndex, uint[] initialMonsterMembers) : base(groupIndex) { - for (int i = 0; i < initialMonsterMembers.Length; i++) - monsterMembers.Add(initialMonsterMembers[i]); + if(initialMonsterMembers != null) + for (int i = 0; i < initialMonsterMembers.Length; i++) + monsterMembers.Add(initialMonsterMembers[i]); } public void AddMember(uint memberId) @@ -47,11 +48,11 @@ namespace FFXIVClassic_Map_Server.actors.group public override void SendInitWorkValues(Session session) { - SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex); + SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex); groupWork.setTarget("/_init"); - SubPacket test = groupWork.buildPacket(session.id, session.id); - session.QueuePacket(test, true, false); + SubPacket test = groupWork.buildPacket(session.id); + session.QueuePacket(test); } public override uint GetTypeId() diff --git a/FFXIVClassic Map Server/actors/group/Party.cs b/FFXIVClassic Map Server/actors/group/Party.cs index 436cc9d9..58e438dd 100644 --- a/FFXIVClassic Map Server/actors/group/Party.cs +++ b/FFXIVClassic Map Server/actors/group/Party.cs @@ -63,12 +63,26 @@ namespace FFXIVClassic_Map_Server.actors.group List groupMembers = new List(); groupMembers.Add(new GroupMember(id, -1, 0, false, true, Server.GetWorldManager().GetActorInWorld(id).customDisplayName)); foreach (uint charaId in members) - { - if (charaId != id) - groupMembers.Add(new GroupMember(charaId, -1, 0, false, true, Server.GetWorldManager().GetActorInWorld(charaId).customDisplayName)); + { + var chara = Server.GetWorldManager().GetActorInWorld(charaId); + if (charaId != id && chara != null) + groupMembers.Add(new GroupMember(charaId, -1, 0, false, true, chara.customDisplayName)); } return groupMembers; } + public void AddMember(uint memberId) + { + members.Add(memberId); + SendGroupPacketsAll(members); + } + + public void RemoveMember(uint memberId) + { + members.Remove(memberId); + SendGroupPacketsAll(members); + if (members.Count == 0) + Server.GetWorldManager().NoMembersInParty(this); + } } } diff --git a/FFXIVClassic Map Server/actors/group/Relation.cs b/FFXIVClassic Map Server/actors/group/Relation.cs index f9693751..600c8b9c 100644 --- a/FFXIVClassic Map Server/actors/group/Relation.cs +++ b/FFXIVClassic Map Server/actors/group/Relation.cs @@ -68,9 +68,9 @@ namespace FFXIVClassic_Map_Server.actors.group groupWork.addProperty(this, "work._globalTemp.variableCommand"); groupWork.setTarget("/_init"); - SubPacket test = groupWork.buildPacket(session.id, session.id); + SubPacket test = groupWork.buildPacket(session.id); test.DebugPrintSubPacket(); - session.QueuePacket(test, true, false); + session.QueuePacket(test); } } diff --git a/FFXIVClassic Map Server/actors/group/RetainerMeetingRelationGroup.cs b/FFXIVClassic Map Server/actors/group/RetainerMeetingRelationGroup.cs new file mode 100644 index 00000000..bbab8b32 --- /dev/null +++ b/FFXIVClassic Map Server/actors/group/RetainerMeetingRelationGroup.cs @@ -0,0 +1,58 @@ +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.chara.npc; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.dataobjects; +using FFXIVClassic_Map_Server.packets.send.group; +using FFXIVClassic_Map_Server.packets.send.groups; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.actors.group +{ + class RetainerMeetingRelationGroup : Group + { + Player player; + Retainer retainer; + + public RetainerMeetingRelationGroup(ulong groupIndex, Player player, Retainer retainer) + : base(groupIndex) + { + this.player = player; + this.retainer = retainer; + } + + public override int GetMemberCount() + { + return 2; + } + + public override List BuildMemberList(uint id) + { + List groupMembers = new List(); + + groupMembers.Add(new GroupMember(player.actorId, -1, 0x83, false, true, player.customDisplayName)); + groupMembers.Add(new GroupMember(retainer.actorId, -1, 0x83, false, true, retainer.customDisplayName)); + + return groupMembers; + } + + public override uint GetTypeId() + { + return 50003; + } + + public override void SendInitWorkValues(Session session) + { + SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex); + groupWork.setTarget("/_init"); + + SubPacket test = groupWork.buildPacket(session.id); + test.DebugPrintSubPacket(); + session.QueuePacket(test); + } + + } +} diff --git a/FFXIVClassic Map Server/actors/quest/Quest.cs b/FFXIVClassic Map Server/actors/quest/Quest.cs index 17a2e292..109f8570 100644 --- a/FFXIVClassic Map Server/actors/quest/Quest.cs +++ b/FFXIVClassic Map Server/actors/quest/Quest.cs @@ -93,7 +93,7 @@ namespace FFXIVClassic_Map_Server.Actors return false; } else - return (questFlags & (1 << bitIndex)) == (1 << bitIndex); + return (questFlags & (1 << bitIndex)) == (1 << bitIndex); } public uint GetPhase() diff --git a/FFXIVClassic Map Server/actors/world/WorldMaster.cs b/FFXIVClassic Map Server/actors/world/WorldMaster.cs index 5174cdfb..132afc4d 100644 --- a/FFXIVClassic Map Server/actors/world/WorldMaster.cs +++ b/FFXIVClassic Map Server/actors/world/WorldMaster.cs @@ -17,24 +17,24 @@ namespace FFXIVClassic_Map_Server.Actors this.className = "WorldMaster"; } - public override SubPacket CreateScriptBindPacket(uint playerActorId) + public override SubPacket CreateScriptBindPacket() { List lParams; lParams = LuaUtils.CreateLuaParamList("/World/WorldMaster_event", false, false, false, false, false, null); - return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, className, lParams); + return ActorInstantiatePacket.BuildPacket(actorId, actorName, className, lParams); } - public override BasePacket GetSpawnPackets(uint playerActorId) + public override List GetSpawnPackets() { List subpackets = new List(); - subpackets.Add(CreateAddActorPacket(playerActorId, 0)); - subpackets.Add(CreateSpeedPacket(playerActorId)); - subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x1)); - subpackets.Add(CreateNamePacket(playerActorId)); - subpackets.Add(CreateStatePacket(playerActorId)); - subpackets.Add(CreateIsZoneingPacket(playerActorId)); - subpackets.Add(CreateScriptBindPacket(playerActorId)); - return BasePacket.CreatePacket(subpackets, true, false); + subpackets.Add(CreateAddActorPacket(0)); + subpackets.Add(CreateSpeedPacket()); + subpackets.Add(CreateSpawnPositonPacket(0x1)); + subpackets.Add(CreateNamePacket()); + subpackets.Add(CreateStatePacket()); + subpackets.Add(CreateIsZoneingPacket()); + subpackets.Add(CreateScriptBindPacket()); + return subpackets; } } } diff --git a/FFXIVClassic Map Server/dataobjects/GuildleveData.cs b/FFXIVClassic Map Server/dataobjects/GuildleveData.cs new file mode 100644 index 00000000..32834f07 --- /dev/null +++ b/FFXIVClassic Map Server/dataobjects/GuildleveData.cs @@ -0,0 +1,61 @@ +using MySql.Data.MySqlClient; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.dataobjects +{ + class GuildleveData + { + public readonly uint id; + public readonly uint classType; + public readonly uint location; + public readonly ushort factionCreditRequired; + public readonly ushort level; + public readonly uint aetheryte; + public readonly uint plateId; + public readonly uint borderId; + public readonly uint objective; + public readonly byte timeLimit; + public readonly uint skill; + public readonly byte favorCount; + + public readonly sbyte[] aimNum = new sbyte[4]; + public readonly uint[] itemTarget = new uint[4]; + public readonly uint[] mobTarget = new uint[4]; + + public GuildleveData(MySqlDataReader reader) + { + id = reader.GetUInt32("id"); + classType = reader.GetUInt32("classType"); + location = reader.GetUInt32("location"); + factionCreditRequired = reader.GetUInt16("factionCreditRequired"); + level = reader.GetUInt16("level"); + aetheryte = reader.GetUInt32("aetheryte"); + plateId = reader.GetUInt32("plateId"); + borderId = reader.GetUInt32("borderId"); + objective = reader.GetUInt32("objective"); + timeLimit = reader.GetByte("timeLimit"); + skill = reader.GetUInt32("skill"); + favorCount = reader.GetByte("favorCount"); + + aimNum[0] = reader.GetSByte("aimNum1"); + aimNum[1] = reader.GetSByte("aimNum2"); + aimNum[2] = reader.GetSByte("aimNum3"); + aimNum[3] = reader.GetSByte("aimNum4"); + + itemTarget[0] = reader.GetUInt32("item1"); + itemTarget[1] = reader.GetUInt32("item2"); + itemTarget[2] = reader.GetUInt32("item3"); + itemTarget[3] = reader.GetUInt32("item4"); + + mobTarget[0] = reader.GetUInt32("mob1"); + mobTarget[1] = reader.GetUInt32("mob2"); + mobTarget[2] = reader.GetUInt32("mob3"); + mobTarget[3] = reader.GetUInt32("mob4"); + } + + } +} diff --git a/FFXIVClassic Map Server/dataobjects/InventoryItem.cs b/FFXIVClassic Map Server/dataobjects/InventoryItem.cs index aec328be..13c51499 100644 --- a/FFXIVClassic Map Server/dataobjects/InventoryItem.cs +++ b/FFXIVClassic Map Server/dataobjects/InventoryItem.cs @@ -23,14 +23,13 @@ namespace FFXIVClassic_Map_Server.dataobjects public byte materia5 = 0; //Bare Minimum - public InventoryItem(uint id, uint itemId, ushort slot) + public InventoryItem(uint id, uint itemId) { this.uniqueId = id; this.itemId = itemId; this.quantity = 1; - this.slot = slot; - Item gItem = Server.GetItemGamedata(itemId); + ItemData gItem = Server.GetItemGamedata(itemId); itemType = gItem.isExclusive ? (byte)0x3 : (byte)0x0; } @@ -55,12 +54,11 @@ namespace FFXIVClassic_Map_Server.dataobjects this.materia5 = item.materia5; } - public InventoryItem(uint uniqueId, uint itemId, int quantity, ushort slot, byte itemType, byte qualityNumber, int durability, ushort spiritbind, byte materia1, byte materia2, byte materia3, byte materia4, byte materia5) + public InventoryItem(uint uniqueId, uint itemId, int quantity, byte itemType, byte qualityNumber, int durability, ushort spiritbind, byte materia1, byte materia2, byte materia3, byte materia4, byte materia5) { this.uniqueId = uniqueId; this.itemId = itemId; this.quantity = quantity; - this.slot = slot; this.itemType = itemType; this.quality = qualityNumber; this.durability = durability; diff --git a/FFXIVClassic Map Server/dataobjects/Item.cs b/FFXIVClassic Map Server/dataobjects/ItemData.cs similarity index 94% rename from FFXIVClassic Map Server/dataobjects/Item.cs rename to FFXIVClassic Map Server/dataobjects/ItemData.cs index b9240aa7..fcdcc9bb 100644 --- a/FFXIVClassic Map Server/dataobjects/Item.cs +++ b/FFXIVClassic Map Server/dataobjects/ItemData.cs @@ -3,7 +3,7 @@ using System; namespace FFXIVClassic_Map_Server.dataobjects { - class Item + class ItemData { //Basic public readonly uint catalogID; @@ -39,7 +39,7 @@ namespace FFXIVClassic_Map_Server.dataobjects public readonly int repairLevel; public readonly int repairLicense; - public Item(MySqlDataReader reader) + public ItemData(MySqlDataReader reader) { catalogID = reader.GetUInt32("catalogID"); name = reader.GetString("name"); @@ -50,6 +50,8 @@ namespace FFXIVClassic_Map_Server.dataobjects isExclusive = reader.GetBoolean("isExclusive"); durability = reader.GetInt32("durability"); + sellPrice = reader.GetInt32("sellPrice"); + icon = reader.GetInt32("icon"); kind = reader.GetInt32("kind"); rarity = reader.GetInt32("rarity"); @@ -387,7 +389,7 @@ namespace FFXIVClassic_Map_Server.dataobjects } - class EquipmentItem : Item + class EquipmentItem : ItemData { //graphics public readonly uint graphicsWeaponId; @@ -467,6 +469,11 @@ namespace FFXIVClassic_Map_Server.dataobjects class WeaponItem : EquipmentItem { + //extra graphics + public readonly uint graphicsOffhandWeaponId; + public readonly uint graphicsOffhandEquipmentId; + public readonly uint graphicsOffhandVariantId; + //weapon sheet public readonly short attack; public readonly short magicAttack; @@ -474,7 +481,7 @@ namespace FFXIVClassic_Map_Server.dataobjects public readonly short craftMagicProcessing; public readonly short harvestPotency; public readonly short harvestLimit; - public readonly byte frequency; + public readonly byte frequency; // hit count, 2 for h2h weapons public readonly short rate; public readonly short magicRate; public readonly short craftProcessControl; @@ -483,7 +490,7 @@ namespace FFXIVClassic_Map_Server.dataobjects public readonly short magicCritical; public readonly short parry; - public readonly int damageAttributeType1; + public readonly int damageAttributeType1; // 1 slashing, 2 piercing, 3 blunt, 4 projectile public readonly float damageAttributeValue1; public readonly int damageAttributeType2; public readonly float damageAttributeValue2; @@ -493,10 +500,18 @@ namespace FFXIVClassic_Map_Server.dataobjects public readonly short damagePower; public readonly float damageInterval; public readonly short ammoVirtualDamagePower; + public readonly float dps; public WeaponItem(MySqlDataReader reader) : base(reader) { + if (!reader.IsDBNull(reader.GetOrdinal("offHandWeaponId")) && !reader.IsDBNull(reader.GetOrdinal("offHandEquipmentId")) && !reader.IsDBNull(reader.GetOrdinal("offHandVarientId"))) + { + graphicsOffhandWeaponId = reader.GetUInt32("offHandWeaponId"); + graphicsOffhandEquipmentId = reader.GetUInt32("offHandEquipmentId"); + graphicsOffhandVariantId = reader.GetUInt32("offHandVarientId"); + } + attack = reader.GetInt16("attack"); magicAttack = reader.GetInt16("magicAttack"); craftProcessing = reader.GetInt16("craftProcessing"); @@ -522,6 +537,7 @@ namespace FFXIVClassic_Map_Server.dataobjects damagePower = reader.GetInt16("damagePower"); damageInterval = reader.GetFloat("damageInterval"); ammoVirtualDamagePower = reader.GetInt16("ammoVirtualDamagePower"); + dps = (damagePower + ammoVirtualDamagePower) / damageInterval; } } diff --git a/FFXIVClassic Map Server/dataobjects/Session.cs b/FFXIVClassic Map Server/dataobjects/Session.cs index bdfcc561..73e9e06b 100644 --- a/FFXIVClassic Map Server/dataobjects/Session.cs +++ b/FFXIVClassic Map Server/dataobjects/Session.cs @@ -1,14 +1,9 @@ -using FFXIVClassic_Map_Server; -using FFXIVClassic.Common; +using FFXIVClassic.Common; using FFXIVClassic_Map_Server.Actors; -using FFXIVClassic_Map_Server.lua; using FFXIVClassic_Map_Server.packets.send.actor; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using FFXIVClassic_Map_Server.actors.chara.npc; namespace FFXIVClassic_Map_Server.dataobjects { @@ -28,17 +23,18 @@ namespace FFXIVClassic_Map_Server.dataobjects { this.id = sessionId; playerActor = new Player(this, sessionId); - actorInstanceList.Add(playerActor); } - public void QueuePacket(BasePacket basePacket) + public void QueuePacket(List packets) { - Server.GetWorldConnection().QueuePacket(basePacket); + foreach (SubPacket s in packets) + QueuePacket(s); } - public void QueuePacket(SubPacket subPacket, bool isAuthed, bool isEncrypted) + public void QueuePacket(SubPacket subPacket) { - Server.GetWorldConnection().QueuePacket(subPacket, isAuthed, isEncrypted); + subPacket.SetTargetId(id); + Server.GetWorldConnection().QueuePacket(subPacket); } public Player GetActor() @@ -68,21 +64,26 @@ namespace FFXIVClassic_Map_Server.dataobjects if (isUpdatesLocked) return; + if (playerActor.positionX == x && playerActor.positionY == y && playerActor.positionZ == z && playerActor.rotation == rot) + return; + + /* playerActor.oldPositionX = playerActor.positionX; playerActor.oldPositionY = playerActor.positionY; playerActor.oldPositionZ = playerActor.positionZ; playerActor.oldRotation = playerActor.rotation; - + playerActor.positionX = x; playerActor.positionY = y; playerActor.positionZ = z; + */ playerActor.rotation = rot; playerActor.moveState = moveState; - GetActor().zone.UpdateActorPosition(GetActor()); - + //GetActor().GetZone().UpdateActorPosition(GetActor()); + playerActor.QueuePositionUpdate(new Vector3(x,y,z)); } - long lastMilis = 0; + public void UpdateInstance(List list) { if (isUpdatesLocked) @@ -95,29 +96,29 @@ namespace FFXIVClassic_Map_Server.dataobjects //Remove missing actors for (int i = 0; i < actorInstanceList.Count; i++) { - if (list.Contains(actorInstanceList[i]) && actorInstanceList[i] is Npc) + //Retainer Instance + if (actorInstanceList[i] is Retainer && playerActor.currentSpawnedRetainer == null) { - Npc npc = (Npc)actorInstanceList[i]; - - - long milliseconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; - - - if (npc.GetUniqueId().Equals("1") && milliseconds - lastMilis > 1000) - { - lastMilis = milliseconds; - GetActor().QueuePacket(RemoveActorPacket.BuildPacket(playerActor.actorId, actorInstanceList[i].actorId)); - actorInstanceList.RemoveAt(i); - continue; - } - } - - if (!list.Contains(actorInstanceList[i])) - { - GetActor().QueuePacket(RemoveActorPacket.BuildPacket(playerActor.actorId, actorInstanceList[i].actorId)); + QueuePacket(RemoveActorPacket.BuildPacket(actorInstanceList[i].actorId)); actorInstanceList.RemoveAt(i); } - + else if (!list.Contains(actorInstanceList[i]) && !(actorInstanceList[i] is Retainer)) + { + QueuePacket(RemoveActorPacket.BuildPacket(actorInstanceList[i].actorId)); + actorInstanceList.RemoveAt(i); + } + } + + //Retainer Instance + if (playerActor.currentSpawnedRetainer != null && !playerActor.sentRetainerSpawn) + { + Actor actor = playerActor.currentSpawnedRetainer; + QueuePacket(actor.GetSpawnPackets(playerActor, 1)); + QueuePacket(actor.GetInitPackets()); + QueuePacket(actor.GetSetEventStatusPackets()); + actorInstanceList.Add(actor); + ((Npc)actor).DoOnActorSpawn(playerActor); + playerActor.sentRetainerSpawn = true; } //Add new actors or move @@ -130,17 +131,14 @@ namespace FFXIVClassic_Map_Server.dataobjects if (actorInstanceList.Contains(actor)) { - //Don't send for static characters (npcs) - if (actor is Character && ((Character)actor).isStatic) - continue; - GetActor().QueuePacket(actor.CreatePositionUpdatePacket(playerActor.actorId)); } else - { - GetActor().QueuePacket(actor.GetSpawnPackets(playerActor.actorId, 1)); - GetActor().QueuePacket(actor.GetInitPackets(playerActor.actorId)); - GetActor().QueuePacket(actor.GetSetEventStatusPackets(playerActor.actorId)); + { + QueuePacket(actor.GetSpawnPackets(playerActor, 1)); + + QueuePacket(actor.GetInitPackets()); + QueuePacket(actor.GetSetEventStatusPackets()); actorInstanceList.Add(actor); if (actor is Npc) diff --git a/FFXIVClassic Map Server/dataobjects/ZoneConnection.cs b/FFXIVClassic Map Server/dataobjects/ZoneConnection.cs index a8e957d1..ef4ab7bf 100644 --- a/FFXIVClassic Map Server/dataobjects/ZoneConnection.cs +++ b/FFXIVClassic Map Server/dataobjects/ZoneConnection.cs @@ -17,21 +17,17 @@ namespace FFXIVClassic_Map_Server.dataobjects private BlockingCollection SendPacketQueue = new BlockingCollection(1000); public int lastPartialSize = 0; - public void QueuePacket(BasePacket packet) + public void QueuePacket(SubPacket subpacket) { - List subPackets = packet.GetSubpackets(); - foreach (SubPacket s in subPackets) - SendPacketQueue.Add(s); - } + if(SendPacketQueue.Count == 1000) + FlushQueuedSendPackets(); - public void QueuePacket(SubPacket subpacket, bool isAuthed, bool isEncrypted) - { SendPacketQueue.Add(subpacket); } public void FlushQueuedSendPackets() { - if (!socket.Connected) + if (socket == null || !socket.Connected) return; while (SendPacketQueue.Count > 0) @@ -45,7 +41,7 @@ namespace FFXIVClassic_Map_Server.dataobjects socket.Send(packetBytes); } catch (Exception e) - { Program.Log.Error("Weird case, socket was d/ced: {0}", e); } + { Program.Log.Error(e, "Weird case, socket was d/ced: {0}"); } } } @@ -68,7 +64,7 @@ namespace FFXIVClassic_Map_Server.dataobjects public void RequestZoneChange(uint sessionId, uint destinationZoneId, byte spawnType, float spawnX, float spawnY, float spawnZ, float spawnRotation) { WorldRequestZoneChangePacket.BuildPacket(sessionId, destinationZoneId, spawnType, spawnX, spawnY, spawnZ, spawnRotation).DebugPrintSubPacket(); - QueuePacket(WorldRequestZoneChangePacket.BuildPacket(sessionId, destinationZoneId, spawnType, spawnX, spawnY, spawnZ, spawnRotation), true, false); + QueuePacket(WorldRequestZoneChangePacket.BuildPacket(sessionId, destinationZoneId, spawnType, spawnX, spawnY, spawnZ, spawnRotation)); } } } diff --git a/FFXIVClassic Map Server/lua/LuaEngine.cs b/FFXIVClassic Map Server/lua/LuaEngine.cs index 65f146d3..9382f4cb 100644 --- a/FFXIVClassic Map Server/lua/LuaEngine.cs +++ b/FFXIVClassic Map Server/lua/LuaEngine.cs @@ -16,18 +16,20 @@ using FFXIVClassic_Map_Server.lua; using FFXIVClassic.Common; using FFXIVClassic_Map_Server.actors.area; using System.Threading; +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara.ai.controllers; namespace FFXIVClassic_Map_Server.lua { class LuaEngine { - const string FILEPATH_PLAYER = "./scripts/player.lua"; - const string FILEPATH_ZONE = "./scripts/unique/{0}/zone.lua"; - const string FILEPATH_CONTENT = "./scripts/content/{0}.lua"; - const string FILEPATH_COMMANDS = "./scripts/commands/{0}.lua"; - const string FILEPATH_DIRECTORS = "./scripts/directors/{0}.lua"; - const string FILEPATH_NPCS = "./scripts/unique/{0}/{1}/{2}.lua"; - const string FILEPATH_QUEST = "./scripts/quests/{0}/{1}.lua"; + public const string FILEPATH_PLAYER = "./scripts/player.lua"; + public const string FILEPATH_ZONE = "./scripts/unique/{0}/zone.lua"; + public const string FILEPATH_CONTENT = "./scripts/content/{0}.lua"; + public const string FILEPATH_COMMANDS = "./scripts/commands/{0}.lua"; + public const string FILEPATH_DIRECTORS = "./scripts/directors/{0}.lua"; + public const string FILEPATH_NPCS = "./scripts/unique/{0}/{1}/{2}.lua"; + public const string FILEPATH_QUEST = "./scripts/quests/{0}/{1}.lua"; private static LuaEngine mThisEngine; private Dictionary mSleepingOnTime = new Dictionary(); @@ -102,7 +104,7 @@ namespace FFXIVClassic_Map_Server.lua } foreach (Coroutine key in mToAwake) - { + { DynValue value = key.Resume(); ResolveResume(null, key, value); } @@ -112,16 +114,227 @@ namespace FFXIVClassic_Map_Server.lua { if (mSleepingOnPlayerEvent.ContainsKey(player.actorId)) { - Coroutine coroutine = mSleepingOnPlayerEvent[player.actorId]; - mSleepingOnPlayerEvent.Remove(player.actorId); - DynValue value = coroutine.Resume(LuaUtils.CreateLuaParamObjectList(args)); - ResolveResume(null, coroutine, value); + try + { + Coroutine coroutine = mSleepingOnPlayerEvent[player.actorId]; + mSleepingOnPlayerEvent.Remove(player.actorId); + DynValue value = coroutine.Resume(LuaUtils.CreateLuaParamObjectList(args)); + ResolveResume(player, coroutine, value); + } + catch (ScriptRuntimeException e) + { + LuaEngine.SendError(player, String.Format("OnEventUpdated: {0}", e.DecoratedMessage)); + player.EndEvent(); + } } else player.EndEvent(); } - private static string GetScriptPath(Actor target) + /// + /// // todo: this is dumb, should probably make a function for each action with different default return values + /// or just make generic function and pass default value as first arg after functionName + /// + public static void CallLuaBattleFunction(Character actor, string functionName, params object[] args) + { + // todo: should use "scripts/zones/ZONE_NAME/battlenpcs/NAME.lua" instead of scripts/unique + string path = ""; + + // todo: should we call this for players too? + if (actor is Player) + { + // todo: check this is correct + path = FILEPATH_PLAYER; + } + else if (actor is Npc) + { + // todo: this is probably unnecessary as im not sure there were pets for players + if (!(actor.aiContainer.GetController()?.GetPetMaster() is Player)) + path = String.Format("./scripts/unique/{0}/{1}/{2}.lua", actor.zone.zoneName, actor is BattleNpc ? "Monster" : "PopulaceStandard", ((Npc)actor).GetUniqueId()); + } + // dont wanna throw an error if file doesnt exist + if (File.Exists(path)) + { + var script = LoadGlobals(); + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleFunction [{functionName}] {e.Message}"); + } + DynValue res = new DynValue(); + + if (!script.Globals.Get(functionName).IsNil()) + { + res = script.Call(script.Globals.Get(functionName), args); + } + } + } + + public static int CallLuaStatusEffectFunction(Character actor, StatusEffect effect, string functionName, params object[] args) + { + // todo: this is stupid, load the actual effect name from db table + string path = $"./scripts/effects/{effect.GetName()}.lua"; + + if (File.Exists(path)) + { + var script = LoadGlobals(); + + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaStatusEffectFunction [{functionName}] {e.Message}"); + } + DynValue res = new DynValue(); + + if (!script.Globals.Get(functionName).IsNil()) + { + res = script.Call(script.Globals.Get(functionName), args); + if (res != null) + return (int)res.Number; + } + } + else + { + Program.Log.Error($"LuaEngine.CallLuaStatusEffectFunction [{effect.GetName()}] Unable to find script {path}"); + } + return -1; + } + + public static int CallLuaBattleCommandFunction(Character actor, BattleCommand command, string folder, string functionName, params object[] args) + { + string path = $"./scripts/commands/{folder}/{command.name}.lua"; + + if (File.Exists(path)) + { + var script = LoadGlobals(); + + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{functionName}] {e.Message}"); + } + DynValue res = new DynValue(); + + if (!script.Globals.Get(functionName).IsNil()) + { + res = script.Call(script.Globals.Get(functionName), args); + if (res != null) + return (int)res.Number; + } + } + else + { + path = $"./scripts/commands/{folder}/default.lua"; + //Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}"); + var script = LoadGlobals(); + + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{functionName}] {e.Message}"); + } + DynValue res = new DynValue(); + // DynValue r = script.Globals.Get(functionName); + + if (!script.Globals.Get(functionName).IsNil()) + { + res = script.Call(script.Globals.Get(functionName), args); + if (res != null) + return (int)res.Number; + } + } + return -1; + } + + + public static void LoadBattleCommandScript(BattleCommand command, string folder) + { + string path = $"./scripts/commands/{folder}/{command.name}.lua"; + + if (File.Exists(path)) + { + var script = LoadGlobals(); + + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}"); + } + command.script = script; + } + else + { + path = $"./scripts/commands/{folder}/default.lua"; + //Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}"); + var script = LoadGlobals(); + + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}"); + } + + command.script = script; + } + } + + public static void LoadStatusEffectScript(StatusEffect effect) + { + string path = $"./scripts/effects/{effect.GetName()}.lua"; + + if (File.Exists(path)) + { + var script = LoadGlobals(); + + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}"); + } + effect.script = script; + } + else + { + path = $"./scripts/effects/default.lua"; + //Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}"); + var script = LoadGlobals(); + + try + { + script.DoFile(path); + } + catch (Exception e) + { + Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}"); + } + + effect.script = script; + } + } + + + public static string GetScriptPath(Actor target) { if (target is Player) { @@ -207,7 +420,7 @@ namespace FFXIVClassic_Map_Server.lua private void CallLuaFunctionNpc(Player player, Npc target, string funcName, bool optional, params object[] args) { - object[] args2 = new object[args.Length + (player == null ? 1:2)]; + object[] args2 = new object[args.Length + (player == null ? 1 : 2)]; Array.Copy(args, 0, args2, (player == null ? 1 : 2), args.Length); if (player != null) { @@ -258,7 +471,7 @@ namespace FFXIVClassic_Map_Server.lua catch (ScriptRuntimeException e) { SendError(player, e.DecoratedMessage); - } + } } } @@ -283,9 +496,9 @@ namespace FFXIVClassic_Map_Server.lua if (script != null) { if (!script.Globals.Get(funcName).IsNil()) - { + { //Run Script - DynValue result = script.Call(script.Globals[funcName], args2); + DynValue result = script.Call(script.Globals[funcName], args2); List lparams = LuaUtils.CreateLuaParamList(result); return lparams; } @@ -315,7 +528,7 @@ namespace FFXIVClassic_Map_Server.lua DynValue result = script.Call(script.Globals[funcName], args); List lparams = LuaUtils.CreateLuaParamList(result); return lparams; - } + } } return null; } @@ -340,9 +553,18 @@ namespace FFXIVClassic_Map_Server.lua { if (!script.Globals.Get(funcName).IsNil()) { - Coroutine coroutine = script.CreateCoroutine(script.Globals[funcName]).Coroutine; - DynValue value = coroutine.Resume(args2); - ResolveResume(player, coroutine, value); + try + { + Coroutine coroutine = script.CreateCoroutine(script.Globals[funcName]).Coroutine; + DynValue value = coroutine.Resume(args2); + ResolveResume(player, coroutine, value); + } + catch(Exception e) + { + player.SendMessage(0x20, "", e.Message); + player.EndEvent(); + + } } else { @@ -354,7 +576,7 @@ namespace FFXIVClassic_Map_Server.lua { if (!(target is Area) && !optional) SendError(player, String.Format("Could not find script for actor {0}.", target.GetName())); - } + } } public void EventStarted(Player player, Actor target, EventStartPacket eventStart) @@ -363,23 +585,37 @@ namespace FFXIVClassic_Map_Server.lua lparams.Insert(0, new LuaParam(2, eventStart.triggerName)); if (mSleepingOnPlayerEvent.ContainsKey(player.actorId)) { - Coroutine coroutine = mSleepingOnPlayerEvent[player.actorId]; - mSleepingOnPlayerEvent.Remove(player.actorId); - DynValue value = coroutine.Resume(); - ResolveResume(null, coroutine, value); + Coroutine coroutine = mSleepingOnPlayerEvent[player.actorId]; + mSleepingOnPlayerEvent.Remove(player.actorId); + + try + { + DynValue value = coroutine.Resume(); + ResolveResume(null, coroutine, value); + } + catch (ScriptRuntimeException e) + { + LuaEngine.SendError(player, String.Format("OnEventStarted: {0}", e.DecoratedMessage)); + player.EndEvent(); + } + } + else + { + if (target is Director) + ((Director)target).OnEventStart(player, LuaUtils.CreateLuaParamObjectList(lparams)); + else + CallLuaFunction(player, target, "onEventStarted", false, LuaUtils.CreateLuaParamObjectList(lparams)); } - else - CallLuaFunction(player, target, "onEventStarted", false, LuaUtils.CreateLuaParamObjectList(lparams)); } - private DynValue ResolveResume(Player player, Coroutine coroutine, DynValue value) + public DynValue ResolveResume(Player player, Coroutine coroutine, DynValue value) { if (value == null || value.IsVoid()) return value; - if (value.String != null && value.String.Equals("_WAIT_EVENT")) - { - GetInstance().AddWaitEventCoroutine(player, coroutine); + if (player != null && value.String != null && value.String.Equals("_WAIT_EVENT")) + { + GetInstance().AddWaitEventCoroutine(player, coroutine); } else if (value.Tuple != null && value.Tuple.Length >= 1 && value.Tuple[0].String != null) { @@ -406,9 +642,13 @@ namespace FFXIVClassic_Map_Server.lua public static void RunGMCommand(Player player, String cmd, string[] param, bool help = false) { bool playerNull = player == null; - if (playerNull && param.Length >= 2) - player = Server.GetWorldManager().GetPCInWorld(param[1] + " " + param[2]); - + if (playerNull) + { + 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 var path = String.Format("./scripts/commands/gm/{0}.lua", cmd.ToLower()); @@ -535,9 +775,17 @@ namespace FFXIVClassic_Map_Server.lua // run the script //script.Call(script.Globals["onTrigger"], LuaParam.ToArray()); - Coroutine coroutine = script.CreateCoroutine(script.Globals["onTrigger"]).Coroutine; - DynValue value = coroutine.Resume(LuaParam.ToArray()); - GetInstance().ResolveResume(player, coroutine, value); + // gm commands dont need to be coroutines? + try + { + Coroutine coroutine = script.CreateCoroutine(script.Globals["onTrigger"]).Coroutine; + DynValue value = coroutine.Resume(LuaParam.ToArray()); + GetInstance().ResolveResume(player, coroutine, value); + } + catch (Exception e) + { + Program.Log.Error("LuaEngine.RunGMCommand: {0} - {1}", path, e.Message); + } return; } } @@ -575,24 +823,23 @@ namespace FFXIVClassic_Map_Server.lua script.Globals["GetStaticActor"] = (Func)Server.GetStaticActors; script.Globals["GetStaticActorById"] = (Func)Server.GetStaticActors; script.Globals["GetWorldMaster"] = (Func)Server.GetWorldManager().GetActor; - script.Globals["GetItemGamedata"] = (Func)Server.GetItemGamedata; + script.Globals["GetItemGamedata"] = (Func)Server.GetItemGamedata; + script.Globals["GetGuildleveGamedata"] = (Func)Server.GetGuildleveGamedata; script.Globals["GetLuaInstance"] = (Func)LuaEngine.GetInstance; script.Options.DebugPrint = s => { Program.Log.Debug(s); }; return script; } - private static void SendError(Player player, string message) + public static void SendError(Player player, string message) { message = "[LuaError] " + message; if (player == null) return; List SendError = new List(); - SendError.Add(EndEventPacket.BuildPacket(player.actorId, player.currentEventOwner, player.currentEventName)); player.SendMessage(SendMessagePacket.MESSAGE_TYPE_SYSTEM_ERROR, "", message); - player.playerSession.QueuePacket(BasePacket.CreatePacket(SendError, true, false)); + player.QueuePacket(EndEventPacket.BuildPacket(player.actorId, player.currentEventOwner, player.currentEventName)); } - + } } - \ No newline at end of file diff --git a/FFXIVClassic Map Server/lua/LuaUtils.cs b/FFXIVClassic Map Server/lua/LuaUtils.cs index a0eb86a2..f1181564 100644 --- a/FFXIVClassic Map Server/lua/LuaUtils.cs +++ b/FFXIVClassic Map Server/lua/LuaUtils.cs @@ -116,6 +116,12 @@ namespace FFXIVClassic_Map_Server public static void WriteLuaParams(BinaryWriter writer, List luaParams) { + if (luaParams == null) + { + Program.Log.Error("LuaUtils.WriteLuaParams LuaParams are null!"); + return; + } + foreach (LuaParam l in luaParams) { if (l.typeID == 0x1) diff --git a/FFXIVClassic Map Server/navmesh/SHARPNAV_LICENSE b/FFXIVClassic Map Server/navmesh/SHARPNAV_LICENSE new file mode 100644 index 00000000..bfec8b95 --- /dev/null +++ b/FFXIVClassic Map Server/navmesh/SHARPNAV_LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2013-2016 Robert Rouhani and other contributors (see CONTRIBUTORS file). + +SharpNav contains some altered source code from Recast Navigation, Copyright (c) 2009 Mikko Mononen memon@inside.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/FFXIVClassic Map Server/navmesh/SharpNav.dll b/FFXIVClassic Map Server/navmesh/SharpNav.dll new file mode 100644 index 00000000..be560e8f Binary files /dev/null and b/FFXIVClassic Map Server/navmesh/SharpNav.dll differ diff --git a/FFXIVClassic Map Server/navmesh/wil0Field01.snb b/FFXIVClassic Map Server/navmesh/wil0Field01.snb new file mode 100644 index 00000000..e8a49ee9 Binary files /dev/null and b/FFXIVClassic Map Server/navmesh/wil0Field01.snb differ diff --git a/FFXIVClassic Map Server/packages.config b/FFXIVClassic Map Server/packages.config index 4e137685..2d2dc7c3 100644 --- a/FFXIVClassic Map Server/packages.config +++ b/FFXIVClassic Map Server/packages.config @@ -1,11 +1,10 @@  - - + diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Receive/SessionBeginPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Receive/SessionBeginPacket.cs index 3baf9b98..f970004e 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Receive/SessionBeginPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Receive/SessionBeginPacket.cs @@ -9,11 +9,25 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Receive { class SessionBeginPacket { + public bool isLogin; public bool invalidPacket = false; public SessionBeginPacket(byte[] data) { - + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryReader binReader = new BinaryReader(mem)) + { + try + { + isLogin = binReader.ReadByte() != 0; + } + catch (Exception) + { + invalidPacket = true; + } + } + } } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs index fbee5641..5a64741f 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/CreateLinkshellPacket.cs @@ -24,7 +24,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write((UInt32)master); } } - return new SubPacket(true, OPCODE, 0, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs index cf93bec4..d9460354 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/DeleteLinkshellPacket.cs @@ -21,7 +21,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write(Encoding.ASCII.GetBytes(name), 0, Encoding.ASCII.GetByteCount(name) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(name)); } } - return new SubPacket(true, OPCODE, 0, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs index a753bc9c..02ca4f25 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/GroupInviteResultPacket.cs @@ -25,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write((UInt32)result); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellChangePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellChangePacket.cs index ec256612..b4e894c8 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellChangePacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellChangePacket.cs @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write(Encoding.ASCII.GetBytes(lsName), 0, Encoding.ASCII.GetByteCount(lsName) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(lsName)); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs index f6823db0..9ad80615 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInviteCancelPacket.cs @@ -17,7 +17,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group public static SubPacket BuildPacket(Session session) { byte[] data = new byte[PACKET_SIZE - 0x20]; - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs index fbe14e10..fee38aa0 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellInvitePacket.cs @@ -25,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write(Encoding.ASCII.GetBytes(linkshellName), 0, Encoding.ASCII.GetByteCount(linkshellName) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(linkshellName)); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs index 008add1a..b902903f 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellLeavePacket.cs @@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write(Encoding.ASCII.GetBytes(lsName), 0, Encoding.ASCII.GetByteCount(lsName) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(lsName)); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs index b2584dc1..1791e492 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/LinkshellRankChangePacket.cs @@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write((Byte)rank); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs index c1828777..c61a7d7c 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/ModifyLinkshellPacket.cs @@ -35,7 +35,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group } } - return new SubPacket(true, OPCODE, 0, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyInvitePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyInvitePacket.cs index 8df14982..de8ccb31 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyInvitePacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyInvitePacket.cs @@ -25,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write(Encoding.ASCII.GetBytes(name), 0, Encoding.ASCII.GetByteCount(name) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(name)); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } public static SubPacket BuildPacket(Session session, uint actorId) @@ -39,7 +39,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write((UInt32)actorId); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyLeavePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyLeavePacket.cs index 4251bc25..652e83d0 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyLeavePacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyLeavePacket.cs @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write((UInt16)(isDisband ? 1 : 0)); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyModifyPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyModifyPacket.cs index 064dff9d..4c58773e 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyModifyPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/Group/PartyModifyPacket.cs @@ -24,7 +24,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write(Encoding.ASCII.GetBytes(name), 0, Encoding.ASCII.GetByteCount(name) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(name)); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } public static SubPacket BuildPacket(Session session, ushort command, uint actorId) @@ -38,7 +38,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group binWriter.Write((UInt32)actorId); } } - return new SubPacket(true, OPCODE, session.id, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionBeginConfirmPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionBeginConfirmPacket.cs index b56439b5..d05e8381 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionBeginConfirmPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionBeginConfirmPacket.cs @@ -21,7 +21,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send binWriter.Write((UInt16)errorCode); } } - return new SubPacket(true, OPCODE, 0, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionEndConfirmPacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionEndConfirmPacket.cs index 9e220947..3f518d51 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionEndConfirmPacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/SessionEndConfirmPacket.cs @@ -22,7 +22,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send binWriter.Write((UInt32)destinationZone); } } - return new SubPacket(true, OPCODE, 0, session.id, data); + return new SubPacket(true, OPCODE, session.id, data); } } } diff --git a/FFXIVClassic Map Server/packets/WorldPackets/Send/WorldRequestZoneChangePacket.cs b/FFXIVClassic Map Server/packets/WorldPackets/Send/WorldRequestZoneChangePacket.cs index 259d5631..197ca935 100644 --- a/FFXIVClassic Map Server/packets/WorldPackets/Send/WorldRequestZoneChangePacket.cs +++ b/FFXIVClassic Map Server/packets/WorldPackets/Send/WorldRequestZoneChangePacket.cs @@ -31,7 +31,7 @@ namespace FFXIVClassic_Map_Server.packets.WorldPackets.Send } } - return new SubPacket(OPCODE, sessionId, sessionId, data); + return new SubPacket(OPCODE, sessionId, data); } } } diff --git a/FFXIVClassic Map Server/packets/receive/CountdownRequestPacket.cs b/FFXIVClassic Map Server/packets/receive/CountdownRequestPacket.cs new file mode 100644 index 00000000..23701717 --- /dev/null +++ b/FFXIVClassic Map Server/packets/receive/CountdownRequestPacket.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; + +namespace FFXIVClassic_Map_Server.packets.receive +{ + class CountdownRequestPacket + { + public bool invalidPacket = false; + public byte countdownLength; + public ulong syncTime; + + public CountdownRequestPacket(byte[] data) + { + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryReader binReader = new BinaryReader(mem)) + { + try{ + countdownLength = binReader.ReadByte(); + binReader.BaseStream.Seek(8, SeekOrigin.Begin); + syncTime = binReader.ReadUInt64(); + } + catch (Exception){ + invalidPacket = true; + } + } + } + } + } +} diff --git a/FFXIVClassic Map Server/packets/receive/SetTargetPacket.cs b/FFXIVClassic Map Server/packets/receive/SetTargetPacket.cs index b38cd5a7..b433a094 100644 --- a/FFXIVClassic Map Server/packets/receive/SetTargetPacket.cs +++ b/FFXIVClassic Map Server/packets/receive/SetTargetPacket.cs @@ -7,7 +7,7 @@ namespace FFXIVClassic_Map_Server.packets.receive { public bool invalidPacket = false; public uint actorID; - public uint otherVal; //Usually 0xE0000000 + public uint attackTarget; //Usually 0xE0000000 public SetTargetPacket(byte[] data) { @@ -17,7 +17,7 @@ namespace FFXIVClassic_Map_Server.packets.receive { try{ actorID = binReader.ReadUInt32(); - otherVal = binReader.ReadUInt32(); + attackTarget = binReader.ReadUInt32(); } catch (Exception){ invalidPacket = true; diff --git a/FFXIVClassic Map Server/packets/send/Actor/ActorDoEmotePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/ActorDoEmotePacket.cs index f8af6034..312ab394 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/ActorDoEmotePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/ActorDoEmotePacket.cs @@ -10,26 +10,29 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00E1; public const uint PACKET_SIZE = 0x30; - public static SubPacket BuildPacket(uint sourceActorId, uint targetActorId, uint targettedActorId, uint emoteID) + public static SubPacket BuildPacket(uint sourceActorId, uint targettedActorId, uint animationId, uint descriptionId) { byte[] data = new byte[PACKET_SIZE - 0x20]; - if (targettedActorId == 0xC0000000) + if (targettedActorId == 0) + { targettedActorId = sourceActorId; + if (descriptionId != 10105) + descriptionId++; + } using (MemoryStream mem = new MemoryStream(data)) { using (BinaryWriter binWriter = new BinaryWriter(mem)) { - uint realAnimID = 0x5000000 | ((emoteID - 100) << 12); - uint realDescID = 20000 + ((emoteID - 1) * 10) + (targettedActorId == sourceActorId ? (uint)2 : (uint)1); + uint realAnimID = 0x5000000 | (animationId << 12); binWriter.Write((UInt32)realAnimID); binWriter.Write((UInt32)targettedActorId); - binWriter.Write((UInt32)realDescID); + binWriter.Write((UInt32)descriptionId); } } - SubPacket packet = new SubPacket(OPCODE, sourceActorId, targetActorId, data); + SubPacket packet = new SubPacket(OPCODE, sourceActorId, data); packet.DebugPrintSubPacket(); return packet; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/ActorInstantiatePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/ActorInstantiatePacket.cs index 49b50b9f..e76853cf 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/ActorInstantiatePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/ActorInstantiatePacket.cs @@ -13,7 +13,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00CC; public const uint PACKET_SIZE = 0x128; - public static SubPacket BuildPacket(uint sourceActorID, uint targetActorID, string objectName, string className, List initParams) + public static SubPacket BuildPacket(uint sourceActorId, string objectName, string className, List initParams) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -33,7 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, sourceActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/ActorSpecialGraphicPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/ActorSpecialGraphicPacket.cs index de6fc3de..45945c2c 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/ActorSpecialGraphicPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/ActorSpecialGraphicPacket.cs @@ -15,7 +15,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00E3; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, int iconCode) + public static SubPacket BuildPacket(uint sourceActorId, int iconCode) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, targetActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/AddActorPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/AddActorPacket.cs index 138fdbe7..33aec37c 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/AddActorPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/AddActorPacket.cs @@ -1,7 +1,5 @@ using FFXIVClassic.Common; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor { class AddActorPacket @@ -9,12 +7,12 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00CA; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint actorID, byte val) + public static SubPacket BuildPacket(uint sourceActorId, byte val) { byte[] data = new byte[PACKET_SIZE-0x20]; data[0] = val; //Why? - return new SubPacket(OPCODE, playerActorID, actorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/BattleAction1Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/BattleAction1Packet.cs deleted file mode 100644 index 84122ce1..00000000 --- a/FFXIVClassic Map Server/packets/send/Actor/BattleAction1Packet.cs +++ /dev/null @@ -1,27 +0,0 @@ -using FFXIVClassic.Common; -using System.IO; - -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.actor -{ - class BattleAction1Packet - { - public const ushort OPCODE = 0x0139; - public const uint PACKET_SIZE = 0x58; - - public static SubPacket BuildPacket(uint sourceId, uint targetId) - { - byte[] data = new byte[PACKET_SIZE - 0x20]; - - using (MemoryStream mem = new MemoryStream(data)) - { - using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - } - } - - return new SubPacket(OPCODE, sourceId, targetId, data); - } - } -} diff --git a/FFXIVClassic Map Server/packets/send/Actor/DeleteAllActorsPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/DeleteAllActorsPacket.cs index b2052341..c2234468 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/DeleteAllActorsPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/DeleteAllActorsPacket.cs @@ -1,7 +1,5 @@ using FFXIVClassic.Common; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor { class DeleteAllActorsPacket @@ -9,9 +7,9 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x0007; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID) + public static SubPacket BuildPacket(uint sourceActorId) { - return new SubPacket(OPCODE, playerActorID, playerActorID, new byte[8]); + return new SubPacket(OPCODE, sourceActorId, new byte[8]); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/MoveActorToPositionPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/MoveActorToPositionPacket.cs index e807c4d6..884fb575 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/MoveActorToPositionPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/MoveActorToPositionPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00CF; public const uint PACKET_SIZE = 0x50; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, float x, float y, float z, float rot, ushort moveState) + public static SubPacket BuildPacket(uint sourceActorId, float x, float y, float z, float rot, ushort moveState) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - SubPacket packet = new SubPacket(OPCODE, playerActorID, targetActorID, data); + SubPacket packet = new SubPacket(OPCODE, sourceActorId, data); return packet; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/PlayAnimationOnActorPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/PlayAnimationOnActorPacket.cs index c186e0d9..cd5ed453 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/PlayAnimationOnActorPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/PlayAnimationOnActorPacket.cs @@ -12,9 +12,9 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00DA; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, uint animationID) + public static SubPacket BuildPacket(uint sourceActorId, uint animationID) { - return new SubPacket(OPCODE, playerActorID, targetActorID, BitConverter.GetBytes((ulong)animationID)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((ulong)animationID)); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/PlayBGAnimation.cs b/FFXIVClassic Map Server/packets/send/Actor/PlayBGAnimation.cs index 7ffc20f7..0e8a4e59 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/PlayBGAnimation.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/PlayBGAnimation.cs @@ -1,8 +1,5 @@ using FFXIVClassic.Common; -using System; using System.IO; - -using FFXIVClassic.Common; using System.Text; namespace FFXIVClassic_Map_Server.packets.send.actor @@ -12,7 +9,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00D9; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, string animName) + public static SubPacket BuildPacket(uint sourceActorId, string animName) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -24,7 +21,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/RemoveActorPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/RemoveActorPacket.cs index 9240f53e..06344f2a 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/RemoveActorPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/RemoveActorPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00CB; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint actorId) + public static SubPacket BuildPacket(uint sourceActorId) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -18,11 +18,11 @@ namespace FFXIVClassic_Map_Server.packets.send.actor { using (BinaryWriter binWriter = new BinaryWriter(mem)) { - binWriter.Write((UInt32)actorId); + binWriter.Write((UInt32)sourceActorId); } } - return new SubPacket(OPCODE, actorId, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorAppearancePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorAppearancePacket.cs index 0e4ed34a..f1a864ea 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorAppearancePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorAppearancePacket.cs @@ -53,7 +53,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor appearanceIDs = appearanceTable; } - public SubPacket BuildPacket(uint playerActorID, uint actorID) + public SubPacket BuildPacket(uint sourceActorId) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -74,7 +74,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } - SubPacket packet = new SubPacket(OPCODE, playerActorID, actorID, data); + SubPacket packet = new SubPacket(OPCODE, sourceActorId, data); return packet; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/_0xD8Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorBGPropertiesPacket.cs similarity index 74% rename from FFXIVClassic Map Server/packets/send/Actor/_0xD8Packet.cs rename to FFXIVClassic Map Server/packets/send/Actor/SetActorBGPropertiesPacket.cs index 6753f7b6..f1acefcb 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/_0xD8Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorBGPropertiesPacket.cs @@ -5,12 +5,12 @@ using System; namespace FFXIVClassic_Map_Server.packets.send.actor { - class _0xD8Packet + class SetActorBGPropertiesPacket { public const ushort OPCODE = 0x00D8; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, uint val1, uint val2) + public static SubPacket BuildPacket(uint sourceActorId, uint val1, uint val2) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorIconPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorIconPacket.cs index 6dde6397..e5d52265 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorIconPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorIconPacket.cs @@ -14,7 +14,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x0145; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, uint iconCode) + public static SubPacket BuildPacket(uint sourceActorId, uint iconCode) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -26,7 +26,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorIsZoningPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorIsZoningPacket.cs index 4dfa29f3..13dff947 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorIsZoningPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorIsZoningPacket.cs @@ -7,11 +7,11 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x017B; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, bool isDimmed) + public static SubPacket BuildPacket(uint sourceActorId, bool isDimmed) { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = (byte)(isDimmed ? 1 : 0); - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorNamePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorNamePacket.cs index 9df353ce..bd421600 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorNamePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorNamePacket.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x013D; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, uint displayNameID, string customName) + public static SubPacket BuildPacket(uint sourceActorId, uint displayNameID, string customName) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -29,7 +29,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - SubPacket packet = new SubPacket(OPCODE, playerActorID, targetActorID, data); + SubPacket packet = new SubPacket(OPCODE, sourceActorId, data); return packet; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorPositionPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorPositionPacket.cs index fce26791..a19dba52 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorPositionPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorPositionPacket.cs @@ -19,7 +19,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort SPAWNTYPE_WARP_DUTY2 = 6; public const ushort SPAWNTYPE_WARP_LIGHT = 7; - public static SubPacket BuildPacket(uint sourceActorID, uint targetActorID, uint actorId, float x, float y, float z, float rotation, ushort spawnType, bool isZoningPlayer) + public static SubPacket BuildPacket(uint sourceActorId, uint actorId, float x, float y, float z, float rotation, ushort spawnType, bool isZoningPlayer) { byte[] data = new byte[PACKET_SIZE-0x20]; @@ -41,7 +41,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, sourceActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorPropetyPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorPropetyPacket.cs index a20ccb82..1d9d1fe0 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorPropetyPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorPropetyPacket.cs @@ -5,8 +5,6 @@ using System.Linq; using System.Reflection; using System.Text; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor { class SetActorPropetyPacket @@ -112,8 +110,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor { string[] split = name.Split('.'); int arrayIndex = 0; - - if (!(split[0].Equals("work") || split[0].Equals("charaWork") || split[0].Equals("playerWork") || split[0].Equals("npcWork"))) + if (!(split[0].Equals("work") || split[0].Equals("charaWork") || split[0].Equals("playerWork") || split[0].Equals("npcWork") || split[0].Equals("guildleveWork"))) return false; Object curObj = actor; @@ -159,6 +156,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor return AddByte(id, (byte)(((bool)curObj) ? 1 : 0)); else if (curObj is byte) return AddByte(id, (byte)curObj); + else if (curObj is sbyte) + return AddByte(id, (byte)(sbyte)curObj); else if (curObj is ushort) return AddShort(id, (ushort)curObj); else if (curObj is short) @@ -207,14 +206,14 @@ namespace FFXIVClassic_Map_Server.packets.send.actor currentTarget = newTarget; } - public SubPacket BuildPacket(uint playerActorID, uint actorID) + public SubPacket BuildPacket(uint sourceActorId) { binWriter.Seek(0, SeekOrigin.Begin); binWriter.Write((byte)runningByteTotal); CloseStreams(); - SubPacket packet = new SubPacket(OPCODE, actorID, playerActorID, data); + SubPacket packet = new SubPacket(OPCODE, sourceActorId, data); return packet; } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorSpeedPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorSpeedPacket.cs index d266ef5b..0e557cf4 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorSpeedPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorSpeedPacket.cs @@ -14,7 +14,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const float DEFAULT_RUN = 5.0f; public const float DEFAULT_ACTIVE = 5.0f; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID) + public static SubPacket BuildPacket(uint sourceActorId) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -40,10 +40,10 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, float stopSpeed, float walkSpeed, float runSpeed, float activeSpeed) + public static SubPacket BuildPacket(uint sourceActorId, float stopSpeed, float walkSpeed, float runSpeed, float activeSpeed) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -69,7 +69,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs index 356b4f38..8bab2346 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatePacket.cs @@ -1,8 +1,6 @@ using FFXIVClassic.Common; using System; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor { class SetActorStatePacket @@ -30,10 +28,10 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x134; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetID, uint mainState, uint subState) + public static SubPacket BuildPacket(uint sourceActorId, uint mainState, uint subState) { ulong combined = (mainState & 0xFF) | ((subState & 0xFF) << 8); - return new SubPacket(OPCODE, playerActorID, targetID, BitConverter.GetBytes(combined)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes(combined)); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusAllPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusAllPacket.cs index 429096ef..79bbbf90 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusAllPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusAllPacket.cs @@ -2,8 +2,6 @@ using System; using System.IO; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor { class SetActorStatusAllPacket @@ -11,7 +9,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x0179; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, ushort[] statusIds) + public static SubPacket BuildPacket(uint sourceActorId, ushort[] statusIds) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -28,7 +26,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - SubPacket packet = new SubPacket(OPCODE, playerActorID, targetActorID, data); + SubPacket packet = new SubPacket(OPCODE, sourceActorId, data); return packet; } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusPacket.cs index 8bd6172d..2cdaf82e 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorStatusPacket.cs @@ -2,8 +2,6 @@ using System; using System.IO; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor { class SetActorStatusPacket @@ -11,7 +9,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x0177; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, ushort index, ushort statusCode) + public static SubPacket BuildPacket(uint sourceActorId, ushort index, ushort statusCode) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -24,7 +22,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatPacket.cs deleted file mode 100644 index b1f36b40..00000000 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatPacket.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.IO; - -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.actor -{ - class SetActorSubStatPacket - { - public const ushort OPCODE = 0x144; - public const uint PACKET_SIZE = 0x28; - - public static SubPacket BuildPacket(uint playerActorID, uint targetID, byte breakage, int leftChant, int rightChant, int guard, int wasteStat, int statMode, uint idleAnimationId) - { - byte[] data = new byte[PACKET_SIZE - 0x20]; - - using (MemoryStream mem = new MemoryStream(data)) - { - using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - binWriter.Write((byte)breakage); - binWriter.Write((byte)(((leftChant & 0xF) << 8) | (rightChant & 0xF))); - binWriter.Write((byte)(guard & 0xF)); - binWriter.Write((byte)((wasteStat & 0xF) << 8)); - binWriter.Write((byte)(statMode & 0xF)); - binWriter.Write((byte)0); - binWriter.Write((UInt16)(idleAnimationId&0xFFFF)); - } - } - - return new SubPacket(OPCODE, playerActorID, targetID, data); - } - } -} diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatePacket.cs new file mode 100644 index 00000000..bbb2b747 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorSubStatePacket.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; + +using FFXIVClassic.Common; +using FFXIVClassic_Map_Server.actors.chara; + +namespace FFXIVClassic_Map_Server.packets.send.actor +{ + class SetActorSubStatePacket + { + public const ushort OPCODE = 0x144; + public const uint PACKET_SIZE = 0x28; + + enum SubStat : int + { + Breakage = 0x00, // (index goes high to low, bitflags) + Chant = 0x01, // [Nibbles: left / right hand = value]) (AKA SubStatObject) + Guard = 0x02, // [left / right hand = true] 0,1,2,3) ||| High byte also defines how many bools to use as flags for byte 0x4. + Waste = 0x03, // (High Nibble) + Mode = 0x04, // ??? + Unknown = 0x05, // ??? + SubStatMotionPack = 0x06, + Unknown2 = 0x07, + } + public static SubPacket BuildPacket(uint sourceActorId, SubState substate) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Write((byte)substate.breakage); + binWriter.Write((byte)substate.chantId); + binWriter.Write((byte)(substate.guard & 0xF)); + binWriter.Write((byte)(substate.waste)); + binWriter.Write((byte)(substate.mode)); + binWriter.Write((byte)0); + binWriter.Write((ushort)substate.motionPack); + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetAnimatedPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetAnimatedPacket.cs index f2f86d75..6cae2cf8 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetAnimatedPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetAnimatedPacket.cs @@ -9,9 +9,9 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00D3; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, uint targetID) + public static SubPacket BuildPacket(uint sourceActorId, uint targetID) { - return new SubPacket(OPCODE, playerActorID, targetID, BitConverter.GetBytes((ulong)targetID)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((ulong)targetID)); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetPacket.cs index cd2e86e6..1fa2e8b6 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/SetActorTargetPacket.cs @@ -8,9 +8,9 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x00DB; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, uint targetID) + public static SubPacket BuildPacket(uint sourceActorId, uint targetID) { - return new SubPacket(OPCODE, playerActorID, targetID, BitConverter.GetBytes((ulong)targetID)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((ulong)targetID)); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/StartCountdownPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/StartCountdownPacket.cs new file mode 100644 index 00000000..83f83c1a --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/Actor/StartCountdownPacket.cs @@ -0,0 +1,32 @@ +using FFXIVClassic.Common; +using System; +using System.IO; +using System.Text; + +namespace FFXIVClassic_Map_Server.packets.send.actor +{ + class StartCountdownPacket + { + public const ushort OPCODE = 0xE5; + public const uint PACKET_SIZE = 0x48; + + public static SubPacket BuildPacket(uint sourceActorId, byte countdownLength, ulong syncTime, string message) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Write((Byte)countdownLength); + binWriter.Seek(8, SeekOrigin.Begin); + binWriter.Write((UInt64)syncTime); + binWriter.Seek(18, SeekOrigin.Begin); + binWriter.Write(Encoding.ASCII.GetBytes(message), 0, Encoding.ASCII.GetByteCount(message) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(message)); + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/Actor/_0x132Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/_0x132Packet.cs index b0f8bb89..0bcd6b15 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/_0x132Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/_0x132Packet.cs @@ -3,7 +3,6 @@ using System; using System.IO; using System.Text; -using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.actor { class _0x132Packet @@ -11,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x132; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, ushort number, string function) + public static SubPacket BuildPacket(uint sourceActorId, ushort number, string function) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -24,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/_0xD9Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/_0xD9Packet.cs deleted file mode 100644 index f5198508..00000000 --- a/FFXIVClassic Map Server/packets/send/Actor/_0xD9Packet.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.IO; - -using FFXIVClassic.Common; -using System; -using System.Text; - -namespace FFXIVClassic_Map_Server.packets.send.actor -{ - class _0xD9Packet - { - public const ushort OPCODE = 0x00D9; - public const uint PACKET_SIZE = 0x28; - - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, string anim) - { - byte[] data = new byte[PACKET_SIZE - 0x20]; - - using (MemoryStream mem = new MemoryStream(data)) - { - using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - binWriter.Write(Encoding.ASCII.GetBytes(anim), 0, Encoding.ASCII.GetByteCount(anim) >= 4 ? 4 : Encoding.ASCII.GetByteCount(anim)); - } - } - - return new SubPacket(OPCODE, playerActorID, targetActorID, data); - } - } -} diff --git a/FFXIVClassic Map Server/packets/send/Actor/_0xFPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/_0xFPacket.cs index 2f54191d..4826218e 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/_0xFPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/_0xFPacket.cs @@ -9,7 +9,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x000F; public const uint PACKET_SIZE = 0x38; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID) + public static SubPacket BuildPacket(uint sourceActor) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -21,7 +21,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActor, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleAction.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleAction.cs deleted file mode 100644 index e1958bf9..00000000 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleAction.cs +++ /dev/null @@ -1,14 +0,0 @@ -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.actor.battle -{ - class BattleAction - { - public uint targetId; - public ushort amount; - public ushort worldMasterTextId; - public uint effectId; - public byte param; - public byte unknown; - } -} diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX10Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX10Packet.cs deleted file mode 100644 index 45884b14..00000000 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX10Packet.cs +++ /dev/null @@ -1,61 +0,0 @@ -using FFXIVClassic.Common; -using System; -using System.IO; - -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.actor.battle -{ - class BattleActionX10Packet - { - public const ushort OPCODE = 0x013A; - public const uint PACKET_SIZE = 0xD8; - - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint animationId, ushort commandId, BattleAction[] actionList) - { - byte[] data = new byte[PACKET_SIZE - 0x20]; - - using (MemoryStream mem = new MemoryStream(data)) - { - using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - binWriter.Write((UInt32)sourceActorId); - binWriter.Write((UInt32)animationId); - - //Missing... last value is float, string in here as well? - - binWriter.Seek(0x20, SeekOrigin.Begin); - binWriter.Write((UInt32) actionList.Length); //Num actions (always 1 for this) - binWriter.Write((UInt16)commandId); - binWriter.Write((UInt16)810); //? - - binWriter.Seek(0x20, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt32)action.targetId); - - binWriter.Seek(0x50, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt16)action.amount); - - binWriter.Seek(0x64, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt16)action.worldMasterTextId); - - binWriter.Seek(0x78, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt32)action.effectId); - - binWriter.Seek(0xA0, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((Byte)action.param); - - binWriter.Seek(0xAA, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((Byte)action.unknown); - } - } - - return new SubPacket(OPCODE, sourceActorId, playerActorID, data); - } - } -} diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX18Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX18Packet.cs deleted file mode 100644 index 8f8e8fdd..00000000 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX18Packet.cs +++ /dev/null @@ -1,61 +0,0 @@ -using FFXIVClassic.Common; -using System; -using System.IO; - -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.actor.battle -{ - class BattleActionX18Packet - { - public const ushort OPCODE = 0x013B; - public const uint PACKET_SIZE = 0x148; - - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint animationId, ushort commandId, BattleAction[] actionList) - { - byte[] data = new byte[PACKET_SIZE - 0x20]; - - using (MemoryStream mem = new MemoryStream(data)) - { - using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - binWriter.Write((UInt32)sourceActorId); - binWriter.Write((UInt32)animationId); - - //Missing... last value is float, string in here as well? - - binWriter.Seek(0x20, SeekOrigin.Begin); - binWriter.Write((UInt32) actionList.Length); //Num actions (always 1 for this) - binWriter.Write((UInt16)commandId); - binWriter.Write((UInt16)810); //? - - binWriter.Seek(0x58, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt32)action.targetId); - - binWriter.Seek(0xA0, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt16)action.amount); - - binWriter.Seek(0xC4, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt16)action.worldMasterTextId); - - binWriter.Seek(0xE8, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((UInt32)action.effectId); - - binWriter.Seek(0x130, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((Byte)action.param); - - binWriter.Seek(0x142, SeekOrigin.Begin); - foreach (BattleAction action in actionList) - binWriter.Write((Byte)action.unknown); - } - } - - return new SubPacket(OPCODE, sourceActorId, playerActorID, data); - } - } -} diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResult.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResult.cs new file mode 100644 index 00000000..bf710540 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResult.cs @@ -0,0 +1,405 @@ +using FFXIVClassic.Common; +using System; +using System.Collections.Generic; +using FFXIVClassic_Map_Server.actors.chara.ai; +using FFXIVClassic_Map_Server.actors.chara.ai.utils; +using FFXIVClassic_Map_Server.Actors; +using FFXIVClassic_Map_Server.packets.send.actor.battle; + +namespace FFXIVClassic_Map_Server.packets.send.actor.battle +{ + //These flags can be stacked and mixed, but the client will prioritize certain flags over others. + [Flags] + public enum HitEffect : uint + { + //This is used for physical attacks + HitEffectType = 8 << 24, + //This is used for additioanl effect hits. Only difference from HitEffectType is that it does not play audio. + AdditionalEffectType = 24 << 24, + //Status effects use 32 << 24 + StatusEffectType = 32 << 24, + //When losing a status effect while using a skill, this prevents the hit effect from playing on the actor playing the animation + StatusLossType = 40 << 24, + //Magic effects use 48 << 24, this is also used for when statuses are lost on attack + MagicEffectType = 48 << 24, + //This places the number on the user regardless of the target this hit effect is for, used for things like bloodbath + SelfHealType = 72 << 24, + + //Each Type has it's own set of flags. These should be split into their own enums, + //but for now just keep them all under HitEffect so we don't have to change anything. + + //HitEffectType flags + + //Not setting RecoilLv2 or RecoilLv3 results in the weaker RecoilLv1. + //These are the recoil animations that play on the target, ranging from weak to strong. + //The recoil that gets set was likely based on the percentage of HP lost from the attack. + //These also have a visual effect with heals and spells but in reverse. RecoilLv1 has a large effect, Lv3 has none. Crit is very large + //For spells they represent resists. Lv0 is a max resist, Lv3 is no resist. Crit is still used for crits. + //Heals used the same effects sometimes but it isn't clear what for, it seems random? Possibly something like a trait proccing or even just a bug + RecoilLv1 = 0, + RecoilLv2 = 1 << 0, + RecoilLv3 = 1 << 1, + + //Setting both recoil flags triggers the "Critical!" pop-up text and hit visual effect. + CriticalHit = RecoilLv2 | RecoilLv3, + + //Hit visual and sound effects when connecting with the target. + //Mixing these flags together will yield different results. + //Each visual likely relates to a specific weapon. + //Ex: HitVisual4 flag alone appears to be the visual and sound effect for hand-to-hand attacks. + + //HitVisual is probably based on attack property. + //HitVisual1 is for slashing attacks + //HitVisual2 is for piercing attacks + //HitVisual1 | Hitvisual2 is for blunt attacks + //HitVisual3 is for projectile attacks + //Basically take the attack property of a weapon and shift it left 2 + //For auto attacks attack property is weapon's damageAttributeType1 + //Still not totally sure how this works with weaponskills or what hitvisual4 or the other combinations are for + HitVisual1 = 1 << 2, + HitVisual2 = 1 << 3, + HitVisual3 = 1 << 4, + HitVisual4 = 1 << 5, + + //An additional visual effect that plays on the target when attacked if: + //The attack is physical and they have the protect buff on. + //The attack is magical and they have the shell buff on. + //Special Note: Shell was removed in later versions of the game. + //Another effect plays when both Protect and Shell flags are activated. + //Not sure what this effect is. + //Random guess: if the attack was a hybrid of both physical and magical and the target had both Protect and Shell buffs applied. + Protect = 1 << 6 | HitEffectType, + Shell = 1 << 7 | HitEffectType, + ProtectShellSpecial = Protect | Shell, + + //If only HitEffect1 is set out of the hit effects, the "Evade!" pop-up text triggers along with the evade visual. + //If no hit effects are set, the "Miss!" pop-up is triggered and no hit visual is played. + HitEffect1 = 1 << 9, + HitEffect2 = 1 << 10, //Plays the standard hit visual effect, but with no sound if used alone. + HitEffect3 = 1 << 11, //Yellow effect, crit? + HitEffect4 = 1 << 12, //Plays the blocking animation + HitEffect5 = 1 << 13, + GustyHitEffect = HitEffect3 | HitEffect2, + GreenTintedHitEffect = HitEffect4 | HitEffect1, + + //For specific animations + Miss = 0, + Evade = HitEffect1, + Hit = HitEffect1 | HitEffect2, + Crit = HitEffect3, + Parry = Hit | HitEffect3, + Block = HitEffect4, + + //Knocks you back away from the attacker. + KnockbackLv1 = HitEffect4 | HitEffect2 | HitEffect1, + KnockbackLv2 = HitEffect4 | HitEffect3, + KnockbackLv3 = HitEffect4 | HitEffect3 | HitEffect1, + KnockbackLv4 = HitEffect4 | HitEffect3 | HitEffect2, + KnockbackLv5 = HitEffect4 | HitEffect3 | HitEffect2 | HitEffect1, + + //Knocks you away from the attacker in a counter-clockwise direction. + KnockbackCounterClockwiseLv1 = HitEffect5, + KnockbackCounterClockwiseLv2 = HitEffect5 | HitEffect1, + + //Knocks you away from the attacker in a clockwise direction. + KnockbackClockwiseLv1 = HitEffect5 | HitEffect2, + KnockbackClockwiseLv2 = HitEffect5 | HitEffect2 | HitEffect1, + + //Completely drags target to the attacker, even across large distances. + DrawIn = HitEffect5 | HitEffect3, + + //An additional visual effect that plays on the target based on according buff. + UnknownShieldEffect = HitEffect5 | HitEffect4, + Stoneskin = HitEffect5 | HitEffect4 | HitEffect1, + + //A special effect when performing appropriate skill combos in succession. + //Ex: Thunder (SkillCombo1 Effect) -> Thundara (SkillCombo2 Effect) -> Thundaga (SkillCombo3 Effect) + //Special Note: SkillCombo4 was never actually used in 1.0 since combos only chained up to 3 times maximum. + SkillCombo1 = 1 << 15, + SkillCombo2 = 1 << 16, + SkillCombo3 = SkillCombo1 | SkillCombo2, + SkillCombo4 = 1 << 17, + + //This is used in the absorb effect for some reason + Unknown = 1 << 19, + + //AdditionalEffectType flags + //The AdditionalEffectType is used for the additional effects some weapons have. + //These effect ids do not repeat the effect of the attack and will not show without a preceding HitEffectType or MagicEffectType + + //It's unclear what this is for. The ifrit fight capture has a BLM using the garuda weapon + //and this flag is set every time but has no apparent effect. + UnknownAdditionalFlag = 1, + + //These play effects on the target + FireEffect = 1 << 10, + IceEffect = 2 << 10, + WindEffect = 3 << 10, + EarthEffect = 4 << 10, + LightningEffect = 5 << 10, + WaterEffect = 6 << 10, + AstralEffect = 7 << 10, //Possibly for blind? + UmbralEffect = 8 << 10, //Posibly for poison? + + //Unknown status effect effects + StatusEffect1 = 12 << 10, + StatusEffect2 = 13 << 10, + + HPAbsorbEffect = 14 << 10, + MPAbsorbEffect = 15 << 10, + TPAbsorbEffect = 16 << 10, + TripleAbsorbEffect = 17 << 10, //Not sure about this + MoogleEffect = 18 << 10, + + //MagicEffectType Flags + //THese are used for magic effects that deal or heal damage as well as damage over time effects + //Crit is the same as HitEffectType + FullResist = 0, + WeakResist = 1 << 0, //Used for level 1, 2, and 3 resists probably + NoResist = 1 << 1, + + MagicShell = 1 << 4, //Used when casting on target with shell effects. MagicEffectType doesnt have a flag for protect or stoneskin + MagicShield = 1 << 5, //When used with an command that has an animation, this plays a purple shield effect. DoTs also have this flag set (at least on ifrit) but they have no animations so it doesnt show + + // Required for heal text to be blue, not sure if that's all it's used for + Heal = 1 << 8, + MP = 1 << 9, //Causes "MP" text to appear when used with MagicEffectType. | with Heal to make text blue + TP = 1 << 10, //Causes "TP" text to appear when used with MagicEffectType. | with Heal to make text blue + + //SelfHealType flags + //This category causes numbers to appear on the user rather regardless of the target associated with the hit effect and do not play an animation + //These determine the text that displays (HP has no text) + SelfHealHP = 0, + SelfHealMP = 1 << 0, //Shows MP text on self. | with SelfHeal to make blue + SelfHealTP = 1 << 1, //Shows TP text on self. | with SelfHeal to make blue + + //Causes self healing numbers to be blue + SelfHeal = 1 << 10, + } + + //Mixing some of these flags will cause the client to crash. + //Setting a flag higher than Left (0x10-0x80) will cause the client to crash. + [Flags] + public enum HitDirection : byte + { + None = 0, + Front = 1 << 0, + Right = 1 << 1, + Rear = 1 << 2, + Left = 1 << 3 + } + + public enum HitType : ushort + { + Miss = 0, + Evade = 1, + Parry = 2, + Block = 3, + SingleResist = 4, + DoubleResist = 5, + TripleResist = 6, + FullResist = 7, + Hit = 8, + Crit = 9 + } + + //Type of action + public enum ActionType : ushort + { + None = 0, + Physical = 1, + Magic = 2, + Heal = 3, + Status = 4 + } + + //There's are two columns in gamecommand that are for action property and action element respectively and both have percentages next to them + //the percentages are for what percent that property or element factors into the attack. Astral and Umbral are always 33% because they are both 3 elments combined + //ActionProperty and ActionElement are slightly different. Property defines whta type of attack it is, and 11-13 are used for "sonic, breath, neutral". Neutral is always used for magic + //For Element 11-13 are used for astral, umbral, and healing magic. + //Right now we aren't actually using these but when things like resists get better defined we'll have to + public enum ActionProperty : ushort + { + None = 0, + Slashing = 1, + Piercing = 2, + Blunt = 3, + Projectile = 4, + + Fire = 5, + Ice = 6, + Wind = 7, + Earth = 8, + Lightning = 9, + Water = 10, + + //These I'm not sure about. Check gameCommand.csv + Astral = 11, + Umbral = 12, + Heal = 13 + } + + + /* + public enum ActionProperty : ushort + { + None = 0, + Slashing = 1, + Piercing = 2, + Blunt = 3, + Projectile = 4, + + Fire = 5, + Ice = 6, + Wind = 7, + Earth = 8, + Lightning = 9, + Water = 10, + + Sonic = 11, + Breath = 12, + Neutral = 13, + Astral = 14, + Umbral = 15 + } + + public enum ActionElement : ushort + { + None = 0, + Slashing = 1, + Piercing = 2, + Blunt = 3, + Projectile = 4, + + Fire = 5, + Ice = 6, + Wind = 7, + Earth = 8, + Lightning = 9, + Water = 10, + + //These I'm not sure about. Check gameCommand.csv + Astral = 11, + Umbral = 12, + Heal = 13 + }*/ + + + class CommandResult + { + public uint targetId; + public ushort amount; + public ushort amountMitigated; //Amount that got blocked/evaded or resisted + public ushort enmity; //Seperate from amount for abilities that cause a different amount of enmity than damage + public ushort worldMasterTextId; + public uint effectId; //Impact effect, damage/heal/status numbers or name + public byte param; //Which side the battle action is coming from + public byte hitNum; //Which hit in a sequence of hits this is + + /// + /// these fields are not actually part of the packet struct + /// + public uint animation; + public CommandType commandType; //What type of command was used (ie weaponskill, ability, etc) + public ActionProperty actionProperty; //Damage type of the action + public ActionType actionType; //Type of this action (ie physical, magic, heal) + public HitType hitType; + + //Rates, I'm not sure if these need to be stored like this but with the way some buffs work maybe they do? + //Makes things like Blindside easy at least. + public double parryRate = 0.0; + public double blockRate = 0.0; + public double resistRate = 0.0; + public double hitRate = 0.0; + public double critRate = 0.0; + + public CommandResult(uint targetId, ushort worldMasterTextId, uint effectId, ushort amount = 0, byte param = 0, byte hitNum = 1) + { + this.targetId = targetId; + this.worldMasterTextId = worldMasterTextId; + this.effectId = effectId; + this.amount = amount; + this.param = param; + this.hitNum = hitNum; + this.hitType = HitType.Hit; + this.enmity = amount; + this.commandType = (byte) CommandType.None; + } + + public CommandResult(uint targetId, BattleCommand command, byte param = 0, byte hitNum = 1) + { + this.targetId = targetId; + this.worldMasterTextId = command.worldMasterTextId; + this.param = param; + this.hitNum = hitNum; + this.commandType = command.commandType; + this.actionProperty = command.actionProperty; + this.actionType = command.actionType; + } + + //Order of what (probably) happens when a skill is used: + //Buffs that alter things like recast times or that only happen once per skill usage like Power Surge are activated + //Script calculates damage and handles any special requirements + //Rates are calculated + //Buffs that impact indiviudal hits like Blindside or Blood for Blood are activated + //The final hit type is determined + //Stoneskin takes damage + //Final damage amount is calculated using the hit type and defender's stats + //Buffs that activate or respond to damage like Rampage. Stoneskin gets removed AFTER damage if it falls off. + //Additional effects that are a part of the skill itself or weapon in case of auto attacks take place like status effects + //Certain buffs that alter the whole skill fall off (Resonance, Excruciate) + + public void DoAction(Character caster, Character target, BattleCommand skill, CommandResultContainer results) + { + //First calculate rates for hit/block/etc + CalcRates(caster, target, skill); + + //Next, modify those rates based on preaction buffs + //Still not sure how we shouldh andle these + PreAction(caster, target, skill, results); + + BattleUtils.DoAction(caster, target, skill, this, results); + } + + + //Calculate the chance of hitting/critting/etc + public void CalcRates(Character caster, Character target, BattleCommand skill) + { + hitRate = BattleUtils.GetHitRate(caster, target, skill, this); + critRate = BattleUtils.GetCritRate(caster, target, skill, this); + blockRate = BattleUtils.GetBlockRate(caster, target, skill, this); + parryRate = BattleUtils.GetParryRate(caster, target, skill, this); + resistRate = BattleUtils.GetResistRate(caster, target, skill, this); + } + + //These are buffs that activate before the action hits. Usually they change things like hit or crit rates or damage + public void PreAction(Character caster, Character target, BattleCommand skill, CommandResultContainer results) + { + target.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnPreactionTarget, "onPreAction", caster, target, skill, this, results); + + caster.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnPreactionCaster, "onPreAction", caster, target, skill, this, results); + } + + //Try and apply a status effect + public void TryStatus(Character caster, Character target, BattleCommand skill, CommandResultContainer results, bool isAdditional = true) + { + BattleUtils.TryStatus(caster, target, skill, this, results, isAdditional); + } + + public ushort GetHitType() + { + return (ushort)hitType; + } + + public void SetTextId(ushort id) + { + worldMasterTextId = id; + } + + //Whether this action didn't miss, and wasn't evaded or resisted + public bool ActionLanded() + { + return hitType > HitType.Evade && hitType != HitType.SingleResist && hitType != HitType.DoubleResist && hitType != HitType.FullResist; + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultContainer.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultContainer.cs new file mode 100644 index 00000000..2cf424bb --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultContainer.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.packets.send.actor.battle +{ + class CommandResultContainer + { + private List actionsList = new List(); + + //EXP messages are always the last mesages in battlea ction packets, so they get appended after all the rest of the actions are done. + private List expActionList = new List(); + + public CommandResultContainer() + { + + } + + public void AddAction(uint targetId, ushort worldMasterTextId, uint effectId, ushort amount = 0, byte param = 0, byte hitNum = 0) + { + AddAction(new CommandResult(targetId, worldMasterTextId, effectId, amount, param, hitNum)); + } + + //Just to make scripting simpler + //These have to be split into the normal actions and absorb actions because they use different flags + //AddMP/HP/TPAction are for actions where the targetID is the person being targeted by command. Like Sanguine Rite would use AddMPAction + public void AddMPAction(uint targetId, ushort worldMasterTextId, ushort amount) + { + uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.MP | HitEffect.Heal); + AddAction(targetId, worldMasterTextId, effectId, amount); + } + + public void AddHPAction(uint targetId, ushort worldMasterTextId, ushort amount) + { + uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.Heal); + AddAction(targetId, worldMasterTextId, effectId, amount); + } + + public void AddTPAction(uint targetId, ushort worldMasterTextId, ushort amount) + { + uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.TP | HitEffect.Heal); + AddAction(targetId, worldMasterTextId, effectId, amount); + } + + //These are used for skills where the targetId is the person using a command. For example casting with parsimony would use AddMPAbsorbAction + public void AddMPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount) + { + uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHealMP | HitEffect.SelfHeal); + AddAction(targetId, worldMasterTextId, effectId, amount); + } + + public void AddHPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount) + { + uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHeal | HitEffect.SelfHeal); + AddAction(targetId, worldMasterTextId, effectId, amount); + } + + public void AddTPAbsorbAction(uint targetId, ushort worldMasterTextId, ushort amount) + { + uint effectId = (uint) (HitEffect.SelfHealType | HitEffect.SelfHealTP | HitEffect.SelfHeal); + AddAction(targetId, worldMasterTextId, effectId, amount); + } + + public void AddHitAction(uint targetId, ushort worldMasterTextId, ushort amount) + { + uint effectId = (uint) (HitEffect.HitEffectType | HitEffect.Hit); + AddAction(targetId, worldMasterTextId, effectId, amount); + } + + public void AddAction(CommandResult action) + { + if (action != null) + actionsList.Add(action); + } + + public void AddActions(List actions) + { + actionsList.AddRange(actions); + } + + public void AddEXPAction(CommandResult action) + { + expActionList.Add(action); + } + + public void AddEXPActions(List actionList) + { + expActionList.AddRange(actionList); + } + + public void CombineLists() + { + actionsList.AddRange(expActionList); + } + + public List GetList() + { + return actionsList; + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX00Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX00Packet.cs similarity index 77% rename from FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX00Packet.cs rename to FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX00Packet.cs index ba3f8b2e..06cbe136 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX00Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX00Packet.cs @@ -2,16 +2,14 @@ using System; using System.IO; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor.battle { - class BattleActionX00Packet + class CommandResultX00Packet { public const ushort OPCODE = 0x013C; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint targetActorId, uint animationId, ushort commandId) + public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -32,7 +30,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle } } - return new SubPacket(OPCODE, sourceActorId, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX01Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX01Packet.cs similarity index 50% rename from FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX01Packet.cs rename to FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX01Packet.cs index 17efb745..035af7d7 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/battle/BattleActionX01Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX01Packet.cs @@ -2,16 +2,21 @@ using System; using System.IO; -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.actor.battle +namespace FFXIVClassic_Map_Server.packets.send.actor.battle { - class BattleActionX01Packet + // see xtx_command + enum CommandResultX01PacketCommand : ushort + { + Disengage = 12002, + Attack = 22104, + } + + class CommandResultX01Packet { public const ushort OPCODE = 0x0139; public const uint PACKET_SIZE = 0x58; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint targetActorId, uint animationId, uint effectId, ushort worldMasterTextId, ushort commandId, ushort amount, byte param) + public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, CommandResult action) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -20,6 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle using (BinaryWriter binWriter = new BinaryWriter(mem)) { binWriter.Write((UInt32)sourceActorId); + binWriter.Write((UInt32)animationId); //Missing... last value is float, string in here as well? @@ -27,21 +33,20 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle binWriter.Seek(0x20, SeekOrigin.Begin); binWriter.Write((UInt32)1); //Num actions (always 1 for this) binWriter.Write((UInt16)commandId); - binWriter.Write((UInt16)810); //? + binWriter.Write((UInt16)0x810); //? - binWriter.Write((UInt32)targetActorId); + binWriter.Write((UInt32)action.targetId); - binWriter.Write((UInt16)amount); - binWriter.Write((UInt16)worldMasterTextId); + binWriter.Write((UInt16)action.amount); + binWriter.Write((UInt16)action.worldMasterTextId); - binWriter.Write((UInt32)effectId); - - binWriter.Write((Byte)param); - binWriter.Write((Byte)1); //? + binWriter.Write((UInt32)action.effectId); + binWriter.Write((Byte)action.param); + binWriter.Write((Byte)action.hitNum); } } - return new SubPacket(OPCODE, sourceActorId, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } -} +} \ No newline at end of file diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX10Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX10Packet.cs new file mode 100644 index 00000000..3a0c4616 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX10Packet.cs @@ -0,0 +1,127 @@ +using FFXIVClassic.Common; +using System; +using System.IO; + +using System.Collections.Generic; + +namespace FFXIVClassic_Map_Server.packets.send.actor.battle +{ + class CommandResultX10Packet + { + public const ushort OPCODE = 0x013A; + public const uint PACKET_SIZE = 0xD8; + + public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, CommandResult[] actionList, ref int listOffset) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + int max; + if (actionList.Length - listOffset <= 10) + max = actionList.Length - listOffset; + else + max = 10; + + binWriter.Write((UInt32)sourceActorId); + binWriter.Write((UInt32)animationId); + + //Missing... last value is float, string in here as well? + + binWriter.Seek(0x20, SeekOrigin.Begin); + binWriter.Write((UInt32)max); //Num actions + binWriter.Write((UInt16)commandId); + binWriter.Write((UInt16)0x810); //? + + //binWriter.Seek(0x20, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt32)actionList[listOffset + i].targetId); + + binWriter.Seek(0x50, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].amount); + + binWriter.Seek(0x64, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId); + + binWriter.Seek(0x78, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt32)actionList[listOffset + i].effectId); + + binWriter.Seek(0xA0, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte)actionList[listOffset + i].param); + + binWriter.Seek(0xAA, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte)actionList[listOffset + i].hitNum); + + listOffset += max; + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + + public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, List actionList, ref int listOffset) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + int max; + if (actionList.Count - listOffset <= 10) + max = actionList.Count - listOffset; + else + max = 10; + + binWriter.Write((UInt32)sourceActorId); + binWriter.Write((UInt32)animationId); + + //Missing... last value is float, string in here as well? + + binWriter.Seek(0x20, SeekOrigin.Begin); + binWriter.Write((UInt32)max); //Num actions + binWriter.Write((UInt16)commandId); + binWriter.Write((UInt16)0x810); //? + + //binWriter.Seek(0x20, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt32)actionList[listOffset + i].targetId); + + binWriter.Seek(0x50, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].amount); + + binWriter.Seek(0x64, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId); + + binWriter.Seek(0x78, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + { + binWriter.Write((UInt32)actionList[listOffset + i].effectId); + } + + binWriter.Seek(0xA0, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte)actionList[listOffset + i].param); + + binWriter.Seek(0xAA, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte) actionList[listOffset + i].hitNum); + + listOffset += max; + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + + } +} diff --git a/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX18Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX18Packet.cs new file mode 100644 index 00000000..1ec257d0 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/Actor/battle/CommandResultX18Packet.cs @@ -0,0 +1,124 @@ +using FFXIVClassic.Common; +using System; +using System.IO; + +using System.Collections.Generic; + +namespace FFXIVClassic_Map_Server.packets.send.actor.battle +{ + class CommandResultX18Packet + { + public const ushort OPCODE = 0x013B; + public const uint PACKET_SIZE = 0x148; + + public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, CommandResult[] actionList, ref int listOffset) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + int max; + if (actionList.Length - listOffset <= 18) + max = actionList.Length - listOffset; + else + max = 18; + + binWriter.Write((UInt32)sourceActorId); + binWriter.Write((UInt32)animationId); + + //Missing... last value is float, string in here as well? + + binWriter.Seek(0x20, SeekOrigin.Begin); + binWriter.Write((UInt32)max); //Num actions + binWriter.Write((UInt16)commandId); + binWriter.Write((UInt16)0x810); //? + + binWriter.Seek(0x28, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt32)actionList[listOffset + i].targetId); + + binWriter.Seek(0x70, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].amount); + + binWriter.Seek(0x94, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId); + + binWriter.Seek(0xB8, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt32)actionList[listOffset + i].effectId); + + binWriter.Seek(0x100, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte)actionList[listOffset + i].param); + + binWriter.Seek(0x112, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte)actionList[listOffset + i].hitNum); + + listOffset += max; + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + + public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, List actionList, ref int listOffset) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + int max; + if (actionList.Count - listOffset <= 18) + max = actionList.Count - listOffset; + else + max = 18; + + binWriter.Write((UInt32)sourceActorId); + binWriter.Write((UInt32)animationId); + + //Missing... last value is float, string in here as well? + + binWriter.Seek(0x20, SeekOrigin.Begin); + binWriter.Write((UInt32)max); //Num actions + binWriter.Write((UInt16)commandId); + binWriter.Write((UInt16)0x818); //? + + binWriter.Seek(0x28, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt32)actionList[listOffset + i].targetId); + + binWriter.Seek(0x70, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].amount); + + binWriter.Seek(0x94, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId); + + binWriter.Seek(0xB8, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((UInt32)actionList[listOffset + i].effectId); + + binWriter.Seek(0x100, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte)actionList[listOffset + i].param); + + binWriter.Seek(0x112, SeekOrigin.Begin); + for (int i = 0; i < max; i++) + binWriter.Write((Byte)actionList[listOffset + i].hitNum); + + listOffset += max; + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/Actor/events/SetEmoteEventCondition.cs b/FFXIVClassic Map Server/packets/send/Actor/events/SetEmoteEventCondition.cs index f23866c2..688fbcf1 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/events/SetEmoteEventCondition.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/events/SetEmoteEventCondition.cs @@ -4,8 +4,6 @@ using System; using System.IO; using System.Text; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor.events { class SetEmoteEventCondition @@ -13,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events public const ushort OPCODE = 0x016C; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorID, EventList.EmoteEventCondition condition) + public static SubPacket BuildPacket(uint sourceActorId, EventList.EmoteEventCondition condition) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -27,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events } } - return new SubPacket(OPCODE, sourceActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/events/SetEventStatus.cs b/FFXIVClassic Map Server/packets/send/Actor/events/SetEventStatus.cs index c86d3a0f..5f0e85e8 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/events/SetEventStatus.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/events/SetEventStatus.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events public const ushort OPCODE = 0x0136; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorID, bool enabled, byte unknown2, string conditionName) + public static SubPacket BuildPacket(uint sourceActorId, bool enabled, byte unknown2, string conditionName) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -25,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events } } - return new SubPacket(OPCODE, sourceActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/events/SetNoticeEventCondition.cs b/FFXIVClassic Map Server/packets/send/Actor/events/SetNoticeEventCondition.cs index f6ed17d4..3f866f44 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/events/SetNoticeEventCondition.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/events/SetNoticeEventCondition.cs @@ -12,7 +12,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events public const ushort OPCODE = 0x016B; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorID, EventList.NoticeEventCondition condition) + public static SubPacket BuildPacket(uint sourceActorId, EventList.NoticeEventCondition condition) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -26,7 +26,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events } } - return new SubPacket(OPCODE, sourceActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithCircle.cs b/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithCircle.cs index a093788a..0caa1982 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithCircle.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithCircle.cs @@ -12,7 +12,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events public const ushort OPCODE = 0x016F; public const uint PACKET_SIZE = 0x58; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorID, EventList.PushCircleEventCondition condition) + public static SubPacket BuildPacket(uint sourceActorId, EventList.PushCircleEventCondition condition) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -31,7 +31,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events } } - return new SubPacket(OPCODE, sourceActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithFan.cs b/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithFan.cs index 8b070ea7..21b270fb 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithFan.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithFan.cs @@ -12,7 +12,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events public const ushort OPCODE = 0x0170; public const uint PACKET_SIZE = 0x60; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorID, EventList.PushFanEventCondition condition) + public static SubPacket BuildPacket(uint sourceActorId, EventList.PushFanEventCondition condition) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events binWriter.Write((Single)condition.radius); binWriter.Write((UInt32)0xbfc90fdb); binWriter.Write((UInt32)0x3f860a92); - binWriter.Write((UInt32)sourceActorID); //Actor Id + binWriter.Write((UInt32)sourceActorId); //Actor Id binWriter.Write((Single)10.0f); binWriter.Seek(4, SeekOrigin.Current); binWriter.Write((Byte)(condition.outwards ? 0x11 : 0x1)); //If == 0x10, Inverted Bounding Box @@ -32,8 +32,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events binWriter.Write(Encoding.ASCII.GetBytes(condition.conditionName), 0, Encoding.ASCII.GetByteCount(condition.conditionName) >= 0x24 ? 0x24 : Encoding.ASCII.GetByteCount(condition.conditionName)); } } - new SubPacket(OPCODE, sourceActorID, playerActorID, data).DebugPrintSubPacket(); - return new SubPacket(OPCODE, sourceActorID, playerActorID, data); + new SubPacket(OPCODE, sourceActorId, data).DebugPrintSubPacket(); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithTriggerBox.cs b/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithTriggerBox.cs index a397e088..81d2cf96 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithTriggerBox.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/events/SetPushEventConditionWithTriggerBox.cs @@ -12,7 +12,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events public const ushort OPCODE = 0x0175; public const uint PACKET_SIZE = 0x60; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorID, EventList.PushBoxEventCondition condition) + public static SubPacket BuildPacket(uint sourceActorId, EventList.PushBoxEventCondition condition) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -31,7 +31,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events } } - return new SubPacket(OPCODE, sourceActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/events/SetTalkEventCondition.cs b/FFXIVClassic Map Server/packets/send/Actor/events/SetTalkEventCondition.cs index f3b47bfd..2d0a6b49 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/events/SetTalkEventCondition.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/events/SetTalkEventCondition.cs @@ -12,7 +12,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events public const ushort OPCODE = 0x012E; public const uint PACKET_SIZE = 0x48; - public static SubPacket BuildPacket(uint playerActorID, uint sourceActorID, EventList.TalkEventCondition condition) + public static SubPacket BuildPacket(uint sourceActorId, EventList.TalkEventCondition condition) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -27,7 +27,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.events } } - return new SubPacket(OPCODE, sourceActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs index 616c4cbd..925758d7 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX01Packet.cs @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX08Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX08Packet.cs index bf8a5d25..c3f94f37 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX08Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX08Packet.cs @@ -38,7 +38,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorId, playerActorId, data); + return new SubPacket(OPCODE, playerActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX16Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX16Packet.cs index 0eeea6ee..fe347718 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX16Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX16Packet.cs @@ -36,7 +36,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorId, playerActorId, data); + return new SubPacket(OPCODE, playerActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX32Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX32Packet.cs index ca882b09..82614765 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX32Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX32Packet.cs @@ -36,7 +36,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorId, playerActorId, data); + return new SubPacket(OPCODE, playerActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX64Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX64Packet.cs index 9f48eb35..05cb42c4 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX64Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/EquipmentListX64Packet.cs @@ -36,7 +36,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorId, playerActorId, data); + return new SubPacket(OPCODE, playerActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryBeginChangePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryBeginChangePacket.cs index ab6183c8..6f423d78 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryBeginChangePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryBeginChangePacket.cs @@ -11,13 +11,13 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { byte[] data = new byte[8]; data[0] = 2; - return new SubPacket(OPCODE, sourceActorId, targetActorId, data); + return new SubPacket(OPCODE, sourceActorId, data); } public static SubPacket BuildPacket(uint playerActorID) { byte[] data = new byte[8]; - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryEndChangePacket.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryEndChangePacket.cs index 24499b25..bab10398 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryEndChangePacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryEndChangePacket.cs @@ -7,14 +7,10 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory public const ushort OPCODE = 0x016E; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint sourceActorId, uint targetActorId) + public static SubPacket BuildPacket(uint sourceActorId) { - return new SubPacket(OPCODE, sourceActorId, targetActorId, new byte[8]); - } - - public static SubPacket BuildPacket(uint playerActorID) - { - return new SubPacket(OPCODE, playerActorID, playerActorID, new byte[8]); + return new SubPacket(OPCODE, sourceActorId, new byte[8]); } + } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemEndPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemEndPacket.cs index 107a3e79..b1f99ec0 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemEndPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemEndPacket.cs @@ -30,7 +30,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory data = mem.GetBuffer(); } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemPacket.cs index 9a35e8e0..f548e240 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryItemPacket.cs @@ -30,7 +30,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory data = mem.GetBuffer(); } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX01Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX01Packet.cs index 964e02a4..db5a2714 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX01Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX01Packet.cs @@ -9,13 +9,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { public const ushort OPCODE = 0x0148; public const uint PACKET_SIZE = 0x90; - - public static SubPacket BuildPacket(uint playerActorId, InventoryItem item) - { - return BuildPacket(playerActorId, playerActorId, item); - } - - public static SubPacket BuildPacket(uint sourceActorId, uint targetActorId, InventoryItem item) + + public static SubPacket BuildPacket(uint sourceActorId, InventoryItem item) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -27,7 +22,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, sourceActorId, targetActorId, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX08Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX08Packet.cs index 448c9f3a..cbabf787 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX08Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX08Packet.cs @@ -11,13 +11,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { public const ushort OPCODE = 0x0149; public const uint PACKET_SIZE = 0x3A8; - - public static SubPacket BuildPacket(uint playerActorId, List items, ref int listOffset) - { - return BuildPacket(playerActorId, playerActorId, items, ref listOffset); - } - - public static SubPacket BuildPacket(uint sourceActorId, uint targetActorId, List items, ref int listOffset) + + public static SubPacket BuildPacket(uint sourceActorId, List items, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -42,7 +37,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, sourceActorId, targetActorId, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX16Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX16Packet.cs index 6c406bf9..90496fb6 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX16Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX16Packet.cs @@ -11,12 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory public const ushort OPCODE = 0x014A; public const uint PACKET_SIZE = 0x720; - public static SubPacket BuildPacket(uint playerActorId, List items, ref int listOffset) - { - return BuildPacket(playerActorId, playerActorId, items, ref listOffset); - } - - public static SubPacket BuildPacket(uint sourceActorId, uint targetActorId, List items, ref int listOffset) + public static SubPacket BuildPacket(uint sourceActorId, List items, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -38,7 +33,8 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, sourceActorId, targetActorId, data); + return new SubPacket(OPCODE, sourceActorId, data); } + } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX32Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX32Packet.cs index b9dd9dd3..a368d90e 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX32Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX32Packet.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { public const ushort OPCODE = 0x014B; public const uint PACKET_SIZE = 0xE20; - + public static SubPacket BuildPacket(uint playerActorID, List items, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -33,7 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX64Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX64Packet.cs index c1c05da0..c37caee0 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX64Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryListX64Packet.cs @@ -33,7 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX01Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX01Packet.cs index cc16251a..2893425b 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX01Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX01Packet.cs @@ -9,7 +9,6 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { public const ushort OPCODE = 0x0152; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, ushort slot) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -21,8 +20,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory binWriter.Write((UInt16)slot); } } - - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX08Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX08Packet.cs index 2dd988b2..270d749f 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX08Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX08Packet.cs @@ -3,15 +3,12 @@ using System; using System.Collections.Generic; using System.IO; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { class InventoryRemoveX08Packet { public const ushort OPCODE = 0x0153; public const uint PACKET_SIZE = 0x38; - public static SubPacket BuildPacket(uint playerActorID, List slots, ref int listOffset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -36,8 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory binWriter.Write((Byte)max); } } - - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX16Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX16Packet.cs index b54820c0..c6586b1f 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX16Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX16Packet.cs @@ -33,7 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX32Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX32Packet.cs index ebb568cd..d4840fa5 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX32Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX32Packet.cs @@ -33,7 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX64Packet.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX64Packet.cs index 208ccbe1..201c7c72 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX64Packet.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventoryRemoveX64Packet.cs @@ -33,7 +33,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetBeginPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetBeginPacket.cs index 74af503d..75d61bfa 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetBeginPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetBeginPacket.cs @@ -10,12 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory public const ushort OPCODE = 0x0146; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorId, ushort size, ushort code) - { - return BuildPacket(playerActorId, playerActorId, size, code); - } - - public static SubPacket BuildPacket(uint sourceActorId, uint targetActorId, ushort size, ushort code) + public static SubPacket BuildPacket(uint sourceActorId, ushort size, ushort code) { byte[] data = new byte[8]; @@ -29,7 +24,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory } } - return new SubPacket(OPCODE, sourceActorId, targetActorId, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetEndPacket.cs b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetEndPacket.cs index 5a7952d2..5f899491 100644 --- a/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetEndPacket.cs +++ b/FFXIVClassic Map Server/packets/send/Actor/inventory/InventorySetEndPacket.cs @@ -4,19 +4,13 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.inventory { class InventorySetEndPacket { - public const ushort OPCODE = 0x0147; public const uint PACKET_SIZE = 0x28; public static SubPacket BuildPacket(uint playerActorId) { - return new SubPacket(OPCODE, playerActorId, playerActorId, new byte[8]); + return new SubPacket(OPCODE, playerActorId, new byte[8]); } - - public static SubPacket BuildPacket(uint sourceActorId, uint targetActorID) - { - return new SubPacket(OPCODE, sourceActorId, targetActorID, new byte[8]); - } - + } } diff --git a/FFXIVClassic Map Server/packets/send/GameMessagePacket.cs b/FFXIVClassic Map Server/packets/send/GameMessagePacket.cs index 553f5eec..52ef05ff 100644 --- a/FFXIVClassic Map Server/packets/send/GameMessagePacket.cs +++ b/FFXIVClassic Map Server/packets/send/GameMessagePacket.cs @@ -58,7 +58,7 @@ namespace FFXIVClassic_Map_Server.packets.send private const ushort SIZE_GAMEMESSAGE_WITHOUT_ACTOR4 = 0x48; private const ushort SIZE_GAMEMESSAGE_WITHOUT_ACTOR5 = 0x68; - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint actorId, uint textOwnerActorId, ushort textId, byte log) + public static SubPacket BuildPacket(uint sourceActorId, uint actorId, uint textOwnerActorId, ushort textId, byte log) { byte[] data = new byte[SIZE_GAMEMESSAGE_WITH_ACTOR1 - 0x20]; @@ -73,10 +73,10 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(OPCODE_GAMEMESSAGE_WITH_ACTOR1, sourceId, targetId, data); + return new SubPacket(OPCODE_GAMEMESSAGE_WITH_ACTOR1, sourceActorId, data); } - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint actorId, uint textOwnerActorId, ushort textId, byte log, List lParams) + public static SubPacket BuildPacket(uint sourceActorId, uint actorId, uint textOwnerActorId, ushort textId, byte log, List lParams) { int lParamsSize = findSizeOfParams(lParams); byte[] data; @@ -121,10 +121,10 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(opcode, sourceId, targetId, data); + return new SubPacket(opcode, sourceActorId, data); } - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint textOwnerActorId, ushort textId, string sender, byte log) + public static SubPacket BuildPacket(uint sourceActorId, uint textOwnerActorId, ushort textId, string sender, byte log) { byte[] data = new byte[SIZE_GAMEMESSAGE_WITH_CUSTOM_SENDER1 - 0x20]; @@ -139,10 +139,10 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(OPCODE_GAMEMESSAGE_WITH_CUSTOM_SENDER1, sourceId, targetId, data); + return new SubPacket(OPCODE_GAMEMESSAGE_WITH_CUSTOM_SENDER1, sourceActorId, data); } - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint textOwnerActorId, ushort textId, string sender, byte log, List lParams) + public static SubPacket BuildPacket(uint sourceActorId, uint textOwnerActorId, ushort textId, string sender, byte log, List lParams) { int lParamsSize = findSizeOfParams(lParams); byte[] data; @@ -187,10 +187,10 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(opcode, sourceId, targetId, data); + return new SubPacket(opcode, sourceActorId, data); } - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint textOwnerActorId, ushort textId, uint senderDisplayNameId, byte log) + public static SubPacket BuildPacket(uint sourceActorId, uint textOwnerActorId, ushort textId, uint senderDisplayNameId, byte log) { byte[] data = new byte[SIZE_GAMEMESSAGE_WITH_DISPID_SENDER1 - 0x20]; @@ -205,10 +205,10 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(OPCODE_GAMEMESSAGE_WITH_DISPID_SENDER1, sourceId, targetId, data); + return new SubPacket(OPCODE_GAMEMESSAGE_WITH_DISPID_SENDER1, sourceActorId, data); } - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint textOwnerActorId, ushort textId, uint senderDisplayNameId, byte log, List lParams) + public static SubPacket BuildPacket(uint sourceActorId, uint textOwnerActorId, ushort textId, uint senderDisplayNameId, byte log, List lParams) { int lParamsSize = findSizeOfParams(lParams); byte[] data; @@ -253,10 +253,10 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(opcode, sourceId, targetId, data); + return new SubPacket(opcode, sourceActorId, data); } - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint textOwnerActorId, ushort textId, byte log) + public static SubPacket BuildPacket(uint sourceActorId, uint textOwnerActorId, ushort textId, byte log) { byte[] data = new byte[SIZE_GAMEMESSAGE_WITHOUT_ACTOR1 - 0x20]; @@ -270,10 +270,10 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(OPCODE_GAMEMESSAGE_WITHOUT_ACTOR1, sourceId, targetId, data); + return new SubPacket(OPCODE_GAMEMESSAGE_WITHOUT_ACTOR1, sourceActorId, data); } - public static SubPacket BuildPacket(uint sourceId, uint targetId, uint textOwnerActorId, ushort textId, byte log, List lParams) + public static SubPacket BuildPacket(uint sourceActorId, uint textOwnerActorId, ushort textId, byte log, List lParams) { int lParamsSize = findSizeOfParams(lParams); byte[] data; @@ -317,7 +317,7 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(opcode, sourceId, targetId, data); + return new SubPacket(opcode, sourceActorId, data); } private static int findSizeOfParams(List lParams) diff --git a/FFXIVClassic Map Server/packets/send/LogoutPacket.cs b/FFXIVClassic Map Server/packets/send/LogoutPacket.cs index 2348a24a..c657e769 100644 --- a/FFXIVClassic Map Server/packets/send/LogoutPacket.cs +++ b/FFXIVClassic Map Server/packets/send/LogoutPacket.cs @@ -9,7 +9,7 @@ namespace FFXIVClassic_Map_Server.packets.send public static SubPacket BuildPacket(uint playerActorID) { - return new SubPacket(OPCODE, playerActorID, playerActorID, new byte[8]); + return new SubPacket(OPCODE, playerActorID, new byte[8]); } } } diff --git a/FFXIVClassic Map Server/packets/send/PongPacket.cs b/FFXIVClassic Map Server/packets/send/PongPacket.cs index 477e0915..72714b25 100644 --- a/FFXIVClassic Map Server/packets/send/PongPacket.cs +++ b/FFXIVClassic Map Server/packets/send/PongPacket.cs @@ -22,7 +22,7 @@ namespace FFXIVClassic_Map_Server.packets.receive } } - SubPacket subpacket = new SubPacket(OPCODE, playerActorID, playerActorID, data); + SubPacket subpacket = new SubPacket(OPCODE, playerActorID, data); return subpacket; } diff --git a/FFXIVClassic Map Server/packets/send/QuitPacket.cs b/FFXIVClassic Map Server/packets/send/QuitPacket.cs index ccdddc74..1edc0699 100644 --- a/FFXIVClassic Map Server/packets/send/QuitPacket.cs +++ b/FFXIVClassic Map Server/packets/send/QuitPacket.cs @@ -7,9 +7,9 @@ namespace FFXIVClassic_Map_Server.packets.send public const ushort OPCODE = 0x0011; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID) + public static SubPacket BuildPacket(uint sourceActorId) { - return new SubPacket(OPCODE, playerActorID, playerActorID, new byte[8]); + return new SubPacket(OPCODE, sourceActorId, new byte[8]); } } } diff --git a/FFXIVClassic Map Server/packets/send/SendMessagePacket.cs b/FFXIVClassic Map Server/packets/send/SendMessagePacket.cs index 5fa97c71..9b368e90 100644 --- a/FFXIVClassic Map Server/packets/send/SendMessagePacket.cs +++ b/FFXIVClassic Map Server/packets/send/SendMessagePacket.cs @@ -36,7 +36,7 @@ namespace FFXIVClassic_Map_Server.packets.send public const ushort OPCODE = 0x0003; public const uint PACKET_SIZE = 0x248; - public static SubPacket BuildPacket(uint playerActorID, uint targetID, uint messageType, string sender, string message) + public static SubPacket BuildPacket(uint sourceActorId, uint messageType, string sender, string message) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -51,7 +51,7 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(OPCODE, playerActorID, targetID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/_0x10Packet.cs b/FFXIVClassic Map Server/packets/send/SetDalamudPacket.cs similarity index 76% rename from FFXIVClassic Map Server/packets/send/_0x10Packet.cs rename to FFXIVClassic Map Server/packets/send/SetDalamudPacket.cs index d04f8154..aca7c9ec 100644 --- a/FFXIVClassic Map Server/packets/send/_0x10Packet.cs +++ b/FFXIVClassic Map Server/packets/send/SetDalamudPacket.cs @@ -5,12 +5,12 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send { - class _0x10Packet + class SetDalamudPacket { public const ushort OPCODE = 0x0010; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorId, int val) + public static SubPacket BuildPacket(uint playerActorId, sbyte dalamudLevel) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -18,11 +18,11 @@ namespace FFXIVClassic_Map_Server.packets.send { using (BinaryWriter binWriter = new BinaryWriter(mem)) { - binWriter.Write((UInt32)val); + binWriter.Write((Int32)dalamudLevel); } } - return new SubPacket(OPCODE, playerActorId, playerActorId, data); + return new SubPacket(OPCODE, playerActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/SetMapPacket.cs b/FFXIVClassic Map Server/packets/send/SetMapPacket.cs index 86ff5236..cffb4507 100644 --- a/FFXIVClassic Map Server/packets/send/SetMapPacket.cs +++ b/FFXIVClassic Map Server/packets/send/SetMapPacket.cs @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/SetMusicPacket.cs b/FFXIVClassic Map Server/packets/send/SetMusicPacket.cs index dfe477b1..666ff463 100644 --- a/FFXIVClassic Map Server/packets/send/SetMusicPacket.cs +++ b/FFXIVClassic Map Server/packets/send/SetMusicPacket.cs @@ -16,10 +16,10 @@ namespace FFXIVClassic_Map_Server.packets.send public const ushort EFFECT_PLAY_NORMAL_CHANNEL = 0x5; //Only works for multi channeled music public const ushort EFFECT_PLAY_BATTLE_CHANNEL = 0x6; - public static SubPacket BuildPacket(uint playerActorID, ushort musicID, ushort musicTrackMode) + public static SubPacket BuildPacket(uint sourceActorId, ushort musicID, ushort musicTrackMode) { ulong combined = (ulong)(musicID | (musicTrackMode << 16)); - return new SubPacket(OPCODE, 0, playerActorID, BitConverter.GetBytes(combined)); + return new SubPacket(OPCODE, 0, BitConverter.GetBytes(combined)); } } } diff --git a/FFXIVClassic Map Server/packets/send/SetWeatherPacket.cs b/FFXIVClassic Map Server/packets/send/SetWeatherPacket.cs index 93d1f425..28c5c25b 100644 --- a/FFXIVClassic Map Server/packets/send/SetWeatherPacket.cs +++ b/FFXIVClassic Map Server/packets/send/SetWeatherPacket.cs @@ -37,10 +37,10 @@ namespace FFXIVClassic_Map_Server.packets.send public const ushort OPCODE = 0x000D; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, ushort weatherId, ushort transitionTime) + public static SubPacket BuildPacket(uint sourceActorId, ushort weatherId, ushort transitionTime) { ulong combined = (ulong)(weatherId | (transitionTime << 16)); - return new SubPacket(OPCODE, 0, playerActorID, BitConverter.GetBytes(combined)); + return new SubPacket(OPCODE, 0, BitConverter.GetBytes(combined)); } } } diff --git a/FFXIVClassic Map Server/packets/send/_0x02Packet.cs b/FFXIVClassic Map Server/packets/send/_0x02Packet.cs index 3e91afd5..ec5d8921 100644 --- a/FFXIVClassic Map Server/packets/send/_0x02Packet.cs +++ b/FFXIVClassic Map Server/packets/send/_0x02Packet.cs @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send } } - return new SubPacket(OPCODE, playerActorId, playerActorId, data); + return new SubPacket(OPCODE, playerActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/_0xE2Packet.cs b/FFXIVClassic Map Server/packets/send/_0xE2Packet.cs index 1602f4cf..b38d3eaf 100644 --- a/FFXIVClassic Map Server/packets/send/_0xE2Packet.cs +++ b/FFXIVClassic Map Server/packets/send/_0xE2Packet.cs @@ -13,7 +13,7 @@ namespace FFXIVClassic_Map_Server.packets.send { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = (Byte) (val & 0xFF); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/events/EndEventPacket.cs b/FFXIVClassic Map Server/packets/send/events/EndEventPacket.cs index ddc8fc51..1c8e3ec5 100644 --- a/FFXIVClassic Map Server/packets/send/events/EndEventPacket.cs +++ b/FFXIVClassic Map Server/packets/send/events/EndEventPacket.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events public const ushort OPCODE = 0x0131; public const uint PACKET_SIZE = 0x50; - public static SubPacket BuildPacket(uint playerActorID, uint eventOwnerActorID, string eventStarter) + public static SubPacket BuildPacket(uint sourcePlayerActorId, uint eventOwnerActorID, string eventStarter) { byte[] data = new byte[PACKET_SIZE - 0x20]; int maxBodySize = data.Length - 0x80; @@ -20,14 +20,14 @@ namespace FFXIVClassic_Map_Server.packets.send.events { using (BinaryWriter binWriter = new BinaryWriter(mem)) { - binWriter.Write((UInt32)playerActorID); + binWriter.Write((UInt32)sourcePlayerActorId); binWriter.Write((UInt32)0); binWriter.Write((Byte)1); binWriter.Write(Encoding.ASCII.GetBytes(eventStarter), 0, Encoding.ASCII.GetByteCount(eventStarter) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(eventStarter)); } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourcePlayerActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs b/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs index 972c5540..fe0a2d19 100644 --- a/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs +++ b/FFXIVClassic Map Server/packets/send/events/KickEventPacket.cs @@ -13,7 +13,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events public const ushort OPCODE = 0x012F; public const uint PACKET_SIZE = 0x90; - public static SubPacket BuildPacket(uint playerActorId, uint targetActorId, string conditionName, List luaParams) + public static SubPacket BuildPacket(uint sourcePlayerActorId, uint targetEventActorId, string conditionName, List luaParams) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -21,8 +21,8 @@ namespace FFXIVClassic_Map_Server.packets.send.events { using (BinaryWriter binWriter = new BinaryWriter(mem)) { - binWriter.Write((UInt32)playerActorId); - binWriter.Write((UInt32)targetActorId); + binWriter.Write((UInt32)sourcePlayerActorId); + binWriter.Write((UInt32)targetEventActorId); int test = 0x75dc1705; //This will crash if set to 0 on pushCommand but not for mining which has to be 0???? @@ -36,7 +36,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events } } - return new SubPacket(OPCODE, playerActorId, playerActorId, data); + return new SubPacket(OPCODE, sourcePlayerActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/events/RunEventFunctionPacket.cs b/FFXIVClassic Map Server/packets/send/events/RunEventFunctionPacket.cs index 8320f103..919114ca 100644 --- a/FFXIVClassic Map Server/packets/send/events/RunEventFunctionPacket.cs +++ b/FFXIVClassic Map Server/packets/send/events/RunEventFunctionPacket.cs @@ -13,7 +13,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events public const ushort OPCODE = 0x0130; public const uint PACKET_SIZE = 0x2B8; - public static SubPacket BuildPacket(uint playerActorID, uint eventOwnerActorID, string eventStarter, string callFunction, List luaParams) + public static SubPacket BuildPacket(uint sourcePlayerActorId, uint eventOwnerActorID, string eventStarter, string callFunction, List luaParams) { byte[] data = new byte[PACKET_SIZE - 0x20]; int maxBodySize = data.Length - 0x80; @@ -22,7 +22,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events { using (BinaryWriter binWriter = new BinaryWriter(mem)) { - binWriter.Write((UInt32)playerActorID); + binWriter.Write((UInt32)sourcePlayerActorId); binWriter.Write((UInt32)eventOwnerActorID); binWriter.Write((Byte)5); binWriter.Write(Encoding.ASCII.GetBytes(eventStarter), 0, Encoding.ASCII.GetByteCount(eventStarter) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(eventStarter)); @@ -34,7 +34,7 @@ namespace FFXIVClassic_Map_Server.packets.send.events } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourcePlayerActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX08Packet.cs b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX08Packet.cs index 6f0f9a21..8bc05ab5 100644 --- a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX08Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX08Packet.cs @@ -45,7 +45,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX16Packet.cs b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX16Packet.cs index 60ef9692..d263d6d4 100644 --- a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX16Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX16Packet.cs @@ -42,7 +42,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX32Packet.cs b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX32Packet.cs index 532b0c1c..363d96c4 100644 --- a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX32Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX32Packet.cs @@ -42,7 +42,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX64Packet.cs b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX64Packet.cs index f8da3882..88d73a06 100644 --- a/FFXIVClassic Map Server/packets/send/groups/ContentMembersX64Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/ContentMembersX64Packet.cs @@ -42,7 +42,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroup.cs b/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroup.cs index 098a26b4..2274ae95 100644 --- a/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroup.cs +++ b/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroup.cs @@ -35,7 +35,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroupMultiple.cs b/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroupMultiple.cs index 9622e484..f6905c97 100644 --- a/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroupMultiple.cs +++ b/FFXIVClassic Map Server/packets/send/groups/CreateNamedGroupMultiple.cs @@ -49,7 +49,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/DeleteGroupPacket.cs b/FFXIVClassic Map Server/packets/send/groups/DeleteGroupPacket.cs index 8a3a559b..571cd879 100644 --- a/FFXIVClassic Map Server/packets/send/groups/DeleteGroupPacket.cs +++ b/FFXIVClassic Map Server/packets/send/groups/DeleteGroupPacket.cs @@ -37,7 +37,7 @@ namespace FFXIVClassic_Map_Server.packets.send.groups } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/GroupHeaderPacket.cs b/FFXIVClassic Map Server/packets/send/groups/GroupHeaderPacket.cs index ee05919c..6cb222b2 100644 --- a/FFXIVClassic Map Server/packets/send/groups/GroupHeaderPacket.cs +++ b/FFXIVClassic Map Server/packets/send/groups/GroupHeaderPacket.cs @@ -56,7 +56,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/GroupMembersBeginPacket.cs b/FFXIVClassic Map Server/packets/send/groups/GroupMembersBeginPacket.cs index 4c328a9a..5b24aa4d 100644 --- a/FFXIVClassic Map Server/packets/send/groups/GroupMembersBeginPacket.cs +++ b/FFXIVClassic Map Server/packets/send/groups/GroupMembersBeginPacket.cs @@ -32,7 +32,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/GroupMembersEndPacket.cs b/FFXIVClassic Map Server/packets/send/groups/GroupMembersEndPacket.cs index 9a796008..9bba4e06 100644 --- a/FFXIVClassic Map Server/packets/send/groups/GroupMembersEndPacket.cs +++ b/FFXIVClassic Map Server/packets/send/groups/GroupMembersEndPacket.cs @@ -31,7 +31,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } diff --git a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX08Packet.cs b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX08Packet.cs index badf0f11..cca76aaf 100644 --- a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX08Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX08Packet.cs @@ -49,7 +49,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX16Packet.cs b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX16Packet.cs index e2b1ca13..b4ba775b 100644 --- a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX16Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX16Packet.cs @@ -41,11 +41,11 @@ namespace FFXIVClassic_Map_Server.packets.send.group binWriter.Write(Encoding.ASCII.GetBytes(entry.name), 0, Encoding.ASCII.GetByteCount(entry.name) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(entry.name)); offset++; - } + } } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX32Packet.cs b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX32Packet.cs index 923e5019..7abee729 100644 --- a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX32Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX32Packet.cs @@ -45,7 +45,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX64Packet.cs b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX64Packet.cs index 05535081..44d98851 100644 --- a/FFXIVClassic Map Server/packets/send/groups/GroupMembersX64Packet.cs +++ b/FFXIVClassic Map Server/packets/send/groups/GroupMembersX64Packet.cs @@ -45,7 +45,7 @@ namespace FFXIVClassic_Map_Server.packets.send.group } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/groups/SynchGroupWorkValuesPacket.cs b/FFXIVClassic Map Server/packets/send/groups/SynchGroupWorkValuesPacket.cs index fbe2e2d6..f33202ef 100644 --- a/FFXIVClassic Map Server/packets/send/groups/SynchGroupWorkValuesPacket.cs +++ b/FFXIVClassic Map Server/packets/send/groups/SynchGroupWorkValuesPacket.cs @@ -194,14 +194,14 @@ namespace FFXIVClassic_Map_Server.packets.send.groups } - public SubPacket buildPacket(uint playerActorID, uint actorID) + public SubPacket buildPacket(uint actorID) { binWriter.Seek(0x8, SeekOrigin.Begin); binWriter.Write((byte)runningByteTotal); closeStreams(); - SubPacket packet = new SubPacket(OPCODE, playerActorID, actorID, data); + SubPacket packet = new SubPacket(OPCODE, actorID, data); return packet; } diff --git a/FFXIVClassic Map Server/packets/send/login/0x2Packet.cs b/FFXIVClassic Map Server/packets/send/login/0x2Packet.cs index 72952578..cd13514a 100644 --- a/FFXIVClassic Map Server/packets/send/login/0x2Packet.cs +++ b/FFXIVClassic Map Server/packets/send/login/0x2Packet.cs @@ -9,7 +9,7 @@ namespace FFXIVClassic_Map_Server.packets.send.login public const ushort OPCODE = 0x0002; public const uint PACKET_SIZE = 0x30; - public static SubPacket BuildPacket(uint playerActorID) + public static SubPacket BuildPacket(uint sourceActorId) { byte[] data = new byte[PACKET_SIZE-0x20]; @@ -18,11 +18,11 @@ namespace FFXIVClassic_Map_Server.packets.send.login using (BinaryWriter binWriter = new BinaryWriter(mem)) { binWriter.BaseStream.Seek(0x8, SeekOrigin.Begin); - binWriter.Write((uint)playerActorID); + binWriter.Write((uint)sourceActorId); } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/login/0x7ResponsePacket.cs b/FFXIVClassic Map Server/packets/send/login/0x7ResponsePacket.cs deleted file mode 100644 index 7278b1f1..00000000 --- a/FFXIVClassic Map Server/packets/send/login/0x7ResponsePacket.cs +++ /dev/null @@ -1,39 +0,0 @@ -using FFXIVClassic.Common; -using System; -using System.IO; - -using FFXIVClassic.Common; - -namespace FFXIVClassic_Map_Server.packets.send.login -{ - class Login0x7ResponsePacket - { - public static BasePacket BuildPacket(uint actorID, uint time, uint type) - { - byte[] data = new byte[0x18]; - - using (MemoryStream mem = new MemoryStream(data)) - { - using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - try - { - binWriter.Write((short)0x18); - binWriter.Write((short)type); - binWriter.Write((uint)0); - binWriter.Write((uint)0); - binWriter.Write((uint)0xFFFFFD7F); - - binWriter.Write((uint)actorID); - binWriter.Write((uint)time); - } - catch (Exception) - { - } - } - } - - return BasePacket.CreatePacket(data, false, false); - } - } -} diff --git a/FFXIVClassic Map Server/packets/send/player/AchievementEarnedPacket.cs b/FFXIVClassic Map Server/packets/send/player/AchievementEarnedPacket.cs index f7a9ed11..b6321ecf 100644 --- a/FFXIVClassic Map Server/packets/send/player/AchievementEarnedPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/AchievementEarnedPacket.cs @@ -9,9 +9,9 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x019E; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint achievementID) + public static SubPacket BuildPacket(uint sourceActorId, uint achievementID) { - return new SubPacket(OPCODE, playerActorID, playerActorID, BitConverter.GetBytes((UInt64)achievementID)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((UInt64)achievementID)); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/InfoRequestResponsePacket.cs b/FFXIVClassic Map Server/packets/send/player/GenericDataPacket.cs similarity index 73% rename from FFXIVClassic Map Server/packets/send/player/InfoRequestResponsePacket.cs rename to FFXIVClassic Map Server/packets/send/player/GenericDataPacket.cs index dd3535c0..1240c077 100644 --- a/FFXIVClassic Map Server/packets/send/player/InfoRequestResponsePacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/GenericDataPacket.cs @@ -6,12 +6,12 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.player { - class InfoRequestResponsePacket + class GenericDataPacket { public const ushort OPCODE = 0x0133; public const uint PACKET_SIZE = 0xE0; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, List luaParams) + public static SubPacket BuildPacket(uint sourceActorId, List luaParams) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SendAchievementRatePacket.cs b/FFXIVClassic Map Server/packets/send/player/SendAchievementRatePacket.cs index 0bef5b62..f7145d07 100644 --- a/FFXIVClassic Map Server/packets/send/player/SendAchievementRatePacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SendAchievementRatePacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x019F; public const uint PACKET_SIZE = 0x30; - public static SubPacket BuildPacket(uint playerActorID, uint achievementId, uint progressCount, uint progressFlags) + public static SubPacket BuildPacket(uint sourceActorId, uint achievementId, uint progressCount, uint progressFlags) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -24,7 +24,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetAchievementPointsPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetAchievementPointsPacket.cs index 8000b570..24f8e277 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetAchievementPointsPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetAchievementPointsPacket.cs @@ -9,9 +9,9 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x019C; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint numAchievementPoints) + public static SubPacket BuildPacket(uint sourceActorId, uint numAchievementPoints) { - return new SubPacket(OPCODE, playerActorID, playerActorID, BitConverter.GetBytes((UInt64) numAchievementPoints)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((UInt64) numAchievementPoints)); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetChocoboNamePacket.cs b/FFXIVClassic Map Server/packets/send/player/SetChocoboNamePacket.cs index 97f9850e..2c2d4d07 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetChocoboNamePacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetChocoboNamePacket.cs @@ -9,11 +9,11 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x0198; public const uint PACKET_SIZE = 0x40; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, string name) + public static SubPacket BuildPacket(uint sourceActorId, string name) { if (Encoding.Unicode.GetByteCount(name) >= 0x20) name = "ERR: Too Long"; - return new SubPacket(OPCODE, playerActorID, targetActorID, Encoding.ASCII.GetBytes(name)); + return new SubPacket(OPCODE, sourceActorId, Encoding.ASCII.GetBytes(name)); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetCompletedAchievementsPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetCompletedAchievementsPacket.cs index 887acc94..657ffadc 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetCompletedAchievementsPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetCompletedAchievementsPacket.cs @@ -1,8 +1,6 @@ using FFXIVClassic.Common; using System.IO; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.player { class SetCompletedAchievementsPacket @@ -26,7 +24,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player public bool[] achievementFlags = new bool[1024]; - public SubPacket BuildPacket(uint playerActorID) + public SubPacket BuildPacket(uint sourceActorId) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -42,7 +40,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetCurrentJobPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetCurrentJobPacket.cs index f890e4e6..b6e131dc 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetCurrentJobPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetCurrentJobPacket.cs @@ -9,9 +9,9 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x01A4; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint sourceActorID, uint targetActorID, uint jobId) + public static SubPacket BuildPacket(uint sourceActorId, uint jobId) { - return new SubPacket(OPCODE, sourceActorID, targetActorID, BitConverter.GetBytes((uint)jobId)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((uint)jobId)); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetCurrentMountChocoboPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetCurrentMountChocoboPacket.cs index 95f3ff97..401df44c 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetCurrentMountChocoboPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetCurrentMountChocoboPacket.cs @@ -24,11 +24,11 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x0197; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, int appearanceId) + public static SubPacket BuildPacket(uint sourceActorId, int appearanceId) { byte[] data = new byte[PACKET_SIZE - 0x20]; data[5] = (byte)(appearanceId & 0xFF); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetCurrentMountGoobbuePacket.cs b/FFXIVClassic Map Server/packets/send/player/SetCurrentMountGoobbuePacket.cs index 71aca97f..168ffd88 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetCurrentMountGoobbuePacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetCurrentMountGoobbuePacket.cs @@ -8,11 +8,11 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x01a0; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, int appearanceId) + public static SubPacket BuildPacket(uint sourceActorId, int appearanceId) { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = (byte)(appearanceId & 0xFF); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetCutsceneBookPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetCutsceneBookPacket.cs index 84bc4c9f..4f8c4b0a 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetCutsceneBookPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetCutsceneBookPacket.cs @@ -3,8 +3,6 @@ using System; using System.IO; using System.Text; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.player { class SetCutsceneBookPacket @@ -62,7 +60,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player public bool[] cutsceneFlags = new bool[2048]; - public SubPacket BuildPacket(uint playerActorID, string sNpcName, short sNpcActorIdOffset, byte sNpcSkin, byte sNpcPersonality) + public SubPacket BuildPacket(uint sourceActorId, string sNpcName, short sNpcActorIdOffset, byte sNpcSkin, byte sNpcPersonality) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -91,7 +89,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetGrandCompanyPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetGrandCompanyPacket.cs index d942efad..b4f120c9 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetGrandCompanyPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetGrandCompanyPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor public const ushort OPCODE = 0x0194; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint sourceActorID, uint targetActorID, ushort currentAllegiance, ushort rankLimsa, ushort rankGridania, ushort rankUldah) + public static SubPacket BuildPacket(uint sourceActorId, ushort currentAllegiance, ushort rankLimsa, ushort rankGridania, ushort rankUldah) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -25,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor } } - return new SubPacket(OPCODE, sourceActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetHasChocoboPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetHasChocoboPacket.cs index 796ac55e..9a72954d 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetHasChocoboPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetHasChocoboPacket.cs @@ -7,11 +7,11 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x0199; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, bool hasChocobo) + public static SubPacket BuildPacket(uint sourceActorId, bool hasChocobo) { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = (byte)(hasChocobo ? 1 : 0); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetHasGoobbuePacket.cs b/FFXIVClassic Map Server/packets/send/player/SetHasGoobbuePacket.cs index 4f35e303..475bee38 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetHasGoobbuePacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetHasGoobbuePacket.cs @@ -7,11 +7,11 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x01A1; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, bool hasGoobbue) + public static SubPacket BuildPacket(uint sourceActorId, bool hasGoobbue) { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = (byte)(hasGoobbue ? 1 : 0); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetLatestAchievementsPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetLatestAchievementsPacket.cs index 5d9c6483..63fe48fc 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetLatestAchievementsPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetLatestAchievementsPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x019B; public const uint PACKET_SIZE = 0x40; - public static SubPacket BuildPacket(uint playerActorID, uint[] latestAchievementIDs) + public static SubPacket BuildPacket(uint sourceActorId, uint[] latestAchievementIDs) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -28,7 +28,7 @@ namespace FFXIVClassic_Map_Server.packets.send.player } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetPlayerDreamPacket.cs b/FFXIVClassic Map Server/packets/send/player/SetPlayerDreamPacket.cs index d3e3d177..82493e6b 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetPlayerDreamPacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetPlayerDreamPacket.cs @@ -1,8 +1,6 @@ using FFXIVClassic.Common; using System; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.player { class SetPlayerDreamPacket @@ -10,10 +8,10 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x01A7; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint dreamID) + public static SubPacket BuildPacket(uint sourceActorId, uint dreamID) { dreamID += 0x20E; - return new SubPacket(OPCODE, playerActorID, playerActorID, BitConverter.GetBytes((uint)dreamID)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((uint)dreamID)); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/SetPlayerItemStoragePacket.cs b/FFXIVClassic Map Server/packets/send/player/SetPlayerItemStoragePacket.cs new file mode 100644 index 00000000..1094ed31 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/player/SetPlayerItemStoragePacket.cs @@ -0,0 +1,26 @@ +using FFXIVClassic.Common; +using System.IO; + +namespace FFXIVClassic_Map_Server.packets.send.player +{ + class SetPlayerItemStoragePacket + { + public const ushort OPCODE = 0x01A5; + public const uint PACKET_SIZE = 0x50; + + public static SubPacket BuildPacket(uint sourceActorId) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Write(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F}); //All items enabled + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/player/SetPlayerTitlePacket.cs b/FFXIVClassic Map Server/packets/send/player/SetPlayerTitlePacket.cs index 75af7666..f99066c9 100644 --- a/FFXIVClassic Map Server/packets/send/player/SetPlayerTitlePacket.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetPlayerTitlePacket.cs @@ -9,9 +9,9 @@ namespace FFXIVClassic_Map_Server.packets.send.player public const ushort OPCODE = 0x019D; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID, uint titleID) + public static SubPacket BuildPacket(uint sourceActorId, uint titleID) { - return new SubPacket(OPCODE, playerActorID, targetActorID, BitConverter.GetBytes((UInt64)titleID)); + return new SubPacket(OPCODE, sourceActorId, BitConverter.GetBytes((UInt64)titleID)); } } } diff --git a/FFXIVClassic Map Server/packets/send/player/_0x196Packet.cs b/FFXIVClassic Map Server/packets/send/player/SetSpecialEventWorkPacket.cs similarity index 57% rename from FFXIVClassic Map Server/packets/send/player/_0x196Packet.cs rename to FFXIVClassic Map Server/packets/send/player/SetSpecialEventWorkPacket.cs index 8eff42ae..93bd2644 100644 --- a/FFXIVClassic Map Server/packets/send/player/_0x196Packet.cs +++ b/FFXIVClassic Map Server/packets/send/player/SetSpecialEventWorkPacket.cs @@ -5,25 +5,25 @@ using FFXIVClassic.Common; namespace FFXIVClassic_Map_Server.packets.send.player { - class _0x196Packet + class SetSpecialEventWorkPacket { public const ushort OPCODE = 0x0196; public const uint PACKET_SIZE = 0x38; - public static SubPacket BuildPacket(uint playerActorID, uint targetActorID) + public static SubPacket BuildPacket(uint sourceActorId) { byte[] data = new byte[PACKET_SIZE - 0x20]; using (MemoryStream mem = new MemoryStream(data)) { using (BinaryWriter binWriter = new BinaryWriter(mem)) - { - binWriter.Seek(0xE, SeekOrigin.Begin); - binWriter.Write((Byte)0x01); + { + binWriter.Write((UInt16)0x00); + binWriter.Write((UInt16)18); //Just set it to Bomb Festival to unlock Bombdance } } - return new SubPacket(OPCODE, playerActorID, targetActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/recruitment/CurrentRecruitmentDetailsPacket.cs b/FFXIVClassic Map Server/packets/send/recruitment/CurrentRecruitmentDetailsPacket.cs index 2b3f7a4d..28b68825 100644 --- a/FFXIVClassic Map Server/packets/send/recruitment/CurrentRecruitmentDetailsPacket.cs +++ b/FFXIVClassic Map Server/packets/send/recruitment/CurrentRecruitmentDetailsPacket.cs @@ -12,7 +12,7 @@ namespace FFXIVClassic_Map_Server.packets.send.recruitment public const ushort OPCODE = 0x01C8; public const uint PACKET_SIZE = 0x218; - public static SubPacket BuildPacket(uint playerActorID, RecruitmentDetails details) + public static SubPacket BuildPacket(uint sourceActorId, RecruitmentDetails details) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -22,7 +22,7 @@ namespace FFXIVClassic_Map_Server.packets.send.recruitment { if (details == null) { - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } binWriter.Write((UInt32)details.purposeId); @@ -48,7 +48,7 @@ namespace FFXIVClassic_Map_Server.packets.send.recruitment } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/recruitment/EndRecruitmentPacket.cs b/FFXIVClassic Map Server/packets/send/recruitment/EndRecruitmentPacket.cs index d6c6ffe6..4ce5d714 100644 --- a/FFXIVClassic Map Server/packets/send/recruitment/EndRecruitmentPacket.cs +++ b/FFXIVClassic Map Server/packets/send/recruitment/EndRecruitmentPacket.cs @@ -7,11 +7,11 @@ namespace FFXIVClassic_Map_Server.packets.send.recruitment public const ushort OPCODE = 0x01C4; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID) + public static SubPacket BuildPacket(uint sourceActorId) { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = 1; - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/recruitment/RecruiterStatePacket.cs b/FFXIVClassic Map Server/packets/send/recruitment/RecruiterStatePacket.cs index 7356f33b..8bccd698 100644 --- a/FFXIVClassic Map Server/packets/send/recruitment/RecruiterStatePacket.cs +++ b/FFXIVClassic Map Server/packets/send/recruitment/RecruiterStatePacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.recruitment public const ushort OPCODE = 0x01C5; public const uint PACKET_SIZE = 0x038; - public static SubPacket BuildPacket(uint playerActorID, bool isRecruiting, bool isRecruiter, long recruitmentId) + public static SubPacket BuildPacket(uint sourceActorId, bool isRecruiting, bool isRecruiter, long recruitmentId) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -25,7 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.send.recruitment } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/recruitment/StartRecruitingResponse.cs b/FFXIVClassic Map Server/packets/send/recruitment/StartRecruitingResponse.cs index efa1b644..9629575c 100644 --- a/FFXIVClassic Map Server/packets/send/recruitment/StartRecruitingResponse.cs +++ b/FFXIVClassic Map Server/packets/send/recruitment/StartRecruitingResponse.cs @@ -7,13 +7,13 @@ namespace FFXIVClassic_Map_Server.packets.send.recruitment public const ushort OPCODE = 0x01C3; public const uint PACKET_SIZE = 0x28; - public static SubPacket BuildPacket(uint playerActorID, bool success) + public static SubPacket BuildPacket(uint sourceActorId, bool success) { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = (byte)(success ? 0x1 : 0x0); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/search/ItemSearchClosePacket.cs b/FFXIVClassic Map Server/packets/send/search/ItemSearchClosePacket.cs new file mode 100644 index 00000000..759efea7 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/ItemSearchClosePacket.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.Text; + +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class ItemSearchClosePacket + { + public const ushort OPCODE = 0x01E1; + public const uint PACKET_SIZE = 0x028; + + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToAdd) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/ItemSearchResult.cs b/FFXIVClassic Map Server/packets/send/search/ItemSearchResult.cs new file mode 100644 index 00000000..7d9c51d1 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/ItemSearchResult.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class ItemSearchResult + { + + public uint itemId; + public uint numItems; + + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsBeginPacket.cs b/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsBeginPacket.cs new file mode 100644 index 00000000..42fc4f51 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsBeginPacket.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.Text; + +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class ItemSearchResultsBeginPacket + { + public const ushort OPCODE = 0x01D7; + public const uint PACKET_SIZE = 0x028; + + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToAdd) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsBodyPacket.cs b/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsBodyPacket.cs new file mode 100644 index 00000000..54df84a8 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsBodyPacket.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Text; +using System; +using FFXIVClassic.Common; +using System.Collections.Generic; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class ItemSearchResultsBodyPacket + { + public const ushort OPCODE = 0x01D8; + public const uint PACKET_SIZE = 0x228; + + public static SubPacket BuildPacket(uint sourceActorId, List itemSearchResult, ref int listOffset) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + int max; + if (itemSearchResult.Count - listOffset <= 64) + max = itemSearchResult.Count - listOffset; + else + max = 64; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Write((UInt32)max); + + foreach (ItemSearchResult item in itemSearchResult) + binWriter.Write((UInt32)item.itemId); + + binWriter.Seek(0x104, SeekOrigin.Begin); + + foreach (ItemSearchResult item in itemSearchResult) + binWriter.Write((UInt32)item.numItems); + } + } + + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsEndPacket.cs b/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsEndPacket.cs new file mode 100644 index 00000000..8eedcb3f --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/ItemSearchResultsEndPacket.cs @@ -0,0 +1,19 @@ +using System.IO; +using System.Text; + +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class ItemSearchResultsEndPacket + { + public const ushort OPCODE = 0x01D9; + public const uint PACKET_SIZE = 0x028; + + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToAdd) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/PlayerSearchCommentResultPacket.cs b/FFXIVClassic Map Server/packets/send/search/PlayerSearchCommentResultPacket.cs new file mode 100644 index 00000000..d6588878 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/PlayerSearchCommentResultPacket.cs @@ -0,0 +1,55 @@ +using System.IO; +using System.Text; + +using FFXIVClassic.Common; +using System; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class PlayerSearchCommentResultPacket + { + public const ushort OPCODE = 0x01E0; + public const uint PACKET_SIZE = 0x288; + + public static SubPacket BuildPacket(uint sourceActorId, uint searchSessionId, byte resultCode, PlayerSearchResult[] results, ref int offset) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + byte count = 0; + + for (int i = offset; i < results.Length; i++) + { + int size = 3 + (Encoding.ASCII.GetByteCount(results[i].comment) >= 597 ? 596 : Encoding.ASCII.GetByteCount(results[i].comment)); + + if (size >= 600) + break; + + count++; + } + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + for (int i = offset; i < count; i++) + { + binWriter.Write((UInt32)searchSessionId); + binWriter.Write((Byte)count); + binWriter.Seek(1, SeekOrigin.Current); + binWriter.Write((Byte)resultCode); + binWriter.Seek(4, SeekOrigin.Current); + + binWriter.Write((Byte)i); + binWriter.Write((UInt16)(Encoding.ASCII.GetByteCount(results[i].comment) >= 597 ? 596 : Encoding.ASCII.GetByteCount(results[i].comment))); + binWriter.Write(Encoding.ASCII.GetBytes(results[i].comment), 0, Encoding.ASCII.GetByteCount(results[i].comment) >= 597 ? 596 : Encoding.ASCII.GetByteCount(results[i].comment)); + } + } + } + + offset += count; + + return new SubPacket(OPCODE, sourceActorId, data); + } + + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/PlayerSearchInfoResultPacket.cs b/FFXIVClassic Map Server/packets/send/search/PlayerSearchInfoResultPacket.cs new file mode 100644 index 00000000..137cc0d3 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/PlayerSearchInfoResultPacket.cs @@ -0,0 +1,55 @@ +using System.IO; +using System.Text; + +using FFXIVClassic.Common; +using System; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class PlayerSearchInfoResultPacket + { + public const ushort OPCODE = 0x01DF; + public const uint PACKET_SIZE = 0x3C8; + + public static SubPacket BuildPacket(uint sourceActorId, uint searchSessionId, byte resultCode, PlayerSearchResult[] results, ref int offset) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + + byte count; + + if (results.Length - offset < 8) + count = (byte)(results.Length - offset); + else + count = 8; + + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + for (int i = offset; i < count; i++) + { + long start = binWriter.BaseStream.Position; + binWriter.Write((Byte)results[i].preferredClass); + binWriter.Write((Byte)0); + binWriter.Write((Byte)results[i].clientLanguage); + binWriter.Write((UInt16)results[i].currentZone); + binWriter.Write((Byte)results[i].initialTown); + binWriter.Write((Byte)0); + binWriter.Write((Byte)results[i].status); + binWriter.Write((Byte)results[i].currentClass); + binWriter.Write((Byte)0); + binWriter.Write(Encoding.ASCII.GetBytes(results[i].name), 0, Encoding.ASCII.GetByteCount(results[i].name) >= 0x20 ? 0x20 : Encoding.ASCII.GetByteCount(results[i].name)); + binWriter.Seek((int)(start + 30), SeekOrigin.Begin); + //classes + binWriter.Seek((int)(start + 30 + 20), SeekOrigin.Begin); + //jobs + } + } + } + + offset += count; + + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/PlayerSearchResult.cs b/FFXIVClassic Map Server/packets/send/search/PlayerSearchResult.cs new file mode 100644 index 00000000..9adf6cff --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/PlayerSearchResult.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class PlayerSearchResult + { + public string name; + public string comment; + + public byte preferredClass; + public byte clientLanguage; + public byte initialTown; + public byte status; + public byte currentClass; + public ushort currentZone; + + + + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/RetainerResultBodyPacket.cs b/FFXIVClassic Map Server/packets/send/search/RetainerResultBodyPacket.cs new file mode 100644 index 00000000..32b0c8ec --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/RetainerResultBodyPacket.cs @@ -0,0 +1,26 @@ +using System.IO; +using System.Text; + +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class RetainerResultBodyPacket + { + public const ushort OPCODE = 0x01DB; + public const uint PACKET_SIZE = 0x028; + + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToAdd) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + + } + } + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/RetainerResultEndPacket.cs b/FFXIVClassic Map Server/packets/send/search/RetainerResultEndPacket.cs new file mode 100644 index 00000000..4e3707fa --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/RetainerResultEndPacket.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Text; + +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class RetainerResultEndPacket + { + public const ushort OPCODE = 0x01DA; + public const uint PACKET_SIZE = 0x038; + + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + data[16] = (byte) (isSuccess ? 1 : 0); + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/RetainerResultUpdatePacket.cs b/FFXIVClassic Map Server/packets/send/search/RetainerResultUpdatePacket.cs new file mode 100644 index 00000000..2a8a0619 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/RetainerResultUpdatePacket.cs @@ -0,0 +1,20 @@ +using System.IO; +using System.Text; +using System; +using FFXIVClassic.Common; +using System.Collections.Generic; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class RetainerResultUpdatePacket + { + public const ushort OPCODE = 0x01DC; + public const uint PACKET_SIZE = 0x028; + + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToAdd) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/RetainerSearchHistoryPacket.cs b/FFXIVClassic Map Server/packets/send/search/RetainerSearchHistoryPacket.cs new file mode 100644 index 00000000..85d79d9c --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/RetainerSearchHistoryPacket.cs @@ -0,0 +1,42 @@ +using FFXIVClassic.Common; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class RetainerSearchHistoryPacket + { + public const ushort OPCODE = 0x01DD; + public const uint PACKET_SIZE = 0x120; + + public static SubPacket BuildPacket(uint sourceActorId, byte count, bool hasEnded) + { + byte[] data = new byte[PACKET_SIZE - 0x20]; + using (MemoryStream mem = new MemoryStream(data)) + { + using (BinaryWriter binWriter = new BinaryWriter(mem)) + { + binWriter.Seek(0x12, SeekOrigin.Begin); + binWriter.Write((UInt16)count); + binWriter.Write((Byte)(hasEnded ? 2 : 0)); + + for (int i = 0; i < count; i++) + { + binWriter.Seek(0x10 + (0x80 * i), SeekOrigin.Begin); + RetainerSearchHistoryResult result = null; + binWriter.Write((UInt32)result.timestamp); + binWriter.Write((UInt16)0); + binWriter.Write((UInt16)result.quanitiy); + binWriter.Write((UInt32)result.gilCostPerItem); + binWriter.Write((Byte)result.numStack); + } + } + } + return new SubPacket(OPCODE, sourceActorId, data); + } + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/RetainerSearchHistoryResult.cs b/FFXIVClassic Map Server/packets/send/search/RetainerSearchHistoryResult.cs new file mode 100644 index 00000000..df801164 --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/RetainerSearchHistoryResult.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class RetainerSearchHistoryResult + { + public uint timestamp; + public ushort quanitiy; + public uint gilCostPerItem; + public byte numStack; + } +} diff --git a/FFXIVClassic Map Server/packets/send/search/RetainerSearchResult.cs b/FFXIVClassic Map Server/packets/send/search/RetainerSearchResult.cs new file mode 100644 index 00000000..7747d90c --- /dev/null +++ b/FFXIVClassic Map Server/packets/send/search/RetainerSearchResult.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_Map_Server.packets.send.search +{ + class RetainerSearchResult + { + public uint itemId; + public uint marketWard; + public uint gilCostPerItem; + public uint quantity; + public byte numStack; + public byte quality; + public string sellerRetainerName; + public byte[] materiaType = new byte[5]; + public byte[] materiaGrade = new byte[5]; + } +} diff --git a/FFXIVClassic Map Server/packets/send/social/BlacklistAddedPacket.cs b/FFXIVClassic Map Server/packets/send/social/BlacklistAddedPacket.cs index 1ac85628..6cb39cdf 100644 --- a/FFXIVClassic Map Server/packets/send/social/BlacklistAddedPacket.cs +++ b/FFXIVClassic Map Server/packets/send/social/BlacklistAddedPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social public const ushort OPCODE = 0x01C9; public const uint PACKET_SIZE = 0x048; - public static SubPacket BuildPacket(uint playerActorID, bool isSuccess, string nameToAdd) + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToAdd) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/social/BlacklistRemovedPacket.cs b/FFXIVClassic Map Server/packets/send/social/BlacklistRemovedPacket.cs index 22c7f8a7..b341c5d4 100644 --- a/FFXIVClassic Map Server/packets/send/social/BlacklistRemovedPacket.cs +++ b/FFXIVClassic Map Server/packets/send/social/BlacklistRemovedPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social public const ushort OPCODE = 0x01CA; public const uint PACKET_SIZE = 0x048; - public static SubPacket BuildPacket(uint playerActorID, bool isSuccess, string nameToRemove) + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToRemove) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/social/FriendStatusPacket.cs b/FFXIVClassic Map Server/packets/send/social/FriendStatusPacket.cs index a9e279fe..449a8873 100644 --- a/FFXIVClassic Map Server/packets/send/social/FriendStatusPacket.cs +++ b/FFXIVClassic Map Server/packets/send/social/FriendStatusPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social public const ushort OPCODE = 0x01CF; public const uint PACKET_SIZE = 0x686; - public static SubPacket BuildPacket(uint playerActorID, Tuple[] friendStatus) + public static SubPacket BuildPacket(uint sourceActorId, Tuple[] friendStatus) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -42,7 +42,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/social/FriendlistAddedPacket.cs b/FFXIVClassic Map Server/packets/send/social/FriendlistAddedPacket.cs index b46caf85..6c7787a3 100644 --- a/FFXIVClassic Map Server/packets/send/social/FriendlistAddedPacket.cs +++ b/FFXIVClassic Map Server/packets/send/social/FriendlistAddedPacket.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social public const ushort OPCODE = 0x01CC; public const uint PACKET_SIZE = 0x067; - public static SubPacket BuildPacket(uint playerActorID, bool isSuccess, long id, bool isOnline, string nameToAdd) + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, long id, bool isOnline, string nameToAdd) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -26,7 +26,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/social/FriendlistRemovedPacket.cs b/FFXIVClassic Map Server/packets/send/social/FriendlistRemovedPacket.cs index f293da3d..8e4d914c 100644 --- a/FFXIVClassic Map Server/packets/send/social/FriendlistRemovedPacket.cs +++ b/FFXIVClassic Map Server/packets/send/social/FriendlistRemovedPacket.cs @@ -10,7 +10,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social public const ushort OPCODE = 0x01CD; public const uint PACKET_SIZE = 0x057; - public static SubPacket BuildPacket(uint playerActorID, bool isSuccess, string nameToRemove) + public static SubPacket BuildPacket(uint sourceActorId, bool isSuccess, string nameToRemove) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -23,7 +23,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/social/SendBlacklistPacket.cs b/FFXIVClassic Map Server/packets/send/social/SendBlacklistPacket.cs index 01654a86..9ddd35c3 100644 --- a/FFXIVClassic Map Server/packets/send/social/SendBlacklistPacket.cs +++ b/FFXIVClassic Map Server/packets/send/social/SendBlacklistPacket.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social public const ushort OPCODE = 0x01CB; public const uint PACKET_SIZE = 0x686; - public static SubPacket BuildPacket(uint playerActorID, string[] blacklistedNames, ref int offset) + public static SubPacket BuildPacket(uint sourceActorId, string[] blacklistedNames, ref int offset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -36,7 +36,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/social/SendFriendlistPacket.cs b/FFXIVClassic Map Server/packets/send/social/SendFriendlistPacket.cs index 117c800e..b07b31f8 100644 --- a/FFXIVClassic Map Server/packets/send/social/SendFriendlistPacket.cs +++ b/FFXIVClassic Map Server/packets/send/social/SendFriendlistPacket.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social public const ushort OPCODE = 0x01CE; public const uint PACKET_SIZE = 0x686; - public static SubPacket BuildPacket(uint playerActorID, Tuple[] friends, ref int offset) + public static SubPacket BuildPacket(uint sourceActorId, Tuple[] friends, ref int offset) { byte[] data = new byte[PACKET_SIZE - 0x20]; @@ -39,7 +39,7 @@ namespace FFXIVClassic_Map_Server.packets.send.social } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, sourceActorId, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/supportdesk/EndGMTicketPacket.cs b/FFXIVClassic Map Server/packets/send/supportdesk/EndGMTicketPacket.cs index b9387de5..c0b0f92c 100644 --- a/FFXIVClassic Map Server/packets/send/supportdesk/EndGMTicketPacket.cs +++ b/FFXIVClassic Map Server/packets/send/supportdesk/EndGMTicketPacket.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.supportdesk { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = 1; - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/supportdesk/FaqBodyResponsePacket.cs b/FFXIVClassic Map Server/packets/send/supportdesk/FaqBodyResponsePacket.cs index 6704747e..09359e91 100644 --- a/FFXIVClassic Map Server/packets/send/supportdesk/FaqBodyResponsePacket.cs +++ b/FFXIVClassic Map Server/packets/send/supportdesk/FaqBodyResponsePacket.cs @@ -24,7 +24,7 @@ namespace FFXIVClassic_Map_Server.packets.send.supportdesk } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/supportdesk/FaqListResponsePacket.cs b/FFXIVClassic Map Server/packets/send/supportdesk/FaqListResponsePacket.cs index b41774f1..6c540d7d 100644 --- a/FFXIVClassic Map Server/packets/send/supportdesk/FaqListResponsePacket.cs +++ b/FFXIVClassic Map Server/packets/send/supportdesk/FaqListResponsePacket.cs @@ -26,7 +26,7 @@ namespace FFXIVClassic_Map_Server.packets.send.supportdesk } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketPacket.cs b/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketPacket.cs index eddd0e33..a4c4c6c4 100644 --- a/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketPacket.cs +++ b/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketPacket.cs @@ -26,7 +26,7 @@ namespace FFXIVClassic_Map_Server.packets.send.supportdesk } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketSentResponsePacket.cs b/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketSentResponsePacket.cs index 80903b2a..e466a941 100644 --- a/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketSentResponsePacket.cs +++ b/FFXIVClassic Map Server/packets/send/supportdesk/GMTicketSentResponsePacket.cs @@ -1,7 +1,5 @@ using FFXIVClassic.Common; -using FFXIVClassic.Common; - namespace FFXIVClassic_Map_Server.packets.send.supportdesk { class GMTicketSentResponsePacket @@ -15,7 +13,7 @@ namespace FFXIVClassic_Map_Server.packets.send.supportdesk data[0] = (byte)(wasSent ? 0x1 : 0x0); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/supportdesk/IssueListResponsePacket.cs b/FFXIVClassic Map Server/packets/send/supportdesk/IssueListResponsePacket.cs index 93337b87..ad838b1d 100644 --- a/FFXIVClassic Map Server/packets/send/supportdesk/IssueListResponsePacket.cs +++ b/FFXIVClassic Map Server/packets/send/supportdesk/IssueListResponsePacket.cs @@ -26,7 +26,7 @@ namespace FFXIVClassic_Map_Server.packets.send.supportdesk } } - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/packets/send/supportdesk/StartGMTicketPacket.cs b/FFXIVClassic Map Server/packets/send/supportdesk/StartGMTicketPacket.cs index 8c643882..20eb219c 100644 --- a/FFXIVClassic Map Server/packets/send/supportdesk/StartGMTicketPacket.cs +++ b/FFXIVClassic Map Server/packets/send/supportdesk/StartGMTicketPacket.cs @@ -11,7 +11,7 @@ namespace FFXIVClassic_Map_Server.packets.send.supportdesk { byte[] data = new byte[PACKET_SIZE - 0x20]; data[0] = (byte)(startGM ? 1 : 0); - return new SubPacket(OPCODE, playerActorID, playerActorID, data); + return new SubPacket(OPCODE, playerActorID, data); } } } diff --git a/FFXIVClassic Map Server/utils/ActorPropertyPacketUtil.cs b/FFXIVClassic Map Server/utils/ActorPropertyPacketUtil.cs index e5f11f8f..20200876 100644 --- a/FFXIVClassic Map Server/utils/ActorPropertyPacketUtil.cs +++ b/FFXIVClassic Map Server/utils/ActorPropertyPacketUtil.cs @@ -9,16 +9,14 @@ namespace FFXIVClassic_Map_Server.utils class ActorPropertyPacketUtil { private Actor forActor; - private uint playerActorId; private List subPackets = new List(); private SetActorPropetyPacket currentActorPropertyPacket; private string currentTarget; - public ActorPropertyPacketUtil(string firstTarget, Actor forActor, uint playerActorId) + public ActorPropertyPacketUtil(string firstTarget, Actor forActor) { currentActorPropertyPacket = new SetActorPropetyPacket(firstTarget); this.forActor = forActor; - this.playerActorId = playerActorId; this.currentTarget = firstTarget; } @@ -28,7 +26,7 @@ namespace FFXIVClassic_Map_Server.utils { currentActorPropertyPacket.SetIsMore(true); currentActorPropertyPacket.AddTarget(); - subPackets.Add(currentActorPropertyPacket.BuildPacket(playerActorId, forActor.actorId)); + subPackets.Add(currentActorPropertyPacket.BuildPacket(forActor.actorId)); currentActorPropertyPacket = new SetActorPropetyPacket(currentTarget); currentActorPropertyPacket.AddProperty(forActor, property); } @@ -45,7 +43,7 @@ namespace FFXIVClassic_Map_Server.utils { currentActorPropertyPacket.AddTarget(); currentActorPropertyPacket.SetIsMore(false); - subPackets.Add(currentActorPropertyPacket.BuildPacket(playerActorId, forActor.actorId)); + subPackets.Add(currentActorPropertyPacket.BuildPacket(forActor.actorId)); return subPackets; } diff --git a/FFXIVClassic Map Server/utils/CharacterUtils.cs b/FFXIVClassic Map Server/utils/CharacterUtils.cs index 3df2bc6a..b420b5fa 100644 --- a/FFXIVClassic Map Server/utils/CharacterUtils.cs +++ b/FFXIVClassic Map Server/utils/CharacterUtils.cs @@ -96,5 +96,31 @@ namespace FFXIVClassic_Map_Server.utils } } + public static string GetClassNameForId(short id) + { + switch (id) + { + case 2: return "pug"; + case 3: return "gla"; + case 4: return "mrd"; + case 7: return "arc"; + case 8: return "lnc"; + case 22: return "thm"; + case 23: return "cnj"; + case 29: return "crp"; + case 30: return "bsm"; + case 31: return "arm"; + case 32: return "gsm"; + case 33: return "ltw"; + case 34: return "wvr"; + case 35: return "alc"; + case 36: return "cul"; + case 39: return "min"; + case 40: return "btn"; + case 41: return "fsh"; + default: return "undefined"; + } + } + } } diff --git a/FFXIVClassic Map Server/utils/NavmeshUtils.cs b/FFXIVClassic Map Server/utils/NavmeshUtils.cs new file mode 100644 index 00000000..8145d0cb --- /dev/null +++ b/FFXIVClassic Map Server/utils/NavmeshUtils.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using SharpNav; +using SharpNav.Pathfinding; +using SharpNav.Crowds; +using SharpNav.IO; +using FFXIVClassic.Common; + +namespace FFXIVClassic_Map_Server.utils +{ + class NavmeshUtils + { + + // navmesh + 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 true; + } + + public static SharpNav.TiledNavMesh LoadNavmesh(TiledNavMesh navmesh, string filePath) + { + SharpNav.IO.NavMeshSerializer serializer; + if (System.IO.Path.GetExtension(filePath) == ".snb") + serializer = new SharpNav.IO.Binary.NavMeshBinarySerializer(); + else + serializer = new SharpNav.IO.Json.NavMeshJsonSerializer(); + + return serializer.Deserialize(System.IO.Path.Combine("../../navmesh/", filePath)); + //return navmesh = new SharpNav.IO.Json.NavMeshJsonSerializer().Deserialize(filePath); + } + + public static List GetPath(actors.area.Zone zone, float x, float y, float z, float targetX, float targetY, float targetZ, float stepSize = 0.70f, int pathSize = 45, float polyRadius = 0.0f, bool skipToTarget = false) + { + return GetPath(zone, new Vector3(x, y, z), new Vector3(targetX, targetY, targetZ), stepSize, pathSize, polyRadius, skipToTarget); + } + + #region sharpnav stuff + // Copyright (c) 2013-2016 Robert Rouhani and other contributors (see CONTRIBUTORS file). + // Licensed under the MIT License - https://raw.github.com/Robmaister/SharpNav/master/LICENSE + + public static List GetPath(actors.area.Zone zone, Vector3 startVec, Vector3 endVec, float stepSize = 0.70f, int pathSize = 45, float polyRadius = 0.0f, bool skipToTarget = false) + { + var navMesh = zone.tiledNavMesh; + var navMeshQuery = zone.navMeshQuery; + + // no navmesh loaded, run straight to player + if (navMesh == null) + { + return new List() { endVec }; + } + + // no need to waste cycles finding path to same point + if (startVec.X == endVec.X && startVec.Y == endVec.Y && startVec.Z == endVec.Z && polyRadius == 0.0f) + { + return null; + } + + var smoothPath = new List(pathSize) { }; + + NavQueryFilter filter = new NavQueryFilter(); + + NavPoint startPt, endPt; + + try + { + SharpNav.Geometry.Vector3 c = new SharpNav.Geometry.Vector3(startVec.X, startVec.Y, startVec.Z); + SharpNav.Geometry.Vector3 ep = new SharpNav.Geometry.Vector3(endVec.X, endVec.Y, endVec.Z); + + 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); + + //calculate the overall path, which contains an array of polygon references + int MAX_POLYS = 256; + var path = new SharpNav.Pathfinding.Path(); + + navMeshQuery.FindPath(ref startPt, ref endPt, filter, path); + + //find a smooth path over the mesh surface + int npolys = path.Count; + SharpNav.Geometry.Vector3 iterPos = new SharpNav.Geometry.Vector3(); + SharpNav.Geometry.Vector3 targetPos = new SharpNav.Geometry.Vector3(); + navMeshQuery.ClosestPointOnPoly(startPt.Polygon, startPt.Position, ref iterPos); + navMeshQuery.ClosestPointOnPoly(path[npolys - 1], endPt.Position, ref targetPos); + + // set target to random point at end of path + if (polyRadius != 0.0f) + { + var randPoly = navMeshQuery.FindRandomPointAroundCircle(endPt, polyRadius); + targetPos = randPoly.Position; + } + + if (skipToTarget) + { + return new List() { new Vector3(targetPos.X, targetPos.Y, targetPos.Z) }; + } + smoothPath.Add(new Vector3(iterPos.X, iterPos.Y, iterPos.Z)); + + //float STEP_SIZE = 0.70f; + float SLOP = 0.15f; + while (npolys > 0 && smoothPath.Count < smoothPath.Capacity) + { + //find location to steer towards + SharpNav.Geometry.Vector3 steerPos = new SharpNav.Geometry.Vector3(); + StraightPathFlags steerPosFlag = 0; + NavPolyId steerPosRef = NavPolyId.Null; + + if (!GetSteerTarget(navMeshQuery, iterPos, targetPos, SLOP, path, ref steerPos, ref steerPosFlag, ref steerPosRef)) + break; + + bool endOfPath = (steerPosFlag & StraightPathFlags.End) != 0 ? true : false; + bool offMeshConnection = (steerPosFlag & StraightPathFlags.OffMeshConnection) != 0 ? true : false; + + //find movement delta + SharpNav.Geometry.Vector3 delta = steerPos - iterPos; + float len = (float)Math.Sqrt(SharpNav.Geometry.Vector3.Dot(delta, delta)); + + //if steer target is at end of path or off-mesh link + //don't move past location + if ((endOfPath || offMeshConnection) && len < stepSize) + len = 1; + else + len = stepSize / len; + + SharpNav.Geometry.Vector3 moveTgt = new SharpNav.Geometry.Vector3(); + VMad(ref moveTgt, iterPos, delta, len); + + //move + SharpNav.Geometry.Vector3 result = new SharpNav.Geometry.Vector3(); + List visited = new List(pathSize); + NavPoint startPoint = new NavPoint(path[0], iterPos); + navMeshQuery.MoveAlongSurface(ref startPoint, ref moveTgt, out result, visited); + path.FixupCorridor(visited); + npolys = path.Count; + float h = 0; + navMeshQuery.GetPolyHeight(path[0], result, ref h); + result.Y = h; + iterPos = result; + + //handle end of path when close enough + if (endOfPath && InRange(iterPos, steerPos, SLOP, 1000.0f)) + { + //reached end of path + iterPos = targetPos; + if (smoothPath.Count < smoothPath.Capacity) + { + smoothPath.Add(new Vector3(iterPos.X, iterPos.Y, iterPos.Z)); + } + break; + } + + //store results + if (smoothPath.Count < smoothPath.Capacity) + { + smoothPath.Add(new Vector3(iterPos.X, iterPos.Y, iterPos.Z)); + } + } + } + catch(Exception e) + { + Program.Log.Error(e.Message); + Program.Log.Error("Start pos {0} {1} {2} end pos {3} {4} {5}", startVec.X, startVec.Y, startVec.Z, endVec.X, endVec.Y, endVec.Z); + // todo: probably log this + return new List() { endVec }; + } + return smoothPath; + } + + /// + /// Scaled vector addition + /// + /// Result + /// Vector 1 + /// Vector 2 + /// Scalar + private static void VMad(ref SharpNav.Geometry.Vector3 dest, SharpNav.Geometry.Vector3 v1, SharpNav.Geometry.Vector3 v2, float s) + { + dest.X = v1.X + v2.X * s; + dest.Y = v1.Y + v2.Y * s; + dest.Z = v1.Z + v2.Z * s; + } + + private static bool GetSteerTarget(NavMeshQuery navMeshQuery, SharpNav.Geometry.Vector3 startPos, SharpNav.Geometry.Vector3 endPos, float minTargetDist, SharpNav.Pathfinding.Path path, + ref SharpNav.Geometry.Vector3 steerPos, ref StraightPathFlags steerPosFlag, ref NavPolyId steerPosRef) + { + StraightPath steerPath = new StraightPath(); + navMeshQuery.FindStraightPath(startPos, endPos, path, steerPath, 0); + int nsteerPath = steerPath.Count; + if (nsteerPath == 0) + return false; + + //find vertex far enough to steer to + int ns = 0; + while (ns < nsteerPath) + { + if ((steerPath[ns].Flags & StraightPathFlags.OffMeshConnection) != 0 || + !InRange(steerPath[ns].Point.Position, startPos, minTargetDist, 1000.0f)) + break; + + ns++; + } + + //failed to find good point to steer to + if (ns >= nsteerPath) + return false; + + steerPos = steerPath[ns].Point.Position; + steerPos.Y = startPos.Y; + steerPosFlag = steerPath[ns].Flags; + if (steerPosFlag == StraightPathFlags.None && ns == (nsteerPath - 1)) + steerPosFlag = StraightPathFlags.End; // otherwise seeks path infinitely!!! + steerPosRef = steerPath[ns].Point.Polygon; + + return true; + } + + private static bool InRange(SharpNav.Geometry.Vector3 v1, SharpNav.Geometry.Vector3 v2, float r, float h) + { + float dx = v2.X - v1.X; + float dy = v2.Y - v1.Y; + float dz = v2.Z - v1.Z; + return (dx * dx + dz * dz) < (r * r) && Math.Abs(dy) < h; + } + #endregion + + public static Vector3 GamePosToNavmeshPos(float x, float y, float z) + { + return new Vector3(x, -z, y); + } + + public static Vector3 NavmeshPosToGamePos(float x, float y, float z) + { + return new Vector3(x, z, -y); + } + + } +} diff --git a/FFXIVClassic Map Server/utils/SQLGeneration.cs b/FFXIVClassic Map Server/utils/SQLGeneration.cs index 26e5dea3..93e626ed 100644 --- a/FFXIVClassic Map Server/utils/SQLGeneration.cs +++ b/FFXIVClassic Map Server/utils/SQLGeneration.cs @@ -50,7 +50,7 @@ namespace FFXIVClassic_Map_Server.utils name = matches[2].Value.Trim(','); } - catch (FormatException e) + catch (FormatException) { continue; } placenames.Add(id, name); @@ -72,7 +72,7 @@ namespace FFXIVClassic_Map_Server.utils id = UInt32.Parse(matches[0].Value.Trim(',')); pId = UInt32.Parse(matches[1].Value.Trim(',')); } - catch (FormatException e) + catch (FormatException) { continue; } cmd.Parameters["@id"].Value = id; @@ -131,7 +131,7 @@ namespace FFXIVClassic_Map_Server.utils nameId = UInt32.Parse(matches[6].Value.Trim(',')); } - catch (FormatException e) + catch (FormatException) { continue; } cmd.Parameters["@id"].Value = id; @@ -198,7 +198,7 @@ namespace FFXIVClassic_Map_Server.utils } - catch (FormatException e) + catch (FormatException) { continue; } cmd.Parameters["@id"].Value = id; @@ -260,7 +260,7 @@ namespace FFXIVClassic_Map_Server.utils name = matches2[9].Value.Trim(','); points = UInt32.Parse(matches[3].Value.Trim(',')); } - catch (FormatException e) + catch (FormatException) { continue; } if (id == 100) diff --git a/FFXIVClassic World Server/App.config b/FFXIVClassic World Server/App.config index 329f3599..0860683f 100644 --- a/FFXIVClassic World Server/App.config +++ b/FFXIVClassic World Server/App.config @@ -1,7 +1,7 @@ - + diff --git a/FFXIVClassic World Server/DataObjects/ClientConnection.cs b/FFXIVClassic World Server/DataObjects/ClientConnection.cs index 282dd52f..7c3427a8 100644 --- a/FFXIVClassic World Server/DataObjects/ClientConnection.cs +++ b/FFXIVClassic World Server/DataObjects/ClientConnection.cs @@ -23,8 +23,11 @@ namespace FFXIVClassic_World_Server SendPacketQueue.Add(packet); } - public void QueuePacket(SubPacket subpacket, bool isAuthed, bool isEncrypted) + public void QueuePacket(SubPacket subpacket) { + bool isAuthed = true; + bool isEncrypted = false; + subpacket.SetTargetId(owner.sessionId); SendPacketQueue.Add(BasePacket.CreatePacket(subpacket, isAuthed, isEncrypted)); } @@ -44,7 +47,7 @@ namespace FFXIVClassic_World_Server socket.Send(packetBytes); } catch (Exception e) - { Program.Log.Error("Weird case, socket was d/ced: {0}", e); } + { Program.Log.Error(e, "Weird case, socket was d/ced: {0}"); } } } diff --git a/FFXIVClassic World Server/DataObjects/Group/Group.cs b/FFXIVClassic World Server/DataObjects/Group/Group.cs index 2cef6f9e..43ce5b77 100644 --- a/FFXIVClassic World Server/DataObjects/Group/Group.cs +++ b/FFXIVClassic World Server/DataObjects/Group/Group.cs @@ -12,6 +12,7 @@ namespace FFXIVClassic_World_Server.DataObjects.Group public const uint GroupInvitationRelationGroup = 50001; public const uint TradeRelationGroup = 50002; + public const uint RetainerMeetingRelationGroup = 50003; public const uint BazaarBuyItemRelationGroup = 50009; public const uint RetainerGroup = 80001; @@ -97,33 +98,34 @@ namespace FFXIVClassic_World_Server.DataObjects.Group ulong time = Utils.MilisUnixTimeStampUTC(); List members = BuildMemberList(session.sessionId); - session.clientConnection.QueuePacket(GroupHeaderPacket.buildPacket(session.sessionId, session.currentZoneId, time, this), true, false); - session.clientConnection.QueuePacket(GroupMembersBeginPacket.buildPacket(session.sessionId, session.currentZoneId, time, this), true, false); + session.clientConnection.QueuePacket(GroupHeaderPacket.buildPacket(session.sessionId, session.currentZoneId, time, this)); + session.clientConnection.QueuePacket(GroupMembersBeginPacket.buildPacket(session.sessionId, session.currentZoneId, time, this)); int currentIndex = 0; while (true) { - if (GetMemberCount() - currentIndex >= 64) - session.clientConnection.QueuePacket(GroupMembersX64Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex), true, false); - else if (GetMemberCount() - currentIndex >= 32) - session.clientConnection.QueuePacket(GroupMembersX32Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex), true, false); - else if (GetMemberCount() - currentIndex >= 16) - session.clientConnection.QueuePacket(GroupMembersX16Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex), true, false); - else if (GetMemberCount() - currentIndex > 0) - session.clientConnection.QueuePacket(GroupMembersX08Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex), true, false); + int memberCount = Math.Min(GetMemberCount(), members.Count); + if (memberCount - currentIndex >= 64) + session.clientConnection.QueuePacket(GroupMembersX64Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex)); + else if (memberCount - currentIndex >= 32) + session.clientConnection.QueuePacket(GroupMembersX32Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex)); + else if (memberCount - currentIndex >= 16) + session.clientConnection.QueuePacket(GroupMembersX16Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex)); + else if (memberCount - currentIndex > 0) + session.clientConnection.QueuePacket(GroupMembersX08Packet.buildPacket(session.sessionId, session.currentZoneId, time, members, ref currentIndex)); else break; } - session.clientConnection.QueuePacket(GroupMembersEndPacket.buildPacket(session.sessionId, session.currentZoneId, time, this), true, false); + session.clientConnection.QueuePacket(GroupMembersEndPacket.buildPacket(session.sessionId, session.currentZoneId, time, this)); } public void SendDeletePacket(Session session) { if (session != null) - session.clientConnection.QueuePacket(DeleteGroupPacket.buildPacket(session.sessionId, this), true, false); + session.clientConnection.QueuePacket(DeleteGroupPacket.buildPacket(session.sessionId, this)); } public virtual void SendInitWorkValues(Session session) diff --git a/FFXIVClassic World Server/DataObjects/Group/Linkshell.cs b/FFXIVClassic World Server/DataObjects/Group/Linkshell.cs index 27748ab7..a49178dc 100644 --- a/FFXIVClassic World Server/DataObjects/Group/Linkshell.cs +++ b/FFXIVClassic World Server/DataObjects/Group/Linkshell.cs @@ -120,9 +120,9 @@ namespace FFXIVClassic_World_Server.DataObjects.Group } groupWork.setTarget("/_init"); - SubPacket test = groupWork.buildPacket(session.sessionId, session.sessionId); + SubPacket test = groupWork.buildPacket(session.sessionId); test.DebugPrintSubPacket(); - session.clientConnection.QueuePacket(test, true, false); + session.clientConnection.QueuePacket(test); } public void ResendWorkValues() @@ -148,8 +148,8 @@ namespace FFXIVClassic_World_Server.DataObjects.Group Session session = Server.GetServer().GetSession(members[i].charaId); if (session != null) { - SubPacket test = groupWork.buildPacket(session.sessionId, session.sessionId); - session.clientConnection.QueuePacket(test, true, false); + SubPacket test = groupWork.buildPacket(session.sessionId); + session.clientConnection.QueuePacket(test); } } } diff --git a/FFXIVClassic World Server/DataObjects/Group/Party.cs b/FFXIVClassic World Server/DataObjects/Group/Party.cs index 7fc1a9eb..da7e4ea4 100644 --- a/FFXIVClassic World Server/DataObjects/Group/Party.cs +++ b/FFXIVClassic World Server/DataObjects/Group/Party.cs @@ -189,7 +189,7 @@ namespace FFXIVClassic_World_Server.DataObjects.Group if (session == null) continue; else - session.clientConnection.QueuePacket(leaderUpdate.buildPacket(session.sessionId, session.sessionId), true, false); + session.clientConnection.QueuePacket(leaderUpdate.buildPacket(session.sessionId)); } } @@ -226,8 +226,8 @@ namespace FFXIVClassic_World_Server.DataObjects.Group groupWork.addProperty(this, "partyGroupWork._globalTemp.owner"); groupWork.setTarget("/_init"); - SubPacket test = groupWork.buildPacket(session.sessionId, session.sessionId); - session.clientConnection.QueuePacket(test, true, false); + SubPacket test = groupWork.buildPacket(session.sessionId); + session.clientConnection.QueuePacket(test); test.DebugPrintSubPacket(); } diff --git a/FFXIVClassic World Server/DataObjects/Group/Relation.cs b/FFXIVClassic World Server/DataObjects/Group/Relation.cs index 8564ee8a..f38727c3 100644 --- a/FFXIVClassic World Server/DataObjects/Group/Relation.cs +++ b/FFXIVClassic World Server/DataObjects/Group/Relation.cs @@ -66,9 +66,9 @@ namespace FFXIVClassic_World_Server.DataObjects.Group groupWork.addProperty(this, "work._globalTemp.variableCommand"); groupWork.setTarget("/_init"); - SubPacket test = groupWork.buildPacket(session.sessionId, session.sessionId); + SubPacket test = groupWork.buildPacket(session.sessionId); test.DebugPrintSubPacket(); - session.clientConnection.QueuePacket(test, true, false); + session.clientConnection.QueuePacket(test); } } diff --git a/FFXIVClassic World Server/DataObjects/Group/RetainerGroup.cs b/FFXIVClassic World Server/DataObjects/Group/RetainerGroup.cs index bf440579..bfe5c69f 100644 --- a/FFXIVClassic World Server/DataObjects/Group/RetainerGroup.cs +++ b/FFXIVClassic World Server/DataObjects/Group/RetainerGroup.cs @@ -49,8 +49,8 @@ namespace FFXIVClassic_World_Server.DataObjects.Group groupWork.setTarget("/_init"); - SubPacket test = groupWork.buildPacket(session.sessionId, session.sessionId); - session.clientConnection.QueuePacket(test, true, false); + SubPacket test = groupWork.buildPacket(session.sessionId); + session.clientConnection.QueuePacket(test); } public override int GetMemberCount() diff --git a/FFXIVClassic World Server/DataObjects/Group/RetainerMeetingRelationGroup.cs b/FFXIVClassic World Server/DataObjects/Group/RetainerMeetingRelationGroup.cs new file mode 100644 index 00000000..d54afb16 --- /dev/null +++ b/FFXIVClassic World Server/DataObjects/Group/RetainerMeetingRelationGroup.cs @@ -0,0 +1,36 @@ +using FFXIVClassic.Common; +using FFXIVClassic_World_Server.Actor.Group.Work; +using FFXIVClassic_World_Server.Packets.Send.Subpackets.Groups; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FFXIVClassic_World_Server.DataObjects.Group +{ + class RetainerMeetingRelationGroup : Relation + { + public RetainerMeetingRelationGroup(ulong groupIndex, uint host, uint other, uint command, ulong topicGroup) + : base(groupIndex, host, other, command, topicGroup) + { + + } + + public override uint GetTypeId() + { + return Group.RetainerMeetingRelationGroup; + } + + public override void SendInitWorkValues(Session session) + { + SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex); + groupWork.setTarget("/_init"); + + SubPacket test = groupWork.buildPacket(session.sessionId); + test.DebugPrintSubPacket(); + session.clientConnection.QueuePacket(test); + } + + } +} diff --git a/FFXIVClassic World Server/DataObjects/Session.cs b/FFXIVClassic World Server/DataObjects/Session.cs index 966d3248..4746f659 100644 --- a/FFXIVClassic World Server/DataObjects/Session.cs +++ b/FFXIVClassic World Server/DataObjects/Session.cs @@ -1,13 +1,4 @@ -using FFXIVClassic.Common; -using FFXIVClassic_World_Server.DataObjects.Group; -using FFXIVClassic_World_Server.Packets.Send.Subpackets; -using FFXIVClassic_World_Server.Packets.Send.Subpackets.Groups; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading.Tasks; +using FFXIVClassic_World_Server.Packets.Send.Subpackets; namespace FFXIVClassic_World_Server.DataObjects { @@ -38,34 +29,34 @@ namespace FFXIVClassic_World_Server.DataObjects { if (msgParams == null || msgParams.Length == 0) { - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, actorId, 0x5FF80001, textId, log), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, actorId, 0x5FF80001, textId, log)); } else - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, actorId, 0x5FF80001, textId, log, LuaUtils.CreateLuaParamList(msgParams)), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, actorId, 0x5FF80001, textId, log, LuaUtils.CreateLuaParamList(msgParams))); } public void SendGameMessage( ushort textId, byte log, params object[] msgParams) { if (msgParams == null || msgParams.Length == 0) - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, 0x5FF80001, textId, log), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, 0x5FF80001, textId, log)); else - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, 0x5FF80001, textId, log, LuaUtils.CreateLuaParamList(msgParams)), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, 0x5FF80001, textId, log, LuaUtils.CreateLuaParamList(msgParams))); } public void SendGameMessage( ushort textId, byte log, string customSender, params object[] msgParams) { if (msgParams == null || msgParams.Length == 0) - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, 0x5FF80001, textId, customSender, log), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, 0x5FF80001, textId, customSender, log)); else - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, 0x5FF80001, textId, customSender, log, LuaUtils.CreateLuaParamList(msgParams)), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, 0x5FF80001, textId, customSender, log, LuaUtils.CreateLuaParamList(msgParams))); } public void SendGameMessage(ushort textId, byte log, uint displayId, params object[] msgParams) { if (msgParams == null || msgParams.Length == 0) - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, 0x5FF80001, textId, displayId, log), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, 0x5FF80001, textId, displayId, log)); else - clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, sessionId, 0x5FF80001, textId, displayId, log, LuaUtils.CreateLuaParamList(msgParams)), true, false); + clientConnection.QueuePacket(GameMessagePacket.BuildPacket(0x5FF80001, 0x5FF80001, textId, displayId, log, LuaUtils.CreateLuaParamList(msgParams))); } diff --git a/FFXIVClassic World Server/DataObjects/ZoneServer.cs b/FFXIVClassic World Server/DataObjects/ZoneServer.cs index 2ed92de4..da665001 100644 --- a/FFXIVClassic World Server/DataObjects/ZoneServer.cs +++ b/FFXIVClassic World Server/DataObjects/ZoneServer.cs @@ -59,7 +59,7 @@ namespace FFXIVClassic_World_Server.DataObjects throw new ApplicationException("Error occured starting listeners, check inner exception", e); } } - catch (Exception e) + catch (Exception) { Program.Log.Error("Failed to connect"); return false; } return true; @@ -76,7 +76,7 @@ namespace FFXIVClassic_World_Server.DataObjects zoneServerConnection.Send(packetBytes); } catch (Exception e) - { Program.Log.Error("Weird case, socket was d/ced: {0}", e); } + { Program.Log.Error(e, "Weird case, socket was d/ced: {0}"); } } else { @@ -150,9 +150,9 @@ namespace FFXIVClassic_World_Server.DataObjects } } - public void SendSessionStart(Session session) + public void SendSessionStart(Session session, bool isLogin = false) { - SendPacket(SessionBeginPacket.BuildPacket(session)); + SendPacket(SessionBeginPacket.BuildPacket(session, isLogin)); } public void SendSessionEnd(Session session) diff --git a/FFXIVClassic World Server/Database.cs b/FFXIVClassic World Server/Database.cs index 0a63ac4e..36c7df4b 100644 --- a/FFXIVClassic World Server/Database.cs +++ b/FFXIVClassic World Server/Database.cs @@ -165,7 +165,7 @@ namespace FFXIVClassic_World_Server try { conn.Open(); - MySqlCommand cmd = new MySqlCommand("SELECT id, name, classActorId, cdIDOffset, placeName, conditions, level FROM server_retainers INNER JOIN characters_retainers ON retainerId = server_retainers.id WHERE characterId = @charaId", conn); + MySqlCommand cmd = new MySqlCommand("SELECT id, name, actorClassId, cdIDOffset, placeName, conditions, level FROM server_retainers INNER JOIN characters_retainers ON retainerId = server_retainers.id WHERE characterId = @charaId", conn); cmd.Parameters.AddWithValue("@charaId", charaId); using (MySqlDataReader Reader = cmd.ExecuteReader()) { @@ -173,13 +173,13 @@ namespace FFXIVClassic_World_Server { uint id = Reader.GetUInt32("id") | 0xE0000000; string name = Reader.GetString("name"); - uint classActorId = Reader.GetUInt32("classActorId"); + uint actorClassId = Reader.GetUInt32("actorClassId"); byte cdIDOffset = Reader.GetByte("cdIDOffset"); ushort placeName = Reader.GetUInt16("placeName"); byte conditions = Reader.GetByte("conditions"); byte level = Reader.GetByte("level"); - members.Add(new RetainerGroupMember(id, name, classActorId, cdIDOffset, placeName, conditions, level)); + members.Add(new RetainerGroupMember(id, name, actorClassId, cdIDOffset, placeName, conditions, level)); } } } diff --git a/FFXIVClassic World Server/FFXIVClassic World Server.csproj b/FFXIVClassic World Server/FFXIVClassic World Server.csproj index 4710093f..affed8c5 100644 --- a/FFXIVClassic World Server/FFXIVClassic World Server.csproj +++ b/FFXIVClassic World Server/FFXIVClassic World Server.csproj @@ -9,7 +9,7 @@ Properties FFXIVClassic_World_Server FFXIVClassic World Server - v4.5 + v4.5.1 512 true @@ -48,15 +48,31 @@ prompt 4 + + true + bin\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + ..\packages\Cyotek.CircularBuffer.1.0.0.0\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll True - - ..\packages\Dapper.1.42\lib\net45\Dapper.dll - True - ..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll True @@ -65,10 +81,6 @@ ..\packages\NLog.4.3.5\lib\net45\NLog.dll True - - ..\packages\RabbitMQ.Client.4.0.0\lib\net451\RabbitMQ.Client.dll - True - @@ -95,6 +107,7 @@ + @@ -190,7 +203,8 @@ - xcopy "$(SolutionDir)data\world_config.ini" "$(SolutionDir)$(ProjectName)\$(OutDir)" /d + +