mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-04-20 11:47:48 +00:00
Merge branch 'ai-open' into develop
# Conflicts: # FFXIVClassic Lobby Server/Database.cs # FFXIVClassic Map Server/Database.cs # FFXIVClassic Map Server/FFXIVClassic Map Server.csproj # FFXIVClassic Map Server/actors/chara/player/Inventory.cs # FFXIVClassic Map Server/actors/chara/player/Player.cs # FFXIVClassic Map Server/dataobjects/Session.cs # FFXIVClassic World Server/Server.cs
This commit is contained in:
commit
1e4a1cf263
402 changed files with 20078 additions and 1348 deletions
|
@ -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" />
|
||||||
|
|
|
@ -84,10 +84,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 +95,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;
|
||||||
|
@ -351,5 +351,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
145
FFXIVClassic Common Class Lib/Vector3.cs
Normal file
145
FFXIVClassic Common Class Lib/Vector3.cs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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,49 +360,106 @@ 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;
|
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)))
|
||||||
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -431,6 +523,8 @@ 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)))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
conn.Open();
|
conn.Open();
|
||||||
|
|
||||||
|
@ -479,18 +573,102 @@ namespace FFXIVClassic_Lobby_Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
|
@ -501,79 +679,48 @@ namespace FFXIVClassic_Lobby_Server
|
||||||
{
|
{
|
||||||
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
|
|
||||||
{
|
|
||||||
conn.Open();
|
|
||||||
nameList = conn.Query<String>("SELECT name FROM reserved_names WHERE userId=@UserId", new { UserId = userId }).ToList();
|
|
||||||
}
|
|
||||||
catch (MySqlException e)
|
|
||||||
{
|
|
||||||
Program.Log.Error(e.ToString());
|
|
||||||
nameList = new List<String>(); }
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
conn.Dispose();
|
|
||||||
}
|
|
||||||
return nameList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Retainer> GetRetainers(uint userId)
|
|
||||||
{
|
|
||||||
List<Retainer> retainers = new List<Retainer>();
|
|
||||||
|
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
conn.Open();
|
conn.Open();
|
||||||
|
|
||||||
string query = @"
|
string query = "SELECT name FROM reserved_names WHERE userId=@UserId";
|
||||||
SELECT characters.id as charaId, server_retainers.id as retainerId, server_retainers.name, characters_retainers.doRename FROM characters
|
|
||||||
INNER JOIN characters_retainers ON characters.id = characters_retainers.characterId
|
|
||||||
INNER JOIN server_retainers ON characters_retainers.retainerId = server_retainers.id
|
|
||||||
WHERE userId = @userId
|
|
||||||
";
|
|
||||||
|
|
||||||
MySqlCommand cmd = new MySqlCommand(query, conn);
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
cmd.Parameters.AddWithValue("@userId", userId);
|
cmd.Parameters.AddWithValue("@UserId", userId);
|
||||||
|
|
||||||
using (MySqlDataReader reader = cmd.ExecuteReader())
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
uint characterId = reader.GetUInt32("charaId");
|
reservedNames.Add(reader.GetString("name"));
|
||||||
uint retainerId = reader.GetUInt32("retainerId");
|
|
||||||
string name = reader.GetString("name");
|
|
||||||
bool doRename = reader.GetBoolean("doRename");
|
|
||||||
|
|
||||||
retainers.Add(new Retainer(characterId, retainerId, name, doRename));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (MySqlException e)
|
catch (MySqlException e)
|
||||||
{
|
{
|
||||||
Program.Log.Error(e.ToString());
|
Program.Log.Error(e.ToString());
|
||||||
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
conn.Dispose();
|
conn.Dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return retainers;
|
return reservedNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<Retainer> GetRetainers(uint userId)
|
||||||
|
{
|
||||||
|
return new List<Retainer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -32,7 +32,7 @@ namespace FFXIVClassic_Map_Server
|
||||||
|
|
||||||
internal bool DoCommand(string input, Session session)
|
internal bool DoCommand(string input, Session session)
|
||||||
{
|
{
|
||||||
if (!input.Any() || input.Equals(""))
|
if (!input.Any() || input.Equals("") || input.Length == 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
input.Trim();
|
input.Trim();
|
||||||
|
@ -47,10 +47,10 @@ namespace FFXIVClassic_Map_Server
|
||||||
|
|
||||||
split = split.ToArray(); // Ignore case on commands
|
split = split.ToArray(); // Ignore case on commands
|
||||||
|
|
||||||
|
if (split.Length > 0)
|
||||||
|
{
|
||||||
var cmd = split[0];
|
var cmd = split[0];
|
||||||
|
|
||||||
if (cmd.Any())
|
|
||||||
{
|
|
||||||
// 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)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
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 FFXIVClassic.Common;
|
using FFXIVClassic.Common;
|
||||||
using FFXIVClassic_Map_Server.utils;
|
using FFXIVClassic_Map_Server.utils;
|
||||||
|
|
||||||
|
@ -12,6 +10,9 @@ using FFXIVClassic_Map_Server.Actors;
|
||||||
using FFXIVClassic_Map_Server.actors.chara.player;
|
using FFXIVClassic_Map_Server.actors.chara.player;
|
||||||
using FFXIVClassic_Map_Server.packets.receive.supportdesk;
|
using FFXIVClassic_Map_Server.packets.receive.supportdesk;
|
||||||
using FFXIVClassic_Map_Server.actors.chara.npc;
|
using FFXIVClassic_Map_Server.actors.chara.npc;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai;
|
||||||
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server
|
namespace FFXIVClassic_Map_Server
|
||||||
{
|
{
|
||||||
|
@ -49,29 +50,6 @@ namespace FFXIVClassic_Map_Server
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Npc> GetNpcList()
|
|
||||||
{
|
|
||||||
using (var conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
|
||||||
{
|
|
||||||
List<Npc> npcList = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
conn.Open();
|
|
||||||
npcList = conn.Query<Npc>("SELECT * FROM npc_list").ToList();
|
|
||||||
}
|
|
||||||
catch (MySqlException e)
|
|
||||||
{
|
|
||||||
Program.Log.Error(e.ToString());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
conn.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return npcList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dictionary<uint, ItemData> GetItemGamedata()
|
public static Dictionary<uint, ItemData> GetItemGamedata()
|
||||||
{
|
{
|
||||||
using (var conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
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)))
|
||||||
|
@ -783,6 +761,62 @@ namespace FFXIVClassic_Map_Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Get class experience
|
||||||
|
query = @"
|
||||||
|
SELECT
|
||||||
|
pug,
|
||||||
|
gla,
|
||||||
|
mrd,
|
||||||
|
arc,
|
||||||
|
lnc,
|
||||||
|
|
||||||
|
thm,
|
||||||
|
cnj,
|
||||||
|
|
||||||
|
crp,
|
||||||
|
bsm,
|
||||||
|
arm,
|
||||||
|
gsm,
|
||||||
|
ltw,
|
||||||
|
wvr,
|
||||||
|
alc,
|
||||||
|
cul,
|
||||||
|
|
||||||
|
min,
|
||||||
|
btn,
|
||||||
|
fsh
|
||||||
|
FROM characters_class_exp WHERE characterId = @charId";
|
||||||
|
|
||||||
|
cmd = new MySqlCommand(query, conn);
|
||||||
|
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
||||||
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
if (reader.Read())
|
||||||
|
{
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_PUG - 1] = reader.GetInt32("pug");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_GLA - 1] = reader.GetInt32("gla");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_MRD - 1] = reader.GetInt32("mrd");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_ARC - 1] = reader.GetInt32("arc");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_LNC - 1] = reader.GetInt32("lnc");
|
||||||
|
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_THM - 1] = reader.GetInt32("thm");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_CNJ - 1] = reader.GetInt32("cnj");
|
||||||
|
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_CRP - 1] = reader.GetInt32("crp");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_BSM - 1] = reader.GetInt32("bsm");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_ARM - 1] = reader.GetInt32("arm");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_GSM - 1] = reader.GetInt32("gsm");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_LTW - 1] = reader.GetInt32("ltw");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_WVR - 1] = reader.GetInt32("wvr");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_ALC - 1] = reader.GetInt32("alc");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_CUL - 1] = reader.GetInt32("cul");
|
||||||
|
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_MIN - 1] = reader.GetInt32("min");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_BTN - 1] = reader.GetInt32("btn");
|
||||||
|
player.charaWork.battleSave.skillPoint[Player.CLASSID_FSH - 1] = reader.GetInt32("fsh");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Load Saved Parameters
|
//Load Saved Parameters
|
||||||
query = @"
|
query = @"
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -879,18 +913,38 @@ namespace FFXIVClassic_Map_Server
|
||||||
query = @"
|
query = @"
|
||||||
SELECT
|
SELECT
|
||||||
statusId,
|
statusId,
|
||||||
expireTime
|
duration,
|
||||||
|
magnitude,
|
||||||
|
tick,
|
||||||
|
tier,
|
||||||
|
extra
|
||||||
FROM characters_statuseffect WHERE characterId = @charId";
|
FROM characters_statuseffect WHERE characterId = @charId";
|
||||||
|
|
||||||
cmd = new MySqlCommand(query, conn);
|
cmd = new MySqlCommand(query, conn);
|
||||||
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
||||||
using (MySqlDataReader reader = cmd.ExecuteReader())
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
{
|
{
|
||||||
int count = 0;
|
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
player.charaWork.status[count] = reader.GetUInt16("statusId");
|
var id = reader.GetUInt32(0);
|
||||||
player.charaWork.statusShownTime[count] = reader.GetUInt32("expireTime");
|
var duration = reader.GetUInt32(1);
|
||||||
|
var magnitude = reader.GetUInt64(2);
|
||||||
|
var tick = reader.GetUInt32(3);
|
||||||
|
var tier = reader.GetByte(4);
|
||||||
|
var extra = reader.GetUInt64(5);
|
||||||
|
|
||||||
|
var effect = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
if (effect != null)
|
||||||
|
{
|
||||||
|
effect.SetDuration(duration);
|
||||||
|
effect.SetMagnitude(magnitude);
|
||||||
|
effect.SetTickMs(tick);
|
||||||
|
effect.SetTier(tier);
|
||||||
|
effect.SetExtra(extra);
|
||||||
|
|
||||||
|
// dont wanna send ton of messages on login (i assume retail doesnt)
|
||||||
|
player.statusEffects.AddStatusEffect(effect, null, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -953,25 +1007,7 @@ namespace FFXIVClassic_Map_Server
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load Hotbar
|
//Load Hotbar
|
||||||
query = @"
|
LoadHotbar(player);
|
||||||
SELECT
|
|
||||||
hotbarSlot,
|
|
||||||
commandId,
|
|
||||||
recastTime
|
|
||||||
FROM characters_hotbar WHERE characterId = @charId AND classId = @classId";
|
|
||||||
|
|
||||||
cmd = new MySqlCommand(query, conn);
|
|
||||||
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
|
||||||
cmd.Parameters.AddWithValue("@classId", player.charaWork.parameterSave.state_mainSkill[0]);
|
|
||||||
using (MySqlDataReader reader = cmd.ExecuteReader())
|
|
||||||
{
|
|
||||||
while (reader.Read())
|
|
||||||
{
|
|
||||||
int index = reader.GetUInt16(0);
|
|
||||||
player.charaWork.command[index + 32] = reader.GetUInt32(1);
|
|
||||||
player.charaWork.parameterSave.commandSlot_recastTime[index] = reader.GetUInt32(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Load Scenario Quests
|
//Load Scenario Quests
|
||||||
query = @"
|
query = @"
|
||||||
|
@ -1210,7 +1246,195 @@ namespace FFXIVClassic_Map_Server
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
public static void EquipAbility(Player player, byte classId, ushort hotbarSlot, uint commandId, uint recastTime)
|
||||||
|
{
|
||||||
|
commandId ^= 0xA0F00000;
|
||||||
|
if (commandId > 0)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(
|
||||||
|
String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}",
|
||||||
|
ConfigConstants.DATABASE_HOST,
|
||||||
|
ConfigConstants.DATABASE_PORT,
|
||||||
|
ConfigConstants.DATABASE_NAME,
|
||||||
|
ConfigConstants.DATABASE_USERNAME,
|
||||||
|
ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
MySqlCommand cmd;
|
||||||
|
string query = @"
|
||||||
|
INSERT INTO characters_hotbar
|
||||||
|
(characterId, classId, hotbarSlot, commandId, recastTime)
|
||||||
|
VALUES
|
||||||
|
(@charId, @classId, @hotbarSlot, @commandId, @recastTime)
|
||||||
|
ON DUPLICATE KEY UPDATE commandId=@commandId, recastTime=@recastTime;
|
||||||
|
";
|
||||||
|
|
||||||
|
cmd = new MySqlCommand(query, conn);
|
||||||
|
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
||||||
|
cmd.Parameters.AddWithValue("@classId", classId);
|
||||||
|
cmd.Parameters.AddWithValue("@commandId", commandId);
|
||||||
|
cmd.Parameters.AddWithValue("@hotbarSlot", hotbarSlot);
|
||||||
|
cmd.Parameters.AddWithValue("@recastTime", recastTime);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
UnequipAbility(player, hotbarSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Unequipping is done by sending an equip packet with 0xA0F00000 as the ability and the hotbar slot of the action being unequipped
|
||||||
|
public static void UnequipAbility(Player player, ushort hotbarSlot)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(
|
||||||
|
String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}",
|
||||||
|
ConfigConstants.DATABASE_HOST,
|
||||||
|
ConfigConstants.DATABASE_PORT,
|
||||||
|
ConfigConstants.DATABASE_NAME,
|
||||||
|
ConfigConstants.DATABASE_USERNAME,
|
||||||
|
ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
MySqlCommand cmd;
|
||||||
|
string query = "";
|
||||||
|
|
||||||
|
query = @"
|
||||||
|
DELETE FROM characters_hotbar
|
||||||
|
WHERE characterId = @charId AND classId = @classId AND hotbarSlot = @hotbarSlot
|
||||||
|
";
|
||||||
|
cmd = new MySqlCommand(query, conn);
|
||||||
|
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
||||||
|
cmd.Parameters.AddWithValue("@classId", player.charaWork.parameterSave.state_mainSkill[0]);
|
||||||
|
cmd.Parameters.AddWithValue("@hotbarSlot", hotbarSlot);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadHotbar(Player player)
|
||||||
|
{
|
||||||
|
string query;
|
||||||
|
MySqlCommand cmd;
|
||||||
|
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
//Load Hotbar
|
||||||
|
query = @"
|
||||||
|
SELECT
|
||||||
|
hotbarSlot,
|
||||||
|
commandId,
|
||||||
|
recastTime
|
||||||
|
FROM characters_hotbar WHERE characterId = @charId AND classId = @classId
|
||||||
|
ORDER BY hotbarSlot";
|
||||||
|
|
||||||
|
cmd = new MySqlCommand(query, conn);
|
||||||
|
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
||||||
|
cmd.Parameters.AddWithValue("@classId", player.GetCurrentClassOrJob());
|
||||||
|
|
||||||
|
player.charaWork.commandBorder = 32;
|
||||||
|
|
||||||
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
int hotbarSlot = reader.GetUInt16("hotbarSlot");
|
||||||
|
uint commandId = reader.GetUInt32("commandId");
|
||||||
|
player.charaWork.command[hotbarSlot + player.charaWork.commandBorder] = 0xA0F00000 | commandId;
|
||||||
|
player.charaWork.commandCategory[hotbarSlot + player.charaWork.commandBorder] = 1;
|
||||||
|
player.charaWork.parameterSave.commandSlot_recastTime[hotbarSlot] = reader.GetUInt32("recastTime");
|
||||||
|
|
||||||
|
//Recast timer
|
||||||
|
BattleCommand ability = Server.GetWorldManager().GetBattleCommand((ushort)(commandId));
|
||||||
|
player.charaWork.parameterTemp.maxCommandRecastTime[hotbarSlot] = (ushort) (ability != null ? ability.maxRecastTimeSeconds : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ushort FindFirstCommandSlot(Player player, byte classId)
|
||||||
|
{
|
||||||
|
ushort slot = 0;
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(
|
||||||
|
String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}",
|
||||||
|
ConfigConstants.DATABASE_HOST,
|
||||||
|
ConfigConstants.DATABASE_PORT,
|
||||||
|
ConfigConstants.DATABASE_NAME,
|
||||||
|
ConfigConstants.DATABASE_USERNAME,
|
||||||
|
ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
MySqlCommand cmd;
|
||||||
|
string query = "";
|
||||||
|
|
||||||
|
//Drop
|
||||||
|
List<Tuple<ushort, uint>> hotbarList = new List<Tuple<ushort, uint>>();
|
||||||
|
query = @"
|
||||||
|
SELECT hotbarSlot
|
||||||
|
FROM characters_hotbar
|
||||||
|
WHERE characterId = @charId AND classId = @classId
|
||||||
|
ORDER BY hotbarSlot
|
||||||
|
";
|
||||||
|
cmd = new MySqlCommand(query, conn);
|
||||||
|
cmd.Parameters.AddWithValue("@charId", player.actorId);
|
||||||
|
cmd.Parameters.AddWithValue("@classId", classId);
|
||||||
|
|
||||||
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
if (slot != reader.GetUInt16("hotbarSlot"))
|
||||||
|
break;
|
||||||
|
|
||||||
|
slot++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
public static List<InventoryItem> GetInventory(Player player, uint slotOffset, uint type)
|
public static List<InventoryItem> GetInventory(Player player, uint slotOffset, uint type)
|
||||||
{
|
{
|
||||||
List<InventoryItem> items = new List<InventoryItem>();
|
List<InventoryItem> items = new List<InventoryItem>();
|
||||||
|
@ -2069,6 +2293,311 @@ namespace FFXIVClassic_Map_Server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Dictionary<uint, StatusEffect> LoadGlobalStatusEffectList()
|
||||||
|
{
|
||||||
|
var effects = new Dictionary<uint, StatusEffect>();
|
||||||
|
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var query = @"SELECT id, name, flags, overwrite, tickMs FROM server_statuseffects;";
|
||||||
|
|
||||||
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
var id = reader.GetUInt32("id");
|
||||||
|
var name = reader.GetString("name");
|
||||||
|
var flags = reader.GetUInt32("flags");
|
||||||
|
var overwrite = reader.GetByte("overwrite");
|
||||||
|
var tickMs = reader.GetUInt32("tickMs");
|
||||||
|
var effect = new StatusEffect(id, name, flags, overwrite, tickMs);
|
||||||
|
lua.LuaEngine.LoadStatusEffectScript(effect);
|
||||||
|
effects.Add(id, effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return effects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SavePlayerStatusEffects(Player player)
|
||||||
|
{
|
||||||
|
string[] faqs = null;
|
||||||
|
List<string> raw = new List<string>();
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
using (MySqlTransaction trns = conn.BeginTransaction())
|
||||||
|
{
|
||||||
|
string query = @"
|
||||||
|
REPLACE INTO characters_statuseffect
|
||||||
|
(characterId, statusId, magnitude, duration, tick, tier, extra)
|
||||||
|
VALUES
|
||||||
|
(@actorId, @statusId, @magnitude, @duration, @tick, @tier, @extra)
|
||||||
|
";
|
||||||
|
using (MySqlCommand cmd = new MySqlCommand(query, conn, trns))
|
||||||
|
{
|
||||||
|
foreach (var effect in player.statusEffects.GetStatusEffects())
|
||||||
|
{
|
||||||
|
var duration = Utils.UnixTimeStampUTC(effect.GetEndTime()) - Utils.UnixTimeStampUTC();
|
||||||
|
|
||||||
|
cmd.Parameters.AddWithValue("@actorId", player.actorId);
|
||||||
|
cmd.Parameters.AddWithValue("@statusId", effect.GetStatusEffectId());
|
||||||
|
cmd.Parameters.AddWithValue("@magnitude", effect.GetMagnitude());
|
||||||
|
cmd.Parameters.AddWithValue("@duration", duration);
|
||||||
|
cmd.Parameters.AddWithValue("@tick", effect.GetTickMs());
|
||||||
|
cmd.Parameters.AddWithValue("@tier", effect.GetTier());
|
||||||
|
cmd.Parameters.AddWithValue("@extra", effect.GetExtra());
|
||||||
|
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
trns.Commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadGlobalBattleCommandList(Dictionary<ushort, BattleCommand> battleCommandDict, Dictionary<Tuple<byte, short>, List<uint>> battleCommandIdByLevel)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var query = ("SELECT `id`, name, classJob, lvl, requirements, mainTarget, validTarget, aoeType, aoeRange, aoeMinRange, aoeConeAngle, aoeRotateAngle, aoeTarget, basePotency, numHits, positionBonus, procRequirement, `range`, minRange, rangeHeight, rangeWidth, statusId, statusDuration, statusChance, " +
|
||||||
|
"castType, castTime, recastTime, mpCost, tpCost, animationType, effectAnimation, modelAnimation, animationDuration, battleAnimation, validUser, comboId1, comboId2, comboStep, accuracyMod, worldMasterTextId, commandType, actionType, actionProperty FROM server_battle_commands;");
|
||||||
|
|
||||||
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
var id = reader.GetUInt16("id");
|
||||||
|
var name = reader.GetString("name");
|
||||||
|
var battleCommand = new BattleCommand(id, name);
|
||||||
|
|
||||||
|
battleCommand.job = reader.GetByte("classJob");
|
||||||
|
battleCommand.level = reader.GetByte("lvl");
|
||||||
|
battleCommand.requirements = (BattleCommandRequirements)reader.GetUInt16("requirements");
|
||||||
|
battleCommand.mainTarget = (ValidTarget)reader.GetByte("mainTarget");
|
||||||
|
battleCommand.validTarget = (ValidTarget)reader.GetByte("validTarget");
|
||||||
|
battleCommand.aoeType = (TargetFindAOEType)reader.GetByte("aoeType");
|
||||||
|
battleCommand.basePotency = reader.GetUInt16("basePotency");
|
||||||
|
battleCommand.numHits = reader.GetByte("numHits");
|
||||||
|
battleCommand.positionBonus = (BattleCommandPositionBonus)reader.GetByte("positionBonus");
|
||||||
|
battleCommand.procRequirement = (BattleCommandProcRequirement)reader.GetByte("procRequirement");
|
||||||
|
battleCommand.range = reader.GetFloat("range");
|
||||||
|
battleCommand.minRange = reader.GetFloat("minRange");
|
||||||
|
battleCommand.rangeHeight = reader.GetInt32("rangeHeight");
|
||||||
|
battleCommand.rangeWidth = reader.GetInt32("rangeWidth");
|
||||||
|
battleCommand.statusId = reader.GetUInt32("statusId");
|
||||||
|
battleCommand.statusDuration = reader.GetUInt32("statusDuration");
|
||||||
|
battleCommand.statusChance = reader.GetFloat("statusChance");
|
||||||
|
battleCommand.castType = reader.GetByte("castType");
|
||||||
|
battleCommand.castTimeMs = reader.GetUInt32("castTime");
|
||||||
|
battleCommand.maxRecastTimeSeconds = reader.GetUInt32("recastTime");
|
||||||
|
battleCommand.recastTimeMs = battleCommand.maxRecastTimeSeconds * 1000;
|
||||||
|
battleCommand.mpCost = reader.GetUInt16("mpCost");
|
||||||
|
battleCommand.tpCost = reader.GetUInt16("tpCost");
|
||||||
|
battleCommand.animationType = reader.GetByte("animationType");
|
||||||
|
battleCommand.effectAnimation = reader.GetUInt16("effectAnimation");
|
||||||
|
battleCommand.modelAnimation = reader.GetUInt16("modelAnimation");
|
||||||
|
battleCommand.animationDurationSeconds = reader.GetUInt16("animationDuration");
|
||||||
|
battleCommand.aoeRange = reader.GetFloat("aoeRange");
|
||||||
|
battleCommand.aoeMinRange = reader.GetFloat("aoeMinRange");
|
||||||
|
battleCommand.aoeConeAngle = reader.GetFloat("aoeConeAngle");
|
||||||
|
battleCommand.aoeRotateAngle = reader.GetFloat("aoeRotateAngle");
|
||||||
|
battleCommand.aoeTarget = (TargetFindAOETarget)reader.GetByte("aoeTarget");
|
||||||
|
|
||||||
|
battleCommand.battleAnimation = reader.GetUInt32("battleAnimation");
|
||||||
|
battleCommand.validUser = (BattleCommandValidUser)reader.GetByte("validUser");
|
||||||
|
|
||||||
|
battleCommand.comboNextCommandId[0] = reader.GetInt32("comboId1");
|
||||||
|
battleCommand.comboNextCommandId[1] = reader.GetInt32("comboId2");
|
||||||
|
battleCommand.comboStep = reader.GetInt16("comboStep");
|
||||||
|
battleCommand.commandType = (CommandType) reader.GetInt16("commandType");
|
||||||
|
battleCommand.actionProperty = (ActionProperty)reader.GetInt16("actionProperty");
|
||||||
|
battleCommand.actionType = (ActionType)reader.GetInt16("actionType");
|
||||||
|
battleCommand.accuracyModifier = reader.GetFloat("accuracyMod");
|
||||||
|
battleCommand.worldMasterTextId = reader.GetUInt16("worldMasterTextId");
|
||||||
|
lua.LuaEngine.LoadBattleCommandScript(battleCommand, "weaponskill");
|
||||||
|
battleCommandDict.Add(id, battleCommand);
|
||||||
|
|
||||||
|
Tuple<byte, short> tuple = Tuple.Create<byte, short>(battleCommand.job, battleCommand.level);
|
||||||
|
if (battleCommandIdByLevel.ContainsKey(tuple))
|
||||||
|
{
|
||||||
|
battleCommandIdByLevel[tuple].Add(id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<uint> list = new List<uint>() { id };
|
||||||
|
battleCommandIdByLevel.Add(tuple, list);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.Log.Info(String.Format("Loaded {0} battle commands.", count));
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadGlobalBattleTraitList(Dictionary<ushort, BattleTrait> battleTraitDict, Dictionary<byte, List<ushort>> battleTraitJobDict)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var query = ("SELECT `id`, name, classJob, lvl, modifier, bonus FROM server_battle_traits;");
|
||||||
|
|
||||||
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
|
using (MySqlDataReader reader = cmd.ExecuteReader())
|
||||||
|
{
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
var id = reader.GetUInt16("id");
|
||||||
|
var name = reader.GetString("name");
|
||||||
|
var job = reader.GetByte("classJob");
|
||||||
|
var level = reader.GetByte("lvl");
|
||||||
|
uint modifier = reader.GetUInt32("modifier");
|
||||||
|
var bonus = reader.GetInt32("bonus");
|
||||||
|
|
||||||
|
var trait = new BattleTrait(id, name, job, level, modifier, bonus);
|
||||||
|
|
||||||
|
battleTraitDict.Add(id, trait);
|
||||||
|
|
||||||
|
if(battleTraitJobDict.ContainsKey(job))
|
||||||
|
{
|
||||||
|
battleTraitJobDict[job].Add(id);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
battleTraitJobDict[job] = new List<ushort>();
|
||||||
|
battleTraitJobDict[job].Add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Program.Log.Info(String.Format("Loaded {0} battle traits.", count));
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetExp(Player player, byte classId, int exp)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var query = String.Format(@"
|
||||||
|
UPDATE characters_class_exp
|
||||||
|
SET
|
||||||
|
{0} = @exp
|
||||||
|
WHERE
|
||||||
|
characterId = @characterId", CharacterUtils.GetClassNameForId(classId));
|
||||||
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
|
cmd.Prepare();
|
||||||
|
cmd = new MySqlCommand(query, conn);
|
||||||
|
cmd.Parameters.AddWithValue("@characterId", player.actorId);
|
||||||
|
cmd.Parameters.AddWithValue("@exp", exp);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetLevel(Player player, byte classId, short level)
|
||||||
|
{
|
||||||
|
using (MySqlConnection conn = new MySqlConnection(String.Format("Server={0}; Port={1}; Database={2}; UID={3}; Password={4}", ConfigConstants.DATABASE_HOST, ConfigConstants.DATABASE_PORT, ConfigConstants.DATABASE_NAME, ConfigConstants.DATABASE_USERNAME, ConfigConstants.DATABASE_PASSWORD)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var query = String.Format(@"
|
||||||
|
UPDATE characters_class_levels
|
||||||
|
SET
|
||||||
|
{0} = @lvl
|
||||||
|
WHERE
|
||||||
|
characterId = @characterId", CharacterUtils.GetClassNameForId(classId));
|
||||||
|
MySqlCommand cmd = new MySqlCommand(query, conn);
|
||||||
|
|
||||||
|
cmd.Prepare();
|
||||||
|
cmd = new MySqlCommand(query, conn);
|
||||||
|
cmd.Parameters.AddWithValue("@characterId", player.actorId);
|
||||||
|
cmd.Parameters.AddWithValue("@lvl", level);
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
catch (MySqlException e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.ToString());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
conn.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Retainer LoadRetainer(Player player, int retainerIndex)
|
public static Retainer LoadRetainer(Player player, int retainerIndex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,17 +104,49 @@
|
||||||
<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\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\GuildleveDirector.cs" />
|
||||||
<Compile Include="actors\director\Work\GuildleveWork.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\GLContentGroup.cs" />
|
||||||
<Compile Include="actors\group\ContentGroup.cs" />
|
<Compile Include="actors\group\ContentGroup.cs" />
|
||||||
|
@ -100,11 +157,11 @@
|
||||||
<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" />
|
||||||
|
@ -162,10 +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\battle\BattleAction.cs" />
|
<Compile Include="packets\send\actor\battle\CommandResult.cs" />
|
||||||
<Compile Include="packets\send\actor\battle\BattleActionX00Packet.cs" />
|
<Compile Include="packets\send\actor\battle\CommandResultContainer.cs" />
|
||||||
<Compile Include="packets\send\actor\battle\BattleActionX18Packet.cs" />
|
<Compile Include="packets\send\actor\battle\CommandResultX00Packet.cs" />
|
||||||
<Compile Include="packets\send\actor\battle\BattleActionX10Packet.cs" />
|
<Compile Include="packets\send\actor\battle\CommandResultX18Packet.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" />
|
||||||
|
@ -192,7 +250,7 @@
|
||||||
<Compile Include="packets\send\actor\StartCountdownPacket.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" />
|
||||||
|
@ -203,7 +261,7 @@
|
||||||
<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\SetActorBGPropertiesPacket.cs" />
|
<Compile Include="packets\send\actor\SetActorBGPropertiesPacket.cs" />
|
||||||
<Compile Include="packets\send\actor\_0xFPacket.cs" />
|
<Compile Include="packets\send\actor\_0xFPacket.cs" />
|
||||||
|
@ -329,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" />
|
||||||
|
@ -351,6 +410,9 @@
|
||||||
<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>
|
<PostBuildEvent>
|
||||||
|
|
|
@ -148,6 +148,7 @@ 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().isAutoAttackEnabled = setTarget.attackTarget != 0xE0000000;
|
||||||
session.GetActor().BroadcastPacket(SetActorTargetAnimatedPacket.BuildPacket(session.id, setTarget.actorID), true);
|
session.GetActor().BroadcastPacket(SetActorTargetAnimatedPacket.BuildPacket(session.id, setTarget.actorID), true);
|
||||||
break;
|
break;
|
||||||
//Lock Target
|
//Lock Target
|
||||||
|
|
|
@ -16,6 +16,10 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -57,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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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's inventory
|
/// Looks up a localized string similar to Removes the specified currency from the current player's inventory
|
||||||
///
|
///
|
||||||
///*Syntax: Removecurrency <quantity>
|
///*Syntax: removecurrency <quantity>
|
||||||
/// Removecurrency <type> <quantity>
|
/// removecurrency <type> <quantity>
|
||||||
///<type> is the specific type of currency desired, defaults to gil if no type specified.
|
///<type> 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's inventory
|
/// Looks up a localized string similar to Removes the specified items to the current player's inventory
|
||||||
///
|
///
|
||||||
///*Syntax: Removeitem <itemid>
|
///*Syntax: removeitem <itemid>
|
||||||
/// Removeitem <itemid> <quantity>
|
/// removeitem <itemid> <quantity>
|
||||||
///<item id> is the item's specific id as defined in the server database.
|
///<item id> is the item'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's inventory
|
/// Looks up a localized string similar to Removes the specified key item to the current player's inventory
|
||||||
///
|
///
|
||||||
///*Syntax: Removekeyitem <itemid>
|
///*Syntax: removekeyitem <itemid>
|
||||||
///<item id> is the key item's specific id as defined in the server database.
|
///<item id> is the key item'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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,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();
|
||||||
|
|
||||||
|
|
BIN
FFXIVClassic Map Server/SharpNav.dll
Normal file
BIN
FFXIVClassic Map Server/SharpNav.dll
Normal file
Binary file not shown.
|
@ -22,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
|
||||||
{
|
{
|
||||||
|
@ -34,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
|
||||||
|
@ -75,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";
|
||||||
|
|
||||||
|
@ -88,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++;
|
||||||
}
|
}
|
||||||
|
@ -412,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.Speed, 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...");
|
||||||
|
@ -419,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.Speed, 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)
|
||||||
{
|
{
|
||||||
|
@ -581,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
|
||||||
|
@ -756,6 +1077,8 @@ namespace FFXIVClassic_Map_Server
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadZone(uint zoneId)
|
public void ReloadZone(uint zoneId)
|
||||||
|
{
|
||||||
|
lock (zoneList)
|
||||||
{
|
{
|
||||||
if (!zoneList.ContainsKey(zoneId))
|
if (!zoneList.ContainsKey(zoneId))
|
||||||
return;
|
return;
|
||||||
|
@ -763,7 +1086,7 @@ namespace FFXIVClassic_Map_Server
|
||||||
Zone zone = zoneList[zoneId];
|
Zone zone = zoneList[zoneId];
|
||||||
//zone.clear();
|
//zone.clear();
|
||||||
//LoadNPCs(zone.actorId);
|
//LoadNPCs(zone.actorId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentGroup CreateContentGroup(Director director, params Actor[] actors)
|
public ContentGroup CreateContentGroup(Director director, params Actor[] actors)
|
||||||
|
@ -1006,10 +1329,16 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1030,6 +1359,8 @@ namespace FFXIVClassic_Map_Server
|
||||||
}
|
}
|
||||||
|
|
||||||
public Actor GetActorInWorld(uint charId)
|
public Actor GetActorInWorld(uint charId)
|
||||||
|
{
|
||||||
|
lock (zoneList)
|
||||||
{
|
{
|
||||||
foreach (Zone zone in zoneList.Values)
|
foreach (Zone zone in zoneList.Values)
|
||||||
{
|
{
|
||||||
|
@ -1037,10 +1368,13 @@ namespace FFXIVClassic_Map_Server
|
||||||
if (a != null)
|
if (a != null)
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Actor GetActorInWorldByUniqueId(string uid)
|
public Actor GetActorInWorldByUniqueId(string uid)
|
||||||
|
{
|
||||||
|
lock (zoneList)
|
||||||
{
|
{
|
||||||
foreach (Zone zone in zoneList.Values)
|
foreach (Zone zone in zoneList.Values)
|
||||||
{
|
{
|
||||||
|
@ -1048,23 +1382,31 @@ namespace FFXIVClassic_Map_Server
|
||||||
if (a != null)
|
if (a != null)
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Zone GetZone(uint zoneId)
|
public Zone GetZone(uint zoneId)
|
||||||
|
{
|
||||||
|
lock (zoneList)
|
||||||
{
|
{
|
||||||
if (!zoneList.ContainsKey(zoneId))
|
if (!zoneList.ContainsKey(zoneId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return zoneList[zoneId];
|
return zoneList[zoneId];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PrivateArea GetPrivateArea(uint zoneId, string privateArea, uint privateAreaType)
|
public PrivateArea GetPrivateArea(uint zoneId, string privateArea, uint privateAreaType)
|
||||||
|
{
|
||||||
|
lock (zoneList)
|
||||||
{
|
{
|
||||||
if (!zoneList.ContainsKey(zoneId))
|
if (!zoneList.ContainsKey(zoneId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType);
|
return zoneList[zoneId].GetPrivateArea(privateArea, privateAreaType);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public WorldMaster GetActor()
|
public WorldMaster GetActor()
|
||||||
{
|
{
|
||||||
|
@ -1115,5 +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>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,35 @@ 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.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;
|
||||||
|
|
||||||
|
@ -22,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;
|
||||||
|
@ -41,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)
|
||||||
|
@ -76,6 +111,17 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public virtual void ResetMoveSpeeds()
|
||||||
|
{
|
||||||
|
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 CreateAddActorPacket(byte val)
|
public SubPacket CreateAddActorPacket(byte val)
|
||||||
{
|
{
|
||||||
return AddActorPacket.BuildPacket(actorId, val);
|
return AddActorPacket.BuildPacket(actorId, val);
|
||||||
|
@ -139,7 +185,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
|
|
||||||
public SubPacket CreateStatePacket()
|
public SubPacket CreateStatePacket()
|
||||||
{
|
{
|
||||||
return SetActorStatePacket.BuildPacket(actorId, currentMainState, currentSubState);
|
return SetActorStatePacket.BuildPacket(actorId, currentMainState, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SubPacket> GetEventConditionPackets()
|
public List<SubPacket> GetEventConditionPackets()
|
||||||
|
@ -325,20 +371,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)
|
||||||
|
{
|
||||||
|
if (newState != currentMainState)
|
||||||
{
|
{
|
||||||
currentMainState = newState;
|
currentMainState = newState;
|
||||||
SubPacket ChangeStatePacket = SetActorStatePacket.BuildPacket(actorId, newState, currentSubState);
|
|
||||||
SubPacket battleActionPacket = BattleActionX01Packet.BuildPacket(actorId, actorId, actorId, 0x72000062, 1, 0, 0x05209, 0, 0);
|
updateFlags |= (ActorUpdateFlags.State | ActorUpdateFlags.Position);
|
||||||
zone.BroadcastPacketAroundActor(this, ChangeStatePacket);
|
}
|
||||||
zone.BroadcastPacketAroundActor(this, battleActionPacket);
|
}
|
||||||
|
|
||||||
|
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, 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)
|
||||||
|
@ -347,12 +413,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, 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)
|
||||||
|
@ -468,7 +590,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
if (value.GetType() == curObj.GetType())
|
if (value.GetType() == curObj.GetType())
|
||||||
parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, value);
|
parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, value);
|
||||||
else
|
else
|
||||||
parentObj.GetType().GetField(split[split.Length-1]).SetValue(parentObj, TypeDescriptor.GetConverter(value.GetType()).ConvertTo(value, curObj.GetType()));
|
parentObj.GetType().GetField(split[split.Length - 1]).SetValue(parentObj, TypeDescriptor.GetConverter(value.GetType()).ConvertTo(value, curObj.GetType()));
|
||||||
|
|
||||||
SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc);
|
SetActorPropetyPacket changeProperty = new SetActorPropetyPacket(uiFunc);
|
||||||
changeProperty.AddProperty(this, name);
|
changeProperty.AddProperty(this, name);
|
||||||
|
@ -483,6 +605,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region positioning
|
||||||
public List<float> GetPos()
|
public List<float> GetPos()
|
||||||
{
|
{
|
||||||
List<float> pos = new List<float>();
|
List<float> pos = new List<float>();
|
||||||
|
@ -496,6 +619,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;
|
||||||
|
@ -521,6 +649,103 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
{
|
{
|
||||||
return zoneId;
|
return zoneId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LookAt(Actor actor)
|
||||||
|
{
|
||||||
|
if (actor != null && actor != this)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ 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.group;
|
||||||
using FFXIVClassic_Map_Server.actors.director;
|
using FFXIVClassic_Map_Server.actors.director;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai.controllers;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.Actors
|
namespace FFXIVClassic_Map_Server.Actors
|
||||||
{
|
{
|
||||||
|
@ -103,15 +104,19 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
return subpackets;
|
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 gridX = (int)actor.positionX / boundingGridSize;
|
||||||
int gridY = (int)actor.positionZ / boundingGridSize;
|
int gridY = (int)actor.positionZ / boundingGridSize;
|
||||||
|
@ -132,10 +137,13 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
lock (mActorBlock)
|
lock (mActorBlock)
|
||||||
mActorBlock[gridX, gridY].Add(actor);
|
mActorBlock[gridX, gridY].Add(actor);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void RemoveActorFromZone(Actor actor)
|
public void RemoveActorFromZone(Actor actor)
|
||||||
{
|
{
|
||||||
|
if (actor != null)
|
||||||
lock (mActorList)
|
lock (mActorList)
|
||||||
|
{
|
||||||
mActorList.Remove(actor.actorId);
|
mActorList.Remove(actor.actorId);
|
||||||
|
|
||||||
int gridX = (int)actor.positionX / boundingGridSize;
|
int gridX = (int)actor.positionX / boundingGridSize;
|
||||||
|
@ -157,6 +165,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
lock (mActorBlock)
|
lock (mActorBlock)
|
||||||
mActorBlock[gridX, gridY].Remove(actor);
|
mActorBlock[gridX, gridY].Remove(actor);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateActorPosition(Actor actor)
|
public void UpdateActorPosition(Actor actor)
|
||||||
{
|
{
|
||||||
|
@ -203,12 +212,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;
|
||||||
|
@ -223,7 +232,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)
|
||||||
{
|
{
|
||||||
|
@ -231,7 +240,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>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -245,11 +254,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;
|
||||||
|
|
||||||
|
@ -269,7 +287,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)
|
||||||
{
|
{
|
||||||
|
@ -277,7 +295,7 @@ 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>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,6 +325,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 +350,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 +388,45 @@ 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)
|
public void BroadcastPacketsAroundActor(Actor actor, List<SubPacket> packets)
|
||||||
{
|
{
|
||||||
foreach (SubPacket packet in packets)
|
foreach (SubPacket packet in packets)
|
||||||
|
@ -384,7 +443,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);
|
||||||
|
@ -395,6 +454,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SpawnActor(SpawnLocation location)
|
public void SpawnActor(SpawnLocation location)
|
||||||
|
{
|
||||||
|
lock (mActorList)
|
||||||
{
|
{
|
||||||
ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId);
|
ActorClass actorClass = Server.GetWorldManager().GetActorClass(location.classId);
|
||||||
|
|
||||||
|
@ -410,12 +471,16 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
|
|
||||||
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 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)
|
||||||
|
{
|
||||||
|
lock (mActorList)
|
||||||
{
|
{
|
||||||
ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId);
|
ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId);
|
||||||
|
|
||||||
|
@ -423,22 +488,31 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
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, rot, state, animId, null);
|
Npc npc;
|
||||||
|
if (isMob)
|
||||||
|
npc = new BattleNpc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null);
|
||||||
|
else
|
||||||
|
npc = new Npc(mActorList.Count + 1, actorClass, uniqueId, this, x, y, z, rot, state, animId, null);
|
||||||
|
|
||||||
npc.LoadEventConditions(actorClass.eventConditions);
|
npc.LoadEventConditions(actorClass.eventConditions);
|
||||||
|
npc.SetMaxHP(100);
|
||||||
|
npc.SetHP(100);
|
||||||
|
npc.ResetMoveSpeeds();
|
||||||
|
|
||||||
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)
|
||||||
|
{
|
||||||
|
lock (mActorList)
|
||||||
{
|
{
|
||||||
ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId);
|
ActorClass actorClass = Server.GetWorldManager().GetActorClass(classId);
|
||||||
|
|
||||||
|
@ -460,6 +534,17 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -579,12 +664,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,8 @@ namespace FFXIVClassic_Map_Server.actors.area
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CheckDestroy()
|
public void CheckDestroy()
|
||||||
|
{
|
||||||
|
lock (mActorList)
|
||||||
{
|
{
|
||||||
if (isContentFinished)
|
if (isContentFinished)
|
||||||
{
|
{
|
||||||
|
@ -52,6 +54,7 @@ namespace FFXIVClassic_Map_Server.actors.area
|
||||||
GetParentZone().DeleteContentArea(this);
|
GetParentZone().DeleteContentArea(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -96,23 +119,38 @@ namespace FFXIVClassic_Map_Server.actors.area
|
||||||
}
|
}
|
||||||
|
|
||||||
public Actor FindActorInZone(uint id)
|
public Actor FindActorInZone(uint id)
|
||||||
|
{
|
||||||
|
lock (mActorList)
|
||||||
{
|
{
|
||||||
if (!mActorList.ContainsKey(id))
|
if (!mActorList.ContainsKey(id))
|
||||||
{
|
{
|
||||||
foreach(Dictionary<uint, PrivateArea> paList in privateAreas.Values)
|
foreach (Dictionary<uint, PrivateArea> paList in privateAreas.Values)
|
||||||
{
|
{
|
||||||
foreach(PrivateArea pa in paList.Values)
|
foreach (PrivateArea pa in paList.Values)
|
||||||
{
|
{
|
||||||
Actor actor = pa.FindActorInArea(id);
|
Actor actor = pa.FindActorInArea(id);
|
||||||
if (actor != null)
|
if (actor != null)
|
||||||
return actor;
|
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
|
else
|
||||||
return mActorList[id];
|
return mActorList[id];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PrivateAreaContent CreateContentArea(Player starterPlayer, string areaClassPath, string contentScript, string areaName, string directorName, params object[] args)
|
public PrivateAreaContent CreateContentArea(Player starterPlayer, string areaClassPath, string contentScript, string areaName, string directorName, params object[] args)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
98
FFXIVClassic Map Server/actors/chara/Modifier.cs
Normal file
98
FFXIVClassic Map Server/actors/chara/Modifier.cs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
NAMEPLATE_SHOWN = 0,
|
||||||
|
TARGETABLE = 1,
|
||||||
|
NAMEPLATE_SHOWN2 = 2,
|
||||||
|
//NAMEPLATE_SHOWN2 = 3,
|
||||||
|
|
||||||
|
Strength = 3,
|
||||||
|
Vitality = 4,
|
||||||
|
Dexterity = 5,
|
||||||
|
Intelligence = 6,
|
||||||
|
Mind = 7,
|
||||||
|
Piety = 8,
|
||||||
|
|
||||||
|
ResistFire = 9,
|
||||||
|
ResistIce = 10,
|
||||||
|
ResistWind = 11,
|
||||||
|
ResistLightning = 12,
|
||||||
|
ResistEarth = 13,
|
||||||
|
ResistWater = 14,
|
||||||
|
|
||||||
|
Accuracy = 15,
|
||||||
|
Evasion = 16,
|
||||||
|
Attack = 17,
|
||||||
|
Defense = 18, //Is there a magic defense stat? 19 maybe?
|
||||||
|
MagicAttack = 23,
|
||||||
|
MagicHeal = 24,
|
||||||
|
MagicEnhancePotency = 25,
|
||||||
|
MagicEnfeeblingPotency = 26,
|
||||||
|
|
||||||
|
MagicAccuracy = 27,
|
||||||
|
MagicEvasion = 28,
|
||||||
|
|
||||||
|
CraftProcessing = 30,
|
||||||
|
CraftMagicProcessing = 31,
|
||||||
|
CraftProcessControl = 32,
|
||||||
|
|
||||||
|
HarvestPotency = 33,
|
||||||
|
HarvestLimit = 34,
|
||||||
|
HarvestRate = 35,
|
||||||
|
|
||||||
|
None = 36,
|
||||||
|
Hp = 37,
|
||||||
|
HpPercent = 38,
|
||||||
|
Mp = 39,
|
||||||
|
MpPercent = 40,
|
||||||
|
Tp = 41,
|
||||||
|
TpPercent = 42,
|
||||||
|
Regen = 43,
|
||||||
|
Refresh = 44,
|
||||||
|
|
||||||
|
AttackRange = 45,
|
||||||
|
Speed = 46,
|
||||||
|
AttackDelay = 47,
|
||||||
|
|
||||||
|
Raise = 48,
|
||||||
|
MinimumHpLock = 49, // hp cannot fall below this value
|
||||||
|
AttackType = 50, // slashing, piercing, etc
|
||||||
|
BlockRate = 51,
|
||||||
|
Block = 52,
|
||||||
|
CritRating = 53,
|
||||||
|
HasShield = 54, // Need this because shields are required for blocks. Could have used BlockRate or Block but BlockRate is provided by Gallant Sollerets and Block is provided by some buffs.
|
||||||
|
HitCount = 55, // Amount of hits in an auto attack. Usually 1, 2 for h2h, 3 with spinning heel
|
||||||
|
|
||||||
|
//Flat percent increases to these rates. Probably a better way to do this
|
||||||
|
RawEvadeRate = 56,
|
||||||
|
RawParryRate = 57,
|
||||||
|
RawBlockRate = 58,
|
||||||
|
RawResistRate = 59,
|
||||||
|
RawHitRate = 60,
|
||||||
|
RawCritRate = 61,
|
||||||
|
|
||||||
|
DamageTakenDown = 62, // Percent damage taken down
|
||||||
|
StoreTP = 63, //.1% extra tp per point. Lancer trait is 50 StoreTP
|
||||||
|
PhysicalCritRate = 64, //CritRating but only for physical attacks. Increases chance of critting.
|
||||||
|
PhysicalCritEvasion = 65, //Opposite of CritRating. Reduces chance of being crit by phyiscal attacks
|
||||||
|
PhysicalCritAttack = 66, //Increases damage done by Physical Critical hits
|
||||||
|
PhysicalCritResilience = 67, //Decreases damage taken by Physical Critical hits
|
||||||
|
Parry = 68, //Increases chance to parry
|
||||||
|
MagicCritPotency = 69, //Increases
|
||||||
|
Regain = 70, //TP regen, should be -90 out of combat, Invigorate sets to 100+ depending on traits
|
||||||
|
RegenDown = 71, //Damage over time effects. Separate from normal Regen because of how they are displayed in game
|
||||||
|
Stoneskin = 72, //Nullifies damage
|
||||||
|
MinimumTpLock = 73, //Don't let TP fall below this, used in openings
|
||||||
|
KnockbackImmune = 74 //Immune to knockback effects when above 0
|
||||||
|
}
|
||||||
|
}
|
58
FFXIVClassic Map Server/actors/chara/ModifierList.cs
Normal file
58
FFXIVClassic Map Server/actors/chara/ModifierList.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
FFXIVClassic Map Server/actors/chara/SubState.cs
Normal file
56
FFXIVClassic Map Server/actors/chara/SubState.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
382
FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs
Normal file
382
FFXIVClassic Map Server/actors/chara/ai/AIContainer.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
395
FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs
Normal file
395
FFXIVClassic Map Server/actors/chara/ai/BattleCommand.cs
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
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 ushort mpCost;
|
||||||
|
public ushort tpCost;
|
||||||
|
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 target
|
||||||
|
public bool IsValidMainTarget(Character user, Character target)
|
||||||
|
{
|
||||||
|
targetFind = new TargetFind(user);
|
||||||
|
|
||||||
|
if (aoeType == TargetFindAOEType.Box)
|
||||||
|
{
|
||||||
|
targetFind.SetAOEBox(validTarget, aoeTarget, aoeRange, rangeWidth, aoeRotateAngle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetFind.SetAOEType(validTarget, aoeType, aoeTarget, aoeRange, aoeMinRange, rangeHeight, aoeRotateAngle, aoeConeAngle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
worldMasterTextId
|
||||||
|
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,
|
||||||
|
32556 unable to execute [weaponskill]. Conditions for use are not met.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// cant target dead
|
||||||
|
if ((mainTarget & (ValidTarget.Corpse | ValidTarget.CorpseOnly)) == 0 && target.IsDead())
|
||||||
|
{
|
||||||
|
// cannot be perfomed on
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32512, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//level too high
|
||||||
|
if (level > user.GetLevel())
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32527, 0x20, (uint)id);
|
||||||
|
//return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Proc requirement
|
||||||
|
if (procRequirement != BattleCommandProcRequirement.None && !user.charaWork.battleTemp.timingCommandFlag[(int) procRequirement - 1])
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//costs too much tp
|
||||||
|
if (CalculateTpCost(user) > user.GetTP())
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32546, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: calculate cost based on modifiers also (probably in BattleUtils)
|
||||||
|
if (BattleUtils.CalculateSpellCost(user, target, this) > user.GetMP())
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32545, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: check target requirements
|
||||||
|
if (requirements != BattleCommandRequirements.None)
|
||||||
|
{
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
// Unable to execute [@SHEET(xtx/command,$E8(1),2)]. Conditions for use are not met.
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32556, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// todo: i dont care to message for each scenario, just the most common ones..
|
||||||
|
if ((mainTarget & ValidTarget.CorpseOnly) != 0)
|
||||||
|
{
|
||||||
|
if (target != null && target.IsAlive())
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32513, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mainTarget & ValidTarget.Enemy) != 0)
|
||||||
|
{
|
||||||
|
if (target == user || target != null &&
|
||||||
|
user.allegiance == target.allegiance)
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32519, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mainTarget & ValidTarget.Ally) != 0)
|
||||||
|
{
|
||||||
|
if (target == null || target.allegiance != user.allegiance)
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mainTarget & ValidTarget.PartyMember) != 0)
|
||||||
|
{
|
||||||
|
if (target == null || target.currentParty != user.currentParty)
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32547, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((mainTarget & ValidTarget.Player) != 0)
|
||||||
|
{
|
||||||
|
if (!(target is Player))
|
||||||
|
{
|
||||||
|
if (user is Player)
|
||||||
|
((Player)user).SendGameMessage(Server.GetWorldManager().GetActor(), 32517, 0x20, (uint)id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;// targetFind.CanTarget(target, true, true, true); //this will be done later
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ushort CalculateTpCost(Character user)
|
||||||
|
{
|
||||||
|
ushort 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 = (ushort)Math.Ceiling((float)tpCost * (1 - player.playerWork.comboCostBonusRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Character> GetTargets()
|
||||||
|
{
|
||||||
|
return targetFind?.GetTargets<Character>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort GetCommandType()
|
||||||
|
{
|
||||||
|
return (ushort) commandType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
FFXIVClassic Map Server/actors/chara/ai/BattleTrait.cs
Normal file
28
FFXIVClassic Map Server/actors/chara/ai/BattleTrait.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs
Normal file
91
FFXIVClassic Map Server/actors/chara/ai/HateContainer.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
657
FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs
Normal file
657
FFXIVClassic Map Server/actors/chara/ai/StatusEffect.cs
Normal file
|
@ -0,0 +1,657 @@
|
||||||
|
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,
|
||||||
|
Silent = 1 << 0, // dont display effect loss message
|
||||||
|
|
||||||
|
//Loss flags
|
||||||
|
LoseOnDeath = 1 << 1, // effects removed on death
|
||||||
|
LoseOnZoning = 1 << 2, // effects removed on zoning
|
||||||
|
LoseOnEsuna = 1 << 3, // effects which can be removed with esuna (debuffs)
|
||||||
|
LoseOnDispel = 1 << 4, // some buffs which player might be able to dispel from mob
|
||||||
|
LoseOnLogout = 1 << 5, // effects removed on logging out
|
||||||
|
LoseOnAttacking = 1 << 6, // effects removed when owner attacks another entity
|
||||||
|
LoseOnCastStart = 1 << 7, // effects removed when owner starts casting
|
||||||
|
LoseOnAggro = 1 << 8, // effects removed when owner gains enmity (swiftsong)
|
||||||
|
|
||||||
|
//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
|
||||||
|
|
||||||
|
Stealth = 1 << 29, // sneak/invis
|
||||||
|
Stance = 1 << 30, // 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 silent = false; // do i send a message on losing effect
|
||||||
|
private bool hidden = false;
|
||||||
|
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.extra = effect.extra;
|
||||||
|
this.script = effect.script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StatusEffect(uint id, string name, uint flags, uint overwrite, uint tickMs)
|
||||||
|
{
|
||||||
|
this.id = (StatusEffectId)id;
|
||||||
|
this.name = name;
|
||||||
|
this.flags = (StatusEffectFlags)flags;
|
||||||
|
this.overwrite = (StatusEffectOverwrite)overwrite;
|
||||||
|
this.tickMs = tickMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true when duration has elapsed
|
||||||
|
public bool Update(DateTime tick)
|
||||||
|
{
|
||||||
|
if (tickMs != 0 && (tick - lastTick).TotalMilliseconds >= tickMs)
|
||||||
|
{
|
||||||
|
lastTick = tick;
|
||||||
|
if (LuaEngine.CallLuaStatusEffectFunction(this.owner, this, "onTick", this.owner, this) > 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 GetSilent()
|
||||||
|
{
|
||||||
|
return silent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetHidden()
|
||||||
|
{
|
||||||
|
return hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStartTime(DateTime time)
|
||||||
|
{
|
||||||
|
this.startTime = time;
|
||||||
|
this.lastTick = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetEndTime(DateTime time)
|
||||||
|
{
|
||||||
|
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 SetSilent(bool silent)
|
||||||
|
{
|
||||||
|
this.silent = silent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetHidden(bool hidden)
|
||||||
|
{
|
||||||
|
this.hidden = hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetAnimation(uint hitEffect)
|
||||||
|
{
|
||||||
|
animationEffect = (HitEffect)hitEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GetAnimation()
|
||||||
|
{
|
||||||
|
return (uint)animationEffect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
440
FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs
Normal file
440
FFXIVClassic Map Server/actors/chara/ai/StatusEffectContainer.cs
Normal file
|
@ -0,0 +1,440 @@
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
//Regen/Refresh/Regain effects tick every 3 seconds
|
||||||
|
if ((DateTime.Now - lastTick).Seconds >= 3)
|
||||||
|
{
|
||||||
|
RegenTick(tick);
|
||||||
|
lastTick = DateTime.Now;
|
||||||
|
}
|
||||||
|
// list of effects to remove
|
||||||
|
|
||||||
|
// if (owner is Player) UpdateTimeAtIndex(4, 4294967295);
|
||||||
|
var removeEffects = new List<StatusEffect>();
|
||||||
|
for (int i = 0; i < effects.Values.Count; i++)
|
||||||
|
{
|
||||||
|
// effect's update function returns true if effect has completed
|
||||||
|
if (effects.Values.ElementAt(i).Update(tick))
|
||||||
|
removeEffects.Add(effects.Values.ElementAt(i));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove effects from this list
|
||||||
|
foreach (var effect in removeEffects)
|
||||||
|
{
|
||||||
|
RemoveStatusEffect(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendUpdate)
|
||||||
|
{
|
||||||
|
owner.zone.BroadcastPacketsAroundActor(owner, owner.GetActorStatusPackets());
|
||||||
|
sendUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//regen/refresh/regain
|
||||||
|
public void RegenTick(DateTime tick)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
CommandResult action = new CommandResult(owner.actorId, 30331, (uint)(HitEffect.HitEffectType | HitEffect.Hit), dotTick);
|
||||||
|
utils.BattleUtils.HandleStoneskin(owner, action);
|
||||||
|
// todo: figure out how to make red numbers appear for enemies getting hurt by dots
|
||||||
|
//owner.DelHP(action.amount);
|
||||||
|
utils.BattleUtils.DamageTarget(owner, owner, action, null);
|
||||||
|
owner.DoBattleAction(0, 0, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
//DoTs are the only effect to show numbers, so that doesnt need to be handled for these
|
||||||
|
owner.AddHP(regenTick);
|
||||||
|
owner.AddMP(refreshtick);
|
||||||
|
owner.AddTP(regainTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasStatusEffect(uint id)
|
||||||
|
{
|
||||||
|
return effects.ContainsKey(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasStatusEffect(StatusEffectId id)
|
||||||
|
{
|
||||||
|
return effects.ContainsKey((uint)id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandResult AddStatusForCommandResult(uint id, byte tier = 1, ulong magnitude = 0, uint duration = 0)
|
||||||
|
{
|
||||||
|
CommandResult action = null;
|
||||||
|
|
||||||
|
if (AddStatusEffect(id, tier, magnitude, duration))
|
||||||
|
action = new CommandResult(owner.actorId, 30328, id | (uint)HitEffect.StatusEffectType);
|
||||||
|
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(uint id)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
|
||||||
|
return AddStatusEffect(se, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(uint id, byte tier)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
|
||||||
|
se.SetTier(tier);
|
||||||
|
|
||||||
|
return AddStatusEffect(se, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(uint id, byte tier, double magnitude)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
|
||||||
|
se.SetMagnitude(magnitude);
|
||||||
|
se.SetTier(tier);
|
||||||
|
|
||||||
|
return AddStatusEffect(se, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(uint id, byte tier, double magnitude, uint duration, int tickMs = 3000)
|
||||||
|
{
|
||||||
|
var se = Server.GetWorldManager().GetStatusEffect(id);
|
||||||
|
if (se != null)
|
||||||
|
{
|
||||||
|
se.SetDuration(duration);
|
||||||
|
se.SetStartTime(DateTime.Now);
|
||||||
|
se.SetOwner(owner);
|
||||||
|
}
|
||||||
|
return AddStatusEffect(se ?? new StatusEffect(this.owner, id, magnitude, 3000, duration, tier), owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AddStatusEffect(StatusEffect newEffect, Character source, bool silent = false, bool hidden = false)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
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 (effect != null && (!silent || !effect.GetSilent() || (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0))
|
||||||
|
{
|
||||||
|
// todo: send packet to client with effect added message
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
if(newEffect.script != null)
|
||||||
|
newEffect.CallLuaFunction(this.owner, "onGain", this.owner, newEffect);
|
||||||
|
else
|
||||||
|
LuaEngine.CallLuaStatusEffectFunction(this.owner, newEffect, "onGain", this.owner, newEffect);
|
||||||
|
effects.Add(newEffect.GetStatusEffectId(), newEffect);
|
||||||
|
//newEffect.SetSilent(silent);
|
||||||
|
newEffect.SetHidden(hidden);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveStatusEffect(StatusEffect effect, bool silent = false)
|
||||||
|
{
|
||||||
|
bool removedEffect = false;
|
||||||
|
if (effect != null && effects.ContainsKey(effect.GetStatusEffectId()))
|
||||||
|
{
|
||||||
|
// send packet to client with effect remove message
|
||||||
|
if (!silent && !effect.GetSilent() && (effect.GetFlags() & (uint)StatusEffectFlags.Silent) == 0)
|
||||||
|
{
|
||||||
|
owner.DoBattleAction(0, 0, new CommandResult(owner.actorId, 30331, effect.GetStatusEffectId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
//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());
|
||||||
|
if(effect.script != null)
|
||||||
|
effect.CallLuaFunction(owner, "onLose", owner, effect);
|
||||||
|
else
|
||||||
|
LuaEngine.CallLuaStatusEffectFunction(this.owner, effect, "onLose", this.owner, effect);
|
||||||
|
owner.RecalculateStats();
|
||||||
|
sendUpdate = true;
|
||||||
|
removedEffect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return removedEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveStatusEffect(uint effectId, bool silent = false)
|
||||||
|
{
|
||||||
|
bool removedEffect = false;
|
||||||
|
foreach (var effect in effects.Values)
|
||||||
|
{
|
||||||
|
if (effect.GetStatusEffectId() == effectId)
|
||||||
|
{
|
||||||
|
RemoveStatusEffect(effect, effect.GetSilent() || silent);
|
||||||
|
removedEffect = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removedEffect;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Remove status effect and return the CommandResult message instead of sending it immediately
|
||||||
|
public CommandResult RemoveStatusEffectForCommandResult(uint effectId, ushort worldMasterTextId = 30331)
|
||||||
|
{
|
||||||
|
CommandResult action = null;
|
||||||
|
if (RemoveStatusEffect(effectId, true))
|
||||||
|
action = new CommandResult(owner.actorId, worldMasterTextId, effectId);
|
||||||
|
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove status effect and return the CommandResult message instead of sending it immediately
|
||||||
|
public CommandResult RemoveStatusEffectForCommandResult(StatusEffect effect, ushort worldMasterTextId = 30331)
|
||||||
|
{
|
||||||
|
CommandResult action = null;
|
||||||
|
if (RemoveStatusEffect(effect, true))
|
||||||
|
action = new CommandResult(owner.actorId, worldMasterTextId, effect.GetStatusEffectId());
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, bool silent = false)
|
||||||
|
{
|
||||||
|
// build list of effects to remove
|
||||||
|
var removeEffects = GetStatusEffectsByFlag(flags);
|
||||||
|
|
||||||
|
// remove effects from main list
|
||||||
|
foreach (var effect in removeEffects)
|
||||||
|
RemoveStatusEffect(effect, silent);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, 30328, (uint) HitEffect.StatusEffectType | newEffectId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,412 @@
|
||||||
|
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.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.Speed, 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.statusEffects.HasStatusEffectsByFlag((uint)StatusEffectFlags.Stealth);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
211
FFXIVClassic Map Server/actors/chara/ai/helpers/PathFind.cs
Normal file
211
FFXIVClassic Map Server/actors/chara/ai/helpers/PathFind.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
462
FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs
Normal file
462
FFXIVClassic Map Server/actors/chara/ai/helpers/TargetFind.cs
Normal file
|
@ -0,0 +1,462 @@
|
||||||
|
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
|
||||||
|
{
|
||||||
|
// https://github.com/Windower/POLUtils/blob/master/PlayOnline.FFXI/Enums.cs
|
||||||
|
[Flags]
|
||||||
|
public enum ValidTarget : ushort
|
||||||
|
{
|
||||||
|
None = 0x00,
|
||||||
|
Self = 0x01,
|
||||||
|
Player = 0x02,
|
||||||
|
PartyMember = 0x04,
|
||||||
|
Ally = 0x08,
|
||||||
|
NPC = 0x10,
|
||||||
|
Enemy = 0x20,
|
||||||
|
Unknown = 0x40,
|
||||||
|
Object = 0x60,
|
||||||
|
CorpseOnly = 0x80,
|
||||||
|
Corpse = 0x9D // CorpseOnly + NPC + Ally + Partymember + Self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 masterTarget; // if target 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 an cone AOEs and the position where line aoes come out
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
this.owner = owner;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
//Curaga starts with lowest health players, so the targets are definitely sorted at least for some abilities
|
||||||
|
//Other aoe abilities might be sorted by distance?
|
||||||
|
//Protect is random
|
||||||
|
targets.Sort(delegate (Character a, Character b) { return a.GetHP().CompareTo(b.GetHP()); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
|
//This skill can't be used on self and target is self, return false
|
||||||
|
if ((validTarget & ValidTarget.Self) == 0 && target == owner)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//This skill can't be used on NPCs and target is an NPC, return false
|
||||||
|
if ((validTarget & ValidTarget.NPC) == 0 && target.isStatic)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//This skill can't be used on corpses and target is dead, return false
|
||||||
|
if ((validTarget & ValidTarget.Corpse) == 0 && target.IsDead())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//This skill must be used on Allies and target is not an ally, return false
|
||||||
|
if ((validTarget & ValidTarget.Ally) != 0 && target.allegiance != owner.allegiance)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
|
||||||
|
//This skill can't be used on players and target is a player, return false
|
||||||
|
//Do we need a player flag? Ally/Enemy flags probably serve the same purpose
|
||||||
|
//if ((validTarget & ValidTarget.Player) == 0 && target is Player)
|
||||||
|
//return false;
|
||||||
|
|
||||||
|
|
||||||
|
//This skill must be used on enemies an target is not an enemy
|
||||||
|
if ((validTarget & ValidTarget.Enemy) != 0 && target.allegiance == owner.allegiance)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//This skill must be used on a party member and target is not in owner's party, return false
|
||||||
|
if ((validTarget & ValidTarget.PartyMember) != 0 && target.currentParty != owner.currentParty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//This skill must be used on a corpse and target is alive, return false
|
||||||
|
if ((validTarget & ValidTarget.CorpseOnly) != 0 && target.IsAlive())
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
FFXIVClassic Map Server/actors/chara/ai/state/AbilityState.cs
Normal file
153
FFXIVClassic Map Server/actors/chara/ai/state/AbilityState.cs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
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 = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "onAbilityPrepare", owner, target, skill);
|
||||||
|
|
||||||
|
this.target = target != null ? target : owner;
|
||||||
|
|
||||||
|
if (returnCode == 0)
|
||||||
|
{
|
||||||
|
OnStart();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorResult = null;
|
||||||
|
interrupt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
|
{
|
||||||
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "ability", "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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs
Normal file
206
FFXIVClassic Map Server/actors/chara/ai/state/AttackState.cs
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
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();
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
for (int hitNum = 0; hitNum < 1 /* owner.GetMod((uint) Modifier.HitCount)*/; hitNum++)
|
||||||
|
{
|
||||||
|
CommandResult action = new CommandResult(target.actorId, 0x765D, (uint)HitEffect.Hit, 100, (byte)HitDirection.None, (byte) hitNum);
|
||||||
|
action.commandType = CommandType.AutoAttack;
|
||||||
|
action.actionType = ActionType.Physical;
|
||||||
|
action.actionProperty = (ActionProperty) owner.GetMod(Modifier.AttackType);
|
||||||
|
// evasion, miss, dodge, etc to be handled in script, calling helpers from scripts/weaponskills.lua
|
||||||
|
// temporary evade/miss/etc function to test hit effects
|
||||||
|
action.DoAction(owner, target, null, actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: this is fuckin stupid, probably only need *one* error packet, not an error for each action
|
||||||
|
CommandResult[] errors = (CommandResult[])actions.GetList().ToArray().Clone();
|
||||||
|
CommandResult error = null;// new BattleAction(0, null, 0, 0);
|
||||||
|
//owner.DoActions(null, actions.GetList(), ref error);
|
||||||
|
//owner.OnAttack(this, actions[0], ref errorResult);
|
||||||
|
var anim = (uint)(17 << 24 | 1 << 12);
|
||||||
|
owner.DoBattleAction(22104, anim, actions.GetList());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
FFXIVClassic Map Server/actors/chara/ai/state/DeathState.cs
Normal file
46
FFXIVClassic Map Server/actors/chara/ai/state/DeathState.cs
Normal 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, true);
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
FFXIVClassic Map Server/actors/chara/ai/state/ItemState.cs
Normal file
23
FFXIVClassic Map Server/actors/chara/ai/state/ItemState.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
202
FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs
Normal file
202
FFXIVClassic Map Server/actors/chara/ai/state/MagicState.cs
Normal 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 = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "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 = target != null ? target : owner;
|
||||||
|
|
||||||
|
if (returnCode == 0 && owner.CanCast(this.target, spell))
|
||||||
|
{
|
||||||
|
OnStart();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorResult = new CommandResult(owner.actorId, 32553, 0);
|
||||||
|
interrupt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
|
{
|
||||||
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "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)))
|
||||||
|
{
|
||||||
|
lua.LuaEngine.CallLuaBattleCommandFunction(owner, spell, "magic", "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.CanCast(target, spell) && spell.IsValidMainTarget(owner, target) && !HasMoved();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
FFXIVClassic Map Server/actors/chara/ai/state/State.cs
Normal file
69
FFXIVClassic Map Server/actors/chara/ai/state/State.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
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.target = skill.targetFind.
|
||||||
|
this.skill = Server.GetWorldManager().GetBattleCommand(skillId);
|
||||||
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onSkillPrepare", owner, target, skill);
|
||||||
|
|
||||||
|
if (returnCode == 0 && owner.CanWeaponSkill(target, skill))
|
||||||
|
{
|
||||||
|
OnStart();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorResult = null;
|
||||||
|
interrupt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnStart()
|
||||||
|
{
|
||||||
|
var returnCode = lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "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)
|
||||||
|
//lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onPositional", owner, target, skill);
|
||||||
|
skill.CallLuaFunction(owner, "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)
|
||||||
|
{
|
||||||
|
lua.LuaEngine.CallLuaBattleCommandFunction(owner, skill, "weaponskill", "onCombo", owner, target, skill);
|
||||||
|
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.CanWeaponSkill(target, skill) && skill.IsValidMainTarget(owner, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BattleCommand GetWeaponSkill()
|
||||||
|
{
|
||||||
|
return skill;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Cleanup()
|
||||||
|
{
|
||||||
|
owner.aiContainer.UpdateLastActionTime(skill.animationDurationSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
FFXIVClassic Map Server/actors/chara/ai/utils/AttackUtils.cs
Normal file
24
FFXIVClassic Map Server/actors/chara/ai/utils/AttackUtils.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
883
FFXIVClassic Map Server/actors/chara/ai/utils/BattleUtils.cs
Normal file
883
FFXIVClassic Map Server/actors/chara/ai/utils/BattleUtils.cs
Normal file
|
@ -0,0 +1,883 @@
|
||||||
|
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> SingleHitTypeTextIds = new Dictionary<HitType, ushort>()
|
||||||
|
{
|
||||||
|
{ HitType.Miss, 30311 },
|
||||||
|
{ HitType.Evade, 30310 },
|
||||||
|
{ HitType.Parry, 30308 },
|
||||||
|
{ HitType.Block, 30306 },
|
||||||
|
{ HitType.Resist, 30310 }, //Resists seem to use the evade text id
|
||||||
|
{ HitType.Hit, 30301 },
|
||||||
|
{ HitType.Crit, 30302 }
|
||||||
|
};
|
||||||
|
|
||||||
|
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.Resist, 0 }, //No spells are multi-hit, so this doesn't exist
|
||||||
|
{ HitType.Hit, 30443 }, //[Target] tales x points of damage
|
||||||
|
{ HitType.Crit, 30444 } //Critical! [Target] takes x points of damage.
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Dictionary<HitType, HitEffect> HitTypeEffects = new Dictionary<HitType, HitEffect>()
|
||||||
|
{
|
||||||
|
{ HitType.Miss, 0 },
|
||||||
|
{ HitType.Evade, HitEffect.Evade },
|
||||||
|
{ HitType.Parry, HitEffect.Parry },
|
||||||
|
{ HitType.Block, HitEffect.Block },
|
||||||
|
{ HitType.Resist, HitEffect.RecoilLv1 },//Probably don't need this, resists are handled differently to the rest
|
||||||
|
{ HitType.Hit, HitEffect.Hit },
|
||||||
|
{ 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)
|
||||||
|
{
|
||||||
|
double percentResist = 0.5;
|
||||||
|
|
||||||
|
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, 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);
|
||||||
|
attacker.OnDamageDealt(defender, action, actionContainer);
|
||||||
|
defender.OnDamageTaken(attacker, 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, CommandResult action, CommandResultContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
target.AddHP(action.amount);
|
||||||
|
|
||||||
|
target.statusEffects.CallLuaFunctionByFlag((uint) StatusEffectFlags.ActivateOnHealed, "onHealed", caster, target, 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.HasShield) != 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.HasShield) == 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryResist(Character attacker, Character defender, BattleCommand skill, CommandResult action)
|
||||||
|
{
|
||||||
|
if ((Program.Random.NextDouble() * 100) <= action.resistRate)
|
||||||
|
{
|
||||||
|
action.hitType = HitType.Resist;
|
||||||
|
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 = SingleHitTypeTextIds;
|
||||||
|
|
||||||
|
//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, action, actionContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FinishActionSpell(Character attacker, Character defender, BattleCommand skill, CommandResult action, CommandResultContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
//Determine the hit type of the action
|
||||||
|
if (!TryMiss(attacker, defender, skill, action))
|
||||||
|
{
|
||||||
|
HandleStoneskin(defender, action);
|
||||||
|
if (!TryCrit(attacker, defender, skill, action))
|
||||||
|
if (!TryResist(attacker, defender, skill, action))
|
||||||
|
action.hitType = HitType.Hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//There are no multi-hit spells
|
||||||
|
action.worldMasterTextId = SingleHitTypeTextIds[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, 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, 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 |= HitTypeEffects[hitType];
|
||||||
|
|
||||||
|
//For combos that land, add the combo effect
|
||||||
|
if (skill != null && skill.isCombo && hitType > HitType.Evade && hitType != HitType.Evade && !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))
|
||||||
|
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;
|
||||||
|
|
||||||
|
//Recoil levels for spells are a bit different than physical. Recoil levels are used for resists.
|
||||||
|
//Lv1 is for larger resists, Lv2 is for smaller resists and Lv3 is for no resists. Crit is still used for crits
|
||||||
|
if (hitType == HitType.Resist)
|
||||||
|
{
|
||||||
|
//todo: calculate resist levels and figure out what the difference between Lv1 and 2 in retail was. For now assuming a full resist with 0 damage dealt is Lv1, all other resists Lv2
|
||||||
|
if (action.amount == 0)
|
||||||
|
hitEffect |= HitEffect.RecoilLv1;
|
||||||
|
else
|
||||||
|
hitEffect |= HitEffect.RecoilLv2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
hitEffect |= HitEffect.RecoilLv3;
|
||||||
|
|
||||||
|
hitEffect |= HitTypeEffects[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 (hitType >= HitType.Block)
|
||||||
|
{
|
||||||
|
//Protect / Shell only show on physical/ magical attacks respectively.
|
||||||
|
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Shell))
|
||||||
|
if (action != null)
|
||||||
|
hitEffect |= HitEffect.Shell;
|
||||||
|
|
||||||
|
if (defender.statusEffects.HasStatusEffect(StatusEffectId.Stoneskin))
|
||||||
|
if (action != null)
|
||||||
|
hitEffect |= HitEffect.Stoneskin;
|
||||||
|
}
|
||||||
|
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.hitType > HitType.Evade && action.hitType != HitType.Resist) && 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];
|
||||||
|
|
||||||
|
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.SetSilent(true);
|
||||||
|
newChain.SetDuration(timeLimit);
|
||||||
|
newChain.SetTier((byte)(Math.Min(expChainNumber + 1, 255)));
|
||||||
|
attacker.statusEffects.AddStatusEffect(newChain, attacker, true, true);
|
||||||
|
|
||||||
|
actionContainer?.AddEXPActions(attacker.AddExp(baseExp, (byte)attacker.GetClass(), (byte)(totalBonus.Min(255))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
25
FFXIVClassic Map Server/actors/chara/npc/Ally.cs
Normal file
25
FFXIVClassic Map Server/actors/chara/npc/Ally.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
463
FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs
Normal file
463
FFXIVClassic Map Server/actors/chara/npc/BattleNpc.cs
Normal file
|
@ -0,0 +1,463 @@
|
||||||
|
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 CanCast(Character target, BattleCommand spell)
|
||||||
|
{
|
||||||
|
// 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 override bool CanWeaponSkill(Character target, BattleCommand skill)
|
||||||
|
{
|
||||||
|
// todo:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanUseAbility(Character target, BattleCommand ability)
|
||||||
|
{
|
||||||
|
// todo:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.mp = charaWork.parameterSave.mpMax;
|
||||||
|
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, CommandResult action, CommandResultContainer actionContainer = null)
|
||||||
|
{
|
||||||
|
if (GetMobMod((uint)MobModifier.DefendScript) != 0)
|
||||||
|
lua.LuaEngine.CallLuaBattleFunction(this, "onDamageTaken", this, attacker, action.amount);
|
||||||
|
base.OnDamageTaken(attacker, action, actionContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs
Normal file
40
FFXIVClassic Map Server/actors/chara/npc/MobModifier.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,9 +17,21 @@ 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;
|
||||||
|
@ -29,6 +41,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
private uint layout, instance;
|
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))
|
||||||
|
@ -50,6 +63,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
|
|
||||||
this.actorClassId = actorClass.actorClassId;
|
this.actorClassId = actorClass.actorClassId;
|
||||||
|
|
||||||
|
this.currentSubState.motionPack = (ushort) animationId;
|
||||||
|
|
||||||
LoadNpcAppearance(actorClass.actorClassId);
|
LoadNpcAppearance(actorClass.actorClassId);
|
||||||
|
|
||||||
className = actorClass.classPath.Substring(actorClass.classPath.LastIndexOf("/") + 1);
|
className = actorClass.classPath.Substring(actorClass.classPath.LastIndexOf("/") + 1);
|
||||||
|
@ -57,13 +72,15 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
|
|
||||||
charaWork.battleSave.potencial = 1.0f;
|
charaWork.battleSave.potencial = 1.0f;
|
||||||
|
|
||||||
|
// todo: these really need to be read from db etc
|
||||||
|
{
|
||||||
charaWork.parameterSave.state_mainSkill[0] = 3;
|
charaWork.parameterSave.state_mainSkill[0] = 3;
|
||||||
charaWork.parameterSave.state_mainSkill[2] = 3;
|
charaWork.parameterSave.state_mainSkill[2] = 3;
|
||||||
charaWork.parameterSave.state_mainSkillLevel = 2;
|
charaWork.parameterSave.state_mainSkillLevel = 1;
|
||||||
|
|
||||||
charaWork.parameterSave.hp[0] = 500;
|
|
||||||
charaWork.parameterSave.hpMax[0] = 500;
|
|
||||||
|
|
||||||
|
charaWork.parameterSave.hp[0] = 80;
|
||||||
|
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);
|
||||||
|
|
||||||
|
@ -84,8 +101,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
isStatic = true;
|
isStatic = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerateActorName((int)actorNumber);
|
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 layout, uint instance)
|
public Npc(int actorNumber, ActorClass actorClass, string uniqueId, Area spawnedArea, float posX, float posY, float posZ, float rot, uint layout, uint instance)
|
||||||
|
@ -124,6 +141,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
|
|
||||||
GenerateActorName((int)actorNumber);
|
GenerateActorName((int)actorNumber);
|
||||||
|
this.aiContainer = new AIContainer(this, null, new PathFind(this), new TargetFind(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubPacket CreateAddActorPacket()
|
public SubPacket CreateAddActorPacket()
|
||||||
|
@ -142,8 +160,8 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
isStatic = true;
|
isStatic = true;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// charaWork.property[2] = 1;
|
//charaWork.property[2] = 1;
|
||||||
// npcWork.hateType = 1;
|
//npcWork.hateType = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lParams == null)
|
if (lParams == null)
|
||||||
|
@ -185,7 +203,7 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
|
|
||||||
subpackets.Add(CreateNamePacket());
|
subpackets.Add(CreateNamePacket());
|
||||||
subpackets.Add(CreateStatePacket());
|
subpackets.Add(CreateStatePacket());
|
||||||
subpackets.Add(CreateIdleAnimationPacket());
|
subpackets.Add(CreateSubStatePacket());
|
||||||
subpackets.Add(CreateInitStatusPacket());
|
subpackets.Add(CreateInitStatusPacket());
|
||||||
subpackets.Add(CreateSetActorIconPacket());
|
subpackets.Add(CreateSetActorIconPacket());
|
||||||
subpackets.Add(CreateIsZoneingPacket());
|
subpackets.Add(CreateIsZoneingPacket());
|
||||||
|
@ -229,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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,11 +410,39 @@ namespace FFXIVClassic_Map_Server.Actors
|
||||||
zone.DespawnActor(this);
|
zone.DespawnActor(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(double deltaTime)
|
public override void Update(DateTime tick)
|
||||||
{
|
{
|
||||||
LuaEngine.GetInstance().CallLuaFunction(null, this, "onUpdate", true, deltaTime);
|
// 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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
24
FFXIVClassic Map Server/actors/chara/npc/Pet.cs
Normal file
24
FFXIVClassic Map Server/actors/chara/npc/Pet.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
using FFXIVClassic.Common;
|
||||||
using FFXIVClassic.Common;
|
|
||||||
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_Map_Server.dataobjects;
|
using FFXIVClassic_Map_Server.dataobjects;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -103,7 +103,7 @@ namespace FFXIVClassic_Map_Server.actors.director
|
||||||
|
|
||||||
List<LuaParam> lparams = CallLuaScript("init", args2);
|
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);
|
||||||
|
@ -128,7 +128,7 @@ namespace FFXIVClassic_Map_Server.actors.director
|
||||||
((GuildleveDirector)this).LoadGuildleve();
|
((GuildleveDirector)this).LoadGuildleve();
|
||||||
}
|
}
|
||||||
|
|
||||||
StartCoroutine("main", this);
|
CallLuaScript("main", this, contentGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartContentGroup()
|
public void StartContentGroup()
|
||||||
|
@ -161,6 +161,9 @@ namespace FFXIVClassic_Map_Server.actors.director
|
||||||
{
|
{
|
||||||
members.Add(actor);
|
members.Add(actor);
|
||||||
|
|
||||||
|
if (actor is Player)
|
||||||
|
((Player)actor).AddDirector(this);
|
||||||
|
|
||||||
if (contentGroup != null)
|
if (contentGroup != null)
|
||||||
contentGroup.AddMember(actor);
|
contentGroup.AddMember(actor);
|
||||||
}
|
}
|
||||||
|
@ -270,6 +273,7 @@ namespace FFXIVClassic_Map_Server.actors.director
|
||||||
{
|
{
|
||||||
if (directorScript != null)
|
if (directorScript != null)
|
||||||
{
|
{
|
||||||
|
directorScript = LuaEngine.LoadScript(String.Format(LuaEngine.FILEPATH_DIRECTORS, directorScriptPath));
|
||||||
if (!directorScript.Globals.Get(funcName).IsNil())
|
if (!directorScript.Globals.Get(funcName).IsNil())
|
||||||
{
|
{
|
||||||
DynValue result = directorScript.Call(directorScript.Globals[funcName], args);
|
DynValue result = directorScript.Call(directorScript.Globals[funcName], args);
|
||||||
|
@ -314,8 +318,5 @@ namespace FFXIVClassic_Map_Server.actors.director
|
||||||
DynValue value = coroutine.Resume(args2);
|
DynValue value = coroutine.Resume(args2);
|
||||||
LuaEngine.GetInstance().ResolveResume(player, coroutine, value);
|
LuaEngine.GetInstance().ResolveResume(player, coroutine, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -42,6 +42,7 @@ namespace FFXIVClassic_Map_Server.actors.group
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
isStarted = true;
|
isStarted = true;
|
||||||
|
|
||||||
SendGroupPacketsAll(members);
|
SendGroupPacketsAll(members);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +51,7 @@ namespace FFXIVClassic_Map_Server.actors.group
|
||||||
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)
|
||||||
|
@ -121,7 +123,6 @@ namespace FFXIVClassic_Map_Server.actors.group
|
||||||
}
|
}
|
||||||
|
|
||||||
session.QueuePacket(GroupMembersEndPacket.buildPacket(session.id, session.GetActor().zoneId, time, this));
|
session.QueuePacket(GroupMembersEndPacket.buildPacket(session.id, session.GetActor().zoneId, time, this));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override uint GetTypeId()
|
public override uint GetTypeId()
|
||||||
|
@ -169,5 +170,9 @@ namespace FFXIVClassic_Map_Server.actors.group
|
||||||
DeleteGroup();
|
DeleteGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<uint> GetMembers()
|
||||||
|
{
|
||||||
|
return members;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace FFXIVClassic_Map_Server.actors.group
|
||||||
public MonsterParty(ulong groupIndex, uint[] initialMonsterMembers)
|
public MonsterParty(ulong groupIndex, uint[] initialMonsterMembers)
|
||||||
: base(groupIndex)
|
: base(groupIndex)
|
||||||
{
|
{
|
||||||
|
if(initialMonsterMembers != null)
|
||||||
for (int i = 0; i < initialMonsterMembers.Length; i++)
|
for (int i = 0; i < initialMonsterMembers.Length; i++)
|
||||||
monsterMembers.Add(initialMonsterMembers[i]);
|
monsterMembers.Add(initialMonsterMembers[i]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,11 +64,25 @@ namespace FFXIVClassic_Map_Server.actors.group
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -481,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;
|
||||||
|
@ -490,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;
|
||||||
|
@ -500,6 +500,7 @@ 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)
|
||||||
|
@ -536,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
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 System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFXIVClassic_Map_Server.actors.chara.npc;
|
using FFXIVClassic_Map_Server.actors.chara.npc;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.dataobjects
|
namespace FFXIVClassic_Map_Server.dataobjects
|
||||||
|
@ -70,6 +64,10 @@ 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;
|
||||||
|
@ -78,13 +76,14 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
||||||
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().GetZone().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)
|
||||||
|
@ -132,11 +131,7 @@ 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;
|
|
||||||
|
|
||||||
QueuePacket(actor.CreatePositionUpdatePacket());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,12 +19,15 @@ namespace FFXIVClassic_Map_Server.dataobjects
|
||||||
|
|
||||||
public void QueuePacket(SubPacket subpacket)
|
public void QueuePacket(SubPacket subpacket)
|
||||||
{
|
{
|
||||||
|
if(SendPacketQueue.Count == 1000)
|
||||||
|
FlushQueuedSendPackets();
|
||||||
|
|
||||||
SendPacketQueue.Add(subpacket);
|
SendPacketQueue.Add(subpacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FlushQueuedSendPackets()
|
public void FlushQueuedSendPackets()
|
||||||
{
|
{
|
||||||
if (!socket.Connected)
|
if (socket == null || !socket.Connected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
while (SendPacketQueue.Count > 0)
|
while (SendPacketQueue.Count > 0)
|
||||||
|
|
|
@ -16,6 +16,8 @@ using FFXIVClassic_Map_Server.lua;
|
||||||
using FFXIVClassic.Common;
|
using FFXIVClassic.Common;
|
||||||
using FFXIVClassic_Map_Server.actors.area;
|
using FFXIVClassic_Map_Server.actors.area;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai.controllers;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.lua
|
namespace FFXIVClassic_Map_Server.lua
|
||||||
{
|
{
|
||||||
|
@ -129,7 +131,210 @@ namespace FFXIVClassic_Map_Server.lua
|
||||||
player.EndEvent();
|
player.EndEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetScriptPath(Actor target)
|
/// <summary>
|
||||||
|
/// // todo: this is dumb, should probably make a function for each action with different default return values
|
||||||
|
/// or just make generic function and pass default value as first arg after functionName
|
||||||
|
/// </summary>
|
||||||
|
public static void CallLuaBattleFunction(Character actor, string functionName, params object[] args)
|
||||||
|
{
|
||||||
|
// todo: should use "scripts/zones/ZONE_NAME/battlenpcs/NAME.lua" instead of scripts/unique
|
||||||
|
string path = "";
|
||||||
|
|
||||||
|
// todo: should we call this for players too?
|
||||||
|
if (actor is Player)
|
||||||
|
{
|
||||||
|
// todo: check this is correct
|
||||||
|
path = FILEPATH_PLAYER;
|
||||||
|
}
|
||||||
|
else if (actor is Npc)
|
||||||
|
{
|
||||||
|
// todo: this is probably unnecessary as im not sure there were pets for players
|
||||||
|
if (!(actor.aiContainer.GetController<PetController>()?.GetPetMaster() is Player))
|
||||||
|
path = String.Format("./scripts/unique/{0}/{1}/{2}.lua", actor.zone.zoneName, actor is BattleNpc ? "Monster" : "PopulaceStandard", ((Npc)actor).GetUniqueId());
|
||||||
|
}
|
||||||
|
// dont wanna throw an error if file doesnt exist
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var script = LoadGlobals();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleFunction [{functionName}] {e.Message}");
|
||||||
|
}
|
||||||
|
DynValue res = new DynValue();
|
||||||
|
|
||||||
|
if (!script.Globals.Get(functionName).IsNil())
|
||||||
|
{
|
||||||
|
res = script.Call(script.Globals.Get(functionName), args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CallLuaStatusEffectFunction(Character actor, StatusEffect effect, string functionName, params object[] args)
|
||||||
|
{
|
||||||
|
// todo: this is stupid, load the actual effect name from db table
|
||||||
|
string path = $"./scripts/effects/{effect.GetName()}.lua";
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaStatusEffectFunction [{functionName}] {e.Message}");
|
||||||
|
}
|
||||||
|
DynValue res = new DynValue();
|
||||||
|
|
||||||
|
if (!script.Globals.Get(functionName).IsNil())
|
||||||
|
{
|
||||||
|
res = script.Call(script.Globals.Get(functionName), args);
|
||||||
|
if (res != null)
|
||||||
|
return (int)res.Number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaStatusEffectFunction [{effect.GetName()}] Unable to find script {path}");
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CallLuaBattleCommandFunction(Character actor, BattleCommand command, string folder, string functionName, params object[] args)
|
||||||
|
{
|
||||||
|
string path = $"./scripts/commands/{folder}/{command.name}.lua";
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{functionName}] {e.Message}");
|
||||||
|
}
|
||||||
|
DynValue res = new DynValue();
|
||||||
|
|
||||||
|
if (!script.Globals.Get(functionName).IsNil())
|
||||||
|
{
|
||||||
|
res = script.Call(script.Globals.Get(functionName), args);
|
||||||
|
if (res != null)
|
||||||
|
return (int)res.Number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = $"./scripts/commands/{folder}/default.lua";
|
||||||
|
//Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}");
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{functionName}] {e.Message}");
|
||||||
|
}
|
||||||
|
DynValue res = new DynValue();
|
||||||
|
// DynValue r = script.Globals.Get(functionName);
|
||||||
|
|
||||||
|
if (!script.Globals.Get(functionName).IsNil())
|
||||||
|
{
|
||||||
|
res = script.Call(script.Globals.Get(functionName), args);
|
||||||
|
if (res != null)
|
||||||
|
return (int)res.Number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void LoadBattleCommandScript(BattleCommand command, string folder)
|
||||||
|
{
|
||||||
|
string path = $"./scripts/commands/{folder}/{command.name}.lua";
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
command.script = script;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = $"./scripts/commands/{folder}/default.lua";
|
||||||
|
//Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}");
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
command.script = script;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LoadStatusEffectScript(StatusEffect effect)
|
||||||
|
{
|
||||||
|
string path = $"./scripts/effects/{effect.GetName()}.lua";
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
effect.script = script;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
path = $"./scripts/effects/default.lua";
|
||||||
|
//Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction [{command.name}] Unable to find script {path}");
|
||||||
|
var script = LoadGlobals();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
script.DoFile(path);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error($"LuaEngine.CallLuaBattleCommandFunction {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
effect.script = script;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static string GetScriptPath(Actor target)
|
||||||
{
|
{
|
||||||
if (target is Player)
|
if (target is Player)
|
||||||
{
|
{
|
||||||
|
@ -215,7 +420,7 @@ namespace FFXIVClassic_Map_Server.lua
|
||||||
|
|
||||||
private void CallLuaFunctionNpc(Player player, Npc target, string funcName, bool optional, params object[] args)
|
private void CallLuaFunctionNpc(Player player, Npc target, string funcName, bool optional, params object[] args)
|
||||||
{
|
{
|
||||||
object[] args2 = new object[args.Length + (player == null ? 1:2)];
|
object[] args2 = new object[args.Length + (player == null ? 1 : 2)];
|
||||||
Array.Copy(args, 0, args2, (player == null ? 1 : 2), args.Length);
|
Array.Copy(args, 0, args2, (player == null ? 1 : 2), args.Length);
|
||||||
if (player != null)
|
if (player != null)
|
||||||
{
|
{
|
||||||
|
@ -347,11 +552,20 @@ namespace FFXIVClassic_Map_Server.lua
|
||||||
if (script != null)
|
if (script != null)
|
||||||
{
|
{
|
||||||
if (!script.Globals.Get(funcName).IsNil())
|
if (!script.Globals.Get(funcName).IsNil())
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Coroutine coroutine = script.CreateCoroutine(script.Globals[funcName]).Coroutine;
|
Coroutine coroutine = script.CreateCoroutine(script.Globals[funcName]).Coroutine;
|
||||||
DynValue value = coroutine.Resume(args2);
|
DynValue value = coroutine.Resume(args2);
|
||||||
ResolveResume(player, coroutine, value);
|
ResolveResume(player, coroutine, value);
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
player.SendMessage(0x20, "", e.Message);
|
||||||
|
player.EndEvent();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!optional)
|
if (!optional)
|
||||||
|
@ -374,7 +588,8 @@ namespace FFXIVClassic_Map_Server.lua
|
||||||
Coroutine coroutine = mSleepingOnPlayerEvent[player.actorId];
|
Coroutine coroutine = mSleepingOnPlayerEvent[player.actorId];
|
||||||
mSleepingOnPlayerEvent.Remove(player.actorId);
|
mSleepingOnPlayerEvent.Remove(player.actorId);
|
||||||
|
|
||||||
try{
|
try
|
||||||
|
{
|
||||||
DynValue value = coroutine.Resume();
|
DynValue value = coroutine.Resume();
|
||||||
ResolveResume(null, coroutine, value);
|
ResolveResume(null, coroutine, value);
|
||||||
}
|
}
|
||||||
|
@ -427,9 +642,13 @@ namespace FFXIVClassic_Map_Server.lua
|
||||||
public static void RunGMCommand(Player player, String cmd, string[] param, bool help = false)
|
public static void RunGMCommand(Player player, String cmd, string[] param, bool help = false)
|
||||||
{
|
{
|
||||||
bool playerNull = player == null;
|
bool playerNull = player == null;
|
||||||
if (playerNull && param.Length >= 3)
|
if (playerNull)
|
||||||
player = Server.GetWorldManager().GetPCInWorld(param[1] + " " + param[2]);
|
{
|
||||||
|
if (param.Length >= 2 && param[1].Contains("\""))
|
||||||
|
player = Server.GetWorldManager().GetPCInWorld(param[1]);
|
||||||
|
else if (param.Length > 2)
|
||||||
|
player = Server.GetWorldManager().GetPCInWorld(param[1] + param[2]);
|
||||||
|
}
|
||||||
// load from scripts/commands/gm/ directory
|
// load from scripts/commands/gm/ directory
|
||||||
var path = String.Format("./scripts/commands/gm/{0}.lua", cmd.ToLower());
|
var path = String.Format("./scripts/commands/gm/{0}.lua", cmd.ToLower());
|
||||||
|
|
||||||
|
@ -556,9 +775,17 @@ namespace FFXIVClassic_Map_Server.lua
|
||||||
// run the script
|
// run the script
|
||||||
//script.Call(script.Globals["onTrigger"], LuaParam.ToArray());
|
//script.Call(script.Globals["onTrigger"], LuaParam.ToArray());
|
||||||
|
|
||||||
|
// gm commands dont need to be coroutines?
|
||||||
|
try
|
||||||
|
{
|
||||||
Coroutine coroutine = script.CreateCoroutine(script.Globals["onTrigger"]).Coroutine;
|
Coroutine coroutine = script.CreateCoroutine(script.Globals["onTrigger"]).Coroutine;
|
||||||
DynValue value = coroutine.Resume(LuaParam.ToArray());
|
DynValue value = coroutine.Resume(LuaParam.ToArray());
|
||||||
GetInstance().ResolveResume(player, coroutine, value);
|
GetInstance().ResolveResume(player, coroutine, value);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error("LuaEngine.RunGMCommand: {0} - {1}", path, e.Message);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -616,4 +843,3 @@ namespace FFXIVClassic_Map_Server.lua
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,12 @@ namespace FFXIVClassic_Map_Server
|
||||||
|
|
||||||
public static void WriteLuaParams(BinaryWriter writer, List<LuaParam> luaParams)
|
public static void WriteLuaParams(BinaryWriter writer, List<LuaParam> luaParams)
|
||||||
{
|
{
|
||||||
|
if (luaParams == null)
|
||||||
|
{
|
||||||
|
Program.Log.Error("LuaUtils.WriteLuaParams LuaParams are null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (LuaParam l in luaParams)
|
foreach (LuaParam l in luaParams)
|
||||||
{
|
{
|
||||||
if (l.typeID == 0x1)
|
if (l.typeID == 0x1)
|
||||||
|
|
22
FFXIVClassic Map Server/navmesh/SHARPNAV_LICENSE
Normal file
22
FFXIVClassic Map Server/navmesh/SHARPNAV_LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013-2016 Robert Rouhani <robert.rouhani@gmail.com> and other contributors (see CONTRIBUTORS file).
|
||||||
|
|
||||||
|
SharpNav contains some altered source code from Recast Navigation, Copyright (c) 2009 Mikko Mononen memon@inside.org
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
BIN
FFXIVClassic Map Server/navmesh/SharpNav.dll
Normal file
BIN
FFXIVClassic Map Server/navmesh/SharpNav.dll
Normal file
Binary file not shown.
BIN
FFXIVClassic Map Server/navmesh/wil0Field01.snb
Normal file
BIN
FFXIVClassic Map Server/navmesh/wil0Field01.snb
Normal file
Binary file not shown.
|
@ -1,11 +1,10 @@
|
||||||
<?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="MoonSharp" version="1.2.1.0" targetFramework="net45" />
|
<package id="MoonSharp" version="1.2.1.0" targetFramework="net45" />
|
||||||
<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="Newtonsoft.Json" version="9.0.1" targetFramework="net451" />
|
||||||
<package id="NLog" version="4.3.5" targetFramework="net45" />
|
<package id="NLog" version="4.3.5" targetFramework="net45" />
|
||||||
<package id="NLog.Config" version="4.3.5" targetFramework="net45" />
|
<package id="NLog.Config" version="4.3.5" targetFramework="net45" />
|
||||||
<package id="NLog.Schema" version="4.3.4" targetFramework="net45" />
|
<package id="NLog.Schema" version="4.3.4" targetFramework="net45" />
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace FFXIVClassic_Map_Server.packets.receive
|
||||||
{
|
{
|
||||||
public bool invalidPacket = false;
|
public bool invalidPacket = false;
|
||||||
public uint actorID;
|
public uint actorID;
|
||||||
public uint otherVal; //Usually 0xE0000000
|
public uint attackTarget; //Usually 0xE0000000
|
||||||
|
|
||||||
public SetTargetPacket(byte[] data)
|
public SetTargetPacket(byte[] data)
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ namespace FFXIVClassic_Map_Server.packets.receive
|
||||||
{
|
{
|
||||||
try{
|
try{
|
||||||
actorID = binReader.ReadUInt32();
|
actorID = binReader.ReadUInt32();
|
||||||
otherVal = binReader.ReadUInt32();
|
attackTarget = binReader.ReadUInt32();
|
||||||
}
|
}
|
||||||
catch (Exception){
|
catch (Exception){
|
||||||
invalidPacket = true;
|
invalidPacket = true;
|
||||||
|
|
|
@ -112,7 +112,6 @@ namespace FFXIVClassic_Map_Server.packets.send.actor
|
||||||
{
|
{
|
||||||
string[] split = name.Split('.');
|
string[] split = name.Split('.');
|
||||||
int arrayIndex = 0;
|
int arrayIndex = 0;
|
||||||
|
|
||||||
if (!(split[0].Equals("work") || split[0].Equals("charaWork") || split[0].Equals("playerWork") || split[0].Equals("npcWork") || split[0].Equals("guildleveWork")))
|
if (!(split[0].Equals("work") || split[0].Equals("charaWork") || split[0].Equals("playerWork") || split[0].Equals("npcWork") || split[0].Equals("guildleveWork")))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
using FFXIVClassic.Common;
|
using FFXIVClassic.Common;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
using FFXIVClassic.Common;
|
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor
|
namespace FFXIVClassic_Map_Server.packets.send.actor
|
||||||
{
|
{
|
||||||
class SetActorStatePacket
|
class SetActorStatePacket
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
using FFXIVClassic.Common;
|
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor
|
|
||||||
{
|
|
||||||
class SetActorSubStatPacket
|
|
||||||
{
|
|
||||||
public const ushort OPCODE = 0x144;
|
|
||||||
public const uint PACKET_SIZE = 0x28;
|
|
||||||
|
|
||||||
public static SubPacket BuildPacket(uint sourceActorId, byte breakage, int leftChant, int rightChant, int guard, int wasteStat, int statMode, uint idleAnimationId)
|
|
||||||
{
|
|
||||||
byte[] data = new byte[PACKET_SIZE - 0x20];
|
|
||||||
|
|
||||||
using (MemoryStream mem = new MemoryStream(data))
|
|
||||||
{
|
|
||||||
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
|
||||||
{
|
|
||||||
binWriter.Write((byte)breakage);
|
|
||||||
binWriter.Write((byte)(((leftChant & 0xF) << 8) | (rightChant & 0xF)));
|
|
||||||
binWriter.Write((byte)(guard & 0xF));
|
|
||||||
binWriter.Write((byte)((wasteStat & 0xF) << 8));
|
|
||||||
binWriter.Write((byte)(statMode & 0xF));
|
|
||||||
binWriter.Write((byte)0);
|
|
||||||
binWriter.Write((UInt16)(idleAnimationId&0xFFFF));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SubPacket(OPCODE, sourceActorId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using FFXIVClassic.Common;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.packets.send.actor
|
||||||
|
{
|
||||||
|
class SetActorSubStatePacket
|
||||||
|
{
|
||||||
|
public const ushort OPCODE = 0x144;
|
||||||
|
public const uint PACKET_SIZE = 0x28;
|
||||||
|
|
||||||
|
enum SubStat : int
|
||||||
|
{
|
||||||
|
Breakage = 0x00, // (index goes high to low, bitflags)
|
||||||
|
Chant = 0x01, // [Nibbles: left / right hand = value]) (AKA SubStatObject)
|
||||||
|
Guard = 0x02, // [left / right hand = true] 0,1,2,3) ||| High byte also defines how many bools to use as flags for byte 0x4.
|
||||||
|
Waste = 0x03, // (High Nibble)
|
||||||
|
Mode = 0x04, // ???
|
||||||
|
Unknown = 0x05, // ???
|
||||||
|
SubStatMotionPack = 0x06,
|
||||||
|
Unknown2 = 0x07,
|
||||||
|
}
|
||||||
|
public static SubPacket BuildPacket(uint sourceActorId, SubState substate)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[PACKET_SIZE - 0x20];
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
||||||
|
{
|
||||||
|
binWriter.Write((byte)substate.breakage);
|
||||||
|
binWriter.Write((byte)substate.chantId);
|
||||||
|
binWriter.Write((byte)(substate.guard & 0xF));
|
||||||
|
binWriter.Write((byte)(substate.waste));
|
||||||
|
binWriter.Write((byte)(substate.mode));
|
||||||
|
binWriter.Write((byte)0);
|
||||||
|
binWriter.Write((ushort)substate.motionPack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SubPacket(OPCODE, sourceActorId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
using FFXIVClassic.Common;
|
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|
||||||
{
|
|
||||||
class BattleAction
|
|
||||||
{
|
|
||||||
public uint targetId;
|
|
||||||
public ushort amount;
|
|
||||||
public ushort worldMasterTextId;
|
|
||||||
public uint effectId;
|
|
||||||
public byte param;
|
|
||||||
public byte unknown;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
using FFXIVClassic.Common;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
using FFXIVClassic.Common;
|
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|
||||||
{
|
|
||||||
class BattleActionX10Packet
|
|
||||||
{
|
|
||||||
public const ushort OPCODE = 0x013A;
|
|
||||||
public const uint PACKET_SIZE = 0xD8;
|
|
||||||
|
|
||||||
public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint animationId, ushort commandId, BattleAction[] actionList)
|
|
||||||
{
|
|
||||||
byte[] data = new byte[PACKET_SIZE - 0x20];
|
|
||||||
|
|
||||||
using (MemoryStream mem = new MemoryStream(data))
|
|
||||||
{
|
|
||||||
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
|
||||||
{
|
|
||||||
binWriter.Write((UInt32)sourceActorId);
|
|
||||||
binWriter.Write((UInt32)animationId);
|
|
||||||
|
|
||||||
//Missing... last value is float, string in here as well?
|
|
||||||
|
|
||||||
binWriter.Seek(0x20, SeekOrigin.Begin);
|
|
||||||
binWriter.Write((UInt32) actionList.Length); //Num actions (always 1 for this)
|
|
||||||
binWriter.Write((UInt16)commandId);
|
|
||||||
binWriter.Write((UInt16)810); //?
|
|
||||||
|
|
||||||
binWriter.Seek(0x20, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt32)action.targetId);
|
|
||||||
|
|
||||||
binWriter.Seek(0x50, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt16)action.amount);
|
|
||||||
|
|
||||||
binWriter.Seek(0x64, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt16)action.worldMasterTextId);
|
|
||||||
|
|
||||||
binWriter.Seek(0x78, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt32)action.effectId);
|
|
||||||
|
|
||||||
binWriter.Seek(0xA0, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((Byte)action.param);
|
|
||||||
|
|
||||||
binWriter.Seek(0xAA, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((Byte)action.unknown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SubPacket(OPCODE, sourceActorId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
using FFXIVClassic.Common;
|
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
using FFXIVClassic.Common;
|
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
|
||||||
{
|
|
||||||
class BattleActionX18Packet
|
|
||||||
{
|
|
||||||
public const ushort OPCODE = 0x013B;
|
|
||||||
public const uint PACKET_SIZE = 0x148;
|
|
||||||
|
|
||||||
public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint animationId, ushort commandId, BattleAction[] actionList)
|
|
||||||
{
|
|
||||||
byte[] data = new byte[PACKET_SIZE - 0x20];
|
|
||||||
|
|
||||||
using (MemoryStream mem = new MemoryStream(data))
|
|
||||||
{
|
|
||||||
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
|
||||||
{
|
|
||||||
binWriter.Write((UInt32)sourceActorId);
|
|
||||||
binWriter.Write((UInt32)animationId);
|
|
||||||
|
|
||||||
//Missing... last value is float, string in here as well?
|
|
||||||
|
|
||||||
binWriter.Seek(0x20, SeekOrigin.Begin);
|
|
||||||
binWriter.Write((UInt32) actionList.Length); //Num actions (always 1 for this)
|
|
||||||
binWriter.Write((UInt16)commandId);
|
|
||||||
binWriter.Write((UInt16)810); //?
|
|
||||||
|
|
||||||
binWriter.Seek(0x58, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt32)action.targetId);
|
|
||||||
|
|
||||||
binWriter.Seek(0xA0, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt16)action.amount);
|
|
||||||
|
|
||||||
binWriter.Seek(0xC4, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt16)action.worldMasterTextId);
|
|
||||||
|
|
||||||
binWriter.Seek(0xE8, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((UInt32)action.effectId);
|
|
||||||
|
|
||||||
binWriter.Seek(0x130, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((Byte)action.param);
|
|
||||||
|
|
||||||
binWriter.Seek(0x142, SeekOrigin.Begin);
|
|
||||||
foreach (BattleAction action in actionList)
|
|
||||||
binWriter.Write((Byte)action.unknown);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SubPacket(OPCODE, sourceActorId, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,333 @@
|
||||||
|
using FFXIVClassic.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai;
|
||||||
|
using FFXIVClassic_Map_Server.actors.chara.ai.utils;
|
||||||
|
using FFXIVClassic_Map_Server.Actors;
|
||||||
|
using FFXIVClassic_Map_Server.packets.send.actor.battle;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
|
{
|
||||||
|
//These flags can be stacked and mixed, but the client will prioritize certain flags over others.
|
||||||
|
[Flags]
|
||||||
|
public enum HitEffect : uint
|
||||||
|
{
|
||||||
|
//All HitEffects have the last byte 0x8
|
||||||
|
HitEffectType = 8 << 24,
|
||||||
|
//Status effects use 32 << 24
|
||||||
|
StatusEffectType = 32 << 24,
|
||||||
|
//Magic effects use 48 << 24
|
||||||
|
MagicEffectType = 48 << 24,
|
||||||
|
|
||||||
|
//Not setting RecoilLv2 or RecoilLv3 results in the weaker RecoilLv1.
|
||||||
|
//These are the recoil animations that play on the target, ranging from weak to strong.
|
||||||
|
//The recoil that gets set was likely based on the percentage of HP lost from the attack.
|
||||||
|
//These also have a visual effect with heals and spells but in reverse. RecoilLv1 has a large effect, Lv3 has none. Crit is very large
|
||||||
|
//For spells they represent resists. Lv0 is a max resist, Lv3 is no resist. Crit is still used for crits.
|
||||||
|
//Heals used the same effects sometimes but it isn't clear what for, it seems random? Possibly something like a trait proccing or even just a bug
|
||||||
|
RecoilLv1 = 0,
|
||||||
|
RecoilLv2 = 1 << 0,
|
||||||
|
RecoilLv3 = 1 << 1,
|
||||||
|
|
||||||
|
//Setting both recoil flags triggers the "Critical!" pop-up text and hit visual effect.
|
||||||
|
CriticalHit = RecoilLv2 | RecoilLv3,
|
||||||
|
|
||||||
|
//Hit visual and sound effects when connecting with the target.
|
||||||
|
//Mixing these flags together will yield different results.
|
||||||
|
//Each visual likely relates to a specific weapon.
|
||||||
|
//Ex: HitVisual4 flag alone appears to be the visual and sound effect for hand-to-hand attacks.
|
||||||
|
|
||||||
|
//HitVisual is probably based on attack property.
|
||||||
|
//HitVisual1 is for slashing attacks
|
||||||
|
//HitVisual2 is for piercing attacks
|
||||||
|
//HitVisual1 | Hitvisual2 is for blunt attacks
|
||||||
|
//HitVisual3 is for projectile attacks
|
||||||
|
//Basically take the attack property of a weapon and shift it left 2
|
||||||
|
//For auto attacks attack property is weapon's damageAttributeType1
|
||||||
|
//Still not totally sure how this works with weaponskills or what hitvisual4 or the other combinations are for
|
||||||
|
HitVisual1 = 1 << 2,
|
||||||
|
HitVisual2 = 1 << 3,
|
||||||
|
HitVisual3 = 1 << 4,
|
||||||
|
HitVisual4 = 1 << 5,
|
||||||
|
|
||||||
|
//An additional visual effect that plays on the target when attacked if:
|
||||||
|
//The attack is physical and they have the protect buff on.
|
||||||
|
//The attack is magical and they have the shell buff on.
|
||||||
|
//Special Note: Shell was removed in later versions of the game.
|
||||||
|
//Another effect plays when both Protect and Shell flags are activated.
|
||||||
|
//Not sure what this effect is.
|
||||||
|
//Random guess: if the attack was a hybrid of both physical and magical and the target had both Protect and Shell buffs applied.
|
||||||
|
Protect = 1 << 6 | HitEffectType,
|
||||||
|
Shell = 1 << 7 | HitEffectType,
|
||||||
|
ProtectShellSpecial = Protect | Shell,
|
||||||
|
|
||||||
|
// Required for heal text to be blue, not sure if that's all it's used for
|
||||||
|
Heal = 1 << 8,
|
||||||
|
MP = 1 << 9, //Causes "MP" text to appear when used with MagicEffectType. | with Heal to make text blue
|
||||||
|
TP = 1 << 10,//Causes "TP" text to appear when used with MagicEffectType. | with Heal to make text blue
|
||||||
|
|
||||||
|
//If only HitEffect1 is set out of the hit effects, the "Evade!" pop-up text triggers along with the evade visual.
|
||||||
|
//If no hit effects are set, the "Miss!" pop-up is triggered and no hit visual is played.
|
||||||
|
HitEffect1 = 1 << 9,
|
||||||
|
HitEffect2 = 1 << 10, //Plays the standard hit visual effect, but with no sound if used alone.
|
||||||
|
HitEffect3 = 1 << 11, //Yellow effect, crit?
|
||||||
|
HitEffect4 = 1 << 12, //Plays the blocking animation
|
||||||
|
HitEffect5 = 1 << 13,
|
||||||
|
GustyHitEffect = HitEffect3 | HitEffect2,
|
||||||
|
GreenTintedHitEffect = HitEffect4 | HitEffect1,
|
||||||
|
|
||||||
|
//For specific animations
|
||||||
|
Miss = 0,
|
||||||
|
Evade = HitEffect1,
|
||||||
|
Hit = HitEffect1 | HitEffect2,
|
||||||
|
Crit = HitEffect3,
|
||||||
|
Parry = Hit | HitEffect3,
|
||||||
|
Block = HitEffect4,
|
||||||
|
|
||||||
|
//Knocks you back away from the attacker.
|
||||||
|
KnockbackLv1 = HitEffect4 | HitEffect2 | HitEffect1,
|
||||||
|
KnockbackLv2 = HitEffect4 | HitEffect3,
|
||||||
|
KnockbackLv3 = HitEffect4 | HitEffect3 | HitEffect1,
|
||||||
|
KnockbackLv4 = HitEffect4 | HitEffect3 | HitEffect2,
|
||||||
|
KnockbackLv5 = HitEffect4 | HitEffect3 | HitEffect2 | HitEffect1,
|
||||||
|
|
||||||
|
//Knocks you away from the attacker in a counter-clockwise direction.
|
||||||
|
KnockbackCounterClockwiseLv1 = HitEffect5,
|
||||||
|
KnockbackCounterClockwiseLv2 = HitEffect5 | HitEffect1,
|
||||||
|
|
||||||
|
//Knocks you away from the attacker in a clockwise direction.
|
||||||
|
KnockbackClockwiseLv1 = HitEffect5 | HitEffect2,
|
||||||
|
KnockbackClockwiseLv2 = HitEffect5 | HitEffect2 | HitEffect1,
|
||||||
|
|
||||||
|
//Completely drags target to the attacker, even across large distances.
|
||||||
|
DrawIn = HitEffect5 | HitEffect3,
|
||||||
|
|
||||||
|
//An additional visual effect that plays on the target based on according buff.
|
||||||
|
UnknownShieldEffect = HitEffect5 | HitEffect4,
|
||||||
|
Stoneskin = HitEffect5 | HitEffect4 | HitEffect1,
|
||||||
|
|
||||||
|
//Unknown = 1 << 14, -- Not sure what this flag does; might be another HitEffect.
|
||||||
|
|
||||||
|
//A special effect when performing appropriate skill combos in succession.
|
||||||
|
//Ex: Thunder (SkillCombo1 Effect) -> Thundara (SkillCombo2 Effect) -> Thundaga (SkillCombo3 Effect)
|
||||||
|
//Special Note: SkillCombo4 was never actually used in 1.0 since combos only chained up to 3 times maximum.
|
||||||
|
SkillCombo1 = 1 << 15,
|
||||||
|
SkillCombo2 = 1 << 16,
|
||||||
|
SkillCombo3 = SkillCombo1 | SkillCombo2,
|
||||||
|
SkillCombo4 = 1 << 17
|
||||||
|
|
||||||
|
//Flags beyond here are unknown/untested.
|
||||||
|
}
|
||||||
|
|
||||||
|
//Mixing some of these flags will cause the client to crash.
|
||||||
|
//Setting a flag higher than Left (0x10-0x80) will cause the client to crash.
|
||||||
|
[Flags]
|
||||||
|
public enum HitDirection : byte
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Front = 1 << 0,
|
||||||
|
Right = 1 << 1,
|
||||||
|
Rear = 1 << 2,
|
||||||
|
Left = 1 << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HitType : ushort
|
||||||
|
{
|
||||||
|
Miss = 0,
|
||||||
|
Evade = 1,
|
||||||
|
Parry = 2,
|
||||||
|
Block = 3,
|
||||||
|
Resist = 4,
|
||||||
|
Hit = 5,
|
||||||
|
Crit = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
//Type of action
|
||||||
|
public enum ActionType : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Physical = 1,
|
||||||
|
Magic = 2,
|
||||||
|
Heal = 3,
|
||||||
|
Status = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
//There's are two columns in gamecommand that are for action property and action element respectively and both have percentages next to them
|
||||||
|
//the percentages are for what percent that property or element factors into the attack. Astral and Umbral are always 33% because they are both 3 elments combined
|
||||||
|
//ActionProperty and ActionElement are slightly different. Property defines whta type of attack it is, and 11-13 are used for "sonic, breath, neutral". Neutral is always used for magic
|
||||||
|
//For Element 11-13 are used for astral, umbral, and healing magic.
|
||||||
|
//Right now we aren't actually using these but when things like resists get better defined we'll have to
|
||||||
|
public enum ActionProperty : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Projectile = 4,
|
||||||
|
|
||||||
|
Fire = 5,
|
||||||
|
Ice = 6,
|
||||||
|
Wind = 7,
|
||||||
|
Earth = 8,
|
||||||
|
Lightning = 9,
|
||||||
|
Water = 10,
|
||||||
|
|
||||||
|
//These I'm not sure about. Check gameCommand.csv
|
||||||
|
Astral = 11,
|
||||||
|
Umbral = 12,
|
||||||
|
Heal = 13
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
public enum ActionProperty : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Projectile = 4,
|
||||||
|
|
||||||
|
Fire = 5,
|
||||||
|
Ice = 6,
|
||||||
|
Wind = 7,
|
||||||
|
Earth = 8,
|
||||||
|
Lightning = 9,
|
||||||
|
Water = 10,
|
||||||
|
|
||||||
|
Sonic = 11,
|
||||||
|
Breath = 12,
|
||||||
|
Neutral = 13,
|
||||||
|
Astral = 14,
|
||||||
|
Umbral = 15
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ActionElement : ushort
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Slashing = 1,
|
||||||
|
Piercing = 2,
|
||||||
|
Blunt = 3,
|
||||||
|
Projectile = 4,
|
||||||
|
|
||||||
|
Fire = 5,
|
||||||
|
Ice = 6,
|
||||||
|
Wind = 7,
|
||||||
|
Earth = 8,
|
||||||
|
Lightning = 9,
|
||||||
|
Water = 10,
|
||||||
|
|
||||||
|
//These I'm not sure about. Check gameCommand.csv
|
||||||
|
Astral = 11,
|
||||||
|
Umbral = 12,
|
||||||
|
Heal = 13
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
class CommandResult
|
||||||
|
{
|
||||||
|
public uint targetId;
|
||||||
|
public ushort amount;
|
||||||
|
public ushort amountMitigated; //Amount that got blocked/evaded or resisted
|
||||||
|
public ushort enmity; //Seperate from amount for abilities that cause a different amount of enmity than damage
|
||||||
|
public ushort worldMasterTextId;
|
||||||
|
public uint effectId; //Impact effect, damage/heal/status numbers or name
|
||||||
|
public byte param; //Which side the battle action is coming from
|
||||||
|
public byte hitNum; //Which hit in a sequence of hits this is
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// these fields are not actually part of the packet struct
|
||||||
|
/// </summary>
|
||||||
|
public uint animation;
|
||||||
|
public CommandType commandType; //What type of command was used (ie weaponskill, ability, etc)
|
||||||
|
public ActionProperty actionProperty; //Damage type of the action
|
||||||
|
public ActionType actionType; //Type of this action (ie physical, magic, heal)
|
||||||
|
public HitType hitType;
|
||||||
|
|
||||||
|
//Rates, I'm not sure if these need to be stored like this but with the way some buffs work maybe they do?
|
||||||
|
//Makes things like Blindside easy at least.
|
||||||
|
public double parryRate = 0.0;
|
||||||
|
public double blockRate = 0.0;
|
||||||
|
public double resistRate = 0.0;
|
||||||
|
public double hitRate = 0.0;
|
||||||
|
public double critRate = 0.0;
|
||||||
|
|
||||||
|
public CommandResult(uint targetId, ushort worldMasterTextId, uint effectId, ushort amount = 0, byte param = 0, byte hitNum = 1)
|
||||||
|
{
|
||||||
|
this.targetId = targetId;
|
||||||
|
this.worldMasterTextId = worldMasterTextId;
|
||||||
|
this.effectId = effectId;
|
||||||
|
this.amount = amount;
|
||||||
|
this.param = param;
|
||||||
|
this.hitNum = hitNum;
|
||||||
|
this.hitType = HitType.Hit;
|
||||||
|
this.enmity = amount;
|
||||||
|
this.commandType = (byte) CommandType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandResult(uint targetId, BattleCommand command, byte param = 0, byte hitNum = 1)
|
||||||
|
{
|
||||||
|
this.targetId = targetId;
|
||||||
|
this.worldMasterTextId = command.worldMasterTextId;
|
||||||
|
this.param = param;
|
||||||
|
this.hitNum = hitNum;
|
||||||
|
this.commandType = command.commandType;
|
||||||
|
this.actionProperty = command.actionProperty;
|
||||||
|
this.actionType = command.actionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Order of what (probably) happens when a skill is used:
|
||||||
|
//Buffs that alter things like recast times or that only happen once per skill usage like Power Surge are activated
|
||||||
|
//Script calculates damage and handles any special requirements
|
||||||
|
//Rates are calculated
|
||||||
|
//Buffs that impact indiviudal hits like Blindside or Blood for Blood are activated
|
||||||
|
//The final hit type is determined
|
||||||
|
//Stoneskin takes damage
|
||||||
|
//Final damage amount is calculated using the hit type and defender's stats
|
||||||
|
//Buffs that activate or respond to damage like Rampage. Stoneskin gets removed AFTER damage if it falls off.
|
||||||
|
//Additional effects that are a part of the skill itself or weapon in case of auto attacks take place like status effects
|
||||||
|
//Certain buffs that alter the whole skill fall off (Resonance, Excruciate)
|
||||||
|
|
||||||
|
public void DoAction(Character caster, Character target, BattleCommand skill, CommandResultContainer results)
|
||||||
|
{
|
||||||
|
//First calculate rates for hit/block/etc
|
||||||
|
CalcRates(caster, target, skill);
|
||||||
|
|
||||||
|
//Next, modify those rates based on preaction buffs
|
||||||
|
//Still not sure how we shouldh andle these
|
||||||
|
PreAction(caster, target, skill, results);
|
||||||
|
|
||||||
|
BattleUtils.DoAction(caster, target, skill, this, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//Calculate the chance of hitting/critting/etc
|
||||||
|
public void CalcRates(Character caster, Character target, BattleCommand skill)
|
||||||
|
{
|
||||||
|
hitRate = BattleUtils.GetHitRate(caster, target, skill, this);
|
||||||
|
critRate = BattleUtils.GetCritRate(caster, target, skill, this);
|
||||||
|
blockRate = BattleUtils.GetBlockRate(caster, target, skill, this);
|
||||||
|
parryRate = BattleUtils.GetParryRate(caster, target, skill, this);
|
||||||
|
resistRate = BattleUtils.GetResistRate(caster, target, skill, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
//These are buffs that activate before the action hits. Usually they change things like hit or crit rates or damage
|
||||||
|
public void PreAction(Character caster, Character target, BattleCommand skill, CommandResultContainer results)
|
||||||
|
{
|
||||||
|
target.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnPreactionTarget, "onPreAction", caster, target, skill, this, results);
|
||||||
|
|
||||||
|
caster.statusEffects.CallLuaFunctionByFlag((uint)StatusEffectFlags.ActivateOnPreactionCaster, "onPreAction", caster, target, skill, this, results);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Try and apply a status effect
|
||||||
|
public void TryStatus(Character caster, Character target, BattleCommand skill, CommandResultContainer results, bool isAdditional = true)
|
||||||
|
{
|
||||||
|
BattleUtils.TryStatus(caster, target, skill, this, results, isAdditional);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ushort GetHitType()
|
||||||
|
{
|
||||||
|
return (ushort)hitType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
|
{
|
||||||
|
class CommandResultContainer
|
||||||
|
{
|
||||||
|
private List<CommandResult> actionsList = new List<CommandResult>();
|
||||||
|
|
||||||
|
//EXP messages are always the last mesages in battlea ction packets, so they get appended after all the rest of the actions are done.
|
||||||
|
private List<CommandResult> expActionList = new List<CommandResult>();
|
||||||
|
|
||||||
|
public CommandResultContainer()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAction(uint targetId, ushort worldMasterTextId, uint effectId, ushort amount = 0, byte param = 0, byte hitNum = 0)
|
||||||
|
{
|
||||||
|
AddAction(new CommandResult(targetId, worldMasterTextId, effectId, amount, param, hitNum));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Just to make scripting simpler
|
||||||
|
public void AddMPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||||
|
{
|
||||||
|
uint effectId = (uint) (HitEffect.MagicEffectType | HitEffect.MP | HitEffect.Heal);
|
||||||
|
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddHPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||||
|
{
|
||||||
|
uint effectId = (uint)(HitEffect.MagicEffectType | HitEffect.Heal);
|
||||||
|
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTPAction(uint targetId, ushort worldMasterTextId, ushort amount)
|
||||||
|
{
|
||||||
|
uint effectId = (uint)(HitEffect.MagicEffectType | HitEffect.TP);
|
||||||
|
AddAction(targetId, worldMasterTextId, effectId, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddAction(CommandResult action)
|
||||||
|
{
|
||||||
|
if (action != null)
|
||||||
|
actionsList.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddActions(List<CommandResult> actions)
|
||||||
|
{
|
||||||
|
actionsList.AddRange(actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEXPAction(CommandResult action)
|
||||||
|
{
|
||||||
|
expActionList.Add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddEXPActions(List<CommandResult> actionList)
|
||||||
|
{
|
||||||
|
expActionList.AddRange(actionList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CombineLists()
|
||||||
|
{
|
||||||
|
actionsList.AddRange(expActionList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommandResult> GetList()
|
||||||
|
{
|
||||||
|
return actionsList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,16 +2,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using FFXIVClassic.Common;
|
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
{
|
{
|
||||||
class BattleActionX00Packet
|
class CommandResultX00Packet
|
||||||
{
|
{
|
||||||
public const ushort OPCODE = 0x013C;
|
public const ushort OPCODE = 0x013C;
|
||||||
public const uint PACKET_SIZE = 0x48;
|
public const uint PACKET_SIZE = 0x48;
|
||||||
|
|
||||||
public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint targetActorId, uint animationId, ushort commandId)
|
public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId)
|
||||||
{
|
{
|
||||||
byte[] data = new byte[PACKET_SIZE - 0x20];
|
byte[] data = new byte[PACKET_SIZE - 0x20];
|
||||||
|
|
|
@ -4,12 +4,19 @@ using System.IO;
|
||||||
|
|
||||||
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
{
|
{
|
||||||
class BattleActionX01Packet
|
// see xtx_command
|
||||||
|
enum CommandResultX01PacketCommand : ushort
|
||||||
|
{
|
||||||
|
Disengage = 12002,
|
||||||
|
Attack = 22104,
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommandResultX01Packet
|
||||||
{
|
{
|
||||||
public const ushort OPCODE = 0x0139;
|
public const ushort OPCODE = 0x0139;
|
||||||
public const uint PACKET_SIZE = 0x58;
|
public const uint PACKET_SIZE = 0x58;
|
||||||
|
|
||||||
public static SubPacket BuildPacket(uint playerActorID, uint sourceActorId, uint targetActorId, uint animationId, uint effectId, ushort worldMasterTextId, ushort commandId, ushort amount, byte param)
|
public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, CommandResult action)
|
||||||
{
|
{
|
||||||
byte[] data = new byte[PACKET_SIZE - 0x20];
|
byte[] data = new byte[PACKET_SIZE - 0x20];
|
||||||
|
|
||||||
|
@ -18,6 +25,7 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
||||||
{
|
{
|
||||||
binWriter.Write((UInt32)sourceActorId);
|
binWriter.Write((UInt32)sourceActorId);
|
||||||
|
|
||||||
binWriter.Write((UInt32)animationId);
|
binWriter.Write((UInt32)animationId);
|
||||||
|
|
||||||
//Missing... last value is float, string in here as well?
|
//Missing... last value is float, string in here as well?
|
||||||
|
@ -25,17 +33,16 @@ namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
binWriter.Seek(0x20, SeekOrigin.Begin);
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
binWriter.Write((UInt32)1); //Num actions (always 1 for this)
|
binWriter.Write((UInt32)1); //Num actions (always 1 for this)
|
||||||
binWriter.Write((UInt16)commandId);
|
binWriter.Write((UInt16)commandId);
|
||||||
binWriter.Write((UInt16)810); //?
|
binWriter.Write((UInt16)0x810); //?
|
||||||
|
|
||||||
binWriter.Write((UInt32)targetActorId);
|
binWriter.Write((UInt32)action.targetId);
|
||||||
|
|
||||||
binWriter.Write((UInt16)amount);
|
binWriter.Write((UInt16)action.amount);
|
||||||
binWriter.Write((UInt16)worldMasterTextId);
|
binWriter.Write((UInt16)action.worldMasterTextId);
|
||||||
|
|
||||||
binWriter.Write((UInt32)effectId);
|
binWriter.Write((UInt32)action.effectId);
|
||||||
|
binWriter.Write((Byte)action.param);
|
||||||
binWriter.Write((Byte)param);
|
binWriter.Write((Byte)action.hitNum);
|
||||||
binWriter.Write((Byte)1); //?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
using FFXIVClassic.Common;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
|
{
|
||||||
|
class CommandResultX10Packet
|
||||||
|
{
|
||||||
|
public const ushort OPCODE = 0x013A;
|
||||||
|
public const uint PACKET_SIZE = 0xD8;
|
||||||
|
|
||||||
|
public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, CommandResult[] actionList, ref int listOffset)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[PACKET_SIZE - 0x20];
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
||||||
|
{
|
||||||
|
int max;
|
||||||
|
if (actionList.Length - listOffset <= 10)
|
||||||
|
max = actionList.Length - listOffset;
|
||||||
|
else
|
||||||
|
max = 10;
|
||||||
|
|
||||||
|
binWriter.Write((UInt32)sourceActorId);
|
||||||
|
binWriter.Write((UInt32)animationId);
|
||||||
|
|
||||||
|
//Missing... last value is float, string in here as well?
|
||||||
|
|
||||||
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
|
binWriter.Write((UInt32)max); //Num actions
|
||||||
|
binWriter.Write((UInt16)commandId);
|
||||||
|
binWriter.Write((UInt16)0x810); //?
|
||||||
|
|
||||||
|
//binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].targetId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x50, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].amount);
|
||||||
|
|
||||||
|
binWriter.Seek(0x64, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x78, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
||||||
|
|
||||||
|
binWriter.Seek(0xA0, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte)actionList[listOffset + i].param);
|
||||||
|
|
||||||
|
binWriter.Seek(0xAA, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte)actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
|
listOffset += max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SubPacket(OPCODE, sourceActorId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, List<CommandResult> actionList, ref int listOffset)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[PACKET_SIZE - 0x20];
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
||||||
|
{
|
||||||
|
int max;
|
||||||
|
if (actionList.Count - listOffset <= 10)
|
||||||
|
max = actionList.Count - listOffset;
|
||||||
|
else
|
||||||
|
max = 10;
|
||||||
|
|
||||||
|
binWriter.Write((UInt32)sourceActorId);
|
||||||
|
binWriter.Write((UInt32)animationId);
|
||||||
|
|
||||||
|
//Missing... last value is float, string in here as well?
|
||||||
|
|
||||||
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
|
binWriter.Write((UInt32)max); //Num actions
|
||||||
|
binWriter.Write((UInt16)commandId);
|
||||||
|
binWriter.Write((UInt16)0x810); //?
|
||||||
|
|
||||||
|
//binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].targetId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x50, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].amount);
|
||||||
|
|
||||||
|
binWriter.Seek(0x64, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x78, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
{
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
binWriter.Seek(0xA0, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte)actionList[listOffset + i].param);
|
||||||
|
|
||||||
|
binWriter.Seek(0xAA, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte) actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
|
listOffset += max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SubPacket(OPCODE, sourceActorId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
using FFXIVClassic.Common;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.packets.send.actor.battle
|
||||||
|
{
|
||||||
|
class CommandResultX18Packet
|
||||||
|
{
|
||||||
|
public const ushort OPCODE = 0x013B;
|
||||||
|
public const uint PACKET_SIZE = 0x148;
|
||||||
|
|
||||||
|
public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, CommandResult[] actionList, ref int listOffset)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[PACKET_SIZE - 0x20];
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
||||||
|
{
|
||||||
|
int max;
|
||||||
|
if (actionList.Length - listOffset <= 18)
|
||||||
|
max = actionList.Length - listOffset;
|
||||||
|
else
|
||||||
|
max = 18;
|
||||||
|
|
||||||
|
binWriter.Write((UInt32)sourceActorId);
|
||||||
|
binWriter.Write((UInt32)animationId);
|
||||||
|
|
||||||
|
//Missing... last value is float, string in here as well?
|
||||||
|
|
||||||
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
|
binWriter.Write((UInt32)max); //Num actions
|
||||||
|
binWriter.Write((UInt16)commandId);
|
||||||
|
binWriter.Write((UInt16)0x810); //?
|
||||||
|
|
||||||
|
binWriter.Seek(0x28, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].targetId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x70, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].amount);
|
||||||
|
|
||||||
|
binWriter.Seek(0x94, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId);
|
||||||
|
|
||||||
|
binWriter.Seek(0xB8, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x100, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte)actionList[listOffset + i].param);
|
||||||
|
|
||||||
|
binWriter.Seek(0x112, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte)actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
|
listOffset += max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SubPacket(OPCODE, sourceActorId, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubPacket BuildPacket(uint sourceActorId, uint animationId, ushort commandId, List<CommandResult> actionList, ref int listOffset)
|
||||||
|
{
|
||||||
|
byte[] data = new byte[PACKET_SIZE - 0x20];
|
||||||
|
|
||||||
|
using (MemoryStream mem = new MemoryStream(data))
|
||||||
|
{
|
||||||
|
using (BinaryWriter binWriter = new BinaryWriter(mem))
|
||||||
|
{
|
||||||
|
int max;
|
||||||
|
if (actionList.Count - listOffset <= 18)
|
||||||
|
max = actionList.Count - listOffset;
|
||||||
|
else
|
||||||
|
max = 18;
|
||||||
|
|
||||||
|
binWriter.Write((UInt32)sourceActorId);
|
||||||
|
binWriter.Write((UInt32)animationId);
|
||||||
|
|
||||||
|
//Missing... last value is float, string in here as well?
|
||||||
|
|
||||||
|
binWriter.Seek(0x20, SeekOrigin.Begin);
|
||||||
|
binWriter.Write((UInt32)max); //Num actions
|
||||||
|
binWriter.Write((UInt16)commandId);
|
||||||
|
binWriter.Write((UInt16)0x818); //?
|
||||||
|
|
||||||
|
binWriter.Seek(0x28, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].targetId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x70, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].amount);
|
||||||
|
|
||||||
|
binWriter.Seek(0x94, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt16)actionList[listOffset + i].worldMasterTextId);
|
||||||
|
|
||||||
|
binWriter.Seek(0xB8, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((UInt32)actionList[listOffset + i].effectId);
|
||||||
|
|
||||||
|
binWriter.Seek(0x100, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte)actionList[listOffset + i].param);
|
||||||
|
|
||||||
|
binWriter.Seek(0x112, SeekOrigin.Begin);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
binWriter.Write((Byte)actionList[listOffset + i].hitNum);
|
||||||
|
|
||||||
|
listOffset += max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SubPacket(OPCODE, sourceActorId, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,5 +96,31 @@ namespace FFXIVClassic_Map_Server.utils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetClassNameForId(short id)
|
||||||
|
{
|
||||||
|
switch (id)
|
||||||
|
{
|
||||||
|
case 2: return "pug";
|
||||||
|
case 3: return "gla";
|
||||||
|
case 4: return "mrd";
|
||||||
|
case 7: return "arc";
|
||||||
|
case 8: return "lnc";
|
||||||
|
case 22: return "thm";
|
||||||
|
case 23: return "cnj";
|
||||||
|
case 29: return "crp";
|
||||||
|
case 30: return "bsm";
|
||||||
|
case 31: return "arm";
|
||||||
|
case 32: return "gsm";
|
||||||
|
case 33: return "ltw";
|
||||||
|
case 34: return "wvr";
|
||||||
|
case 35: return "alc";
|
||||||
|
case 36: return "cul";
|
||||||
|
case 39: return "min";
|
||||||
|
case 40: return "btn";
|
||||||
|
case 41: return "fsh";
|
||||||
|
default: return "undefined";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
266
FFXIVClassic Map Server/utils/NavmeshUtils.cs
Normal file
266
FFXIVClassic Map Server/utils/NavmeshUtils.cs
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using SharpNav;
|
||||||
|
using SharpNav.Pathfinding;
|
||||||
|
using SharpNav.Crowds;
|
||||||
|
using SharpNav.IO;
|
||||||
|
using FFXIVClassic.Common;
|
||||||
|
|
||||||
|
namespace FFXIVClassic_Map_Server.utils
|
||||||
|
{
|
||||||
|
class NavmeshUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
// navmesh
|
||||||
|
public static bool CanSee(actors.area.Zone zone, float x1, float y1, float z1, float x2, float y2, float z2)
|
||||||
|
{
|
||||||
|
// todo: prolly shouldnt raycast
|
||||||
|
var navMesh = zone.tiledNavMesh;
|
||||||
|
if (navMesh != null)
|
||||||
|
{
|
||||||
|
var navMeshQuery = zone.navMeshQuery;
|
||||||
|
|
||||||
|
NavPoint startPt, endPt;
|
||||||
|
SharpNav.Pathfinding.Path path = new SharpNav.Pathfinding.Path();
|
||||||
|
|
||||||
|
RaycastHit hit = new RaycastHit();
|
||||||
|
|
||||||
|
SharpNav.Geometry.Vector3 c = new SharpNav.Geometry.Vector3(x1, y1, z1);
|
||||||
|
SharpNav.Geometry.Vector3 ep = new SharpNav.Geometry.Vector3(x2, y2, z2);
|
||||||
|
|
||||||
|
SharpNav.Geometry.Vector3 e = new SharpNav.Geometry.Vector3(5, 5, 5);
|
||||||
|
navMeshQuery.FindNearestPoly(ref c, ref e, out startPt);
|
||||||
|
navMeshQuery.FindNearestPoly(ref ep, ref e, out endPt);
|
||||||
|
|
||||||
|
|
||||||
|
if (navMeshQuery.Raycast(ref startPt, ref ep, RaycastOptions.None, out hit, path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SharpNav.TiledNavMesh LoadNavmesh(TiledNavMesh navmesh, string filePath)
|
||||||
|
{
|
||||||
|
SharpNav.IO.NavMeshSerializer serializer;
|
||||||
|
if (System.IO.Path.GetExtension(filePath) == ".snb")
|
||||||
|
serializer = new SharpNav.IO.Binary.NavMeshBinarySerializer();
|
||||||
|
else
|
||||||
|
serializer = new SharpNav.IO.Json.NavMeshJsonSerializer();
|
||||||
|
|
||||||
|
return serializer.Deserialize(System.IO.Path.Combine("../../navmesh/", filePath));
|
||||||
|
//return navmesh = new SharpNav.IO.Json.NavMeshJsonSerializer().Deserialize(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Vector3> GetPath(actors.area.Zone zone, float x, float y, float z, float targetX, float targetY, float targetZ, float stepSize = 0.70f, int pathSize = 45, float polyRadius = 0.0f, bool skipToTarget = false)
|
||||||
|
{
|
||||||
|
return GetPath(zone, new Vector3(x, y, z), new Vector3(targetX, targetY, targetZ), stepSize, pathSize, polyRadius, skipToTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region sharpnav stuff
|
||||||
|
// Copyright (c) 2013-2016 Robert Rouhani <robert.rouhani@gmail.com> and other contributors (see CONTRIBUTORS file).
|
||||||
|
// Licensed under the MIT License - https://raw.github.com/Robmaister/SharpNav/master/LICENSE
|
||||||
|
|
||||||
|
public static List<Vector3> GetPath(actors.area.Zone zone, Vector3 startVec, Vector3 endVec, float stepSize = 0.70f, int pathSize = 45, float polyRadius = 0.0f, bool skipToTarget = false)
|
||||||
|
{
|
||||||
|
var navMesh = zone.tiledNavMesh;
|
||||||
|
var navMeshQuery = zone.navMeshQuery;
|
||||||
|
|
||||||
|
// no navmesh loaded, run straight to player
|
||||||
|
if (navMesh == null)
|
||||||
|
{
|
||||||
|
return new List<Vector3>() { endVec };
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need to waste cycles finding path to same point
|
||||||
|
if (startVec.X == endVec.X && startVec.Y == endVec.Y && startVec.Z == endVec.Z && polyRadius == 0.0f)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var smoothPath = new List<Vector3>(pathSize) { };
|
||||||
|
|
||||||
|
NavQueryFilter filter = new NavQueryFilter();
|
||||||
|
|
||||||
|
NavPoint startPt, endPt;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SharpNav.Geometry.Vector3 c = new SharpNav.Geometry.Vector3(startVec.X, startVec.Y, startVec.Z);
|
||||||
|
SharpNav.Geometry.Vector3 ep = new SharpNav.Geometry.Vector3(endVec.X, endVec.Y, endVec.Z);
|
||||||
|
|
||||||
|
SharpNav.Geometry.Vector3 e = new SharpNav.Geometry.Vector3(5, 5, 5);
|
||||||
|
navMeshQuery.FindNearestPoly(ref c, ref e, out startPt);
|
||||||
|
navMeshQuery.FindNearestPoly(ref ep, ref e, out endPt);
|
||||||
|
|
||||||
|
//calculate the overall path, which contains an array of polygon references
|
||||||
|
int MAX_POLYS = 256;
|
||||||
|
var path = new SharpNav.Pathfinding.Path();
|
||||||
|
|
||||||
|
navMeshQuery.FindPath(ref startPt, ref endPt, filter, path);
|
||||||
|
|
||||||
|
//find a smooth path over the mesh surface
|
||||||
|
int npolys = path.Count;
|
||||||
|
SharpNav.Geometry.Vector3 iterPos = new SharpNav.Geometry.Vector3();
|
||||||
|
SharpNav.Geometry.Vector3 targetPos = new SharpNav.Geometry.Vector3();
|
||||||
|
navMeshQuery.ClosestPointOnPoly(startPt.Polygon, startPt.Position, ref iterPos);
|
||||||
|
navMeshQuery.ClosestPointOnPoly(path[npolys - 1], endPt.Position, ref targetPos);
|
||||||
|
|
||||||
|
// set target to random point at end of path
|
||||||
|
if (polyRadius != 0.0f)
|
||||||
|
{
|
||||||
|
var randPoly = navMeshQuery.FindRandomPointAroundCircle(endPt, polyRadius);
|
||||||
|
targetPos = randPoly.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipToTarget)
|
||||||
|
{
|
||||||
|
return new List<Vector3>() { new Vector3(targetPos.X, targetPos.Y, targetPos.Z) };
|
||||||
|
}
|
||||||
|
smoothPath.Add(new Vector3(iterPos.X, iterPos.Y, iterPos.Z));
|
||||||
|
|
||||||
|
//float STEP_SIZE = 0.70f;
|
||||||
|
float SLOP = 0.15f;
|
||||||
|
while (npolys > 0 && smoothPath.Count < smoothPath.Capacity)
|
||||||
|
{
|
||||||
|
//find location to steer towards
|
||||||
|
SharpNav.Geometry.Vector3 steerPos = new SharpNav.Geometry.Vector3();
|
||||||
|
StraightPathFlags steerPosFlag = 0;
|
||||||
|
NavPolyId steerPosRef = NavPolyId.Null;
|
||||||
|
|
||||||
|
if (!GetSteerTarget(navMeshQuery, iterPos, targetPos, SLOP, path, ref steerPos, ref steerPosFlag, ref steerPosRef))
|
||||||
|
break;
|
||||||
|
|
||||||
|
bool endOfPath = (steerPosFlag & StraightPathFlags.End) != 0 ? true : false;
|
||||||
|
bool offMeshConnection = (steerPosFlag & StraightPathFlags.OffMeshConnection) != 0 ? true : false;
|
||||||
|
|
||||||
|
//find movement delta
|
||||||
|
SharpNav.Geometry.Vector3 delta = steerPos - iterPos;
|
||||||
|
float len = (float)Math.Sqrt(SharpNav.Geometry.Vector3.Dot(delta, delta));
|
||||||
|
|
||||||
|
//if steer target is at end of path or off-mesh link
|
||||||
|
//don't move past location
|
||||||
|
if ((endOfPath || offMeshConnection) && len < stepSize)
|
||||||
|
len = 1;
|
||||||
|
else
|
||||||
|
len = stepSize / len;
|
||||||
|
|
||||||
|
SharpNav.Geometry.Vector3 moveTgt = new SharpNav.Geometry.Vector3();
|
||||||
|
VMad(ref moveTgt, iterPos, delta, len);
|
||||||
|
|
||||||
|
//move
|
||||||
|
SharpNav.Geometry.Vector3 result = new SharpNav.Geometry.Vector3();
|
||||||
|
List<NavPolyId> visited = new List<NavPolyId>(pathSize);
|
||||||
|
NavPoint startPoint = new NavPoint(path[0], iterPos);
|
||||||
|
navMeshQuery.MoveAlongSurface(ref startPoint, ref moveTgt, out result, visited);
|
||||||
|
path.FixupCorridor(visited);
|
||||||
|
npolys = path.Count;
|
||||||
|
float h = 0;
|
||||||
|
navMeshQuery.GetPolyHeight(path[0], result, ref h);
|
||||||
|
result.Y = h;
|
||||||
|
iterPos = result;
|
||||||
|
|
||||||
|
//handle end of path when close enough
|
||||||
|
if (endOfPath && InRange(iterPos, steerPos, SLOP, 1000.0f))
|
||||||
|
{
|
||||||
|
//reached end of path
|
||||||
|
iterPos = targetPos;
|
||||||
|
if (smoothPath.Count < smoothPath.Capacity)
|
||||||
|
{
|
||||||
|
smoothPath.Add(new Vector3(iterPos.X, iterPos.Y, iterPos.Z));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//store results
|
||||||
|
if (smoothPath.Count < smoothPath.Capacity)
|
||||||
|
{
|
||||||
|
smoothPath.Add(new Vector3(iterPos.X, iterPos.Y, iterPos.Z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
Program.Log.Error(e.Message);
|
||||||
|
Program.Log.Error("Start pos {0} {1} {2} end pos {3} {4} {5}", startVec.X, startVec.Y, startVec.Z, endVec.X, endVec.Y, endVec.Z);
|
||||||
|
// todo: probably log this
|
||||||
|
return new List<Vector3>() { endVec };
|
||||||
|
}
|
||||||
|
return smoothPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scaled vector addition
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dest">Result</param>
|
||||||
|
/// <param name="v1">Vector 1</param>
|
||||||
|
/// <param name="v2">Vector 2</param>
|
||||||
|
/// <param name="s">Scalar</param>
|
||||||
|
private static void VMad(ref SharpNav.Geometry.Vector3 dest, SharpNav.Geometry.Vector3 v1, SharpNav.Geometry.Vector3 v2, float s)
|
||||||
|
{
|
||||||
|
dest.X = v1.X + v2.X * s;
|
||||||
|
dest.Y = v1.Y + v2.Y * s;
|
||||||
|
dest.Z = v1.Z + v2.Z * s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool GetSteerTarget(NavMeshQuery navMeshQuery, SharpNav.Geometry.Vector3 startPos, SharpNav.Geometry.Vector3 endPos, float minTargetDist, SharpNav.Pathfinding.Path path,
|
||||||
|
ref SharpNav.Geometry.Vector3 steerPos, ref StraightPathFlags steerPosFlag, ref NavPolyId steerPosRef)
|
||||||
|
{
|
||||||
|
StraightPath steerPath = new StraightPath();
|
||||||
|
navMeshQuery.FindStraightPath(startPos, endPos, path, steerPath, 0);
|
||||||
|
int nsteerPath = steerPath.Count;
|
||||||
|
if (nsteerPath == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
//find vertex far enough to steer to
|
||||||
|
int ns = 0;
|
||||||
|
while (ns < nsteerPath)
|
||||||
|
{
|
||||||
|
if ((steerPath[ns].Flags & StraightPathFlags.OffMeshConnection) != 0 ||
|
||||||
|
!InRange(steerPath[ns].Point.Position, startPos, minTargetDist, 1000.0f))
|
||||||
|
break;
|
||||||
|
|
||||||
|
ns++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//failed to find good point to steer to
|
||||||
|
if (ns >= nsteerPath)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
steerPos = steerPath[ns].Point.Position;
|
||||||
|
steerPos.Y = startPos.Y;
|
||||||
|
steerPosFlag = steerPath[ns].Flags;
|
||||||
|
if (steerPosFlag == StraightPathFlags.None && ns == (nsteerPath - 1))
|
||||||
|
steerPosFlag = StraightPathFlags.End; // otherwise seeks path infinitely!!!
|
||||||
|
steerPosRef = steerPath[ns].Point.Polygon;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool InRange(SharpNav.Geometry.Vector3 v1, SharpNav.Geometry.Vector3 v2, float r, float h)
|
||||||
|
{
|
||||||
|
float dx = v2.X - v1.X;
|
||||||
|
float dy = v2.Y - v1.Y;
|
||||||
|
float dz = v2.Z - v1.Z;
|
||||||
|
return (dx * dx + dz * dz) < (r * r) && Math.Abs(dy) < h;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static Vector3 GamePosToNavmeshPos(float x, float y, float z)
|
||||||
|
{
|
||||||
|
return new Vector3(x, -z, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 NavmeshPosToGamePos(float x, float y, float z)
|
||||||
|
{
|
||||||
|
return new Vector3(x, z, -y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
<?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>
|
||||||
|
|
|
@ -1,13 +1,4 @@
|
||||||
using FFXIVClassic.Common;
|
using FFXIVClassic_World_Server.Packets.Send.Subpackets;
|
||||||
using FFXIVClassic_World_Server.DataObjects.Group;
|
|
||||||
using FFXIVClassic_World_Server.Packets.Send.Subpackets;
|
|
||||||
using FFXIVClassic_World_Server.Packets.Send.Subpackets.Groups;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFXIVClassic_World_Server.DataObjects
|
namespace FFXIVClassic_World_Server.DataObjects
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>FFXIVClassic_World_Server</RootNamespace>
|
<RootNamespace>FFXIVClassic_World_Server</RootNamespace>
|
||||||
<AssemblyName>FFXIVClassic World Server</AssemblyName>
|
<AssemblyName>FFXIVClassic World Server</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
<TargetFrameworkProfile />
|
<TargetFrameworkProfile />
|
||||||
|
@ -48,15 +48,31 @@
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<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>
|
||||||
|
<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="MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
|
<Reference Include="MySql.Data, Version=6.9.8.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
|
||||||
<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>
|
||||||
|
|
|
@ -172,6 +172,7 @@ namespace FFXIVClassic_World_Server
|
||||||
|
|
||||||
if (subpacket.gameMessage.opcode != 0x1 && subpacket.gameMessage.opcode != 0xca)
|
if (subpacket.gameMessage.opcode != 0x1 && subpacket.gameMessage.opcode != 0xca)
|
||||||
subpacket.DebugPrintSubPacket();
|
subpacket.DebugPrintSubPacket();
|
||||||
|
|
||||||
if (subpacket.gameMessage.opcode >= 0x1000)
|
if (subpacket.gameMessage.opcode >= 0x1000)
|
||||||
{
|
{
|
||||||
//subpacket.DebugPrintSubPacket();
|
//subpacket.DebugPrintSubPacket();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<?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="net452" />
|
<package id="Cyotek.CircularBuffer" version="1.0.0.0" targetFramework="net452" />
|
||||||
<package id="Dapper" version="1.42" targetFramework="net452" />
|
|
||||||
<package id="MySql.Data" version="6.9.8" targetFramework="net452" />
|
<package id="MySql.Data" version="6.9.8" targetFramework="net452" />
|
||||||
<package id="NLog" version="4.3.5" targetFramework="net452" />
|
<package id="NLog" version="4.3.5" targetFramework="net452" />
|
||||||
<package id="NLog.Config" version="4.3.5" targetFramework="net452" />
|
<package id="NLog.Config" version="4.3.5" targetFramework="net452" />
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 14
|
# Visual Studio 2013
|
||||||
VisualStudioVersion = 14.0.23107.0
|
VisualStudioVersion = 12.0.31101.0
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFXIVClassic Map Server", "FFXIVClassic Map Server\FFXIVClassic Map Server.csproj", "{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFXIVClassic Map Server", "FFXIVClassic Map Server\FFXIVClassic Map Server.csproj", "{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}"
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
ProjectSection(ProjectDependencies) = postProject
|
||||||
|
@ -25,31 +25,56 @@ EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Debug|x64.Build.0 = Debug|x64
|
||||||
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{E8FA2784-D4B9-4711-8CC6-712A4B1CD54F}.Release|x64.Build.0 = Release|x64
|
||||||
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Debug|x64.Build.0 = Debug|x64
|
||||||
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{703091E0-F69C-4177-8FAE-C258AC6A65AA}.Release|x64.Build.0 = Release|x64
|
||||||
{3A3D6626-C820-4C18-8C81-64811424F20E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3A3D6626-C820-4C18-8C81-64811424F20E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Debug|x64.Build.0 = Debug|x64
|
||||||
{3A3D6626-C820-4C18-8C81-64811424F20E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3A3D6626-C820-4C18-8C81-64811424F20E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{3A3D6626-C820-4C18-8C81-64811424F20E}.Release|x64.Build.0 = Release|x64
|
||||||
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Debug|x64.Build.0 = Debug|x64
|
||||||
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{3067889D-8A50-40D6-9CD5-23AA8EA96F26}.Release|x64.Build.0 = Release|x64
|
||||||
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Debug|x64.Build.0 = Debug|x64
|
||||||
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{0FFA9D2F-41C6-443C-99B7-665702CF548F}.Release|x64.Build.0 = Release|x64
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {F350E848-7622-48E1-87DC-4C2B1596122B}
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -32,6 +32,26 @@
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<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>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<Prefer32Bit>true</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
|
|
58
data/scripts/ability.lua
Normal file
58
data/scripts/ability.lua
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
-- todo: add enums for status effects in global.lua
|
||||||
|
require("global")
|
||||||
|
require("battleutils")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
statId - see BattleTemp.cs
|
||||||
|
modifier - Modifier.Intelligence, Modifier.Mind (see Modifier.cs)
|
||||||
|
multiplier -
|
||||||
|
]]
|
||||||
|
function HandleHealingSkill(caster, target, skill, action, statId, modifierId, multiplier, baseAmount)
|
||||||
|
potency = potency or 1.0;
|
||||||
|
healAmount = baseAmount;
|
||||||
|
|
||||||
|
-- todo: shit based on mnd
|
||||||
|
local mind = caster.GetMod(Modifier.Mind);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function HandleAttackSkill(caster, target, skill, action, statId, modifierId, multiplier, baseAmount)
|
||||||
|
-- todo: actually handle this
|
||||||
|
damage = baseAmount or math.random(1,10) * 10;
|
||||||
|
|
||||||
|
return damage;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function HandleStoneskin(caster, target, skill, action, statId, modifierId, damage)
|
||||||
|
--[[
|
||||||
|
if target.statusEffects.HasStatusEffect(StatusEffect.Stoneskin) then
|
||||||
|
-- todo: damage reduction
|
||||||
|
return true;
|
||||||
|
end;
|
||||||
|
]]
|
||||||
|
return false;
|
||||||
|
end;
|
||||||
|
|
||||||
|
--For abilities that inflict statuses, like aegis boon or taunt
|
||||||
|
function onStatusAbilityFinish(caster, target, skill, action)
|
||||||
|
--action.CalcHitType(caster, target, skill);
|
||||||
|
action.DoAction(caster, target, skill);
|
||||||
|
action.TryStatus(caster, target, skill, false);
|
||||||
|
|
||||||
|
return action.amount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onAttackAbilityFinish(caster, target, skill, action)
|
||||||
|
local damage = math.random(50, 150);
|
||||||
|
action.amount = damage;
|
||||||
|
action.DoAction(caster, target, skill);
|
||||||
|
|
||||||
|
return action.amount;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function onHealAbilityFinish(caster, target, skill, action)
|
||||||
|
local amount = math.random(150, 250);
|
||||||
|
action.amount = amount;
|
||||||
|
action.DoAction(caster, target, skill);
|
||||||
|
action.TryStatus(caster, target, skill, true);
|
||||||
|
return action.amount;
|
||||||
|
end;
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue