1
Fork 0
mirror of https://bitbucket.org/Ioncannon/project-meteor-server.git synced 2025-04-20 03:37:48 +00:00

Merged in skeletonhorn/ffxiv-classic-server-ai-fork/develop (pull request #6)

Update develop and pr combat changes to demo's fork
This commit is contained in:
yogurt 2019-05-30 06:33:30 +00:00
commit 6ae181fa0a
822 changed files with 31094 additions and 3890 deletions

View file

@ -239,7 +239,14 @@ namespace FFXIVClassic.Common
{ {
var subpacketData = subpacket.GetBytes(); var subpacketData = subpacket.GetBytes();
Array.Copy(subpacketData, 0, data, offset, subpacketData.Length); 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); Debug.Assert(data != null && offset == data.Length && header.packetSize == 0x10 + offset);
@ -266,7 +273,15 @@ namespace FFXIVClassic.Common
data = new byte[header.packetSize - 0x10]; data = new byte[header.packetSize - 0x10];
//Add Subpackets //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); Array.Copy(subpacketData, 0, data, 0, subpacketData.Length);
Debug.Assert(data != null); Debug.Assert(data != null);
@ -291,6 +306,13 @@ namespace FFXIVClassic.Common
//Get packet size //Get packet size
header.packetSize += (ushort) data.Length; 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); var packet = new BasePacket(header, data);
return packet; return packet;
} }
@ -390,17 +412,31 @@ namespace FFXIVClassic.Common
{ {
zipStream.CopyTo(resultStream); zipStream.CopyTo(resultStream);
packet.data = resultStream.ToArray(); 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 zipStream = new ZlibStream(compressedStream, Ionic.Zlib.CompressionMode.Compress))
using (var resultStream = new MemoryStream()) using (var resultStream = new MemoryStream())
{ {
zipStream.CopyTo(resultStream); 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();
} }
} }

View file

@ -60,7 +60,7 @@ namespace FFXIVClassic.Common
// Calculate a bitmask of the desired length // Calculate a bitmask of the desired length
long mask = 0; long mask = 0;
for (int i = 0; i < fieldLength; i++) for (int i = 0; i < fieldLength; i++)
mask |= 1 << i; mask |= 1L << i;
r |= ((UInt32)f.GetValue(t) & mask) << offset; r |= ((UInt32)f.GetValue(t) & mask) << offset;

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props')" /> <Import Project="..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props') AND '$(OS)' == 'Windows_NT'" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props') AND '$(OS)' == 'Windows_NT'" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FFXIVClassic.Common</RootNamespace> <RootNamespace>FFXIVClassic.Common</RootNamespace>
<AssemblyName>FFXIVClassic.Common</AssemblyName> <AssemblyName>FFXIVClassic.Common</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<TargetFrameworkProfile> <TargetFrameworkProfile>
</TargetFrameworkProfile> </TargetFrameworkProfile>
@ -36,6 +36,27 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="DotNetZip"> <Reference Include="DotNetZip">
@ -51,6 +72,7 @@
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -68,6 +90,7 @@
<Compile Include="STA_INIFile.cs" /> <Compile Include="STA_INIFile.cs" />
<Compile Include="SubPacket.cs" /> <Compile Include="SubPacket.cs" />
<Compile Include="Utils.cs" /> <Compile Include="Utils.cs" />
<Compile Include="Vector3.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />

View file

@ -72,9 +72,9 @@ namespace FFXIVClassic.Common
offset += header.subpacketSize; 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(); header = new SubPacketHeader();
@ -89,7 +89,7 @@ namespace FFXIVClassic.Common
} }
header.sourceId = sourceId; header.sourceId = sourceId;
header.targetId = targetId; header.targetId = 0;
if (isGameMessage) if (isGameMessage)
header.type = 0x03; header.type = 0x03;
@ -117,6 +117,11 @@ namespace FFXIVClassic.Common
data = original.data; data = original.data;
} }
public void SetTargetId(uint target)
{
this.header.targetId = target;
}
public byte[] GetHeaderBytes() public byte[] GetHeaderBytes()
{ {
var size = Marshal.SizeOf(header); var size = Marshal.SizeOf(header);

View file

@ -7,6 +7,7 @@ namespace FFXIVClassic.Common
public static class Utils public static class Utils
{ {
private static readonly uint[] _lookup32 = CreateLookup32(); 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() private static uint[] CreateLookup32()
{ {
@ -84,10 +85,10 @@ namespace FFXIVClassic.Common
return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray());
} }
public static uint UnixTimeStampUTC() public static uint UnixTimeStampUTC(DateTime? time = null)
{ {
uint unixTimeStamp; uint unixTimeStamp;
var currentTime = DateTime.Now; var currentTime = time ?? DateTime.Now;
var zuluTime = currentTime.ToUniversalTime(); var zuluTime = currentTime.ToUniversalTime();
var unixEpoch = new DateTime(1970, 1, 1); var unixEpoch = new DateTime(1970, 1, 1);
unixTimeStamp = (uint)zuluTime.Subtract(unixEpoch).TotalSeconds; unixTimeStamp = (uint)zuluTime.Subtract(unixEpoch).TotalSeconds;
@ -95,10 +96,10 @@ namespace FFXIVClassic.Common
return unixTimeStamp; return unixTimeStamp;
} }
public static ulong MilisUnixTimeStampUTC() public static ulong MilisUnixTimeStampUTC(DateTime? time = null)
{ {
ulong unixTimeStamp; ulong unixTimeStamp;
var currentTime = DateTime.Now; var currentTime = time ?? DateTime.Now;
var zuluTime = currentTime.ToUniversalTime(); var zuluTime = currentTime.ToUniversalTime();
var unixEpoch = new DateTime(1970, 1, 1); var unixEpoch = new DateTime(1970, 1, 1);
unixTimeStamp = (ulong)zuluTime.Subtract(unixEpoch).TotalMilliseconds; unixTimeStamp = (ulong)zuluTime.Subtract(unixEpoch).TotalMilliseconds;
@ -106,6 +107,11 @@ namespace FFXIVClassic.Common
return unixTimeStamp; return unixTimeStamp;
} }
public static DateTime UnixTimeStampToDateTime(uint timestamp)
{
return epoch.AddSeconds(timestamp);
}
public static ulong SwapEndian(ulong input) public static ulong SwapEndian(ulong input)
{ {
return 0x00000000000000FF & (input >> 56) | return 0x00000000000000FF & (input >> 56) |
@ -351,5 +357,95 @@ namespace FFXIVClassic.Common
{ {
return (value >> bits) | (value << (16 - bits)); return (value >> bits) | (value << (16 - bits));
} }
public static T Clamp<T>(this T value, T min, T max) where T : IComparable<T>
{
if (value.CompareTo(min) < 0)
return min;
else if (value.CompareTo(max) > 0)
return max;
else
return value;
}
public static T Min<T>(this T value, T min) where T : IComparable<T>
{
if (value.CompareTo(min) > 0)
return min;
else
return value;
}
public static T Max<T>(this T value, T max) where T : IComparable<T>
{
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;
}
} }
} }

View file

@ -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;
}
}
}

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<system.data> <system.data>
<DbProviderFactories> <DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient" /> <remove invariant="MySql.Data.MySqlClient"/>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" /> <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"/>
</DbProviderFactories> </DbProviderFactories>
</system.data> </system.data>
</configuration> <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>

View file

@ -58,7 +58,7 @@ namespace FFXIVClassic_Lobby_Server
socket.Send(packetBytes); socket.Send(packetBytes);
} }
catch(Exception e) 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}"); }
} }
} }

View file

@ -1,6 +1,5 @@
using FFXIVClassic_Lobby_Server.dataobjects; using FFXIVClassic_Lobby_Server.dataobjects;
using MySql.Data.MySqlClient; using MySql.Data.MySqlClient;
using Dapper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -219,7 +218,7 @@ namespace FFXIVClassic_Lobby_Server
{ {
MySqlCommand cmd = new MySqlCommand(); MySqlCommand cmd = new MySqlCommand();
cmd.Connection = conn; 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.Prepare();
cmd.Parameters.AddWithValue("@characterId", cid); cmd.Parameters.AddWithValue("@characterId", cid);
@ -231,15 +230,51 @@ namespace FFXIVClassic_Lobby_Server
catch (MySqlException e) catch (MySqlException e)
{ {
Program.Log.Error(e.ToString()); 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<uint> defaultActions = new List<uint>();
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 finally
{ {
conn.Dispose(); conn.Dispose();
} }
} }
Program.Log.Debug("[SQL] CID={0} state updated to active(2).", cid); Program.Log.Debug("[SQL] CID={0} state updated to active(2).", cid);
@ -325,48 +360,105 @@ namespace FFXIVClassic_Lobby_Server
public static List<World> GetServers() public static List<World> 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<World> worldList = new List<World>();
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<World> worldList = null;
try try
{ {
conn.Open(); conn.Open();
worldList = conn.Query<World>("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) catch (MySqlException e)
{ {
Program.Log.Error(e.ToString()); Program.Log.Error(e.ToString());
worldList = new List<World>(); } worldList = new List<World>();
}
finally finally
{ {
conn.Dispose(); conn.Dispose();
} }
return worldList;
} }
return worldList;
} }
public static World GetServer(uint serverId) 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 try
{ {
conn.Open(); conn.Open();
world = conn.Query<World>("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) catch (MySqlException e)
{ {
Program.Log.Error(e.ToString()); Program.Log.Error(e.ToString());
} }
finally finally
{ {
conn.Dispose(); conn.Dispose();
} }
return world;
} }
return world;
} }
public static List<Character> GetCharacters(uint userId) public static List<Character> GetCharacters(uint userId)
@ -432,9 +524,11 @@ namespace FFXIVClassic_Lobby_Server
Character chara = null; 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))) 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 SELECT
id, id,
slot, slot,
@ -454,100 +548,179 @@ namespace FFXIVClassic_Lobby_Server
INNER JOIN characters_parametersave ON id = characters_parametersave.characterId INNER JOIN characters_parametersave ON id = characters_parametersave.characterId
WHERE id = @charId"; WHERE id = @charId";
MySqlCommand cmd = new MySqlCommand(query, conn); MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("@charId", charId); cmd.Parameters.AddWithValue("@charId", charId);
using (MySqlDataReader reader = cmd.ExecuteReader()) using (MySqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{ {
chara = new Character(); if (reader.Read())
chara.id = reader.GetUInt32("id"); {
chara.slot = reader.GetUInt16("slot"); chara = new Character();
chara.serverId = reader.GetUInt16("serverId"); chara.id = reader.GetUInt32("id");
chara.name = reader.GetString("name"); chara.slot = reader.GetUInt16("slot");
chara.isLegacy = reader.GetBoolean("isLegacy"); chara.serverId = reader.GetUInt16("serverId");
chara.doRename = reader.GetBoolean("doRename"); chara.name = reader.GetString("name");
chara.currentZoneId = reader.GetUInt32("currentZoneId"); chara.isLegacy = reader.GetBoolean("isLegacy");
chara.guardian = reader.GetByte("guardian"); chara.doRename = reader.GetBoolean("doRename");
chara.birthMonth = reader.GetByte("birthMonth"); chara.currentZoneId = reader.GetUInt32("currentZoneId");
chara.birthDay = reader.GetByte("birthDay"); chara.guardian = reader.GetByte("guardian");
chara.initialTown = reader.GetByte("initialTown"); chara.birthMonth = reader.GetByte("birthMonth");
chara.tribe = reader.GetByte("tribe"); chara.birthDay = reader.GetByte("birthDay");
chara.currentClass = reader.GetByte("mainSkill"); chara.initialTown = reader.GetByte("initialTown");
//chara.currentJob = ??? chara.tribe = reader.GetByte("tribe");
chara.currentLevel = reader.GetInt16("mainSkillLevel"); 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; return chara;
} }
public static Appearance GetAppearance(uint charaId) 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))) 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 try
{ {
conn.Open(); conn.Open();
appearance = conn.Query<Appearance>("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) catch (MySqlException e)
{ {
Program.Log.Error(e.ToString()); Program.Log.Error(e.ToString());
} }
finally finally
{ {
conn.Dispose(); conn.Dispose();
} }
return appearance;
} }
return appearance;
} }
public static List<String> GetReservedNames(uint userId) public static List<String> GetReservedNames(uint userId)
{ {
List<String> reservedNames = new List<String>();
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))) 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<String> nameList = null;
try try
{ {
conn.Open(); conn.Open();
nameList = conn.Query<String>("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) catch (MySqlException e)
{ {
Program.Log.Error(e.ToString()); Program.Log.Error(e.ToString());
nameList = new List<String>(); }
}
finally finally
{ {
conn.Dispose(); conn.Dispose();
} }
return nameList;
} }
return reservedNames;
} }
public static List<Retainer> GetRetainers(uint userId) public static List<Retainer> 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))) return new List<Retainer>();
{
List<Retainer> retainerList = null;
try
{
conn.Open();
retainerList = conn.Query<Retainer>("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<Retainer>(); }
finally
{
conn.Dispose();
}
return retainerList;
}
} }
} }

View file

@ -10,7 +10,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FFXIVClassic_Lobby_Server</RootNamespace> <RootNamespace>FFXIVClassic_Lobby_Server</RootNamespace>
<AssemblyName>FFXIVClassic_Lobby_Server</AssemblyName> <AssemblyName>FFXIVClassic_Lobby_Server</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper> <IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl> <PublishUrl>publish\</PublishUrl>
@ -28,6 +28,7 @@
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<NuGetPackageImportStamp>cc1ba6f5</NuGetPackageImportStamp> <NuGetPackageImportStamp>cc1ba6f5</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -50,13 +51,32 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Cyotek.Collections.Generic.CircularBuffer"> <Reference Include="Cyotek.Collections.Generic.CircularBuffer">
<HintPath>..\packages\Cyotek.CircularBuffer.1.0.0.0\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll</HintPath> <HintPath>..\packages\Cyotek.CircularBuffer.1.0.0.0\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll</HintPath>
</Reference> </Reference>
<Reference Include="Dapper">
<HintPath>..\packages\Dapper.1.42\lib\net45\Dapper.dll</HintPath>
</Reference>
<Reference Include="FFXIVClassic.Common"> <Reference Include="FFXIVClassic.Common">
<HintPath>..\FFXIVClassic Common Class Lib\bin\Debug\FFXIVClassic.Common.dll</HintPath> <HintPath>..\FFXIVClassic Common Class Lib\bin\Debug\FFXIVClassic.Common.dll</HintPath>
</Reference> </Reference>
@ -64,10 +84,6 @@
<HintPath>..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll</HintPath> <HintPath>..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
<Private>True</Private> <Private>True</Private>

View file

@ -90,14 +90,15 @@ namespace FFXIVClassic_Lobby_Server
if (userId == 0) if (userId == 0)
{ {
ErrorPacket errorPacket = new ErrorPacket(sessionPacket.sequence, 0, 0, 13001, "Your session has expired, please login again."); ErrorPacket errorPacket = new ErrorPacket(sessionPacket.sequence, 0, 0, 13001, "Your session has expired, please login again.");
SubPacket subpacket = errorPacket.BuildPacket(); SubPacket subpacket = errorPacket.BuildPacket();
BasePacket errorBasePacket = BasePacket.CreatePacket(subpacket, true, false); subpacket.SetTargetId(0xe0006868);
BasePacket.EncryptPacket(client.blowfish, errorBasePacket); BasePacket errorBasePacket = BasePacket.CreatePacket(subpacket, true, false);
client.QueuePacket(errorBasePacket); BasePacket.EncryptPacket(client.blowfish, errorBasePacket);
client.QueuePacket(errorBasePacket);
Program.Log.Info("Invalid session, kicking..."); Program.Log.Info("Invalid session, kicking...");
return; return;
} }
Program.Log.Info("USER ID: {0}", userId); Program.Log.Info("USER ID: {0}", userId);

View file

@ -20,7 +20,10 @@ namespace FFXIVClassic_Lobby_Server
TextWriterTraceListener myWriter = new TextWriterTraceListener(System.Console.Out); TextWriterTraceListener myWriter = new TextWriterTraceListener(System.Console.Out);
Debug.Listeners.Add(myWriter); Debug.Listeners.Add(myWriter);
#endif #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; bool startServer = true;

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<system.data> <system.data>
<DbProviderFactories> <DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient" /> <remove invariant="MySql.Data.MySqlClient"/>
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" /> <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d"/>
</DbProviderFactories> </DbProviderFactories>
</system.data> </system.data>
</configuration> <startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>

View file

@ -2,10 +2,17 @@
{ {
class Retainer class Retainer
{ {
public uint id; public readonly uint id;
public uint characterId; public readonly uint characterId;
public string name; public readonly string name;
public ushort slot; public readonly bool doRename;
public bool doRename;
public Retainer(uint characterId, uint retainerId, string name, bool doRename)
{
this.id = retainerId;
this.characterId = characterId;
this.name = name;
this.doRename = doRename;
}
} }
} }

View file

@ -2,12 +2,30 @@
{ {
class World class World
{ {
public ushort id; public readonly ushort id;
public string address; public readonly string address;
public ushort port; public readonly ushort port;
public ushort listPosition; public readonly ushort listPosition;
public ushort population; public readonly ushort population;
public string name; public readonly string name;
public bool isActive; 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;
}
} }
} }

View file

@ -1,10 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Cyotek.CircularBuffer" version="1.0.0.0" targetFramework="net45" /> <package id="Cyotek.CircularBuffer" version="1.0.0.0" targetFramework="net45" />
<package id="Dapper" version="1.42" targetFramework="net45" />
<package id="Microsoft.Net.Compilers" version="2.0.0-beta3" targetFramework="net45" developmentDependency="true" /> <package id="Microsoft.Net.Compilers" version="2.0.0-beta3" targetFramework="net45" developmentDependency="true" />
<package id="MySql.Data" version="6.9.8" targetFramework="net45" /> <package id="MySql.Data" version="6.9.8" targetFramework="net45" />
<package id="Newtonsoft.Json" version="8.0.3" targetFramework="net45" />
<package id="NLog" version="4.3.4" targetFramework="net45" /> <package id="NLog" version="4.3.4" targetFramework="net45" />
<package id="NLog.Config" version="4.3.4" targetFramework="net45" /> <package id="NLog.Config" version="4.3.4" targetFramework="net45" />
<package id="NLog.Schema" version="4.3.4" targetFramework="net45" /> <package id="NLog.Schema" version="4.3.4" targetFramework="net45" />

View file

@ -61,7 +61,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
accountCount = 0; accountCount = 0;
} }
@ -88,7 +89,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
} }

View file

@ -59,7 +59,7 @@ namespace FFXIVClassic_Lobby_Server.packets
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
return new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); return new SubPacket(OPCODE, 0xe0006868, data);
} }
} }
} }

View file

@ -87,7 +87,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
characterCount = 0; characterCount = 0;
} }
@ -133,7 +134,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
characterCount = 0; characterCount = 0;
} }
@ -145,7 +147,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
} }

View file

@ -38,7 +38,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
return subpacket; return subpacket;
} }
} }

View file

@ -64,7 +64,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
namesCount = 0; namesCount = 0;
} }
@ -91,7 +92,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
} }

View file

@ -51,7 +51,7 @@ namespace FFXIVClassic_Lobby_Server.packets
//Write Entries //Write Entries
binWriter.Write((uint)retainer.id); binWriter.Write((uint)retainer.id);
binWriter.Write((uint)retainer.characterId); binWriter.Write((uint)retainer.characterId);
binWriter.Write((ushort)retainer.slot); binWriter.Write((ushort)totalCount);
binWriter.Write((ushort)(retainer.doRename ? 0x04 : 0x00)); binWriter.Write((ushort)(retainer.doRename ? 0x04 : 0x00));
binWriter.Write((uint)0); binWriter.Write((uint)0);
binWriter.Write(Encoding.ASCII.GetBytes(retainer.name.PadRight(0x20, '\0'))); binWriter.Write(Encoding.ASCII.GetBytes(retainer.name.PadRight(0x20, '\0')));
@ -65,7 +65,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
retainerCount = 0; retainerCount = 0;
} }
@ -92,7 +93,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
} }

View file

@ -49,7 +49,8 @@ namespace FFXIVClassic_Lobby_Server.packets
data = memStream.GetBuffer(); data = memStream.GetBuffer();
} }
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
return subPackets; return subPackets;

