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!

Handler Hooking

Jeff

Lord
One of the biggest things I see in the Custom Script Release forum on RunUO.com is distro and core mods that have to be made in order to use a script. Being that I hate having to track these, and support issues that arise from beginners that do not quite understand C#. I generally try to make all my scripts require as few of these edits as possible. Editing the distro/core code can get messy when you have several systems that all function off the same core elements (notoriety for example). So, to address these issues, I've come up with some solutions. Over then next few posts I will explain how to get around editing distro/core scripts so that you can make more plug-able code and reduce the number of support issues due to novice user mistake.

Lets start with the most commonly used and most difficult to modify (due to heavy branching) portion of RunUO, notoriety. First, let me explain how notoriety works from a high level. When the client requests the hue of a mobiles name, or if 1 mobile can use a harmful and/or beneficial action on another mobile it calls delegates that take the source mobile and that target mobile and decides if the action is doable, or the color of the hue the mobile should have. If you look in Scripts/Misc/Notoriety.cs you will see a few things...
Code:
		public static void Initialize()
		{
			...

			Notoriety.Handler = new NotorietyHandler( MobileNotoriety );

			Mobile.AllowBeneficialHandler = new AllowBeneficialHandler( Mobile_AllowBeneficial );
			Mobile.AllowHarmfulHandler = new AllowHarmfulHandler( Mobile_AllowHarmful );
		}
When the server is successful in compiling the scripts, the first thing it does is searches every single type for these 2 methods:
Code:
public static void Configure()
public static void Initialize()
It looks in for Configure first, then Initialize. It also executes them in that order. So, from this we can see that when Initialize is called in the Notoriety script it attaches 3 functions used to determine name hue, beneficial action and harmful action. These are the functions that will be called when ever the system needs to know any of those 3 action results. For most users, they will edit this file directly, release a "how to install" guide and expect the user themselves to add the proper edits. Now... there are 2 inherent problems with this. The biggest one is "Order of operation", if the custom code is not put in the proper spot, an unwanted result can occur since we could pass a specific check before we got to the custom code. The second problem is code cleanliness. As the user adds more and more scripts that modify this code, it becomes long, nested, and hard to read. So, lets look at the solution.

In programming there are countless numbers of design patterns, each having there own use case. One of those patterns is known as "Chain-of-responsibility" pattern. A simple way to explain this pattern would be to think of a Chain. A chain can have anywhere number of links on it, even 1 of you really wanted to. Now, using this mental image of a chain, lets think of each link on the chain as a function call that can return a result. As we need more functionality, we can add a new link to the chain. Since each link knows about it's successor link, if we decide we do not need to do anything with the arguments supplied, we can pass it on to the successor link, and so on, and so on till a result is produced.

If you want to know more about this pattern here is a link to Wikipedia explaining it more in detail http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern.

