#W# Added: More things to the ToAdd Folder.

This commit is contained in:
WarrentyExpired 2026-09-28 20:41:19 -04:00
parent cdc061dc54
commit 910cdde124
16 changed files with 3389 additions and 0 deletions

309
ToAdd/Traps/CloneTrap.cs Normal file
View file

@ -0,0 +1,309 @@
/**********************************
* Author: Expa *
* Optimized ModernUO .Net 10/C#14 *
**********************************/
using System;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using ModernUO.Serialization;
using Server.Items;
using Server.Mobiles;
namespace Server.Items
{
[SerializationGenerator(0, false)]
public partial class CloneTrap : Item
{
private const int GraphicId = 0xA723;
private const int MoldHue = 2894;
[Constructible]
public CloneTrap() : base(GraphicId)
{
Name = "a Clone trap";
Movable = false;
Visible = false;
Hue = MoldHue;
}
public override bool OnMoveOver(Mobile m)
{
if (m?.CanBeDamaged() != true)
return base.OnMoveOver(m);
m.FixedParticles(0x36BD, 20, 10, 5044, EffectLayer.Head);
m.PlaySound(0x307);
_ = new MirrorClone(m) { Location = m.Location, Map = m.Map };
Delete();
return base.OnMoveOver(m);
}
private void Deserialize(IGenericReader reader, int version) { }
}
[SerializationGenerator(0, false)]
public partial class MirrorClone : BaseCreature
{
private const int CloneHue = 2894;
private const int TimerMin = 120;
private const int TimerMax = 300;
private const double DmgMult = 2.0;
private const double ResMult = 0.5;
[SerializableField(0)]
private Mobile _victim;
private MirrorCloneTimer _timer;
public MirrorClone(Mobile victim) : base(AIType.AI_Melee, FightMode.Closest, 10, 1)
{
_victim = victim;
Hue = CloneHue;
Copy();
(_timer = new MirrorCloneTimer(this)).Start();
}
public override bool AlwaysMurderer => true;
private void Copy()
{
var v = _victim;
if (v is null)
return;
BodyMod = v.Body;
Name = v.Name;
Title = v.Title;
Fame = v.Fame;
Female = v.Female;
NameHue = v.NameHue;
SpeechHue = v.SpeechHue;
Criminal = v.Criminal;
Karma = v.Karma;
Kills = v.Kills;
RawStr = Math.Clamp((int)(v.RawStr * DmgMult), 3, 225);
RawDex = v.RawDex;
RawInt = v.RawInt;
StatCap = Math.Clamp((int)(v.StatCap * DmgMult), 60, 900);
SkillsCap = v.SkillsCap;
Hits = Math.Clamp((int)(v.Hits * DmgMult), 1, 32767);
Mana = v.Mana;
Stam = v.Stam;
ApplyResistanceBalance();
CopySkills();
CopyEquipment();
CopyMount();
}
private void ApplyResistanceBalance()
{
if (_victim is null)
return;
SetResistance(ResistanceType.Physical, Math.Clamp((int)(_victim.PhysicalResistance * ResMult), 0, 100));
SetResistance(ResistanceType.Fire, Math.Clamp((int)(_victim.FireResistance * ResMult), 0, 100));
SetResistance(ResistanceType.Cold, Math.Clamp((int)(_victim.ColdResistance * ResMult), 0, 100));
SetResistance(ResistanceType.Poison, Math.Clamp((int)(_victim.PoisonResistance * ResMult), 0, 100));
SetResistance(ResistanceType.Energy, Math.Clamp((int)(_victim.EnergyResistance * ResMult), 0, 100));
}
private void CopySkills()
{
if (_victim is null)
return;
var skills = _victim.Skills;
var len = skills.Length;
for (int i = 0; i < len; i++)
{
Skills[i].Base = skills[i].Base;
Skills[i].Cap = skills[i].Cap;
}
}
private void CopyEquipment()
{
var items = _victim?.Items;
if (items is null || items.Count == 0)
return;
for (int i = 0, cnt = items.Count; i < cnt; i++)
{
var item = items[i];
if (item?.Parent != _victim || item is IMountItem)
continue;
var copy = ItemFactory.CreateInstance(item);
if (copy is not null)
{
CopyItemProperties(copy, item);
AddItem(copy);
}
}
}
private void CopyMount()
{
var mount = _victim?.FindItemOnLayer(Layer.Mount);
if (mount is not IMountItem)
return;
var copy = ItemFactory.CreateInstance(mount);
if (copy is null)
return;
CopyItemProperties(copy, mount);
AddItem(copy);
if (copy is IMountItem { Mount: BaseMount baseMount })
baseMount.Rider = this;
}
private static void CopyItemProperties(Item dest, Item src)
{
dest.ItemID = src.ItemID;
dest.Amount = src.Amount;
dest.Weight = src.Weight;
dest.Layer = src.Layer;
dest.Hue = src.Hue;
dest.Name = src.Name;
dest.Movable = src.Movable;
dest.LootType = src.LootType;
}
public override void OnDeath(Container c)
{
Cleanup();
base.OnDeath(c);
}
public override void OnDelete()
{
Cleanup();
base.OnDelete();
}
private void Cleanup()
{
_timer?.Stop();
DismountAndClear();
}
private void DismountAndClear()
{
if (Mount is BaseMount mount)
{
mount.Rider = null;
mount.Delete();
}
var items = Items;
for (int i = items.Count - 1; i >= 0; i--)
items[i].Delete();
Corpse?.Delete();
}
private void Deserialize(IGenericReader reader, int version)
{
_victim = reader.ReadEntity<Mobile>();
if (_victim is not null)
Delete();
}
private sealed class MirrorCloneTimer : Timer
{
private WeakReference<MirrorClone> _ref;
public MirrorCloneTimer(MirrorClone clone)
: base(TimeSpan.FromSeconds(Utility.Random(TimerMin, TimerMax)))
{
_ref = new WeakReference<MirrorClone>(clone);
}
protected override void OnTick()
{
if (!_ref.TryGetTarget(out var clone) || clone.Deleted)
{
Stop();
return;
}
clone.Warmode = false;
Effects.SendLocationParticles(
EffectItem.Create(clone.Location, clone.Map, EffectItem.DefaultDuration),
0x3728, 10, 10, 2023
);
clone.Delete();
Stop();
}
}
private static class ItemFactory
{
private static readonly ConcurrentDictionary<Type, Func<Item>> _ctors = new();
public static Item CreateInstance(Item src)
{
if (src is null)
return null;
var type = src.GetType();
var factory = _ctors.GetOrAdd(type, CreateFactory);
if (factory is null)
{
try
{
return new Item(src.ItemID);
}
catch
{
return null;
}
}
try
{
var item = factory();
return item;
}
catch
{
try
{
return new Item(src.ItemID);
}
catch
{
return null;
}
}
}
private static Func<Item> CreateFactory(Type t)
{
try
{
var ctor = t.GetConstructor(Type.EmptyTypes);
if (ctor == null)
return null;
var newExp = Expression.New(ctor);
var conv = Expression.Convert(newExp, typeof(Item));
var lambda = Expression.Lambda<Func<Item>>(conv);
return lambda.Compile();
}
catch
{
return null;
}
}
}
}
}