View file

@ -63,7 +63,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
serverCount = 0; serverCount = 0;
} }
@ -90,7 +91,8 @@ namespace FFXIVClassic_Lobby_Server.packets
byte[] data = memStream.GetBuffer(); byte[] data = memStream.GetBuffer();
binWriter.Dispose(); binWriter.Dispose();
memStream.Dispose(); memStream.Dispose();
SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, 0xe0006868, data); SubPacket subpacket = new SubPacket(OPCODE, 0xe0006868, data);
subpacket.SetTargetId(0xe0006868);
subPackets.Add(subpacket); subPackets.Add(subpacket);
} }

View file

@ -1,11 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
</startup> </startup>
<system.data> <system.data>
<DbProviderFactories> <DbProviderFactories>
<remove invariant="MySql.Data.MySqlClient" /> <remove invariant="MySql.Data.MySqlClient" />
<add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" /> <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL" type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d" />
</DbProviderFactories> </DbProviderFactories>
</system.data></configuration> </system.data>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View file

@ -14,7 +14,7 @@ namespace FFXIVClassic_Map_Server
{ {
class CommandProcessor class CommandProcessor
{ {
private static Dictionary<uint, Item> gamedataItems = Server.GetGamedataItems(); private static Dictionary<uint, ItemData> gamedataItems = Server.GetGamedataItems();
const UInt32 ITEM_GIL = 1000001; const UInt32 ITEM_GIL = 1000001;
@ -27,12 +27,12 @@ namespace FFXIVClassic_Map_Server
private void SendMessage(Session session, String message) private void SendMessage(Session session, String message)
{ {
if (session != null) 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) internal bool DoCommand(string input, Session session)
{ {
if (!input.Any() || input.Equals("")) if (!input.Any() || input.Equals("") || input.Length == 1)
return false; return false;
input.Trim(); input.Trim();
@ -47,10 +47,10 @@ namespace FFXIVClassic_Map_Server
split = split.ToArray(); // Ignore case on commands split = split.ToArray(); // Ignore case on commands
var cmd = split[0]; if (split.Length > 0)
if (cmd.Any())
{ {
var cmd = split[0];
// if client isnt null, take player to be the player actor // if client isnt null, take player to be the player actor
Player player = null; Player player = null;
if (session != null) if (session != null)

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props')" /> <Import Project="..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props" Condition="Exists('..\packages\Microsoft.Net.Compilers.2.0.0-beta3\build\Microsoft.Net.Compilers.props') AND '$(OS)' == 'Windows_NT'" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props') AND '$(OS)' == 'Windows_NT'" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@ -10,9 +10,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FFXIVClassic_Map_Server</RootNamespace> <RootNamespace>FFXIVClassic_Map_Server</RootNamespace>
<AssemblyName>FFXIVClassic Map Server</AssemblyName> <AssemblyName>FFXIVClassic Map Server</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<NuGetPackageImportStamp>1d22ec4a</NuGetPackageImportStamp> <NuGetPackageImportStamp>1d22ec4a</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -24,6 +25,7 @@
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -38,15 +40,33 @@
<PropertyGroup> <PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent> <RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Cyotek.Collections.Generic.CircularBuffer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58daa28b0b2de221, processorArchitecture=MSIL"> <Reference Include="Cyotek.Collections.Generic.CircularBuffer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=58daa28b0b2de221, processorArchitecture=MSIL">
<HintPath>..\packages\Cyotek.CircularBuffer.1.0.0.0\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll</HintPath> <HintPath>..\packages\Cyotek.CircularBuffer.1.0.0.0\lib\net20\Cyotek.Collections.Generic.CircularBuffer.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Dapper, Version=1.40.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.1.42\lib\net45\Dapper.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FFXIVClassic.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="FFXIVClassic.Common, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\FFXIVClassic Common Class Lib\bin\Debug\FFXIVClassic.Common.dll</HintPath> <HintPath>..\FFXIVClassic Common Class Lib\bin\Debug\FFXIVClassic.Common.dll</HintPath>
@ -58,16 +78,21 @@
<HintPath>..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll</HintPath> <HintPath>..\packages\MySql.Data.6.9.8\lib\net45\MySql.Data.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> <Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll</HintPath> <SpecificVersion>False</SpecificVersion>
<Private>True</Private> <HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="SharpNav, Version=1.0.0.1, Culture=neutral, PublicKeyToken=b467138d8cacd85b, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>navmesh\SharpNav.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@ -79,31 +104,69 @@
<Compile Include="actors\area\PrivateAreaContent.cs" /> <Compile Include="actors\area\PrivateAreaContent.cs" />
<Compile Include="actors\area\SpawnLocation.cs" /> <Compile Include="actors\area\SpawnLocation.cs" />
<Compile Include="actors\area\Zone.cs" /> <Compile Include="actors\area\Zone.cs" />
<Compile Include="actors\chara\ai\BattleTrait.cs" />
<Compile Include="actors\chara\ai\controllers\AllyController.cs" />
<Compile Include="actors\chara\ai\helpers\ActionQueue.cs" />
<Compile Include="actors\chara\ai\AIContainer.cs" />
<Compile Include="actors\chara\ai\controllers\Controller.cs" />
<Compile Include="actors\chara\ai\controllers\BattleNpcController.cs" />
<Compile Include="actors\chara\ai\controllers\PetController.cs" />
<Compile Include="actors\chara\ai\controllers\PlayerController.cs" />
<Compile Include="actors\chara\ai\HateContainer.cs" />
<Compile Include="actors\chara\ai\helpers\PathFind.cs" />
<Compile Include="actors\chara\ai\BattleCommand.cs" />
<Compile Include="actors\chara\ai\state\AbilityState.cs" />
<Compile Include="actors\chara\ai\state\AttackState.cs" />
<Compile Include="actors\chara\ai\state\DeathState.cs" />
<Compile Include="actors\chara\ai\state\DespawnState.cs" />
<Compile Include="actors\chara\ai\state\InactiveState.cs" />
<Compile Include="actors\chara\ai\state\ItemState.cs" />
<Compile Include="actors\chara\ai\state\MagicState.cs" />
<Compile Include="actors\chara\ai\state\State.cs" />
<Compile Include="actors\chara\ai\state\WeaponSkillState.cs" />
<Compile Include="actors\chara\ai\StatusEffect.cs" />
<Compile Include="actors\chara\ai\StatusEffectContainer.cs" />
<Compile Include="actors\chara\ai\helpers\TargetFind.cs" />
<Compile Include="actors\chara\ai\utils\AttackUtils.cs" />
<Compile Include="actors\chara\ai\utils\BattleUtils.cs" />
<Compile Include="actors\chara\Modifier.cs" />
<Compile Include="actors\chara\ModifierList.cs" />
<Compile Include="actors\chara\npc\ActorClass.cs" /> <Compile Include="actors\chara\npc\ActorClass.cs" />
<Compile Include="actors\chara\npc\Ally.cs" />
<Compile Include="actors\chara\npc\BattleNpc.cs" />
<Compile Include="actors\chara\npc\MobModifier.cs" />
<Compile Include="actors\chara\npc\NpcWork.cs" /> <Compile Include="actors\chara\npc\NpcWork.cs" />
<Compile Include="actors\chara\AetheryteWork.cs" /> <Compile Include="actors\chara\AetheryteWork.cs" />
<Compile Include="actors\chara\npc\Retainer.cs" />
<Compile Include="actors\chara\npc\Pet.cs" />
<Compile Include="actors\chara\player\Equipment.cs" /> <Compile Include="actors\chara\player\Equipment.cs" />
<Compile Include="actors\chara\player\Inventory.cs" /> <Compile Include="actors\chara\player\Inventory.cs" />
<Compile Include="actors\chara\SubState.cs" />
<Compile Include="actors\chara\Work.cs" /> <Compile Include="actors\chara\Work.cs" />
<Compile Include="actors\debug\Debug.cs" /> <Compile Include="actors\debug\Debug.cs" />
<Compile Include="actors\director\Director.cs" /> <Compile Include="actors\director\Director.cs" />
<Compile Include="actors\director\GuildleveDirector.cs" />
<Compile Include="actors\director\work\GuildleveWork.cs" />
<Compile Include="actors\EventList.cs" /> <Compile Include="actors\EventList.cs" />
<Compile Include="actors\group\GLContentGroup.cs" />
<Compile Include="actors\group\ContentGroup.cs" /> <Compile Include="actors\group\ContentGroup.cs" />
<Compile Include="actors\group\RetainerMeetingRelationGroup.cs" />
<Compile Include="actors\group\Work\ContentGroupWork.cs" /> <Compile Include="actors\group\Work\ContentGroupWork.cs" />
<Compile Include="actors\group\Work\GlobalTemp.cs" /> <Compile Include="actors\group\Work\GlobalTemp.cs" />
<Compile Include="actors\group\Group.cs" /> <Compile Include="actors\group\Group.cs" />
<Compile Include="actors\group\MonsterParty.cs" /> <Compile Include="actors\group\MonsterParty.cs" />
<Compile Include="actors\group\Party.cs" /> <Compile Include="actors\group\Party.cs" />
<Compile Include="actors\group\Relation.cs" /> <Compile Include="actors\group\Relation.cs" />
<Compile Include="actors\group\Work\GroupGlobalSave.cs" /> <Compile Include="actors\group\work\GroupGlobalSave.cs" />
<Compile Include="actors\group\Work\GroupGlobalTemp.cs" /> <Compile Include="actors\group\work\GroupGlobalTemp.cs" />
<Compile Include="actors\group\Work\GroupMemberSave.cs" /> <Compile Include="actors\group\work\GroupMemberSave.cs" />
<Compile Include="actors\group\Work\PartyWork.cs" /> <Compile Include="actors\group\work\PartyWork.cs" />
<Compile Include="actors\group\Work\RelationWork.cs" /> <Compile Include="actors\group\work\RelationWork.cs" />
<Compile Include="actors\judge\Judge.cs" /> <Compile Include="actors\judge\Judge.cs" />
<Compile Include="actors\quest\Quest.cs" /> <Compile Include="actors\quest\Quest.cs" />
<Compile Include="actors\StaticActors.cs" /> <Compile Include="actors\StaticActors.cs" />
<Compile Include="actors\world\WorldMaster.cs" /> <Compile Include="actors\world\WorldMaster.cs" />
<Compile Include="dataobjects\GuildleveData.cs" />
<Compile Include="dataobjects\ZoneConnection.cs" /> <Compile Include="dataobjects\ZoneConnection.cs" />
<Compile Include="CommandProcessor.cs" /> <Compile Include="CommandProcessor.cs" />
<Compile Include="ConfigConstants.cs" /> <Compile Include="ConfigConstants.cs" />
@ -123,7 +186,7 @@
<Compile Include="actors\chara\player\PlayerWork.cs" /> <Compile Include="actors\chara\player\PlayerWork.cs" />
<Compile Include="dataobjects\InventoryItem.cs" /> <Compile Include="dataobjects\InventoryItem.cs" />
<Compile Include="dataobjects\Session.cs" /> <Compile Include="dataobjects\Session.cs" />
<Compile Include="dataobjects\Item.cs" /> <Compile Include="dataobjects\ItemData.cs" />
<Compile Include="dataobjects\RecruitmentDetails.cs" /> <Compile Include="dataobjects\RecruitmentDetails.cs" />
<Compile Include="dataobjects\SeamlessBoundry.cs" /> <Compile Include="dataobjects\SeamlessBoundry.cs" />
<Compile Include="dataobjects\SearchEntry.cs" /> <Compile Include="dataobjects\SearchEntry.cs" />
@ -137,6 +200,7 @@
<Compile Include="packets\receive\events\EventStartPacket.cs" /> <Compile Include="packets\receive\events\EventStartPacket.cs" />
<Compile Include="packets\receive\GroupCreatedPacket.cs" /> <Compile Include="packets\receive\GroupCreatedPacket.cs" />
<Compile Include="packets\receive\HandshakePacket.cs" /> <Compile Include="packets\receive\HandshakePacket.cs" />
<Compile Include="packets\receive\CountdownRequestPacket.cs" />
<Compile Include="packets\receive\LangaugeCodePacket.cs" /> <Compile Include="packets\receive\LangaugeCodePacket.cs" />
<Compile Include="packets\receive\ParameterDataRequestPacket.cs" /> <Compile Include="packets\receive\ParameterDataRequestPacket.cs" />
<Compile Include="packets\receive\recruitment\RecruitmentDetailsRequestPacket.cs" /> <Compile Include="packets\receive\recruitment\RecruitmentDetailsRequestPacket.cs" />
@ -155,11 +219,11 @@
<Compile Include="packets\send\actor\ActorDoEmotePacket.cs" /> <Compile Include="packets\send\actor\ActorDoEmotePacket.cs" />
<Compile Include="packets\send\actor\ActorInstantiatePacket.cs" /> <Compile Include="packets\send\actor\ActorInstantiatePacket.cs" />
<Compile Include="packets\send\actor\ActorSpecialGraphicPacket.cs" /> <Compile Include="packets\send\actor\ActorSpecialGraphicPacket.cs" />
<Compile Include="packets\send\actor\BattleAction1Packet.cs" /> <Compile Include="packets\send\actor\battle\CommandResult.cs" />
<Compile Include="packets\send\actor\battle\BattleAction.cs" /> <Compile Include="packets\send\actor\battle\CommandResultContainer.cs" />
<Compile Include="packets\send\actor\battle\BattleActionX00Packet.cs" /> <Compile Include="packets\send\actor\battle\CommandResultX00Packet.cs" />
<Compile Include="packets\send\actor\battle\BattleActionX18Packet.cs" /> <Compile Include="packets\send\actor\battle\CommandResultX18Packet.cs" />
<Compile Include="packets\send\actor\battle\BattleActionX10Packet.cs" /> <Compile Include="packets\send\actor\battle\CommandResultX10Packet.cs" />
<Compile Include="packets\send\actor\DeleteAllActorsPacket.cs" /> <Compile Include="packets\send\actor\DeleteAllActorsPacket.cs" />
<Compile Include="packets\send\actor\events\SetEventStatus.cs" /> <Compile Include="packets\send\actor\events\SetEventStatus.cs" />
<Compile Include="packets\send\actor\events\SetNoticeEventCondition.cs" /> <Compile Include="packets\send\actor\events\SetNoticeEventCondition.cs" />
@ -183,9 +247,10 @@
<Compile Include="packets\send\actor\inventory\InventoryListX32Packet.cs" /> <Compile Include="packets\send\actor\inventory\InventoryListX32Packet.cs" />
<Compile Include="packets\send\actor\PlayAnimationOnActorPacket.cs" /> <Compile Include="packets\send\actor\PlayAnimationOnActorPacket.cs" />
<Compile Include="packets\send\actor\PlayBGAnimation.cs" /> <Compile Include="packets\send\actor\PlayBGAnimation.cs" />
<Compile Include="packets\send\actor\StartCountdownPacket.cs" />
<Compile Include="packets\send\actor\_0x132Packet.cs" /> <Compile Include="packets\send\actor\_0x132Packet.cs" />
<Compile Include="packets\send\actor\SetActorIsZoningPacket.cs" /> <Compile Include="packets\send\actor\SetActorIsZoningPacket.cs" />
<Compile Include="packets\send\actor\battle\BattleActionX01Packet.cs" /> <Compile Include="packets\send\actor\battle\CommandResultX01Packet.cs" />
<Compile Include="packets\send\actor\inventory\EquipmentListX01Packet.cs" /> <Compile Include="packets\send\actor\inventory\EquipmentListX01Packet.cs" />
<Compile Include="packets\send\actor\inventory\InventoryBeginChangePacket.cs" /> <Compile Include="packets\send\actor\inventory\InventoryBeginChangePacket.cs" />
<Compile Include="packets\send\actor\inventory\InventoryEndChangePacket.cs" /> <Compile Include="packets\send\actor\inventory\InventoryEndChangePacket.cs" />
@ -196,10 +261,9 @@
<Compile Include="packets\send\actor\inventory\EquipmentListX08Packet.cs" /> <Compile Include="packets\send\actor\inventory\EquipmentListX08Packet.cs" />
<Compile Include="packets\send\actor\RemoveActorPacket.cs" /> <Compile Include="packets\send\actor\RemoveActorPacket.cs" />
<Compile Include="packets\send\actor\SetActorIconPacket.cs" /> <Compile Include="packets\send\actor\SetActorIconPacket.cs" />
<Compile Include="packets\send\actor\SetActorSubStatPacket.cs" /> <Compile Include="packets\send\actor\SetActorSubStatePacket.cs" />
<Compile Include="packets\send\actor\SetActorStatusPacket.cs" /> <Compile Include="packets\send\actor\SetActorStatusPacket.cs" />
<Compile Include="packets\send\actor\_0xD9Packet.cs" /> <Compile Include="packets\send\actor\SetActorBGPropertiesPacket.cs" />
<Compile Include="packets\send\actor\_0xD8Packet.cs" />
<Compile Include="packets\send\actor\_0xFPacket.cs" /> <Compile Include="packets\send\actor\_0xFPacket.cs" />
<Compile Include="packets\send\groups\CreateNamedGroup.cs" /> <Compile Include="packets\send\groups\CreateNamedGroup.cs" />
<Compile Include="packets\send\groups\CreateNamedGroupMultiple.cs" /> <Compile Include="packets\send\groups\CreateNamedGroupMultiple.cs" />
@ -221,25 +285,24 @@
<Compile Include="packets\send\groups\ContentMembersX64Packet.cs" /> <Compile Include="packets\send\groups\ContentMembersX64Packet.cs" />
<Compile Include="packets\send\groups\GroupMembersX64Packet.cs" /> <Compile Include="packets\send\groups\GroupMembersX64Packet.cs" />
<Compile Include="packets\send\groups\SynchGroupWorkValuesPacket.cs" /> <Compile Include="packets\send\groups\SynchGroupWorkValuesPacket.cs" />
<Compile Include="packets\send\player\InfoRequestResponsePacket.cs" /> <Compile Include="packets\send\player\GenericDataPacket.cs" />
<Compile Include="packets\send\player\SendAchievementRatePacket.cs" /> <Compile Include="packets\send\player\SendAchievementRatePacket.cs" />
<Compile Include="packets\send\player\SetCurrentJobPacket.cs" /> <Compile Include="packets\send\player\SetCurrentJobPacket.cs" />
<Compile Include="packets\send\player\SetCurrentMountGoobbuePacket.cs" /> <Compile Include="packets\send\player\SetCurrentMountGoobbuePacket.cs" />
<Compile Include="packets\send\player\SetCurrentMountChocoboPacket.cs" /> <Compile Include="packets\send\player\SetCurrentMountChocoboPacket.cs" />
<Compile Include="packets\send\player\SetGrandCompanyPacket.cs" /> <Compile Include="packets\send\player\SetGrandCompanyPacket.cs" />
<Compile Include="packets\send\actor\SetActorNamePacket.cs" /> <Compile Include="packets\send\Actor\SetActorNamePacket.cs" />
<Compile Include="packets\send\actor\SetActorPropetyPacket.cs" /> <Compile Include="packets\send\Actor\SetActorPropetyPacket.cs" />
<Compile Include="packets\send\actor\SetActorSpeedPacket.cs" /> <Compile Include="packets\send\Actor\SetActorSpeedPacket.cs" />
<Compile Include="packets\send\actor\SetActorStatePacket.cs" /> <Compile Include="packets\send\Actor\SetActorStatePacket.cs" />
<Compile Include="packets\send\actor\SetActorTargetAnimatedPacket.cs" /> <Compile Include="packets\send\Actor\SetActorTargetAnimatedPacket.cs" />
<Compile Include="packets\send\actor\SetActorTargetPacket.cs" /> <Compile Include="packets\send\Actor\SetActorTargetPacket.cs" />
<Compile Include="packets\send\actor\SetActorStatusAllPacket.cs" /> <Compile Include="packets\send\Actor\SetActorStatusAllPacket.cs" />
<Compile Include="packets\send\login\0x2Packet.cs" /> <Compile Include="packets\send\login\0x2Packet.cs" />
<Compile Include="packets\send\actor\AddActorPacket.cs" /> <Compile Include="packets\send\actor\AddActorPacket.cs" />
<Compile Include="packets\send\actor\MoveActorToPositionPacket.cs" /> <Compile Include="packets\send\actor\MoveActorToPositionPacket.cs" />
<Compile Include="packets\send\actor\SetActorAppearancePacket.cs" /> <Compile Include="packets\send\actor\SetActorAppearancePacket.cs" />
<Compile Include="packets\send\actor\SetActorPositionPacket.cs" /> <Compile Include="packets\send\actor\SetActorPositionPacket.cs" />
<Compile Include="packets\send\login\0x7ResponsePacket.cs" />
<Compile Include="packets\send\LogoutPacket.cs" /> <Compile Include="packets\send\LogoutPacket.cs" />
<Compile Include="packets\send\player\SetCompletedAchievementsPacket.cs" /> <Compile Include="packets\send\player\SetCompletedAchievementsPacket.cs" />
<Compile Include="packets\send\player\AchievementEarnedPacket.cs" /> <Compile Include="packets\send\player\AchievementEarnedPacket.cs" />
@ -249,15 +312,30 @@
<Compile Include="packets\send\player\SetHasGoobbuePacket.cs" /> <Compile Include="packets\send\player\SetHasGoobbuePacket.cs" />
<Compile Include="packets\send\player\SetHasChocoboPacket.cs" /> <Compile Include="packets\send\player\SetHasChocoboPacket.cs" />
<Compile Include="packets\send\player\SetLatestAchievementsPacket.cs" /> <Compile Include="packets\send\player\SetLatestAchievementsPacket.cs" />
<Compile Include="packets\send\player\SetPlayerItemStoragePacket.cs" />
<Compile Include="packets\send\player\SetPlayerDreamPacket.cs" /> <Compile Include="packets\send\player\SetPlayerDreamPacket.cs" />
<Compile Include="packets\send\player\SetPlayerTitlePacket.cs" /> <Compile Include="packets\send\player\SetPlayerTitlePacket.cs" />
<Compile Include="packets\send\player\_0x196Packet.cs" /> <Compile Include="packets\send\player\SetSpecialEventWorkPacket.cs" />
<Compile Include="packets\send\PongPacket.cs" /> <Compile Include="packets\send\PongPacket.cs" />
<Compile Include="packets\send\QuitPacket.cs" /> <Compile Include="packets\send\QuitPacket.cs" />
<Compile Include="packets\send\recruitment\CurrentRecruitmentDetailsPacket.cs" /> <Compile Include="packets\send\recruitment\CurrentRecruitmentDetailsPacket.cs" />
<Compile Include="packets\send\recruitment\EndRecruitmentPacket.cs" /> <Compile Include="packets\send\recruitment\EndRecruitmentPacket.cs" />
<Compile Include="packets\send\recruitment\RecruiterStatePacket.cs" /> <Compile Include="packets\send\recruitment\RecruiterStatePacket.cs" />
<Compile Include="packets\send\recruitment\StartRecruitingResponse.cs" /> <Compile Include="packets\send\recruitment\StartRecruitingResponse.cs" />
<Compile Include="packets\send\search\ItemSearchClosePacket.cs" />
<Compile Include="packets\send\search\PlayerSearchResult.cs" />
<Compile Include="packets\send\search\ItemSearchResult.cs" />
<Compile Include="packets\send\search\PlayerSearchCommentResultPacket.cs" />
<Compile Include="packets\send\search\PlayerSearchInfoResultPacket.cs" />
<Compile Include="packets\send\search\ItemSearchResultsEndPacket.cs" />
<Compile Include="packets\send\search\ItemSearchResultsBodyPacket.cs" />
<Compile Include="packets\send\search\ItemSearchResultsBeginPacket.cs" />
<Compile Include="packets\send\search\RetainerResultBodyPacket.cs" />
<Compile Include="packets\send\search\RetainerResultEndPacket.cs" />
<Compile Include="packets\send\search\RetainerResultUpdatePacket.cs" />
<Compile Include="packets\send\search\RetainerSearchHistoryPacket.cs" />
<Compile Include="packets\send\search\RetainerSearchHistoryResult.cs" />
<Compile Include="packets\send\search\RetainerSearchResult.cs" />
<Compile Include="packets\send\SendMessagePacket.cs" /> <Compile Include="packets\send\SendMessagePacket.cs" />
<Compile Include="packets\send\SetMapPacket.cs" /> <Compile Include="packets\send\SetMapPacket.cs" />
<Compile Include="packets\send\SetMusicPacket.cs" /> <Compile Include="packets\send\SetMusicPacket.cs" />
@ -277,7 +355,7 @@
<Compile Include="packets\send\supportdesk\GMTicketPacket.cs" /> <Compile Include="packets\send\supportdesk\GMTicketPacket.cs" />
<Compile Include="packets\send\supportdesk\GMTicketSentResponsePacket.cs" /> <Compile Include="packets\send\supportdesk\GMTicketSentResponsePacket.cs" />
<Compile Include="packets\send\_0x02Packet.cs" /> <Compile Include="packets\send\_0x02Packet.cs" />
<Compile Include="packets\send\_0x10Packet.cs" /> <Compile Include="packets\send\SetDalamudPacket.cs" />
<Compile Include="packets\send\_0xE2Packet.cs" /> <Compile Include="packets\send\_0xE2Packet.cs" />
<Compile Include="packets\receive\PingPacket.cs" /> <Compile Include="packets\receive\PingPacket.cs" />
<Compile Include="packets\receive\UpdatePlayerPositionPacket.cs" /> <Compile Include="packets\receive\UpdatePlayerPositionPacket.cs" />
@ -309,6 +387,7 @@
<Compile Include="Server.cs" /> <Compile Include="Server.cs" />
<Compile Include="utils\ActorPropertyPacketUtil.cs" /> <Compile Include="utils\ActorPropertyPacketUtil.cs" />
<Compile Include="utils\CharacterUtils.cs" /> <Compile Include="utils\CharacterUtils.cs" />
<Compile Include="utils\NavmeshUtils.cs" />
<Compile Include="utils\SQLGeneration.cs" /> <Compile Include="utils\SQLGeneration.cs" />
<Compile Include="actors\area\Area.cs" /> <Compile Include="actors\area\Area.cs" />
<Compile Include="WorldManager.cs" /> <Compile Include="WorldManager.cs" />
@ -331,10 +410,13 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="navmesh\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup> <PropertyGroup>
<PostBuildEvent>xcopy "$(SolutionDir)data\map_config.ini" "$(SolutionDir)$(ProjectName)\$(OutDir)" /d <PostBuildEvent>
xcopy "$(SolutionDir)data\scripts" "$(SolutionDir)$(ProjectName)\$(OutDir)scripts\" /e /d /y /s</PostBuildEvent> </PostBuildEvent>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<PreBuildEvent> <PreBuildEvent>

View file

@ -35,7 +35,7 @@ namespace FFXIVClassic_Map_Server
public void ProcessPacket(ZoneConnection client, SubPacket subpacket) 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) if (session == null && subpacket.gameMessage.opcode != 0x1000)
return; return;
@ -56,9 +56,12 @@ namespace FFXIVClassic_Map_Server
//World Server - Session Begin //World Server - Session Begin
case 0x1000: case 0x1000:
subpacket.DebugPrintSubPacket(); 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); Server.GetWorldManager().DoZoneIn(session.GetActor(), false, session.GetActor().destinationSpawnType);
Program.Log.Info("{0} has been added to the session list.", session.GetActor().customDisplayName); 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); Server.GetServer().RemoveSession(session.id);
Program.Log.Info("{0} has been removed from the session list.", session.GetActor().customDisplayName); 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(); client.FlushQueuedSendPackets();
break; break;
//World Server - Party Synch //World Server - Party Synch
@ -89,14 +92,14 @@ namespace FFXIVClassic_Map_Server
case 0x0001: case 0x0001:
//subpacket.DebugPrintSubPacket(); //subpacket.DebugPrintSubPacket();
PingPacket pingPacket = new PingPacket(subpacket.data); 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(); session.Ping();
break; break;
//Unknown //Unknown
case 0x0002: case 0x0002:
subpacket.DebugPrintSubPacket(); subpacket.DebugPrintSubPacket();
client.QueuePacket(_0x2Packet.BuildPacket(session.id), true, false); session.QueuePacket(_0x2Packet.BuildPacket(session.id));
client.FlushQueuedSendPackets(); client.FlushQueuedSendPackets();
break; break;
@ -112,14 +115,12 @@ namespace FFXIVClassic_Map_Server
} }
if (chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SAY || chatMessage.logType == SendMessagePacket.MESSAGE_TYPE_SHOUT) 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; break;
//Langauge Code (Client safe to send packets to now) //Langauge Code (Client safe to send packets to now)
case 0x0006: case 0x0006:
LangaugeCodePacket langCode = new LangaugeCodePacket(subpacket.data); LangaugeCodePacket langCode = new LangaugeCodePacket(subpacket.data);
session = mServer.AddSession(subpacket.header.targetId);
LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onBeginLogin", true); LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onBeginLogin", true);
Server.GetWorldManager().DoZoneIn(session.GetActor(), true, 0x1); Server.GetWorldManager().DoZoneIn(session.GetActor(), true, 0x1);
LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onLogin", true); LuaEngine.GetInstance().CallLuaFunction(session.GetActor(), session.GetActor(), "onLogin", true);
@ -147,7 +148,8 @@ namespace FFXIVClassic_Map_Server
SetTargetPacket setTarget = new SetTargetPacket(subpacket.data); SetTargetPacket setTarget = new SetTargetPacket(subpacket.data);
session.GetActor().currentTarget = setTarget.actorID; 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; break;
//Lock Target //Lock Target
case 0x00CC: case 0x00CC:
@ -181,8 +183,12 @@ namespace FFXIVClassic_Map_Server
if (ownerActor == null) 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? //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) if (ownerActor == null)
{ {
//Is it a Director? //Is it a Director?
@ -205,6 +211,11 @@ namespace FFXIVClassic_Map_Server
case 0x00CE: case 0x00CE:
subpacket.DebugPrintSubPacket(); subpacket.DebugPrintSubPacket();
break; break;
//Countdown requested
case 0x00CF:
CountdownRequestPacket countdownPacket = new CountdownRequestPacket(subpacket.data);
session.GetActor().BroadcastCountdown(countdownPacket.countdownLength, countdownPacket.syncTime);
break;
//Event Result //Event Result
case 0x012E: case 0x012E:
subpacket.DebugPrintSubPacket(); subpacket.DebugPrintSubPacket();
@ -244,15 +255,15 @@ namespace FFXIVClassic_Map_Server
//Start Recruiting //Start Recruiting
case 0x01C3: case 0x01C3:
StartRecruitingRequestPacket recruitRequestPacket = new StartRecruitingRequestPacket(subpacket.data); 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; break;
//End Recruiting //End Recruiting
case 0x01C4: case 0x01C4:
client.QueuePacket(BasePacket.CreatePacket(EndRecruitmentPacket.BuildPacket(session.id), true, false)); session.QueuePacket(EndRecruitmentPacket.BuildPacket(session.id));
break; break;
//Party Window Opened, Request State //Party Window Opened, Request State
case 0x01C5: 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; break;
//Search Recruiting //Search Recruiting
case 0x01C7: case 0x01C7:
@ -268,7 +279,7 @@ namespace FFXIVClassic_Map_Server
details.subTaskId = 1; details.subTaskId = 1;
details.comment = "This is a test details packet sent by the server. No implementation has been Created yet..."; details.comment = "This is a test details packet sent by the server. No implementation has been Created yet...";
details.num[0] = 1; details.num[0] = 1;
client.QueuePacket(BasePacket.CreatePacket(CurrentRecruitmentDetailsPacket.BuildPacket(session.id, details), true, false)); session.QueuePacket(CurrentRecruitmentDetailsPacket.BuildPacket(session.id, details));
break; break;
//Accepted Recruiting //Accepted Recruiting
case 0x01C6: case 0x01C6:
@ -277,64 +288,64 @@ namespace FFXIVClassic_Map_Server
/* SOCIAL STUFF */ /* SOCIAL STUFF */
case 0x01C9: case 0x01C9:
AddRemoveSocialPacket addBlackList = new AddRemoveSocialPacket(subpacket.data); 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; break;
case 0x01CA: case 0x01CA:
AddRemoveSocialPacket RemoveBlackList = new AddRemoveSocialPacket(subpacket.data); 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; break;
case 0x01CB: case 0x01CB:
int offset1 = 0; 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; break;
case 0x01CC: case 0x01CC:
AddRemoveSocialPacket addFriendList = new AddRemoveSocialPacket(subpacket.data); 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; break;
case 0x01CD: case 0x01CD:
AddRemoveSocialPacket RemoveFriendList = new AddRemoveSocialPacket(subpacket.data); 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; break;
case 0x01CE: case 0x01CE:
int offset2 = 0; int offset2 = 0;
client.QueuePacket(BasePacket.CreatePacket(SendFriendlistPacket.BuildPacket(session.id, new Tuple<long, string>[] { new Tuple<long, string>(01, "Test2") }, ref offset2), true, false)); session.QueuePacket(SendFriendlistPacket.BuildPacket(session.id, new Tuple<long, string>[] { new Tuple<long, string>(01, "Test2") }, ref offset2));
break; break;
case 0x01CF: case 0x01CF:
client.QueuePacket(BasePacket.CreatePacket(FriendStatusPacket.BuildPacket(session.id, null), true, false)); session.QueuePacket(FriendStatusPacket.BuildPacket(session.id, null));
break; break;
/* SUPPORT DESK STUFF */ /* SUPPORT DESK STUFF */
//Request for FAQ/Info List //Request for FAQ/Info List
case 0x01D0: case 0x01D0:
FaqListRequestPacket faqRequest = new FaqListRequestPacket(subpacket.data); 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; break;
//Request for body of a faq/info selection //Request for body of a faq/info selection
case 0x01D1: case 0x01D1:
FaqBodyRequestPacket faqBodyRequest = new FaqBodyRequestPacket(subpacket.data); 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; break;
//Request issue list //Request issue list
case 0x01D2: case 0x01D2:
GMTicketIssuesRequestPacket issuesRequest = new GMTicketIssuesRequestPacket(subpacket.data); 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; break;
//Request if GM ticket exists //Request if GM ticket exists
case 0x01D3: case 0x01D3:
client.QueuePacket(BasePacket.CreatePacket(StartGMTicketPacket.BuildPacket(session.id, false), true, false)); session.QueuePacket(StartGMTicketPacket.BuildPacket(session.id, false));
break; break;
//Request for GM response message //Request for GM response message
case 0x01D4: 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; break;
//GM Ticket Sent //GM Ticket Sent
case 0x01D5: case 0x01D5:
GMSupportTicketPacket gmTicket = new GMSupportTicketPacket(subpacket.data); GMSupportTicketPacket gmTicket = new GMSupportTicketPacket(subpacket.data);
Program.Log.Info("Got GM Ticket: \n" + gmTicket.ticketTitle + "\n" + gmTicket.ticketBody); 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; break;
//Request to end ticket //Request to end ticket
case 0x01D6: case 0x01D6:
client.QueuePacket(BasePacket.CreatePacket(EndGMTicketPacket.BuildPacket(session.id), true, false)); session.QueuePacket(EndGMTicketPacket.BuildPacket(session.id));
break; break;
default: default:
Program.Log.Debug("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode); Program.Log.Debug("Unknown command 0x{0:X} received.", subpacket.gameMessage.opcode);
@ -346,3 +357,4 @@ namespace FFXIVClassic_Map_Server
} }
} }

View file

@ -16,10 +16,13 @@ namespace FFXIVClassic_Map_Server
class Program class Program
{ {
public static Logger Log; 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) static void Main(string[] args)
{ {
// set up logging // set up logging
Log = LogManager.GetCurrentClassLogger(); Log = LogManager.GetCurrentClassLogger();
#if DEBUG #if DEBUG
@ -28,7 +31,10 @@ namespace FFXIVClassic_Map_Server
#endif #endif
bool startServer = true; 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 //Load Config
ConfigConstants.Load(); ConfigConstants.Load();
@ -55,9 +61,10 @@ namespace FFXIVClassic_Map_Server
//Start server if A-OK //Start server if A-OK
if (startServer) if (startServer)
{ {
Server server = new Server(); Random = new Random();
Server = new Server();
server.StartServer(); Tick = DateTime.Now;
Server.StartServer();
while (startServer) while (startServer)
{ {

View file

@ -17,7 +17,7 @@ namespace FFXIVClassic_Map_Server.Properties {
/// </summary> /// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder // This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio. // 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. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
@ -105,7 +105,7 @@ namespace FFXIVClassic_Map_Server.Properties {
/// ///
///Available commands: ///Available commands:
///Standard: mypos, music, warp ///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. ///Test: test weather.
/// </summary> /// </summary>
public static string CPhelp { public static string CPhelp {
@ -176,38 +176,38 @@ namespace FFXIVClassic_Map_Server.Properties {
/// <summary> /// <summary>
/// Looks up a localized string similar to Removes the specified currency from the current player&apos;s inventory /// Looks up a localized string similar to Removes the specified currency from the current player&apos;s inventory
/// ///
///*Syntax: Removecurrency &lt;quantity&gt; ///*Syntax: removecurrency &lt;quantity&gt;
/// Removecurrency &lt;type&gt; &lt;quantity&gt; /// removecurrency &lt;type&gt; &lt;quantity&gt;
///&lt;type&gt; is the specific type of currency desired, defaults to gil if no type specified. ///&lt;type&gt; is the specific type of currency desired, defaults to gil if no type specified.
/// </summary> /// </summary>
public static string CPRemovecurrency { public static string CPremovecurrency {
get { get {
return ResourceManager.GetString("CPRemovecurrency", resourceCulture); return ResourceManager.GetString("CPremovecurrency", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Removes the specified items to the current player&apos;s inventory /// Looks up a localized string similar to Removes the specified items to the current player&apos;s inventory
/// ///
///*Syntax: Removeitem &lt;itemid&gt; ///*Syntax: removeitem &lt;itemid&gt;
/// Removeitem &lt;itemid&gt; &lt;quantity&gt; /// removeitem &lt;itemid&gt; &lt;quantity&gt;
///&lt;item id&gt; is the item&apos;s specific id as defined in the server database. ///&lt;item id&gt; is the item&apos;s specific id as defined in the server database.
/// </summary> /// </summary>
public static string CPRemoveitem { public static string CPremoveitem {
get { get {
return ResourceManager.GetString("CPRemoveitem", resourceCulture); return ResourceManager.GetString("CPremoveitem", resourceCulture);
} }
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Removes the specified key item to the current player&apos;s inventory /// Looks up a localized string similar to Removes the specified key item to the current player&apos;s inventory
/// ///
///*Syntax: Removekeyitem &lt;itemid&gt; ///*Syntax: removekeyitem &lt;itemid&gt;
///&lt;item id&gt; is the key item&apos;s specific id as defined in the server database. ///&lt;item id&gt; is the key item&apos;s specific id as defined in the server database.
/// </summary> /// </summary>
public static string CPRemovekeyitem { public static string CPremovekeyitem {
get { get {
return ResourceManager.GetString("CPRemovekeyitem", resourceCulture); return ResourceManager.GetString("CPremovekeyitem", resourceCulture);
} }
} }

View file

@ -27,7 +27,8 @@ namespace FFXIVClassic_Map_Server
private static CommandProcessor mCommandProcessor = new CommandProcessor(); private static CommandProcessor mCommandProcessor = new CommandProcessor();
private static ZoneConnection mWorldConnection = new ZoneConnection(); private static ZoneConnection mWorldConnection = new ZoneConnection();
private static WorldManager mWorldManager; private static WorldManager mWorldManager;
private static Dictionary<uint, Item> mGamedataItems; private static Dictionary<uint, ItemData> mGamedataItems;
private static Dictionary<uint, GuildleveData> mGamedataGuildleves;
private static StaticActors mStaticActors; private static StaticActors mStaticActors;
private PacketProcessor mProcessor; private PacketProcessor mProcessor;
@ -43,6 +44,8 @@ namespace FFXIVClassic_Map_Server
mGamedataItems = Database.GetItemGamedata(); mGamedataItems = Database.GetItemGamedata();
Program.Log.Info("Loaded {0} items.", mGamedataItems.Count); Program.Log.Info("Loaded {0} items.", mGamedataItems.Count);
mGamedataGuildleves = Database.GetGuildleveGamedata();
Program.Log.Info("Loaded {0} guildleves.", mGamedataGuildleves.Count);
mWorldManager = new WorldManager(this); mWorldManager = new WorldManager(this);
mWorldManager.LoadZoneList(); mWorldManager.LoadZoneList();
@ -50,6 +53,10 @@ namespace FFXIVClassic_Map_Server
mWorldManager.LoadSeamlessBoundryList(); mWorldManager.LoadSeamlessBoundryList();
mWorldManager.LoadActorClasses(); mWorldManager.LoadActorClasses();
mWorldManager.LoadSpawnLocations(); mWorldManager.LoadSpawnLocations();
mWorldManager.LoadBattleNpcs();
mWorldManager.LoadStatusEffects();
mWorldManager.LoadBattleCommands();
mWorldManager.LoadBattleTraits();
mWorldManager.SpawnAllActors(); mWorldManager.SpawnAllActors();
mWorldManager.StartZoneThread(); mWorldManager.StartZoneThread();
@ -267,7 +274,7 @@ namespace FFXIVClassic_Map_Server
return mWorldManager; return mWorldManager;
} }
public static Dictionary<uint, Item> GetGamedataItems() public static Dictionary<uint, ItemData> GetGamedataItems()
{ {
return mGamedataItems; return mGamedataItems;
} }
@ -282,7 +289,7 @@ namespace FFXIVClassic_Map_Server
return mStaticActors.FindStaticActor(name); return mStaticActors.FindStaticActor(name);
} }
public static Item GetItemGamedata(uint id) public static ItemData GetItemGamedata(uint id)
{ {
if (mGamedataItems.ContainsKey(id)) if (mGamedataItems.ContainsKey(id))
return mGamedataItems[id]; return mGamedataItems[id];
@ -290,5 +297,13 @@ namespace FFXIVClassic_Map_Server
return null; return null;
} }
public static GuildleveData GetGuildleveGamedata(uint id)
{
if (mGamedataGuildleves.ContainsKey(id))
return mGamedataGuildleves[id];
else
return null;
}
} }
} }

Binary file not shown.

View file

@ -3,7 +3,6 @@ using FFXIVClassic.Common;
using FFXIVClassic_Map_Server.actors.area; using FFXIVClassic_Map_Server.actors.area;
using FFXIVClassic_Map_Server.actors.chara.npc; using FFXIVClassic_Map_Server.actors.chara.npc;
using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.Actors;
using FFXIVClassic.Common;
using FFXIVClassic_Map_Server.dataobjects; using FFXIVClassic_Map_Server.dataobjects;
using FFXIVClassic_Map_Server.dataobjects.chara; using FFXIVClassic_Map_Server.dataobjects.chara;
using FFXIVClassic_Map_Server.lua; using FFXIVClassic_Map_Server.lua;
@ -23,6 +22,9 @@ using FFXIVClassic_Map_Server.packets.WorldPackets.Send.Group;
using System.Threading; using System.Threading;
using System.Diagnostics; using System.Diagnostics;
using FFXIVClassic_Map_Server.actors.director; 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 namespace FFXIVClassic_Map_Server
{ {
@ -35,10 +37,18 @@ namespace FFXIVClassic_Map_Server
private Dictionary<uint, ZoneEntrance> zoneEntranceList; private Dictionary<uint, ZoneEntrance> zoneEntranceList;
private Dictionary<uint, ActorClass> actorClasses = new Dictionary<uint,ActorClass>(); private Dictionary<uint, ActorClass> actorClasses = new Dictionary<uint,ActorClass>();
private Dictionary<ulong, Party> currentPlayerParties = new Dictionary<ulong, Party>(); //GroupId, Party object private Dictionary<ulong, Party> currentPlayerParties = new Dictionary<ulong, Party>(); //GroupId, Party object
private Dictionary<uint, StatusEffect> statusEffectList = new Dictionary<uint, StatusEffect>();
private Dictionary<ushort, BattleCommand> battleCommandList = new Dictionary<ushort, BattleCommand>();
private Dictionary<Tuple<byte, short>, List<uint>> battleCommandIdByLevel = new Dictionary<Tuple<byte, short>, List<uint>>();//Holds battle command ids keyed by class id and level (in that order)
private Dictionary<ushort, BattleTrait> battleTraitList = new Dictionary<ushort, BattleTrait>();
private Dictionary<byte, List<ushort>> battleTraitIdsForClass = new Dictionary<byte, List<ushort>>();
private Dictionary<uint, ModifierList> battleNpcGenusMods = new Dictionary<uint, ModifierList>();
private Dictionary<uint, ModifierList> battleNpcPoolMods = new Dictionary<uint, ModifierList>();
private Dictionary<uint, ModifierList> battleNpcSpawnMods = new Dictionary<uint, ModifierList>();
private Server mServer; private Server mServer;
private const int MILIS_LOOPTIME = 10; private const int MILIS_LOOPTIME = 333;
private Timer mZoneTimer; private Timer mZoneTimer;
//Content Groups //Content Groups
@ -76,7 +86,8 @@ namespace FFXIVClassic_Map_Server
isInn, isInn,
canRideChocobo, canRideChocobo,
canStealth, canStealth,
isInstanceRaid isInstanceRaid,
loadNavMesh
FROM server_zones FROM server_zones
WHERE zoneName IS NOT NULL and serverIp = @ip and serverPort = @port"; WHERE zoneName IS NOT NULL and serverIp = @ip and serverPort = @port";
@ -89,7 +100,8 @@ namespace FFXIVClassic_Map_Server
{ {
while (reader.Read()) 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; zoneList[zone.actorId] = zone;
count1++; count1++;
} }
@ -413,6 +425,121 @@ namespace FFXIVClassic_Map_Server
Program.Log.Info(String.Format("Loaded {0} spawn(s).", count)); 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() public void SpawnAllActors()
{ {
Program.Log.Info("Spawning actors..."); Program.Log.Info("Spawning actors...");
@ -420,6 +547,200 @@ namespace FFXIVClassic_Map_Server
z.SpawnAllActors(true); 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<uint, ModifierList> 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. //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) public void DoSeamlessZoneChange(Player player, uint destinationZoneId)
{ {
@ -582,7 +903,6 @@ namespace FFXIVClassic_Map_Server
{ {
oldZone.RemoveActorFromZone(player); oldZone.RemoveActorFromZone(player);
} }
newArea.AddActorToZone(player); newArea.AddActorToZone(player);
//Update player actor's properties //Update player actor's properties
@ -596,19 +916,24 @@ namespace FFXIVClassic_Map_Server
player.positionZ = spawnZ; player.positionZ = spawnZ;
player.rotation = spawnRotation; player.rotation = spawnRotation;
//Delete any GL directors
GuildleveDirector glDirector = player.GetGuildleveDirector();
if (glDirector != null)
player.RemoveDirector(glDirector);
//Delete content if have //Delete content if have
if (player.currentContentGroup != null) if (player.currentContentGroup != null)
{ {
player.currentContentGroup.RemoveMember(player.actorId); player.currentContentGroup.RemoveMember(player.actorId);
player.SetCurrentContentGroup(null, player); player.SetCurrentContentGroup(null);
if (oldZone is PrivateAreaContent) if (oldZone is PrivateAreaContent)
((PrivateAreaContent)oldZone).CheckDestroy(); ((PrivateAreaContent)oldZone).CheckDestroy();
} }
//Send packets //Send packets
player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId), true, false); player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId));
player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10), true, false); player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x2));
player.SendZoneInPackets(this, spawnType); player.SendZoneInPackets(this, spawnType);
player.playerSession.ClearInstance(); player.playerSession.ClearInstance();
player.SendInstanceUpdate(); player.SendInstanceUpdate();
@ -645,7 +970,8 @@ namespace FFXIVClassic_Map_Server
//Remove player from currentZone if transfer else it's login //Remove player from currentZone if transfer else it's login
if (player.zone != null) if (player.zone != null)
{ {
player.zone.RemoveActorFromZone(player); player.playerSession.LockUpdates(true);
player.zone.RemoveActorFromZone(player);
player.zone.AddActorToZone(player); player.zone.AddActorToZone(player);
//Update player actor's properties; //Update player actor's properties;
@ -655,10 +981,11 @@ namespace FFXIVClassic_Map_Server
player.rotation = spawnRotation; player.rotation = spawnRotation;
//Send packets //Send packets
player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10), true, false); player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10));
player.playerSession.QueuePacket(player.CreateSpawnTeleportPacket(player.actorId, spawnType), true, false); player.playerSession.QueuePacket(player.CreateSpawnTeleportPacket(spawnType));
player.SendInstanceUpdate();
player.playerSession.LockUpdates(false);
player.SendInstanceUpdate();
} }
} }
@ -698,8 +1025,8 @@ namespace FFXIVClassic_Map_Server
player.SendGameMessage(GetActor(), 34108, 0x20); player.SendGameMessage(GetActor(), 34108, 0x20);
//Send packets //Send packets
player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId), true, false); player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId));
player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10), true, false); player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x10));
player.SendZoneInPackets(this, spawnType); player.SendZoneInPackets(this, spawnType);
player.playerSession.ClearInstance(); player.playerSession.ClearInstance();
player.SendInstanceUpdate(); player.SendInstanceUpdate();
@ -733,9 +1060,9 @@ namespace FFXIVClassic_Map_Server
//Send packets //Send packets
if (!isLogin) if (!isLogin)
{ {
player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId), true, false); player.playerSession.QueuePacket(DeleteAllActorsPacket.BuildPacket(player.actorId));
player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x2), true, false); player.playerSession.QueuePacket(_0xE2Packet.BuildPacket(player.actorId, 0x2));
player.SendZoneInPackets(this, spawnType); //player.SendZoneInPackets(this, spawnType);
} }
player.SendZoneInPackets(this, spawnType); player.SendZoneInPackets(this, spawnType);
@ -751,18 +1078,15 @@ namespace FFXIVClassic_Map_Server
public void ReloadZone(uint zoneId) public void ReloadZone(uint zoneId)
{ {
if (!zoneList.ContainsKey(zoneId)) lock (zoneList)
return; {
if (!zoneList.ContainsKey(zoneId))
return;
Zone zone = zoneList[zoneId]; Zone zone = zoneList[zoneId];
//zone.clear(); //zone.clear();
//LoadNPCs(zone.actorId); //LoadNPCs(zone.actorId);
}
}
public ContentGroup CreateContentGroup(Director director)
{
return CreateContentGroup(director, null);
} }
public ContentGroup CreateContentGroup(Director director, params Actor[] actors) public ContentGroup CreateContentGroup(Director director, params Actor[] actors)
@ -793,6 +1117,62 @@ namespace FFXIVClassic_Map_Server
} }
} }
public ContentGroup CreateContentGroup(Director director, List<Actor> 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<Actor> 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) public void DeleteContentGroup(ulong groupId)
{ {
lock (groupLock) lock (groupLock)
@ -800,7 +1180,6 @@ namespace FFXIVClassic_Map_Server
if (mContentGroups.ContainsKey(groupId) && mContentGroups[groupId] is ContentGroup) if (mContentGroups.ContainsKey(groupId) && mContentGroups[groupId] is ContentGroup)
{ {
ContentGroup group = (ContentGroup)mContentGroups[groupId]; ContentGroup group = (ContentGroup)mContentGroups[groupId];
group.SendDeletePackets();
mContentGroups.Remove(groupId); mContentGroups.Remove(groupId);
} }
} }
@ -819,55 +1198,55 @@ namespace FFXIVClassic_Map_Server
public void RequestWorldLinkshellCreate(Player player, string name, ushort crest) public void RequestWorldLinkshellCreate(Player player, string name, ushort crest)
{ {
SubPacket packet = CreateLinkshellPacket.BuildPacket(player.playerSession, name, crest, player.actorId); 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) public void RequestWorldLinkshellCrestModify(Player player, string name, ushort crest)
{ {
SubPacket packet = ModifyLinkshellPacket.BuildPacket(player.playerSession, 1, name, null, crest, 0); 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) public void RequestWorldLinkshellDelete(Player player, string name)
{ {
SubPacket packet = DeleteLinkshellPacket.BuildPacket(player.playerSession, 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) public void RequestWorldLinkshellRankChange(Player player, string lsname, string memberName, byte newRank)
{ {
SubPacket packet = LinkshellRankChangePacket.BuildPacket(player.playerSession, memberName, lsname, 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) public void RequestWorldLinkshellInviteMember(Player player, string lsname, uint invitedActorId)
{ {
SubPacket packet = LinkshellInvitePacket.BuildPacket(player.playerSession, invitedActorId, lsname); SubPacket packet = LinkshellInvitePacket.BuildPacket(player.playerSession, invitedActorId, lsname);
Server.GetWorldConnection().QueuePacket(packet, true, false); player.QueuePacket(packet);
} }
public void RequestWorldLinkshellCancelInvite(Player player) public void RequestWorldLinkshellCancelInvite(Player player)
{ {
SubPacket packet = LinkshellInviteCancelPacket.BuildPacket(player.playerSession); SubPacket packet = LinkshellInviteCancelPacket.BuildPacket(player.playerSession);
Server.GetWorldConnection().QueuePacket(packet, true, false); player.QueuePacket(packet);
} }
public void RequestWorldLinkshellLeave(Player player, string lsname) public void RequestWorldLinkshellLeave(Player player, string lsname)
{ {
SubPacket packet = LinkshellLeavePacket.BuildPacket(player.playerSession, lsname, null, false); 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) public void RequestWorldLinkshellKick(Player player, string lsname, string kickedName)
{ {
SubPacket packet = LinkshellLeavePacket.BuildPacket(player.playerSession, lsname, kickedName, true); 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) public void RequestWorldLinkshellChangeActive(Player player, string lsname)
{ {
SubPacket packet = LinkshellChangePacket.BuildPacket(player.playerSession, 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) 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) 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) lock (zoneList)
{ {
foreach (Area area in zoneList.Values) Program.Tick = DateTime.Now;
area.Update(MILIS_LOOPTIME); 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) public Actor GetActorInWorld(uint charId)
{ {
foreach (Zone zone in zoneList.Values) lock (zoneList)
{ {
Actor a = zone.FindActorInZone(charId); foreach (Zone zone in zoneList.Values)
if (a != null) {
return a; Actor a = zone.FindActorInZone(charId);
if (a != null)
return a;
}
} }
return null; return null;
} }
public Actor GetActorInWorldByUniqueId(string uid) public Actor GetActorInWorldByUniqueId(string uid)
{ {
foreach (Zone zone in zoneList.Values) lock (zoneList)
{ {
Actor a = zone.FindActorInZoneByUniqueID(uid); foreach (Zone zone in zoneList.Values)
if (a != null) {
return a; Actor a = zone.FindActorInZoneByUniqueID(uid);
if (a != null)
return a;
}
} }
return null; return null;
} }
public Zone GetZone(uint zoneId) public Zone GetZone(uint zoneId)
{ {
if (!zoneList.ContainsKey(zoneId)) lock (zoneList)
return null; {
return zoneList[zoneId]; if (!zoneList.ContainsKey(zoneId))
return null;
return zoneList[zoneId];
}
} }
public PrivateArea GetPrivateArea(uint zoneId, string privateArea, uint privateAreaType) public PrivateArea GetPrivateArea(uint zoneId, string privateArea, uint privateAreaType)
{ {
if (!zoneList.ContainsKey(zoneId)) lock (zoneList)
return null; {
if (!zoneList.ContainsKey(zoneId))
return null;
return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType); return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType);
}
} }
public WorldMaster GetActor() public WorldMaster GetActor()
@ -1059,7 +1457,52 @@ namespace FFXIVClassic_Map_Server
else else
return null; 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<uint> GetBattleCommandIdByLevel(byte classId, short level)
{
List<uint> ids;
return battleCommandIdByLevel.TryGetValue(Tuple.Create(classId, level), out ids) ? ids : new List<uint>();
}
public BattleTrait GetBattleTrait(ushort id)
{
BattleTrait battleTrait;
battleTraitList.TryGetValue(id, out battleTrait);
return battleTrait;
}
public List<ushort> GetAllBattleTraitIdsForClass(byte classId)
{
List<ushort> ids;
return battleTraitIdsForClass.TryGetValue(classId, out ids) ? ids : new List<ushort>();
}
} }
} }

View file

@ -9,11 +9,36 @@ using System.Collections.Generic;
using FFXIVClassic_Map_Server.actors.area; using FFXIVClassic_Map_Server.actors.area;
using System.Reflection; using System.Reflection;
using System.ComponentModel; 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 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 class Actor
{ {
public static uint INVALID_ACTORID = 0xC0000000;
public uint actorId; public uint actorId;
public string actorName; public string actorName;
@ -21,7 +46,9 @@ namespace FFXIVClassic_Map_Server.Actors
public string customDisplayName; public string customDisplayName;
public ushort currentMainState = SetActorStatePacket.MAIN_STATE_PASSIVE; 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 positionX, positionY, positionZ, rotation;
public float oldPositionX, oldPositionY, oldPositionZ, oldRotation; public float oldPositionX, oldPositionY, oldPositionZ, oldRotation;
public ushort moveState, oldMoveState; public ushort moveState, oldMoveState;
@ -40,6 +67,15 @@ namespace FFXIVClassic_Map_Server.Actors
public string className; public string className;
public List<LuaParam> classParams; public List<LuaParam> classParams;
public List<Vector3> positionUpdates;
protected DateTime lastUpdateScript;
protected DateTime lastUpdate;
public Actor target;
public bool isAtSpawn = true;
public ActorUpdateFlags updateFlags;
public EventList eventConditions; public EventList eventConditions;
public Actor(uint actorId) public Actor(uint actorId)
@ -58,6 +94,7 @@ namespace FFXIVClassic_Map_Server.Actors
this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK; this.moveSpeeds[1] = SetActorSpeedPacket.DEFAULT_WALK;
this.moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN; this.moveSpeeds[2] = SetActorSpeedPacket.DEFAULT_RUN;
this.moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE; this.moveSpeeds[3] = SetActorSpeedPacket.DEFAULT_ACTIVE;
positionUpdates = new List<Vector3>();
} }
public void SetPushCircleRange(string triggerName, float size) 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; SubPacket spawnPacket;
if (!spawnedFirstTime && playerActorId == actorId) 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) 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 else
{ {
if (this is Player) 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 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; spawnedFirstTime = true;
return spawnPacket; return spawnPacket;
} }
public SubPacket CreateSpawnTeleportPacket(uint playerActorId, ushort spawnType) public SubPacket CreateSpawnTeleportPacket(ushort spawnType)
{ {
SubPacket spawnPacket; 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(); //spawnPacket.DebugPrintSubPacket();
return spawnPacket; 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<SubPacket> GetEventConditionPackets(uint playerActorId) public List<SubPacket> GetEventConditionPackets()
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
@ -145,126 +200,147 @@ namespace FFXIVClassic_Map_Server.Actors
if (eventConditions.talkEventConditions != null) if (eventConditions.talkEventConditions != null)
{ {
foreach (EventList.TalkEventCondition condition in eventConditions.talkEventConditions) foreach (EventList.TalkEventCondition condition in eventConditions.talkEventConditions)
subpackets.Add(SetTalkEventCondition.BuildPacket(playerActorId, actorId, condition)); subpackets.Add(SetTalkEventCondition.BuildPacket(actorId, condition));
} }
if (eventConditions.noticeEventConditions != null) if (eventConditions.noticeEventConditions != null)
{ {
foreach (EventList.NoticeEventCondition condition in eventConditions.noticeEventConditions) foreach (EventList.NoticeEventCondition condition in eventConditions.noticeEventConditions)
subpackets.Add(SetNoticeEventCondition.BuildPacket(playerActorId, actorId, condition)); subpackets.Add(SetNoticeEventCondition.BuildPacket(actorId, condition));
} }
if (eventConditions.emoteEventConditions != null) if (eventConditions.emoteEventConditions != null)
{ {
foreach (EventList.EmoteEventCondition condition in eventConditions.emoteEventConditions) foreach (EventList.EmoteEventCondition condition in eventConditions.emoteEventConditions)
subpackets.Add(SetEmoteEventCondition.BuildPacket(playerActorId, actorId, condition)); subpackets.Add(SetEmoteEventCondition.BuildPacket(actorId, condition));
} }
if (eventConditions.pushWithCircleEventConditions != null) if (eventConditions.pushWithCircleEventConditions != null)
{ {
foreach (EventList.PushCircleEventCondition condition in eventConditions.pushWithCircleEventConditions) foreach (EventList.PushCircleEventCondition condition in eventConditions.pushWithCircleEventConditions)
subpackets.Add(SetPushEventConditionWithCircle.BuildPacket(playerActorId, actorId, condition)); subpackets.Add(SetPushEventConditionWithCircle.BuildPacket(actorId, condition));
} }
if (eventConditions.pushWithFanEventConditions != null) if (eventConditions.pushWithFanEventConditions != null)
{ {
foreach (EventList.PushFanEventCondition condition in eventConditions.pushWithFanEventConditions) foreach (EventList.PushFanEventCondition condition in eventConditions.pushWithFanEventConditions)
subpackets.Add(SetPushEventConditionWithFan.BuildPacket(playerActorId, actorId, condition)); subpackets.Add(SetPushEventConditionWithFan.BuildPacket(actorId, condition));
} }
if (eventConditions.pushWithBoxEventConditions != null) if (eventConditions.pushWithBoxEventConditions != null)
{ {
foreach (EventList.PushBoxEventCondition condition in eventConditions.pushWithBoxEventConditions) foreach (EventList.PushBoxEventCondition condition in eventConditions.pushWithBoxEventConditions)
subpackets.Add(SetPushEventConditionWithTriggerBox.BuildPacket(playerActorId, actorId, condition)); subpackets.Add(SetPushEventConditionWithTriggerBox.BuildPacket(actorId, condition));
} }
return subpackets; return subpackets;
} }
public BasePacket GetSetEventStatusPackets(uint playerActorId) public List<SubPacket> GetSetEventStatusPackets()
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
//Return empty list //Return empty list
if (eventConditions == null) if (eventConditions == null)
return BasePacket.CreatePacket(subpackets, true, false); return subpackets;
if (eventConditions.talkEventConditions != null) if (eventConditions.talkEventConditions != null)
{ {
foreach (EventList.TalkEventCondition condition in eventConditions.talkEventConditions) 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) if (eventConditions.noticeEventConditions != null)
{ {
foreach (EventList.NoticeEventCondition condition in eventConditions.noticeEventConditions) 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) if (eventConditions.emoteEventConditions != null)
{ {
foreach (EventList.EmoteEventCondition condition in eventConditions.emoteEventConditions) 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) if (eventConditions.pushWithCircleEventConditions != null)
{ {
foreach (EventList.PushCircleEventCondition condition in eventConditions.pushWithCircleEventConditions) 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) if (eventConditions.pushWithFanEventConditions != null)
{ {
foreach (EventList.PushFanEventCondition condition in eventConditions.pushWithFanEventConditions) 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) if (eventConditions.pushWithBoxEventConditions != null)
{ {
foreach (EventList.PushBoxEventCondition condition in eventConditions.pushWithBoxEventConditions) 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<SubPacket> GetSpawnPackets(Player player, ushort spawnType)
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
subpackets.Add(CreateAddActorPacket(playerActorId, 8)); subpackets.Add(CreateAddActorPacket(8));
subpackets.AddRange(GetEventConditionPackets(playerActorId)); subpackets.AddRange(GetEventConditionPackets());
subpackets.Add(CreateSpeedPacket(playerActorId)); subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(playerActorId, spawnType)); subpackets.Add(CreateSpawnPositonPacket(player, spawnType));
subpackets.Add(CreateNamePacket(playerActorId)); subpackets.Add(CreateNamePacket());
subpackets.Add(CreateStatePacket(playerActorId)); subpackets.Add(CreateStatePacket());
subpackets.Add(CreateIsZoneingPacket(playerActorId)); subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket(playerActorId)); subpackets.Add(CreateScriptBindPacket(player));
return BasePacket.CreatePacket(subpackets, true, false); return subpackets;
} }
public virtual BasePacket GetInitPackets(uint playerActorId) public virtual List<SubPacket> GetSpawnPackets()
{ {
return GetSpawnPackets(0x1);
}
public virtual List<SubPacket> GetSpawnPackets(ushort spawnType)
{
List<SubPacket> subpackets = new List<SubPacket>();
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<SubPacket> GetInitPackets()
{
List<SubPacket> packets = new List<SubPacket>();
SetActorPropetyPacket initProperties = new SetActorPropetyPacket("/_init"); SetActorPropetyPacket initProperties = new SetActorPropetyPacket("/_init");
initProperties.AddByte(0xE14B0CA8, 1); initProperties.AddByte(0xE14B0CA8, 1);
initProperties.AddByte(0x2138FD71, 1); initProperties.AddByte(0x2138FD71, 1);
initProperties.AddByte(0xFBFBCFB1, 1); initProperties.AddByte(0xFBFBCFB1, 1);
initProperties.AddTarget(); initProperties.AddTarget();
return BasePacket.CreatePacket(initProperties.BuildPacket(playerActorId, actorId), true, false); packets.Add(initProperties.BuildPacket(actorId));
return packets;
} }
public override bool Equals(Object obj) public override bool Equals(Object obj)
@ -296,20 +372,40 @@ namespace FFXIVClassic_Map_Server.Actors
return classParams; return classParams;
} }
//character's newMainState kind of messes with this
public void ChangeState(ushort newState) public void ChangeState(ushort newState)
{ {
currentMainState = newState; if (newState != currentMainState)
SubPacket ChangeStatePacket = SetActorStatePacket.BuildPacket(actorId, actorId, newState, currentSubState); {
SubPacket battleActionPacket = BattleAction1Packet.BuildPacket(actorId, actorId); currentMainState = newState;
zone.BroadcastPacketAroundActor(this, ChangeStatePacket);
zone.BroadcastPacketAroundActor(this, battleActionPacket); 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) public void ChangeSpeed(int type, float value)
{ {
moveSpeeds[type] = value; moveSpeeds[type] = value;
SubPacket ChangeSpeedPacket = SetActorSpeedPacket.BuildPacket(actorId, actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3]); updateFlags |= ActorUpdateFlags.Speed;
zone.BroadcastPacketAroundActor(this, ChangeSpeedPacket);
} }
public void ChangeSpeed(float speedStop, float speedWalk, float speedRun, float speedActive) public void ChangeSpeed(float speedStop, float speedWalk, float speedRun, float speedActive)
@ -318,12 +414,68 @@ namespace FFXIVClassic_Map_Server.Actors
moveSpeeds[1] = speedWalk; moveSpeeds[1] = speedWalk;
moveSpeeds[2] = speedRun; moveSpeeds[2] = speedRun;
moveSpeeds[3] = speedActive; moveSpeeds[3] = speedActive;
SubPacket ChangeSpeedPacket = SetActorSpeedPacket.BuildPacket(actorId, actorId, moveSpeeds[0], moveSpeeds[1], moveSpeeds[2], moveSpeeds[3]); updateFlags |= ActorUpdateFlags.Speed;
zone.BroadcastPacketAroundActor(this, ChangeSpeedPacket);
} }
public void Update(double deltaTime) public virtual void Update(DateTime tick)
{ {
}
public virtual void PostUpdate(DateTime tick, List<SubPacket> packets = null)
{
if (updateFlags != ActorUpdateFlags.None)
{
packets = packets ?? new List<SubPacket>();
if ((updateFlags & ActorUpdateFlags.Position) != 0)
{
if (positionUpdates != null && positionUpdates.Count > 0)
{
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) public void GenerateActorName(int actorNumber)
@ -358,7 +510,7 @@ namespace FFXIVClassic_Map_Server.Actors
{ {
className = className.Substring(0, 20 - zoneName.Length); className = className.Substring(0, 20 - zoneName.Length);
} }
catch (ArgumentOutOfRangeException e) catch (ArgumentOutOfRangeException)
{ } { }
//Convert actor number to base 63 //Convert actor number to base 63
@ -426,8 +578,8 @@ namespace FFXIVClassic_Map_Server.Actors
SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc); SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc);
changeProperty.AddProperty(this, name); changeProperty.AddProperty(this, name);
changeProperty.AddTarget(); changeProperty.AddTarget();
SubPacket subpacket = changeProperty.BuildPacket(player.actorId, player.actorId); SubPacket subpacket = changeProperty.BuildPacket(player.actorId);
player.playerSession.QueuePacket(subpacket, true, false); player.playerSession.QueuePacket(subpacket);
subpacket.DebugPrintSubPacket(); subpacket.DebugPrintSubPacket();
return true; return true;
} }
@ -439,21 +591,22 @@ namespace FFXIVClassic_Map_Server.Actors
if (value.GetType() == curObj.GetType()) if (value.GetType() == curObj.GetType())
parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, value); parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, value);
else else
parentObj.GetType().GetField(split[split.Length-1]).SetValue(parentObj, TypeDescriptor.GetConverter(value.GetType()).ConvertTo(value, curObj.GetType())); parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, TypeDescriptor.GetConverter(value.GetType()).ConvertTo(value, curObj.GetType()));
SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc); SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc);
changeProperty.AddProperty(this, name); changeProperty.AddProperty(this, name);
changeProperty.AddTarget(); changeProperty.AddTarget();
SubPacket subpacket = changeProperty.BuildPacket(player.actorId, player.actorId); SubPacket subpacket = changeProperty.BuildPacket(player.actorId);
player.playerSession.QueuePacket(subpacket, true, false); player.playerSession.QueuePacket(subpacket);
subpacket.DebugPrintSubPacket(); subpacket.DebugPrintSubPacket();
return true; return true;
} }
} }
return false; return false;
} }
} }
#region positioning
public List<float> GetPos() public List<float> GetPos()
{ {
List<float> pos = new List<float>(); List<float> pos = new List<float>();
@ -467,6 +620,11 @@ namespace FFXIVClassic_Map_Server.Actors
return pos; 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) public void SetPos(float x, float y, float z, float rot = 0, uint zoneId = 0)
{ {
oldPositionX = positionX; oldPositionX = positionX;
@ -480,7 +638,7 @@ namespace FFXIVClassic_Map_Server.Actors
rotation = rot; rotation = rot;
// todo: handle zone? // 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() public Area GetZone()
@ -492,6 +650,107 @@ namespace FFXIVClassic_Map_Server.Actors
{ {
return zoneId; 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<Vector3>();
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
} }
} }

View file

@ -41,7 +41,7 @@ namespace FFXIVClassic_Map_Server.Actors
byte byteOut = (Byte)(byteIn ^ 0x73); byte byteOut = (Byte)(byteIn ^ 0x73);
binWriter.Write((Byte)byteOut); binWriter.Write((Byte)byteOut);
} }
catch (EndOfStreamException e) { break; } catch (EndOfStreamException) { break; }
} }
binReader.Close(); 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.Error("Could not find staticactors file."); return false; }
Program.Log.Info("Loaded {0} static actors.", mStaticActors.Count()); Program.Log.Info("Loaded {0} static actors.", mStaticActors.Count());

