ace/Source/ACE.Server/WorldObjects/Player_Use.cs
Mag-nus 9d70b48380
Improve logging overhead by fixing string interpolation (#4107)
* First batch

* Second batch

* another batch

* last batch hopefully

* fix
2024-03-13 02:38:39 +00:00

391 lines
14 KiB
C#

using System;
using ACE.Entity;
using ACE.Entity.Enum;
using ACE.Server.Entity;
using ACE.Server.Entity.Actions;
using ACE.Server.Managers;
using ACE.Server.Network.GameEvent.Events;
namespace ACE.Server.WorldObjects
{
partial class Player
{
/// <summary>
/// This is set by HandleActionUseItem / TryUseItem
/// </summary>
public ObjectGuid LastOpenedContainerId { get; set; }
/// <summary>
/// This is set by Hook.ActOnUse
/// </summary>
public ObjectGuid LasUsedHookId { get; set; }
/// <summary>
/// Handles the 'GameAction 0x35 - UseWithTarget' network message
/// when player double clicks an inventory item resulting in a target indicator
/// and then clicks another item
/// </summary>
public void HandleActionUseWithTarget(uint sourceObjectGuid, uint targetObjectGuid)
{
if (PKLogout)
{
SendUseDoneEvent(WeenieError.YouHaveBeenInPKBattleTooRecently);
return;
}
StopExistingMoveToChains();
// source item is always in our possession
var sourceItem = FindObject(sourceObjectGuid, SearchLocations.MyInventory | SearchLocations.MyEquippedItems, out _, out _, out var sourceItemIsEquipped);
if (sourceItem == null)
{
log.Warn($"{Name}.HandleActionUseWithTarget({sourceObjectGuid:X8}, {targetObjectGuid:X8}): couldn't find {sourceObjectGuid:X8}");
SendUseDoneEvent();
return;
}
// Resolve the guid to an object that is either in our possession or on the Landblock
var target = FindObject(targetObjectGuid, SearchLocations.MyInventory | SearchLocations.MyEquippedItems | SearchLocations.Landblock);
if (target == null)
{
log.Warn($"{Name}.HandleActionUseWithTarget({sourceObjectGuid:X8}, {targetObjectGuid:X8}): couldn't find {targetObjectGuid:X8}");
SendUseDoneEvent();
return;
}
// handle objects with built-in spells
if (sourceItem.SpellDID != null)
{
if (!RecipeManager.VerifyUse(this, sourceItem, target))
{
//var spell = new Spell((int)sourceItem.SpellDID);
//if (spell != null)
// Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, $"{spell.Name} cannot be cast on {target.Name}."));
var usable = sourceItem.ItemUseable ?? Usable.Undef;
var action = "";
if (usable.HasFlag(Usable.Wielded))
action = "wield";
else if (usable.HasFlag(Usable.Contained))
action = "contain";
Session.Network.EnqueueSend(new GameEventCommunicationTransientString(Session, $"You must {action} the {sourceItem.Name} to use it."));
SendUseDoneEvent();
return;
}
// check activation requirements
var result = sourceItem.CheckUseRequirements(this);
if (!result.Success)
{
if (result.Message != null)
Session.Network.EnqueueSend(result.Message);
SendUseDoneEvent();
return;
}
else
{
HandleActionCastTargetedSpell(targetObjectGuid, sourceItem.SpellDID ?? 0, sourceItem);
return;
}
}
// handle casters with built-in spells
//if (sourceItemIsEquipped)
//{
// if (sourceItem.SpellDID != null)
// {
// // check activation requirements
// var result = sourceItem.CheckUseRequirements(this);
// if (!result.Success)
// {
// if (result.Message != null)
// Session.Network.EnqueueSend(result.Message);
// SendUseDoneEvent();
// }
// else
// {
// HandleActionCastTargetedSpell(targetObjectGuid, sourceItem.SpellDID ?? 0, true);
// return;
// }
// }
// else
// {
// SendUseDoneEvent();
// }
// return;
//}
if (IsTrading)
{
if (sourceItem.IsBeingTradedOrContainsItemBeingTraded(ItemsInTradeWindow))
{
SendUseDoneEvent(WeenieError.TradeItemBeingTraded);
//SendWeenieError(WeenieError.TradeItemBeingTraded);
return;
}
if (target.IsBeingTradedOrContainsItemBeingTraded(ItemsInTradeWindow))
{
SendUseDoneEvent(WeenieError.TradeItemBeingTraded);
//SendWeenieError(WeenieError.TradeItemBeingTraded);
return;
}
}
// re-verify client checks
if (((sourceItem.TargetType ?? ItemType.None) & target.ItemType) == ItemType.None)
{
// ItemHolder::TargetCompatibleWithObject
SendTransientError($"Cannot use the {sourceItem.Name} with the {target.Name}");
SendUseDoneEvent();
return;
}
if (target.CurrentLandblock != null && target != this)
{
// todo: verify target can be used remotely
// move RecipeManager.VerifyUse logic into base Player_Use
// this was avoided because i didn't want to deal with the ramifications of random items missing the correct ItemUseable flags,
// and because there are still some ItemUseable flags with missing logic we haven't quite figured out yet
if (IsBusy)
{
SendUseDoneEvent(WeenieError.YoureTooBusy);
return;
}
CreateMoveToChain(target, (success) =>
{
if (success)
sourceItem.HandleActionUseOnTarget(this, target);
else
SendUseDoneEvent();
});
}
else
sourceItem.HandleActionUseOnTarget(this, target);
}
/// <summary>
/// Handles the 'GameAction 0x36 - UseItem' network message
/// when player double clicks an item
/// </summary>
public void HandleActionUseItem(uint itemGuid)
{
if (PKLogout)
{
SendUseDoneEvent(WeenieError.YouHaveBeenInPKBattleTooRecently);
return;
}
StopExistingMoveToChains();
var item = FindObject(itemGuid, SearchLocations.MyInventory | SearchLocations.MyEquippedItems | SearchLocations.Landblock);
if (IsTrading && item.IsBeingTradedOrContainsItemBeingTraded(ItemsInTradeWindow))
{
SendUseDoneEvent(WeenieError.TradeItemBeingTraded);
//SendWeenieError(WeenieError.TradeItemBeingTraded);
return;
}
if (item != null)
{
if (item.CurrentLandblock != null && !item.Visibility && item.Guid != LastOpenedContainerId)
{
if (IsBusy)
{
SendUseDoneEvent(WeenieError.YoureTooBusy);
return;
}
CreateMoveToChain(item, (success) => TryUseItem(item, success));
}
else
TryUseItem(item);
}
else
{
log.DebugFormat("{0}.HandleActionUseItem({1:X8}): couldn't find object", Name, itemGuid);
SendUseDoneEvent();
}
}
public DateTime NextUseTime { get; set; }
public float LastUseTime { get; set; }
/// <summary>
/// Attempts to use an item - checks activation requirements
/// </summary>
public void TryUseItem(WorldObject item, bool success = true)
{
//Console.WriteLine($"{Name}.TryUseItem({item.Name}, {success})");
LastUseTime = 0.0f;
if (success)
item.OnActivate(this);
// manually managed
if (LastUseTime == float.MinValue)
return;
var actionChain = new ActionChain();
actionChain.AddDelaySeconds(LastUseTime);
actionChain.AddAction(this, () => SendUseDoneEvent());
actionChain.EnqueueChain();
NextUseTime = DateTime.UtcNow + TimeSpan.FromSeconds(LastUseTime);
}
/// <summary>
/// Sends the GameEventUseDone network message for a player
/// </summary>
/// <param name="errorType">An optional error message</param>
public void SendUseDoneEvent(WeenieError errorType = WeenieError.None)
{
Session.Network.EnqueueSend(new GameEventUseDone(Session, errorType));
}
/// <summary>
/// This method processes the Game Action (F7B1) No Longer Viewing Contents (0x0195)
/// This is raised when we:
/// - have a container open and open up a second container without closing the first container.
/// </summary>
public void HandleActionNoLongerViewingContents(uint objectGuid)
{
var container = CurrentLandblock?.GetObject(objectGuid) as Container;
if (container != null && container.Viewer == Guid.Full)
container.Close(this);
}
public Pet CurrentActivePet { get; set; }
public void ApplyConsumable(MotionCommand useMotion, Action action, float animMod = 1.0f)
{
if (PropertyManager.GetBool("allow_fast_chug").Item && FastTick)
{
ApplyConsumableWithAnimationCallbacks(useMotion, action);
return;
}
IsBusy = true;
var actionChain = new ActionChain();
// if something other that NonCombat.Ready,
// manually send this swap
var prevStance = CurrentMotionState.Stance;
var animTime = 0.0f;
if (prevStance != MotionStance.NonCombat)
animTime = EnqueueMotion_Force(actionChain, MotionStance.NonCombat, MotionCommand.Ready, (MotionCommand)prevStance);
// start the eat/drink motion
var useAnimTime = EnqueueMotion_Force(actionChain, MotionStance.NonCombat, useMotion, null, 1.0f, animMod);
animTime += useAnimTime;
// apply consumable
actionChain.AddAction(this, action);
if (animMod == 1.0f)
{
// return to ready stance
animTime += EnqueueMotion_Force(actionChain, MotionStance.NonCombat, MotionCommand.Ready, useMotion);
}
else
actionChain.AddDelaySeconds(useAnimTime * (1.0f - animMod));
if (prevStance != MotionStance.NonCombat)
animTime += EnqueueMotion_Force(actionChain, prevStance, MotionCommand.Ready, MotionCommand.NonCombat);
actionChain.AddAction(this, () => { IsBusy = false; });
actionChain.EnqueueChain();
LastUseTime = animTime;
}
/// <summary>
/// Fast chugging state variable
/// </summary>
public FoodState FoodState { get; set; }
public void ApplyConsumableWithAnimationCallbacks(MotionCommand useMotion, Action action)
{
IsBusy = true;
var actionChain = new ActionChain();
// if combat mode, temporarily drop to non-combat
var prevStance = CurrentMotionState.Stance;
var animTime = 0.0f;
if (prevStance != MotionStance.NonCombat)
animTime = EnqueueMotion_Force(actionChain, MotionStance.NonCombat, MotionCommand.Ready, (MotionCommand)prevStance);
// start the eat/drink motion
var useAnimTime = EnqueueMotion_Force(actionChain, MotionStance.NonCombat, useMotion);
animTime += useAnimTime;
// the rest is based on animation callback now
FoodState.StartChugging(useMotion, action, useAnimTime, prevStance);
actionChain.EnqueueChain();
// manually managed
LastUseTime = float.MinValue;
}
public void HandleMotionDone_UseConsumable(uint motionID, bool success)
{
//Console.WriteLine($"HandleMotionDone_UseConsumable({(MotionCommand)motionID}, {success})");
if (!FastTick || !FoodState.IsChugging) return;
if (motionID != (uint)FoodState.UseMotion)
return;
// restore state vars
var animTime = 0.0f;
var actionChain = new ActionChain();
var useMotion = FoodState.UseMotion;
var useAnimTime = FoodState.UseAnimTime;
var prevStance = FoodState.PrevStance;
if (motionID != (uint)MotionCommand.Ready)
{
if (FoodState.Callback != null)
{
FoodState.Callback();
FoodState.Callback = null;
}
FoodState.UseMotion = MotionCommand.Ready;
animTime += EnqueueMotion_Force(actionChain, MotionStance.NonCombat, MotionCommand.Ready, useMotion);
}
else
{
FoodState.FinishChugging();
if (prevStance != MotionStance.NonCombat)
animTime += EnqueueMotion_Force(actionChain, prevStance, MotionCommand.Ready, MotionCommand.NonCombat);
actionChain.AddAction(this, () =>
{
SendUseDoneEvent();
IsBusy = false;
});
}
actionChain.EnqueueChain();
}
}
}