View file

@ -0,0 +1,301 @@
/**********************************
* Author: Expa *
* Optimized ModernUO .Net 10/C#14 *
**********************************/
using System;
using System.Collections.Generic;
using ModernUO.Serialization;
using Server;
using Server.Items;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Items;
static class CurseConfig
{
public const int ScrollId = 0x1F2E;
public const int ScrollHue = 0x58;
public const int TrapId = 0x1B7B;
public const int Range = 1;
public const int SoundRemove = 0x1F7;
public const int SoundTrap = 0x1E1;
public const int PartRemove = 0x373A;
public const int PartTrap = 0x374B;
public const string TrapName = "Hidden Curse Trap";
public static readonly TimeSpan Cooldown = TimeSpan.FromMinutes(5);
public const bool ShowWhileCooling = true;
public const string Suffix = " [The Cursed!]";
public const string MsgNotInPack = "Scroll must be in backpack.";
public const string MsgPrompt = "Choose cursed item.";
public const string MsgNotCursed = "Not cursed.";
public const string MsgSelect = "Select an item.";
public const string MsgRemovedPack = "Curse removed (backpack).";
public const string MsgRemovedGround = "Curse removed (ground).";
public const string MsgReload = "Trap reloading!";
public const string MsgCursed = "Item cursed and moved.";
}
[SerializationGenerator(0, false)]
public partial class CurseRemovalScroll : Item
{
[Constructible]
public CurseRemovalScroll() : base(CurseConfig.ScrollId) =>
Name = "Curse Removal Scroll";
public CurseRemovalScroll(Serial s) : base(s) { }
public override void OnDoubleClick(Mobile from)
{
if (from?.Backpack is null || !this.IsChildOf(from.Backpack))
{
from?.SendMessage(CurseConfig.MsgNotInPack);
return;
}
from.Target = new T(this);
from.SendMessage(CurseConfig.MsgPrompt);
}
private sealed class T : Target
{
private readonly CurseRemovalScroll _s;
public T(CurseRemovalScroll s) : base(3, false, TargetFlags.None) => _s = s;
protected override void OnTarget(Mobile from, object target)
{
if (_s?.Deleted != false || from?.Deleted != false)
return;
if (target is not Item it)
{
from.SendMessage(CurseConfig.MsgSelect);
return;
}
var isCursed = it.LootType == LootType.Cursed ||
(it.Name?.EndsWith(CurseConfig.Suffix, StringComparison.Ordinal) == true);
if (!isCursed)
{
from.SendMessage(CurseConfig.MsgNotCursed);
return;
}
it.LootType = LootType.Regular;
it.Movable = true;
if (!string.IsNullOrEmpty(it.Name) && it.Name.EndsWith(CurseConfig.Suffix, StringComparison.Ordinal))
{
var baseName = it.Name[..^CurseConfig.Suffix.Length];
it.Name = string.IsNullOrEmpty(baseName) ? null : baseName;
}
from.PlaySound(CurseConfig.SoundRemove);
from.FixedParticles(CurseConfig.PartRemove, 10, 15, 5018, EffectLayer.Waist);
from.SendMessage(it.IsChildOf(from.Backpack) ? CurseConfig.MsgRemovedPack : CurseConfig.MsgRemovedGround);
_s.Delete();
}
}
}
[SerializationGenerator(0, false)]
public partial class CurseTrap : BaseTrap
{
private DateTime _next = DateTime.Now;
[Constructible]
public CurseTrap() : base(CurseConfig.TrapId) => InitTrap();
public CurseTrap(Serial s) : base(s) => InitTrap();
private void InitTrap()
{
Name = CurseConfig.TrapName;
Movable = false;
Visible = false;
}
public override bool PassivelyTriggered => true;
public override TimeSpan PassiveTriggerDelay => TimeSpan.Zero;
public override int PassiveTriggerRange => CurseConfig.Range;
public override TimeSpan ResetDelay => TimeSpan.Zero;
public override void OnTrigger(Mobile from)
{
if (from?.Alive != true || !from.Player)
return;
if (Core.Now < _next)
{
from.SendMessage(CurseConfig.MsgReload);
return;
}
var item = SelectTarget(from);
if (item == null)
return;
var oldName = item.Name;
var oldLoot = item.LootType;
var oldMovable = item.Movable;
try
{
if (item.Parent == from)
from.RemoveItem(item);
item.LootType = LootType.Regular;
item.Movable = false;
if (!(item.Name?.EndsWith(CurseConfig.Suffix) == true))
{
var baseName = oldName ?? item.ItemData.Name ?? item.GetType().Name;
item.Name = baseName + CurseConfig.Suffix;
}
if (!item.IsChildOf(from.Backpack) && !TryPlaceInBackpack(from, item))
item.MoveToWorld(from.Location, from.Map);
from.PlaySound(CurseConfig.SoundTrap);
from.FixedParticles(CurseConfig.PartTrap, 10, 10, 5044, EffectLayer.Head);
from.SendMessage(CurseConfig.MsgCursed);
_next = Core.Now + CurseConfig.Cooldown;
}
catch
{
if (item?.Deleted == false)
{
item.LootType = oldLoot;
item.Movable = oldMovable;
item.Name = oldName;
}
}
}
private static bool TryPlaceInBackpack(Mobile to, Item it)
{
if (to?.Backpack is null || it.IsChildOf(to.Backpack))
return false;
try
{
return to.Backpack.TryDropItem(to, it, false);
}
catch
{
try
{
to.Backpack.DropItem(it);
return true;
}
catch
{
return false;
}
}
}
private static Item SelectTarget(Mobile m)
{
if (m == null)
return null;
double totalWeight = 0.0;
Item firstCandidate = null;
foreach (var itm in m.Items)
{
if (itm?.Deleted != false)
continue;
var inEquip = itm.Parent == m;
var inPack = m.Backpack != null && itm.IsChildOf(m.Backpack);
if (!inEquip && !inPack)
continue;
if (itm.LootType == LootType.Cursed || !itm.Movable)
continue;
if (itm.Layer is Layer.Backpack or Layer.Hair or Layer.FacialHair)
continue;
var weight = CalculateWeight(itm);
if (weight <= 0.0)
continue;
if (firstCandidate == null)
firstCandidate = itm;
totalWeight += weight;
}
if (totalWeight <= 0.0)
return firstCandidate;
var roll = Utility.RandomDouble() * totalWeight;
var accumulated = 0.0;
foreach (var itm in m.Items)
{
if (itm?.Deleted != false)
continue;
var inEquip = itm.Parent == m;
var inPack = m.Backpack != null && itm.IsChildOf(m.Backpack);
if (!inEquip && !inPack)
continue;
if (itm.LootType == LootType.Cursed || !itm.Movable)
continue;
if (itm.Layer is Layer.Backpack or Layer.Hair or Layer.FacialHair)
continue;
var weight = CalculateWeight(itm);
if (weight <= 0.0)
continue;
accumulated += weight;
if (accumulated >= roll)
return itm;
}
return firstCandidate;
}
private static double CalculateWeight(Item it)
{
var baseWeight = it.Amount > 1 ? Math.Min(it.Amount, 100) : 1.0;
return it switch
{
Gold g => baseWeight + Math.Min(g.Amount, 1000) / 10.0,
Bandage => baseWeight + 50,
Arrow => baseWeight + 50,
Bolt => baseWeight + 50,
SpellScroll => baseWeight + 40,
BasePotion => baseWeight + 40,
BaseReagent => baseWeight + 40,
Spellbook => baseWeight + 60,
Runebook => baseWeight + 80,
_ => GetByTypeName(it, baseWeight)
};
}
private static double GetByTypeName(Item it, double baseWeight)
{
var typeName = it.GetType().Name;
if (typeName.Contains("scroll", StringComparison.OrdinalIgnoreCase))
return baseWeight + 20;
if (typeName.Contains("book", StringComparison.OrdinalIgnoreCase) && it is not Spellbook)
return baseWeight + 15;
return baseWeight;
}
}

