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!

Scripting for Dummies: Troubling Things That Start With "T" Part 1: Timers

Status
Not open for further replies.

David

Moderate
Scripting for Dummies: Troubling Things That Start With "T" Part 1: Timers

As with my other tutorials, the ones you will find here are written for newer scripters. I will attempt to explain the chosen topics in enough (but not too much) detail for a good basic understanding. Please DO NOT post support questions in this thread as they will simply be removed.

I highly recommend that you have some knowledge of C# before beginning any RunUO script, and I recommend the thread “Scripting for Dummies: Creating an Item” if you have not yet written your first successful RunUO script. This series of lessons assumes you understand the key C# concepts such as Classes and Methods, as well as a familiarity with RunUO.

In this lesson we will cover the basics of a Timer by building one step by step. Since it will be easier to explain without any extraneous code, this timer will be in a class by itself. Often you will see a timer embedded in another script which we will get too later or added to the end of another script. There is no real difference between a stand-alone script with a timer class and a timer class added to the end of another script. The second simply makes keeping the script and the timer it uses easier to keep together.

Our timer will serve a somewhat useful purpose for those building quest npc’s or such. It will delete a given Mobile after a certain amount of time and optionally cause the Mobile to say something first. Not only will we explore timers, but we will delve into Overloading—a way to have more than one entry point into a given method or procedure.

Let’s get started.

