mirror of
https://bitbucket.org/Ioncannon/project-meteor-server.git
synced 2025-04-02 19:42:05 -04:00
373 lines
15 KiB
C#
373 lines
15 KiB
C#
|
|
using FFXIVClassic.Common;
|
|
using FFXIVClassic_Map_Server.actors.area;
|
|
using FFXIVClassic_Map_Server.actors.group;
|
|
using FFXIVClassic_Map_Server.Actors.Chara;
|
|
using FFXIVClassic_Map_Server.packets.send.actor;
|
|
using FFXIVClassic_Map_Server.utils;
|
|
using FFXIVClassic_Map_Server.actors.chara.ai;
|
|
using System;
|
|
|
|
namespace FFXIVClassic_Map_Server.Actors
|
|
{
|
|
class Character:Actor
|
|
{
|
|
public const int SIZE = 0;
|
|
public const int COLORINFO = 1;
|
|
public const int FACEINFO = 2;
|
|
public const int HIGHLIGHT_HAIR = 3;
|
|
public const int VOICE = 4;
|
|
public const int MAINHAND = 5;
|
|
public const int OFFHAND = 6;
|
|
public const int SPMAINHAND = 7;
|
|
public const int SPOFFHAND = 8;
|
|
public const int THROWING = 9;
|
|
public const int PACK = 10;
|
|
public const int POUCH = 11;
|
|
public const int HEADGEAR = 12;
|
|
public const int BODYGEAR = 13;
|
|
public const int LEGSGEAR = 14;
|
|
public const int HANDSGEAR = 15;
|
|
public const int FEETGEAR = 16;
|
|
public const int WAISTGEAR = 17;
|
|
public const int NECKGEAR = 18;
|
|
public const int L_EAR = 19;
|
|
public const int R_EAR = 20;
|
|
public const int R_WRIST = 21;
|
|
public const int L_WRIST = 22;
|
|
public const int R_RINGFINGER = 23;
|
|
public const int L_RINGFINGER = 24;
|
|
public const int R_INDEXFINGER = 25;
|
|
public const int L_INDEXFINGER = 26;
|
|
public const int UNKNOWN = 27;
|
|
|
|
public bool isStatic = false;
|
|
|
|
public bool isMovingToSpawn = false;
|
|
|
|
public uint modelId;
|
|
public uint[] appearanceIds = new uint[28];
|
|
|
|
public uint animationId = 0;
|
|
|
|
public uint currentTarget = 0xC0000000;
|
|
public uint currentLockedTarget = 0xC0000000;
|
|
|
|
public uint currentActorIcon = 0;
|
|
|
|
public Work work = new Work();
|
|
public CharaWork charaWork = new CharaWork();
|
|
|
|
public Group currentParty = null;
|
|
public ContentGroup currentContentGroup = null;
|
|
|
|
public DateTime lastAiUpdate;
|
|
|
|
public AIContainer aiContainer;
|
|
|
|
public Character(uint actorID) : base(actorID)
|
|
{
|
|
//Init timer array to "notimer"
|
|
for (int i = 0; i < charaWork.statusShownTime.Length; i++)
|
|
charaWork.statusShownTime[i] = 0xFFFFFFFF;
|
|
}
|
|
|
|
public SubPacket CreateAppearancePacket(uint playerActorId)
|
|
{
|
|
SetActorAppearancePacket setappearance = new SetActorAppearancePacket(modelId, appearanceIds);
|
|
return setappearance.BuildPacket(actorId, playerActorId);
|
|
}
|
|
|
|
public SubPacket CreateInitStatusPacket(uint playerActorId)
|
|
{
|
|
return (SetActorStatusAllPacket.BuildPacket(actorId, playerActorId, charaWork.status));
|
|
}
|
|
|
|
public SubPacket CreateSetActorIconPacket(uint playerActorId)
|
|
{
|
|
return SetActorIconPacket.BuildPacket(actorId, playerActorId, currentActorIcon);
|
|
}
|
|
|
|
public SubPacket CreateIdleAnimationPacket(uint playerActorId)
|
|
{
|
|
return SetActorSubStatPacket.BuildPacket(actorId, playerActorId, 0, 0, 0, 0, 0, 0, animationId);
|
|
}
|
|
|
|
public void SetQuestGraphic(Player player, int graphicNum)
|
|
{
|
|
player.QueuePacket(SetActorQuestGraphicPacket.BuildPacket(player.actorId, actorId, graphicNum));
|
|
}
|
|
|
|
public void SetCurrentContentGroup(ContentGroup group, Player player = null)
|
|
{
|
|
if (group != null)
|
|
charaWork.currentContentGroup = group.GetTypeId();
|
|
else
|
|
charaWork.currentContentGroup = 0;
|
|
|
|
currentContentGroup = group;
|
|
|
|
if (player != null)
|
|
{
|
|
ActorPropertyPacketUtil propPacketUtil = new ActorPropertyPacketUtil("charaWork/currentContentGroup", this, actorId);
|
|
propPacketUtil.AddProperty("charaWork.currentContentGroup");
|
|
player.QueuePackets(propPacketUtil.Done());
|
|
}
|
|
}
|
|
|
|
public void PlayAnimation(uint animId)
|
|
{
|
|
zone.BroadcastPacketAroundActor(this, PlayAnimationOnActorPacket.BuildPacket(actorId, actorId, animId));
|
|
}
|
|
|
|
public void PathTo(float x, float y, float z, float stepSize = 0.70f, int maxPath = 40, float polyRadius = 0.0f)
|
|
{
|
|
var pos = new Vector3(positionX, positionY, positionZ);
|
|
var dest = new Vector3(x, y, z);
|
|
|
|
var sw = new System.Diagnostics.Stopwatch();
|
|
sw.Start();
|
|
|
|
var path = utils.NavmeshUtils.GetPath(((Zone)GetZone()), pos, dest, stepSize, maxPath, polyRadius);
|
|
|
|
if (path != null)
|
|
{
|
|
if (oldPositionX == 0.0f && oldPositionY == 0.0f && oldPositionZ == 0.0f)
|
|
{
|
|
oldPositionX = positionX;
|
|
oldPositionY = positionY;
|
|
oldPositionZ = positionZ;
|
|
}
|
|
|
|
// todo: something went wrong
|
|
if (path.Count == 0)
|
|
{
|
|
positionX = oldPositionX;
|
|
positionY = oldPositionY;
|
|
positionZ = oldPositionZ;
|
|
}
|
|
|
|
positionUpdates = path;
|
|
|
|
this.hasMoved = true;
|
|
this.isAtSpawn = false;
|
|
|
|
sw.Stop();
|
|
((Zone)zone).pathCalls++;
|
|
((Zone)zone).pathCallTime += sw.ElapsedMilliseconds;
|
|
|
|
if (path.Count == 1)
|
|
Program.Log.Info($"mypos: {positionX} {positionY} {positionZ} | targetPos: {x} {y} {z} | step {stepSize} | maxPath {maxPath} | polyRadius {polyRadius}");
|
|
|
|
Program.Log.Error("[{0}][{1}] Created {2} points in {3} milliseconds", actorId, actorName, path.Count, sw.ElapsedMilliseconds);
|
|
}
|
|
}
|
|
|
|
public void FollowTarget(Actor target, float stepSize = 1.2f, int maxPath = 25, float radius = 0.0f)
|
|
{
|
|
var player = target as Player;
|
|
|
|
if (player != null)
|
|
{
|
|
if (this.target != player)
|
|
{
|
|
this.target = target;
|
|
}
|
|
this.moveState = player.moveState;
|
|
this.moveSpeeds = player.moveSpeeds;
|
|
|
|
PathTo(player.positionX, player.positionY, player.positionZ, stepSize, maxPath, radius);
|
|
}
|
|
}
|
|
|
|
public void OnPath(Vector3 point)
|
|
{
|
|
if (positionUpdates != null && positionUpdates.Count > 0)
|
|
{
|
|
if (point == positionUpdates[positionUpdates.Count - 1])
|
|
{
|
|
var myPos = new Vector3(positionX, positionY, positionZ);
|
|
//point = NavmeshUtils.GetPath((Zone)zone, myPos, point, 0.35f, 1, 0.000001f, true)?[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Update(DateTime tick)
|
|
{
|
|
// todo: actual ai controllers
|
|
// todo: mods to control different params instead of hardcode
|
|
// todo: other ai helpers
|
|
|
|
// time elapsed since last ai update
|
|
|
|
if (aiContainer != null)
|
|
{
|
|
this.aiContainer.Update(tick);
|
|
}
|
|
|
|
/*
|
|
var diffTime = (tick - lastAiUpdate);
|
|
|
|
if (this is Player)
|
|
{
|
|
// todo: handle player stuff here
|
|
}
|
|
else
|
|
{
|
|
// todo: handle mobs only?
|
|
//if (this.isStatic)
|
|
// return;
|
|
|
|
// todo: this too
|
|
if (diffTime.Milliseconds >= 10)
|
|
{
|
|
bool foundActor = false;
|
|
|
|
// leash back to spawn
|
|
if (!isMovingToSpawn && this.oldPositionX != 0.0f && this.oldPositionY != 0.0f && this.oldPositionZ != 0.0f)
|
|
{
|
|
//var spawnDistanceSq = Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ);
|
|
|
|
// todo: actual spawn leash and modifiers read from table
|
|
// set a leash to path back to spawn even if have target
|
|
// (50 yalms)
|
|
if (Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ) >= 3025)
|
|
{
|
|
this.isMovingToSpawn = true;
|
|
this.target = null;
|
|
this.lastMoveUpdate = this.lastMoveUpdate.AddSeconds(-5);
|
|
this.hasMoved = false;
|
|
ClearPositionUpdates();
|
|
}
|
|
}
|
|
|
|
// check if player
|
|
if (target != null && target is Player)
|
|
{
|
|
var player = target as Player;
|
|
|
|
// deaggro if zoning/logging
|
|
// todo: player.isZoning seems to be busted
|
|
if (player.playerSession.isUpdatesLocked)
|
|
{
|
|
target = null;
|
|
ClearPositionUpdates();
|
|
}
|
|
}
|
|
|
|
Player closestPlayer = null;
|
|
float closestPlayerDistanceSq = 1000.0f;
|
|
|
|
// dont bother checking for any in-range players if going back to spawn
|
|
if (!this.isMovingToSpawn)
|
|
{
|
|
foreach (var actor in zone.GetActorsAroundActor(this, 65))
|
|
{
|
|
if (actor is Player && actor != this)
|
|
{
|
|
var player = actor as Player;
|
|
|
|
// skip if zoning/logging
|
|
// todo: player.isZoning seems to be busted
|
|
if (player != null && player.playerSession.isUpdatesLocked)
|
|
continue;
|
|
|
|
// find distance between self and target
|
|
var distanceSq = Utils.DistanceSquared(positionX, positionY, positionZ, player.positionX, player.positionY, player.positionZ);
|
|
|
|
int maxDistanceSq = player == target ? 900 : 100;
|
|
|
|
// check target isnt too far
|
|
// todo: create cone thing for IsFacing
|
|
if (distanceSq <= maxDistanceSq && distanceSq <= closestPlayerDistanceSq && (IsFacing(player) || true))
|
|
{
|
|
closestPlayerDistanceSq = distanceSq;
|
|
closestPlayer = player;
|
|
foundActor = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// found a target
|
|
if (foundActor)
|
|
{
|
|
// make sure we're not already moving so we dont spam packets
|
|
if (!hasMoved)
|
|
{
|
|
// todo: include model size and mob specific distance checks
|
|
if (closestPlayerDistanceSq >= 9)
|
|
{
|
|
FollowTarget(closestPlayer, 2.5f, 4);
|
|
}
|
|
// too close, spread out
|
|
else if (closestPlayerDistanceSq <= 0.85f)
|
|
{
|
|
QueuePositionUpdate(target.FindRandomPointAroundActor(0.65f, 0.85f));
|
|
}
|
|
|
|
// we have a target, face them
|
|
if (target != null)
|
|
{
|
|
LookAt(target);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// time elapsed since last move update
|
|
var diffMove = (tick - lastMoveUpdate);
|
|
|
|
// todo: modifier for DelayBeforeRoamToSpawn
|
|
// player disappeared
|
|
if (!foundActor && diffMove.Seconds >= 5)
|
|
{
|
|
// dont path if havent moved before
|
|
if (!hasMoved && oldPositionX != 0.0f && oldPositionY != 0.0f && oldPositionZ != 0.0f)
|
|
{
|
|
// check within spawn radius
|
|
this.isAtSpawn = Utils.DistanceSquared(positionX, positionY, positionZ, oldPositionX, oldPositionY, oldPositionZ) <= 625.0f;
|
|
|
|
// make sure we have no target
|
|
if (this.target == null)
|
|
{
|
|
// path back to spawn
|
|
if (!this.isAtSpawn)
|
|
{
|
|
PathTo(oldPositionX, oldPositionY, oldPositionZ, 2.8f);
|
|
}
|
|
// within spawn range, find a random point
|
|
else if (diffMove.Seconds >= 15)
|
|
{
|
|
// todo: polyRadius isnt euclidean distance..
|
|
// pick a random point within 10 yalms of spawn
|
|
PathTo(oldPositionX, oldPositionY, oldPositionZ, 2.5f, 7, 2.5f);
|
|
|
|
// face destination
|
|
if (positionUpdates.Count > 0)
|
|
{
|
|
var destinationPos = positionUpdates[positionUpdates.Count - 1];
|
|
LookAt(destinationPos.X, destinationPos.Y);
|
|
}
|
|
if (this.isMovingToSpawn)
|
|
{
|
|
this.isMovingToSpawn = false;
|
|
this.ResetMoveSpeedsToDefault();
|
|
this.ChangeState(SetActorStatePacket.MAIN_STATE_DEAD2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// todo: this is retarded. actually no it isnt, i didnt deaggro if out of range..
|
|
target = null;
|
|
}
|
|
// update last ai update time to now
|
|
lastAiUpdate = DateTime.Now;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
}
|
|
|
|
}
|