View file

@ -1,20 +1,13 @@
using FFXIVClassic_Map_Server; using FFXIVClassic_Map_Server;
using FFXIVClassic.Common; using FFXIVClassic.Common;
using FFXIVClassic_Map_Server.actors.area; using FFXIVClassic_Map_Server.actors.area;
using FFXIVClassic_Map_Server.actors.chara.npc; 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.lua;
using FFXIVClassic_Map_Server.packets.send.actor; using FFXIVClassic_Map_Server.packets.send.actor;
using MoonSharp.Interpreter;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FFXIVClassic_Map_Server.packets.send; using FFXIVClassic_Map_Server.packets.send;
using FFXIVClassic_Map_Server.actors.group;
using FFXIVClassic_Map_Server.actors.director; using FFXIVClassic_Map_Server.actors.director;
namespace FFXIVClassic_Map_Server.Actors namespace FFXIVClassic_Map_Server.Actors
@ -30,7 +23,7 @@ namespace FFXIVClassic_Map_Server.Actors
protected string classPath; protected string classPath;
public int boundingGridSize = 50; 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 numXBlocks, numYBlocks;
protected int halfWidth, halfHeight; protected int halfWidth, halfHeight;
@ -84,79 +77,87 @@ namespace FFXIVClassic_Map_Server.Actors
} }
} }
public override SubPacket CreateScriptBindPacket(uint playerActorId) public override SubPacket CreateScriptBindPacket()
{ {
List<LuaParam> lParams; List<LuaParam> lParams;
lParams = LuaUtils.CreateLuaParamList(classPath, false, true, zoneName, "/Area/Zone/ZoneDefault", -1, (byte)1, true, false, false, false, false, false, false, false); 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<SubPacket> GetSpawnPackets()
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
subpackets.Add(CreateAddActorPacket(playerActorId, 0)); subpackets.Add(CreateAddActorPacket(0));
subpackets.Add(CreateSpeedPacket(playerActorId)); subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x1)); subpackets.Add(CreateSpawnPositonPacket(0x1));
subpackets.Add(CreateNamePacket(playerActorId)); subpackets.Add(CreateNamePacket());
subpackets.Add(CreateStatePacket(playerActorId)); subpackets.Add(CreateStatePacket());
subpackets.Add(CreateIsZoneingPacket(playerActorId)); subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket(playerActorId)); subpackets.Add(CreateScriptBindPacket());
return BasePacket.CreatePacket(subpackets, true, false); return subpackets;
} }
// todo: handle instance areas in derived class? (see virtuals)
#region Actor Management #region Actor Management
public void AddActorToZone(Actor actor) public void AddActorToZone(Actor actor)
{ {
lock (mActorList) lock (mActorList)
{ {
if (actor is Character)
((Character)actor).ResetTempVars();
if (!mActorList.ContainsKey(actor.actorId)) if (!mActorList.ContainsKey(actor.actorId))
mActorList.Add(actor.actorId, actor); 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) public void RemoveActorFromZone(Actor actor)
{ {
lock (mActorList) if (actor != null)
mActorList.Remove(actor.actorId); lock (mActorList)
{
mActorList.Remove(actor.actorId);
int gridX = (int)actor.positionX / boundingGridSize; int gridX = (int)actor.positionX / boundingGridSize;
int gridY = (int)actor.positionZ / boundingGridSize; int gridY = (int)actor.positionZ / boundingGridSize;
gridX += halfWidth; gridX += halfWidth;
gridY += halfHeight; gridY += halfHeight;
//Boundries //Boundries
if (gridX < 0) if (gridX < 0)
gridX = 0; gridX = 0;
if (gridX >= numXBlocks) if (gridX >= numXBlocks)
gridX = numXBlocks - 1; gridX = numXBlocks - 1;
if (gridY < 0) if (gridY < 0)
gridY = 0; gridY = 0;
if (gridY >= numYBlocks) if (gridY >= numYBlocks)
gridY = numYBlocks - 1; gridY = numYBlocks - 1;
lock (mActorBlock) lock (mActorBlock)
mActorBlock[gridX, gridY].Remove(actor); mActorBlock[gridX, gridY].Remove(actor);
}
} }
public void UpdateActorPosition(Actor actor) public void UpdateActorPosition(Actor actor)
@ -204,12 +205,12 @@ namespace FFXIVClassic_Map_Server.Actors
} }
} }
public List<Actor> GetActorsAroundPoint(float x, float y, int checkDistance) public virtual List<T> GetActorsAroundPoint<T>(float x, float y, int checkDistance) where T : Actor
{ {
checkDistance /= boundingGridSize; checkDistance /= boundingGridSize;
int gridX = (int)x/boundingGridSize; int gridX = (int)x / boundingGridSize;
int gridY = (int)y/boundingGridSize; int gridY = (int)y / boundingGridSize;
gridX += halfWidth; gridX += halfWidth;
gridY += halfHeight; gridY += halfHeight;
@ -224,7 +225,7 @@ namespace FFXIVClassic_Map_Server.Actors
if (gridY >= numYBlocks) if (gridY >= numYBlocks)
gridY = numYBlocks - 1; gridY = numYBlocks - 1;
List<Actor> result = new List<Actor>(); List<T> result = new List<T>();
lock (mActorBlock) lock (mActorBlock)
{ {
@ -232,7 +233,7 @@ namespace FFXIVClassic_Map_Server.Actors
{ {
for (int gy = gridY - checkDistance; gy <= gridY + checkDistance; gy++) for (int gy = gridY - checkDistance; gy <= gridY + checkDistance; gy++)
{ {
result.AddRange(mActorBlock[gx, gy]); result.AddRange(mActorBlock[gx, gy].OfType<T>());
} }
} }
} }
@ -246,11 +247,20 @@ namespace FFXIVClassic_Map_Server.Actors
result.RemoveAt(i); result.RemoveAt(i);
} }
} }
return result; return result;
} }
public List<Actor> GetActorsAroundActor(Actor actor, int checkDistance) public virtual List<Actor> GetActorsAroundPoint(float x, float y, int checkDistance)
{
return GetActorsAroundPoint<Actor>(x, y, checkDistance);
}
public virtual List<Actor> GetActorsAroundActor(Actor actor, int checkDistance)
{
return GetActorsAroundActor<Actor>(actor, checkDistance);
}
public virtual List<T> GetActorsAroundActor<T>(Actor actor, int checkDistance) where T : Actor
{ {
checkDistance /= boundingGridSize; checkDistance /= boundingGridSize;
@ -270,7 +280,7 @@ namespace FFXIVClassic_Map_Server.Actors
if (gridY >= numYBlocks) if (gridY >= numYBlocks)
gridY = numYBlocks - 1; gridY = numYBlocks - 1;
List<Actor> result = new List<Actor>(); var result = new List<T>();
lock (mActorBlock) 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++) 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<T>());
} }
} }
} }
//Remove players if isolation zone //Remove players if isolation zone
if (isIsolated) if (isIsolated)
{ {
@ -307,6 +318,11 @@ namespace FFXIVClassic_Map_Server.Actors
} }
} }
public T FindActorInArea<T>(uint id) where T : Actor
{
return FindActorInArea(id) as T;
}
public Actor FindActorInZoneByUniqueID(string uniqueId) public Actor FindActorInZoneByUniqueID(string uniqueId)
{ {
lock (mActorList) lock (mActorList)
@ -327,13 +343,10 @@ namespace FFXIVClassic_Map_Server.Actors
{ {
lock (mActorList) lock (mActorList)
{ {
foreach (Actor a in mActorList.Values) foreach (Player player in mActorList.Values.OfType<Player>())
{ {
if (a is Player) if (player.customDisplayName.ToLower().Equals(name.ToLower()))
{ return player;
if (((Player)a).customDisplayName.ToLower().Equals(name.ToLower()))
return (Player)a;
}
} }
return null; 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<T> GetAllActors<T>() where T : Actor
{
lock (mActorList)
{
List<T> actorList = new List<T>(mActorList.Count);
actorList.AddRange(mActorList.Values.OfType<T>());
return actorList;
}
}
public int GetActorCount()
{
lock (mActorList)
{
return mActorList.Count;
}
}
public virtual List<Actor> GetAllActors()
{
return GetAllActors<Actor>();
}
public virtual List<Player> GetPlayers()
{
return GetAllActors<Player>();
}
public virtual List<BattleNpc> GetMonsters()
{
return GetAllActors<BattleNpc>();
}
public virtual List<Ally> GetAllies()
{
return GetAllActors<Ally>();
}
public void BroadcastPacketsAroundActor(Actor actor, List<SubPacket> packets)
{
foreach (SubPacket packet in packets)
BroadcastPacketAroundActor(actor, packet);
}
public void BroadcastPacketAroundActor(Actor actor, SubPacket packet) public void BroadcastPacketAroundActor(Actor actor, SubPacket packet)
{ {
if (isIsolated) if (isIsolated)
@ -378,7 +436,7 @@ namespace FFXIVClassic_Map_Server.Actors
{ {
if (a is Player) if (a is Player)
{ {
if (isIsolated && packet.header.sourceId != a.actorId) if (isIsolated)
continue; continue;
SubPacket clonedPacket = new SubPacket(packet, a.actorId); SubPacket clonedPacket = new SubPacket(packet, a.actorId);
@ -390,70 +448,95 @@ namespace FFXIVClassic_Map_Server.Actors
public void SpawnActor(SpawnLocation location) public void SpawnActor(SpawnLocation location)
{ {
ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId); lock (mActorList)
{
if (actorClass == null) ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId);
return;
uint zoneId; if (actorClass == null)
return;
if (this is PrivateArea) uint zoneId;
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); 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) if (actorClass == null)
return null; return null;
uint zoneId; uint zoneId;
if (this is PrivateArea)
zoneId = ((PrivateArea)this).GetParentZone().actorId;
else
zoneId = actorId;
if (this is PrivateArea) Npc npc;
zoneId = ((PrivateArea)this).GetParentZone().actorId; if (isMob)
else npc = new BattleNpc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null);
zoneId = actorId; 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) 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) if (actorClass == null)
return null; return null;
uint zoneId; uint zoneId;
if (this is PrivateArea) if (this is PrivateArea)
zoneId = ((PrivateArea)this).GetParentZone().actorId; zoneId = ((PrivateArea)this).GetParentZone().actorId;
else else
zoneId = actorId; 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<BattleNpc>())
{
if (bnpc.GetBattleNpcId() == id)
return bnpc;
}
return null;
} }
public void DespawnActor(string uniqueId) public void DespawnActor(string uniqueId)
@ -461,6 +544,11 @@ namespace FFXIVClassic_Map_Server.Actors
RemoveActorFromZone(FindActorInZoneByUniqueID(uniqueId)); RemoveActorFromZone(FindActorInZoneByUniqueID(uniqueId));
} }
public void DespawnActor(Actor actor)
{
RemoveActorFromZone(actor);
}
public Director GetWeatherDirector() public Director GetWeatherDirector()
{ {
return mWeatherDirector; return mWeatherDirector;
@ -472,7 +560,7 @@ namespace FFXIVClassic_Map_Server.Actors
if (player != null && !zoneWide) 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) if (zoneWide)
{ {
@ -483,23 +571,67 @@ namespace FFXIVClassic_Map_Server.Actors
if (actor.Value is Player) if (actor.Value is Player)
{ {
player = ((Player)actor.Value); 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) 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()) public Director CreateGuildleveDirector(uint glid, byte difficulty, Player owner, params object[] args)
return null; {
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++; directorIdCount++;
return director; return director;
} }
@ -511,7 +643,8 @@ namespace FFXIVClassic_Map_Server.Actors
{ {
if (currentDirectors.ContainsKey(id)) if (currentDirectors.ContainsKey(id))
{ {
currentDirectors[id].RemoveChildren(); if (!currentDirectors[id].IsDeleted())
currentDirectors[id].EndDirector();
currentDirectors.Remove(id); currentDirectors.Remove(id);
} }
} }
@ -524,12 +657,18 @@ namespace FFXIVClassic_Map_Server.Actors
return null; return null;
} }
public void Update(double deltaTime) public override void Update(DateTime tick)
{ {
lock (mActorList) lock (mActorList)
{ {
foreach (Actor a in mActorList.Values) foreach (Actor a in mActorList.Values.ToList())
a.Update(deltaTime); a.Update(tick);
if ((tick - lastUpdateScript).TotalMilliseconds > 1500)
{
//LuaEngine.GetInstance().CallLuaFunctionForReturn(LuaEngine.GetScriptPath(this), "onUpdate", true, this, tick);
lastUpdateScript = tick;
}
} }
} }

View file

@ -41,7 +41,7 @@ namespace FFXIVClassic_Map_Server.actors.area
return parentZone; return parentZone;
} }
public override SubPacket CreateScriptBindPacket(uint playerActorId) public override SubPacket CreateScriptBindPacket()
{ {
List<LuaParam> lParams; List<LuaParam> lParams;
@ -50,8 +50,8 @@ namespace FFXIVClassic_Map_Server.actors.area
string realClassName = className.Substring(className.LastIndexOf("/") + 1); 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); 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(); ActorInstantiatePacket.BuildPacket(actorId, actorName, realClassName, lParams).DebugPrintSubPacket();
return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, realClassName, lParams); return ActorInstantiatePacket.BuildPacket(actorId, actorName, realClassName, lParams);
} }

