RunUO Community

This is a sample guest message. Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!

Smart Programming (tutorial)

P

php-junkie

Guest
Smart Programming (tutorial)

I've noticed when allot of us C# newbies want to script something most of us are constantly trying to modify (edit) the RunUO script package so I thought I would share my some of new found knowledge with the rest of the C# newbies here.

Class inheritance in RunUO
The script I hate updating the most every time the dev's release a new beta is the PlayerMobile.cs. This script also takes the most abuse from programmers on the script submission forum so for this example I'm going to create a new class using PlayerMobile as a parent class. In this class we'll create titles for your staff members. Some examples of other things that could be put inside this new class are the properties, methods, etc of the PlayerMobile.cs in Archeronne's faction system. Also, remember you can do this with any object in the RunUO script package.

I started by creating a new file under \Scripts\Custom\Mobiles. You can name this file what ever you want and put it where ever you want as long as it's inside the scripts folder. I named mine IntrepidMobile.cs because Intrepid is the name of my shard. It means, without fear.


First thing we need are the using directives and namespace.
Code:
using Server;
using Server.Network;

namespace Server.Mobiles
{
}
Now lets create our new class. Make sure you take note of how I use the PlayerMobile as the parent class.
Code:
using Server;
using Server.Network;

namespace Server.Mobiles
{
	public class IntrepidMobile : PlayerMobile
	{
	}
}

Did you noticed how I used the PlayerMobile as a parent class? This is the most important part of my tutorial and it is also the reason why I'm writing this tutorial so make sure you understand what it is I did here before you continue. Also, you can name the class what ever you want. I named mine IntrepidMobile for the same reason I named the file IntrepidMobile.cs.

Ok, my point for doing this is to show you that you don't need to edit any of the script files inside the RunUO Distro so I'm going to skip over the rest of it and finish the script so you can see what it is I'm doing.

Code:
using Server;
using Server.Network;

namespace Server.Mobiles
{
	public class IntrepidMobile : PlayerMobile
	{
		private string m_StaffTitle;

		[CommandProperty( AccessLevel.GameMaster )]
		public string StaffTitle
		{
			get{ return m_StaffTitle; }
			set{ m_StaffTitle = value }
		}

		public IntrepidMobile()
		{
         		m_StaffTitle = null;
		}

		public override void OnSingleClick( Mobile from )
		{
			if ( from.AccessLevel > AccessLevel.Player && m_StaffTitle != null )
				PrivateOverheadMessage( MessageType.Label, this.SpeachHue, true,"["+ m_StaffTitle +"]" , from.NetState );
		}

		public IntrepidMobile( Serial serial ) : base( serial )
		{
		}
		
		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );
			int version = reader.ReadInt();

			switch ( version )
			{
				case 0:
				{
					m_StaffTitle = reader.ReadString();
					break;
				}
			}


		public override void Serialize( GenericWriter writer )
		{

			base.Serialize( writer );
			
			writer.Write( (int) 0 ); // version
         		writer.Write( m_StaffTitle );

		}
	}
}

Now that we've finished the script. There is two other things we need to do here before we can use our new class on our players. The first is in \Scripts\Misc\CharacterCreation.cs. In order for this new class to be attached to newly created Characters we need to edit line 429 from PlayerMobile() to IntrepidMobile() so change line 429 from this;
Code:
return (a[i] = new PlayerMobile());
to this;
Code:
return (a[i] = new IntrpidMobile());
Or change it to what ever you named your new Mobile class.

The next step is to convert all of your players to the new class. This is rather simple to do considering there is a script in the RunUO package that converts players to the PlayerMobile class. I'm not going to go into details on how to mod this script. Instead I'll provide you with my modified version of the script. Remember the hole point of this is not to edit the RunUO package so do not copy this over the original ConvertPlayers.cs. Put it in a new file. I named mine simply ConvertPlayer.cs. I also change the command from ConvertPlayers to ConvertPlayer.
Code:
using System;
using System.Collections;
using System.Reflection;
using Server;
using Server.Items;
using Server.Mobiles;
using Server.Network;
using Server.Accounting;

namespace Server.Scripts.Commands
{
	public class ConvertPlayer
	{
		public static void Initialize()
		{
			Server.Commands.Register( "ConvertPlayer", AccessLevel.Administrator, new CommandEventHandler( ConvertPlayer_OnCommand ) );
		}
		