167
ToAdd/Traps/LandMobTrap.cs Normal file
View file

@ -0,0 +1,167 @@
/**********************************
* Author: Expa *
* Optimized ModernUO .Net 10/C#14 *
**********************************/
using System;
using ModernUO.Serialization;
using Server;
using Server.Mobiles;
namespace Server.Items;
[SerializationGenerator(0, false)]
public partial class LandMobTrap : Item
{
[SerializableField(0)] private int _minSpawn = 3;
[SerializableField(1)] private int _maxSpawn = 6;
[SerializableField(2)] private double _triggerDelaySeconds = 0.5;
[SerializableField(3)] private int _soundID = 0x1C6;
[SerializableField(4)] private int _particleItemId = 0x373A;
[SerializableField(5)] private byte _particleCount = 20;
[SerializableField(6)] private byte _particleSpeed = 10;
[SerializableField(7)] private byte _flags = 1;
private const int MaxAllowedSpawn = 12;
private TimerExecutionToken _spawnToken;
private bool Armed
{
get => (_flags & 1) != 0;
set => _flags = (byte)(value ? (_flags | 1) : (_flags & ~1));
}
private bool BlockMovement
{
get => (_flags & 2) != 0;
set => _flags = (byte)(value ? (_flags | 2) : (_flags & ~2));
}
[Constructible]
public LandMobTrap() : base(0x1B7A)
{
Movable = false;
Hue = 1058;
Visible = false;
Name = "land mob trap";
}
public override bool HandlesOnMovement => true;
public override bool OnMoveOver(Mobile m)
{
var map = Map;
if (Deleted || map == null || map == Map.Internal)
return true;
if (!Armed || !m.Player || m.AccessLevel > AccessLevel.Player)
return true;
foreach (var bc in GetMobilesInRange<BaseCreature>(1))
{
if (bc is Skeleton or SkeletalMage or SkeletalKnight)
return true;
}
Armed = false;
InvalidateProperties();
var min = Math.Max(1, _minSpawn);
var max = Math.Clamp(_maxSpawn, min, MaxAllowedSpawn);
if (_triggerDelaySeconds <= 0.0)
DoSpawn(m, min, max);
else
Timer.StartTimer(TimeSpan.FromSeconds(_triggerDelaySeconds), () => DoSpawn(m, min, max), out _spawnToken);
return !BlockMovement;
}
private void DoSpawn(Mobile trigger, int minSpawn, int maxSpawn)
{
try
{
var map = Map;
if (Deleted || map == null || map == Map.Internal)
return;
var count = Utility.RandomMinMax(minSpawn, maxSpawn);
for (var i = 0; i < count; ++i)
{
var cr = CreateRandomCreature();
if (cr is null)
continue;
try
{
if (TryFindSpawnLocation(map, out var loc))
{
cr.MoveToWorld(loc, map);
cr.Combatant = trigger;
}
else
{
cr.Delete();
}
}
catch
{
cr?.Delete();
}
}
Effects.SendLocationParticles(
EffectItem.Create(Location, Map, EffectItem.DefaultDuration),
_particleItemId, _particleCount, _particleSpeed, 5044
);
Effects.PlaySound(Location, Map, _soundID);
}
finally
{
Delete();
}
}
private static BaseCreature CreateRandomCreature() =>
Utility.Random(5) switch
{
0 or 1 => new Skeleton(),
2 or 3 => new SkeletalMage(),
_ => new SkeletalKnight()
};
private bool TryFindSpawnLocation(Map map, out Point3D loc)
{
loc = Location;
for (var i = 0; i < 10; ++i)
{
var x = X + Utility.Random(3) - 1;
var y = Y + Utility.Random(3) - 1;
var z = map.GetAverageZ(x, y);
if (map.CanSpawnMobile(new Point2D(x, y), z))
{
loc = new Point3D(x, y, z);
return true;
}
if (map.CanFit(x, y, Z, 16, false, false))
{
loc = new Point3D(x, y, Z);
return true;
}
}
return false;
}
public override void OnAfterDelete()
{
_spawnToken.Cancel();
base.OnAfterDelete();
}
[AfterDeserialization]
private void AfterDeserialization() { }
}

