mirror of
https://github.com/ACEmulator/ACE
synced 2026-08-17 12:26:06 -04:00
314 lines
10 KiB
C#
314 lines
10 KiB
C#
using System;
|
|
using System.Threading;
|
|
using ACE.Entity;
|
|
using ACE.Entity.Enum;
|
|
using ACE.Server.Entity;
|
|
using ACE.Server.Entity.Actions;
|
|
using ACE.Server.Network.GameEvent.Events;
|
|
using ACE.Server.Network.GameMessages.Messages;
|
|
using ACE.Server.Physics.Animation;
|
|
using ACE.Server.Physics.Collision;
|
|
|
|
namespace ACE.Server.WorldObjects
|
|
{
|
|
partial class Player
|
|
{
|
|
private TimeSpan defaultMoveToTimeout = TimeSpan.FromSeconds(15); // This is just a starting point number. It may be far off from retail.
|
|
|
|
private int moveToChainCounter;
|
|
private DateTime moveToChainStartTime;
|
|
|
|
private int lastCompletedMove;
|
|
|
|
public bool IsPlayerMovingTo => moveToChainCounter > lastCompletedMove;
|
|
|
|
private int GetNextMoveToChainNumber()
|
|
{
|
|
return Interlocked.Increment(ref moveToChainCounter);
|
|
}
|
|
|
|
public void StopExistingMoveToChains()
|
|
{
|
|
Interlocked.Increment(ref moveToChainCounter);
|
|
|
|
lastCompletedMove = moveToChainCounter;
|
|
}
|
|
|
|
public void CreateMoveToChain(WorldObject target, Action<bool> callback, float? useRadius = null, bool rotate = true)
|
|
{
|
|
if (FastTick)
|
|
{
|
|
CreateMoveToChain2(target, callback, useRadius, rotate);
|
|
return;
|
|
}
|
|
|
|
var thisMoveToChainNumber = GetNextMoveToChainNumber();
|
|
|
|
if (target.Location == null)
|
|
{
|
|
StopExistingMoveToChains();
|
|
log.Error($"{Name}.CreateMoveToChain({target.Name}): target.Location is null");
|
|
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
// fix bug in magic combat mode after walking to target,
|
|
// crouch animation steps out of range
|
|
if (useRadius == null)
|
|
useRadius = target.UseRadius ?? 0.6f;
|
|
|
|
if (CombatMode == CombatMode.Magic)
|
|
useRadius = Math.Max(0.0f, useRadius.Value - 0.2f);
|
|
|
|
// already within use distance?
|
|
var withinUseRadius = CurrentLandblock.WithinUseRadius(this, target.Guid, out var targetValid, useRadius);
|
|
if (withinUseRadius)
|
|
{
|
|
if (rotate)
|
|
{
|
|
// send TurnTo motion
|
|
var rotateTime = Rotate(target);
|
|
var actionChain = new ActionChain();
|
|
actionChain.AddDelaySeconds(rotateTime);
|
|
actionChain.AddAction(this, () =>
|
|
{
|
|
lastCompletedMove = thisMoveToChainNumber;
|
|
callback(true);
|
|
});
|
|
actionChain.EnqueueChain();
|
|
}
|
|
else
|
|
{
|
|
lastCompletedMove = thisMoveToChainNumber;
|
|
callback(true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (target.WeenieType == WeenieType.Portal)
|
|
MoveToPosition(target.Location);
|
|
else
|
|
MoveToObject(target, useRadius);
|
|
|
|
moveToChainStartTime = DateTime.UtcNow;
|
|
|
|
MoveToChain(target, thisMoveToChainNumber, callback, useRadius);
|
|
}
|
|
|
|
public void MoveToChain(WorldObject target, int thisMoveToChainNumber, Action<bool> callback, float? useRadius = null)
|
|
{
|
|
if (thisMoveToChainNumber != moveToChainCounter)
|
|
{
|
|
if (thisMoveToChainNumber > lastCompletedMove)
|
|
lastCompletedMove = thisMoveToChainNumber;
|
|
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
// Break loop if CurrentLandblock == null (we portaled or logged out)
|
|
if (CurrentLandblock == null)
|
|
{
|
|
StopExistingMoveToChains(); // This increments our moveToChainCounter and thus, should stop any additional actions in this chain
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
// Have we timed out?
|
|
if (moveToChainStartTime + defaultMoveToTimeout <= DateTime.UtcNow)
|
|
{
|
|
StopExistingMoveToChains(); // This increments our moveToChainCounter and thus, should stop any additional actions in this chain
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
// Are we within use radius?
|
|
var success = CurrentLandblock.WithinUseRadius(this, target.Guid, out var targetValid, useRadius);
|
|
|
|
// If one of the items isn't on a landblock
|
|
if (!targetValid)
|
|
{
|
|
StopExistingMoveToChains(); // This increments our moveToChainCounter and thus, should stop any additional actions in this chain
|
|
callback(false);
|
|
return;
|
|
}
|
|
|
|
if (!success)
|
|
{
|
|
// target not reached yet
|
|
var actionChain = new ActionChain();
|
|
actionChain.AddDelaySeconds(0.1f);
|
|
actionChain.AddAction(this, () => MoveToChain(target, thisMoveToChainNumber, callback, useRadius));
|
|
actionChain.EnqueueChain();
|
|
}
|
|
else
|
|
{
|
|
if (thisMoveToChainNumber > lastCompletedMove)
|
|
lastCompletedMove = thisMoveToChainNumber;
|
|
|
|
callback(true);
|
|
}
|
|
}
|
|
|
|
public Position StartJump;
|
|
|
|
public override void MoveTo(WorldObject target, float runRate = 0.0f)
|
|
{
|
|
if (runRate == 0.0f)
|
|
runRate = GetRunRate();
|
|
|
|
//Console.WriteLine($"{Name}.MoveTo({target.Name})");
|
|
|
|
var motion = new Motion(this, target, MovementType.MoveToObject);
|
|
motion.MoveToParameters.MovementParameters |= MovementParams.CanCharge | MovementParams.FailWalk | MovementParams.UseFinalHeading | MovementParams.Sticky | MovementParams.MoveAway;
|
|
motion.MoveToParameters.WalkRunThreshold = 1.0f;
|
|
motion.MoveToParameters.Speed = 1.5f; // charge modifier
|
|
motion.MoveToParameters.FailDistance = 15.0f;
|
|
motion.RunRate = runRate;
|
|
|
|
CurrentMotionState = motion;
|
|
|
|
EnqueueBroadcastMotion(motion);
|
|
|
|
var mvp = GetChargeParameters();
|
|
PhysicsObj.MoveToObject(target.PhysicsObj, mvp);
|
|
|
|
IsMoving = true;
|
|
|
|
MoveTo_Tick();
|
|
}
|
|
|
|
public static float MoveToRate = 0.1f;
|
|
|
|
public void MoveTo_Tick()
|
|
{
|
|
//Console.WriteLine($"{Name}.MoveTo_Tick()");
|
|
|
|
PhysicsObj.update_object();
|
|
|
|
if (IsMoving)
|
|
Enqueue_NextMoveTick();
|
|
}
|
|
|
|
public void Enqueue_NextMoveTick()
|
|
{
|
|
var actionChain = new ActionChain();
|
|
actionChain.AddDelaySeconds(MoveToRate);
|
|
actionChain.AddAction(this, MoveTo_Tick);
|
|
actionChain.EnqueueChain();
|
|
}
|
|
|
|
public override void OnMoveComplete(WeenieError status)
|
|
{
|
|
//Console.WriteLine($"{Name}.OnMoveComplete({status})");
|
|
|
|
IsMoving = false;
|
|
|
|
if (IsPlayerMovingTo2)
|
|
{
|
|
OnMoveComplete_MoveTo2(status);
|
|
|
|
if (MagicState.IsCasting)
|
|
OnMoveComplete_Magic(status);
|
|
|
|
return;
|
|
}
|
|
|
|
switch (status)
|
|
{
|
|
case WeenieError.None:
|
|
|
|
Attack(MeleeTarget, AttackSequence);
|
|
break;
|
|
|
|
default:
|
|
|
|
SendWeenieError(status);
|
|
HandleActionCancelAttack();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public MovementParameters GetChargeParameters()
|
|
{
|
|
var mvp = new MovementParameters();
|
|
|
|
// set non-default params for player melee charge
|
|
mvp.Flags &= ~MovementParamFlags.CanWalk;
|
|
mvp.Flags |= MovementParamFlags.CanCharge | MovementParamFlags.FailWalk | MovementParamFlags.UseFinalHeading | MovementParamFlags.Sticky | MovementParamFlags.MoveAway;
|
|
mvp.HoldKeyToApply = HoldKey.Run;
|
|
mvp.FailDistance = 15.0f;
|
|
mvp.Speed = 1.5f;
|
|
|
|
return mvp;
|
|
}
|
|
|
|
public void HandleFallingDamage(EnvCollisionProfile collision)
|
|
{
|
|
// starting with phat logic
|
|
|
|
// jumping skill sort of used as a damping factor here
|
|
//var jumpVelocity = 0.0f;
|
|
//PhysicsObj.WeenieObj.InqJumpVelocity(1.0f, out jumpVelocity);
|
|
var jumpVelocity = 11.25434f; // TODO: figure out how to scale this better
|
|
|
|
//var currVelocity = FastTick ? PhysicsObj.Velocity : PhysicsObj.CachedVelocity;
|
|
var currVelocity = PhysicsObj.Velocity;
|
|
|
|
var overspeed = jumpVelocity + currVelocity.Z + 4.5f; // a little leeway
|
|
|
|
var ratio = -overspeed / jumpVelocity;
|
|
|
|
/*Console.WriteLine($"Jump velocity: {jumpVelocity}");
|
|
Console.WriteLine($"Velocity: {currVelocity}");
|
|
Console.WriteLine($"Overspeed: {overspeed}");
|
|
Console.WriteLine($"Ratio: {ratio}");*/
|
|
|
|
if (ratio > 0.0f)
|
|
{
|
|
//var damage = ratio * 40.0f;
|
|
var damage = ratio * 87.293810f;
|
|
//Console.WriteLine($"Damage: {damage}");
|
|
|
|
// bludgeon damage
|
|
// impact damage
|
|
//if (damage > 0.0f && (FastTick || StartJump == null || StartJump.PositionZ - PhysicsObj.Position.Frame.Origin.Z > 10.0f))
|
|
if (damage > 0.0f)
|
|
TakeDamage_Falling(damage);
|
|
}
|
|
}
|
|
|
|
public void TakeDamage_Falling(float amount)
|
|
{
|
|
if (IsDead || Invincible) return;
|
|
|
|
// handle lifestone protection?
|
|
if (UnderLifestoneProtection)
|
|
{
|
|
HandleLifestoneProtection();
|
|
return;
|
|
}
|
|
|
|
// scale by bludgeon protection
|
|
var resistance = GetResistanceMod(DamageType.Bludgeon, null, null);
|
|
var damage = (uint)Math.Round(amount * resistance);
|
|
|
|
// update health
|
|
var damageTaken = (uint)-UpdateVitalDelta(Health, (int)-damage);
|
|
DamageHistory.Add(this, DamageType.Bludgeon, damageTaken);
|
|
|
|
var msg = Strings.GetFallMessage(damageTaken, Health.MaxValue);
|
|
|
|
SendMessage(msg, ChatMessageType.Combat);
|
|
|
|
if (Health.Current <= 0)
|
|
{
|
|
OnDeath(new DamageHistoryInfo(this), DamageType.Bludgeon, false);
|
|
Die();
|
|
}
|
|
else
|
|
EnqueueBroadcast(new GameMessageSound(Guid, Sound.Wound3, 1.0f));
|
|
}
|
|
}
|
|
}
|