ModernUO/ToAdd/Traps/MagnetItemsTrap.cs

215 lines
No EOL
6.7 KiB
C#

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