301 lines
No EOL
8.7 KiB
C#
301 lines
No EOL
8.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;
|
|
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;
|
|
}
|
|
} |