Author Topic: Learning C# - READ ME FIRST  (Read 7246 times)

July 13, 2013, 09:12:44 PM

Offline Kyn

  • Hero Member
  • *****
  • Posts: 534
  • Karma: +232/-35

This will take you through the creation of a RunUO script written in C# (pronounced C sharp) which will add a unique item to the game. We will then expand on the Item by giving it some functionality. Although I will endeavor to introduce some key C# concepts here, it would help immensely if you read up on C# a bit first. The script I will walk you through, a Magic Eight Ball, was not written by me and unfortunately I do not know who the original author is. If this is your script please contact me and I will give you credit.


C# source files or scripts are simply text files. You may use any text editor to create or edit them including Windows Notepad. However there are several free programs that are designed to make editing C# easier; for example SharpDevelop, or Visual C# Express (see important note below) from Microsoft. Any of these products will make scripting easier and faster and prevent many of the most common mistakes. (My recommendation would be Visual C# Express.)


C# uses "Object Oriented Programming." Anything created in C# is an Object and any given Object is referred to as an Instance of that Object. We describe or define objects by writing Classes. There is actually a fundamental Class that defines "Object" itself. A Class that defines an object will be Based on another Class. When one Class is based on another it can Inherit the features of the base, or Parent, Class. It can also serve to further refine or add to the parent Class. For example in RunUO, there is a Class (or object) called Dagger which is based on BaseKnife which is based on BaseMeleeWeapon which is based on BaseWeapon which is based on Item which is based on Object. Each Class that is based on, or Derived from an earlier Class either adds functionality or refines the definition of the object, or both. There are certain things that all Weapons share so those things are defined in BaseWeapon. Likewise, There are certain things all knives have in common so those are defined in BaseKnife. When we finally get to defining the actual Dagger, there is little left to do but specify the proper graphic to use and how much damage it can do.


RunUO has two base Classes which we as scripters can build on. Those are Item and Mobile. Both Item and Mobile are defined in the RunUO core software and are not available for us to edit or view directly. But we can easily build on them to create new objects. We can define a new Class which is based on either Item or Mobile, or any of the many child Classes already defined in the RunUO distribution scripts. We can add or change Properties which define the "style" of our object or add or change Methods which define the "functionality" of our object. For detailed information on what objects are defined in RunUO, the Hierarchy they are defined in, and what Properties and Methods they expose look at Overview.html in your RunUO\Docs directory (more on this in lesson 2.)


I will introduce one more C# concept as we get started on our script. Namespaces. The concept of namespaces is how C# organizes Classes and other program components. A namespace is a set of related Classes. A namespace may also contain other namespaces. When you make a reference to another Class you must include in your reference the namespace that Class exists within, unless the two share the same namespace. You may complete this reference one of two ways; by explicitly stating the namespace as part of the name of the Class, or by use of the using directive at the beginning of your script. For C# and .NET arguably the most important namespace is called System, for RunUO it is Server. Therefore most RunUO scripts start out with


Code (text):
Code: [Select]
using System;
using Server;


Including those directives (a directive is an instruction to the compiler) allows us free use of any Classes in those namespaces. Next we must identify the namespace our Class exists within, in this case it will be "Server.Items".


Code (text):
Code: [Select]
using System;
using Server;


namespace Server.Items
{


Note that each statement in C# ends with a semicolon, however the namespace keyword indicates that we are identifying a block of code rather than issuing a command. Blocks of code in C# are contained within a matched set of curly braces {}. For every opening brace there must be exactly one closing brace. Mismatched braces are the most common scripting error.


Now we are ready to define our Class. We will be creating a Class called "EightBall" which will be derived from (or based on) the Item Class. Our Class will be accessible from any part of RunUO provided it is referenced properly, therefore it is said to be "public." After the public modifier we declare that this is indeed a Class and it's name followed by a colon and the name of the parent Class which this Class is to be based on. Since our parent or base Class is Item, we inherit all the features of the Item Class. Again note that the class keyword defines a block of code.
Code (text):
Code: [Select]
using System;
using Server;


namespace Server.Items
{
    public class EightBall : Item
    {


You begin here to see the indentation most programmers use when writing code. This helps to keep straight what is happening and at what level. Now we will begin to add Methods to our Class. These methods are C# code that specifies how things are supposed to happen, or the method by which these things occur. The order that we put the methods in does not matter, the compiler takes care of making sure the right one is called at the right time. Normally though you will find methods dealing with creation of the object at the top and methods dealing with saving the object during a World Save at the bottom.


The first method we will add is called the constructor. The constructor is called when an "instance" of the object is created. Every object must have at least one constructor, but often there are two or more. If the object may be placed ingame by a GM it must be marked by the tag "Constructable." Tags immediately precede a method and are contained within square brackets []. The basic form of our constructor is this.
Code (text):
Code: [Select]
       
