#W# Added: More things to the ToAdd Folder.
This commit is contained in:
parent
cdc061dc54
commit
910cdde124
16 changed files with 3389 additions and 0 deletions
309
ToAdd/Traps/CloneTrap.cs
Normal file
309
ToAdd/Traps/CloneTrap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
301
ToAdd/Traps/CurseItemsTrap.cs
Normal file
301
ToAdd/Traps/CurseItemsTrap.cs
Normal 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
167
ToAdd/Traps/LandMobTrap.cs
Normal 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() { }
|
||||
}
|
||||
215
ToAdd/Traps/MagnetItemsTrap.cs
Normal file
215
ToAdd/Traps/MagnetItemsTrap.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
182
ToAdd/Traps/SealedItemsTrap.cs
Normal file
182
ToAdd/Traps/SealedItemsTrap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue