using System;
using ACE.Entity;
using ACE.Entity.Enum;
using ACE.Entity.Enum.Properties;
using ACE.Server.Entity;
using ACE.Server.Managers;
using ACE.Server.Network.GameEvent.Events;
using ACE.Server.Network.GameMessages.Messages;
namespace ACE.Server.WorldObjects
{
partial class WorldObject
{
public double? UseTimestamp
{
get => GetProperty(PropertyFloat.UseTimestamp);
set { if (!value.HasValue) RemoveProperty(PropertyFloat.UseTimestamp); else SetProperty(PropertyFloat.UseTimestamp, value.Value); }
}
protected double? ResetTimestamp
{
get => GetProperty(PropertyFloat.ResetTimestamp);
set { if (!value.HasValue) RemoveProperty(PropertyFloat.ResetTimestamp); else SetProperty(PropertyFloat.ResetTimestamp, value.Value); }
}
protected double? ResetInterval
{
get => GetProperty(PropertyFloat.ResetInterval);
set { if (!value.HasValue) RemoveProperty(PropertyFloat.ResetInterval); else SetProperty(PropertyFloat.ResetInterval, value.Value); }
}
protected bool DefaultLocked
{
get => GetProperty(PropertyBool.DefaultLocked) ?? false;
set { if (!value) RemoveProperty(PropertyBool.DefaultLocked); else SetProperty(PropertyBool.DefaultLocked, value); }
}
protected bool DefaultOpen
{
get => GetProperty(PropertyBool.DefaultOpen) ?? false;
set { if (!value) RemoveProperty(PropertyBool.DefaultOpen); else SetProperty(PropertyBool.DefaultOpen, value); }
}
///
/// Used to determine how close you need to be to use an item.
///
public bool IsWithinUseRadiusOf(WorldObject wo, float? useRadius = null)
{
if (useRadius == null)
useRadius = wo.UseRadius ?? 0.6f;
var cylDist = GetCylinderDistance(wo);
return cylDist <= useRadius;
}
public float GetCylinderDistance(WorldObject wo)
{
return (float)Physics.Common.Position.CylinderDistance(PhysicsObj.GetRadius(), PhysicsObj.GetHeight(), PhysicsObj.Position,
wo.PhysicsObj.GetRadius(), wo.PhysicsObj.GetHeight(), wo.PhysicsObj.Position);
}
///
/// Handles the 'GameAction 0x35 - UseWithTarget' network message
/// on a per-object type basis.
public virtual void HandleActionUseOnTarget(Player player, WorldObject target)
{
RecipeManager.UseObjectOnTarget(player, this, target);
}
public virtual void OnActivate(WorldObject activator)
{
//Console.WriteLine($"{Name}.OnActivate({activator.Name})");
// when players double click an object,
// and the packet comes in as GameAction 0x36 - UseItem
// from the game perspective, technically this starts as an 'Activate',
// which can have a list of possible ActivationResponses -
// Use (by far the most common), Animate, Talk, Emote, CastSpell, Generate
// PropertyInt.Active indicates if this object can be activated, default is true
if (!Active) return;
// verify use requirements
var result = CheckUseRequirements(activator);
var player = activator as Player;
if (!result.Success)
{
if (result.Message != null && player != null)
player.Session.Network.EnqueueSend(result.Message);
return;
}
if (player != null)
player.EnchantmentManager.StartCooldown(this);
// perform motion animation - rarely used (only 4 instances in PY16 db)
if (ActivationResponse.HasFlag(ActivationResponse.Animate))
OnAnimate(activator);
// perform activation emote
if (ActivationResponse.HasFlag(ActivationResponse.Emote))
OnEmote(activator);
// cast a spell on the player (spell traps)
if (ActivationResponse.HasFlag(ActivationResponse.CastSpell))
OnCastSpell(activator);
// call to generator to spawn new object
if (ActivationResponse.HasFlag(ActivationResponse.Generate))
OnGenerate(activator);
// default use action
if (ActivationResponse.HasFlag(ActivationResponse.Use))
{
if (activator is Creature creature)
{
//target.EmoteManager.OnActivation(creature); // found a few things with Activation on them but not ActivationResponse.Emote...
EmoteManager.OnUse(creature);
}
ActOnUse(activator);
}
// send chat text - rarely used (only 8 instances in PY16 db)
if (ActivationResponse.HasFlag(ActivationResponse.Talk))
OnTalk(activator);
if (!(this is Creature) && ActivationTarget > 0)
{
var activationTarget = CurrentLandblock?.GetObject(new ObjectGuid(ActivationTarget));
if (activationTarget != null)
activationTarget.OnActivate(activator);
else
{
log.Warn($"{Name}.OnActivate({activator.Name}): couldn't find activation target {ActivationTarget:X8}");
}
}
}
public virtual void ActOnUse(WorldObject activator)
{
// empty base - individual WorldObject types should override
var msg = $"{Name}.ActOnUse({activator.Name}) - undefined for wcid {WeenieClassId} type {WeenieType}";
log.Error(msg);
if (activator is Player _player)
_player.Session.Network.EnqueueSend(new GameMessageSystemChat(msg, ChatMessageType.Broadcast));
}
public virtual void OnAnimate(WorldObject activator)
{
var motion = new Motion(this, ActivationAnimation);
EnqueueBroadcastMotion(motion);
}
public virtual void OnTalk(WorldObject activator)
{
// todo: verify the format of this message
if (activator is Player player)
player.Session.Network.EnqueueSend(new GameMessageSystemChat(ActivationTalk, ChatMessageType.Broadcast));
}
public virtual void OnEmote(WorldObject activator)
{
if (activator is Creature creature)
EmoteManager.OnActivation(creature);
}
public virtual void OnCastSpell(WorldObject activator)
{
if (SpellDID != null)
{
var spell = new Spell(SpellDID.Value);
TryCastSpell(spell, activator, this);
}
}
public virtual void OnGenerate(WorldObject activator)
{
if (IsGenerator)
Generator_Generate();
}
///
/// Verifies the use requirements for activating an item
///
public virtual ActivationResult CheckUseRequirements(WorldObject activator)
{
//Console.WriteLine($"{Name}.CheckUseRequirements({activator.Name})");
if (activator == null)
{
log.Error($"0x{Guid}:{Name}.CheckUseRequirements() (wcid: {WeenieClassId}): activator is null");
return new ActivationResult(false);
}
if (!(activator is Player player))
return new ActivationResult(true);
// verify arcane lore requirement
if (ItemDifficulty != null)
{
var arcaneLore = player.GetCreatureSkill(Skill.ArcaneLore);
if (arcaneLore.Current < ItemDifficulty.Value)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_IsTooLowToUseItemMagic, arcaneLore.Skill.ToSentence()));
}
// verify skill - does this have to be trained, or only in conjunction with UseRequiresSkillLevel?
// only seems to be used for summoning so far...
if (ItemSkillLimit != null && ItemSkillLevelLimit != null)
{
var skill = activator.ConvertToMoASkill((Skill)ItemSkillLimit.Value);
var playerSkill = player.GetCreatureSkill(skill);
//if (playerSkill.AdvancementClass < SkillAdvancementClass.Trained)
//{
// //return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_SkillMustBeTrained, playerSkill.Skill.ToSentence()));
// player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, $"You must have {playerSkill.Skill.ToSentence()} trained to use that item's magic"));
// return new ActivationResult(false);
//}
// verify skill level
if (playerSkill.Current < ItemSkillLevelLimit.Value)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_IsTooLowToUseItemMagic, playerSkill.Skill.ToSentence()));
}
if (UseRequiresSkill != null)
{
var skill = activator.ConvertToMoASkill((Skill)UseRequiresSkill.Value);
var playerSkill = player.GetCreatureSkill(skill);
if (playerSkill.AdvancementClass < SkillAdvancementClass.Trained)
{
//return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_SkillMustBeTrained, playerSkill.Skill.ToSentence()));
player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, $"You must have {playerSkill.Skill.ToSentence()} trained to use that item's magic"));
return new ActivationResult(false);
}
// verify skill level
if (UseRequiresSkillLevel != null)
{
if (playerSkill.Current < UseRequiresSkillLevel.Value)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_IsTooLowToUseItemMagic, playerSkill.Skill.ToSentence()));
}
}
// verify skill specialized
// is this always in conjunction with UseRequiresSkill?
// again, only seems to be for summoning so far...
if (UseRequiresSkillSpec != null)
{
var skill = activator.ConvertToMoASkill((Skill)UseRequiresSkillSpec.Value);
var playerSkill = player.GetCreatureSkill(skill);
if (playerSkill.AdvancementClass < SkillAdvancementClass.Specialized)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.YouMustSpecialize_ToUseItemMagic, playerSkill.Skill.ToSentence()));
// verify skill level
if (UseRequiresSkillLevel != null)
{
if (playerSkill.Current < UseRequiresSkillLevel.Value)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_IsTooLowToUseItemMagic, playerSkill.Skill.ToSentence()));
}
}
// verify skill specialized
// only found on a few items, doesn't show up on ID panel so is effectively a hidden requirement unless noted in ShortDesc/LongDesc string text
if (ItemSpecializedOnly != null)
{
var skill = activator.ConvertToMoASkill((Skill)ItemSpecializedOnly.Value);
var playerSkill = player.GetCreatureSkill(skill);
if (playerSkill.AdvancementClass < SkillAdvancementClass.Specialized)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.YouMustSpecialize_ToUseItemMagic, playerSkill.Skill.ToSentence()));
// verify skill level (if this was included)
if (ItemSkillLevelLimit != null)
{
if (playerSkill.Current < ItemSkillLevelLimit.Value)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_IsTooLowToUseItemMagic, playerSkill.Skill.ToSentence()));
}
}
// verify player level
if (UseRequiresLevel != null)
{
var playerLevel = player.Level ?? 1;
if (playerLevel < UseRequiresLevel.Value)
//return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.YouMustBe_ToUseItemMagic, $"level {UseRequiresLevel.Value}")); // not retail
return new ActivationResult(new GameEventCommunicationTransientString(player.Session, "You are not high enough level to use that!"));
}
// verify attribute / vital limits
if (ItemAttributeLimit != null)
{
var playerAttr = player.Attributes[ItemAttributeLimit.Value];
if (playerAttr.Current < ItemAttributeLevelLimit)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_IsTooLowToUseItemMagic, playerAttr.Attribute.ToString()));
}
if (ItemAttribute2ndLimit != null)
{
var playerVital = player.Vitals[ItemAttribute2ndLimit.Value];
if (playerVital.MaxValue < ItemAttribute2ndLevelLimit)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.Your_IsTooLowToUseItemMagic, playerVital.Vital.ToSentence()));
}
// verify heritage group
if (HeritageGroup != HeritageGroup.Invalid)
{
if (this is not Creature) // Creatures are not restricted to their own hertigage group
{
var playerHeritageGroup = player.HeritageGroup;
if (playerHeritageGroup != HeritageGroup)
return new ActivationResult(new GameEventWeenieErrorWithString(player.Session, WeenieErrorWithString.YouMustBe_ToUseItemMagic, HeritageGroup.ToSentence()));
}
}
// Check for a cooldown
if (!player.EnchantmentManager.CheckCooldown(CooldownId))
{
// TODO: werror/string not found, find exact message
/*var cooldown = player.GetCooldown(this);
var timer = cooldown.GetFriendlyString();
player.Session.Network.EnqueueSend(new GameMessageSystemChat($"{Name} can be activated again in {timer}", ChatMessageType.Broadcast));*/
player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, "You have used this item too recently"));
return new ActivationResult(false);
}
if (player.IsOlthoiPlayer)
{
//player.Session.Network.EnqueueSend(new GameEventCommunicationTransientString(player.Session, "Olthoi can't interact with that!"));
//player.SendWeenieError(WeenieError.OlthoiCannotInteractWithThat);
//return new ActivationResult(false);
if (this is Creature)
{
if (CreatureType == ACE.Entity.Enum.CreatureType.Olthoi)
return new ActivationResult(true);
else
{
if (this is Vendor)
player.SendWeenieError(WeenieError.OlthoiVendorLooksInHorror);
else if (NpcLooksLikeObject ?? false)
player.SendWeenieError(WeenieError.OlthoiCannotInteractWithThat);
else
player.SendWeenieErrorWithString(WeenieErrorWithString._CowersFromYou, Name);
return new ActivationResult(false);
}
}
else if (this is Lifestone)
{
player.SendWeenieError(WeenieError.OlthoiCannotUseLifestones);
return new ActivationResult(false);
}
else if (this is Container && !(this is Corpse))
{
player.SendWeenieError(WeenieError.OlthoiCannotInteractWithThat);
return new ActivationResult(false);
}
else if (this is AttributeTransferDevice || this is AugmentationDevice || this is Bindstone || this is Book
|| this is Game || this is Gem || this is GenericObject || this is Key || this is SkillAlterationDevice)
{
player.SendWeenieError(WeenieError.OlthoiCannotInteractWithThat);
return new ActivationResult(false);
}
}
return new ActivationResult(true);
}
}
}