View file

@ -14,7 +14,6 @@ namespace FFXIVClassic_Map_Server.actors.area
class PrivateAreaContent : PrivateArea class PrivateAreaContent : PrivateArea
{ {
private Director currentDirector; private Director currentDirector;
private ContentGroup currentContentGroup;
private bool isContentFinished = false; private bool isContentFinished = false;
public static PrivateAreaContent CreateContentArea(String scriptPath) 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) : base(parent, parent.actorId, classPath, privateAreaName, privateAreaType, 0, 0, 0)
{ {
currentDirector = director; currentDirector = director;
currentContentGroup = Server.GetWorldManager().CreateContentGroup(director); LuaEngine.GetInstance().CallLuaFunction(contentStarter, this, "onCreate", false, currentDirector);
LuaEngine.GetInstance().CallLuaFunction(contentStarter, this, "onCreate", false, currentContentGroup, currentDirector);
} }
public Director GetContentDirector() public Director GetContentDirector()
@ -35,11 +33,6 @@ namespace FFXIVClassic_Map_Server.actors.area
return currentDirector; return currentDirector;
} }
public ContentGroup GetContentGroup()
{
return currentContentGroup;
}
public void ContentFinished() public void ContentFinished()
{ {
isContentFinished = true; isContentFinished = true;
@ -47,17 +40,20 @@ namespace FFXIVClassic_Map_Server.actors.area
public void CheckDestroy() public void CheckDestroy()
{ {
if (isContentFinished) lock (mActorList)
{ {
bool noPlayersLeft = true; if (isContentFinished)
foreach (Actor a in mActorList.Values)
{ {
if (a is Player) bool noPlayersLeft = true;
noPlayersLeft = false; foreach (Actor a in mActorList.Values)
{
if (a is Player)
noPlayersLeft = false;
}
if (noPlayersLeft)
GetParentZone().DeleteContentArea(this);
} }
if (noPlayersLeft) }
GetParentZone().DeleteContentArea(this);
}
} }
} }