 [Constructable]
        public EightBall() : base( 0xE2F )
        {
        }
Note the name of the constructor method is the same as the name of the Class and that it is declared as public and marked Constructable. The EightBall Class was previously identified as based on the Item Class, and one of the constructors of the Item Class accepts a single integer value which becomes the ItemID or the number of the graphic used to display the Item. (Graphic numbers may be found using a program called InsideUO.) Therefore when we first create an instance of an EightBall, we tell the base Class we want to use an ItemID of 0xE2F which is hexadecimal for 3631. (You may use either hex or decimal, but most programmers eventually use hex.)


Within the constructor method we should set any properties of the object that need to be set. There are two properties of the Item Class we will set here for all instances of our object. Those are Weight and Name. Our final constructor looks like this.


Code (text):
Code: [Select]
        [Constructable]
        public EightBall() : base( 0xE2F )
        {
            Weight = 1.0;
            Name = "a magic eight ball";
        }


In RunUO all Items also need a "serialization" constructor. This constructor is called for any specific instances of our object during a World Load (when you start up the server.) Normally the serialization constructor contains no code and is not tagged. The constructor must accept a Serial number from the server and pass it on to the serialization constructor of the base Item Class.
Code (text):
Code: [Select]
        public EightBall( Serial serial ) : base( serial )
        {
        }
During every World Save the Serialize method of every object is called. It is the job of the Serialize method to record the state of the object. Any information specific to this instance of this Class should be saved at this time. Keep in mind the base Item class will likewise record any information it is responsible for. In this case all we have done is set the Weight and Name both of which the Item class is responsible for. All we will do is accept the reference to the GenericWriter object and pass it on to the Serialize method of our base Class. We also use the GenericWriter to save one integer, a zero, to denote the version of this object. If we modify the object in the future and must add to the information we save, this number will increment.


Code (text):
Code: [Select]
        public override void Serialize( GenericWriter writer )
        {
            base.Serialize( writer );
            writer.Write( (int) 0 );
        }


Now we will add the Deserialize method. This method is called during a World Load to read back in all the information recorded about this instance of this Class in the Serialize method. Serialize and Deserialize work hand in hand. Any information written by Serialize must be read by Deserialize in the exact same order. This is another source of many scripting errors.
Code (text):


Code: [Select]
       
 public override void Deserialize(GenericReader reader)
        {
            base.Deserialize( reader );
            int version = reader.ReadInt();
        }


Here is the entire script at this point. This is complete in the sense that it will compile and may be created ingame. At its most basic form this is a RunUO Item.


Code (text):
Code: [Select]
using System;
using Server;


namespace Server.Items
{
    public class EightBall : Item
    {
        [Constructable]
        public EightBall() : base( 0xE2F )
        {
            Weight = 1.0;
            Name = "a magic eight ball";
        }


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


        public override void Serialize( GenericWriter writer )
        {
            base.Serialize( writer );
            writer.Write( (int) 0 );
        }
       
        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize( reader );
            int version = reader.ReadInt();
        }
    }
}


Next we will add some functionality to our EightBall Class. We will cause the EightBall to send a message to the Player when it is double clicked. Looking in the RunUO Docs, we can find a list of all the methods and properties of the Item Class. One of those is shown as
RunUO Docs said:
virtual void OnDoubleClick( Mobile from )
Virtual means that this method may be overridden by a child Class to change it's functionality. Void means that this method does not have a return value, whatever calls it won't get anything back. OnDoubleClick is the name of the method, and the method expects to receive a reference to an object of type Mobile which this method will call "from." Since the method will be called from elsewhere, we will also declare it public. When you override a virtual method you may not add to or remove any of the parameters of the method, and you must change "virtual" to "override."
Code (text):


     
Code: [Select]
 
