Vorspire
Knight
[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:
All edits are to be made to: PlayerMobile.cs
Assembly Reference:
<PlayerMobile>: CREATE
Property Declarations:
<PlayerMobile>.m_Level: CREATE
<PlayerMobile>.m_LevelCap: CREATE
<PlayerMobile>.m_Experience: CREATE
<PlayerMobile>.m_Rested: CREATE
<PlayerMobile>.m_RestedCap: CREATE
<PlayerMobile>.m_LastOnline: CREATE
Command Properties:
<PlayerMobile>.LastOnline: CREATE
<PlayerMobile>.Rested: CREATE
<PlayerMobile>.RestedCap: CREATE
<PlayerMobile>.Level: CREATE
<PlayerMobile>.Experience: CREATE
<PlayerMobile>.ExperienceReq: CREATE
Methods:
<PlayerMobile>.GetXPZone( bool byLevel ): CREATE
<PlayerMobile>.SetExperience( int value ): CREATE
<PlayerMobile>.CheckLevelGain( ): CREATE
<PlayerMobile>.LevelUp( int count ): CREATE
<PlayerMobile>.LevelDown( int count ): CREATE
<PlayerMobile>.AwardExperience( int count ): CREATE
<PlayerMobile>.OnLogout( LogoutEventArgs e ): EDIT
<PlayerMobile>.Serialize( GenericWriter writer ): EDIT
<PlayerMobile>.Deserialize( GenericReader reader ): EDIT
All edits are to be made to: BaseCreature.cs
Assembly Reference:
<BaseCreature>: CREATE
Property Declarations:
<BaseCreature>.m_Level: CREATE
<BaseCreature>.m_IsElite: CREATE
Command Properties:
<BaseCreature>.Level: CREATE
<BaseCreature>.IsElite: CREATE
Methods:
<BaseCreature>.SetElite( bool value ): CREATE
<BaseCreature>.GetBonusExperience( ): CREATE
<BaseCreature>.OnDamage( int amount, Mobile from, bool willKill ): EDIT
<BaseCreature>.Serialize( GenericWriter writer ): EDIT
<BaseCreature>.Deserialize( GenericReader reader ): EDIT
All edits are to be made to: BaseArmor.cs, BaseWeapon.cs
Assembly Reference:
<BaseArmor/BaseWeapon>: CREATE
Property Declarations:
<BaseArmor/BaseWeapon>.m_LevelReq: CREATE
Command Properties:
<BaseArmor/BaseWeapon>.LevelReq: CREATE
Methods:
<BaseArmor/BaseWeapon>.CheckLevel( Mobile from ): CREATE
<BaseArmor/BaseWeapon>.CanEquip( Mobile from ): EDIT
<BaseArmor/BaseWeapon>.Serialize( GenericWriter writer ): EDIT
<BaseArmor/BaseWeapon>.Deserialize( GenericReader reader ): EDIT
Create a new file: ExperienceCheque.cs
Complete Script Code:
Create a new file: LevelStatusCMD.cs
Complete Script Code:
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:
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.
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:
<PlayerMobile>: CREATE
Rich (BB code):
using System.Collections;
using System.Collections.Generic;
using Server.WoW;
Property Declarations:
<PlayerMobile>.m_Level: CREATE
<PlayerMobile>.m_LevelCap: CREATE
<PlayerMobile>.m_Experience: CREATE
<PlayerMobile>.m_Rested: CREATE
<PlayerMobile>.m_RestedCap: CREATE
<PlayerMobile>.m_LastOnline: CREATE
Rich (BB 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:
<PlayerMobile>.LastOnline: CREATE
Rich (BB code):
[CommandProperty(AccessLevel.GameMaster)]
public DateTime LastOnline
{
get
{
return m_LastOnline;
}
set
{
m_LastOnline = value;
}
}
<PlayerMobile>.Rested: CREATE
Rich (BB 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."));
}
}
}
}
<PlayerMobile>.RestedCap: CREATE
Rich (BB code):
[CommandProperty(AccessLevel.Administrator)]
public int RestedCap
{
get
{
if (m_RestedCap < 0)
m_RestedCap = 0;
return m_RestedCap;
}
set
{
m_RestedCap = value;
}
}
<PlayerMobile>.Level: CREATE
Rich (BB 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 );
}
}
}
<PlayerMobile>.Experience: CREATE
Rich (BB 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 );
}
}
}
<PlayerMobile>.ExperienceReq: CREATE
Rich (BB code):
[CommandProperty(AccessLevel.Administrator)]
public int ExperienceReq
{
get
{
ZoneXPBase xpBase = GetXPZone( true );
return LevelUtility.ComputeExperienceReq( m_Level, xpBase );
}
}
Methods:
<PlayerMobile>.GetXPZone( bool byLevel ): CREATE
Rich (BB 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;
}
}
<PlayerMobile>.SetExperience( int value ): CREATE
Rich (BB 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( );
}
}
<PlayerMobile>.CheckLevelGain( ): CREATE
Rich (BB 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);
}
}
<PlayerMobile>.LevelUp( int count ): CREATE
Rich (BB code):
public void LevelUp( int count )
{
if (m_Level + count > m_LevelCap)
count = m_LevelCap - m_Level;
m_Experience = 0;
m_Level += count;
}
<PlayerMobile>.LevelDown( int count ): CREATE
Rich (BB code):
public void LevelDown( int count )
{
if (m_Level - count < 1)
count = m_Level - 1;
m_Experience = 0;
m_Level -= count;
}
<PlayerMobile>.AwardExperience( int count ): CREATE
Rich (BB 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;
}
<PlayerMobile>.OnLogout( LogoutEventArgs e ): EDIT
Rich (BB code):
m_LastOnline = DateTime.Now;
<PlayerMobile>.Serialize( GenericWriter writer ): EDIT
Rich (BB 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 );
<PlayerMobile>.Deserialize( GenericReader reader ): EDIT
Rich (BB 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:
<BaseCreature>: CREATE
Rich (BB code):
using System.Collections;
using System.Collections.Generic;
using Server.WoW;
using Server.Engines.PartySystem;
Property Declarations:
<BaseCreature>.m_Level: CREATE
Rich (BB code):
private int
m_Level = 1;
<BaseCreature>.m_IsElite: CREATE
Rich (BB code):
private bool
m_IsElite = false;
Command Properties:
<BaseCreature>.Level: CREATE
Rich (BB code):
[CommandProperty(AccessLevel.Administrator)]
public int Level
{
get
{
if (m_Level < 1)
m_Level = 1;
return m_Level;
}
set
{
m_Level = value;
}
}
<BaseCreature>.IsElite: CREATE
Rich (BB code):
[CommandProperty(AccessLevel.Administrator)]
public bool IsElite
{
get
{
return m_IsElite;
}
set
{
SetElite( value );
}
}
Methods:
<BaseCreature>.SetElite( bool value ): CREATE
Rich (BB code):
public virtual void SetElite( bool value )
{
m_IsElite = value;
}
<BaseCreature>.GetBonusExperience( ): CREATE
Rich (BB code):
public virtual int GetBonusExperience( )
{
return 0;
}
<BaseCreature>.OnDamage( int amount, Mobile from, bool willKill ): EDIT
Rich (BB code):
if (willKill)
{
Dictionary<PlayerMobile, int> toAward = new Dictionary<PlayerMobile, int>();
ArrayList entries = DamageEntries;
List<Party> cycled = new List<Party>();
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<PlayerMobile, int> valid in toAward)
{
if (valid.Key == null || valid.Key.Deleted)
continue;
valid.Key.AwardExperience(this, GetBonusExperience(), valid.Value);
}
}
<BaseCreature>.Serialize( GenericWriter writer ): EDIT
Rich (BB code):
writer.Write( (int) m_Level );
writer.Write( (bool) m_IsElite );
<BaseCreature>.Deserialize( GenericReader reader ): EDIT
Rich (BB code):
m_Level = reader.ReadInt( );
SetElite( reader.ReadBool() );
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
All edits are to be made to: BaseArmor.cs, BaseWeapon.cs
Assembly Reference:
<BaseArmor/BaseWeapon>: CREATE
Rich (BB code):
using System.Collections;
using System.Collections.Generic;
using Server.WoW;
Property Declarations:
<BaseArmor/BaseWeapon>.m_LevelReq: CREATE
Rich (BB code):
private int
m_LevelReq = 1;
Command Properties:
<BaseArmor/BaseWeapon>.LevelReq: CREATE
Rich (BB code):
[CommandProperty(AccessLevel.GameMaster)]
public int LevelReq
{
get
{
if( m_LevelReq < 0 )
m_LevelReq = 0;
return m_LevelReq;
}
set
{
m_LevelReq = value;
}
}
Methods:
<BaseArmor/BaseWeapon>.CheckLevel( Mobile from ): CREATE
Rich (BB 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;
}
<BaseArmor/BaseWeapon>.CanEquip( Mobile from ): EDIT
Rich (BB code):
//<ADD THIS>
else if( !CheckLevel( from ) )
{
return false;
}
//<BEFORE THIS>
else
{
return base.CanEquip( from );
}
<BaseArmor/BaseWeapon>.Serialize( GenericWriter writer ): EDIT
Rich (BB code):
writer.Write( (int) m_LevelReq );
<BaseArmor/BaseWeapon>.Deserialize( GenericReader reader ): EDIT
Rich (BB code):
m_LevelReq = reader.ReadInt( );
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
Create a new file: ExperienceCheque.cs
Complete Script Code:
Rich (BB 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:
Rich (BB 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 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.