using System; using System.Collections; using System.Collections.Generic; using Server; using Server.ContextMenus; using Server.Engines.PartySystem; using Server.Guilds; using Server.Misc; using Server.Mobiles; using Server.Network; namespace Server.Items { public interface IDevourer { bool Devour( Corpse corpse ); } [Flags] public enum CorpseFlag { None = 0x00000000, /// /// Has this corpse been carved? /// Carved = 0x00000001, /// /// If true, this corpse will not turn into bones /// NoBones = 0x00000002, /// /// If true, the corpse has turned into bones /// IsBones = 0x00000004, /// /// Has this corpse yet been visited by a taxidermist? /// VisitedByTaxidermist = 0x00000008, /// /// Was the owner criminal when he died? /// Criminal = 0x00000010, /// /// Has this corpse been animated? /// Animated = 0x00000020, } public class Corpse : Container, ICarvable { private Mobile m_Owner; // Whos corpse is this? private Mobile m_Killer; // Who killed the owner? private CorpseFlag m_Flags; // @see CorpseFlag private List m_Looters; // Who's looted this corpse? private List m_EquipItems; // List of items equiped when the owner died. Ingame, these items display /on/ the corpse, not just inside private List m_Aggressors; // Anyone from this list will be able to loot this corpse; we attacked them, or they attacked us when we were freely attackable private string m_CorpseName; // Value of the CorpseNameAttribute attached to the owner when he died -or- null if the owner had no CorpseNameAttribute; use "the remains of ~name~" private IDevourer m_Devourer; // The creature that devoured this corpse // For notoriety: private AccessLevel m_AccessLevel; // Which AccessLevel the owner had when he died private Guild m_Guild; // Which Guild the owner was in when he died private int m_Kills; // How many kills the owner had when he died private DateTime m_TimeOfDeath; // What time was this corpse created? private HairInfo m_Hair; // This contains the hair of the owner private FacialHairInfo m_FacialHair; // This contains the facial hair of the owner public static readonly TimeSpan MonsterLootRightSacrifice = TimeSpan.FromMinutes( 2.0 ); public override bool IsChildVisibleTo( Mobile m, Item child ) { if ( !m.Player || m.AccessLevel > AccessLevel.Player ) //Staff and creatures not subject to instancing. return true; return true; } public override bool IsDecoContainer { get{ return false; } } [CommandProperty( AccessLevel.GameMaster )] public DateTime TimeOfDeath { get{ return m_TimeOfDeath; } set{ m_TimeOfDeath = value; } } public override bool DisplayWeight { get { return false; } } public HairInfo Hair { get { return m_Hair; } } public FacialHairInfo FacialHair { get { return m_FacialHair; } } [CommandProperty( AccessLevel.GameMaster )] public bool IsBones { get { return GetFlag( CorpseFlag.IsBones ); } } [CommandProperty( AccessLevel.GameMaster )] public bool Devoured { get { return (m_Devourer != null); } } [CommandProperty( AccessLevel.GameMaster )] public bool Carved { get{ return GetFlag( CorpseFlag.Carved ); } set { SetFlag( CorpseFlag.Carved, value ); } } [CommandProperty( AccessLevel.GameMaster )] public bool VisitedByTaxidermist { get { return GetFlag( CorpseFlag.VisitedByTaxidermist ); } set { SetFlag( CorpseFlag.VisitedByTaxidermist, value ); } } [CommandProperty( AccessLevel.GameMaster )] public bool Animated { get { return GetFlag( CorpseFlag.Animated ); } set { SetFlag( CorpseFlag.Animated, value ); } } [CommandProperty( AccessLevel.GameMaster )] public AccessLevel AccessLevel { get{ return m_AccessLevel; } } public List Aggressors { get{ return m_Aggressors; } } public List Looters { get{ return m_Looters; } } [CommandProperty( AccessLevel.GameMaster )] public Mobile Killer { get{ return m_Killer; } } public List EquipItems { get{ return m_EquipItems; } } public Guild Guild { get{ return m_Guild; } } [CommandProperty( AccessLevel.GameMaster )] public int Kills { get{ return m_Kills; } set{ m_Kills = value; } } [CommandProperty( AccessLevel.GameMaster )] public bool Criminal { get { return GetFlag( CorpseFlag.Criminal ); } set { SetFlag( CorpseFlag.Criminal, value ); } } [CommandProperty( AccessLevel.GameMaster )] public Mobile Owner { get{ return m_Owner; } } public void TurnToBones() { if ( Deleted ) return; ProcessDelta(); SendRemovePacket(); ItemID = Utility.Random( 0xECA, 9 ); // bone graphic Hue = 0; ProcessDelta(); SetFlag( CorpseFlag.NoBones, true ); SetFlag( CorpseFlag.IsBones, true ); BeginDecay( m_BoneDecayTime ); } private static TimeSpan m_DefaultDecayTime = TimeSpan.FromMinutes( 7.0 ); private static TimeSpan m_BoneDecayTime = TimeSpan.FromMinutes( 7.0 ); private Timer m_DecayTimer; private DateTime m_DecayTime; public void BeginDecay( TimeSpan delay ) { if ( m_DecayTimer != null ) m_DecayTimer.Stop(); m_DecayTime = DateTime.Now + delay; m_DecayTimer = new InternalTimer( this, delay ); m_DecayTimer.Start(); } public override void OnAfterDelete() { if ( m_DecayTimer != null ) m_DecayTimer.Stop(); m_DecayTimer = null; } private class InternalTimer : Timer { private Corpse m_Corpse; public InternalTimer( Corpse c, TimeSpan delay ) : base( delay ) { m_Corpse = c; Priority = TimerPriority.FiveSeconds; } protected override void OnTick() { if ( !m_Corpse.GetFlag( CorpseFlag.NoBones ) ) m_Corpse.TurnToBones(); else m_Corpse.Delete(); } } public static string GetCorpseName( Mobile m ) { Type t = m.GetType(); object[] attrs = t.GetCustomAttributes( typeof( CorpseNameAttribute ), true ); if ( attrs != null && attrs.Length > 0 ) { CorpseNameAttribute attr = attrs[0] as CorpseNameAttribute; if ( attr != null ) return attr.Name; } return null; } public static void Initialize() { Mobile.CreateCorpseHandler += new CreateCorpseHandler( Mobile_CreateCorpseHandler ); } public static Container Mobile_CreateCorpseHandler( Mobile owner, HairInfo hair, FacialHairInfo facialhair, List initialContent, List equipItems ) { bool shouldFillCorpse = true; //if ( owner is BaseCreature ) // shouldFillCorpse = !((BaseCreature)owner).IsBonded; Corpse c = new Corpse( owner, hair, facialhair, shouldFillCorpse ? equipItems : new List() ); owner.Corpse = c; if ( shouldFillCorpse ) { for ( int i = 0; i < initialContent.Count; ++i ) { Item item = initialContent[i]; c.DropItem( item ); } } else { c.Carved = true; // TODO: Is it needed? } Point3D loc = owner.Location; Map map = owner.Map; if ( map == null || map == Map.Internal ) { loc = owner.LogoutLocation; map = owner.LogoutMap; } c.MoveToWorld( loc, map ); return c; } public override bool IsPublicContainer{ get{ return true; } } public Corpse( Mobile owner, List equipItems ) : this( owner, null, null, equipItems ) { } public Corpse( Mobile owner, HairInfo hair, FacialHairInfo facialhair, List equipItems ) : base( 0x2006 ) { // To supress console warnings, stackable must be true Stackable = true; Amount = owner.Body; // protocol defines that for itemid 0x2006, amount=body Stackable = false; Movable = false; Hue = owner.Hue; Direction = owner.Direction; Name = owner.Name; m_Owner = owner; m_CorpseName = GetCorpseName( owner ); m_TimeOfDeath = DateTime.Now; m_AccessLevel = owner.AccessLevel; m_Guild = owner.Guild as Guild; m_Kills = owner.Kills; SetFlag( CorpseFlag.Criminal, owner.Criminal ); m_Hair = hair; m_FacialHair = facialhair; // This corpse does not turn to bones if: the owner is not a player SetFlag( CorpseFlag.NoBones, !owner.Player ); m_Looters = new List(); m_EquipItems = equipItems; m_Aggressors = new List( owner.Aggressors.Count + owner.Aggressed.Count ); //bool addToAggressors = !( owner is BaseCreature ); bool isBaseCreature = (owner is BaseCreature); TimeSpan lastTime = TimeSpan.MaxValue; for ( int i = 0; i < owner.Aggressors.Count; ++i ) { AggressorInfo info = owner.Aggressors[i]; if ( (DateTime.Now - info.LastCombatTime) < lastTime ) { m_Killer = info.Attacker; lastTime = (DateTime.Now - info.LastCombatTime); } if ( !isBaseCreature && !info.CriminalAggression ) m_Aggressors.Add( info.Attacker ); } for ( int i = 0; i < owner.Aggressed.Count; ++i ) { AggressorInfo info = owner.Aggressed[i]; if ( (DateTime.Now - info.LastCombatTime) < lastTime ) { m_Killer = info.Defender; lastTime = (DateTime.Now - info.LastCombatTime); } if ( !isBaseCreature ) m_Aggressors.Add( info.Defender ); } if ( isBaseCreature ) { BaseCreature bc = (BaseCreature)owner; Mobile master = bc.GetMaster(); if( master != null ) m_Aggressors.Add( master ); List rights = BaseCreature.GetLootingRights( bc.DamageEntries, bc.HitsMax ); for ( int i = 0; i < rights.Count; ++i ) { DamageStore ds = rights[i]; if ( ds.m_HasRight ) m_Aggressors.Add( ds.m_Mobile ); } } BeginDecay( m_DefaultDecayTime ); DevourCorpse(); } public Corpse( Serial serial ) : base( serial ) { } protected bool GetFlag( CorpseFlag flag ) { return ((m_Flags & flag) != 0); } protected void SetFlag( CorpseFlag flag, bool on ) { m_Flags = (on ? m_Flags | flag : m_Flags & ~flag); } public override void Serialize( GenericWriter writer ) { base.Serialize( writer ); writer.Write( (int) 11 ); // version writer.Write( (int)m_Flags ); writer.WriteDeltaTime( m_TimeOfDeath ); List> list = ( m_RestoreTable == null ? null : new List>( m_RestoreTable ) ); int count = ( list == null ? 0 : list.Count ); writer.Write( count ); for ( int i = 0; i < count; ++i ) { KeyValuePair kvp = list[i]; Item item = kvp.Key; Point3D loc = kvp.Value; writer.Write( item ); if ( item.Location == loc ) { writer.Write( false ); } else { writer.Write( true ); writer.Write( loc ); } } writer.Write( m_DecayTimer != null ); if ( m_DecayTimer != null ) writer.WriteDeltaTime( m_DecayTime ); writer.Write( m_Looters ); writer.Write( m_Killer ); writer.Write( m_Aggressors ); writer.Write( m_Owner ); writer.Write( (string) m_CorpseName ); writer.Write( (int) m_AccessLevel ); writer.Write( (Guild) m_Guild ); writer.Write( (int) m_Kills ); writer.Write( m_EquipItems ); } public override void Deserialize( GenericReader reader ) { base.Deserialize( reader ); int version = reader.ReadInt(); switch ( version ) { case 11: { // Version 11, we move all bools to a CorpseFlag m_Flags = (CorpseFlag)reader.ReadInt(); m_TimeOfDeath = reader.ReadDeltaTime(); int count = reader.ReadInt(); for( int i = 0; i < count; ++i ) { Item item = reader.ReadItem(); if( reader.ReadBool() ) SetRestoreInfo( item, reader.ReadPoint3D() ); else if( item != null ) SetRestoreInfo( item, item.Location ); } if( reader.ReadBool() ) BeginDecay( reader.ReadDeltaTime() - DateTime.Now ); m_Looters = reader.ReadStrongMobileList(); m_Killer = reader.ReadMobile(); m_Aggressors = reader.ReadStrongMobileList(); m_Owner = reader.ReadMobile(); m_CorpseName = reader.ReadString(); m_AccessLevel = (AccessLevel)reader.ReadInt(); reader.ReadInt(); // guild reserve m_Kills = reader.ReadInt(); m_EquipItems = reader.ReadStrongItemList(); break; } case 10: { m_TimeOfDeath = reader.ReadDeltaTime(); goto case 9; } case 9: { int count = reader.ReadInt(); for ( int i = 0; i < count; ++i ) { Item item = reader.ReadItem(); if ( reader.ReadBool() ) SetRestoreInfo( item, reader.ReadPoint3D() ); else if ( item != null ) SetRestoreInfo( item, item.Location ); } goto case 8; } case 8: { SetFlag( CorpseFlag.VisitedByTaxidermist, reader.ReadBool() ); goto case 7; } case 7: { if ( reader.ReadBool() ) BeginDecay( reader.ReadDeltaTime() - DateTime.Now ); goto case 6; } case 6: { m_Looters = reader.ReadStrongMobileList(); m_Killer = reader.ReadMobile(); goto case 5; } case 5: { SetFlag( CorpseFlag.Carved, reader.ReadBool() ); goto case 4; } case 4: { m_Aggressors = reader.ReadStrongMobileList(); goto case 3; } case 3: { m_Owner = reader.ReadMobile(); goto case 2; } case 2: { SetFlag( CorpseFlag.NoBones, reader.ReadBool() ); goto case 1; } case 1: { m_CorpseName = reader.ReadString(); goto case 0; } case 0: { if ( version < 10 ) m_TimeOfDeath = DateTime.Now; if ( version < 7 ) BeginDecay( m_DefaultDecayTime ); if ( version < 6 ) m_Looters = new List(); if ( version < 4 ) m_Aggressors = new List(); m_AccessLevel = (AccessLevel)reader.ReadInt(); reader.ReadInt(); // guild reserve m_Kills = reader.ReadInt(); SetFlag( CorpseFlag.Criminal, reader.ReadBool() ); m_EquipItems = reader.ReadStrongItemList(); break; } } } public bool DevourCorpse() { if( Devoured || Deleted || m_Killer == null || m_Killer.Deleted || !m_Killer.Alive || !(m_Killer is IDevourer) || m_Owner == null || m_Owner.Deleted ) return false; m_Devourer = (IDevourer)m_Killer; // Set the devourer the killer return m_Devourer.Devour( this ); // Devour the corpse if it hasn't } public override void SendInfoTo( NetState state, bool sendOplPacket ) { base.SendInfoTo( state, sendOplPacket ); if ( ItemID == 0x2006 ) { state.Send( new CorpseContent( state.Mobile, this ) ); state.Send( new CorpseEquip( state.Mobile, this ) ); } } public bool IsCriminalAction( Mobile from ) { if ( from == m_Owner || from.AccessLevel >= AccessLevel.GameMaster ) return false; Party p = Party.Get( m_Owner ); if ( p != null && p.Contains( from ) ) { PartyMemberInfo pmi = p[m_Owner]; if ( pmi != null && pmi.CanLoot ) return false; } return ( NotorietyHandlers.CorpseNotoriety( from, this ) == Notoriety.Innocent ); } public override bool CheckItemUse( Mobile from, Item item ) { if ( !base.CheckItemUse( from, item ) ) return false; if ( item != this ) return CanLoot( from, item ); return true; } public override bool CheckLift( Mobile from, Item item, ref LRReason reject ) { if ( !base.CheckLift( from, item, ref reject ) ) return false; return CanLoot( from,item ); } public override void OnItemUsed( Mobile from, Item item ) { base.OnItemUsed( from, item ); if ( item is Food ) from.RevealingAction(); if ( item != this && IsCriminalAction( from ) ) from.CriminalAction( true ); if ( !m_Looters.Contains( from ) ) m_Looters.Add( from ); } public override void OnItemLifted( Mobile from, Item item ) { base.OnItemLifted( from, item ); if ( item != this && from != m_Owner ) from.RevealingAction(); if ( item != this && IsCriminalAction( from ) ) from.CriminalAction( true ); if ( !m_Looters.Contains( from ) ) m_Looters.Add( from ); } private class OpenCorpseEntry : ContextMenuEntry { public OpenCorpseEntry() : base( 6215, 2 ) { } public override void OnClick() { Corpse corpse = Owner.Target as Corpse; if ( corpse != null && Owner.From.CheckAlive() ) corpse.Open( Owner.From ); } } public override void GetContextMenuEntries( Mobile from, List list ) { base.GetContextMenuEntries( from, list ); } private Dictionary m_RestoreTable; public bool GetRestoreInfo( Item item, ref Point3D loc ) { if ( m_RestoreTable == null || item == null ) return false; return m_RestoreTable.TryGetValue( item, out loc ); } public void SetRestoreInfo( Item item, Point3D loc ) { if ( item == null ) return; if ( m_RestoreTable == null ) m_RestoreTable = new Dictionary(); m_RestoreTable[item] = loc; } public void ClearRestoreInfo( Item item ) { if ( m_RestoreTable == null || item == null ) return; m_RestoreTable.Remove( item ); if ( m_RestoreTable.Count == 0 ) m_RestoreTable = null; } public bool CanLoot( Mobile from, Item item ) { if ( !IsCriminalAction( from ) ) return true; Map map = this.Map; if ( map == null || (map.Rules & MapRules.HarmfulRestrictions) != 0 ) return false; return true; } public bool CheckLoot( Mobile from, Item item ) { if ( !CanLoot( from, item ) ) { if ( m_Owner == null || !m_Owner.Player ) from.SendLocalizedMessage( 1005035 ); // You did not earn the right to loot this creature! else from.SendLocalizedMessage( 1010049 ); // You may not loot this corpse. return false; } else if ( IsCriminalAction( from ) ) { if ( m_Owner == null || !m_Owner.Player ) from.SendLocalizedMessage( 1005036 ); // Looting this monster corpse will be a criminal act! else from.SendLocalizedMessage( 1005038 ); // Looting this corpse will be a criminal act! } return true; } public virtual void Open( Mobile from ) { if ( from.AccessLevel > AccessLevel.Player || from.InRange( this.GetWorldLocation(), 2 ) ) { if ( !CheckLoot( from, null ) ) return; base.OnDoubleClick( from ); } else { from.SendLocalizedMessage( 500446 ); // That is too far away. return; } } public override void OnDoubleClick( Mobile from ) { Open( from ); } public override bool CheckContentDisplay( Mobile from ) { return false; } public override bool DisplaysContent{ get{ return false; } } public override void AddNameProperty( ObjectPropertyList list ) { if ( ItemID == 0x2006 ) // Corpse form { if ( m_CorpseName != null ) list.Add( m_CorpseName ); else list.Add( 1046414, this.Name ); // the remains of ~1_NAME~ } else // Bone form { list.Add( 1046414, this.Name ); // the remains of ~1_NAME~ } } public override void OnSingleClick( Mobile from ) { int hue = Notoriety.GetHue( NotorietyHandlers.CorpseNotoriety( from, this ) ); if ( ItemID == 0x2006 ) // Corpse form { if ( m_CorpseName != null ) from.Send( new AsciiMessage( Serial, ItemID, MessageType.Label, hue, 3, "", m_CorpseName ) ); else from.Send( new MessageLocalized( Serial, ItemID, MessageType.Label, hue, 3, 1046414, "", Name ) ); } else // Bone form { from.Send( new MessageLocalized( Serial, ItemID, MessageType.Label, hue, 3, 1046414, "", Name ) ); } } public void Carve( Mobile from, Item item ) { if ( IsCriminalAction( from ) && this.Map != null && (this.Map.Rules & MapRules.HarmfulRestrictions) != 0 ) { if ( m_Owner == null || !m_Owner.Player ) from.SendLocalizedMessage( 1005035 ); // You did not earn the right to loot this creature! else from.SendLocalizedMessage( 1010049 ); // You may not loot this corpse. return; } Mobile dead = m_Owner; if ( GetFlag( CorpseFlag.Carved ) || dead == null ) { from.SendLocalizedMessage( 500485 ); // You see nothing useful to carve from the corpse. } else if ( ((Body)Amount).IsHuman && ItemID == 0x2006 ) { new Blood( 0x122D ).MoveToWorld( Location, Map ); new Torso().MoveToWorld( Location, Map ); new LeftLeg().MoveToWorld( Location, Map ); new LeftArm().MoveToWorld( Location, Map ); new RightLeg().MoveToWorld( Location, Map ); new RightArm().MoveToWorld( Location, Map ); new Head( dead.Name ).MoveToWorld( Location, Map ); SetFlag( CorpseFlag.Carved, true ); ProcessDelta(); SendRemovePacket(); ItemID = Utility.Random( 0xECA, 9 ); // bone graphic Hue = 0; ProcessDelta(); if ( IsCriminalAction( from ) ) from.CriminalAction( true ); } else if ( dead is BaseCreature ) { ((BaseCreature)dead).OnCarve( from, this, item ); } else { from.SendLocalizedMessage( 500485 ); // You see nothing useful to carve from the corpse. } } } }