First off, as with any good RunUO script we need to reference the System and Server namespaces. It is hard to write a script that does not use something from those two.
Code:
using System;
using Server;
Next we need to declare a namespace for our class. Since the timer deals specifically with Mobiles, the easiest thing to do is to put it in the Mobile namespace. It could go elsewhere but you would need to add “using Server.Mobiles;” to the above directives.
Code:
namespace Server.Mobiles
{
Note the opening brace, it must have exactly one matching closing brace at the end of the code denoting that everything in between is in the namespace declaration we just made. I cannot stress enough that every opening brace must have exactly one closing brace.

Now we will declare the class itself, it will be public so we can use it from elsewhere, and it will be derived from the base class “Timer.”
Code:
    public class MobileDeleteTimer : Timer
    {
So at this point our code is:
Code:
using System;
using Server;

namespace Server.Mobiles
{
    public class MobileDeleteTimer : Timer
    {
Just as when we create an Item or a Mobile, there are certain variables we need at the class level so that we have access to them in any of the class’s methods. To start with, we will need to know what Mobile we are acting on. We will declare it as private since we do not need it outside of this class, and we will call it “mob.”
Code:
        private Mobile mob;
We will come back to this point after the script evolves a bit.

Next we need the constructor for our timer. This is called when the timer is created—when it is called with the “new” keyword. If we look in the Docs, we will see there are three Timer constructors (ctor) we can call our base class with, the one we will use is Timer( TimeSpan delay ) which you see takes one TimeSpan which is the delay before the timer “ticks.” For now I am going to hard code it to 10 seconds. We will make that adjustable later.
Code:
        public MobileDeleteTimer( Mobile m ) : base( TimeSpan.FromSeconds( 10 ) )
        {
Now when we are creating our timer, there are a few things we need to do. First we need to fetch the reference to the Mobile that was passed and store it in our variable we defined at the class level so we can use it later. Next we are going to set the timer’s “priority.”
Code:
            mob = m;
            Priority = TimerPriority.OneSecond;
Priority is a property of the base timer class and accepts one of the TimerPriority enumerations (enum.) Those enums are
  • EveryTick
  • TenMS
  • TwentyFiveMS
  • FiftyMS
  • TwoFiftyMS
  • OneSecond
  • FiveSeconds
  • OneMinute
The Priority determines how often the timer is “checked” too low a value adds overhead to your server, too high a number may mean the timer fires later than expected. Use whichever one seems appropriate for your needs.

Our complete constructor is
Code:
        public MobileDeleteTimer( Mobile m ) : base( TimeSpan.FromSeconds( 10 ) )
        {
            mob = m;
            Priority = TimerPriority.OneSecond;
        }

Next we will Override the OnTick() method of the base Timer class. The OnTick() method is called every time the timer fires (or ticks.) When you override a method, you may not change the signature of the method in any way. Meaning you cannot use a different number or type of parameters, and it must be declared in the same way as in the base class, with one required exception… a method which can be overridden is declared virtual a method which is doing the overriding is declared override. So we have
Code:
        protected override void OnTick()
        {
Now, and this becomes more important when dealing with timers, the very first thing we should do is make sure our target still exists. Remember it is now 10 seconds later than when the timer was created, a lot can happen. We will check to see if the Mobile is gone completely, (is null) and if it is deleted but still in the cleanup stage. If either is the case, we will stop the timer and exit.
Code:
            if( mob == null || mob.Deleted )
            {
                this.Stop();
                return;
            }
Now, since we are comfortable that our Mobile exists, it is a simple matter to make it no longer exist. We simply add one more line to our OnTick() code.
Code:
            mob.Delete();

The complete class at this point (with closing braces in place) is:
Code:
using System;
using Server;

namespace Server.Mobiles
{
    public class MobileDeleteTimer : Timer
    {
        private Mobile mob;
        
        public MobileDeleteTimer( Mobile m ) : base( TimeSpan.FromSeconds( 10 ) )
        {
            mob = m;
            Priority = TimerPriority.OneSecond;
        }

        protected override void OnTick()
        {
            if( mob == null || mob.Deleted )
            {
                Stop();
                return;
            }

            mob.Delete();
        }
    }
}
This script does the basic job of deleting a Mobile 10 seconds after it is called. To use it from another script you would “instantiate” or create a MobileDeleteTimer with the reference to the Mobile you want to delete, then start the timer. For example
Code:
    Timer m_timer = new MobileDeleteTimer( this ); 
    m_timer.Start();

Tomorrow we will add some features to make the timer more useful.
 

David

Moderate
Overloading the constructor

Now that we have a working timer, we will add some features to it in an effort to make it more useful. The first thing we will do is add a way to adjust the delay… But, suppose we also like the 10 second delay—that will be good for most cases; we just want to be able to change it if we want to.

It would be nice if we could create the timer most of the time just the way it is…
Code:
Timer m_timer = new MobileDeleteTimer( this );
But sometimes be able to add a delay setting…
Code:
Timer m_timer = new MobileDeleteTimer( this, TimeSpan.FromSeconds( 30 )

That calls for a technique called overloading, which we will do to the constructor. When you overload a method, each version of the method must have a different signature—that is it must require different types of parameters. You can for example have one version of a method that requires an integer and a boolean, and another that requires a string and a boolean. You cannot have one that requires an integer and a boolean then another that requires a different integer and a boolean. The compiler must very clearly know which version to use based on what types of information is passed to the method.

As you recall our current constructor is
Code:
        public MobileDeleteTimer( Mobile m ) : base( TimeSpan.FromSeconds( 10 ) )
        {
            mob = m;
            Priority = TimerPriority.OneSecond;
        }
We don’t really want to change anything inside the constructor—which is good. Overloaded methods should behave as closely as possible to each other at all times. So we will actually call one constructor from the other by changing where it is derived from. We want the method above to be the version that ultimately gets executed, so first we will adjust it to require a TimeSpan for the delay which it will pass immediately to the base Timer constructor.
Code:
        public MobileDeleteTimer( Mobile m, TimeSpan delay ) : base( delay )
        {
            mob = m;
            Priority = TimerPriority.OneSecond;
        }
Next we will create the overload—which will actually have the signature of our original version.
Code:
        public MobileDeleteTimer( Mobile m ) : this( m, TimeSpan.FromSeconds( 10 ) )
        {
        }
Note that this version has the same name (all constructors have the same name as the class they are in) but different parameters; only a Mobile rather than a Mobile and a TimeSpan. However all it does is add the hard coded TimeSpan and call the appropriate constructor of it’s own class via the “this” keyword.

Here is the complete script with our first overloaded constructor.
Code:
using System;
using Server;

namespace Server.Mobiles
{
    public class MobileDeleteTimer : Timer
    {
        private Mobile mob;

        public MobileDeleteTimer( Mobile m ) : this( m, TimeSpan.FromSeconds( 10 ) )
        {
        }

        public MobileDeleteTimer( Mobile m, TimeSpan delay ) : base( delay )
        {
            mob = m;
            Priority = TimerPriority.OneSecond;
        }

        protected override void OnTick()
        {
            if( mob == null || mob.Deleted )
            {
                Stop();
                return;
            }

            mob.Delete();
        }
    }
}

Now an npc can delete itself by calling the timer in either of these fashions;
Code:
Timer m_timer = new MobileDeleteTimer( this );
or…
Code:
Timer m_timer = new MobileDeleteTimer( this, TimeSpan.FromSeconds( 30 )
 

David

Moderate
Another Option

Overloading wasn’t so bad. Lets jump right into the next modification (which will require a couple more overloads.) This time we are going to give the timer the ability to have the Mobile say something before it disappears.

We need to change the primary constructor to accept a string as a parameter and then after a sanity check on the Mobile in question, and if the text is not null (if there is actually something to say,) have the Mobile speak the string. The updated constructor will appear as so…
Code:
        public MobileDeleteTimer( Mobile m, String toSay, TimeSpan delay ) : base( delay )
        {
            mob = m;
            Priority = TimerPriority.OneSecond;

            if( mob != null && toSay != null ) 
                mob.Say( toSay );
        }
Pretty easy, huh? Notice here that the timer constructor is just another method that can execute code.

However, now the overloaded constructor is calling a primary constructor that no longer exists. It must be modified as well. Since we still want a constructor that only requires a Mobile as a parameter, all we need to do is pass a null to our primary constructor where it is expecting a string. That should be enough to make it happy.
Code:
        public MobileDeleteTimer( Mobile m ) : this( m, null, TimeSpan.FromSeconds( 10 ) )
        {
        }

Finally, there is one condition we do not have accounted for. Perhaps we will want to use the default delay but pass a string to be spoken. We only have to add one more constructor that satisfies our requirements…
Code:
        public MobileDeleteTimer( Mobile m, String toSay ) : this( m, toSay, TimeSpan.FromSeconds( 10 ) )
        {
        }
Note that we still have the hard coded 10 second delay, but allow the scripter the option of declaring a Mobile and a text string when the timer is created. Again if we use the example of an npc script that will delete itself…
Code:
    Timer m_timer = new MobileDeleteTimer( this, “Thank you my lord, I will be going now.” ); 
    m_timer.Start();

Our completed code:
Code:
using System;
using Server;

namespace Server.Mobiles
{
    public class MobileDeleteTimer : Timer
    {
        private Mobile mob;

        public MobileDeleteTimer( Mobile m ) : this( m, null, TimeSpan.FromSeconds( 10 ) )
        {
        }

        public MobileDeleteTimer( Mobile m, String toSay ) : this( m, toSay, TimeSpan.FromSeconds( 10 ) )
        {
        }

        public MobileDeleteTimer( Mobile m, String toSay, TimeSpan delay ) : base( delay )
        {
            mob = m;
            Priority = TimerPriority.OneSecond;

            if( toSay != null ) 
                mob.Say( toSay );
        }

        protected override void OnTick()
        {
            if( mob == null || mob.Deleted )
            {
                Stop();
                return;
            }

            mob.Delete();
        }
    }
}

There are three constructors. One requires a Mobile, another requires a Mobile and a String, and the third requires a Mobile, a String, and TimeSpan. All three signatures are different. All the first two really do however, is fill in the missing information and pass it all on the the third version.

We have now built a basic timer that has several options and serves a useful purpose. However timers still have a few more tricks up their sleeves.
 

David

Moderate
Review

A simple one-shot timer.
  • To create a Timer, we first create a Class which is derived from the base Timer class. Any variables that will be used in more than one method (constructor and OnTick() ) must be declared here.
  • We then create one or more constructors for the Timer which will be called when the Timer is first initialized.
  • Finally we override the OnTick() method to provide the code which will execute when the Timer’s time has expired.
 

David

Moderate
Now lets dig a little deeper into timers. Looking at the RunUO docs, we see the Timer class has a handful of methods and properties. However only a few are normally used by scripters, those are:

Timer Constructors
  • Timer( TimeSpan delay ) – A “single shot” timer which times out once then stops.
  • Timer( TimeSpan delay, TimeSpan interval ) – A repeating timer.
  • Timer( TimeSpan delay, TimeSpan interval, int count ) – A repeating timer that stops after a set number of cycles.
Timer Properties
  • Priority – Use to set the Priority of the timer to adjust demand on system resources. Read or Write using a member of the TimerPriority enum.
  • Running – Use to determine if the timer is in a running state. Property is Read/Write, however the Start() and Stop() methods are normally used to change the state.
Timer Methods
  • OnTick() – A virtual method which is called each time the timer “ticks.” Normally this method is overridden to perform the task for which the timer is intended.
  • Start() – Used to start the Timer
  • Stop() – Used to stop the Timer

A repeating timer is not much different than the single shot timer detailed above. Here is the complete class for a repeating timer. It will cause a given Mobile to emote some text on a regular interval until the timer is stopped.
Code:
using System;
using Server;

namespace Server.Mobiles
{
    public class MobileEmoteTimer : Timer
    {
        private Mobile mob;
        private string something;

        public MobileEmoteTimer( Mobile m, string emote, TimeSpan delay ) : base( delay, delay )
        {
            mob = m;
            something = emote;
            Priority = TimerPriority.TwoFiftyMS; // timer is checked every quarter second
        }

        protected override void OnTick()
        {
            if( mob == null || mob.Deleted )
            {
                Stop();
                return;
            }

            mob.Emote( "*" + something + "*" );
        }
    }
}
It is of course intentionally very similar to the first example. There is really only one line we need to examine.
Code:
 public MobileEmoteTimer( Mobile m, string emote, TimeSpan delay ) : base( delay, delay )
You will notice that the timer requires a TimeSpan as the third argument. This TimeSpan is immediately passed to the base Timer constructor twice—it is used for the initial delay before the first tick as well as the interval between each of the subsequent ticks.

In the OnTick() method we check for the existence of our Mobile, if it has been deleted we stop the timer to avoid a shard crashing null reference exception. Otherwise the Mobile emotes our text. Note there is no other provision in this code to stop the timer; that should be handled from the same script that calls the timer.
 
Status
Not open for further replies.
Top