mirror of
https://github.com/ServUO/ServUO
synced 2026-08-11 20:23:04 -04:00
- Shop item prices now reset at startup, per EA - Nether Cyclone spell damage fixed - magic reflect spell now EA like - Vis list now persists through restarts. - Fixed an exploit with ChickenStatue and CowStatue.cs - Fishing poles now get luck accounted - Restless soul is now grey - Added proper CUB point value to tmaps - Doom spawns are now seperated and EA accurate. Co-authored-by: TrueUO <knight.daniel.r@gmail.com>
663 lines
18 KiB
C#
663 lines
18 KiB
C#
using Server.Mobiles;
|
|
using Server.Network;
|
|
using Server.Spells;
|
|
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Server.Items
|
|
{
|
|
public interface IDamageableItem : IDamageable
|
|
{
|
|
bool CanDamage { get; }
|
|
bool CheckHit(Mobile attacker);
|
|
void OnHarmfulSpell(Mobile attacker);
|
|
}
|
|
|
|
public class DamageableItem : Item, IDamageableItem
|
|
{
|
|
public enum ItemLevel
|
|
{
|
|
NotSet,
|
|
VeryEasy,
|
|
Easy,
|
|
Average,
|
|
Hard,
|
|
VeryHard,
|
|
Insane
|
|
}
|
|
|
|
private int m_Hits;
|
|
private int m_HitsMax;
|
|
private int m_StartID;
|
|
private int m_DestroyedID;
|
|
private int m_HalfHitsID;
|
|
private ItemLevel m_ItemLevel;
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public ItemLevel Level
|
|
{
|
|
get
|
|
{
|
|
return m_ItemLevel;
|
|
}
|
|
set
|
|
{
|
|
m_ItemLevel = value;
|
|
|
|
double bonus = ((int)m_ItemLevel * 100.0) * ((int)m_ItemLevel * 5);
|
|
|
|
HitsMax = ((int)(100 + bonus));
|
|
Hits = ((int)(100 + bonus));
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int IDStart
|
|
{
|
|
get
|
|
{
|
|
return m_StartID;
|
|
}
|
|
set
|
|
{
|
|
if (value < 0)
|
|
m_StartID = 0;
|
|
else if (value > int.MaxValue)
|
|
m_StartID = int.MaxValue;
|
|
else
|
|
m_StartID = value;
|
|
|
|
if (m_Hits >= (m_HitsMax * IDChange))
|
|
{
|
|
if (ItemID != m_StartID)
|
|
ItemID = m_StartID;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int IDHalfHits
|
|
{
|
|
get
|
|
{
|
|
return m_HalfHitsID;
|
|
}
|
|
set
|
|
{
|
|
if (value < 0)
|
|
m_HalfHitsID = 0;
|
|
else if (value > int.MaxValue)
|
|
m_HalfHitsID = int.MaxValue;
|
|
else
|
|
m_HalfHitsID = value;
|
|
|
|
if (m_Hits < (m_HitsMax * IDChange))
|
|
{
|
|
if (ItemID != m_HalfHitsID)
|
|
ItemID = m_HalfHitsID;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int IDDestroyed
|
|
{
|
|
get
|
|
{
|
|
return m_DestroyedID;
|
|
}
|
|
set
|
|
{
|
|
if (value < 0 || value > int.MaxValue)
|
|
m_DestroyedID = -1;
|
|
else
|
|
m_DestroyedID = value;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int Hits
|
|
{
|
|
get
|
|
{
|
|
return m_Hits;
|
|
}
|
|
set
|
|
{
|
|
if (value > HitsMax)
|
|
{
|
|
value = HitsMax;
|
|
}
|
|
|
|
if (m_Hits != value)
|
|
{
|
|
int oldValue = m_Hits;
|
|
m_Hits = value;
|
|
UpdateDelta();
|
|
OnHitsChange(oldValue);
|
|
}
|
|
|
|
int id = ItemID;
|
|
|
|
if (m_Hits >= (m_HitsMax * IDChange) && id != m_StartID)
|
|
{
|
|
ItemID = m_StartID;
|
|
OnIDChange(id);
|
|
}
|
|
else if (m_Hits <= (m_HitsMax * IDChange) && id == m_StartID)
|
|
{
|
|
ItemID = m_HalfHitsID;
|
|
OnIDChange(id);
|
|
}
|
|
|
|
if (m_Hits < 0)
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int HitsMax
|
|
{
|
|
get
|
|
{
|
|
return m_HitsMax;
|
|
}
|
|
set
|
|
{
|
|
if (value > int.MaxValue)
|
|
m_HitsMax = int.MaxValue;
|
|
else
|
|
m_HitsMax = value;
|
|
|
|
if (Hits > m_HitsMax)
|
|
Hits = m_HitsMax;
|
|
}
|
|
}
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public bool Destroyed { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistBasePhys { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistBaseFire { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistBaseCold { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistBasePoison { get; set; }
|
|
|
|
[CommandProperty(AccessLevel.GameMaster)]
|
|
public int ResistBaseEnergy { get; set; }
|
|
|
|
public Dictionary<Mobile, int> DamageStore { get; set; }
|
|
|
|
public virtual int HitEffect => -1;
|
|
public virtual int DestroySound => 0x3B3;
|
|
public virtual double IDChange => 0.5;
|
|
public virtual bool DeleteOnDestroy => true;
|
|
public virtual bool Alive => !Destroyed;
|
|
public virtual bool CanDamage => true;
|
|
|
|
public override int PhysicalResistance => ResistBasePhys;
|
|
public override int FireResistance => ResistBaseFire;
|
|
public override int ColdResistance => ResistBaseCold;
|
|
public override int PoisonResistance => ResistBasePoison;
|
|
public override int EnergyResistance => ResistBaseEnergy;
|
|
|
|
public override bool ForceShowProperties => false;
|
|
|
|
[Constructable]
|
|
public DamageableItem(int startID)
|
|
: this(startID, startID, -1)
|
|
{
|
|
}
|
|
|
|
[Constructable]
|
|
public DamageableItem(int startID, int halfID)
|
|
: this(startID, halfID, -1)
|
|
{
|
|
}
|
|
|
|
[Constructable]
|
|
public DamageableItem(int startID, int halfID, int destroyID = -1)
|
|
: base(startID)
|
|
{
|
|
Hue = 0;
|
|
Movable = false;
|
|
|
|
Level = ItemLevel.NotSet;
|
|
|
|
IDStart = startID;
|
|
IDHalfHits = halfID;
|
|
IDDestroyed = destroyID;
|
|
}
|
|
|
|
public override void OnDoubleClick(Mobile m)
|
|
{
|
|
if (m.Warmode)
|
|
m.Attack(this);
|
|
}
|
|
|
|
public virtual bool CheckHit(Mobile attacker)
|
|
{
|
|
return true; // Always hits
|
|
}
|
|
|
|
public virtual void OnHarmfulSpell(Mobile attacker)
|
|
{
|
|
}
|
|
|
|
public virtual bool CheckReflect(Spell spell, IDamageable caster)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public override void OnStatsQuery(Mobile from)
|
|
{
|
|
if (from.Map == Map && Utility.InUpdateRange(from, this) && from.CanSee(this))
|
|
{
|
|
from.Send(new MobileStatusCompact(false, this));
|
|
}
|
|
}
|
|
|
|
public virtual void UpdateDelta()
|
|
{
|
|
IPooledEnumerable<NetState> eable = Map.GetClientsInRange(Location);
|
|
Mobile beholder = null;
|
|
|
|
Packet status = Packet.Acquire(new MobileHitsN(this));
|
|
|
|
foreach (NetState ns in eable)
|
|
{
|
|
beholder = ns.Mobile;
|
|
|
|
if (beholder != null && beholder.CanSee(this))
|
|
{
|
|
ns.Send(status);
|
|
}
|
|
}
|
|
|
|
Packet.Release(status);
|
|
eable.Free();
|
|
}
|
|
|
|
public virtual void OnHitsChange(int oldhits)
|
|
{
|
|
}
|
|
|
|
public virtual bool OnBeforeDestroyed()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public virtual void OnAfterDestroyed()
|
|
{
|
|
}
|
|
|
|
public virtual int Damage(int amount, Mobile from)
|
|
{
|
|
if (!CanDamage && from.Combatant == this)
|
|
{
|
|
from.Combatant = null;
|
|
return 0;
|
|
}
|
|
|
|
Hits -= amount;
|
|
|
|
if (amount > 0)
|
|
RegisterDamage(from, amount);
|
|
|
|
if (HitEffect > 0)
|
|
Effects.SendLocationEffect(Location, Map, HitEffect, 10, 5);
|
|
|
|
SendDamagePacket(from, amount);
|
|
|
|
OnDamage(amount, from, Hits < 0);
|
|
|
|
return amount;
|
|
}
|
|
|
|
public virtual void SendDamagePacket(Mobile from, int amount)
|
|
{
|
|
NetState theirState = (from == null ? null : from.NetState);
|
|
|
|
if (theirState == null && from != null)
|
|
{
|
|
Mobile master = from.GetDamageMaster(null);
|
|
|
|
if (master != null)
|
|
{
|
|
theirState = master.NetState;
|
|
}
|
|
}
|
|
|
|
if (amount > 0 && theirState != null)
|
|
{
|
|
theirState.Send(new DamagePacket(this, amount));
|
|
}
|
|
}
|
|
|
|
public void RegisterDamage(Mobile m, int damage)
|
|
{
|
|
if (m == null)
|
|
return;
|
|
|
|
if (DamageStore == null)
|
|
DamageStore = new Dictionary<Mobile, int>();
|
|
|
|
if (m is BaseCreature && ((BaseCreature)m).GetMaster() is PlayerMobile)
|
|
m = ((BaseCreature)m).GetMaster();
|
|
|
|
if (!DamageStore.ContainsKey(m))
|
|
DamageStore[m] = 0;
|
|
|
|
DamageStore[m] += damage;
|
|
}
|
|
|
|
public List<Mobile> GetLootingRights()
|
|
{
|
|
if (DamageStore == null)
|
|
return null;
|
|
|
|
return DamageStore.Keys.Where(m => DamageStore[m] > 0 && DamageStore[m] >= HitsMax / 16).ToList();
|
|
}
|
|
|
|
public virtual void OnDamage(int amount, Mobile from, bool willkill)
|
|
{
|
|
}
|
|
|
|
public bool Destroy()
|
|
{
|
|
if (this == null || Deleted || Destroyed)
|
|
return false;
|
|
|
|
Effects.PlaySound(Location, Map, DestroySound);
|
|
|
|
if (OnBeforeDestroyed())
|
|
{
|
|
if (DeleteOnDestroy)
|
|
{
|
|
Delete();
|
|
}
|
|
else if (m_DestroyedID >= 0)
|
|
{
|
|
ItemID = m_DestroyedID;
|
|
|
|
if (Spawner != null)
|
|
Spawner.Remove(this);
|
|
}
|
|
|
|
Destroyed = true;
|
|
OnAfterDestroyed();
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override void OnAfterDelete()
|
|
{
|
|
base.OnAfterDelete();
|
|
|
|
if (DamageStore != null)
|
|
DamageStore.Clear();
|
|
}
|
|
|
|
public virtual void OnIDChange(int oldID)
|
|
{
|
|
}
|
|
|
|
#region Effects/Sounds & Particles
|
|
public void PlaySound(int soundID)
|
|
{
|
|
if (soundID == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Map != null)
|
|
{
|
|
Packet p = Packet.Acquire(new PlaySound(soundID, this));
|
|
|
|
IPooledEnumerable<NetState> eable = Map.GetClientsInRange(Location);
|
|
|
|
foreach (NetState state in eable)
|
|
{
|
|
if (state.Mobile.CanSee(this))
|
|
{
|
|
state.Send(p);
|
|
}
|
|
}
|
|
|
|
Packet.Release(p);
|
|
|
|
eable.Free();
|
|
}
|
|
}
|
|
|
|
public void MovingEffect(
|
|
IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes, int hue, int renderMode)
|
|
{
|
|
Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes, hue, renderMode);
|
|
}
|
|
|
|
public void MovingEffect(IEntity to, int itemID, int speed, int duration, bool fixedDirection, bool explodes)
|
|
{
|
|
Effects.SendMovingEffect(this, to, itemID, speed, duration, fixedDirection, explodes, 0, 0);
|
|
}
|
|
|
|
public void MovingParticles(
|
|
IEntity to,
|
|
int itemID,
|
|
int speed,
|
|
int duration,
|
|
bool fixedDirection,
|
|
bool explodes,
|
|
int hue,
|
|
int renderMode,
|
|
int effect,
|
|
int explodeEffect,
|
|
int explodeSound,
|
|
EffectLayer layer,
|
|
int unknown)
|
|
{
|
|
Effects.SendMovingParticles(
|
|
this,
|
|
to,
|
|
itemID,
|
|
speed,
|
|
duration,
|
|
fixedDirection,
|
|
explodes,
|
|
hue,
|
|
renderMode,
|
|
effect,
|
|
explodeEffect,
|
|
explodeSound,
|
|
layer,
|
|
unknown);
|
|
}
|
|
|
|
public void MovingParticles(
|
|
IEntity to,
|
|
int itemID,
|
|
int speed,
|
|
int duration,
|
|
bool fixedDirection,
|
|
bool explodes,
|
|
int hue,
|
|
int renderMode,
|
|
int effect,
|
|
int explodeEffect,
|
|
int explodeSound,
|
|
int unknown)
|
|
{
|
|
Effects.SendMovingParticles(
|
|
this,
|
|
to,
|
|
itemID,
|
|
speed,
|
|
duration,
|
|
fixedDirection,
|
|
explodes,
|
|
hue,
|
|
renderMode,
|
|
effect,
|
|
explodeEffect,
|
|
explodeSound,
|
|
(EffectLayer)255,
|
|
unknown);
|
|
}
|
|
|
|
public void MovingParticles(
|
|
IEntity to,
|
|
int itemID,
|
|
int speed,
|
|
int duration,
|
|
bool fixedDirection,
|
|
bool explodes,
|
|
int effect,
|
|
int explodeEffect,
|
|
int explodeSound,
|
|
int unknown)
|
|
{
|
|
Effects.SendMovingParticles(
|
|
this, to, itemID, speed, duration, fixedDirection, explodes, effect, explodeEffect, explodeSound, unknown);
|
|
}
|
|
|
|
public void MovingParticles(
|
|
IEntity to,
|
|
int itemID,
|
|
int speed,
|
|
int duration,
|
|
bool fixedDirection,
|
|
bool explodes,
|
|
int effect,
|
|
int explodeEffect,
|
|
int explodeSound)
|
|
{
|
|
Effects.SendMovingParticles(
|
|
this, to, itemID, speed, duration, fixedDirection, explodes, 0, 0, effect, explodeEffect, explodeSound, 0);
|
|
}
|
|
|
|
public void FixedEffect(int itemID, int speed, int duration, int hue, int renderMode)
|
|
{
|
|
Effects.SendTargetEffect(this, itemID, speed, duration, hue, renderMode);
|
|
}
|
|
|
|
public void FixedEffect(int itemID, int speed, int duration)
|
|
{
|
|
Effects.SendTargetEffect(this, itemID, speed, duration, 0, 0);
|
|
}
|
|
|
|
public void FixedParticles(
|
|
int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer, int unknown)
|
|
{
|
|
Effects.SendLocationParticles(this, itemID, speed, duration, hue, renderMode, effect, unknown);
|
|
}
|
|
|
|
public void FixedParticles(
|
|
int itemID, int speed, int duration, int effect, int hue, int renderMode, EffectLayer layer)
|
|
{
|
|
Effects.SendLocationParticles(this, itemID, speed, duration, hue, renderMode, effect, 0);
|
|
}
|
|
|
|
public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer, int unknown)
|
|
{
|
|
Effects.SendLocationParticles(this, itemID, speed, duration, 0, 0, effect, unknown);
|
|
}
|
|
|
|
public void FixedParticles(int itemID, int speed, int duration, int effect, EffectLayer layer)
|
|
{
|
|
Effects.SendLocationParticles(this, itemID, speed, duration, 0, 0, effect, 0);
|
|
}
|
|
|
|
public void BoltEffect(int hue)
|
|
{
|
|
Effects.SendBoltEffect(this, true, hue);
|
|
}
|
|
#endregion
|
|
|
|
public DamageableItem(Serial serial)
|
|
: base(serial)
|
|
{
|
|
}
|
|
|
|
public override void Serialize(GenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
|
|
writer.Write(0); // version
|
|
|
|
writer.Write(m_StartID);
|
|
writer.Write(m_HalfHitsID);
|
|
writer.Write(m_DestroyedID);
|
|
writer.Write((int)m_ItemLevel);
|
|
writer.Write(m_Hits);
|
|
writer.Write(m_HitsMax);
|
|
writer.Write(Destroyed);
|
|
|
|
writer.Write(ResistBasePhys);
|
|
writer.Write(ResistBaseFire);
|
|
writer.Write(ResistBaseCold);
|
|
writer.Write(ResistBasePoison);
|
|
writer.Write(ResistBaseEnergy);
|
|
}
|
|
|
|
public override void Deserialize(GenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
|
|
int version = reader.ReadInt();
|
|
|
|
m_StartID = reader.ReadInt();
|
|
m_HalfHitsID = reader.ReadInt();
|
|
m_DestroyedID = reader.ReadInt();
|
|
m_ItemLevel = (ItemLevel)reader.ReadInt();
|
|
m_Hits = reader.ReadInt();
|
|
m_HitsMax = reader.ReadInt();
|
|
Destroyed = reader.ReadBool();
|
|
|
|
ResistBasePhys = reader.ReadInt();
|
|
ResistBaseFire = reader.ReadInt();
|
|
ResistBaseCold = reader.ReadInt();
|
|
ResistBasePoison = reader.ReadInt();
|
|
ResistBaseEnergy = reader.ReadInt();
|
|
}
|
|
}
|
|
|
|
public class TestDamageableItem : DamageableItem
|
|
{
|
|
[Constructable]
|
|
public TestDamageableItem(int itemid)
|
|
: base(itemid, itemid)
|
|
{
|
|
Name = "Test Damageable Item";
|
|
}
|
|
|
|
public TestDamageableItem(Serial serial)
|
|
: base(serial)
|
|
{
|
|
}
|
|
|
|
public override void Serialize(GenericWriter writer)
|
|
{
|
|
base.Serialize(writer);
|
|
writer.Write(0); // version
|
|
}
|
|
|
|
public override void Deserialize(GenericReader reader)
|
|
{
|
|
base.Deserialize(reader);
|
|
int version = reader.ReadInt();
|
|
}
|
|
}
|
|
}
|