using System; using System.Collections.Generic; using ModernUO.CodeGeneratedEvents; using Server.Engines.ConPVP; using Server.Engines.Stealables; using Server.Factions; using Server.Items; using Server.Mobiles; using Server.Spells; using Server.Spells.Fifth; using Server.Spells.Ninjitsu; using Server.Spells.Seventh; using Server.Targeting; namespace Server.SkillHandlers; public static class Stealing { public static bool ClassicMode { get; private set; } public static bool SuspendOnMurder { get; private set; } public static int MaxWeightToSteal { get; private set; } public static bool CanStealContainers { get; private set; } public static void Configure() { ClassicMode = ServerConfiguration.GetSetting("stealing.classicMode", !Core.AOS); SuspendOnMurder = ServerConfiguration.GetSetting("stealing.suspendOnMurder", !Core.AOS); CanStealContainers = ServerConfiguration.GetSetting("stealing.canStealContainers", !Core.AOS); MaxWeightToSteal = ServerConfiguration.GetSetting("stealing.maxWeightToSteal", 10); } public static void Initialize() { SkillInfo.Table[33].Callback = OnUse; } public static bool IsInGuild(Mobile m) => m is PlayerMobile mobile && mobile.NpcGuild == NpcGuild.ThievesGuild; public static bool IsInnocentTo(Mobile from, Mobile to) => Notoriety.Compute(from, to) == Notoriety.Innocent; public static bool IsEmptyHanded(Mobile from) => from.FindItemOnLayer(Layer.OneHanded) == null && from.FindItemOnLayer(Layer.TwoHanded) == null; public static TimeSpan OnUse(Mobile m) { if (!IsEmptyHanded(m)) { m.SendLocalizedMessage(1005584); // Both hands must be free to steal. } else if (m.Region.IsPartOf()) { m.SendMessage("You may not steal in this area."); } else { m.Target = new StealingTarget(m); m.RevealingAction(); m.SendLocalizedMessage(502698); // Which item do you want to steal? } return TimeSpan.FromSeconds(30.0); } private class StealingTarget : Target { private readonly Mobile _thief; public StealingTarget(Mobile thief) : base(1, false, TargetFlags.None) { _thief = thief; AllowNonlocal = true; } protected override void OnTargetCancel(Mobile from, TargetCancelType cancelType) { from.NextSkillTime = Core.TickCount; } private Item TryStealItem(Item toSteal, ref bool caught) { Item stolen = null; var root = toSteal.RootParent; var mobRoot = root as Mobile; var rootIsPlayer = mobRoot?.Player == true; var si = toSteal.Parent == null || !toSteal.Movable ? StealableArtifacts.GetStealableInstance(toSteal) : null; if (!IsEmptyHanded(_thief)) { _thief.SendLocalizedMessage(1005584); // Both hands must be free to steal. } else if (_thief.Region.IsPartOf()) { _thief.SendMessage("You may not steal in this area."); } else if ((_thief as PlayerMobile)?.Young == true && (rootIsPlayer || mobRoot is BaseCreature)) { _thief.SendLocalizedMessage(502700); // You cannot steal from people or monsters right now. Practice on chests and barrels. } else if (rootIsPlayer && !IsInGuild(_thief)) { _thief.SendLocalizedMessage(1005596); // You must be in the thieves guild to steal from other players. } else if (SuspendOnMurder && rootIsPlayer && IsInGuild(_thief) && _thief.Kills > 0) { _thief.SendLocalizedMessage(502706); // You are currently suspended from the thieves guild. } else if ((mobRoot as PlayerMobile)?.Young == true) { _thief.SendLocalizedMessage(502699); // You cannot steal from the Young. } else if ((root as BaseVendor)?.IsInvulnerable == true) { _thief.SendLocalizedMessage(1005598); // You can't steal from shopkeepers. } else if (root is PlayerVendor) { _thief.SendLocalizedMessage(502709); // You can't steal from vendors. } else if (!_thief.CanSee(toSteal)) { _thief.SendLocalizedMessage(500237); // Target can not be seen. } else if (toSteal is Sigil sig) { var pl = PlayerState.Find(_thief); var faction = pl?.Faction; if (!_thief.InRange(sig.GetWorldLocation(), 1)) { _thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it. } else if (root != null) // not on the ground { _thief.SendLocalizedMessage(502710); // You can't steal that! } else if (faction == null) { _thief.SendLocalizedMessage(1005588); // You must join a faction to do that } else if (!_thief.CanBeginAction()) { _thief.SendLocalizedMessage(1010581); // You cannot steal the sigil when you are incognito } else if (DisguisePersistence.IsDisguised(_thief)) { _thief.SendLocalizedMessage(1010583); // You cannot steal the sigil while disguised } else if (!_thief.CanBeginAction()) { _thief.SendLocalizedMessage(1010582); // You cannot steal the sigil while polymorphed } else if (TransformationSpellHelper.UnderTransformation(_thief)) { _thief.SendLocalizedMessage(1061622); // You cannot steal the sigil while in that form. } else if (AnimalForm.UnderTransformation(_thief)) { _thief.SendLocalizedMessage(1063222); // You cannot steal the sigil while mimicking an animal. } else if (pl.IsLeaving) { // You are currently quitting a faction and cannot steal the town sigil _thief.SendLocalizedMessage(1005589); } else if (sig.IsBeingCorrupted && sig.LastMonolith.Faction == faction) { _thief.SendLocalizedMessage(1005590); // You cannot steal your own sigil } else if (sig.IsPurifying) { _thief.SendLocalizedMessage(1005592); // You cannot steal this sigil until it has been purified } else if (!_thief.CheckTargetSkill(SkillName.Stealing, toSteal, 80.0, 80.0)) { _thief.SendLocalizedMessage(1005594); // You do not have enough skill to steal the sigil } else if (Sigil.ExistsOn(_thief)) { // The sigil has gone back to its home location because you already have a sigil. _thief.SendLocalizedMessage(1010258); } else if (_thief.Backpack?.CheckHold(_thief, sig, false, true) != true) { // The sigil has gone home because your backpack is full _thief.SendLocalizedMessage(1010259); } else { if (sig.IsBeingCorrupted) { sig.GraceStart = Core.Now; // begin grace period } _thief.SendLocalizedMessage(1010586); // YOU STOLE THE SIGIL!!! (woah, calm down now) if (sig.LastMonolith?.Sigil != null) { sig.LastMonolith.Sigil = null; } sig.LastStolen = Core.Now; return sig; } } else if (_thief.Backpack?.CheckHold(_thief, toSteal, false, true) != true) { _thief.SendLocalizedMessage(1048147); // Your backpack can't hold anything else. } else if (si == null && (toSteal.Parent == null || !toSteal.Movable) || toSteal.LootType == LootType.Newbied || toSteal.CheckBlessed(mobRoot) || !CanStealContainers && si == null && toSteal is Container) { _thief.SendLocalizedMessage(502710); // You can't steal that! } else if (!_thief.InRange(toSteal.GetWorldLocation(), 1)) { _thief.SendLocalizedMessage(502703); // You must be standing next to an item to steal it. } else if (si != null && _thief.Skills.Stealing.Value < 100.0) { // You're not skilled enough to attempt the theft of this item. _thief.SendLocalizedMessage(1060025, "", 0x66D); } else if (toSteal.Parent is Mobile) { _thief.SendLocalizedMessage(1005585); // You cannot steal items which are equipped. } else if (root == _thief) { _thief.SendLocalizedMessage(502704); // You catch yourself red-handed. } else if (mobRoot?.AccessLevel > AccessLevel.Player) { _thief.SendLocalizedMessage(502710); // You can't steal that! } else if (mobRoot != null && !_thief.CanBeHarmful(mobRoot)) { } else if (root is Corpse) { _thief.SendLocalizedMessage(502710); // You can't steal that! } else { var w = toSteal.Weight + toSteal.TotalWeight; if (w > MaxWeightToSteal) { // This item is too heavy to steal from someone's backpack. _thief.SendLocalizedMessage(502722); } else { if (toSteal.Stackable && toSteal.Amount > 1) { var maxAmount = Math.Clamp( (int)(_thief.Skills.Stealing.Value / 10.0 / toSteal.Weight), 1, toSteal.Amount ); var amount = Utility.RandomMinMax(1, maxAmount); if (amount >= toSteal.Amount) { var pileWeight = (int)Math.Ceiling(toSteal.Weight * toSteal.Amount); pileWeight *= 10; if (_thief.CheckTargetSkill( SkillName.Stealing, toSteal, pileWeight - 22.5, pileWeight + 27.5 )) { stolen = toSteal; } } else { var pileWeight = (int)Math.Ceiling(toSteal.Weight * amount); pileWeight *= 10; if (_thief.CheckTargetSkill( SkillName.Stealing, toSteal, pileWeight - 22.5, pileWeight + 27.5 )) { stolen = Mobile.LiftItemDupe(toSteal, toSteal.Amount - amount) ?? toSteal; } } } else { var iw = (int)Math.Ceiling(w); iw *= 10; if (_thief.CheckTargetSkill(SkillName.Stealing, toSteal, iw - 22.5, iw + 27.5)) { stolen = toSteal; } } if (stolen != null) { _thief.SendLocalizedMessage(502724); // You successfully steal the item. if (si != null) { toSteal.Movable = true; si.Item = null; } } else { _thief.SendLocalizedMessage(502723); // You fail to steal the item. } caught = _thief.Skills.Stealing.Value < Utility.Random(150); } } return stolen; } protected override void OnTarget(Mobile from, object target) { from.RevealingAction(); Item stolen = null; IEntity root = null; var caught = false; if (target is Item item) { root = item.RootParent; stolen = TryStealItem(item, ref caught); } else if (target is Mobile mobile) { var pack = mobile.Backpack; if (pack?.Items.Count > 0) { root = mobile; stolen = TryStealItem(pack.Items.RandomElement(), ref caught); } } else { _thief.SendLocalizedMessage(502710); // You can't steal that! } var mobRoot = root as Mobile; if (stolen != null) { from.AddToBackpack(stolen); if (!(stolen is Container || stolen.Stackable)) { StolenItem.Add(stolen, _thief, mobRoot); } } var corpse = root as Corpse; if (caught) { if (root == null || corpse?.IsCriminalAction(_thief) == true) { _thief.CriminalAction(false); } else if (mobRoot != null) { if (!IsInGuild(mobRoot) && IsInnocentTo(_thief, mobRoot)) { _thief.CriminalAction(false); } var message = $"You notice {_thief.Name} trying to steal from {mobRoot.Name}."; foreach (var ns in _thief.GetClientsInRange(8)) { if (ns.Mobile != _thief) { ns.Mobile.SendMessage(message); } } } } else if (corpse?.IsCriminalAction(_thief) == true) { _thief.CriminalAction(false); } if (mobRoot?.Player == true && _thief is PlayerMobile pm && IsInnocentTo(pm, mobRoot) && !IsInGuild(mobRoot)) { pm.PermaFlags.Add(mobRoot); pm.Delta(MobileDelta.Noto); } const int TargeterCooldown = 30000; // 30s const int SkillCooldown = 10000; // 10s // Calculate how much time has passed since the targeter was opened var ticksSinceTargeter = (int)(Core.TickCount - (from.NextSkillTime - TargeterCooldown)); var remainingCooldown = Math.Max(0, SkillCooldown - ticksSinceTargeter); from.NextSkillTime = Core.TickCount + remainingCooldown; } } } public class StolenItem { public static readonly TimeSpan StealTime = TimeSpan.FromMinutes(2.0); private static readonly Queue _queue = []; public StolenItem(Item stolen, Mobile thief, Mobile victim) { Stolen = stolen; Thief = thief; Victim = victim; Expires = Core.Now + StealTime; } public Item Stolen { get; } public Mobile Thief { get; } public Mobile Victim { get; } public DateTime Expires { get; private set; } public bool IsExpired => Core.Now >= Expires; public static void Add(Item item, Mobile thief, Mobile victim) { Clean(); _queue.Enqueue(new StolenItem(item, thief, victim)); } public static bool IsStolen(Item item) { Mobile victim = null; return IsStolen(item, ref victim); } public static bool IsStolen(Item item, ref Mobile victim) { Clean(); foreach (var si in _queue) { if (si.Stolen == item && !si.IsExpired) { victim = si.Victim; return true; } } return false; } [OnEvent(nameof(PlayerMobile.PlayerDeathEvent))] public static void ReturnOnDeath(Mobile killed) { Clean(); var corpse = killed.Corpse; foreach (var si in _queue) { if (si.Stolen.RootParent != corpse || si.Victim == null || si.IsExpired) { continue; } if (si.Victim.AddToBackpack(si.Stolen)) { si.Victim.SendLocalizedMessage(1010464); // the item that was stolen is returned to you. } else { si.Victim.SendLocalizedMessage(1010463); // the item that was stolen from you falls to the ground. } si.Expires = Core.Now; // such a hack } } public static void Clean() { while (_queue.Count > 0) { var si = _queue.Peek(); if (!si.IsExpired) { break; } _queue.Dequeue(); } } }