View file

@ -0,0 +1,215 @@
/**********************************
* Author: Expa *
* Optimized ModernUO .Net 10/C#14 *
**********************************/
using System;
using System.Collections.Generic;
using ModernUO.Serialization;
using Server;
using Server.Items;
using Server.Mobiles;
namespace Server.Items;
[SerializationGenerator(0, false)]
public partial class MagnetTrap : Item
{
private TimerExecutionToken _timerToken;
private TimerExecutionToken _deleteTimerToken;
private readonly TimeSpan _autoDelete = TimeSpan.FromSeconds(30);
private readonly Dictionary<PlayerMobile, (int Count, DateTime LastSeen)> _dropState = new();
private DateTime _lastCleanup = Core.Now;
private const int MetalDropChancePercent = 60;
private const int MaxDropsPerPlayer = 3;
private const int CleanupIntervalSeconds = 5;
private const int DropStateStaleSeconds = 10;
private static readonly HashSet<string> MetalResourceNames = new(StringComparer.OrdinalIgnoreCase)
{
"Iron", "Steel", "DullCopper", "ShadowIron", "Copper", "Bronze",
"Gold", "Silver", "Agapite", "Verite", "Valorite"
};
[Constructible]
public MagnetTrap() : base(0x1AC5)
{
Movable = false;
Name = "a magnetic trap";
Hue = 1341;
Timer.StartTimer(TimeSpan.Zero, TimeSpan.FromSeconds(1), DoMagneticEffect, out _timerToken);
StartDeleteTimer();
}
private void DoMagneticEffect()
{
if (Deleted)
return;
Effects.SendLocationParticles(
EffectItem.Create(Location, Map, EffectItem.DefaultDuration),
0x376A, 9, 32, 5024, 0, 5024, 0
);
Effects.PlaySound(Location, Map, 0x1F0);
foreach (var m in GetMobilesInRange(1))
{
if (m is PlayerMobile pm)
TryDropRandomItem(pm);
}
var now = Core.Now;
if ((now - _lastCleanup).TotalSeconds >= CleanupIntervalSeconds)
{
_lastCleanup = now;
CleanupDropState(now);
}
}
private void TryDropRandomItem(PlayerMobile player)
{
if (player?.Deleted != false)
return;
var now = Core.Now;
if (!_dropState.TryGetValue(player, out var state))
state = (0, now);
if (state.Count >= MaxDropsPerPlayer)
return;
_dropState[player] = (state.Count, now);
var preferMetal = Utility.Random(100) < MetalDropChancePercent;
var dropped = preferMetal
? TryDropFrom(player.Items, IsMetal, player) || TryDropFrom(player.Backpack?.Items, IsMetal, player)
: TryDropFrom(player.Backpack?.Items, IsMetal, player) || TryDropFrom(player.Items, IsMetal, player);
if (dropped)
_dropState[player] = (state.Count + 1, now);
}
private bool TryDropFrom(IEnumerable<Item> source, Func<Item, bool> predicate, PlayerMobile player)
{
if (source is null)
return false;
Item candidate = null;
var count = 0;
foreach (var item in source)
{
if (item?.Movable != true || !CanDropItem(item) || !predicate(item))
continue;
count++;
if (Utility.Random(count) == 0)
candidate = item;
}
return candidate is not null && DropItemToGround(candidate, player);
}
private static bool CanDropItem(Item item)
{
if (item?.Movable != true)
return false;
if (item.LootType == LootType.Blessed || item.LootType == LootType.Newbied)
return false;
if (item is LockableContainer lockable && lockable.Locked)
return false;
return true;
}
private static bool IsMetal(Item item)
{
if (item is null)
return false;
if (item is BaseWeapon weapon)
return IsMetalResource(weapon.Resource);
if (item is BaseArmor armor)
return IsMetalResource(armor.Resource);
if (item is BaseShield shield)
return IsMetalResource(shield.Resource);
if (item is BaseJewel)
return true;
var typeName = item.GetType().Name;
return typeName.Contains("sword", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("axe", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("mace", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("dagger", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("knife", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("plate", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("chain", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("shield", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("helmet", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("gauntlet", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("ring", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("necklace", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("earrings", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("bracelet", StringComparison.OrdinalIgnoreCase) ||
typeName.Contains("talisman", StringComparison.OrdinalIgnoreCase);
}
private static bool IsMetalResource(CraftResource resource)
{
var resName = CraftResources.GetName(resource);
return !string.IsNullOrEmpty(resName) && MetalResourceNames.Contains(resName);
}
private bool DropItemToGround(Item item, PlayerMobile player)
{
if (item?.Movable != true || player is null || !CanDropItem(item))
return false;
try
{
item.MoveToWorld(player.Location, player.Map);
player.SendLocalizedMessage(502690);
return true;
}
catch
{
return false;
}
}
private void CleanupDropState(DateTime now)
{
var keysToRemove = new List<PlayerMobile>();
foreach (var kv in _dropState)
{
var player = kv.Key;
var lastSeen = kv.Value.LastSeen;
if (player is null || player.Deleted || (now - lastSeen).TotalSeconds > DropStateStaleSeconds)
keysToRemove.Add(player);
}
foreach (var key in keysToRemove)
_dropState.Remove(key);
}
private void StartDeleteTimer()
{
_deleteTimerToken.Cancel();
Timer.StartTimer(_autoDelete, Delete, out _deleteTimerToken);
}
public override void OnDelete()
{
_timerToken.Cancel();
_deleteTimerToken.Cancel();
_dropState.Clear();
base.OnDelete();
}
}

View file

@ -0,0 +1,182 @@
/**********************************
* Author: Expa *
* Optimized ModernUO .Net 10/C#14 *
**********************************/
using System;
using ModernUO.Serialization;
using Server;
using Server.Mobiles;
using Server.Targeting;
namespace Server.Items;
public static class SealedTrapConfig
{
public const int MinItemsToSeal = 1;
public const int MaxItemsToSeal = 5;
public const int TrapItemId = 0x9CDD;
public const int SealedChestItemId = 0x9AA;
public const bool DeleteAfterTrigger = true;
public const string ActivationMessage = "The trap has sealed {0} items in the chest!";
public const string SealedChestMessage = "This chest is magically sealed! Only the Seal Removal Scroll can open it.";
}
[SerializationGenerator(0, false)]
public partial class ItemSealingTrap : Item
{
[Constructible]
public ItemSealingTrap() : base(SealedTrapConfig.TrapItemId)
{
Movable = false;
Visible = false;
Name = "The Sealing trap";
}
public override bool OnMoveOver(Mobile m)
{
if (m is not PlayerMobile player || player.AccessLevel > AccessLevel.Player)
return base.OnMoveOver(m);
var backpack = player.Backpack;
if (backpack?.Items.Count == 0)
return base.OnMoveOver(m);
var itemsToSeal = Math.Min(Utility.RandomMinMax(SealedTrapConfig.MinItemsToSeal, SealedTrapConfig.MaxItemsToSeal), backpack.Items.Count);
var chest = new SealedItemsChest();
for (var i = 0; i < itemsToSeal; i++)
{
var idx = Utility.Random(backpack.Items.Count);
var item = backpack.Items[idx];
backpack.RemoveItem(item);
chest.DropItem(item);
}
chest.MoveToWorld(Location, Map);
player.SendMessage(string.Format(SealedTrapConfig.ActivationMessage, itemsToSeal));
if (SealedTrapConfig.DeleteAfterTrigger)
Delete();
return base.OnMoveOver(m);
}
}
[Furniture]
[SerializationGenerator(0, false)]
public partial class SealedItemsChest : LockableContainer
{
private bool _sealed = true;
[CommandProperty(AccessLevel.GameMaster)]
public bool Sealed
{
get => _sealed;
set
{
_sealed = value;
Locked = value;
InvalidateProperties();
}
}
[Constructible]
public SealedItemsChest() : base(SealedTrapConfig.SealedChestItemId)
{
Locked = true;
Name = "A sealed little chest";
LockLevel = ILockpickable.CannotPick;
RequiredSkill = 0;
MaxLockLevel = 0;
}
public override void OnDoubleClick(Mobile from)
{
if (Sealed)
from?.SendMessage(SealedTrapConfig.SealedChestMessage);
else
base.OnDoubleClick(from);
}
public override void GetProperties(IPropertyList list)
{
base.GetProperties(list);
if (_sealed)
list.Add("<BASEFONT COLOR=#FF0000>Sealed</BASEFONT>");
}
public void Unseal()
{
Sealed = false;
LockLevel = ILockpickable.CannotPick;
Name = "A small chest";
}
public override void Open(Mobile from)
{
if (_sealed)
from?.SendMessage(SealedTrapConfig.SealedChestMessage);
else
base.Open(from);
}
public override bool CheckLocked(Mobile from)
{
if (!Locked)
return false;
if (_sealed)
{
from?.SendMessage(SealedTrapConfig.SealedChestMessage);
return true;
}
return base.CheckLocked(from);
}
}
[SerializationGenerator(0, false)]
public partial class ScrollOfUnsealing : Item
{
[Constructible]
public ScrollOfUnsealing() : base(0x1F4F)
{
Name = "The Seal Removal Scroll";
Weight = 1.0;
}
public override void OnDoubleClick(Mobile from)
{
from?.Target = new UnsealTarget(this);
from?.SendMessage("Select a sealed chest.");
}
private sealed class UnsealTarget : Target
{
private readonly ScrollOfUnsealing _scroll;
public UnsealTarget(ScrollOfUnsealing scroll) : base(3, false, TargetFlags.None) =>
_scroll = scroll;
protected override void OnTarget(Mobile from, object targeted)
{
if (_scroll?.Deleted != false)
return;
switch (targeted)
{
case SealedItemsChest { Sealed: true } chest:
chest.Unseal();
from?.SendMessage("The seal has been lifted!");
_scroll.Delete();
break;
case SealedItemsChest:
from?.SendMessage("The chest has already been opened!");
break;
default:
from?.SendMessage("It's not a sealed chest!");
break;
}
}
}
}