[RUO X] World of Warcraft Level System Utility/SDK Implimentation Guide Before you continue to read this post, make sure you have read and installed this script: >> World of Warcraft Level System Utility/SDK This guide is written based on the assumption of the reader having a common undestanding of C# termanology, key words and member placement, as well as the RunUO 2.0 API and serializing/deserializing methods. The scale used for deciding the WoW-Style XP Zone is; Trammel: Azeroth Felucca: Azeroth Ilshenar: Outland Malas: Northrend Tokuno: Northrend Internal: Northrend This guide will provide the following information: •Editing PlayerMobile •Editing BaseCreature [*]Editing BaseWeapon and BaseArmor (Level Requirements) [*]Creating a Cheque for Experience [*]Creating a Command to View Level Status ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// All edits are to be made to: PlayerMobile.cs Assembly Reference: : CREATE Code: using System.Collections; using System.Collections.Generic; using Server.WoW; Property Declarations: .m_Level: CREATE .m_LevelCap: CREATE .m_Experience: CREATE .m_Rested: CREATE .m_RestedCap: CREATE .m_LastOnline: CREATE Code: private int m_Level = 1, m_LevelCap = 80, m_Experience = 0, m_Rested = 0, m_RestedCap = 8; private DateTime m_LastOnline = DateTime.Now; Command Properties: .LastOnline: CREATE Code: [CommandProperty(AccessLevel.GameMaster)] public DateTime LastOnline { get { return m_LastOnline; } set { m_LastOnline = value; } } .Rested: CREATE Code: [CommandProperty(AccessLevel.GameMaster)] public int Rested { get { if( m_Rested > m_RestedCap ) m_Rested = m_RestedCap; else if( m_Rested < 0 ) m_Rested = 0; return m_Rested; } set { if (value == m_Rested) return; else { if (value > m_RestedCap) m_Rested = m_RestedCap; else { m_Rested = value; if (m_Rested > 0 && m_Rested < m_RestedCap) SendMessage(0x55, String.Format("You feel rested. Experience gained from killing monsters is increased by {0}% for {1} hours.", LevelUtility.RestedXPFactor * 100, m_Rested)); else if (m_Rested >= m_RestedCap) SendMessage(0x55, String.Format("You are fully rested. Experience gained from killing monsters is increased by {0}% for {1} Hours.", LevelUtility.RestedXPFactor * 100, m_Rested)); else if (m_Rested == 0) SendMessage(0x55, String.Format("You feel normal. Experience gained from killing monsters is now normal.")); } } } } .RestedCap: CREATE Code: [CommandProperty(AccessLevel.Administrator)] public int RestedCap { get { if (m_RestedCap < 0) m_RestedCap = 0; return m_RestedCap; } set { m_RestedCap = value; } } .Level: CREATE Code: [CommandProperty( AccessLevel.Administrator )] public int Level { get { if (m_Level < 1) m_Level = 1; return m_Level; } set { if (value == m_Level) return; else if (value > m_Level) { int diff = value - m_Level; LevelUp( diff ); } else if (value < m_Level) { int diff = m_Level - value; LevelDown( diff ); } } } .Experience: CREATE Code: [CommandProperty(AccessLevel.Administrator)] public int Experience { get { if (m_Experience < 0) m_Experience = 0; return m_Experience; } set { if (value == m_Experience) return; else { SetExperience( value ); } } } .ExperienceReq: CREATE Code: [CommandProperty(AccessLevel.Administrator)] public int ExperienceReq { get { ZoneXPBase xpBase = GetXPZone( true ); return LevelUtility.ComputeExperienceReq( m_Level, xpBase ); } } Methods: .GetXPZone( bool byLevel ): CREATE Code: public ZoneXPBase GetXPZone( bool byLevel ) { if( byLevel ) { if( m_Level <= 59 ) return ZoneXPBase.Azeroth; else if( m_Level <= 69 ) return ZoneXPBase.Outland; else return ZoneXPBase.Northrend; } else { if( Map == Map.Felucca || Map == Map.Trammel ) return ZoneXPBase.Azeroth; else if( Map == Map.Ilshenar ) return ZoneXPBase.Outland; else return ZoneXPBase.Northrend; } } .SetExperience( int value ): CREATE Code: public void SetExperience( int value ) { int difference = value - m_Experience; if (m_Experience != value) m_Experience = value; if ((Math.Abs(difference) > 0)) { SendMessage((difference > 0 ? 57 : 37), "You {0} {1} XP", (difference > 0 ? "gained" : "lost"), Math.Abs(difference)); CheckLevelGain( ); } } .CheckLevelGain( ): CREATE Code: public void CheckLevelGain( ) { int count = 0; ZoneXPBase xpBase = GetXPZone( true ); while (m_Experience >= ComputeExperienceReq(m_Level, m_Level + (count + 1), xpBase)) { count++; } if (count > 0) { LevelUp(count); } } .LevelUp( int count ): CREATE Code: public void LevelUp( int count ) { if (m_Level + count > m_LevelCap) count = m_LevelCap - m_Level; m_Experience = 0; m_Level += count; } .LevelDown( int count ): CREATE Code: public void LevelDown( int count ) { if (m_Level - count < 1) count = m_Level - 1; m_Experience = 0; m_Level -= count; } .AwardExperience( int count ): CREATE Code: public void AwardExperience( BaseCreature killed, int bonus, int split ) { if (split < 1) split = 1; double baseXP = 0.0; if (killed != null) { if (killed.ControlMaster != null || killed.SummonMaster != null || killed.IsBonded || killed.Summoned) return; ZoneXPBase xpBase = GetXPZone( false ); baseXP = LevelUtility.XPFromNPC( m_Level, killed.Level, xpBase ); if (killed.IsElite) baseXP *= LevelUtility.EliteXPFactor; if (m_Rested > 0) { TimeSpan span = DateTime.Now.Subtract(m_LastOnline); int hours = (int)Math.Floor(span.TotalHours); if (hours > 0) m_Rested = m_Rested - hours; baseXP *= LevelUtility.RestedXPFactor; } } int final = (int)Math.Floor((double)((baseXP + bonus) / split)); Experience += final; } .OnLogout( LogoutEventArgs e ): EDIT Code: m_LastOnline = DateTime.Now; .Serialize( GenericWriter writer ): EDIT Code: writer.Write( (int) m_Level ); writer.Write( (int) m_LevelCap ); writer.Write( (int) m_Experience ); writer.Write( (int) m_Rested ); writer.Write( (int) m_RestedCap ); writer.Write( (DateTime) m_LastOnline ); .Deserialize( GenericReader reader ): EDIT Code: m_Level = reader.ReadInt( ); m_LevelCap = reader.ReadInt( ); m_Experience = reader.ReadInt( ); m_Rested = reader.ReadInt( ); m_RestedCap = reader.ReadInt( ); m_LastOnline = reader.ReadDateTime( ); ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// All edits are to be made to: BaseCreature.cs Assembly Reference: : CREATE Code: using System.Collections; using System.Collections.Generic; using Server.WoW; using Server.Engines.PartySystem; Property Declarations: .m_Level: CREATE Code: private int m_Level = 1; .m_IsElite: CREATE Code: private bool m_IsElite = false; Command Properties: .Level: CREATE Code: [CommandProperty(AccessLevel.Administrator)] public int Level { get { if (m_Level < 1) m_Level = 1; return m_Level; } set { m_Level = value; } } .IsElite: CREATE Code: [CommandProperty(AccessLevel.Administrator)] public bool IsElite { get { return m_IsElite; } set { SetElite( value ); } } Methods: .SetElite( bool value ): CREATE Code: public virtual void SetElite( bool value ) { m_IsElite = value; } .GetBonusExperience( ): CREATE Code: public virtual int GetBonusExperience( ) { return 0; } .OnDamage( int amount, Mobile from, bool willKill ): EDIT Code: if (willKill) { Dictionary toAward = new Dictionary(); ArrayList entries = DamageEntries; List cycled = new List(); foreach (DamageEntry de in entries) { if (de.Damager == null && de.Damager.Deleted) continue; Mobile damager = de.Damager; if (damager == null || damager.Deleted) continue; if (damager is PlayerMobile) { Party p = Party.Get(damager); if (p == null) { toAward.Add((PlayerMobile)damager, 1); } else if (!cycled.Contains(p)) { ArrayList members = p.Members; for (int foo = 0; foo < members.Count; foo++) { PlayerMobile member = members[foo] as PlayerMobile; if (member == null || member.Deleted) continue; if (member.InRange(Location, 25)) toAward.Add(member, members.Count); } cycled.Add(p); } } else if (damager is BaseCreature) { BaseCreature creature = damager as BaseCreature; PlayerMobile master = null; if (creature.Controled) { if (creature.ControlMaster != null && creature.ControlMaster is PlayerMobile) master = (PlayerMobile)creature.ControlMaster; } else if (creature.Summoned) { if (creature.SummonMaster != null && creature.SummonMaster is PlayerMobile) master = (PlayerMobile)creature.SummonMaster; } if (master == null || master.Deleted) continue; Party p = Party.Get(master); if (p == null) { toAward.Add(master, 1); } else if (!cycled.Contains(p)) { ArrayList members = p.Members; for (int foo = 0; foo < members.Count; foo++) { PlayerMobile member = members[foo] as PlayerMobile; if (member == null || member.Deleted) continue; if (member.InRange(Location, 25)) toAward.Add(member, members.Count); } cycled.Add(p); } } } foreach (KeyValuePair valid in toAward) { if (valid.Key == null || valid.Key.Deleted) continue; valid.Key.AwardExperience(this, GetBonusExperience(), valid.Value); } } .Serialize( GenericWriter writer ): EDIT Code: writer.Write( (int) m_Level ); writer.Write( (bool) m_IsElite ); .Deserialize( GenericReader reader ): EDIT Code: m_Level = reader.ReadInt( ); SetElite( reader.ReadBool() ); ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// All edits are to be made to: BaseArmor.cs, BaseWeapon.cs Assembly Reference: : CREATE Code: using System.Collections; using System.Collections.Generic; using Server.WoW; Property Declarations: .m_LevelReq: CREATE Code: private int m_LevelReq = 1; Command Properties: .LevelReq: CREATE Code: [CommandProperty(AccessLevel.GameMaster)] public int LevelReq { get { if( m_LevelReq < 0 ) m_LevelReq = 0; return m_LevelReq; } set { m_LevelReq = value; } } Methods: .CheckLevel( Mobile from ): CREATE Code: public bool CheckLevel(Mobile from) { if( from == null || from.Deleted || !( from is PlayerMobile ) ) return true; PlayerMobile equipee = from as PlayerMobile; if (m_LevelReq <= 0) return true; if (equipee.Level < m_LevelReq) { equipee.SendMessage(0x22, "You do not meet the level requirements for this item."); return false; } return true; } .CanEquip( Mobile from ): EDIT Code: // else if( !CheckLevel( from ) ) { return false; } // else { return base.CanEquip( from ); } .Serialize( GenericWriter writer ): EDIT Code: writer.Write( (int) m_LevelReq ); .Deserialize( GenericReader reader ): EDIT Code: m_LevelReq = reader.ReadInt( ); ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// Create a new file: ExperienceCheque.cs Complete Script Code: Code: using System; using Server; using Server.Mobiles; using Server.WoW; namespace Server.Items { public class ExperienceCheque : Item { public int m_XPAmount; [CommandProperty( AccessLevel.GameMaster )] public int XPAmount { get { return m_XPAmount; } set { m_XPAmount = value; InvalidateProperties( ); } } [Constructable] public ExperienceCheque( ) : base( 0x14F0 ) { Name = "a Blank Experience Cheque"; m_XPAmount = 0; } [Constructable] public ExperienceCheque( int xpamount ) : base( 0x14F0 ) { Name = "a Cheque for " + xpamount.ToString("#,#") + " Experience"; m_XPAmount = xpamount; } public override void AddNameProperty( ObjectPropertyList list ) { list.Add( ( m_XPAmount == 0 ) ? "a Blank Experience Cheque" : String.Format( "a Cheque for {0} Experience", m_XPAmount.ToString("#,#") ) ); } public override void OnSingleClick( Mobile from ) { LabelTo( from, ( m_XPAmount == 0 ) ? "a Blank Experience Cheque" : String.Format( "a Cheque for {0} XP", m_XPAmount.ToString("#,#") ) ); } public override void OnDoubleClick( Mobile from ) { PlayerMobile pm = from as PlayerMobile; if( pm != null && !pm.Deleted && pm.Alive ) { if( IsChildOf( from.Backpack ) ) { pm.Experience += m_XPAmount; Delete( ); } else { from.SendLocalizedMessage( 1042001 ); // That must be in your pack for you to use it. } } } public ExperienceCheque( Serial serial ) : base( serial ) { } public override void Serialize( GenericWriter writer ) { base.Serialize( writer ); writer.Write( ( int )0 ); // version writer.Write( ( int )m_XPAmount ); } public override void Deserialize( GenericReader reader ) { base.Deserialize( reader ); int version = reader.ReadInt( ); switch( version ) { case 0: { m_XPAmount = reader.ReadInt( ); break; } } } } } ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// Create a new file: LevelStatusCMD.cs Complete Script Code: Code: using System; using Server.Commands; using Server.Mobiles; namespace Server.WoW { public class LevelStatusCMD { public static void Initialize() { CommandSystem.Register( "LevelStatus", AccessLevel.Player, new CommandEventHandler( LevelStatus_OnCommand ) ); } [Usage( "LevelStatus" )] [Description( "Displays your current level status." )] private static void LevelStatus_OnCommand( CommandEventArgs e) { PlayerMobile pm = e.Mobile as PlayerMobile; if(pm != null) { pm.SendMessage("You are currently on level {0} and have {1} experience points! You need {2} experience points to advance to the next level.", pm.Level, pm.Experience, pm.ExperienceReq ); } } } } ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// Done! OK, so hopefuly you've finished all of the edits, it wasn't too hard was it? Now you should have a fully working level system! Although the Level System we have just created is very basic, it should be easy enough to add in your own tweaks. The part of the level system that handles the experience gained when killing a BaseCreature: •Is based on all damagers found in the BaseCreature's DamageEntries list. •Takes into account that the player may be a member of a party, if so, it will cycle through all of the members of the party and check if they are in range of the kill (25 tiles I think I set it to) and then distribute the experience evenly to each member. If a party has been "cycled", it will not be "cycled" again, so it will not grant more experience than it should. •If a damager is a BaseCreature, the system will look for the ControlMaster or SummonMaster as the main damager. I really hope this helps everyone who posted earlier asking about how to impliment this system I can not guarantee that the code in the guide is fully working, as a lot of last-minute edits were made in the forum post editor. <#4.0> VitaNexCore Project: RunUO Extension Library <#4.0>