		public static void ConvertPlayer_OnCommand( CommandEventArgs e )
		{
			e.Mobile.SendMessage( "Converting all players to IntrepidMobile.  You will be disconnected.  Please Restart the server after the world has finished saving." );
			
			ArrayList mobs = new ArrayList( World.Mobiles.Values );
			int count = 0;

			foreach ( Mobile m in mobs )
			{
				if ( m.Player && !(m is IntrepidMobile ) )
				{
					count++;
					if ( m.NetState != null )
						m.NetState.Dispose();
						
					
					IntrepidMobile im = new IntrepidMobile( m.Serial );
					im.DefaultMobileInit();
					
					ArrayList copy = new ArrayList( m.Items );
					for (int i=0;i<copy.Count;i++)
						im.AddItem( (Item)copy[i] );
					
					CopyProps( im, m );
					
					for (int i=0;i<m.Skills.Length;i++)
					{
						im.Skills[i].Base = m.Skills[i].Base;
						im.Skills[i].SetLockNoRelay( m.Skills[i].Lock );
					}
					
               Account acct = m.Account as Account;
               PlayerMobile pm = m as PlayerMobile;
               for ( int i = 0; i < 5; ++i )
				     if ( acct[i] == pm )
				     {
                     acct[i].Delete();
                     acct[i] = im;
                 }

				}
			}
			
			if ( count > 0 )
			{
				NetState.ProcessDisposedQueue();
				World.Save();
			
				Console.WriteLine( "{0} players have been converted to IntrepidMobile.  Please restart the server.", count );
				while ( true )
					Console.ReadLine();
			}
			else
			{
				e.Mobile.SendMessage( "Couldn't find any Players to convert." );
			}
		}
		
		private static void CopyProps( Mobile to, Mobile from )
		{
			Type pmtype = typeof( PlayerMobile );
			
			PropertyInfo[] pmprops = pmtype.GetProperties( BindingFlags.Public | BindingFlags.Instance );
			
			for (int p=0;p<pmprops.Length;p++)
			{
				PropertyInfo prop = pmprops[p];
				
				if ( prop.CanRead && prop.CanWrite )
				{
					try
					{
						prop.SetValue( to, prop.GetValue( from, null ), null );
					}
					catch
					{
					}
				}
			}

			Type mtype = typeof( Mobile );
			
			PropertyInfo[] mprops = mtype.GetProperties( BindingFlags.Public | BindingFlags.Instance );
			
			for (int p=0;p<mprops.Length;p++)
			{
				PropertyInfo prop = mprops[p];
				
				if ( prop.CanRead && prop.CanWrite )
				{
					try
					{
						prop.SetValue( to, prop.GetValue( from, null ), null );
					}
					catch
					{
					}
				}
			}
		}
	}
}

Congratulation, you just added a new property to your players without editing the PlayerMobile.cs file. I tried to make this as simple as I possibly could so if you still don't understand don't worry because I'm going to post a couple more examples of class inheritance in RunUO.
 
P

php-junkie

Guest
In this example of class inheritance we are going to add a player accessible pack to the Nightmare just like what the Beetle has. This time we're going to use the Nightmare as the parent class.
Code:
using System;
using Server;
using Server.Items;
using Server.Mobiles;

namespace Server.Mobiles
{
	public class PackMare : Nightmare
	{
		[Constructable]
		public PackMare()
		{
		}
	}
}
As you can see I named my new class PackMare and used Nightmare as the parent class. Next I'm going to add my serialize and deserialize methods.
Code:
using System;
using Server;
using Server.Items;
using Server.Mobiles;

namespace Server.Mobiles
{
	public class PackMare : Nightmare
	{
		[Constructable]
		public PackMare()
		{
			Container pack = Backpack;

			if ( pack != null )
				pack.Delete();

			pack = new StrongBackpack();
			pack.Movable = false;

			AddItem( pack );
		}

		public PackMare( Serial serial ) : base( serial )
		{
		}

		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );

			writer.Write( (int) 0 ); // version
		}

		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );

			int version = reader.ReadInt();
		}
	}
}
Ok this next part we're going to copy and past some lines from \Scripts\Mobiles\Animals\Mounts\Beetle.cs. Lets start by copying lines 48 - 56 and past them to line 13 in our new script. This next part we have to pay careful attention to because these lines we just added deletes the loot added by the parent (Nightmare) class, so now we have to re-add it's loot to the new pack. Open \Scripts\Mobiles\Animals\Mounts\Nightmare.cs and copy lines 76 - 85 and past them to line 23 in our new script.

Here is what you should have thus far.
Code:
using System;
using Server;
using Server.Items;
using Server.Mobiles;