   public override void OnDoubleClick( Mobile from )
        {
     


So at this point the object knows it was double clicked and by whom. Next we will call one of the Random methods of the Utility Class to generate a random number between 0 and 7 (eight choices.) Since Utility is in the Server namespace included at the beginning of the script we are ready to go. We will use the random number as the argument of a switch statement, which will send a different message to the Player for each possible random number.
Code (text):


Code: [Select]
        public override void OnDoubleClick( Mobile from )
        {
            switch ( Utility.Random( 8 ) )
            {
                default:
                case  0: from.SendMessage( "IT IS CERTAIN" ); break;
                case  1: from.SendMessage( "WITHOUT A DOUBT" ); break;
                case  2: from.SendMessage( "MY REPLY IS NO" ); break;
                case  3: from.SendMessage( "ASK AGAIN LATER" ); break;
                case  4: from.SendMessage( "VERY DOUBTFUL" ); break;
                case  5: from.SendMessage( "CONCENTRATE AND ASK AGAIN" ); break;
                case  6: from.SendMessage( "DON'T COUNT ON IT" ); break;
                case  7: from.SendMessage( "YES" ); break;
            }
        }


I took a common shortcut in each case statement. Technically, each case defines a block of code and as such should be enclosed in braces. Instead I placed the entire block on one line, still using a semicolon after each statement. I is important here to know that this


Code (text):
Code: [Select]
        case  0: from.SendMessage( "IT IS CERTAIN" ); break;
is the same as this
Code (text):
        case  0:
        {
            from.SendMessage( "IT IS CERTAIN" );
            break;
        }


Likewise it is important to know that a case statement may not "fall through" to the next statement. Each case must end with a break, a return, or a goto statement.


So finally, our completed script looks like this
Code (text):


Code: [Select]
using System;
using Server;


namespace Server.Items
{
    public class EightBall : Item
    {
        [Constructable]
        public EightBall() : base( 0xE2F )
        {
            Weight = 1.0;
            Name = "a magic eight ball";
        }


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


        public override void Serialize( GenericWriter writer )
        {
            base.Serialize( writer );
            writer.Write( (int) 0 );
        }
       
        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize( reader );
            int version = reader.ReadInt();
        }


        public override void OnDoubleClick( Mobile from )
        {
            switch ( Utility.Random( 8 ) )
            {
                default:
                case  0: from.SendMessage( "IT IS CERTAIN" ); break;
                case  1: from.SendMessage( "WITHOUT A DOUBT" ); break;
                case  2: from.SendMessage( "MY REPLY IS NO" ); break;
                case  3: from.SendMessage( "ASK AGAIN LATER" ); break;
                case  4: from.SendMessage( "VERY DOUBTFUL" ); break;
                case  5: from.SendMessage( "CONCENTRATE AND ASK AGAIN" ); break;
                case  6: from.SendMessage( "DON'T COUNT ON IT" ); break;
                case  7: from.SendMessage( "YES" ); break;
            }
        }
    }
}


Hopefully you now have a good understanding of how a basic script is created, all credit goes to David from the RunUO forums for providing code snippets and text for the tutorial above.
« Last Edit: July 13, 2013, 09:27:01 PM by Kyn »

July 14, 2013, 06:33:52 AM
Reply #1

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
Reading over this I had an idea for a script that I am going to play with.  The question that comes up is if i want to take this magic 8 ball and instead of it sending text it were to generate a random item from a list.  Would I have to create a separate script for each item I am generating or can they all be declared within the script then at the end have the random rules for the generation of the random item?
Just a ghost

July 14, 2013, 06:35:34 AM
Reply #2

Offline Kyn

  • Hero Member
  • *****
  • Posts: 534
  • Karma: +232/-35
Reading over this I had an idea for a script that I am going to play with.  The question that comes up is if i want to take this magic 8 ball and instead of it sending text it were to generate a random item from a list.  Would I have to create a separate script for each item I am generating or can they all be declared within the script then at the end have the random rules for the generation of the random item?


I would create a separate  script for each item to keep it organized and then in the eight ball script I would call each one for the random utility. Sounds like your really learning Keldon. I'm very happy to see someone taking a serious interest!

July 14, 2013, 06:38:00 AM
Reply #3

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
I took some programming in high school and always loved it.  If I did not fall into the degree and job I am currently working I would have pressed on into a career in programming.


After I get some stuff typed up I will post them for you to look over.  First I need to dig out my D&D books, it will be a replica of an item from there.
Just a ghost

July 14, 2013, 06:38:58 AM
Reply #4

Offline Kyn

