per line would add paragraph spacing from the renderer.)
- var skillHtml = new StringBuilder(2048);
-
- for (int i = 0; i < visibleSkills.Count; i++)
- {
- Skill sk = visibleSkills[i];
-
- if (i > 0)
- {
- skillHtml.Append("
");
- }
-
- skillHtml.Append($"{sk.Name} {sk.Value:F1} / {sk.Cap:F1}");
- }
-
- // scrollbar is named arg #11 in current ModernUO: AddHtml(x,y,w,h,text,color,size,fontStyle,align,background,scrollbar)
- // Height 225 ends the area at Y=820 so the scrollbar's down-arrow clears the window frame.
- builder.AddHtml(
- RightCol, colY + 375, 280, 225,
- $"
{skillHtml}", scrollbar: true
- );
- }
-
- public override void OnResponse(NetState sender, in RelayInfo info)
- {
- if (info.ButtonID == 1) // Refresh
- {
- DisplayTo(from);
- }
- }
-
- // Renders any batch of StatRows — attributes, resistances and ratings all share this loop.
- private static void AddRows(ref DynamicGumpBuilder builder, ReadOnlySpan
rows)
- {
- for (int i = 0; i < rows.Length; i++)
- {
- var r = rows[i];
- builder.AddLabel(r.X, r.Y, C_WHITE, r.Name);
- builder.AddLabel(r.X + r.ValueOffset, r.Y, r.ValueHue, r.Value);
- }
- }
-
- private static string ColorAndCenter(string color, string text) =>
- $"{text}";
-
- private static void DrawDivider(ref DynamicGumpBuilder builder, int x, int y, int width) =>
- builder.AddImageTiled(x, y, width, 2, 9264);
-}
diff --git a/ToAdd/PET TRAINING.zip b/ToAdd/PET TRAINING.zip
deleted file mode 100644
index 68a83950e..000000000
Binary files a/ToAdd/PET TRAINING.zip and /dev/null differ
diff --git a/ToAdd/PirateSystem.zip b/ToAdd/PirateSystem.zip
deleted file mode 100644
index 95c3fd368..000000000
Binary files a/ToAdd/PirateSystem.zip and /dev/null differ
diff --git a/ToAdd/SecretDoorLink.cs b/ToAdd/SecretDoorLink.cs
deleted file mode 100644
index 625d82a20..000000000
--- a/ToAdd/SecretDoorLink.cs
+++ /dev/null
@@ -1,239 +0,0 @@
-using System;
-using ModernUO.Serialization;
-using Server;
-using Server.Commands;
-using Server.Items;
-using Server.Targeting;
-
-namespace Server.Items
-{
- [SerializationGenerator(0, false)]
- public partial class SecretDoorLink : Item
- {
- [SerializableField(0)]
- private Serial _triggerSerial;
-
- [SerializableField(1)]
- private Serial _secretSerial;
-
- [SerializableField(2)]
- private Point3D _triggerSpot;
-
- [SerializableField(3)]
- private Map _triggerMap;
-
- [SerializableField(4)]
- private Point3D _secretHomeLoc;
-
- [SerializableField(5)]
- private Map _secretHomeMap;
-
- [SerializableField(6)]
- private bool _secretIsHidden;
-
- private Timer _timer;
-
- [Constructible]
- public SecretDoorLink() : base(0x1F14)
- {
- Name = "Secret Door Link (controller)";
- Movable = true;
- StartTimer();
- }
-
- // ✅ This is the key fix for "doesn't work after restart"
- [AfterDeserialization(false)]
- private void AfterDeserialization()
- {
- StartTimer();
- Timer.DelayCall(TimeSpan.Zero, Tick);
- }
-
- public override void OnAfterDelete()
- {
- base.OnAfterDelete();
- _timer?.Stop();
- _timer = null;
- }
-
- private void StartTimer()
- {
- _timer?.Stop();
- _timer = Timer.DelayCall(TimeSpan.FromMilliseconds(400), TimeSpan.FromMilliseconds(400), Tick);
- }
-
- private Item TriggerItem => _triggerSerial.IsValid ? World.FindItem(_triggerSerial) : null;
- private Item SecretItem => _secretSerial.IsValid ? World.FindItem(_secretSerial) : null;
-
- private void Tick()
- {
- if (!_triggerSerial.IsValid || !_secretSerial.IsValid)
- return;
-
- var trigger = TriggerItem;
- var secret = SecretItem;
-
- if (trigger == null || trigger.Deleted || secret == null || secret.Deleted)
- return;
-
- if (_triggerMap == null || _secretHomeMap == null)
- return;
-
- bool onSpot =
- trigger.Map == _triggerMap &&
- trigger.Location == _triggerSpot;
-
- // REVERSED behavior:
- // Trigger ON spot => secret disappears
- // Trigger OFF spot => secret appears
- if (onSpot)
- HideSecret(secret);
- else
- ShowSecret(secret);
- }
-
- private void ShowSecret(Item secret)
- {
- if (!_secretIsHidden)
- return;
-
- secret.MoveToWorld(_secretHomeLoc, _secretHomeMap);
- secret.Visible = true;
-
- _secretIsHidden = false;
- }
-
- private void HideSecret(Item secret)
- {
- if (_secretIsHidden)
- return;
-
- secret.Visible = false;
- secret.MoveToWorld(Point3D.Zero, Map.Internal);
-
- _secretIsHidden = true;
- }
-
- // ---------- GM Commands ----------
- public static void Initialize()
- {
- CommandSystem.Register("SecretDoorLink", AccessLevel.GameMaster, SecretDoorLinkCommand);
- CommandSystem.Register("SecretDoorStatus", AccessLevel.GameMaster, SecretDoorStatusCommand);
- }
-
- [Usage("SecretDoorLink")]
- [Description("Links a trigger item to a secret item that disappears when the trigger is placed on a chosen tile, and reappears when moved off.")]
- private static void SecretDoorLinkCommand(CommandEventArgs e)
- {
- e.Mobile.SendMessage("Target the TRIGGER item (the one you will move).");
- e.Mobile.Target = new PickTriggerTarget();
- }
-
- [Usage("SecretDoorStatus")]
- [Description("Target a SecretDoorLink controller to print its configuration.")]
- private static void SecretDoorStatusCommand(CommandEventArgs e)
- {
- e.Mobile.SendMessage("Target the SecretDoorLink controller.");
- e.Mobile.Target = new StatusTarget();
- }
-
- private class PickTriggerTarget : Target
- {
- public PickTriggerTarget() : base(12, false, TargetFlags.None) { }
-
- protected override void OnTarget(Mobile from, object targeted)
- {
- if (targeted is not Item trigger || trigger.Deleted)
- {
- from.SendMessage("That is not a valid item.");
- return;
- }
-
- from.SendMessage("Now target the SECRET item (the door/item that will appear/disappear).");
- from.Target = new PickSecretTarget(trigger);
- }
- }
-
- private class PickSecretTarget : Target
- {
- private readonly Item _trigger;
-
- public PickSecretTarget(Item trigger) : base(12, false, TargetFlags.None)
- {
- _trigger = trigger;
- }
-
- protected override void OnTarget(Mobile from, object targeted)
- {
- if (targeted is not Item secret || secret.Deleted)
- {
- from.SendMessage("That is not a valid item.");
- return;
- }
-
- from.SendMessage("Now target the LOCATION (ground tile) where the trigger must be placed.");
- from.Target = new PickLocationTarget(_trigger, secret);
- }
- }
-
- private class PickLocationTarget : Target
- {
- private readonly Item _trigger;
- private readonly Item _secret;
-
- public PickLocationTarget(Item trigger, Item secret) : base(18, true, TargetFlags.None)
- {
- _trigger = trigger;
- _secret = secret;
- }
-
- protected override void OnTarget(Mobile from, object targeted)
- {
- if (targeted is not IPoint3D p)
- {
- from.SendMessage("Invalid location.");
- return;
- }
-
- var map = from.Map;
-
- var controller = new SecretDoorLink
- {
- _triggerSerial = _trigger.Serial,
- _secretSerial = _secret.Serial,
- _triggerSpot = new Point3D(p),
- _triggerMap = map,
- _secretHomeLoc = _secret.Location,
- _secretHomeMap = _secret.Map,
- _secretIsHidden = false
- };
-
- controller.MoveToWorld(from.Location, from.Map);
-
- // Force initial evaluation
- controller.Tick();
-
- from.SendMessage("Linked! Trigger ON spot => secret disappears. Trigger OFF spot => secret appears.");
- }
- }
-
- private class StatusTarget : Target
- {
- public StatusTarget() : base(12, false, TargetFlags.None) { }
-
- protected override void OnTarget(Mobile from, object targeted)
- {
- if (targeted is not SecretDoorLink link || link.Deleted)
- {
- from.SendMessage("That is not a SecretDoorLink controller.");
- return;
- }
-
- from.SendMessage($"Trigger: {link._triggerSerial} Secret: {link._secretSerial}");
- from.SendMessage($"Spot: {link._triggerSpot} on {link._triggerMap}");
- from.SendMessage($"Secret home: {link._secretHomeLoc} on {link._secretHomeMap}");
- from.SendMessage($"Hidden now: {link._secretIsHidden}");
- }
- }
- }
-}
diff --git a/ToAdd/SquireSystem.zip b/ToAdd/SquireSystem.zip
deleted file mode 100644
index b594e1ec2..000000000
Binary files a/ToAdd/SquireSystem.zip and /dev/null differ
diff --git a/ToAdd/Traps/CloneTrap.cs b/ToAdd/Traps/CloneTrap.cs
deleted file mode 100644
index 551250e3d..000000000
--- a/ToAdd/Traps/CloneTrap.cs
+++ /dev/null
@@ -1,309 +0,0 @@
-/**********************************
-* 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();
- if (_victim is not null)
- Delete();
- }
-
- private sealed class MirrorCloneTimer : Timer
- {
- private WeakReference _ref;
-
- public MirrorCloneTimer(MirrorClone clone)
- : base(TimeSpan.FromSeconds(Utility.Random(TimerMin, TimerMax)))
- {
- _ref = new WeakReference(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> _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- 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>(conv);
- return lambda.Compile();
- }
- catch
- {
- return null;
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ToAdd/Traps/CurseItemsTrap.cs b/ToAdd/Traps/CurseItemsTrap.cs
deleted file mode 100644
index a2b26ff4e..000000000
--- a/ToAdd/Traps/CurseItemsTrap.cs
+++ /dev/null
@@ -1,301 +0,0 @@
-/**********************************
-* 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;
- }
-}
\ No newline at end of file
diff --git a/ToAdd/Traps/LandMobTrap.cs b/ToAdd/Traps/LandMobTrap.cs
deleted file mode 100644
index dc59f99d2..000000000
--- a/ToAdd/Traps/LandMobTrap.cs
+++ /dev/null
@@ -1,167 +0,0 @@
-/**********************************
-* 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(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() { }
-}
\ No newline at end of file
diff --git a/ToAdd/Traps/MagnetItemsTrap.cs b/ToAdd/Traps/MagnetItemsTrap.cs
deleted file mode 100644
index 3d90ec3cf..000000000
--- a/ToAdd/Traps/MagnetItemsTrap.cs
+++ /dev/null
@@ -1,215 +0,0 @@
-/**********************************
-* 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 _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 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
- source, Func
- 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();
-
- 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();
- }
-}
\ No newline at end of file
diff --git a/ToAdd/Traps/SealedItemsTrap.cs b/ToAdd/Traps/SealedItemsTrap.cs
deleted file mode 100644
index 166dd9af0..000000000
--- a/ToAdd/Traps/SealedItemsTrap.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-/**********************************
-* 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("Sealed");
- }
-
- 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;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/ToAdd/WisdomWisp.zip b/ToAdd/WisdomWisp.zip
deleted file mode 100644
index 035d3db24..000000000
Binary files a/ToAdd/WisdomWisp.zip and /dev/null differ
diff --git a/dev-docs/claude-skills/migrate-from-runuo/migrate-items-mobiles.md b/dev-docs/claude-skills/migrate-from-runuo/migrate-items-mobiles.md
index bd994b983..7310c104e 100644
--- a/dev-docs/claude-skills/migrate-from-runuo/migrate-items-mobiles.md
+++ b/dev-docs/claude-skills/migrate-from-runuo/migrate-items-mobiles.md
@@ -31,9 +31,7 @@ description: >
- Expression-bodied overrides: `public override int Meat { get { return 1; } }` -> `public override int Meat => 1;`
- AI movement calls lose the `run` flag: `MoveTo(m, true, range)` -> `MoveTo(m, range)` (also `WalkMobileRange`, `ApproachTarget`, `MoveToPoint`, `PathFollower.Follow`); the Running bit is derived from step pace -> `dev-docs/runuo-migration-docs/09-items-mobiles-creatures.md` § AI Movement
- `AcquireOnApproach` (bool) -> `AcquireOnApproachDelay` (TimeSpan; `Zero` = old instant behavior) -> same doc § Target Acquisition
-- `BaseAI.OnAggressiveAction(aggressor)` is `bool`, not `void`: the AI owns the retaliation policy (`BaseCreature` no longer sets `Combatant` after it); an early `return;` that refused a fight -> `return false;`, otherwise `return true;`; swap-only overrides call `PreferCloserAggressor(aggressor)` instead of `base` -> same doc § Retaliation
- `DamageEntries` is an inline `ref readonly ValueLinkList`, not a `List`: indexer/`Add`/`Remove`/`Clear` -> `foreach` / `.ByDescending()` (needs `using Server.Collections;`) and `ClearDamageEntries()`; `GetLootingRights` takes it by `in` -> same doc § Damage Entries
-- `ControlMaster`/`SummonMaster` are views of one `Master`, gated by `Controlled`/`Summoned`; assignments still compile (they set `Master`). `ControlMaster ?? SummonMaster` and controlled-or-summoned pairs -> `GetMaster()`; a creature that sets `SummonMaster` without `Summoned` (RunUO's `BaseEnraged`) must read `Master`, because `SummonMaster` now reads null -> same doc § Masters
## Anti-Patterns
- Using `_field--` instead of `Property--` (bypasses MarkDirty tracking)
diff --git a/dev-docs/claude-skills/modernuo-content-patterns.md b/dev-docs/claude-skills/modernuo-content-patterns.md
index ae83888af..55aa32605 100644
--- a/dev-docs/claude-skills/modernuo-content-patterns.md
+++ b/dev-docs/claude-skills/modernuo-content-patterns.md
@@ -40,12 +40,6 @@ description: >
trigger cooldown is the rate limit, `ChanceToTrigger` is per-sample jitter, and a
zero-cooldown `Think`/`CombatAction` ability triggers every sampled think -- see
`dev-docs/content-patterns.md` § OnThink: the excess-call contract
-9. **One master, read through views** -- `ControlMaster` (owner, only while `Controlled`),
- `SummonMaster` (summoner, only while `Summoned`), `GetMaster()` (either: who answers for the
- creature), `Master` (the stored reference, flags ignored; both setters assign it). Tame with
- `SetControlMaster`, summon with `BaseCreature.Summon`. Don't repeat the view's flag:
- `Controlled && ControlMaster == x` -> `ControlMaster == x`, but `!=` does not reduce -- see
- `dev-docs/content-patterns.md` § Masters
## New Item Template
diff --git a/dev-docs/content-patterns.md b/dev-docs/content-patterns.md
index b138ce9f0..85ce6075c 100644
--- a/dev-docs/content-patterns.md
+++ b/dev-docs/content-patterns.md
@@ -231,9 +231,6 @@ class — `OnThink` is for content extras (see the excess-call contract below).
creature is dispatched through `Obey()`, an uncontrolled one through `Think()`; an AI that owns
both routes them into one decision (`FamiliarAI.Act`). Overriding `IssueOrder` to return a
fixed order is how "command immunity" is expressed without bypassing the order machinery.
-`OnAggressiveAction(aggressor) -> bool` is the retaliation policy: `BaseCreature` sets no
-combatant of its own and runs `StopFlee`/`ForceReacquire` only on `true`, so an AI that
-returns `false` has refused the fight outright.
### Fight Modes
| FightMode | Behavior |
@@ -267,37 +264,6 @@ public override int TreasureMapLevel => 3; // Drops treasure map
public override double WeaponAbilityChance => 0.4; // Weapon ability chance
```
-### Masters: owner, summoner, responsible party
-
-A creature stores one master. Read it through the view that matches the question:
-
-| Read | Returns | Use it for |
-|---|---|---|
-| `ControlMaster` | the master while `Controlled`, else null | the owner: commands, bonding, friends, stabling |
-| `SummonMaster` | the master while `Summoned`, else null | the summoner: dispel, summon acquire rules |
-| `GetMaster()` | `ControlMaster ?? SummonMaster` | whoever answers for the creature: notoriety, kill credit, guild and party checks |
-| `Master` | the stored reference, flags ignored | a creature's own AI following or defending a master it has no flag for |
-
-A master with neither flag is legal. A meer mage's enraged creatures carry their meer in `Master`
-without being `Controlled` or `Summoned`, so `GetMaster()` is null and the meer never answers for them.
-
-To set a master, tame through `SetControlMaster(m)` (it raises `Controlled`, resets the order and
-checks follower slots) and summon through `BaseCreature.Summon(...)`. Assign `Master` directly only
-for a master with no flag. The `ControlMaster` and `SummonMaster` setters are aliases for `Master`:
-they don't set `Controlled` or `Summoned`, so the value reads back as null until the flag is set.
-Every assignment moves the creature's follower slots from the old master to the new one.
-
-The view already checks its flag, so don't repeat it:
-
-```csharp
-// Redundant: ControlMaster is null unless Controlled
-if (Controlled && ControlMaster == from) { ... }
-if (ControlMaster == from) { ... }
-
-// Not equivalent: without the flag this is true for every wild creature
-if (Controlled && ControlMaster != from) { ... }
-```
-
### Creature Speeds (think vs move clocks)
All "speed" values are **delays in seconds** (smaller = faster). A creature runs two clocks:
diff --git a/dev-docs/runuo-migration-docs/09-items-mobiles-creatures.md b/dev-docs/runuo-migration-docs/09-items-mobiles-creatures.md
index 7bde0a898..def2ecf48 100644
--- a/dev-docs/runuo-migration-docs/09-items-mobiles-creatures.md
+++ b/dev-docs/runuo-migration-docs/09-items-mobiles-creatures.md
@@ -497,83 +497,6 @@ An isolated step (after the creature stood for at least a walk interval) goes ou
walk regardless of pace — only a continuing cadence, or a pace faster than the run
interpolation, flags run.
-## Retaliation: `OnAggressiveAction` Returns `bool`
-
-RunUO's `BaseAI.OnAggressiveAction` was a `void` hook that only swapped to a closer
-aggressor; `BaseCreature.AggressiveAction` then decided the response itself and set
-`Combatant` regardless of what the AI had done. ModernUO moves the whole retaliation policy
-into the hook — the stand-down verdict for commanded pets, the closer-aggressor swap
-(`PreferCloserAggressor`), and the `IssueOrder(Attack)` vs `Combatant =` choice — and
-`BaseCreature` only runs its bookkeeping (`StopFlee`, `ForceReacquire`, ethics shield) when
-the hook returns `true`:
-
-```csharp
-// RunUO — swap only; BaseCreature picks the combatant afterwards
-public override void OnAggressiveAction(Mobile aggressor)
-{
- if (m_Mobile.Combatant == null)
- {
- m_Mobile.Combatant = aggressor;
- return;
- }
-
- base.OnAggressiveAction(aggressor);
-}
-
-// ModernUO — the AI owns the response; false = stood down, nothing else fires
-public override bool OnAggressiveAction(Mobile aggressor)
-{
- if (Mobile.Combatant == null)
- {
- Mobile.Combatant = aggressor;
- }
- else
- {
- PreferCloserAggressor(aggressor);
- }
-
- return true;
-}
-```
-
-An override that used to `return;` early to refuse a fight now returns `false`; calling
-`base.OnAggressiveAction` runs the full stock policy, including setting `Combatant`, so an AI
-that only wants the closer-aggressor swap calls `PreferCloserAggressor` instead.
-
-## Masters: One Reference, Not Two
-
-RunUO kept `ControlMaster` and `SummonMaster` as two independent fields. ModernUO stores one `Master`.
-`ControlMaster` and `SummonMaster` are views of it gated by `Controlled` and `Summoned`, and
-`GetMaster()` is `ControlMaster ?? SummonMaster` (see `dev-docs/content-patterns.md` § Masters).
-
-`ControlMaster != null` implies `Controlled`, so a bare `ControlMaster` check needs no `Controlled` in front of it.
-
-Assignments compile unchanged, because both setters assign `Master`. Reads need a look:
-
-| RunUO | ModernUO |
-|---|---|
-| `bc.ControlMaster ?? bc.SummonMaster` | `bc.GetMaster()` |
-| `bc.Controlled && bc.ControlMaster == m \|\| bc.Summoned && bc.SummonMaster == m` | `bc.GetMaster() == m` |
-| `Controlled && ControlMaster == m` (`m` non-null) | `ControlMaster == m` |
-| `SummonMaster` on a creature that sets it without `Summoned = true` (RunUO's `BaseEnraged`) | `Master` |
-
-Gotchas:
-
-- **A summon master without `Summoned` reads back null.** A RunUO creature that assigned
- `SummonMaster = caster;` without `Summoned = true` and later read `SummonMaster` now reads null.
- The write still compiles; only the read changes. Use `Master` for both, and override `IsEnemy` if the
- creature must never target its master (see `BaseEnraged`).
-- **One master per creature.** `SummonMaster = x` on a tamed pet replaces its owner, and
- `ControlMaster = null` on an uncontrolled summon clears its caster. Code that tracked a different
- owner and summoner keeps only the last assignment.
-- **`ControlMaster = x;` is not a tame.** The setter only moves follower slots. It skips everything
- `SetControlMaster(x)` does: raising `Controlled`, checking the follower cap, resetting the order
- and `ControlTarget`, unlinking from the spawner, clearing the waypoint, home and guild. On its own it
- leaves a creature that uses up `x`'s follower slots, can't be commanded and still reads as wild.
- Use `SetControlMaster(x)`.
-- **`SetControlMaster(null)` keeps an uncontrolled summon's caster.** It clears the master only
- while the creature is `Controlled`.
-
## Target Acquisition: `AcquireOnApproach` Is a Delay
RunUO's `AcquireOnApproach` bool (paragon insta-aggro on approach) is now
diff --git a/dev-docs/runuo-migration-docs/11-api-reference.md b/dev-docs/runuo-migration-docs/11-api-reference.md
index 788a7cd78..b6c12bfe8 100644
--- a/dev-docs/runuo-migration-docs/11-api-reference.md
+++ b/dev-docs/runuo-migration-docs/11-api-reference.md
@@ -134,7 +134,6 @@ Alphabetical by RunUO API name. Use Ctrl+F / Cmd+F to search.
| `WalkMobileRange(m, steps, run, min, max)` | `WalkMobileRange(m, steps, min, max)` | Same |
| `PathFollower.Follow(run, range)` | `Follow(range)` | Same |
| `AcquireOnApproach` (bool) | `AcquireOnApproachDelay` (TimeSpan) | Reaction-time gradient; `Zero` = old instant behavior |
-| `BaseAI.OnAggressiveAction(m)` (`void`) | `OnAggressiveAction(m)` (`bool`) | The AI owns the retaliation policy; `false` = stood down; swap-only overrides call `PreferCloserAggressor` |
| `m.DamageEntries` (`List`) | `m.DamageEntries` (`ref readonly ValueLinkList`) | Inline, least→most recent; `foreach` / `.ByDescending()` only, needs `using Server.Collections;`; no indexer, `Add`, `Remove`, `Clear` |
| `m.DamageEntries.Clear()` | `m.ClearDamageEntries()` | |
| `GetLootingRights(List, int)` | `GetLootingRights(in ValueLinkList, int)` | Callers passing `m.DamageEntries` compile unchanged |
diff --git a/publish.sh b/publish.sh
index 141f02b58..30d80c4f2 100755
--- a/publish.sh
+++ b/publish.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env bash
+#!/bin/bash
set -e
REPO_ROOT="$(cd "$(dirname "$0")" && pwd)"