View file

@ -10,6 +10,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.IO;
using FFXIVClassic_Map_Server.actors.director; using FFXIVClassic_Map_Server.actors.director;
namespace FFXIVClassic_Map_Server.actors.area namespace FFXIVClassic_Map_Server.actors.area
@ -20,10 +22,31 @@ namespace FFXIVClassic_Map_Server.actors.area
Dictionary<string, List<PrivateAreaContent>> contentAreas = new Dictionary<string, List<PrivateAreaContent>>(); Dictionary<string, List<PrivateAreaContent>> contentAreas = new Dictionary<string, List<PrivateAreaContent>>();
Object contentAreasLock = new Object(); 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) : 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) public void AddPrivateArea(PrivateArea pa)
@ -51,13 +74,13 @@ namespace FFXIVClassic_Map_Server.actors.area
return null; return null;
} }
public override SubPacket CreateScriptBindPacket(uint playerActorId) public override SubPacket CreateScriptBindPacket()
{ {
bool isEntranceDesion = false; bool isEntranceDesion = false;
List<LuaParam> lParams; List<LuaParam> lParams;
lParams = LuaUtils.CreateLuaParamList(classPath, false, true, zoneName, "", -1, canRideChocobo ? (byte)1 : (byte)0, canStealth, isInn, false, false, false, true, isInstanceRaid, isEntranceDesion); 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) public void AddSpawnLocation(SpawnLocation spawn)
@ -97,28 +120,43 @@ namespace FFXIVClassic_Map_Server.actors.area
public Actor FindActorInZone(uint id) public Actor FindActorInZone(uint id)
{ {
if (!mActorList.ContainsKey(id)) lock (mActorList)
{ {
foreach(Dictionary<uint, PrivateArea> paList in privateAreas.Values) if (!mActorList.ContainsKey(id))
{ {
foreach(PrivateArea pa in paList.Values) foreach (Dictionary<uint, PrivateArea> paList in privateAreas.Values)
{ {
Actor actor = pa.FindActorInArea(id); foreach (PrivateArea pa in paList.Values)
if (actor != null) {
return actor; Actor actor = pa.FindActorInArea(id);
if (actor != null)
return actor;
}
} }
foreach (List<PrivateAreaContent> 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) lock (contentAreasLock)
{ {
Director director = CreateDirector(directorName); Director director = CreateDirector(directorName, true, args);
if (director == null) if (director == null)
return null; return null;
@ -127,6 +165,7 @@ namespace FFXIVClassic_Map_Server.actors.area
contentAreas.Add(areaName, new List<PrivateAreaContent>()); contentAreas.Add(areaName, new List<PrivateAreaContent>());
PrivateAreaContent contentArea = new PrivateAreaContent(this, classPath, areaName, 1, director, starterPlayer); PrivateAreaContent contentArea = new PrivateAreaContent(this, classPath, areaName, 1, director, starterPlayer);
contentAreas[areaName].Add(contentArea); contentAreas[areaName].Add(contentArea);
return 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;
}
}
} }
} }

View file

@ -5,7 +5,7 @@
public float potencial = 6.6f; public float potencial = 6.6f;
public short[] skillLevel = new short[52]; public short[] skillLevel = new short[52];
public short[] skillLevelCap = 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 short physicalLevel;
public int physicalExp; public int physicalExp;

View file

@ -2,10 +2,11 @@
{ {
class BattleTemp class BattleTemp
{ {
//Are these right?
public const uint NAMEPLATE_SHOWN = 0; public const uint NAMEPLATE_SHOWN = 0;
public const uint TARGETABLE = 1; public const uint TARGETABLE = 1;
//public const uint NAMEPLATE_SHOWN2 = 2; public const uint NAMEPLATE_SHOWN2 = 2;
public const uint NAMEPLATE_SHOWN2 = 3; //public const uint NAMEPLATE_SHOWN2 = 3;
public const uint STAT_STRENGTH = 3; public const uint STAT_STRENGTH = 3;
public const uint STAT_VITALITY = 4; public const uint STAT_VITALITY = 4;
@ -25,13 +26,13 @@
public const uint STAT_ACCURACY = 15; public const uint STAT_ACCURACY = 15;
public const uint STAT_NORMALDEFENSE = 18; public const uint STAT_NORMALDEFENSE = 18;
public const uint STAT_EVASION = 16; public const uint STAT_EVASION = 16;
public const uint STAT_ATTACK_MAGIC = 24; public const uint STAT_ATTACK_MAGIC = 23;
public const uint STAT_HEAL_MAGIC = 25; public const uint STAT_HEAL_MAGIC = 24;
public const uint STAT_ENCHANCEMENT_MAGIC_POTENCY = 26; public const uint STAT_ENCHANCEMENT_MAGIC_POTENCY = 25;
public const uint STAT_ENFEEBLING_MAGIC_POTENCY = 27; public const uint STAT_ENFEEBLING_MAGIC_POTENCY = 26;
public const uint STAT_MAGIC_ACCURACY = 28; public const uint STAT_MAGIC_ACCURACY = 27;
public const uint STAT_MAGIC_EVASION = 29; public const uint STAT_MAGIC_EVASION = 28;
public const uint STAT_CRAFT_PROCESSING = 30; public const uint STAT_CRAFT_PROCESSING = 30;
public const uint STAT_CRAFT_MAGIC_PROCESSING = 31; public const uint STAT_CRAFT_MAGIC_PROCESSING = 31;
@ -43,6 +44,6 @@
public float[] castGauge_speed = { 1.0f, 0.25f}; public float[] castGauge_speed = { 1.0f, 0.25f};
public bool[] timingCommandFlag = new bool[4]; public bool[] timingCommandFlag = new bool[4];
public ushort[] generalParameter = new ushort[35]; public short[] generalParameter = new short[35];
} }
} }

View file

@ -23,7 +23,7 @@
public uint[] command = new uint[64]; //ACTORS public uint[] command = new uint[64]; //ACTORS
public byte[] commandCategory = new byte[64]; public byte[] commandCategory = new byte[64];
public byte commandBorder = 0x20; public byte commandBorder = 0x20;
public bool[] commandAcquired = new bool[4096]; public bool[] commandAcquired = new bool[4096];
public bool[] additionalCommandAcquired = new bool[36]; public bool[] additionalCommandAcquired = new bool[36];
public uint currentContentGroup; public uint currentContentGroup;

File diff suppressed because it is too large Load diff

View file

@ -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
}
}

View file

@ -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<uint, ModifierListEntry> modList;
public Dictionary<uint, ModifierListEntry> mobModList;
public ModifierList(uint id)
{
modList = new Dictionary<uint, ModifierListEntry>();
mobModList = new Dictionary<uint, ModifierListEntry>();
}
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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<State> 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<State>();
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<T>() 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<T>() 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));
}
}
}