namespace Server.Mobiles
{
	public class PackMare : Nightmare
	{
		[Constructable]
		public PackMare()
		{
			Container pack = Backpack;

			if ( pack != null )
				pack.Delete();

			pack = new StrongBackpack();
			pack.Movable = false;

			AddItem( pack );

			PackGem();
			PackGold( 250, 350 );
			PackItem( new SulfurousAsh( Utility.RandomMinMax( 3, 5 ) ) );
			PackScroll( 1, 5 );
			PackPotion();

			PackNecroScroll( 1 ); // Blood Oath
			PackNecroScroll( 10 ); // Strangle
			PackNecroScroll( 5 ); // Horrific Beast
			PackNecroScroll( 13 ); // Vengeful Spirit
		}

		public PackMare( Serial serial ) : base( serial )
		{
		}

		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );

			writer.Write( (int) 0 ); // version
		}

		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );

			int version = reader.ReadInt();
		}
	}
}
One more thing left to do. Lets copy lines 90 - 139 from Beetle.cs and past them to line 39 in our new script. Here's what you should have when your finished.
Code:
using System;
using Server;
using Server.Items;
using Server.Mobiles;

namespace Server.Mobiles
{
	public class PackMare : Nightmare
	{
		[Constructable]
		public PackMare()
		{
			Container pack = Backpack;

			if ( pack != null )
				pack.Delete();

			pack = new StrongBackpack();
			pack.Movable = false;

			AddItem( pack );

			PackGem();
			PackGold( 250, 350 );
			PackItem( new SulfurousAsh( Utility.RandomMinMax( 3, 5 ) ) );
			PackScroll( 1, 5 );
			PackPotion();

			PackNecroScroll( 1 ); // Blood Oath
			PackNecroScroll( 10 ); // Strangle
			PackNecroScroll( 5 ); // Horrific Beast
			PackNecroScroll( 13 ); // Vengeful Spirit
		}

		public PackMare( Serial serial ) : base( serial )
		{
		}

		#region Pack Animal Methods
		public override bool OnBeforeDeath()
		{
			if ( !base.OnBeforeDeath() )
				return false;

			PackAnimal.CombineBackpacks( this );

			return true;
		}

		public override bool IsSnoop( Mobile from )
		{
			if ( PackAnimal.CheckAccess( this, from ) )
				return false;

			return base.IsSnoop( from );
		}

		public override bool OnDragDrop( Mobile from, Item item )
		{
			if ( CheckFeed( from, item ) )
				return true;

			if ( PackAnimal.CheckAccess( this, from ) )
			{
				AddToBackpack( item );
				return true;
			}

			return base.OnDragDrop( from, item );
		}

		public override bool CheckNonlocalDrop( Mobile from, Item item, Item target )
		{
			return PackAnimal.CheckAccess( this, from );
		}

		public override bool CheckNonlocalLift( Mobile from, Item item )
		{
			return PackAnimal.CheckAccess( this, from );
		}

		public override void GetContextMenuEntries( Mobile from, System.Collections.ArrayList list )
		{
			base.GetContextMenuEntries( from, list );

			PackAnimal.GetContextMenuEntries( this, from, list );
		}
		#endregion

		public override void Serialize( GenericWriter writer )
		{
			base.Serialize( writer );

			writer.Write( (int) 0 ); // version
		}

		public override void Deserialize( GenericReader reader )
		{
			base.Deserialize( reader );

			int version = reader.ReadInt();
		}
	}
}
You just made a new kind of Nightmare with a player accessible pack without editing the Nightmare.cs file. Now when the dev's release a new beta there isn't anything you need to modify. Simply copy the script to the new beta release and start the server. Also, remember you can do this with anything in the RunUO script package.
 

Zippy

Razor Creator
One VERY important error in junkie's new convertplayer.cs:

Code:
IntrepidMobile im = new IntrepidMobile();
should be
Code:
IntrepidMobile im = new IntrepidMobile( m.Serial );
THis can cause some potentially serious issues with old players in your world.
 
P

php-junkie

Guest
I'm looking to get confirmation on another question but first wouldn't this give the new mobile the same serial as the deleted mobile?
 

Zippy

Razor Creator
Yes it makes them the same serial, this is VERY important for keeping compatability with exisiting houses and items and such.
 
P

php-junkie

Guest
I was trying to figure out why this would cause a error so I check the defs and noticed that the Mobile.Serial property is read only and because of this the property isn't copied to the new mobile. Correct?
 

Zippy

Razor Creator
Correct, you want them both to have the same serial, so the next time the world loads, the corect mobile is loaded for all of their houses and things.
 
Top