  • Hero Member
  • *****
  • Posts: 534
  • Karma: +232/-35
I took some programming in high school and always loved it.  If I did not fall into the degree and job I am currently working I would have pressed on into a career in programming.


After I get some stuff typed up I will post them for you to look over.  First I need to dig out my D&D books, it will be a replica of an item from there.


Awesome, I look forward to seeing what you come up with. :)

July 14, 2013, 03:54:31 PM
Reply #5

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
Kyn,


Can you take a look at this so far?  The general basis is a box in your inventory is smashed to spawn 1 of 100 different items.  If I am reading things right so far the only arguments for the 100 cases is case 0 which is basically you got nothing and case 1 which will spawn a dagger in your backpack, tell you "The box shatters to reveal the weapon inside", and delete the box you smashed.


I would like you to take a look at it before I make an error I would end up repeating possibly 100 times.


End result of the script is going to be a box that basically simulates a d100 roll for items.  The first 75 or so items will be non magical mundane items.  About 20 items of existing artifact weapons and 5 custom weapons.
Just a ghost

July 14, 2013, 05:15:23 PM
Reply #6

Offline Montmatre

  • Newbie
  • *
  • Posts: 23
  • Karma: +1/-0
Nice work Keldon :), always glad to see people expanding out in the c# scene.


Bravo Kyn, lets get more scripts up and running , can't have too many of those :P. Seriously though a lot of people think of coding as a prison wall, it isn't just get in there!

July 14, 2013, 05:58:02 PM
Reply #7

Offline Kyn

  • Hero Member
  • *****
  • Posts: 534
  • Karma: +232/-35
Kyn,


Can you take a look at this so far?  The general basis is a box in your inventory is smashed to spawn 1 of 100 different items.  If I am reading things right so far the only arguments for the 100 cases is case 0 which is basically you got nothing and case 1 which will spawn a dagger in your backpack, tell you "The box shatters to reveal the weapon inside", and delete the box you smashed.


I would like you to take a look at it before I make an error I would end up repeating possibly 100 times.


End result of the script is going to be a box that basically simulates a d100 roll for items.  The first 75 or so items will be non magical mundane items.  About 20 items of existing artifact weapons and 5 custom weapons.


So far so good man! All I can say is remember your opening and closing braces and keep at it. :)  - also make sure that all C# scripts are .cs files instead of .txt

July 14, 2013, 06:11:18 PM
Reply #8

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
for making them .cs files, certain program or when I save them can i selected them as .cs?  I am just using notepad for writing currently.
« Last Edit: July 14, 2013, 06:22:41 PM by Keldon »
Just a ghost

July 14, 2013, 06:41:28 PM
Reply #9

Offline Kyn

  • Hero Member
  • *****
  • Posts: 534
  • Karma: +232/-35
for making them .cs files, certain program or when I save them can i selected them as .cs?  I am just using notepad for writing currently.


When you use notepad its as simple as going to File -> Save as -> then change the save type to all files then change the .txt to a .cs


Simple as that :)

February 12, 2014, 07:27:04 PM
Reply #10

Offline Jericho

  • Newbie
  • *
  • Posts: 10
  • Karma: +0/-0
So after reading this and following this tutorial i made an Item based off this idea, but when i go to compile it to see if it works i get an error saying New line in constant on lines 34 and 35, can you help please?

February 12, 2014, 07:42:14 PM
Reply #11

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
So after reading this and following this tutorial i made an Item based off this idea, but when i go to compile it to see if it works i get an error saying New line in constant on lines 34 and 35, can you help please?


There are actually 2 issues:


First here is the section of code where your compiler is hanging up.


Code: [Select]

