From 910cdde124e4a84f5daf3d7e0aecc101fdc4ee5e Mon Sep 17 00:00:00 2001 From: WarrentyExpired Date: Mon, 28 Sep 2026 20:41:19 -0400 Subject: [PATCH] #W# Added: More things to the ToAdd Folder. --- ToAdd/AutoBandCommand.cs | 180 ++++ ToAdd/GraveRobbing/GraveRobbingCore.cs | 815 +++++++++++++++++++ ToAdd/GraveRobbing/PandoraChestConfig.cs | 280 +++++++ ToAdd/GraveRobbing/PandoraChestConfig.json | 62 ++ ToAdd/GraveRobbing/PandoraChestLogger.cs | 270 ++++++ ToAdd/GraveRobbing/PandoraChestSpawnGuard.cs | 66 ++ ToAdd/LockTrapBox.cs | 220 +++++ ToAdd/LoginStats.cs | 83 ++ ToAdd/PirateSystem.zip | Bin 0 -> 32687 bytes ToAdd/SecretDoorLink.cs | 239 ++++++ ToAdd/SquireSystem.zip | Bin 0 -> 78442 bytes ToAdd/Traps/CloneTrap.cs | 309 +++++++ ToAdd/Traps/CurseItemsTrap.cs | 301 +++++++ ToAdd/Traps/LandMobTrap.cs | 167 ++++ ToAdd/Traps/MagnetItemsTrap.cs | 215 +++++ ToAdd/Traps/SealedItemsTrap.cs | 182 +++++ 16 files changed, 3389 insertions(+) create mode 100644 ToAdd/AutoBandCommand.cs create mode 100644 ToAdd/GraveRobbing/GraveRobbingCore.cs create mode 100644 ToAdd/GraveRobbing/PandoraChestConfig.cs create mode 100644 ToAdd/GraveRobbing/PandoraChestConfig.json create mode 100644 ToAdd/GraveRobbing/PandoraChestLogger.cs create mode 100644 ToAdd/GraveRobbing/PandoraChestSpawnGuard.cs create mode 100644 ToAdd/LockTrapBox.cs create mode 100644 ToAdd/LoginStats.cs create mode 100644 ToAdd/PirateSystem.zip create mode 100644 ToAdd/SecretDoorLink.cs create mode 100644 ToAdd/SquireSystem.zip create mode 100644 ToAdd/Traps/CloneTrap.cs create mode 100644 ToAdd/Traps/CurseItemsTrap.cs create mode 100644 ToAdd/Traps/LandMobTrap.cs create mode 100644 ToAdd/Traps/MagnetItemsTrap.cs create mode 100644 ToAdd/Traps/SealedItemsTrap.cs diff --git a/ToAdd/AutoBandCommand.cs b/ToAdd/AutoBandCommand.cs new file mode 100644 index 000000000..abd6226d4 --- /dev/null +++ b/ToAdd/AutoBandCommand.cs @@ -0,0 +1,180 @@ +/********************************** +* Author: Expa (refactored) * +* Script Name: AutoBandCommand.cs * +* Target: ModernUO (.NET 10/C#14) * +**********************************/ +using System; +using ModernUO.Serialization; +using Server; +using Server.Commands; +using Server.Items; +using Server.Mobiles; +using Server.Targeting; + +namespace Server.Scripts.Commands; + +public static class AutoBandCommand +{ + public static void Initialize() + { + CommandSystem.Register("bas", AccessLevel.Player, OnBandSelf); + CommandSystem.Register("bat", AccessLevel.Player, OnBandTarget); + } + + public static void OnBandSelf(CommandEventArgs e) + { + if (e.Mobile is not PlayerMobile pm) return; + Start(pm, pm); + } + + public static void OnBandTarget(CommandEventArgs e) + => e.Mobile.BeginTarget(-1, false, TargetFlags.None, OnTarget); + + private static void OnTarget(Mobile from, object targeted) + { + if (from is not PlayerMobile pm || targeted is not Mobile target || target.Deleted || !target.Alive) + { + from.SendMessage("Invalid target."); + return; + } + + Start(pm, target); + } + + private static void Start(PlayerMobile pm, Mobile target) + { + if (pm.AutoBandageTimer is AutoBandageTimer existing && existing.Targets(target)) + { + StopTimer(pm); + SendHealthMessage(pm, $"Bandage auto-heal DISABLED{(pm == target ? "" : $" on {target.Name}")}."); + return; + } + + StopTimer(pm); + + var band = pm.Backpack?.FindItemByType(); + if (band == null || band.Amount <= 0) + { + pm.SendMessage("You have no bandages!"); + return; + } + + var timer = new AutoBandageTimer(pm, target); + pm.AutoBandageTimer = timer; + timer.Start(); + + SendHealthMessage(pm, $"Bandage auto-heal ENABLED{(pm == target ? "" : $" on {target.Name}")}. Health: {target.Hits}/{target.HitsMax}"); + } + + internal static void SendHealthMessage(Mobile m, string msg) + { + var denom = Math.Max(1, m.HitsMax); + double p = (double)m.Hits / denom; + var hue = p < 0.3 ? 33 : p < 0.7 ? 53 : 63; + m.SendMessage(hue, msg); + } + + private static void StopTimer(PlayerMobile pm) + { + pm.AutoBandageTimer?.Stop(); + pm.AutoBandageTimer = null; + } +} + +[SerializationGenerator(0)] +public partial class AutoBandageTimer : Timer +{ + private const double ResumeAt = 90.0; + private const double PauseAt = 100.0; + private static readonly TimeSpan TickInterval = TimeSpan.FromMilliseconds(100); + + private readonly WeakReference _casterRef; + private readonly WeakReference _targetRef; + private bool _paused; + private DateTime _next; + + public AutoBandageTimer(Mobile caster, Mobile target) + : base(TimeSpan.Zero, TickInterval) + { + _casterRef = new(caster); + _targetRef = new(target); + _next = Core.Now; + } + + public bool Targets(Mobile m) + => m is not null && _targetRef.TryGetTarget(out var t) && t == m; + + protected override void OnTick() + { + if (!_casterRef.TryGetTarget(out var caster) || caster.Deleted || !caster.Alive || caster.Map == Map.Internal) + { + Stop(); + Cleanup(caster); + return; + } + + if (!_targetRef.TryGetTarget(out var target) || target.Deleted || !target.Alive || target.Map == Map.Internal) + { + caster.SendMessage("Target is no longer valid. Automatic bandaging stopped."); + Stop(); + Cleanup(caster); + return; + } + + if (BandageContext.GetContext(caster) != null) + return; + + var denom = Math.Max(1, target.HitsMax); + double hpPercent = (double)target.Hits / denom * 100.0; + + if (!_paused && hpPercent >= PauseAt) + { + _paused = true; + caster.SendMessage("Bandaging paused. Target's health is full."); + return; + } + + if (_paused && hpPercent < ResumeAt) + { + _paused = false; + caster.SendMessage($"Bandaging resumed. Target's health dropped below {ResumeAt}%."); + _next = Core.Now; + } + + if (_paused || Core.Now < _next) return; + + var band = caster.Backpack?.FindItemByType(); + if (band == null || band.Amount <= 0) + { + caster.SendMessage("Out of bandages. Automatic bandaging stopped."); + Stop(); + Cleanup(caster); + return; + } + + int preHits = target.Hits; + double delaySec = 5.0 + 0.05 * Math.Max(0, 120 - caster.Dex); + _next = Core.Now + TimeSpan.FromSeconds(delaySec); + + Bandage.BandageTargetRequest(caster, band, target); + Timer.DelayCall(TimeSpan.FromSeconds(delaySec * 0.95), () => ShowResult(caster, target, preHits)); + } + + private static void ShowResult(Mobile? caster, Mobile? target, int preHits) + { + if (caster == null || target == null) return; + if (caster.Deleted || !caster.Alive || target.Deleted || !target.Alive) return; + + int healed = target.Hits - preHits; + string msg = healed > 0 + ? $"Healing {target.Name}: {target.Hits}/{target.HitsMax} HP (+{healed})" + : $"Healing {target.Name}: {target.Hits}/{target.HitsMax} HP"; + + AutoBandCommand.SendHealthMessage(caster, msg); + } + + private static void Cleanup(Mobile? caster) + { + if (caster is PlayerMobile pm) pm.AutoBandageTimer = null; + } +} \ No newline at end of file diff --git a/ToAdd/GraveRobbing/GraveRobbingCore.cs b/ToAdd/GraveRobbing/GraveRobbingCore.cs new file mode 100644 index 000000000..edb42f172 --- /dev/null +++ b/ToAdd/GraveRobbing/GraveRobbingCore.cs @@ -0,0 +1,815 @@ +/********************************** +* Author: Unknow * +* Update/Fixed/Refactoring: Expa * +* Script: GraveRobbingCore.cs * +* Optimized for ModernUO 0.15.4.18* +**********************************/ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using ModernUO.Serialization; +using Server; +using Server.Items; +using Server.Mobiles; +using Server.Engines.Harvest; +using Server.Network; + +static class GRUtil +{ + public static double GetSkillValue(Mobile m, SkillName s) => m?.Skills?[s]?.Value ?? 0.0; +} + +namespace Server.Engines.Harvest +{ + [SerializationGenerator(0, false)] + public partial class GraveRobbing : HarvestSystem + { + static GraveRobbing _system; + public static GraveRobbing System => _system ??= new GraveRobbing(); + + readonly HarvestDefinition _def; + public HarvestDefinition Definition => _def; + + private GraveRobbing() + { + _def = new HarvestDefinition + { + BankWidth = 2, + BankHeight = 2, + MinTotal = 1, + MaxTotal = 2, + MinRespawn = TimeSpan.FromMinutes(20), + MaxRespawn = TimeSpan.FromMinutes(30), + Skill = SkillName.Mining, + MaxRange = 2, + ConsumedPerHarvest = 1, + ConsumedPerFeluccaHarvest = 1, + EffectActions = new[] { 13 }, + EffectSounds = new[] { 0x13E }, + EffectCounts = new[] { 1 }, + StaticTiles = new[] { 0xED3, 0xEDF, 0xEE0, 0xEE1, 0xEE2, 0xEE8 }, + RangedTiles = false, + NoResourcesMessage = "There is nothing here to dig up.", + FailMessage = "You dig but find nothing of value.", + OutOfRangeMessage = 500446, + TimedOutOfRangeMessage = 503041, + DoubleHarvestMessage = 503042, + PackFullMessage = 1010481, + ToolBrokeMessage = 1044038 + }; + + _def.Resources = new[] + { + new HarvestResource(60, 10, 150, "You dig up some bones.", typeof(Bone)), + new HarvestResource(40, 30, 150, "You uncover some gold.", typeof(Gold)) + }; + + _def.Veins = new[] + { + new HarvestVein(70, 0, _def.Resources[0], null), + new HarvestVein(30, 0, _def.Resources[1], null) + }; + + Definitions = new[] { _def }; + + PandoraChestConfig.Load(); + PandoraChestLogger.Initialize(); + PandoraChestSpawnGuard.Initialize(); + } + + public override bool CheckHarvest(Mobile from, Item tool) + { + var region = from?.Region; + var regionName = region?.Name ?? "(null)"; + var ok = false; + + if (region != null) + { + try + { + if (region.IsPartOf("Cemetery")) + ok = true; + } + catch (Exception ex) + { + PandoraChestLogger.LogError("GraveRobbing.CheckHarvest.IsPartOf", ex, $"RegionName={regionName}", from); + } + + if (!ok && !string.IsNullOrEmpty(regionName)) + { + var rn = regionName.ToLowerInvariant(); + if (rn.Contains("grave") || rn.Contains("graveyard") || rn.Contains("cemetery")) + ok = true; + } + } + + if (!ok) + { + from?.SendMessage($"You may only rob graves in a cemetery. Region: {regionName}"); + return false; + } + + if (tool == null) + { + from?.SendMessage("You need to use a shovel or pickaxe to dig here."); + return false; + } + + return base.CheckHarvest(from, tool); + } + + public override void OnHarvestFinished(Mobile from, Item tool, HarvestDefinition def, HarvestVein vein, HarvestBank bank, HarvestResource resource, object harvested) + { + base.OnHarvestFinished(from, tool, def, vein, bank, resource, harvested); + + GetHarvestDetails(from, tool, harvested, out _, out var map, out var loc, out _); + + if (from != null) + { + from.Karma -= Utility.RandomMinMax(50, 150); + } + + var hour = DateTime.Now.Hour; + var night = hour < 6 || hour > 20; + + MoonPhase? moon = null; + if (map != null) + { + try + { + moon = Clock.GetMoonPhase(map, loc.X, loc.Y); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("GraveRobbing.OnHarvestFinished.GetMoonPhase", ex, $"Map={map},Loc={loc}", from); + moon = null; + } + } + + double baseSpiritChance = 0.10; + if (moon == MoonPhase.FullMoon) baseSpiritChance += 0.15; + if (night) baseSpiritChance += 0.10; + + double spiritSpeak = GRUtil.GetSkillValue(from, SkillName.SpiritSpeak); + double necromancy = GRUtil.GetSkillValue(from, SkillName.Necromancy); + double skillReduction = (spiritSpeak + necromancy) / 2000.0; + double spiritChanceFinal = Math.Max(0.01, baseSpiritChance - skillReduction); + + if (Utility.RandomDouble() < spiritChanceFinal) + { + var spirit = new VengefulSpirit(); + if (night) + { + spirit.Hits += 20; + spirit.DamageMin += 2; + spirit.DamageMax += 2; + } + + Point3D spawnLoc; + Map spawnMap; + if (map != null) + { + spawnLoc = loc; + spawnMap = map; + } + else + { + spawnLoc = from?.Location ?? Point3D.Zero; + spawnMap = from?.Map; + } + + if (spawnMap != null) + { + try + { + spirit.MoveToWorld(spawnLoc, spawnMap); + spirit.Combatant = from; + spirit.Say("You should not have disturbed the dead!"); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("GraveRobbing.OnHarvestFinished.SpawnSpirit", ex, $"Player={from?.Name},Map={spawnMap},Loc={spawnLoc}", from); + } + } + } + + if (from != null && Utility.RandomDouble() < 0.25) + { + from.SendMessage(0x21, "A grave curse weakens you!"); + try + { + from.AddStatMod(new StatMod(StatType.Str, "GraveCurse", -5, TimeSpan.FromMinutes(5))); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("GraveRobbing.OnHarvestFinished.AddStatMod", ex, $"Player={from?.Name}", from); + } + } + + TrySpawnPandoraChest(from, loc, map); + } + + void TrySpawnPandoraChest(Mobile from, Point3D loc, Map map) + { + if (from == null || map == null) return; + var cfg = PandoraChestConfig.Current; + if (!PandoraChestSpawnGuard.CanSpawnAt(map, loc, cfg)) return; + + string acc = "(null)"; + try + { + acc = from?.Account?.Username ?? from?.Account?.ToString() ?? "(null)"; + } + catch (Exception ex) + { + PandoraChestLogger.LogError("GraveRobbing.TrySpawnPandoraChest.GetAccount", ex, $"Player={from?.Name}", from); + acc = "(null)"; + } + + if (!PandoraChestSpawnGuard.CanSpawnForAccount(acc, cfg)) return; + + double mining = GRUtil.GetSkillValue(from, SkillName.Mining); + double spiritSpeak = GRUtil.GetSkillValue(from, SkillName.SpiritSpeak); + double necro = GRUtil.GetSkillValue(from, SkillName.Necromancy); + double spawn = cfg.Spawn.BaseSpawnChance + mining * cfg.Spawn.MiningFactor + spiritSpeak * cfg.Spawn.SpiritSpeakFactor + necro * cfg.Spawn.NecromancyFactor; + spawn = Math.Min(spawn, cfg.Spawn.MaxSpawnChance); + if (Utility.RandomDouble() >= spawn) return; + + int level = DetermineChestLevel(mining, spiritSpeak, necro, cfg); + var chest = new PandoraChest(level, from); + try + { + chest.MoveToWorld(loc, map); + chest.Populate(from, map); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("GraveRobbing.TrySpawnPandoraChest.SpawnChest", ex, $"Player={from?.Name},Map={map},Loc={loc},Level={level}", from); + } + + PandoraChestLogger.LogSpawn(from, map, loc, level, spawn); + PandoraChestSpawnGuard.RecordAt(map, loc); + PandoraChestSpawnGuard.RecordAccount(acc); + + double guardChance = cfg.Guardian.BaseChance + level * cfg.Guardian.LevelMultiplier; + guardChance = Math.Min(guardChance, cfg.Guardian.MaxChance); + if (Utility.RandomDouble() < guardChance) + { + int count = 1 + (level / 3); + for (int i = 0; i < count; i++) + { + var g = new VengefulSpirit(); + g.Hits += (int)(g.Hits * (1.0 + cfg.Guardian.HpMultiplierPerQuality * (level - 1))); + g.DamageMin += (int)(cfg.Guardian.DamageMultiplierPerQuality * (level - 1)); + g.DamageMax += (int)(cfg.Guardian.DamageMultiplierPerQuality * (level - 1) * 2); + try + { + g.MoveToWorld(new Point3D(loc.X + Utility.RandomMinMax(-2, 2), loc.Y + Utility.RandomMinMax(-2, 2), loc.Z), map); + g.Combatant = from; + } + catch (Exception ex) + { + PandoraChestLogger.LogError("GraveRobbing.TrySpawnPandoraChest.SpawnGuard", ex, $"Player={from?.Name},Map={map},Loc={loc},Level={level}", from); + } + } + } + } + + int DetermineChestLevel(double mining, double spirit, double necro, PandoraChestConfig cfg) + { + double weighted = mining * cfg.Weights.Mining + spirit * cfg.Weights.SpiritSpeak + necro * cfg.Weights.Necromancy; + double norm = Math.Min(weighted / 120.0, 1.0); + var q = cfg.LevelQualityBaseCached; + int levels = Math.Max(1, Math.Min(cfg.Spawn.Levels, q.Length)); + double[] adj = new double[levels]; + for (int i = 0; i < levels; i++) + { + double qq = (i < q.Length) ? q[i] : q.Last(); + adj[i] = Math.Max(0.001, (0.2 + qq) * (1.0 + norm * (i / (double)levels) * 3.0)); + } + double sum = adj.Sum(); + double r = Utility.RandomDouble() * sum; + double acc = 0; + for (int i = 0; i < adj.Length; i++) + { + acc += adj[i]; + if (r <= acc) return i + 1; + } + return levels; + } + } + + public static class GraveRobbingInit { public static void Initialize() { _ = GraveRobbing.System; } } +} + +namespace Server.Items +{ + [SerializationGenerator(0, false)] + [Flippable(0x09AA, 0x0E7D)] + public partial class PandoraChest : LockableContainer + { + int _level; + bool _isCursed; + bool _isTrapped; + int _trapPower; + Mobile _owner; + enum TrapType { None, Explosion, Poison, SummonUndead } + TrapType _trapType = TrapType.None; + + [Constructible] public PandoraChest() : this(1, null) { } + public PandoraChest(int level, Mobile owner) : base(0x09AA) + { + Weight = 5.0; + LootType = LootType.Regular; + _owner = owner; + + var cfg = PandoraChestConfig.Current; + _level = Math.Max(1, Math.Min(level, cfg.Spawn.Levels)); + Name = $"Pandora's Chest [L{_level}] {(_level >= 4 ? "(Jeweled)" : _level >= 3 ? "(Polished)" : "(Simple)")}"; + int hue = (cfg.HueByLevelCached != null && cfg.HueByLevelCached.Length >= _level) ? cfg.HueByLevelCached[_level - 1] : 0; + if (hue != 0) Hue = hue; + + _isCursed = Utility.RandomDouble() < cfg.BaseCursedChance; + Locked = Utility.RandomDouble() < cfg.BaseLockedChance; + _isTrapped = Utility.RandomDouble() < cfg.BaseTrappedChance; + LockLevel = 10 * _level; + MaxLockLevel = Math.Max(LockLevel, 120); + RequiredSkill = 10 * _level; + _trapPower = _isTrapped ? (10 + 15 * _level) : 0; + if (_isCursed) Name = $"Cursed {Name}"; + } + + public void Populate(Mobile opener, Map map) => FillWithLoot(_level, opener, map); + + void FillWithLoot(int level, Mobile opener, Map map) + { + if (Items.Count > 0) return; + + var cfg = PandoraChestConfig.Current; + var ranges = cfg.GoldRangeByLevelCached; + int idx = Math.Max(0, Math.Min(level - 1, ranges.Length - 1)); + int gold = Utility.RandomMinMax(ranges[idx].Min, ranges[idx].Max); + DropItem(new Gold(gold)); + var dropped = new List { $"Gold:{gold}" }; + + int minR = cfg.ReagentsMinCached.Length > idx ? cfg.ReagentsMinCached[idx] : 1; + int maxR = cfg.ReagentsMaxCached.Length > idx ? cfg.ReagentsMaxCached[idx] : Math.Max(1, minR); + int reagCount = Utility.RandomMinMax(minR, maxR); + + var reagentStacks = new Dictionary(); + for (int i = 0; i < reagCount; i++) + { + var r = ReagentForLevel(level, cfg); + if (r == null) continue; + var t = r.GetType(); + reagentStacks.TryGetValue(t, out var cur); + reagentStacks[t] = cur + (r.Amount > 0 ? r.Amount : 1); + } + + foreach (var kv in reagentStacks) + { + try + { + var inst = (Item)Activator.CreateInstance(kv.Key)!; + try { inst.Amount = Math.Max(1, kv.Value); } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.FillWithLoot.SetReagentAmount", ex, $"Type={kv.Key.FullName},Level={level}", opener); } + DropItem(inst); + dropped.Add(inst.GetType().Name + (inst.Amount > 1 ? $"x{inst.Amount}" : "")); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.FillWithLoot.CreateReagentInstance", ex, $"Type={kv.Key.FullName},Level={level}", opener); + } + } + + if (level >= 4) + { + if (level >= 5) + { + try + { + var bp = new BlackPearl(); + try { bp.Amount = 1; } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.FillWithLoot.SetBlackPearlAmount", ex, $"Level={level}", opener); } + DropItem(bp); + dropped.Add("Guaranteed:BlackPearl"); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.FillWithLoot.CreateBlackPearl", ex, $"Level={level}", opener); + } + } + else + { + try + { + var sa = new SulfurousAsh(); + try { sa.Amount = 1; } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.FillWithLoot.SetSulfurousAshAmount", ex, $"Level={level}", opener); } + DropItem(sa); + dropped.Add("Guaranteed:SulfurousAsh"); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.FillWithLoot.CreateSulfurousAsh", ex, $"Level={level}", opener); + } + } + } + + if (Utility.Random(100) < Math.Min(95, cfg.Weights.Level * 10 + 30 + level * 8)) + { + DropItem(new NightSightPotion()); + dropped.Add("NightSightPotion"); + } + + if (Utility.Random(100) < Math.Min(95, 20 + level * 6)) + { + DropItem(new LesserHealPotion()); + dropped.Add("LesserHealPotion"); + } + + double jewelryChance = Math.Min(95.0, 8 + level * 6 + cfg.Weights.Level * 10); + bool jewelryDropped = false; + if (Utility.Random(100) < jewelryChance) + { + Item j = (level >= 4) ? MakeJeweledJewelry(level) : (level >= 3) ? MakePolishedJewelry(level) : MakeSimpleJewelry(level); + if (j != null) { DropItem(j); dropped.Add(j.GetType().Name + ":" + (j.Name ?? "j")); jewelryDropped = true; } + } + + if (level >= 5 && !jewelryDropped) + { + var j = MakeJeweledJewelry(level); + if (j != null) { DropItem(j); dropped.Add("GuaranteedJeweled:" + (j.Name ?? j.GetType().Name)); } + } + + if (Utility.Random(100) < (20 + level * 8)) { DropItem(new Bone()); dropped.Add("Bone"); } + + if (Utility.RandomDouble() < cfg.RareDrop.BaseChance * level) { DropItem(new SpecialRareBoneItem()); dropped.Add("SpecialRareBoneItem"); } + + if (Utility.Random(100) < 10 + level * 4) { DropItem(new Lockpick()); dropped.Add("Lockpick"); } + + if (cfg.Magic.Enabled) + { + double mapMult = MapMultiplier(map, cfg); + double luck = opener?.Luck ?? 0; + double skillSum = (GRUtil.GetSkillValue(opener, SkillName.Mining) + GRUtil.GetSkillValue(opener, SkillName.SpiritSpeak) + GRUtil.GetSkillValue(opener, SkillName.Necromancy)) / 3.0; + double skillFactor = 1.0 + skillSum / 200.0; + double chance = cfg.Magic.BaseChanceByLevelMultiplier * level * mapMult * (1.0 + luck / 1000.0) * skillFactor; + chance = Math.Min(chance, 0.95); + if (Utility.RandomDouble() < chance) + { + Item? magic = null; + try + { + magic = GenerateMagicItem(level, opener, cfg); + if (magic != null) + { + DropItem(magic); + PandoraChestLogger.LogMagicDrop(opener, level, magic); + dropped.Add("Magic:" + (magic.Name ?? magic.GetType().Name)); + int minutes = Math.Max(1, cfg.Magic.DecayMinutesIfDropped); + Timer.DelayCall(TimeSpan.FromMinutes(minutes), () => + { + try { if (magic != null && !magic.Deleted && magic.Parent == this) magic.Delete(); } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.FillWithLoot.MagicDecayDelete", ex, $"Player={opener?.Name},Level={level},Item={magic?.GetType().Name}", opener); } + }); + } + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.FillWithLoot.GenerateMagicItem", ex, $"Player={opener?.Name},Level={level}", opener); + } + } + + if (Utility.RandomDouble() < cfg.Magic.TreasureMapChancePerLevel * level * MapMultiplier(map, cfg)) + { + try { var tm = new TreasureMap(level, map); DropItem(tm); dropped.Add("TreasureMap:L" + level); } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.FillWithLoot.TreasureMap", ex, $"Player={opener?.Name},Level={level}", opener); } + } + } + + var tcfg = PandoraChestConfig.Current.Trap; + if (tcfg != null) + { + double mapMult = MapMultiplier(map, PandoraChestConfig.Current); + double spirit = GRUtil.GetSkillValue(_owner, SkillName.SpiritSpeak); + double necro = GRUtil.GetSkillValue(_owner, SkillName.Necromancy); + double luck = _owner?.Luck ?? 0; + double reduction = (spirit + necro) / 300.0 + luck / 2000.0; + double trapBase = tcfg.BaseChancePerLevel * level * mapMult; + if (Utility.RandomDouble() < Math.Max(0.02, trapBase - reduction)) + { + try + { + int total = tcfg.TypeWeights.Values.Sum(); + int r = Utility.Random(total); + foreach (var kv in tcfg.TypeWeights) + { + r -= kv.Value; + if (r < 0) + { + if (Enum.TryParse(kv.Key, out var tt)) _trapType = tt; + break; + } + } + _isTrapped = true; + _trapPower = 1 + (int)Math.Round(level * tcfg.PowerMultiplierPerLevel); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.FillWithLoot.DetermineTrap", ex, $"Player={_owner?.Name},Level={level}", _owner); + } + } + } + + PandoraChestLogger.LogContents(opener, _level, dropped); + } + + static double MapMultiplier(Map m, PandoraChestConfig cfg) + { + if (m == null) return 1.0; + var nm = m.ToString().ToLowerInvariant(); + if (nm.Contains("felucca")) return cfg.MapMultiplier.Felucca; + if (nm.Contains("trammel")) return cfg.MapMultiplier.Trammel; + return cfg.MapMultiplier.Trammel; + } + + static Item? GenerateMagicItem(int level, Mobile opener, PandoraChestConfig cfg) + { + try + { + double pick = Utility.RandomDouble(); + Item it = (pick < 0.55) ? Loot.RandomWeapon() : (pick < 0.90) ? Loot.RandomArmor() : Loot.RandomJewelry(); + if (it == null) return null; + + it.Name = $"Enchanted {it.GetType().Name} (L{level})"; + if (level >= 4) it.Hue = 0x496; + else if (level >= 3) it.Hue = 0x47E; + + int baseAff = cfg.Magic.MaxAffixesByLevelBase; + int extra = level >= 5 ? 1 : 0; + int affixes = baseAff + extra; + + double luck = opener?.Luck ?? 0; + double skillSum = (GRUtil.GetSkillValue(opener, SkillName.Mining) + GRUtil.GetSkillValue(opener, SkillName.SpiritSpeak) + GRUtil.GetSkillValue(opener, SkillName.Necromancy)) / 3.0; + double mult = 1.0 + (level - 1) * 0.15 + luck / 500.0 + skillSum / 200.0; + + var keys = cfg.Magic.AffixRanges?.Keys.ToArray() ?? Array.Empty(); + for (int i = 0; i < affixes && keys.Length > 0; i++) + { + var key = keys[Utility.Random(keys.Length)]; + var range = cfg.Magic.AffixRanges[key]; + int raw = Utility.RandomMinMax(range.Min, range.Max); + int val = Math.Max(1, (int)Math.Round(raw * mult)); + ApplyAffixToItem(it, key, val); + } + return it; + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.GenerateMagicItem", ex, $"Level={level},Player={opener?.Name}", opener); + return null; + } + } + + static void ApplyAffixToItem(Item it, string affixName, int value) + { + if (it == null) return; + try + { + if (it is BaseWeapon weapon) + { + if (weapon.Attributes != null) + { + switch (affixName) + { + case "BonusStr": weapon.Attributes.BonusStr += value; return; + case "BonusDex": weapon.Attributes.BonusDex += value; return; + case "BonusHits": weapon.Attributes.BonusHits += value; return; + case "WeaponDamage": weapon.Attributes.WeaponDamage += value; return; + } + } + + var wattrsProp = weapon.GetType().GetProperty("WeaponAttributes", BindingFlags.Public | BindingFlags.Instance); + if (wattrsProp != null) + { + var wattrs = wattrsProp.GetValue(weapon); + if (wattrs != null) + { + var p = wattrs.GetType().GetProperty(affixName, BindingFlags.Public | BindingFlags.Instance); + if (p != null && p.PropertyType == typeof(int)) + { + int old = (int)p.GetValue(wattrs); + p.SetValue(wattrs, old + value); + return; + } + } + } + } + + if (it is BaseArmor armor) + { + if (armor.Attributes != null) + { + switch (affixName) + { + case "BonusStr": armor.Attributes.BonusStr += value; return; + case "BonusDex": armor.Attributes.BonusDex += value; return; + case "BonusHits": armor.Attributes.BonusHits += value; return; + } + } + + var aattrsProp = armor.GetType().GetProperty("ArmorAttributes", BindingFlags.Public | BindingFlags.Instance); + if (aattrsProp != null) + { + var aattrs = aattrsProp.GetValue(armor); + if (aattrs != null) + { + var p = aattrs.GetType().GetProperty(affixName, BindingFlags.Public | BindingFlags.Instance); + if (p != null && p.PropertyType == typeof(int)) + { + int old = (int)p.GetValue(aattrs); + p.SetValue(aattrs, old + value); + return; + } + } + } + } + + if (it is BaseJewel jewel) + { + if (jewel.Attributes != null) + { + switch (affixName) + { + case "BonusStr": jewel.Attributes.BonusStr += value; return; + case "BonusDex": jewel.Attributes.BonusDex += value; return; + case "BonusHits": jewel.Attributes.BonusHits += value; return; + case "WeaponDamage": jewel.Attributes.WeaponDamage += value; return; + } + } + } + + var attrsProp = it.GetType().GetProperty("Attributes", BindingFlags.Public | BindingFlags.Instance); + if (attrsProp != null) + { + var attrsObj = attrsProp.GetValue(it); + if (attrsObj != null) + { + var prop = attrsObj.GetType().GetProperty(affixName, BindingFlags.Public | BindingFlags.Instance); + if (prop != null && prop.PropertyType == typeof(int)) + { + int old = (int)prop.GetValue(attrsObj); + prop.SetValue(attrsObj, old + value); + return; + } + } + } + + it.Name = $"{it.Name} [+{value} {affixName}]"; + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.ApplyAffixToItem", ex, $"Item={it?.GetType().FullName},Affix={affixName},Value={value}"); + try { it.Name = $"{it.Name} [+{value} {affixName}]"; } catch { } + } + } + + static Item ReagentForLevel(int level, PandoraChestConfig cfg) + { + int r = Utility.Random(100); + if (level >= 5 && r < 25) return new BlackPearl(); + if (level >= 4 && r < 30) return new SulfurousAsh(); + Item[] common = { new Garlic(), new Ginseng(), new MandrakeRoot(), new Bloodmoss() }; + return common[Utility.Random(common.Length)]; + } + + static Item MakeSimpleJewelry(int level) => Utility.RandomBool() ? new GoldRing() : new GoldNecklace(); + + static Item MakePolishedJewelry(int level) + { + var it = MakeSimpleJewelry(level); + if (it is GoldRing r) { r.Name = "Polished Gold Ring"; r.Hue = 0x47E; } + if (it is GoldNecklace n) { n.Name = "Polished Gold Necklace"; n.Hue = 0x47E; } + return it; + } + + static Item MakeJeweledJewelry(int level) + { + var it = MakeSimpleJewelry(level); + if (it is GoldRing r) { r.Name = "Jeweled Gold Ring"; r.Hue = 0x496; } + if (it is GoldNecklace n) { n.Name = "Jeweled Gold Necklace"; n.Hue = 0x496; } + return it; + } + + public override bool CheckLocked(Mobile from) + { + if (_isCursed && from != null) + { + double necro = GRUtil.GetSkillValue(from, SkillName.Necromancy); + double spirit = GRUtil.GetSkillValue(from, SkillName.SpiritSpeak); + double resist = (necro + spirit) / 240.0; + if (Utility.RandomDouble() > resist) + { + from.SendMessage(0x22, "An ancient curse flares as you touch the chest..."); + try { from.ApplyPoison(from, Poison.Deadly); } + catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.CheckLocked.ApplyPoison", ex, $"Player={from?.Name}", from); } + } + } + return base.CheckLocked(from); + } + + public override void OnDoubleClick(Mobile from) + { + if (from == null || from.Deleted) return; + if (Locked) { base.OnDoubleClick(from); return; } + + if (_isTrapped && _trapType != TrapType.None) + { + double trapChance = _trapPower / 100.0; + double spirit = GRUtil.GetSkillValue(from, SkillName.SpiritSpeak); + double necro = GRUtil.GetSkillValue(from, SkillName.Necromancy); + double resist = (spirit + necro) / 240.0; + trapChance = Math.Max(0.05, trapChance - resist); + if (Utility.RandomDouble() < trapChance) { TriggerTrap(from); _isTrapped = false; Locked = true; return; } + } + + if (_isCursed) + { + from.SendMessage(0x21, "A dark curse hits you for opening a desecrated chest!"); + try { from.AddStatMod(new StatMod(StatType.Dex, "PandoraCurseDex", -3, TimeSpan.FromMinutes(3))); } + catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.OnDoubleClick.AddStatMod.Dex", ex, $"Player={from?.Name}", from); } + try { from.AddStatMod(new StatMod(StatType.Str, "PandoraCurseStr", -3, TimeSpan.FromMinutes(3))); } + catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.OnDoubleClick.AddStatMod.Str", ex, $"Player={from?.Name}", from); } + if (Utility.RandomDouble() < 0.35) try { from.ApplyPoison(from, Poison.Lesser); } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.OnDoubleClick.ApplyPoison", ex, $"Player={from?.Name}", from); } + } + + base.OnDoubleClick(from); + } + + void TriggerTrap(Mobile from) + { + if (from == null || from.Deleted) return; + var tcfg = PandoraChestConfig.Current.Trap; + switch (_trapType) + { + case TrapType.Explosion: + try + { + int min = tcfg.ExplosionBaseMin + (_trapPower * 2); + int max = tcfg.ExplosionBaseMax + (_trapPower * 4); + int dmg = Utility.RandomMinMax(min, max); + from.Damage(dmg); + foreach (var m in from.GetMobilesInRange(2)) + if (m != from && m.Alive) try { m.Damage(dmg / 2); } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.TriggerTrap.DamageNearby", ex, $"Player={from?.Name},Target={m?.Name}", from); } + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.TriggerTrap.Explosion", ex, $"Player={from?.Name},TrapPower={_trapPower},Level={_level}", from); + } + break; + case TrapType.Poison: + try + { + var p = _trapPower >= 3 ? Poison.Deadly : (_trapPower == 2 ? Poison.Greater : Poison.Lesser); + from.ApplyPoison(from, p); + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.TriggerTrap.Poison", ex, $"Player={from?.Name},TrapPower={_trapPower},Level={_level}", from); + } + break; + case TrapType.SummonUndead: + try + { + int count = Math.Max(1, PandoraChestConfig.Current.Trap.SummonPerPower * _trapPower); + for (int i = 0; i < count; i++) + { + var z = new VengefulSpirit(); + z.Hits += 10 * _trapPower; + z.DamageMin += _trapPower; + z.DamageMax += _trapPower * 2; + try { z.MoveToWorld(from.Location, from.Map); z.Combatant = from; } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.TriggerTrap.SummonUndead.Move", ex, $"Player={from?.Name}", from); } + } + } + catch (Exception ex) + { + PandoraChestLogger.LogError("PandoraChest.TriggerTrap.SummonUndead", ex, $"Player={from?.Name},TrapPower={_trapPower},Level={_level}", from); + } + break; + } + try { from.SendMessage(0x22, "A hidden trap explodes as you touch the chest!"); } catch (Exception ex) { PandoraChestLogger.LogError("PandoraChest.TriggerTrap.SendMessage", ex, $"Player={from?.Name}", from); } + } + } + + [SerializationGenerator(0, false)] + public partial class SpecialRareBoneItem : Item { [Constructible] public SpecialRareBoneItem() : base(0x1B) { Name = "Ancient Bone Relic"; Hue = 0x485; Weight = 1.0; } } + + [SerializationGenerator(0, false)] + public partial class VengefulSpirit : BaseCreature + { + [Constructible] + public VengefulSpirit() : base(AIType.AI_Mage, FightMode.Closest, 10, 1) + { + Name = "Vengeful Spirit"; Body = 26; Hue = 0x4001; SetStr(50); SetDex(50); SetInt(100); SetHits(40); SetDamage(8, 12); Fame = 4000; Karma = -4000; VirtualArmor = 30; + } + public override bool AlwaysAttackable => true; + public override bool BleedImmune => true; + } +} \ No newline at end of file diff --git a/ToAdd/GraveRobbing/PandoraChestConfig.cs b/ToAdd/GraveRobbing/PandoraChestConfig.cs new file mode 100644 index 000000000..7cee599e9 --- /dev/null +++ b/ToAdd/GraveRobbing/PandoraChestConfig.cs @@ -0,0 +1,280 @@ +/********************************** +* Author: Expa * +* Script: PandoraChestConfig.cs * +* Optimized for ModernUO 0.15.4.18* +**********************************/ +using System; +using System.IO; +using System.Linq; +using System.Text.Json; + +public class PandoraChestConfig +{ + private static PandoraChestConfig _current; + private static readonly object _lock = new(); + + public static PandoraChestConfig Current + { + get + { + if (_current == null) Load(); + return _current!; + } + } + + public SpawnSection Spawn { get; set; } = new(); + public double[] LevelQualityBase { get; set; } = new double[] { 0.0, 0.10, 0.25, 0.45, 0.85 }; + public GoldRange[] GoldRangeByLevel { get; set; } = new[] + { + new GoldRange{ Min = 10, Max = 60}, + new GoldRange{ Min = 30, Max = 120}, + new GoldRange{ Min = 80, Max = 300}, + new GoldRange{ Min = 200, Max = 1200}, + new GoldRange{ Min = 1500, Max = 5000} + }; + public RareDropSection RareDrop { get; set; } = new RareDropSection(); + public ReagentsSection Reagents { get; set; } = new ReagentsSection(); + public WeightsSection Weights { get; set; } = new WeightsSection(); + public GuardianSection Guardian { get; set; } = new GuardianSection(); + public LoggingSection Logging { get; set; } = new LoggingSection(); + public MagicSection Magic { get; set; } = new MagicSection(); + public TrapSection Trap { get; set; } = new TrapSection(); + public MapMultiplierSection MapMultiplier { get; set; } = new MapMultiplierSection(); + public int[] HueByLevel { get; set; } = new int[] { 0, 0x47E, 0x47E, 0x496, 0x496 }; + public double BaseCursedChance { get; set; } = 0.10; + public double BaseTrappedChance { get; set; } = 0.12; + public double BaseLockedChance { get; set; } = 0.65; + + public double[] LevelQualityBaseCached = Array.Empty(); + public GoldRange[] GoldRangeByLevelCached = Array.Empty(); + public int[] ReagentsMinCached = Array.Empty(); + public int[] ReagentsMaxCached = Array.Empty(); + public int[] HueByLevelCached = Array.Empty(); + + public class SpawnSection + { + public int Levels { get; set; } = 5; + public double BaseSpawnChance { get; set; } = 0.08; + public double MiningFactor { get; set; } = 0.0006; + public double SpiritSpeakFactor { get; set; } = 0.0008; + public double NecromancyFactor { get; set; } = 0.0008; + public double MaxSpawnChance { get; set; } = 0.60; + public bool AllowIfLoggingDisabled { get; set; } = true; + } + + public class GoldRange + { + public int Min { get; set; } + public int Max { get; set; } + } + + public class RareDropSection + { + public double BaseChance { get; set; } = 0.02; + } + + public class ReagentsSection + { + public int[] MinPerLevel { get; set; } = new int[] { 1, 2, 3, 4, 6 }; + public int[] MaxPerLevel { get; set; } = new int[] { 2, 4, 6, 8, 12 }; + } + + public class WeightsSection + { + public double Mining { get; set; } = 0.6; + public double SpiritSpeak { get; set; } = 1.2; + public double Necromancy { get; set; } = 1.2; + public double Level { get; set; } = 0.1; + } + + public class GuardianSection + { + public double BaseChance { get; set; } = 0.05; + public double LevelMultiplier { get; set; } = 0.06; + public double MaxChance { get; set; } = 0.95; + public double HpMultiplierPerQuality { get; set; } = 1.0; + public double DamageMultiplierPerQuality { get; set; } = 0.8; + } + + public class LoggingSection + { + public bool Enabled { get; set; } = true; + public long MaxFileSizeBytes { get; set; } = 10 * 1024 * 1024; + public int FlushIntervalMs { get; set; } = 500; + public int LocationCooldownSeconds { get; set; } = 60; + public int AccountCooldownSeconds { get; set; } = 30; + } + + public class MagicSection + { + public bool Enabled { get; set; } = true; + public double BaseChanceByLevelMultiplier { get; set; } = 0.02; + public int DecayMinutesIfDropped { get; set; } = 30; + public int MaxAffixesByLevelBase { get; set; } = 1; + public System.Collections.Generic.Dictionary AffixRanges { get; set; } = new() + { + { "BonusStr", new AffixRange{ Min = 1, Max = 5 } }, + { "BonusDex", new AffixRange{ Min = 1, Max = 5 } }, + { "BonusHits", new AffixRange{ Min = 1, Max = 10 } }, + { "WeaponDamage", new AffixRange{ Min = 1, Max = 10 } } + }; + public double TreasureMapChancePerLevel { get; set; } = 0.03; + } + + public class TrapSection + { + public double BaseChancePerLevel { get; set; } = 0.12; + public System.Collections.Generic.Dictionary TypeWeights { get; set; } = new() + { + { "Explosion", 30 }, + { "Poison", 40 }, + { "SummonUndead", 30 } + }; + public double PowerMultiplierPerLevel { get; set; } = 0.6; + public int ExplosionBaseMin { get; set; } = 8; + public int ExplosionBaseMax { get; set; } = 16; + public int SummonPerPower { get; set; } = 1; + } + + public class AffixRange + { + public int Min { get; set; } + public int Max { get; set; } + } + + public class MapMultiplierSection + { + public double Felucca { get; set; } = 1.15; + public double Trammel { get; set; } = 1.0; + } + + public static void Load(string path = null) + { + lock (_lock) + { + try + { + string baseDir = AppDomain.CurrentDomain.BaseDirectory ?? Environment.CurrentDirectory; + string cfgPath = path ?? Path.Combine(baseDir, "PandoraChestConfig.json"); + if (!File.Exists(cfgPath)) + { + _current = new PandoraChestConfig(); + _current.FinalizeCache(); + return; + } + + string json = File.ReadAllText(cfgPath); + var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; + var cfg = JsonSerializer.Deserialize(json, opts) ?? new PandoraChestConfig(); + + cfg.Spawn ??= new SpawnSection(); + cfg.RareDrop ??= new RareDropSection(); + cfg.Reagents ??= new ReagentsSection(); + cfg.Weights ??= new WeightsSection(); + cfg.Guardian ??= new GuardianSection(); + cfg.Logging ??= new LoggingSection(); + cfg.Magic ??= new MagicSection(); + cfg.Trap ??= new TrapSection(); + cfg.MapMultiplier ??= new MapMultiplierSection(); + + if (cfg.LevelQualityBase == null) cfg.LevelQualityBase = Enumerable.Repeat(0.2, cfg.Spawn.Levels).ToArray(); + + if (cfg.GoldRangeByLevel == null || cfg.GoldRangeByLevel.Length < cfg.Spawn.Levels) + { + var arr = new GoldRange[cfg.Spawn.Levels]; + var src = cfg.GoldRangeByLevel ?? Array.Empty(); + for (int i = 0; i < arr.Length; i++) + arr[i] = (i < src.Length && src[i] != null) ? src[i] : new GoldRange { Min = 10, Max = 60 }; + cfg.GoldRangeByLevel = arr; + } + + if (cfg.Reagents.MinPerLevel == null || cfg.Reagents.MinPerLevel.Length < cfg.Spawn.Levels) + { + cfg.Reagents.MinPerLevel = new int[cfg.Spawn.Levels]; + for (int i = 0; i < cfg.Spawn.Levels; i++) cfg.Reagents.MinPerLevel[i] = Math.Max(1, 1 + i); + } + + if (cfg.Reagents.MaxPerLevel == null || cfg.Reagents.MaxPerLevel.Length < cfg.Spawn.Levels) + { + cfg.Reagents.MaxPerLevel = new int[cfg.Spawn.Levels]; + for (int i = 0; i < cfg.Spawn.Levels; i++) cfg.Reagents.MaxPerLevel[i] = Math.Max(cfg.Reagents.MinPerLevel[i], 2 + i * 2); + } + + ValidateAndClamp(cfg); + cfg.FinalizeCache(); + _current = cfg; + } + catch (Exception ex) + { + try { Console.Error.WriteLine($"PandoraChestConfig.Load failed: {ex}"); } catch { } + _current = new PandoraChestConfig(); + _current.FinalizeCache(); + } + } + } + + public static void Reload() => Load(); + + private static void ValidateAndClamp(PandoraChestConfig cfg) + { + cfg.Spawn ??= new SpawnSection(); + cfg.RareDrop ??= new RareDropSection(); + cfg.Reagents ??= new ReagentsSection(); + cfg.Weights ??= new WeightsSection(); + cfg.Guardian ??= new GuardianSection(); + cfg.Logging ??= new LoggingSection(); + cfg.Magic ??= new MagicSection(); + cfg.Trap ??= new TrapSection(); + cfg.MapMultiplier ??= new MapMultiplierSection(); + + cfg.Spawn.BaseSpawnChance = Clamp(cfg.Spawn.BaseSpawnChance, 0.0, 1.0); + cfg.Spawn.MiningFactor = Math.Max(0.0, cfg.Spawn.MiningFactor); + cfg.Spawn.SpiritSpeakFactor = Math.Max(0.0, cfg.Spawn.SpiritSpeakFactor); + cfg.Spawn.NecromancyFactor = Math.Max(0.0, cfg.Spawn.NecromancyFactor); + cfg.Spawn.MaxSpawnChance = Clamp(cfg.Spawn.MaxSpawnChance, 0.0, 1.0); + + cfg.RareDrop.BaseChance = Clamp(cfg.RareDrop.BaseChance, 0.0, 1.0); + cfg.BaseCursedChance = Clamp(cfg.BaseCursedChance, 0.0, 1.0); + cfg.BaseTrappedChance = Clamp(cfg.BaseTrappedChance, 0.0, 1.0); + cfg.BaseLockedChance = Clamp(cfg.BaseLockedChance, 0.0, 1.0); + cfg.Guardian.BaseChance = Clamp(cfg.Guardian.BaseChance, 0.0, 1.0); + cfg.Guardian.MaxChance = Clamp(cfg.Guardian.MaxChance, 0.0, 1.0); + + if (cfg.GoldRangeByLevel != null) + foreach (var gr in cfg.GoldRangeByLevel) + if (gr != null) + { + if (gr.Min < 0) gr.Min = 0; + if (gr.Max < gr.Min) gr.Max = gr.Min; + } + + if (cfg.Reagents.MinPerLevel == null || cfg.Reagents.MinPerLevel.Length < cfg.Spawn.Levels) + { + cfg.Reagents.MinPerLevel = new int[cfg.Spawn.Levels]; + for (int i = 0; i < cfg.Spawn.Levels; i++) cfg.Reagents.MinPerLevel[i] = Math.Max(1, 1 + i); + } + + if (cfg.Reagents.MaxPerLevel == null || cfg.Reagents.MaxPerLevel.Length < cfg.Spawn.Levels) + { + cfg.Reagents.MaxPerLevel = new int[cfg.Spawn.Levels]; + for (int i = 0; i < cfg.Spawn.Levels; i++) cfg.Reagents.MaxPerLevel[i] = Math.Max(cfg.Reagents.MinPerLevel[i], 2 + i * 2); + } + + if (cfg.Logging.MaxFileSizeBytes <= 0) cfg.Logging.MaxFileSizeBytes = 10 * 1024 * 1024; + if (cfg.Logging.FlushIntervalMs < 50) cfg.Logging.FlushIntervalMs = 50; + if (cfg.Logging.LocationCooldownSeconds < 1) cfg.Logging.LocationCooldownSeconds = 60; + if (cfg.Logging.AccountCooldownSeconds < 1) cfg.Logging.AccountCooldownSeconds = 30; + if (cfg.Magic.DecayMinutesIfDropped < 1) cfg.Magic.DecayMinutesIfDropped = 30; + } + + private static double Clamp(double v, double min, double max) => v < min ? min : (v > max ? max : v); + + public void FinalizeCache() + { + LevelQualityBaseCached = LevelQualityBase ?? Array.Empty(); + GoldRangeByLevelCached = GoldRangeByLevel ?? Array.Empty(); + ReagentsMinCached = Reagents?.MinPerLevel ?? Array.Empty(); + ReagentsMaxCached = Reagents?.MaxPerLevel ?? Array.Empty(); + HueByLevelCached = HueByLevel ?? Array.Empty(); + } +} \ No newline at end of file diff --git a/ToAdd/GraveRobbing/PandoraChestConfig.json b/ToAdd/GraveRobbing/PandoraChestConfig.json new file mode 100644 index 000000000..54ea37b59 --- /dev/null +++ b/ToAdd/GraveRobbing/PandoraChestConfig.json @@ -0,0 +1,62 @@ +{ + "Spawn": { + "Levels": 5, + "BaseSpawnChance": 0.08, + "MiningFactor": 0.0006, + "SpiritSpeakFactor": 0.0008, + "NecromancyFactor": 0.0008, + "MaxSpawnChance": 0.6 + }, + "LevelQualityBase": [0.0, 0.10, 0.25, 0.45, 0.85], + "GoldRangeByLevel": [ + { "Min": 10, "Max": 60 }, + { "Min": 30, "Max": 120 }, + { "Min": 80, "Max": 300 }, + { "Min": 200, "Max": 1200 }, + { "Min": 1500, "Max": 5000 } + ], + "RareDrop": { "BaseChance": 0.02 }, + "Reagents": { + "MinPerLevel": [1, 2, 3, 4, 6], + "MaxPerLevel": [2, 4, 6, 8, 12] + }, + "Weights": { + "Mining": 0.6, + "SpiritSpeak": 1.2, + "Necromancy": 1.2, + "Level": 0.1 + }, + "Guardian": { + "BaseChance": 0.05, + "LevelMultiplier": 0.06, + "MaxChance": 0.95, + "HpMultiplierPerQuality": 1.0, + "DamageMultiplierPerQuality": 0.8 + }, + "Logging": { + "Enabled": true, + "MaxFileSizeBytes": 10485760, + "FlushIntervalMs": 500, + "LocationCooldownSeconds": 60, + "AccountCooldownSeconds": 30 + }, + "Magic": { + "Enabled": true, + "BaseChanceByLevelMultiplier": 0.02, + "DecayMinutesIfDropped": 30, + "MaxAffixesByLevelBase": 1, + "TreasureMapChancePerLevel": 0.03, + "AffixRanges": { + "BonusStr": { "Min": 1, "Max": 5 }, + "BonusDex": { "Min": 1, "Max": 5 }, + "BonusHits": { "Min": 1, "Max": 10 }, + "WeaponDamage": { "Min": 1, "Max": 10 }, + "ArmorPhysical": { "Min": 1, "Max": 10 } + } + }, + "MapMultiplier": { + "Felucca": 1.15, + "Trammel": 1.0 + }, + "HueByLevel": [0, 114, 114, 118, 118] +} \ No newline at end of file diff --git a/ToAdd/GraveRobbing/PandoraChestLogger.cs b/ToAdd/GraveRobbing/PandoraChestLogger.cs new file mode 100644 index 000000000..b0b6d92b6 --- /dev/null +++ b/ToAdd/GraveRobbing/PandoraChestLogger.cs @@ -0,0 +1,270 @@ +/********************************** +* Author: Unknow * +* Update/Fixed/Refactoring: Expa * +* Script: PandoraChestLogger.cs * +* Optimized for ModernUO 0.15.4.18* +**********************************/ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Server; +using Server.Items; +using Server.Mobiles; + +static class PandoraChestLogger +{ + static readonly ConcurrentQueue Q = new(); + static CancellationTokenSource _cts; + static Task _worker; + static string _logFile; + static long _maxSize; + static int _flushInterval; + static readonly object _initLock = new(); + static readonly JsonSerializerOptions _jsonOpts = new() { WriteIndented = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + + public static void Initialize() + { + lock (_initLock) + { + if (_worker != null && !_worker.IsCompleted) return; + var baseDir = AppDomain.CurrentDomain.BaseDirectory ?? Environment.CurrentDirectory; + var logsDir = Path.Combine(baseDir, "Logs"); + try { Directory.CreateDirectory(logsDir); } catch (Exception ex) { try { Console.Error.WriteLine($"PandoraChestLogger.Initialize: CreateDirectory failed: {ex}"); } catch { } } + _logFile = Path.Combine(logsDir, "PandoraChestSpawns.log"); + var cfg = PandoraChestConfig.Current ?? new PandoraChestConfig(); + _maxSize = cfg.Logging?.MaxFileSizeBytes ?? (10 * 1024 * 1024); + _flushInterval = Math.Max(50, cfg.Logging?.FlushIntervalMs ?? 500); + _cts = new CancellationTokenSource(); + _worker = Task.Run(() => Worker(_cts.Token)); + } + } + + static async Task Worker(CancellationToken token) + { + var batch = new List(); + while (!token.IsCancellationRequested) + { + try { await Task.Delay(_flushInterval, token).ConfigureAwait(false); } catch { break; } + batch.Clear(); + while (Q.TryDequeue(out var l)) { batch.Add(l); if (batch.Count >= 1000) break; } + if (batch.Count == 0) continue; + try + { + RotateIfNeeded(); + if (!string.IsNullOrEmpty(_logFile)) File.AppendAllLines(_logFile, batch, Encoding.UTF8); + } + catch (Exception ex) + { + try { Console.Error.WriteLine($"PandoraChestLogger.Worker AppendAllLines failed: {ex}"); } catch { } + foreach (var l in batch) try { Console.WriteLine(l); } catch { } + } + } + var rem = new List(); + while (Q.TryDequeue(out var l)) rem.Add(l); + if (rem.Count > 0) + { + try { RotateIfNeeded(); if (!string.IsNullOrEmpty(_logFile)) File.AppendAllLines(_logFile, rem, Encoding.UTF8); } catch (Exception ex) { try { Console.Error.WriteLine($"PandoraChestLogger.Worker final flush failed: {ex}"); } catch { } } + } + } + + static void RotateIfNeeded() + { + try + { + if (string.IsNullOrEmpty(_logFile)) return; + var fi = new FileInfo(_logFile); + if (fi.Exists && fi.Length > _maxSize) + { + var dest = _logFile + "." + DateTime.UtcNow.ToString("yyyyMMddHHmmss"); + try { File.Move(_logFile, dest); } catch (Exception ex) { try { Console.Error.WriteLine($"PandoraChestLogger.RotateIfNeeded File.Move failed: {ex}"); } catch { } } + } + } + catch (Exception ex) { try { Console.Error.WriteLine($"PandoraChestLogger.RotateIfNeeded failed: {ex}"); } catch { } } + } + + static string HashShort(string input) + { + if (string.IsNullOrEmpty(input)) return "(null)"; + try + { + using var sha = SHA256.Create(); + var h = sha.ComputeHash(Encoding.UTF8.GetBytes(input)); + var hex = BitConverter.ToString(h).Replace("-", "").ToLowerInvariant(); + return hex.Length >= 16 ? hex.Substring(0, 16) : hex; + } + catch (Exception ex) { try { Console.Error.WriteLine($"PandoraChestLogger.HashShort failed: {ex}"); } catch { } return "(err)"; } + } + + static void EnqueueStructured(object obj) + { + try + { + var line = JsonSerializer.Serialize(obj, _jsonOpts); + Q.Enqueue(line); + } + catch (Exception ex) + { + try { Console.Error.WriteLine($"PandoraChestLogger.EnqueueStructured failed: {ex}"); } catch { } + } + } + + public static void LogInfo(string tag, string context = null, Mobile who = null, object extra = null) + { + try + { + var entry = new + { + timestamp = DateTime.UtcNow, + level = "INFO", + tag, + context, + player = who?.Name ?? "(null)", + accountHash = SafeAccountHash(who), + extra + }; + EnqueueStructured(entry); + } + catch { } + } + + public static void LogWarn(string tag, string context = null, Mobile who = null, object extra = null) + { + try + { + var entry = new + { + timestamp = DateTime.UtcNow, + level = "WARN", + tag, + context, + player = who?.Name ?? "(null)", + accountHash = SafeAccountHash(who), + extra + }; + EnqueueStructured(entry); + } + catch { } + } + + public static void LogError(string tag, Exception ex, string context = null, Mobile who = null) + { + try + { + var entry = new + { + timestamp = DateTime.UtcNow, + level = "ERROR", + tag, + context, + player = who?.Name ?? "(null)", + accountHash = SafeAccountHash(who), + exception = ex?.ToString() + }; + EnqueueStructured(entry); + } + catch (Exception inner) + { + try { Console.Error.WriteLine($"PandoraChestLogger.LogError failed to enqueue: {inner}. Original: {tag} {context} {ex}"); } catch { } + } + } + + static string SafeAccountHash(Mobile who) + { + try { var acc = who?.Account?.Username ?? who?.Account?.ToString() ?? "(null)"; return HashShort(acc); } catch { return "(err)"; } + } + + public static void LogSpawn(Mobile who, Map map, Point3D loc, int level, double spawnChance) + { + try + { + if (!PandoraChestConfig.Current.Logging.Enabled) return; + } + catch { /* best-effort: fall through */ } + + try + { + var entry = new + { + timestamp = DateTime.UtcNow, + level = "INFO", + tag = "SPAWN", + player = who?.Name ?? "(null)", + accountHash = SafeAccountHash(who), + map = map?.ToString(), + loc = new { x = loc.X, y = loc.Y, z = loc.Z }, + levelNumber = level, + chance = spawnChance + }; + EnqueueStructured(entry); + } + catch (Exception ex) { LogError("PandoraChestLogger.LogSpawn", ex, null, who); } + } + + public static void LogContents(Mobile who, int level, IEnumerable items) + { + try + { + if (!PandoraChestConfig.Current.Logging.Enabled) return; + } + catch { } + + try + { + var entry = new + { + timestamp = DateTime.UtcNow, + level = "INFO", + tag = "CONTENTS", + player = who?.Name ?? "(null)", + accountHash = SafeAccountHash(who), + levelNumber = level, + items = items + }; + EnqueueStructured(entry); + } + catch (Exception ex) { LogError("PandoraChestLogger.LogContents", ex, null, who); } + } + + public static void LogMagicDrop(Mobile who, int level, Item it) + { + try + { + if (!PandoraChestConfig.Current.Logging.Enabled) return; + } + catch { } + + try + { + var entry = new + { + timestamp = DateTime.UtcNow, + level = "INFO", + tag = "MAGIC", + player = who?.Name ?? "(null)", + accountHash = SafeAccountHash(who), + levelNumber = level, + itemType = it?.GetType().Name, + itemName = it?.Name, + hue = it?.Hue ?? 0 + }; + EnqueueStructured(entry); + } + catch (Exception ex) { LogError("PandoraChestLogger.LogMagicDrop", ex, null, who); } + } + + public static void Shutdown(int waitMs = 3000) + { + try + { + _cts?.Cancel(); + _worker?.Wait(waitMs); + } + catch (Exception ex) { try { Console.Error.WriteLine($"PandoraChestLogger.Shutdown error: {ex}"); } catch { } } + } +} \ No newline at end of file diff --git a/ToAdd/GraveRobbing/PandoraChestSpawnGuard.cs b/ToAdd/GraveRobbing/PandoraChestSpawnGuard.cs new file mode 100644 index 000000000..82d1862ef --- /dev/null +++ b/ToAdd/GraveRobbing/PandoraChestSpawnGuard.cs @@ -0,0 +1,66 @@ +/********************************** +* Author: Unknow * +* Update/Fixed/Refactoring: Expa * +* Script: PandoraChestSpawnGuard.cs* +* Optimized for ModernUO 0.15.4.18 * +***********************************/ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Server; + +static class PandoraChestSpawnGuard +{ + static readonly ConcurrentDictionary locs = new(); + static readonly ConcurrentDictionary accs = new(); + + public static void Initialize() => Task.Run(Cleanup); + + static async Task Cleanup() + { + while (true) + { + try + { + await Task.Delay(30000).ConfigureAwait(false); + var now = DateTime.UtcNow; + + foreach (var kv in locs.ToArray()) + { + if ((now - kv.Value).TotalSeconds > 3600) locs.TryRemove(kv.Key, out _); + } + + foreach (var kv in accs.ToArray()) + { + if ((now - kv.Value).TotalSeconds > 3600) accs.TryRemove(kv.Key, out _); + } + } + catch { } + } + } + + static string LocKey(Map? m, Point3D p, int bucket = 2) + { + if (m == null) return $"(null):{p.X / bucket}:{p.Y / bucket}"; + return $"{m}:{p.X / bucket}:{p.Y / bucket}"; + } + + public static bool CanSpawnAt(Map m, Point3D p, PandoraChestConfig cfg) + { + var k = LocKey(m, p); + if (locs.TryGetValue(k, out var t) && (DateTime.UtcNow - t).TotalSeconds < cfg.Logging.LocationCooldownSeconds) return false; + return true; + } + + public static void RecordAt(Map m, Point3D p) => locs[LocKey(m, p)] = DateTime.UtcNow; + + public static bool CanSpawnForAccount(string a, PandoraChestConfig cfg) + { + if (string.IsNullOrEmpty(a)) a = "(null)"; + if (accs.TryGetValue(a, out var t) && (DateTime.UtcNow - t).TotalSeconds < cfg.Logging.AccountCooldownSeconds) return false; + return true; + } + + public static void RecordAccount(string a) { if (string.IsNullOrEmpty(a)) a = "(null)"; accs[a] = DateTime.UtcNow; } +} \ No newline at end of file diff --git a/ToAdd/LockTrapBox.cs b/ToAdd/LockTrapBox.cs new file mode 100644 index 000000000..b6e98deb2 --- /dev/null +++ b/ToAdd/LockTrapBox.cs @@ -0,0 +1,220 @@ +/*********************************** +* Author: Zik (12/21/2013) * +* Refactoring: Expa (05/08/2026) * +* Optimized for ModernUO 0.15.6.48 * +***********************************/ +using ModernUO.Serialization; +using Server; +using Server.Mobiles; +using System; +using System.Runtime.CompilerServices; + +namespace Server.Items; + +public static class TrainingConfig +{ + public const int MaxDistance = 1; + public const int AntiMacroDelayMs = 5000; + public const double MaxSkill = 100.0; + public const double MaxSkillDifficulty = 100.0; +} + +public abstract class SkillTrainingBoxBase : LockableContainer +{ + protected SkillTrainingBoxBase(int itemID) : base(itemID) + { + RequiredSkill = 0; + Movable = false; + } + + protected SkillTrainingBoxBase(Serial serial) : base(serial) + { + } + + private sealed class TrainTimeRecord + { + public DateTime LastTime { get; set; } + } + + private static readonly ConditionalWeakTable _lastTrainTime = new(); + private static readonly object _syncLock = new(); + + protected bool ValidateTraining(Mobile from) + { + if (!from.Alive) + { + from.SendMessage("You are dead."); + return false; + } + + if (!InRange(from.Location, TrainingConfig.MaxDistance)) + { + from.SendMessage("You are too far away."); + return false; + } + + return CheckAntiMacro(from); + } + + private static bool CheckAntiMacro(Mobile from) + { + lock (_syncLock) + { + var now = DateTime.UtcNow; + + if (_lastTrainTime.TryGetValue(from, out var record)) + { + if ((now - record.LastTime).TotalMilliseconds < TrainingConfig.AntiMacroDelayMs) + return false; + } + + return true; + } + } + + protected static void UpdateTrainingTime(Mobile from) + { + lock (_syncLock) + { + if (_lastTrainTime.TryGetValue(from, out var record)) + record.LastTime = DateTime.UtcNow; + else + _lastTrainTime.Add(from, new TrainTimeRecord { LastTime = DateTime.UtcNow }); + } + } +} + +[SerializationGenerator(1, false)] +[Flippable(0xE86, 0xE85)] +public sealed partial class LockpickTrainingBox : SkillTrainingBoxBase +{ + [Constructible] + public LockpickTrainingBox() : base(0x9A8) + { + Name = "Lockpick Training chest"; + Locked = true; + LockLevel = 1; + } + + public LockpickTrainingBox(Serial serial) : base(serial) + { + } + + public override void OnDoubleClick(Mobile from) + { + if (!ValidateTraining(from)) + return; + + var skill = from.Skills.Lockpicking; + + if (skill.Base >= TrainingConfig.MaxSkill) + { + from.SendMessage($"Your Lockpicking skill is already at the maximum level of {TrainingConfig.MaxSkill}."); + return; + } + + var skillValue = Math.Min(skill.Value, TrainingConfig.MaxSkillDifficulty); + RequiredSkill = (int)skillValue; + LockLevel = RequiredSkill; + from.SendMessage($"Training Chest adjusted to {skillValue:F1} Lockpicking"); + Locked = true; + UpdateTrainingTime(from); + } + + public override void LockPick(Mobile from) + { + if (!ValidateTraining(from)) + return; + + var difficulty = RequiredSkill > 0 ? RequiredSkill : 50; + + from.CheckSkill(SkillName.Lockpicking, difficulty * 0.8, difficulty * 1.2); + + Locked = true; + } + + public override void OnSingleClick(Mobile from) + { + LabelTo(from, $"Set to {RequiredSkill} Lockpicking"); + LabelTo(from, Name); + from.SendMessage("Double click the box to set it to your Lockpicking Skill."); + } + + private void Deserialize(IGenericReader reader, int version) + { + } +} + +[SerializationGenerator(1, false)] +[Flippable(0xE86, 0xE85)] +public sealed partial class RemoveTrapTrainingBox : SkillTrainingBoxBase +{ + [Constructible] + public RemoveTrapTrainingBox() : base(0x9A8) + { + Name = "Remove Trap Training chest"; + Locked = false; + LockLevel = 0; + TrapOnLockpick = true; + TrapLevel = 1; + TrapType = TrapType.ExplosionTrap; + } + + public RemoveTrapTrainingBox(Serial serial) : base(serial) + { + } + + public override void OnDoubleClick(Mobile from) + { + if (!ValidateTraining(from)) + return; + + var skill = from.Skills.RemoveTrap; + + if (skill.Base >= TrainingConfig.MaxSkill) + { + from.SendMessage($"Your Remove Trap skill is already at the maximum level of {TrainingConfig.MaxSkill}."); + return; + } + + var skillValue = Math.Min(skill.Value, TrainingConfig.MaxSkillDifficulty); + RequiredSkill = (int)skillValue; + TrapLevel = RequiredSkill; + from.SendMessage($"Training Chest adjusted to {skillValue:F1} Remove Trap"); + Locked = false; + TrapOnLockpick = true; + TrapType = TrapType.ExplosionTrap; + UpdateTrainingTime(from); + } + + public override void LockPick(Mobile from) + { + if (!ValidateTraining(from)) + return; + + var difficulty = TrapLevel > 0 ? TrapLevel : 50; + + if (from.CheckSkill(SkillName.RemoveTrap, difficulty * 0.8, difficulty * 1.2)) + { + } + else + { + ExecuteTrap(from); + } + + TrapType = TrapType.ExplosionTrap; + TrapLevel = RequiredSkill > 0 ? RequiredSkill : 1; + TrapOnLockpick = true; + } + + public override void OnSingleClick(Mobile from) + { + LabelTo(from, $"Set to {RequiredSkill} Remove Trap"); + LabelTo(from, Name); + from.SendMessage("Double click the box to set it to your Remove Trap Skill."); + } + + private void Deserialize(IGenericReader reader, int version) + { + } +} \ No newline at end of file diff --git a/ToAdd/LoginStats.cs b/ToAdd/LoginStats.cs new file mode 100644 index 000000000..5556c9527 --- /dev/null +++ b/ToAdd/LoginStats.cs @@ -0,0 +1,83 @@ +/********************************* +* Author: Anna * +* Fixed and Refactoring: Expa * +* Optimized ModernUO .Net10/C#14 * +**********************************/ +using System; +using System.Linq; +using System.Diagnostics; +using Server; +using Server.Mobiles; +using Server.Network; + +namespace Server.Misc; + +public static class LoginStats +{ + static readonly TimeSpan ReadyDelay = TimeSpan.FromSeconds(1); + static readonly TimeSpan InvulDuration = TimeSpan.FromSeconds(10); + const int HuePersonal = 0x35, HueGlobalLoginLogout = 0x3B2, HueDisconnected = 0x22; + const int MaxReadyAttempts = 10; + + public static void Initialize() + { + EventSink.Connected += OnConnected; + EventSink.BeforeDisconnected += OnBeforeDisconnected; + EventSink.Logout += OnDisconnect; + EventSink.Disconnected += OnDisconnect; + Trace.WriteLine("[LoginStats] initialized."); + } + + static void OnConnected(Mobile m) + { + if (m is PlayerMobile pm && pm.AccessLevel == AccessLevel.Player) + Timer.StartTimer(ReadyDelay, () => HandleLogin(pm, 0)); + } + + static void OnBeforeDisconnected(Mobile m) + { + if (m is PlayerMobile pm) + try { pm.NetState?.Disconnect("Connection lost"); } catch { } + } + + static void OnDisconnect(Mobile m) + { + if (m is PlayerMobile pm) + { + World.Broadcast(HueDisconnected, false, $"{pm.Name} has disconnected."); + Trace.WriteLine($"[LoginStats] Disconnected: {pm.Name}"); + } + } + + static void HandleLogin(PlayerMobile pm, int attempt) + { + if (pm == null || pm.Deleted) return; + if ((pm.NetState == null || pm.SessionStart == default) && attempt < MaxReadyAttempts) + { + Timer.StartTimer(ReadyDelay, () => HandleLogin(pm, attempt + 1)); return; + } + if (pm.NetState == null || pm.SessionStart == default) return; + + var gt = pm.GameTime; + if (gt.TotalDays > 10000) gt = TimeSpan.Zero; + pm.SendMessage(HuePersonal, $"Welcome back {pm.Name}! Total play time: {FormatPlaytime(gt)}"); + + Timer.StartTimer(TimeSpan.FromMilliseconds(500), () => + { + if (pm == null || pm.Deleted) return; + var text = $"{pm.Name} has logged in."; + foreach (var ns in NetState.Instances.ToArray()) + { + var mob = ns?.Mobile; + if (mob == null || mob == pm) continue; + try { mob.SendMessage(HueGlobalLoginLogout, text); } catch { } + } + }); + + pm.Hidden = true; pm.Blessed = true; + Timer.StartTimer(InvulDuration, () => { if (pm != null && !pm.Deleted) { pm.Hidden = false; pm.Blessed = false; } }); + Trace.WriteLine($"[LoginStats] Login: {pm.Name}"); + } + + static string FormatPlaytime(TimeSpan ts) => $"{ts.Days}d {ts.Hours}h {ts.Minutes}m"; +} \ No newline at end of file diff --git a/ToAdd/PirateSystem.zip b/ToAdd/PirateSystem.zip new file mode 100644 index 0000000000000000000000000000000000000000..95c3fd3683a30e3e45de47ae75502dc13143c3f5 GIT binary patch literal 32687 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 _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 new file mode 100644 index 0000000000000000000000000000000000000000..b594e1ec2a0667d1beba8db08099688d4935799f GIT binary patch literal 78442 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 fi| 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 new file mode 100644 index 000000000..a2b26ff4e --- /dev/null +++ b/ToAdd/Traps/CurseItemsTrap.cs @@ -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; + } +} \ No newline at end of file diff --git a/ToAdd/Traps/LandMobTrap.cs b/ToAdd/Traps/LandMobTrap.cs new file mode 100644 index 000000000..dc59f99d2 --- /dev/null +++ b/ToAdd/Traps/LandMobTrap.cs @@ -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(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 new file mode 100644 index 000000000..3d90ec3cf --- /dev/null +++ b/ToAdd/Traps/MagnetItemsTrap.cs @@ -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 _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 new file mode 100644 index 000000000..166dd9af0 --- /dev/null +++ b/ToAdd/Traps/SealedItemsTrap.cs @@ -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("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