View file

@ -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<Character> GetTargets()
{
return targetFind?.GetTargets<Character>();
}
public ushort GetCommandType()
{
return (ushort) commandType;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<Character, HateEntry> hateList;
private Character owner;
public HateContainer(Character owner)
{
this.owner = owner;
this.hateList = new Dictionary<Character, HateEntry>();
}
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<Character, HateEntry> 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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<uint, StatusEffect> 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<SubPacket> statusSubpackets;
private ActorPropertyPacketUtil statusTimerPropPacketUtil;
public StatusEffectContainer(Character owner)
{
this.owner = owner;
this.effects = new Dictionary<uint, StatusEffect>();
statusSubpackets = new List<SubPacket>();
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<StatusEffect>();
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<StatusEffect> GetStatusEffectsByFlag(uint flag)
{
var list = new List<StatusEffect>();
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<StatusEffect> 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<SubPacket> GetStatusPackets()
{
return statusSubpackets;
}
public List<SubPacket> 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);
}
}
}

View file

@ -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<Character> GetContentGroupCharas()
{
List<Character> contentGroupCharas = null;
if (owner.currentContentGroup != null)
{
contentGroupCharas = new List<Character>(owner.currentContentGroup.GetMemberCount());
foreach (var charaId in owner.currentContentGroup.GetMembers())
{
var chara = owner.zone.FindActorInArea<Character>(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<Character> contentGroupCharas = null)
{
if (contentGroupCharas == null)
{
contentGroupCharas = GetContentGroupCharas();
}
base.DoCombatTick(tick, contentGroupCharas);
}
protected override void DoRoamTick(DateTime tick, List<Character> contentGroupCharas = null)
{
if (contentGroupCharas == null)
{
contentGroupCharas = GetContentGroupCharas();
}
base.DoRoamTick(tick, contentGroupCharas);
}
}
}

View file

@ -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<Character>(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<Character> 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<Character> 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<Character>(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<Character>(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<MagicState>())
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<Player>(owner, 50))
player.QueuePacket(owner.GetHateTypePacket(player));
base.ChangeTarget(target);
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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<Action> actionQueue;
private Queue<Action> timerQueue;
public bool IsEmpty { get { return actionQueue.Count > 0 || timerQueue.Count > 0; } }
public ActionQueue(Character owner)
{
this.owner = owner;
actionQueue = new Queue<Action>();
timerQueue = new Queue<Action>();
}
public void PushAction(Action action)
{
}
public void Update(DateTime tick)
{
}
public void HandleAction(Action action)
{
}
public void CheckAction(DateTime tick)
{
}
}
}

View file

@ -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<Vector3> 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<Vector3>(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;
}
}
}

View file