Now, lets see how we can adapt this to Notoriety. Here is the Notoriety chain class I've created
Code:
    public abstract class NotorietyHandlerChain
    {
        private readonly AllowBeneficialHandler _allowBeneficialHandlerSuccessor;
        private readonly AllowHarmfulHandler _allowHarmfulHandlerSuccessor;
        private readonly NotorietyHandler _notorietyHandlerSuccessor;

        protected NotorietyHandlerChain()
        {
        protected NotorietyHandlerChain()
        {
            // Store the original handlers in some read only private variables 
            // so we can call them later if need be.
            _notorietyHandlerSuccessor = Notoriety.Handler;
            _allowBeneficialHandlerSuccessor = Mobile.AllowBeneficialHandler;
            _allowHarmfulHandlerSuccessor = Mobile.AllowHarmfulHandler;

            // Set the current handlers to our new chain handlers.
            Notoriety.Handler = HandleNotoriety;
            Mobile.AllowBeneficialHandler = AllowBeneficial;
            Mobile.AllowHarmfulHandler = AllowHarmful;
        }

        protected bool InvokeAllowBeneficialHandlerSuccessor(Mobile source, Mobile target)
        {
            return _allowBeneficialHandlerSuccessor(source, target);
        }

        protected bool InvokeAllowHarmfulHandlerSuccessor(Mobile source, Mobile target)
        {
            return _allowHarmfulHandlerSuccessor(source, target);
        }

        protected int InvokeNotorietyHandlerSuccessor(Mobile source, Mobile target)
        {
            return _notorietyHandlerSuccessor(source, target);
        }

        public virtual bool AllowBeneficial(Mobile source, Mobile target)
        {
            // Call the successor since there is no implementation in this base class.
            return InvokeAllowBeneficialHandlerSuccessor(source, target);
        }

        public virtual bool AllowHarmful(Mobile source, Mobile target)
        {
            // Call the successor since there is no implementation in this base class.
            return InvokeAllowHarmfulHandlerSuccessor(source, target);
        }

        public virtual int HandleNotoriety(Mobile source, Mobile target)
        {
            // Call the successor since there is no implementation in this base class.
            return InvokeNotorietyHandlerSuccessor(source, target);
        }
    }
As you can see I've basically stored the previous handler values, and replaced them with the handlers in the class. This allows us to inherit and override functionality as needed. So, lets say for the sake of example we have an event system of some sort that needs to add it's own custom notoriety. Let's see how we would go about doing that. First we need a new class EventNotorietyChain that inherits from our new NotorietyHandlerChain class. In each of the overridable functions from NotorietyHandlerChain, we want to see if the source mobile is in an event, if so let the event object handle the notoriety, if not, invoke the successor. Here is how we would go about doing that
Code:
    public sealed class EventNotorietyChain : NotorietyHandlerChain
    {
        public override int HandleNotoriety(Server.Mobile source, Server.Mobile target)
        {
            EventContext sourceContext = EventController.FindEvent(source);

            if (sourceContext != null && sourceContext.OverridesHandleNotoriety)
                return sourceContext.HandleNotoriety(source, target);

            return base.HandleNotoriety(source, target);
        }

        public override bool AllowBeneficial(Server.Mobile source, Server.Mobile target)
        {
            EventContext sourceContext = EventController.FindEvent(source);

            if (sourceContext != null && sourceContext.OverridesAllowBeneficial)
                return sourceContext.AllowBeneficial(source, target);

            return base.AllowBeneficial(source, target);
        }

        public override bool AllowHarmful(Server.Mobile source, Server.Mobile target)
        {
            EventContext sourceContext = EventController.FindEvent(source);

            if (sourceContext != null && sourceContext.OverridesAllowHarmful)
                return sourceContext.AllowHarmful(source, target);

            return base.AllowHarmful(source, target);
        }
    }
Notice each of the functions calls the base classes handler function. We do this because we know the base class will call the successor as you can see here....
Code:
        public virtual bool AllowBeneficial(Mobile source, Mobile target)
        {
            // Call the successor since there is no implementation in this base class.
            return InvokeAllowBeneficialHandlerSuccessor(source, target);
        }

        public virtual bool AllowHarmful(Mobile source, Mobile target)
        {
            // Call the successor since there is no implementation in this base class.
            return InvokeAllowHarmfulHandlerSuccessor(source, target);
        }

        public virtual int HandleNotoriety(Mobile source, Mobile target)
        {
            // Call the successor since there is no implementation in this base class.
            return InvokeNotorietyHandlerSuccessor(source, target);
        }
The successor calls the next link in the chain until the original Notoriety handler is called which will give a result. This allows us to add as many notoriety systems as we want, without cluttering the distro code, and making it easier to read and debug the newly added notoriety handler.

Here is a quick example of how we would create and register this new chain.
Code:
    public static class EventController
    {
        public static void Initialize()
        {
            new EventNotorietyChain();
            ...
        }

        ...
    }
Pretty simple right? Please let me know if you have any questions about the code above. As I go through these I will release the code on the Custom Script Release forum as a framework that can be used to help write better plug-and-play scripts/systems for RunUO.
 
jeff my Notoriety.cs doesnt have have the code
Code:
public static void Initialize()
        {
            ...

            Notoriety.Handler = new NotorietyHandler( MobileNotoriety );

            Mobile.AllowBeneficialHandler = new AllowBeneficialHandler( Mobile_AllowBeneficial );
            Mobile.AllowHarmfulHandler = new AllowHarmfulHandler( Mobile_AllowHarmful );
        }

and the it is in my sever folder and not in my scripts folder
 

ABCGizmo

Sorceror
After reading the post... Opened up many doors and concepts for me. I might actually start making great custom scripts now without the distro edits. + 10 to the 10th
 
Top