switch ( Utility.Random( 2) )
{
default:
case 0: from.SendMessage ( "HEADS! ); break;
case 1: from.SendMessage ( "TAILS! ); break;
}
}


When programming every time you have a { you have to close it with a }.  This is the same for ( needs to be closed with a ) and a " needs to be closed with another ".  Take a look at your code and see if you can find your error.


Also, Kyn correct me if I am wrong, when you have a Serialize you need a Deserialize.  These are the routines that the server uses to convert all items and mobs in the world into and out of the save files.  Without both the items will not move in and out of the save files properly.


What program are you using to write your scripts in?
Just a ghost

February 12, 2014, 07:53:56 PM
Reply #12

Offline Jericho

  • Newbie
  • *
  • Posts: 10
  • Karma: +0/-0
yeah i forgot about the deserialize my bad haha but i dont see any open braces or brackets i forgot about :/ and im on a new computer so i never got any developer programs on my computer and my computer for some reason(assuming my connection because im on a switch(dad's idea)) because they wont fully download and work, so i take a script i already had in CS form and wipe it and start a new so i dont have to worry about making the CS file

I have 8 open and 8 closed braces and 15 open ( and 15 closed )
« Last Edit: February 12, 2014, 07:57:36 PM by Jericho »

February 12, 2014, 08:15:06 PM
Reply #13

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
yeah i forgot about the deserialize my bad haha but i dont see any open braces or brackets i forgot about :/ and im on a new computer so i never got any developer programs on my computer and my computer for some reason(assuming my connection because im on a switch(dad's idea)) because they wont fully download and work, so i take a script i already had in CS form and wipe it and start a new so i dont have to worry about making the CS file

I have 8 open and 8 closed braces and 15 open ( and 15 closed )


Ok you do not have any open {} or ().  What about the 3rd item I listed?
Just a ghost

February 12, 2014, 08:23:41 PM
Reply #14

Offline Jericho

  • Newbie
  • *
  • Posts: 10
  • Karma: +0/-0
what do you mean? i only have a random of 2 items and i have 2 cases

February 12, 2014, 08:27:27 PM
Reply #15

Offline Jericho

  • Newbie
  • *
  • Posts: 10
  • Karma: +0/-0
looking back at Kyn's eightball example script(which is the same layout as my quarter) and its the exact same minus the name and the added hue
« Last Edit: February 12, 2014, 08:33:44 PM by Jericho »

February 12, 2014, 10:05:49 PM
Reply #16

Offline Minos

  • Full Member
  • ***
  • Posts: 244
  • Karma: +42/-11
Visual Studio Express will help you spot some really simple errors, like forgetting to close the strings with a ".

February 12, 2014, 10:35:36 PM
Reply #17

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
Visual Studio Express will help you spot some really simple errors, like forgetting to close the strings with a ".


Jericho, both of these pieces of advice are sound.


Look at Kyn's script again.  All of his text to the player is in this format:
Code: [Select]
from.SendMessage("What you want the player to know");


When you what to say something it is a string.  Strings must be enclosed in a pair of ".
Just a ghost

February 12, 2014, 11:03:47 PM
Reply #18

Offline Jericho

  • Newbie
  • *
  • Posts: 10
  • Karma: +0/-0
oh i found my problem lol i wasnt paying attention to my speech brackets thats totally my bad, thanks for the help guys

February 12, 2014, 11:10:07 PM
Reply #19

Offline Jericho

  • Newbie
  • *
  • Posts: 10
  • Karma: +0/-0
alright final, tested and everything

February 12, 2014, 11:26:50 PM
Reply #20

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
Congrats!


I am at work so I have to view it in notepad but I do not see any issues.  Maybe this item could have extra functions.  Like along with saying Heads or Tails it also heals them or hurts them (or my favorite hurt as in kill them).
Just a ghost

February 13, 2014, 08:33:31 AM
Reply #21

Offline Jericho

  • Newbie
  • *
  • Posts: 10
  • Karma: +0/-0
i compiled it and tested it and it works :) my next question is with that thought you just gave me instead of implementing into the quarter i came up with a new idea, but my question is how or what would be the base code for killing someone within a utility random?

February 13, 2014, 08:41:20 AM
Reply #22

Offline Keldon

  • Full Member
  • ***
  • Posts: 235
  • Karma: +26/-0
Look at this thread:


http://uoevolution.com/forum/index.php?topic=5890.0


That is one of the threads that Kyn suggested you take a look at.  That is a quick little tutorial i wrote on giving a weapon hidden procs.
« Last Edit: February 13, 2014, 09:08:22 AM by Keldon »
Just a ghost