@ -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.)
}
/// <summary> Targeting from/to different entity types </summary>
enum TargetFindCharacterType : byte
{
None,
/// <summary> Player can target all <see cref="Player">s in party </summary>
PlayerToPlayer,
/// <summary> Player can target all <see cref="BattleNpc"/>s (excluding player owned <see cref="Pet"/>s) </summary>
PlayerToBattleNpc,
/// <summary> BattleNpc can target other <see cref="BattleNpc"/>s </summary>
BattleNpcToBattleNpc,
/// <summary> BattleNpc can target <see cref="Player"/>s and their <see cref="Pet"/>s </summary>
BattleNpcToPlayer,
}
/// <summary> Type of AOE region to create </summary>
enum TargetFindAOEType : byte
{
None,
/// <summary> Really a cylinder, uses maxDistance parameter in SetAOEType </summary>
Circle,
/// <summary> Create a cone with param in radians </summary>
Cone,
/// <summary> Box using self/target coords and </summary>
Box
}
/// <summary> Set AOE around self or target </summary>
enum TargetFindAOETarget : byte
{
/// <summary> Set AOE's origin at target's position </summary>
Target,
/// <summary> Set AOE's origin to own position. </summary>
Self
}
/// <summary> Target finding helper class </summary>
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<Character> 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<Character>();
}
public List<T> GetTargets<T>() where T : Character
{
return new List<T>(targets.OfType<T>());
}
public List<Character> GetTargets()
{
return targets;
}
/// <summary>
/// Call this before <see cref="FindWithinArea"/> <para/>
/// </summary>
/// <param name="maxDistance">
/// <see cref="TargetFindAOEType.Circle"/> - radius of circle <para/>
/// <see cref="TargetFindAOEType.Cone"/> - height of cone <para/>
/// <see cref="TargetFindAOEType.Box"/> - width of box / 2 (todo: set box length not just between user and target)
/// </param>
/// <param name="param"> param in degrees of cone (todo: probably use radians and forget converting at runtime) </param>
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;
}
/// <summary>
/// Call this to prepare Box AOE
/// </summary>
/// <param name="validTarget"></param>
/// <param name="aoeTarget"></param>
/// <param name="length"></param>
/// <param name="width"></param>
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;
}
/// <summary>
/// Find and try to add a single target to target list
/// </summary>
public void FindTarget(Character target, ValidTarget flags)
{
validTarget = flags;
// todo: maybe this should only be set if successfully added?
AddTarget(target, false);
}
/// <summary>
/// <para> Call SetAOEType before calling this </para>
/// Find targets within area set by <see cref="SetAOEType"/>
/// </summary>
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);
}
/// <summary>
/// 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
/// </summary>
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<Character>(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<BattleNpc>(target, dist);
foreach (BattleNpc actor in actors)
{
AddTarget(actor, false);
}
}
private void AddAllInZone(Character target, bool withPet)
{
var actors = owner.zone.GetAllActors<Character>();
foreach (Character actor in actors)
{
AddTarget(actor, withPet);
}
}
private void AddAllInRange(Character target, bool withPet)
{
int dist = (int)maxDistance;
var actors = owner.zone.GetActorsAroundActor<Character>(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<PetController>();
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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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<Character>(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<BattleAction> actions = new List<BattleAction>();
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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<StatusEffect> 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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<HitType, ushort> PhysicalHitTypeTextIds = new Dictionary<HitType, ushort>()
{
{ HitType.Miss, 30311 },
{ HitType.Evade, 30310 },
{ HitType.Parry, 30308 },
{ HitType.Block, 30306 },
{ HitType.Hit, 30301 },
{ HitType.Crit, 30302 }
};
public static Dictionary<HitType, ushort> MagicalHitTypeTextIds = new Dictionary<HitType, ushort>()
{
{ HitType.SingleResist,30318 },
{ HitType.DoubleResist,30317 },
{ HitType.TripleResist, 30316 },//Triple Resists seem to use the same text ID as full resists
{ HitType.FullResist,30316 },
{ HitType.Hit, 30319 },
{ HitType.Crit, 30392 } //Unsure why crit is separated from the rest of the ids
};
public static Dictionary<HitType, ushort> MultiHitTypeTextIds = new Dictionary<HitType, ushort>()
{
{ HitType.Miss, 30449 }, //The attack misses.
{ HitType.Evade, 0 }, //Evades were removed before multi hit skills got their own messages, so this doesnt exist
{ HitType.Parry, 30448 }, //[Target] parries, taking x points of damage.
{ HitType.Block, 30447 }, //[Target] blocks, taking x points of damage.
{ HitType.Hit, 30443 }, //[Target] tales x points of damage
{ HitType.Crit, 30444 } //Critical! [Target] takes x points of damage.
};
public static Dictionary<HitType, HitEffect> HitTypeEffectsPhysical = new Dictionary<HitType, HitEffect>()
{
{ HitType.Miss, 0 },
{ HitType.Evade, HitEffect.Evade },
{ HitType.Parry, HitEffect.Parry },
{ HitType.Block, HitEffect.Block },
{ HitType.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<HitType, HitEffect> HitTypeEffectsMagical = new Dictionary<HitType, HitEffect>()
{
{ HitType.SingleResist, HitEffect.WeakResist },
{ HitType.DoubleResist, HitEffect.WeakResist },
{ HitType.TripleResist, HitEffect.WeakResist },
{ HitType.FullResist, HitEffect.FullResist },
{ HitType.Hit, HitEffect.NoResist },
{ HitType.Crit, HitEffect.Crit }
};
public static Dictionary<KnockbackType, HitEffect> KnockbackEffects = new Dictionary<KnockbackType, HitEffect>()
{
{ 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<byte, ushort> ClassExperienceTextIds = new Dictionary<byte, ushort>()
{
{ 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<HitType, ushort> textIds = PhysicalHitTypeTextIds;
//If this is the first hit of a multi hit command, add the "You use [command] on [target]" action
//Needs to be done here because certain buff messages appear before it.
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
}
}

View file

@ -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;
}
}
}

View file

@ -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<uint, BattleCommand> skillList = new Dictionary<uint, BattleCommand>();
public Dictionary<uint, BattleCommand> spellList = new Dictionary<uint, BattleCommand>();
public uint poolId, genusId;
public ModifierList poolMods;
public ModifierList genusMods;
public ModifierList spawnMods;
protected Dictionary<MobModifier, Int64> mobModifiers = new Dictionary<MobModifier, Int64>();
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<SubPacket> GetSpawnPackets(Player player, ushort spawnType)
{
List<SubPacket> subpackets = new List<SubPacket>();
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<SubPacket> packets = null)
{
// todo: should probably add another flag for battleTemp since all this uses reflection
packets = new List<SubPacket>();
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;
}
///<summary> // todo: create an action object? </summary>
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<PetController>() != null && lastAttacker.aiContainer.GetController<PetController>().GetPetMaster() is Player)
{
lastAttacker = lastAttacker.aiContainer.GetController<PetController>().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
// <actor> defeat/defeats <target>
if (actionContainer != null)
actionContainer.AddEXPAction(new CommandResult(actorId, 30108, 0));
if (lastAttacker.currentParty != null && lastAttacker.currentParty is Party)
{
foreach (var memberId in ((Party)lastAttacker.currentParty).members)
{
var partyMember = zone.FindActorInArea<Character>(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<Character>(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<Character>(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<Character>(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);
}
}
}

View file

@ -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
}
}

View file

@ -17,16 +17,31 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using FFXIVClassic_Map_Server.actors.chara.ai;
namespace FFXIVClassic_Map_Server.Actors namespace FFXIVClassic_Map_Server.Actors
{ {
[Flags]
enum NpcSpawnType : ushort
{
Normal = 0x00,
Scripted = 0x01,
Nighttime = 0x02,
Evening = 0x04,
Daytime = 0x08,
Weather = 0x10,
}
class Npc : Character class Npc : Character
{ {
private uint actorClassId; private uint actorClassId;
private string uniqueIdentifier; private string uniqueIdentifier;
private uint regionId, layoutId;
private bool isMapObj = false;
private uint layout, instance;
public NpcWork npcWork = new NpcWork(); 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) 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)) : base((4 << 28 | spawnedArea.actorId << 19 | (uint)actorNumber))
@ -48,31 +63,49 @@ namespace FFXIVClassic_Map_Server.Actors
this.actorClassId = actorClass.actorClassId; this.actorClassId = actorClass.actorClassId;
this.currentSubState.motionPack = (ushort) animationId;
LoadNpcAppearance(actorClass.actorClassId); LoadNpcAppearance(actorClass.actorClassId);
this.classPath = actorClass.classPath; className = actorClass.classPath.Substring(actorClass.classPath.LastIndexOf("/") + 1);
className = classPath.Substring(classPath.LastIndexOf("/")+1); this.classPath = String.Format("{0}/{1}", actorClass.classPath.Substring(0, actorClass.classPath.LastIndexOf('/')).ToLower(), className);
charaWork.battleSave.potencial = 1.0f; charaWork.battleSave.potencial = 1.0f;
charaWork.parameterSave.state_mainSkill[0] = 3; // todo: these really need to be read from db etc
charaWork.parameterSave.state_mainSkill[2] = 3; {
charaWork.parameterSave.state_mainSkillLevel = 2; charaWork.parameterSave.state_mainSkill[0] = 3;
charaWork.parameterSave.state_mainSkill[2] = 3;
charaWork.parameterSave.state_mainSkillLevel = 1;
charaWork.parameterSave.hp[0] = 500; charaWork.parameterSave.hp[0] = 80;
charaWork.parameterSave.hpMax[0] = 500; charaWork.parameterSave.hpMax[0] = 80;
}
for (int i = 0; i < 32; i++ ) for (int i = 0; i < 32; i++ )
charaWork.property[i] = (byte)(((int)actorClass.propertyFlags >> i) & 1); charaWork.property[i] = (byte)(((int)actorClass.propertyFlags >> i) & 1);
npcWork.pushCommand = actorClass.pushCommand; npcWork.pushCommand = actorClass.pushCommand;
npcWork.pushCommandSub = actorClass.pushCommandSub; npcWork.pushCommandSub = actorClass.pushCommandSub;
npcWork.pushCommandPriority = actorClass.pushCommandPriority; 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<LuaParam> 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)) : base((4 << 28 | spawnedArea.actorId << 19 | (uint)actorNumber))
{ {
this.positionX = posX; this.positionX = posX;
@ -103,58 +136,42 @@ namespace FFXIVClassic_Map_Server.Actors
npcWork.pushCommandSub = actorClass.pushCommandSub; npcWork.pushCommandSub = actorClass.pushCommandSub;
npcWork.pushCommandPriority = actorClass.pushCommandPriority; npcWork.pushCommandPriority = actorClass.pushCommandPriority;
this.regionId = region; this.isMapObj = true;
this.layoutId = layout; this.layout = layout;
this.instance = instance;
GenerateActorName((int)actorNumber); 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 // actorClassId, [], [], numBattleCommon, [battleCommon], numEventCommon, [eventCommon], args for either initForBattle/initForEvent
public override SubPacket CreateScriptBindPacket(uint playerActorId) public override SubPacket CreateScriptBindPacket(Player player)
{ {
List<LuaParam> lParams; List<LuaParam> lParams;
Player player = Server.GetWorldManager().GetPCInWorld(playerActorId);
lParams = LuaEngine.GetInstance().CallLuaFunctionForReturn(player, this, "init", false); 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) if (lParams != null && lParams.Count >= 3 && lParams[2].typeID == 0 && (int)lParams[2].value == 0)
isStatic = true; isStatic = true;
else else
{ {
// charaWork.property[2] = 1; //charaWork.property[2] = 1;
// npcWork.hateType = 1; //npcWork.hateType = 1;
} }
if (regionId != 0 && layoutId != 0) if (lParams == null)
{
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)
{ {
string classPathFake = "/Chara/Npc/Populace/PopulaceStandard"; string classPathFake = "/Chara/Npc/Populace/PopulaceStandard";
string classNameFake = "PopulaceStandard"; string classNameFake = "PopulaceStandard";
lParams = LuaUtils.CreateLuaParamList(classPathFake, false, false, false, false, false, 0xF47F6, false, false, 0, 0); lParams = LuaUtils.CreateLuaParamList(classPathFake, false, false, false, false, false, 0xF47F6, false, false, 0, 0);
isStatic = true; isStatic = true;
//ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, classNameFake, lParams).DebugPrintSubPacket(); //ActorInstantiatePacket.BuildPacket(actorId, actorName, classNameFake, lParams).DebugPrintSubPacket();
return ActorInstantiatePacket.BuildPacket(actorId, playerActorId, actorName, classNameFake, lParams); return ActorInstantiatePacket.BuildPacket(actorId, actorName, classNameFake, lParams);
} }
else else
{ {
@ -167,95 +184,37 @@ namespace FFXIVClassic_Map_Server.Actors
lParams.Insert(6, new LuaParam(0, (int)actorClassId)); lParams.Insert(6, new LuaParam(0, (int)actorClassId));
} }
//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<SubPacket> GetSpawnPackets(Player player, ushort spawnType)
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
subpackets.Add(CreateAddActorPacket(playerActorId)); subpackets.Add(CreateAddActorPacket());
subpackets.AddRange(GetEventConditionPackets(playerActorId)); subpackets.AddRange(GetEventConditionPackets());
subpackets.Add(CreateSpeedPacket(playerActorId)); subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x0)); subpackets.Add(CreateSpawnPositonPacket(0x0));
if (regionId != 0 && layoutId != 0) if (isMapObj)
{ subpackets.Add(SetActorBGPropertiesPacket.BuildPacket(actorId, instance, layout));
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));
}
else else
subpackets.Add(CreateAppearancePacket(playerActorId)); subpackets.Add(CreateAppearancePacket());
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));
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<SubPacket> GetInitPackets()
{ {
ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("/_init", this, playerActorId); ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("/_init", this);
//Potential //Potential
propPacketUtil.AddProperty("charaWork.battleSave.potencial"); propPacketUtil.AddProperty("charaWork.battleSave.potencial");
@ -288,7 +247,7 @@ namespace FFXIVClassic_Map_Server.Actors
//Status Times //Status Times
for (int i = 0; i < charaWork.statusShownTime.Length; i++) 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)); propPacketUtil.AddProperty(String.Format("charaWork.statusShownTime[{0}]", i));
} }
@ -309,7 +268,7 @@ namespace FFXIVClassic_Map_Server.Actors
propPacketUtil.AddProperty("npcWork.pushCommandPriority"); propPacketUtil.AddProperty("npcWork.pushCommandPriority");
} }
return BasePacket.CreatePacket(propPacketUtil.Done(), true, false); return propPacketUtil.Done();
} }
public string GetUniqueId() public string GetUniqueId()
@ -325,7 +284,7 @@ namespace FFXIVClassic_Map_Server.Actors
public void ChangeNpcAppearance(uint id) public void ChangeNpcAppearance(uint id)
{ {
LoadNpcAppearance(id); LoadNpcAppearance(id);
zone.BroadcastPacketAroundActor(this, CreateAppearancePacket(actorId)); zone.BroadcastPacketAroundActor(this, CreateAppearancePacket());
} }
public void LoadNpcAppearance(uint id) public void LoadNpcAppearance(uint id)
@ -443,21 +402,54 @@ namespace FFXIVClassic_Map_Server.Actors
public void PlayMapObjAnimation(Player player, string animationName) 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<SubPacket> packets = null)
{
packets = packets ?? new List<SubPacket>();
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 //A party member list packet came, set the party
/* public void SetParty(MonsterPartyGroup group) /* public void SetParty(MonsterPartyGroup group)
{ {
if (group is MonsterPartyGroup) if (group is MonsterPartyGroup)
currentParty = group; currentParty = group;
} }
*/ */
} }
} }

View file

@ -2,6 +2,10 @@
{ {
class NpcWork 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 ushort pushCommand;
public int pushCommandSub; public int pushCommandSub;
public byte pushCommandPriority; public byte pushCommandPriority;

View file

@ -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);
}
}
}

View file

@ -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<ushort, Inventory> inventories = new Dictionary<ushort, Inventory>();
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;
}
}
}

View file

@ -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; int currentIndex = 0;
while (true) while (true)
{ {
if (items.Count - currentIndex >= 16) 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) 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) 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++; currentIndex++;
} }
else else
break; break;
} }
toPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId, toPlayer.actorId)); toPlayer.QueuePacket(InventorySetEndPacket.BuildPacket(owner.actorId));
} }
public void SendFullEquipment(bool DoClear) public void SendFullEquipment(bool DoClear)
@ -106,13 +106,13 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
for (int i = 0; i < slots.Length; i++) for (int i = 0; i < slots.Length; i++)
{ {
InventoryItem item = normalInventory.GetItemBySlot(itemSlots[i]); InventoryItem item = normalInventory.GetItemAtSlot(itemSlots[i]);
if (item == null) if (item == null)
continue; continue;
Database.EquipItem(owner, slots[i], item.uniqueId); 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)); owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId));
@ -133,7 +133,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
public void Equip(ushort slot, ushort invSlot) public void Equip(ushort slot, ushort invSlot)
{ {
InventoryItem item = normalInventory.GetItemBySlot(invSlot); InventoryItem item = normalInventory.GetItemAtSlot(invSlot);
if (item == null) if (item == null)
return; return;
@ -152,9 +152,9 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId));
if (list[slot] != null) if (list[slot] != null)
normalInventory.RefreshItem(list[slot], item); normalInventory.RefreshItem(owner, list[slot], item);
else else
normalInventory.RefreshItem(item); normalInventory.RefreshItem(owner, item);
owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode));
SendEquipmentPackets(slot, item); SendEquipmentPackets(slot, item);
@ -163,6 +163,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId));
list[slot] = item; list[slot] = item;
owner.CalculateBaseStats();// RecalculateStats();
} }
public void ToggleDBWrite(bool flag) public void ToggleDBWrite(bool flag)
@ -180,7 +181,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId)); owner.QueuePacket(InventoryBeginChangePacket.BuildPacket(owner.actorId));
normalInventory.RefreshItem(list[slot]); normalInventory.RefreshItem(owner, list[slot]);
owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode)); owner.QueuePacket(InventorySetBeginPacket.BuildPacket(owner.actorId, inventoryCapacity, inventoryCode));
SendEquipmentPackets(slot, null); SendEquipmentPackets(slot, null);
@ -189,6 +190,7 @@ namespace FFXIVClassic_Map_Server.actors.chara.player
owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId)); owner.QueuePacket(InventoryEndChangePacket.BuildPacket(owner.actorId));
list[slot] = null; list[slot] = null;
owner.RecalculateStats();
} }
private void SendEquipmentPackets(ushort equipSlot, InventoryItem item) private void SendEquipmentPackets(ushort equipSlot, InventoryItem item)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -18,8 +18,8 @@
public bool isContentsCommand; public bool isContentsCommand;
public int castCommandClient; public uint castCommandClient;
public int castEndClient; public uint castEndClient;
public int[] comboNextCommandId = new int[2]; public int[] comboNextCommandId = new int[2];
public float comboCostBonusRate; public float comboCostBonusRate;

View file

@ -18,24 +18,24 @@ namespace FFXIVClassic_Map_Server.Actors
this.className = "Debug"; this.className = "Debug";
} }
public override SubPacket CreateScriptBindPacket(uint playerActorId) public override SubPacket CreateScriptBindPacket()
{ {
List<LuaParam> lParams; List<LuaParam> lParams;
lParams = LuaUtils.CreateLuaParamList("/System/Debug.prog", false, false, false, false, true, 0xC51F, true, true); 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<SubPacket> GetSpawnPackets()
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
subpackets.Add(CreateAddActorPacket(playerActorId, 0)); subpackets.Add(CreateAddActorPacket(0));
subpackets.Add(CreateSpeedPacket(playerActorId)); subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x1)); subpackets.Add(CreateSpawnPositonPacket(0x1));
subpackets.Add(CreateNamePacket(playerActorId)); subpackets.Add(CreateNamePacket());
subpackets.Add(CreateStatePacket(playerActorId)); subpackets.Add(CreateStatePacket());
subpackets.Add(CreateIsZoneingPacket(playerActorId)); subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket(playerActorId)); subpackets.Add(CreateScriptBindPacket());
return BasePacket.CreatePacket(subpackets, true, false); return subpackets;
} }
} }

View file

@ -1,6 +1,7 @@
 
using FFXIVClassic.Common; using FFXIVClassic.Common;
using FFXIVClassic_Map_Server.actors.area; using FFXIVClassic_Map_Server.actors.area;
using FFXIVClassic_Map_Server.actors.group;
using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.Actors;
using FFXIVClassic_Map_Server.lua; using FFXIVClassic_Map_Server.lua;
using FFXIVClassic_Map_Server.packets.send.actor; using FFXIVClassic_Map_Server.packets.send.actor;
@ -15,25 +16,36 @@ namespace FFXIVClassic_Map_Server.actors.director
{ {
private uint directorId; private uint directorId;
private string directorScriptPath; private string directorScriptPath;
private List<Actor> childrenOwners = new List<Actor>(); private List<Actor> members = new List<Actor>();
protected ContentGroup contentGroup;
private bool isCreated = false; 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)) : base((6 << 28 | zone.actorId << 19 | (uint)id))
{ {
directorId = id; directorId = id;
this.zone = zone; this.zone = zone;
directorScriptPath = directorPath; this.zoneId = zone.actorId;
DoActorInit(directorScriptPath); directorScriptPath = directorPath;
GenerateActorName((int)id);
LoadLuaScript();
if (hasContentGroup)
contentGroup = Server.GetWorldManager().CreateContentGroup(this, GetMembers());
eventConditions = new EventList(); eventConditions = new EventList();
eventConditions.noticeEventConditions = new List<EventList.NoticeEventCondition>(); eventConditions.noticeEventConditions = new List<EventList.NoticeEventCondition>();
eventConditions.noticeEventConditions.Add(new EventList.NoticeEventCondition("noticeEvent", 0xE,0x0)); 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<LuaParam> actualLParams = new List<LuaParam>(); List<LuaParam> actualLParams = new List<LuaParam>();
actualLParams.Insert(0, new LuaParam(2, classPath)); 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(3, new LuaParam(4, 4));
actualLParams.Insert(4, new LuaParam(4, 4)); actualLParams.Insert(4, new LuaParam(4, 4));
actualLParams.Insert(5, 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<LuaParam> 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<SubPacket> GetSpawnPackets(ushort spawnType = 1)
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
subpackets.Add(CreateAddActorPacket(playerActorId, 0)); subpackets.Add(CreateAddActorPacket(0));
subpackets.AddRange(GetEventConditionPackets(playerActorId)); subpackets.AddRange(GetEventConditionPackets());
subpackets.Add(CreateSpeedPacket(playerActorId)); subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0)); subpackets.Add(CreateSpawnPositonPacket(0));
subpackets.Add(CreateNamePacket(playerActorId)); subpackets.Add(CreateNamePacket());
subpackets.Add(CreateStatePacket(playerActorId)); subpackets.Add(CreateStatePacket());
subpackets.Add(CreateIsZoneingPacket(playerActorId)); subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket(playerActorId)); subpackets.Add(CreateScriptBindPacket());
return BasePacket.CreatePacket(subpackets, true, false); return subpackets;
} }
public override BasePacket GetInitPackets(uint playerActorId) public override List<SubPacket> GetInitPackets()
{ {
List<SubPacket> subpackets = new List<SubPacket>();
SetActorPropetyPacket initProperties = new SetActorPropetyPacket("/_init"); SetActorPropetyPacket initProperties = new SetActorPropetyPacket("/_init");
initProperties.AddTarget(); 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) public void OnTalkEvent(Player player, Npc npc)
@ -76,38 +93,105 @@ namespace FFXIVClassic_Map_Server.actors.director
public void OnCommandEvent(Player player, Command command) public void OnCommandEvent(Player player, Command command)
{ {
LuaEngine.GetInstance().CallLuaFunction(player, this, "onCommandEvent", false, command); LuaEngine.GetInstance().CallLuaFunction(player, this, "onCommandEvent", false, command);
} }
public void DoActorInit(string directorPath) public void StartDirector(bool spawnImmediate, params object[] args)
{ {
List<LuaParam> 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<LuaParam> 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; classPath = (string)lparams[0].value;
className = classPath.Substring(classPath.LastIndexOf("/") + 1); className = classPath.Substring(classPath.LastIndexOf("/") + 1);
GenerateActorName((int)directorId);
isCreated = true; 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<Actor> 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)) if (members.Contains(actor))
childrenOwners.Add(actor); members.Remove(actor);
if (contentGroup != null)
contentGroup.RemoveMember(actor.actorId);
if (GetPlayerMembers().Count == 0 && !isDeleting)
EndDirector();
} }
public void RemoveChild(Actor actor) public List<Actor> GetMembers()
{ {
if (childrenOwners.Contains(actor)) return members;
childrenOwners.Remove(actor);
if (childrenOwners.Count == 0)
Server.GetWorldManager().GetZone(zoneId).DeleteDirector(actorId);
} }
public void RemoveChildren() public List<Actor> GetPlayerMembers()
{ {
childrenOwners.Clear(); return members.FindAll(s => s is Player);
Server.GetWorldManager().GetZone(zoneId).DeleteDirector(actorId); }
public List<Actor> GetNpcMembers()
{
return members.FindAll(s => s is Npc);
} }
public bool IsCreated() public bool IsCreated()
@ -115,6 +199,21 @@ namespace FFXIVClassic_Map_Server.actors.director
return isCreated; return isCreated;
} }
public bool IsDeleted()
{
return isDeleted;
}
public bool HasContentGroup()
{
return contentGroup != null;
}
public ContentGroup GetContentGroup()
{
return contentGroup;
}
public void GenerateActorName(int actorNumber) public void GenerateActorName(int actorNumber)
{ {
//Format Class Name //Format Class Name
@ -142,7 +241,7 @@ namespace FFXIVClassic_Map_Server.actors.director
{ {
className = className.Substring(0, 20 - zoneName.Length); className = className.Substring(0, 20 - zoneName.Length);
} }
catch (ArgumentOutOfRangeException e) catch (ArgumentOutOfRangeException)
{ } { }
//Convert actor number to base 63 //Convert actor number to base 63
@ -162,5 +261,62 @@ namespace FFXIVClassic_Map_Server.actors.director
return directorScriptPath; 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<LuaParam> 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<LuaParam> lparams = LuaUtils.CreateLuaParamList(result);
return lparams;
}
else
Program.Log.Error("Could not find script for director {0}.", GetName());
}
return null;
}
private List<LuaParam> 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);
}
} }
} }

