From 5d07b8f64a1202e133d18acae2ed2da452ff90cb Mon Sep 17 00:00:00 2001 From: ravenal Date: Sun, 18 Oct 2009 15:49:24 +0000 Subject: [PATCH] Added IDevourable for PlaugeBeast and any other creatures that devour corpses. Corpses no longer have booleans all over the place, I converted the main variables to CorpseFlag Added something to the Repair when deeds are deleted it'll also send a message in regards to repairing Added OnBefore and OnAfter Convert/Unconvert for Paragon which is used in PlaugeBeast... Basically if the plauge beast is a paragon, then the devour corpse total will increase. --- Scripts/Engines/Craft/Core/Repair.cs | 1 + Scripts/Items/Misc/Corpses/Corpse.cs | 194 ++++++++++++++---- Scripts/Mobiles/AI/Creature/BaseCreature.cs | 16 ++ Scripts/Mobiles/AI/Creature/Paragon.cs | 7 + .../Monsters/Misc/Melee/PlagueBeast.cs | 142 ++++++++++++- 5 files changed, 311 insertions(+), 49 deletions(-) diff --git a/Scripts/Engines/Craft/Core/Repair.cs b/Scripts/Engines/Craft/Core/Repair.cs index cbc4d62c6..a98fe650e 100644 --- a/Scripts/Engines/Craft/Core/Repair.cs +++ b/Scripts/Engines/Craft/Core/Repair.cs @@ -428,6 +428,7 @@ namespace Server.Engines.Craft } else if( toDelete ) { + from.SendLocalizedMessage( number ); m_Deed.Delete(); } } diff --git a/Scripts/Items/Misc/Corpses/Corpse.cs b/Scripts/Items/Misc/Corpses/Corpse.cs index 71d1c39f1..15f049ac2 100644 --- a/Scripts/Items/Misc/Corpses/Corpse.cs +++ b/Scripts/Items/Misc/Corpses/Corpse.cs @@ -14,32 +14,69 @@ using Server.Network; namespace Server.Items { + public interface IDevourable + { + 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, + + /// + /// Has this corpse yet been used to channel spiritual energy? (AOS Spirit Speak) + /// + Channeled = 0x00000010, + + /// + /// Was the owner criminal when he died? + /// + Criminal = 0x00000020, + } + public class Corpse : Container, ICarvable { - private Mobile m_Owner; // Whos corpse is this? - private Mobile m_Killer; // Who killed the owner? - private bool m_Carved; // Has this corpse been carved? + 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 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 bool m_NoBones; // If true, this corpse will not turn into bones - - private bool m_VisitedByTaxidermist; // Has this corpse yet been visited by a taxidermist? - private bool m_Channeled; // Has this corpse yet been used to channel spiritual energy? (AOS Spirit Speak) + 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 IDevourable 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 bool m_Criminal; // Was the owner criminal when he died? + 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 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 + 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 ); @@ -76,25 +113,37 @@ namespace Server.Items 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 m_Carved; } - set{ m_Carved = value; } + get{ return GetFlag( CorpseFlag.Carved ); } + set { SetFlag( CorpseFlag.Carved, value ); } } [CommandProperty( AccessLevel.GameMaster )] public bool VisitedByTaxidermist { - get{ return m_VisitedByTaxidermist; } - set{ m_VisitedByTaxidermist = value; } + get { return GetFlag( CorpseFlag.VisitedByTaxidermist ); } + set { SetFlag( CorpseFlag.VisitedByTaxidermist, value ); } } [CommandProperty( AccessLevel.GameMaster )] public bool Channeled { - get{ return m_Channeled; } - set{ m_Channeled = value; } + get { return GetFlag( CorpseFlag.Channeled ); } + set { SetFlag( CorpseFlag.Channeled, value ); } } [CommandProperty( AccessLevel.GameMaster )] @@ -139,8 +188,8 @@ namespace Server.Items [CommandProperty( AccessLevel.GameMaster )] public bool Criminal { - get{ return m_Criminal; } - set{ m_Criminal = value; } + get { return GetFlag( CorpseFlag.Criminal ); } + set { SetFlag( CorpseFlag.Criminal, value ); } } [CommandProperty( AccessLevel.GameMaster )] @@ -160,7 +209,9 @@ namespace Server.Items Hue = 0; ProcessDelta(); - m_NoBones = true; + SetFlag( CorpseFlag.NoBones, true ); + SetFlag( CorpseFlag.IsBones, true ); + BeginDecay( m_BoneDecayTime ); } @@ -201,7 +252,7 @@ namespace Server.Items protected override void OnTick() { - if ( !m_Corpse.m_NoBones ) + if ( !m_Corpse.GetFlag( CorpseFlag.NoBones ) ) m_Corpse.TurnToBones(); else m_Corpse.Delete(); @@ -307,14 +358,14 @@ namespace Server.Items m_AccessLevel = owner.AccessLevel; m_Guild = owner.Guild as Guild; m_Kills = owner.Kills; - m_Criminal = owner.Criminal; + 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 - m_NoBones = !owner.Player; + SetFlag( CorpseFlag.NoBones, !owner.Player ); m_Looters = new List(); m_EquipItems = equipItems; @@ -371,6 +422,8 @@ namespace Server.Items } BeginDecay( m_DefaultDecayTime ); + + DevourCorpse(); } private void AssignInstancedLoot() @@ -383,11 +436,23 @@ namespace Server.Items { } + 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) 10 ); // version + writer.Write( (int) 11 ); // version + + writer.Write( (int)m_Flags ); writer.WriteDeltaTime( m_TimeOfDeath ); @@ -415,8 +480,6 @@ namespace Server.Items } } - writer.Write( m_VisitedByTaxidermist ); - writer.Write( m_DecayTimer != null ); if ( m_DecayTimer != null ) @@ -425,20 +488,15 @@ namespace Server.Items writer.Write( m_Looters ); writer.Write( m_Killer ); - writer.Write( (bool) m_Carved ); - writer.Write( m_Aggressors ); writer.Write( m_Owner ); - writer.Write( m_NoBones ); - writer.Write( (string) m_CorpseName ); writer.Write( (int) m_AccessLevel ); writer.Write( (Guild) m_Guild ); writer.Write( (int) m_Kills ); - writer.Write( (bool) m_Criminal ); writer.Write( m_EquipItems ); } @@ -451,6 +509,43 @@ namespace Server.Items 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(); @@ -475,7 +570,7 @@ namespace Server.Items } case 8: { - m_VisitedByTaxidermist = reader.ReadBool(); + SetFlag( CorpseFlag.VisitedByTaxidermist, reader.ReadBool() ); goto case 7; } @@ -495,7 +590,7 @@ namespace Server.Items } case 5: { - m_Carved = reader.ReadBool(); + SetFlag( CorpseFlag.Carved, reader.ReadBool() ); goto case 4; } @@ -513,7 +608,7 @@ namespace Server.Items } case 2: { - m_NoBones = reader.ReadBool(); + SetFlag( CorpseFlag.NoBones, reader.ReadBool() ); goto case 1; } @@ -540,7 +635,7 @@ namespace Server.Items m_AccessLevel = (AccessLevel)reader.ReadInt(); reader.ReadInt(); // guild reserve m_Kills = reader.ReadInt(); - m_Criminal = reader.ReadBool(); + SetFlag( CorpseFlag.Criminal, reader.ReadBool() ); m_EquipItems = reader.ReadStrongItemList(); @@ -549,6 +644,15 @@ namespace Server.Items } } + public bool DevourCorpse() + { + if( Devoured || Deleted || m_Killer == null || m_Killer.Deleted || !m_Killer.Alive || !(m_Killer is IDevourable) || m_Owner == null || m_Owner.Deleted ) + return false; + + m_Devourer = (IDevourable)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 ); @@ -780,7 +884,7 @@ namespace Server.Items if ( gathered && !didntFit ) { - m_Carved = true; + SetFlag( CorpseFlag.Carved, true ); if ( ItemID == 0x2006 ) { @@ -941,7 +1045,7 @@ namespace Server.Items Mobile dead = m_Owner; - if ( m_Carved || dead == null ) + if ( GetFlag( CorpseFlag.Carved ) || dead == null ) { from.SendLocalizedMessage( 500485 ); // You see nothing useful to carve from the corpse. } @@ -956,7 +1060,7 @@ namespace Server.Items new RightArm().MoveToWorld( Location, Map ); new Head( dead.Name ).MoveToWorld( Location, Map ); - m_Carved = true; + SetFlag( CorpseFlag.Carved, true ); ProcessDelta(); SendRemovePacket(); diff --git a/Scripts/Mobiles/AI/Creature/BaseCreature.cs b/Scripts/Mobiles/AI/Creature/BaseCreature.cs index 71f01506c..98d315877 100644 --- a/Scripts/Mobiles/AI/Creature/BaseCreature.cs +++ b/Scripts/Mobiles/AI/Creature/BaseCreature.cs @@ -955,6 +955,22 @@ namespace Server.Mobiles base.OnBeforeSpawn( location, m ); } + public virtual void OnBeforeParagonConvert() + { + } + + public virtual void OnAfterParagonConvert() + { + } + + public virtual void OnBeforeParagonUnConvert() + { + } + + public virtual void OnAfterParagonUnConvert() + { + } + public override ApplyPoisonResult ApplyPoison( Mobile from, Poison poison ) { if ( !Alive || IsDeadPet ) diff --git a/Scripts/Mobiles/AI/Creature/Paragon.cs b/Scripts/Mobiles/AI/Creature/Paragon.cs index a699c6f89..ccb1a8de3 100644 --- a/Scripts/Mobiles/AI/Creature/Paragon.cs +++ b/Scripts/Mobiles/AI/Creature/Paragon.cs @@ -46,6 +46,8 @@ namespace Server.Mobiles if ( bc.IsParagon ) return; + bc.OnBeforeParagonConvert(); + bc.Hue = Hue; if ( bc.HitsMaxSeed >= 0 ) @@ -88,6 +90,8 @@ namespace Server.Mobiles if( Math.Abs( bc.Karma ) > 32000 ) bc.Karma = 32000 * Math.Sign( bc.Karma ); } + + bc.OnAfterParagonConvert(); } public static void UnConvert( BaseCreature bc ) @@ -95,6 +99,7 @@ namespace Server.Mobiles if ( !bc.IsParagon ) return; + bc.OnBeforeParagonUnConvert(); bc.Hue = 0; if ( bc.HitsMaxSeed >= 0 ) @@ -126,6 +131,8 @@ namespace Server.Mobiles bc.Fame = (int)( bc.Fame / FameBuff ); if ( bc.Karma != 0 ) bc.Karma = (int)( bc.Karma / KarmaBuff ); + + bc.OnAfterParagonUnConvert(); } public static bool CheckConvert( BaseCreature bc ) diff --git a/Scripts/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs b/Scripts/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs index 18ee2b94f..d32fd01ad 100644 --- a/Scripts/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs +++ b/Scripts/Mobiles/Monsters/Misc/Melee/PlagueBeast.cs @@ -2,12 +2,37 @@ using System; using System.Collections; using Server.Items; using Server.Targeting; +using Server.Network; namespace Server.Mobiles { [CorpseName( "a plague beast corpse" )] - public class PlagueBeast : BaseCreature + public class PlagueBeast : BaseCreature, IDevourable { + private int m_DevourTotal; + private int m_DevourGoal; + private bool m_HasMetalChest = false; + + [CommandProperty( AccessLevel.GameMaster )] + public int TotalDevoured + { + get { return m_DevourTotal; } + set { m_DevourTotal = value; } + } + + [CommandProperty( AccessLevel.GameMaster )] + public int DevourGoal + { + get { return m_DevourGoal; } + set { m_DevourGoal = value; } + } + + [CommandProperty( AccessLevel.GameMaster )] + public bool HasMetalChest + { + get { return m_HasMetalChest; } + } + [Constructable] public PlagueBeast() : base( AIType.AI_Melee, FightMode.Closest, 10, 1, 0.2, 0.4 ) { @@ -43,9 +68,11 @@ namespace Server.Mobiles if ( Utility.RandomDouble() < 0.80 ) PackItem( new PlagueBeastGland() ); - if ( Core.ML && Utility.RandomDouble() < .33 ) + if ( Core.ML && Utility.RandomDouble() < 0.33 ) PackItem( Engines.Plants.Seed.RandomPeculiarSeed(4) ); + m_DevourTotal = 0; + m_DevourGoal = Utility.RandomMinMax( 15, 25 ); // The goal amount of how many corpses must devour before metal chest is rewarded } public override void GenerateLoot() @@ -55,7 +82,24 @@ namespace Server.Mobiles // TODO: dungeon chest, healthy gland } - // TODO: Poison attack + public override void OnAfterParagonConvert() + { + m_DevourGoal += 25; + } + + public override void OnAfterParagonUnConvert() + { + m_DevourGoal -= 25; + } + + public override void OnGaveMeleeAttack( Mobile defender ) + { + base.OnGaveMeleeAttack( defender ); + + defender.ApplyPoison( this, IsParagon ? Poison.Lethal : Poison.Deadly ); + defender.FixedParticles( 0x374A, 10, 15, 5021, EffectLayer.Waist ); + defender.PlaySound( 0x1CB ); + } public override void OnDamagedBySpell( Mobile caster ) { @@ -119,13 +163,103 @@ namespace Server.Mobiles public override void Serialize( GenericWriter writer ) { base.Serialize( writer ); - writer.Write( (int) 0 ); + writer.Write( (int) 1 ); + + writer.Write( m_HasMetalChest ); + writer.Write( m_DevourTotal ); + writer.Write( m_DevourGoal ); } public override void Deserialize( GenericReader reader ) { base.Deserialize( reader ); int version = reader.ReadInt(); + + switch( version ) + { + case 1: + { + m_HasMetalChest = reader.ReadBool(); + m_DevourTotal = reader.ReadInt(); + m_DevourGoal = reader.ReadInt(); + break; + } + } + } + + public override void OnThink() + { + base.OnThink(); + + // Check to see if we need to devour any corpses + IPooledEnumerable eable = GetItemsInRange( 3 ); // Get all corpses in range + + foreach( Item item in eable ) + { + if( item is Corpse ) // For each Corpse + { + Corpse corpse = item as Corpse; + + // Ensure that the corpse was killed by us + if( corpse != null && corpse.Killer == this && corpse.Owner != null ) + { + if( !corpse.DevourCorpse() && !corpse.Devoured ) + PublicOverheadMessage( MessageType.Emote, 0x3B2, 1053032 ); // * The plague beast attempts to absorb the remains, but cannot! * + } + } + } + eable.Free(); + } + + #region IDevourable Members + + public bool Devour( Corpse corpse ) + { + if( corpse == null || corpse.Owner == null ) // sorry we can't devour because the corpse's owner is null + return false; + + if( corpse.Owner.Body.IsHuman ) + corpse.TurnToBones(); // Not bones yet, and we are a human body therefore we turn to bones. + + IncreaseHits( (int)Math.Ceiling( (double)corpse.Owner.HitsMax * 0.75 ) ); + m_DevourTotal++; + + PublicOverheadMessage( MessageType.Emote, 0x3B2, 1053033 ); // * The plague beast absorbs the fleshy remains of the corpse * + + if( !m_HasMetalChest && m_DevourTotal >= m_DevourGoal ) + { + PackItem( new MetalChest() ); + m_HasMetalChest = true; + } + + return true; + } + + #endregion + + private void IncreaseHits( int hp ) + { + int maxhits = (int)(2000 * (IsParagon ? Paragon.HitsBuff : 1)); + + if( hp < 1000 && !Core.AOS ) + hp = (hp * 100) / 60; + + if( HitsMaxSeed >= maxhits ) + { + HitsMaxSeed = maxhits; + Hits += hp + Utility.RandomMinMax( 10, 20 ); // increase the hp until it hits if it goes over it'll max at 2000 + // Also provide heal for each devour on top of the hp increase + } + else + { + int min = (hp / 2) + 10; + int max = hp + 20; + int hpToIncrease = Utility.RandomMinMax( min, max ); + + HitsMaxSeed += hpToIncrease; + Hits += hpToIncrease; + // Also provide heal for each devour + } } } } \ No newline at end of file