View file

@ -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<SubPacket> packets)
{
List<Actor> 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;
}
}
}

View file

@ -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;
}
}

View file

@ -19,37 +19,53 @@ namespace FFXIVClassic_Map_Server.actors.group
public ContentGroupWork contentGroupWork = new ContentGroupWork(); public ContentGroupWork contentGroupWork = new ContentGroupWork();
private Director director; private Director director;
private List<uint> members = new List<uint>(); private List<uint> members = new List<uint>();
private bool isStarted = false;
public ContentGroup(ulong groupIndex, Director director, uint[] initialMembers) : base(groupIndex) public ContentGroup(ulong groupIndex, Director director, uint[] initialMembers) : base(groupIndex)
{ {
if (initialMembers != null) if (initialMembers != null)
{ {
for (int i = 0; i < initialMembers.Length; i++) 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]); members.Add(initialMembers[i]);
}
} }
this.director = director; this.director = director;
contentGroupWork._globalTemp.director = (ulong)director.actorId << 32; contentGroupWork._globalTemp.director = (ulong)director.actorId << 32;
} }
public void Start()
{
isStarted = true;
SendGroupPacketsAll(members);
}
public void AddMember(Actor actor) public void AddMember(Actor actor)
{ {
if (actor == null) if (actor == null)
return; 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); ((Character)actor).SetCurrentContentGroup(this);
SendCurrentContentSync(actor);
} if (isStarted)
SendGroupPacketsAll(members); SendGroupPacketsAll(members);
} }
public void RemoveMember(uint memberId) public void RemoveMember(uint memberId)
{ {
members.Remove(memberId); members.Remove(memberId);
SendGroupPacketsAll(members); if (isStarted)
SendGroupPacketsAll(members);
CheckDestroy(); CheckDestroy();
} }
@ -77,9 +93,9 @@ namespace FFXIVClassic_Map_Server.actors.group
groupWork.addByte(Utils.MurmurHash2("contentGroupWork.property[0]", 0), 1); groupWork.addByte(Utils.MurmurHash2("contentGroupWork.property[0]", 0), 1);
groupWork.setTarget("/_init"); groupWork.setTarget("/_init");
SubPacket test = groupWork.buildPacket(session.id, session.id); SubPacket test = groupWork.buildPacket(session.id);
test.DebugPrintSubPacket(); test.DebugPrintSubPacket();
session.QueuePacket(test, true, false); session.QueuePacket(test);
} }
public override void SendGroupPackets(Session session) public override void SendGroupPackets(Session session)
@ -87,41 +103,26 @@ namespace FFXIVClassic_Map_Server.actors.group
ulong time = Utils.MilisUnixTimeStampUTC(); ulong time = Utils.MilisUnixTimeStampUTC();
List<GroupMember> members = BuildMemberList(session.id); List<GroupMember> members = BuildMemberList(session.id);
session.QueuePacket(GroupHeaderPacket.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), true, false); session.QueuePacket(GroupMembersBeginPacket.buildPacket(session.id, session.GetActor().zoneId, time, this));
int currentIndex = 0; int currentIndex = 0;
while (true) while (true)
{ {
if (GetMemberCount() - currentIndex >= 64) 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) 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) 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) 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 else
break; 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 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());
}
}
} }
public override uint GetTypeId() public override uint GetTypeId()
@ -135,12 +136,23 @@ namespace FFXIVClassic_Map_Server.actors.group
SendGroupPacketsAll(members); SendGroupPacketsAll(members);
} }
public void DeleteAll() public void DeleteGroup()
{ {
SendDeletePackets(members); 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() public void CheckDestroy()
{ {
bool foundSession = false; bool foundSession = false;
@ -155,8 +167,12 @@ namespace FFXIVClassic_Map_Server.actors.group
} }
if (!foundSession) if (!foundSession)
Server.GetWorldManager().DeleteContentGroup(groupIndex); DeleteGroup();
} }
public List<uint> GetMembers()
{
return members;
}
} }
} }

View file

@ -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;
}
}
}

View file

@ -120,33 +120,34 @@ namespace FFXIVClassic_Map_Server.actors.group
ulong time = Utils.MilisUnixTimeStampUTC(); ulong time = Utils.MilisUnixTimeStampUTC();
List<GroupMember> members = BuildMemberList(session.id); List<GroupMember> members = BuildMemberList(session.id);
session.QueuePacket(GroupHeaderPacket.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), true, false); session.QueuePacket(GroupMembersBeginPacket.buildPacket(session.id, session.GetActor().zoneId, time, this));
int currentIndex = 0; int currentIndex = 0;
while (true) while (true)
{ {
if (GetMemberCount() - currentIndex >= 64) int memberCount = Math.Min(GetMemberCount(), members.Count);
session.QueuePacket(GroupMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); if (memberCount - currentIndex >= 64)
else if (GetMemberCount() - currentIndex >= 32) session.QueuePacket(GroupMembersX64Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
session.QueuePacket(GroupMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); else if (memberCount - currentIndex >= 32)
else if (GetMemberCount() - currentIndex >= 16) session.QueuePacket(GroupMembersX32Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
session.QueuePacket(GroupMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); else if (memberCount - currentIndex >= 16)
else if (GetMemberCount() - currentIndex > 0) session.QueuePacket(GroupMembersX16Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
session.QueuePacket(GroupMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex), true, false); else if (memberCount - currentIndex > 0)
session.QueuePacket(GroupMembersX08Packet.buildPacket(session.id, session.GetActor().zoneId, time, members, ref currentIndex));
else else
break; 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) public void SendDeletePacket(Session session)
{ {
if (session != null) 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) public virtual void SendInitWorkValues(Session session)

View file

@ -17,8 +17,9 @@ namespace FFXIVClassic_Map_Server.actors.group
public MonsterParty(ulong groupIndex, uint[] initialMonsterMembers) public MonsterParty(ulong groupIndex, uint[] initialMonsterMembers)
: base(groupIndex) : base(groupIndex)
{ {
for (int i = 0; i < initialMonsterMembers.Length; i++) if(initialMonsterMembers != null)
monsterMembers.Add(initialMonsterMembers[i]); for (int i = 0; i < initialMonsterMembers.Length; i++)
monsterMembers.Add(initialMonsterMembers[i]);
} }
public void AddMember(uint memberId) public void AddMember(uint memberId)
@ -47,11 +48,11 @@ namespace FFXIVClassic_Map_Server.actors.group
public override void SendInitWorkValues(Session session) public override void SendInitWorkValues(Session session)
{ {
SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex); SynchGroupWorkValuesPacket groupWork = new SynchGroupWorkValuesPacket(groupIndex);
groupWork.setTarget("/_init"); groupWork.setTarget("/_init");
SubPacket test = groupWork.buildPacket(session.id, session.id); SubPacket test = groupWork.buildPacket(session.id);
session.QueuePacket(test, true, false); session.QueuePacket(test);
} }
public override uint GetTypeId() public override uint GetTypeId()

View file

@ -63,12 +63,26 @@ namespace FFXIVClassic_Map_Server.actors.group
List<GroupMember> groupMembers = new List<GroupMember>(); List<GroupMember> groupMembers = new List<GroupMember>();
groupMembers.Add(new GroupMember(id, -1, 0, false, true, Server.GetWorldManager().GetActorInWorld(id).customDisplayName)); groupMembers.Add(new GroupMember(id, -1, 0, false, true, Server.GetWorldManager().GetActorInWorld(id).customDisplayName));
foreach (uint charaId in members) foreach (uint charaId in members)
{ {
if (charaId != id) var chara = Server.GetWorldManager().GetActorInWorld(charaId);
groupMembers.Add(new GroupMember(charaId, -1, 0, false, true, Server.GetWorldManager().GetActorInWorld(charaId).customDisplayName)); if (charaId != id && chara != null)
groupMembers.Add(new GroupMember(charaId, -1, 0, false, true, chara.customDisplayName));
} }
return groupMembers; 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);
}
} }
} }

View file

@ -68,9 +68,9 @@ namespace FFXIVClassic_Map_Server.actors.group
groupWork.addProperty(this, "work._globalTemp.variableCommand"); groupWork.addProperty(this, "work._globalTemp.variableCommand");
groupWork.setTarget("/_init"); groupWork.setTarget("/_init");
SubPacket test = groupWork.buildPacket(session.id, session.id); SubPacket test = groupWork.buildPacket(session.id);
test.DebugPrintSubPacket(); test.DebugPrintSubPacket();
session.QueuePacket(test, true, false); session.QueuePacket(test);
} }
} }

View file

@ -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<GroupMember> BuildMemberList(uint id)
{
List<GroupMember> groupMembers = new List<GroupMember>();
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);
}
}
}

View file

@ -93,7 +93,7 @@ namespace FFXIVClassic_Map_Server.Actors
return false; return false;
} }
else else
return (questFlags & (1 << bitIndex)) == (1 << bitIndex); return (questFlags & (1 << bitIndex)) == (1 << bitIndex);
} }
public uint GetPhase() public uint GetPhase()

View file

@ -17,24 +17,24 @@ namespace FFXIVClassic_Map_Server.Actors
this.className = "WorldMaster"; this.className = "WorldMaster";
} }
public override SubPacket CreateScriptBindPacket(uint playerActorId) public override SubPacket CreateScriptBindPacket()
{ {
List<LuaParam> lParams; List<LuaParam> lParams;
lParams = LuaUtils.CreateLuaParamList("/World/WorldMaster_event", false, false, false, false, false, null); 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<SubPacket> GetSpawnPackets()
{ {
List<SubPacket> subpackets = new List<SubPacket>(); List<SubPacket> subpackets = new List<SubPacket>();
subpackets.Add(CreateAddActorPacket(playerActorId, 0)); subpackets.Add(CreateAddActorPacket(0));
subpackets.Add(CreateSpeedPacket(playerActorId)); subpackets.Add(CreateSpeedPacket());
subpackets.Add(CreateSpawnPositonPacket(playerActorId, 0x1)); subpackets.Add(CreateSpawnPositonPacket(0x1));
subpackets.Add(CreateNamePacket(playerActorId)); subpackets.Add(CreateNamePacket());
subpackets.Add(CreateStatePacket(playerActorId)); subpackets.Add(CreateStatePacket());
subpackets.Add(CreateIsZoneingPacket(playerActorId)); subpackets.Add(CreateIsZoneingPacket());
subpackets.Add(CreateScriptBindPacket(playerActorId)); subpackets.Add(CreateScriptBindPacket());
return BasePacket.CreatePacket(subpackets, true, false); return subpackets;
} }
} }
} }

View file

@ -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");
}
}
}

View file

@ -23,14 +23,13 @@ namespace FFXIVClassic_Map_Server.dataobjects
public byte materia5 = 0; public byte materia5 = 0;
//Bare Minimum //Bare Minimum
public InventoryItem(uint id, uint itemId, ushort slot) public InventoryItem(uint id, uint itemId)
{ {
this.uniqueId = id; this.uniqueId = id;
this.itemId = itemId; this.itemId = itemId;
this.quantity = 1; this.quantity = 1;
this.slot = slot;
Item gItem = Server.GetItemGamedata(itemId); ItemData gItem = Server.GetItemGamedata(itemId);
itemType = gItem.isExclusive ? (byte)0x3 : (byte)0x0; itemType = gItem.isExclusive ? (byte)0x3 : (byte)0x0;
} }
@ -55,12 +54,11 @@ namespace FFXIVClassic_Map_Server.dataobjects
this.materia5 = item.materia5; 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.uniqueId = uniqueId;
this.itemId = itemId; this.itemId = itemId;
this.quantity = quantity; this.quantity = quantity;
this.slot = slot;
this.itemType = itemType; this.itemType = itemType;
this.quality = qualityNumber; this.quality = qualityNumber;
this.durability = durability; this.durability = durability;

View file

@ -3,7 +3,7 @@ using System;
namespace FFXIVClassic_Map_Server.dataobjects namespace FFXIVClassic_Map_Server.dataobjects
{ {
class Item class ItemData
{ {
//Basic //Basic
public readonly uint catalogID; public readonly uint catalogID;
@ -39,7 +39,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
public readonly int repairLevel; public readonly int repairLevel;
public readonly int repairLicense; public readonly int repairLicense;
public Item(MySqlDataReader reader) public ItemData(MySqlDataReader reader)
{ {
catalogID = reader.GetUInt32("catalogID"); catalogID = reader.GetUInt32("catalogID");
name = reader.GetString("name"); name = reader.GetString("name");
@ -50,6 +50,8 @@ namespace FFXIVClassic_Map_Server.dataobjects
isExclusive = reader.GetBoolean("isExclusive"); isExclusive = reader.GetBoolean("isExclusive");
durability = reader.GetInt32("durability"); durability = reader.GetInt32("durability");
sellPrice = reader.GetInt32("sellPrice");
icon = reader.GetInt32("icon"); icon = reader.GetInt32("icon");
kind = reader.GetInt32("kind"); kind = reader.GetInt32("kind");
rarity = reader.GetInt32("rarity"); rarity = reader.GetInt32("rarity");
@ -387,7 +389,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
} }
class EquipmentItem : Item class EquipmentItem : ItemData
{ {
//graphics //graphics
public readonly uint graphicsWeaponId; public readonly uint graphicsWeaponId;
@ -467,6 +469,11 @@ namespace FFXIVClassic_Map_Server.dataobjects
class WeaponItem : EquipmentItem class WeaponItem : EquipmentItem
{ {
//extra graphics
public readonly uint graphicsOffhandWeaponId;
public readonly uint graphicsOffhandEquipmentId;
public readonly uint graphicsOffhandVariantId;
//weapon sheet //weapon sheet
public readonly short attack; public readonly short attack;
public readonly short magicAttack; public readonly short magicAttack;
@ -474,7 +481,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
public readonly short craftMagicProcessing; public readonly short craftMagicProcessing;
public readonly short harvestPotency; public readonly short harvestPotency;
public readonly short harvestLimit; 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 rate;
public readonly short magicRate; public readonly short magicRate;
public readonly short craftProcessControl; public readonly short craftProcessControl;
@ -483,7 +490,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
public readonly short magicCritical; public readonly short magicCritical;
public readonly short parry; 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 float damageAttributeValue1;
public readonly int damageAttributeType2; public readonly int damageAttributeType2;
public readonly float damageAttributeValue2; public readonly float damageAttributeValue2;
@ -493,10 +500,18 @@ namespace FFXIVClassic_Map_Server.dataobjects
public readonly short damagePower; public readonly short damagePower;
public readonly float damageInterval; public readonly float damageInterval;
public readonly short ammoVirtualDamagePower; public readonly short ammoVirtualDamagePower;
public readonly float dps;
public WeaponItem(MySqlDataReader reader) public WeaponItem(MySqlDataReader reader)
: base(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"); attack = reader.GetInt16("attack");
magicAttack = reader.GetInt16("magicAttack"); magicAttack = reader.GetInt16("magicAttack");
craftProcessing = reader.GetInt16("craftProcessing"); craftProcessing = reader.GetInt16("craftProcessing");
@ -522,6 +537,7 @@ namespace FFXIVClassic_Map_Server.dataobjects
damagePower = reader.GetInt16("damagePower"); damagePower = reader.GetInt16("damagePower");
damageInterval = reader.GetFloat("damageInterval"); damageInterval = reader.GetFloat("damageInterval");
ammoVirtualDamagePower = reader.GetInt16("ammoVirtualDamagePower"); ammoVirtualDamagePower = reader.GetInt16("ammoVirtualDamagePower");
dps = (damagePower + ammoVirtualDamagePower) / damageInterval;
} }
} }

View file

@ -1,14 +1,9 @@
using FFXIVClassic_Map_Server; using FFXIVClassic.Common;
using FFXIVClassic.Common;
using FFXIVClassic_Map_Server.Actors; using FFXIVClassic_Map_Server.Actors;
using FFXIVClassic_Map_Server.lua;
using FFXIVClassic_Map_Server.packets.send.actor; using FFXIVClassic_Map_Server.packets.send.actor;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using FFXIVClassic_Map_Server.actors.chara.npc;
using System.Text;
using System.Threading.Tasks;
namespace FFXIVClassic_Map_Server.dataobjects namespace FFXIVClassic_Map_Server.dataobjects
{ {
@ -28,17 +23,18 @@ namespace FFXIVClassic_Map_Server.dataobjects
{ {
this.id = sessionId; this.id = sessionId;
playerActor = new Player(this, sessionId); playerActor = new Player(this, sessionId);
actorInstanceList.Add(playerActor);
} }
public void QueuePacket(BasePacket basePacket) public void QueuePacket(List<SubPacket> 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() public Player GetActor()
@ -68,21 +64,26 @@ namespace FFXIVClassic_Map_Server.dataobjects
if (isUpdatesLocked) if (isUpdatesLocked)
return; return;
if (playerActor.positionX == x && playerActor.positionY == y && playerActor.positionZ == z && playerActor.rotation == rot)
return;
/*
playerActor.oldPositionX = playerActor.positionX; playerActor.oldPositionX = playerActor.positionX;
playerActor.oldPositionY = playerActor.positionY; playerActor.oldPositionY = playerActor.positionY;
playerActor.oldPositionZ = playerActor.positionZ; playerActor.oldPositionZ = playerActor.positionZ;
playerActor.oldRotation = playerActor.rotation; playerActor.oldRotation = playerActor.rotation;
playerActor.positionX = x; playerActor.positionX = x;
playerActor.positionY = y; playerActor.positionY = y;
playerActor.positionZ = z; playerActor.positionZ = z;
*/
playerActor.rotation = rot; playerActor.rotation = rot;
playerActor.moveState = moveState; 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<Actor> list) public void UpdateInstance(List<Actor> list)
{ {
if (isUpdatesLocked) if (isUpdatesLocked)
@ -95,29 +96,29 @@ namespace FFXIVClassic_Map_Server.dataobjects
//Remove missing actors //Remove missing actors
for (int i = 0; i < actorInstanceList.Count; i++) 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]; QueuePacket(RemoveActorPacket.BuildPacket(actorInstanceList[i].actorId));
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));
actorInstanceList.RemoveAt(i); 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 //Add new actors or move
@ -130,17 +131,14 @@ namespace FFXIVClassic_Map_Server.dataobjects
if (actorInstanceList.Contains(actor)) 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 else
{ {
GetActor().QueuePacket(actor.GetSpawnPackets(playerActor.actorId, 1)); QueuePacket(actor.GetSpawnPackets(playerActor, 1));
GetActor().QueuePacket(actor.GetInitPackets(playerActor.actorId));
GetActor().QueuePacket(actor.GetSetEventStatusPackets(playerActor.actorId)); QueuePacket(actor.GetInitPackets());
QueuePacket(actor.GetSetEventStatusPackets());
actorInstanceList.Add(actor); actorInstanceList.Add(actor);
if (actor is Npc) if (actor is Npc)

Some files were not shown because too many files have changed in this diff Show more