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!

[Tutorial] First Quest -> Bunny Attack

fearyourself

Wanderer
[Tutorial] First Quest -> Bunny Attack

Hi all, I didn't know about this sub-forum so I posted this rant in the Script forum, I'll put it here so that more people can find it ;)

Any optimizations are welcome, I'm only a noob...


Introduction

Ok, I've only been looking through the UO scripting system since yesterday and yeah I've started C# pretty much at the same time. But I tried yesterday to play around with the system and it took me a while to centralize all the information needed to make even the most basic quest...

So I'm starting this thread so that you can tell me what I'm doing wrong or what I'm doing right ;)

Take this thread as a learning rant by my part, tell me where I'm wrong or what I've done badly that way I can correct myself :)

Hopefully the moderators won't be too annoyed but I learnt a bit, wasted lots of time trying to gather in one place this information. If a moderator prefers to delete this thread, don't worry I won't mind ;)

Story of the quest :

Basically this is an excuse to play around with the scripting language : Bob is a mobile we'll be creating and he'll give you the opportunity to have fun killing 10 rabbits (cruel world isn't it?).

If you say yes then 10 rabbits are created next to Bob, if you say no then he'll just ignore you after that...

Now since we're in a MMORPG, it's possible that another player comes around and tries to kill the rabbits, we'll have to handle that later and I'll show two techniques I found to do it ;)

Oh, and I forgot, the reward you get for killing the 10 rabbits is a nice black bear :)


Ok, so that's for the excuse of the quest, now there are only fourfiles that are going to be attached to this quest.

The file explaining who's Bob, the behaviour of Bob's rabbits and the gump you'll get when you talk to bob for the first time and of course a special marker to handle the quest's stage.

Configuration

First things first, in your RunUO directory, go in the Scripts subdirectory and create a Custom subdirectory. When you'll launch RunUO, it goes through the directories and compiles everything that's new (At least I assume it doesn't try to compile everything from scratch when there's a change...)

In this new Custom directory, we'll add a BunnyAttack directory. Thanks to Rosey1's way of putting the files, I made a mobiles, items and gumps directory. I assume it's a classical way of separating types of elements and it does make sense so go ahead and make those three directories.

Let's start with the easiest file : BobsQuestMarker.cs that I'll put in a new directory named markers, itself put in the items folder.

A marker is basically an invisible object that will help the NPCs (and bunnies) to know where the player is up to in the quest. This marker is like any other item in the game, it inherits from the base class Item and, of course, you can add as many variables that you will need to handle your quest.

Some people like to have a certain number of markers to handle their quests. That's ok too but generally it becomes more a hassle to remember what marker is what. Centralizing everything (though you'll be using slightly more memory) is, I think, a good idea...


Code : BobsQuestMarker.cs
Anyhow, here goes ! The beginning of this file just takes care of the includes, defines what namespace we're in (we're in Server.Items because a marker is an item) and we define our new class BobsQuestMarker.

Code:
using System;
using Server.Items;

namespace Server.Items 
{
    public class BobsQuestMarker : Item
    {

Since our quest deals with killing a certain number of rabbits, we'll need a counter to keep track of the number of rabbits the player killed. And since a quest is mainly a state thing (the quest is starting, it's finished, it was refused, etc...) we'll add a enum :

Code:
 public enum BobsQuestState
        {
            START,
            GETREWARD,
            FINISHED,
            IGNORE
        }

        private int pm_counter;
        private BobsQuestState pm_state;

A enum is basically just constants that we'll use. You could say that the variable pm_state is an integer and decide yourself that 0 means the quest is starting, 22 is that the player can go get the reward and 4321 means that the quest is finished. But to have an nice easily readable code, enums is the place to go...

Here, we define 4 states : START means the quest is started, GETREWARD that the player did what was necessary and can get the reward, FINISHED means the quest is finished and IGNORE is the state when the NPC should ignore the player (if the player refused the quest for example)... Of course, you can add states or remove a few depending on your needs...

Now if you notice, I declared the variables private because, as all Object oriented language, we don't like it when people can just change the values. What if someone tries to change the state to a value not handled ? The solution is to define get/set methods and in C# it's done this way :

Code:
        public int Counter
        {
            get {return pm_counter;}
            set {pm_counter = value;}
        }

        public BobsQuestState QuestState
        {
            get { return pm_state; }
            set { pm_state = value; }
        }

This looks complicated but actually it's quite easy... What we have here is basically an easy way to modify the value of the counter or the state. Let's say we had an object BobsQuestMarker named bqm and we wanted to decrement the counter. All we have to do is this :

Code:
//Decrement counter
bqm.Counter--;

What's the difference with putting the p_counter variable public ? In this case, nothing really except that we can change the way the counter is decremented, check when it is decremented, decide NOT to decrement by changing the get/set functions. And of course the rest of the code using this class doesn't have (and doesn't need) to know about it... So it's always a good idea to put your class members private and use these get/set functions...


Anyway, next comes the constructor of this object :

Code:
        public BobsQuestMarker()
            : base(0x176B)
        {
            Weight = 0;
            Name = "BobsQuestMarker";
            Hue = 0;
            pm_counter = 0;
            pm_state = BobsQuestState.START;
            LootType = LootType.Blessed;
            Movable = false;
            Visible = false;
        }

Ok, as you can see here : http://www.runuo.com/forums/showthread.php?t=40091, we pass to the super class base the ItemID to say what graphics to use. Here we use a set of keys sprite. But that doesn't really matter because we set the object's Visible parameter to false. This means that the object is not visible to the normal user (admins will be able to see it).

Of course, the element is not movable, it's blessed so that it doesn't degrade, the counter is put to 0 and the state is the beginning of the quest. We put the weight to 0 and set a name. The name could be a parameter of the constructor to define different markers that have the same kind of information needs (a counter and a state...).

Finally we have the serialization and deserialization methods that need to store and load the counter and the state, this is how we can do it :

Code:
        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
            writer.Write((int) 0);
            writer.Write(pm_counter);
            writer.Write((int) pm_state);
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();

            pm_counter = reader.ReadInt();
            pm_state = (BobsQuestState) reader.ReadInt();
        }

Notice how we have to cast the enum as an integer, I don't know if there other ways to do it but this is generally done so I just followed the flow ;).


As you can see, nothing really complicated here but we now have a really nice marker that can count the number of Rabbits we kill and Bob can see where we are at in the quest by looking in this marker. That is already something quite sweet (at least I think so...)

If I've done anything wrong, please tell me about it, as I've said, this is my first attempt so I might be doing things the wrong way...

The full code is here :
Code:
using System;
using Server.Items;

namespace Server.Items 
{
    public class BobsQuestMarker : Item
    {

        public enum BobsQuestState
        {
            START,
            GETREWARD,
            FINISHED,
            IGNORE
        }

        private int pm_counter;
        private BobsQuestState pm_state;

        public int Counter
        {
            get {return pm_counter;}
            set {pm_counter = value;}
        }

        public BobsQuestState QuestState
        {
            get { return pm_state; }
            set { pm_state = value; }
        }

        public BobsQuestMarker()
            : base(0x176B)
        {
            Weight = 0;
            Name = "BobsQuestMarker";
            Hue = 0;
            pm_counter = 0;
            pm_state = BobsQuestState.START;
            LootType = LootType.Blessed;
            Movable = false;
            Visible = false;
        }

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

        public override void Serialize(GenericWriter writer)
        {
            base.Serialize(writer);
            writer.Write((int) 0);
            writer.Write(pm_counter);
            writer.Write((int) pm_state);
        }

        public override void Deserialize(GenericReader reader)
        {
            base.Deserialize(reader);
            int version = reader.ReadInt();

            pm_counter = reader.ReadInt();
            pm_state = (BobsQuestState) reader.ReadInt();
        }
    }

} 
Jc
**edit daat99: removed quote**
 

fearyourself

Wanderer
Here is the second part :

Introduction

Ok, now let's define Bob... This file will define Bob's behaviour towards the player. Basically he has these behaviours :

- If he's never seen the player,
he'll send a GUMP (a little window) and ask if the player wants to have fun with his rabbits
- If the player has accepted the quest but has not finished,
he'll tell the player to continue and tell him how many rabbits still need to be killed
- If the player has accepted the quest and is waiting for a reward,
he'll create a bear and say "Wow, you've done it, here is your surprise!"
- If the player has finished the quest (got the reward),
he'll say "You've already had fun, now go away"
- If the player has refused the quest,
he'll say "You refused the quest, it is too late now"


So Bob is a mobile, naturally he'll be in the Server.Mobiles namespace.

Code file : Bob.cs
We start off with a certain number of includes :

Code:
using System;
using Server;
using Server.ContextMenus;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using System.Collections;
using System.Collections.Generic;

Nothing really important but just includes for what we want ;)

Anyway, here's the beginning of the class

Code:
namespace Server.Mobiles
{
    [CorpseName("a noble corpse")]
    public class Bob : Mobile
    {
        public virtual bool IsInvulnerable { get { return true; } }

So we defined Bob as being a mobile. We can also define what will display over his corpse (morbid thought...). Of course this is a paradox since we define him as being invulnerable but this is just an explaination thread so... If you don't define that IsInvulnerable function then you could kill bob, IF you can touch him because he does have 50 as armour and is blessed ! :)

Anyway, after that we have the constructor that defines if Bob is a woman or a man, his name, his clothes, his hair color, is he blessed, etc.


Code:
        [Constructable]
        public Bob()
        {
            Name = "Bob";
            Body = 400;
            VirtualArmor = 50;
            CantWalk = true;
            Female = false;

            HairItemID = 0x203B;
            HairHue = 0x1;

            AddItem(new Server.Items.LongPants(0x1));
            AddItem(new Server.Items.FancyShirt(0x481)); ;
            AddItem(new Server.Items.ThighBoots(0x1));
            AddItem(new Server.Items.FeatheredHat(0x1));
            AddItem(new Server.Items.Cloak(0x485));

            Blessed = true;
        }

We then have the serialization methods that don't have anything special since we haven't added any internal members to Bob's class.

Code:
        public Bob(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();
        }


Ok now, what we want is to handle his behavior when we talk to him. What we want to do is add a new entry in the ContextMenu, this is done by adding a BobEntry class and a instance to the ContextMenu :

Code:
        public override void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
        {
            base.GetContextMenuEntries(from, list);
            list.Add(new BobEntry(from, this));
        }

This new class basically will add a element to the list popup when you click on Bob :

Code:
        public class BobEntry : ContextMenuEntry
        {
            private Mobile m_Mobile;
            private Mobile m_Giver;

            public BobEntry(Mobile from, Mobile giver)
                : base(6146, 3)
            {
                m_Mobile = from;
                m_Giver = giver;
            }

Ok a few things that are important :
- We have two private members that give us the NPC that is clicked on and the player that clicked on the NPC. This is really important since it can give us the possibility to check where the player is up to in the quest... So m_Mobile is the player and m_Giver is the NPC (Bob in our case)

- The constructor of BobEntry calls the superclass with two parameters : 6146 is the code to say that it is a TextEntry (but I have been unable to find where all the codes are defined and I only suppose this code is for that). The second parameter is the range for this action. That means that the distance between the player and Bob must be less than 3 to be able to talk to him.

Now we implement only one function in this class : OnClick :

First thing is to check we have a mobile that has clicked on this NPC (I don't think it's possible that m_Mobile be null but let's be paranoid...

Code:
            public override void OnClick()
            {
                PlayerMobile mobile = (PlayerMobile)m_Mobile;

                if (mobile != null)
                {

Ok, if we do have a mobile, we can check if he has a BobsQuestMarker (the marker we created before, remember?).

Code:
                    BobsQuestMarker bqm = mobile.Backpack.FindItemByType(typeof(BobsQuestMarker)) as BobsQuestMarker;

Notice how we check this, we go in the Backpack and find a type of BobsQuestMarker. This works since we only have one type of marker per player. But we could also use the name of the marker if we had multiple instances of the same class of marker. It really depends on what you are doing and here it's the same so I'll present this one ;)

If the player does not have the marker bqm will be null. If so, we'll popup a gump (a little window) to ask the player if he wants to start the quest. Needless to say that if the player accepts, the gump will add the marker to the player.

If the player refuses then it's your choice. You can either do nothing and, when the player retalks to Bob, he'll ask if the player wants the quest again. Or you can add the marker and set it somehow so that Bob knows the player refused the quest.

I chose the later solution since it's a little bit more interesting...

Code:
		    //No marker
                    if (bqm == null)
                    {
                        mobile.SendGump(new StartQuest());
                    }

In the case there is the marker then we have to look at the QuestState (remember the enum we defined ?). We'll use a switch to go through the different states the quest can have :

Code:
                    else
                    {
                        switch (bqm.QuestState)
                        {
Ok now this is where it becomes tricky :

- If the player has just started the quest or is doing it, the state is START, thus Bob will remind the player how many rabbits still need to be killed :

Code:
                            case BobsQuestMarker.BobsQuestState.START:
                                m_Giver.Say("You've got work to do : " + bqm.Counter + " rabbits to kill !");
                                break;

Seems logical no ? To get the enum value, we have to go in the BobsQuestMarker class and use the name of the enum... It seems long but it's quite straight forward.

Then, if it's in the START state, Bob will say You've got work to do : 4 rabbits to kill ! for example. This is done using the m_Giver (that's Bob, he's the one who Gives the event). Using the method Say we can make him say something and we'll use the counter in the marker to make him keep count for the player.

Next state is when the player has finished killing the monsters :

Code:
                            case BobsQuestMarker.BobsQuestState.GETREWARD:

                                bqm.QuestState = BobsQuestMarker.BobsQuestState.FINISHED;

                                m_Giver.Say("Wow, you've done it, here is your surprise!");
                                m_Giver.Say("A bear!");

                                Point3D pt = m_Giver.Location;

                                pt.X += Utility.Random(6) - 3;
                                pt.Y += Utility.Random(6) - 3;

                                BlackBear bear = new BlackBear();
                                bear.MoveToWorld(pt, Map.Trammel);
                                break;

Ok, we're in the middle of more complications. If the state is GETREWARD, then we want Bob to pass the state to FINISHED which we do straight away. This means the player can only get once the reward, which is for the best!

Next, we make him say two phrases to interact with the player. And now we want to create a bear somewhere nearby. Of course, we could make it appear at a fixed place but I prefer to randomize it and use Bob's current position. This means that an admin can put Bob anywhere in the world and the script will still work !

So we get the location of m_Giver (Bob in our case), we add to the position a random number between [-3,3] (though depending if Random does [0,5] or [0,6], it could be just [-3,5]). That's not really important in this case so I didn't spend too much time looking into the implementation of Random ;)


Once we've decided on the position, we create the BlackBear and move it in the world. There should be a way to get the map Bob is in but I haven't taken the time to look into that yet either.

Note: Normally, we should check that the point is in the Map since if Bob is too close to the edge the bear might be out of the map. This can be a problem if the function MoveToWorld doesn't check the position we want to move the bear to. A simple test of the position pt could handle this correctly.

Now let's finish this switch ! The state FINISHED makes bob just say something, as does the state IGNORE :
Code:
                            case BobsQuestMarker.BobsQuestState.FINISHED:
                                m_Giver.Say("You've already had fun, now go away");
                                break;
                            case BobsQuestMarker.BobsQuestState.IGNORE:
                                m_Giver.Say("You refused the quest, it is too late now");
                                break;
                            default:
                                break;
                        }
                        
                    }

There ! Done, we now have a NPC that reacts to presence of the marker we created earlier. Not only that but he creates a gump that asks the player if he wants to accept the quest ! Of course, we haven't seen the gump code yet, but that's the next step !

Again, there are some spots where I'm not sure of myself, if someone could complete or clarify those things, it would be great !

Jc

Here's the full code :

Code:
using System;
using Server;
using Server.ContextMenus;
using Server.Gumps;
using Server.Items;
using Server.Mobiles;
using System.Collections;
using System.Collections.Generic;

namespace Server.Mobiles
{
    [CorpseName("a noble corpse")]
    public class Bob : Mobile
    {
        public virtual bool IsInvulnerable { get { return true; } }

        [Constructable]
        public Bob()
        {
            Name = "Bob";
            Body = 400;
            VirtualArmor = 50;
            CantWalk = true;
            Female = false;

            HairItemID = 0x203B;
            HairHue = 0x1;

            AddItem(new Server.Items.LongPants(0x1));
            AddItem(new Server.Items.FancyShirt(0x481)); ;
            AddItem(new Server.Items.ThighBoots(0x1));
            AddItem(new Server.Items.FeatheredHat(0x1));
            AddItem(new Server.Items.Cloak(0x485));

            Blessed = true;
        }

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

        public override void GetContextMenuEntries(Mobile from, List<ContextMenuEntry> list)
        {
            base.GetContextMenuEntries(from, list);
            list.Add(new BobEntry(from, this));
        }

        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 class BobEntry : ContextMenuEntry
        {
            private Mobile m_Mobile;
            private Mobile m_Giver;
            
            public BobEntry(Mobile from, Mobile giver)
                : base(6146, 3)
            {
                m_Mobile = from;
                m_Giver = giver;
            }

            public override void OnClick()
            {
                PlayerMobile mobile = (PlayerMobile)m_Mobile;

                if (mobile != null)
                {
                    BobsQuestMarker bqm = mobile.Backpack.FindItemByType(typeof(BobsQuestMarker)) as BobsQuestMarker;

                    //No marker
                    if (bqm == null)
                    {
                        mobile.SendGump(new StartQuest());
                    }
                    else
                    {
                        switch (bqm.QuestState)
                        {
                            case BobsQuestMarker.BobsQuestState.START:
                                m_Giver.Say("You've got work to do : " + bqm.Counter + " rabbits to kill !");
                                break;
                            case BobsQuestMarker.BobsQuestState.GETREWARD:
                                bqm.QuestState = BobsQuestMarker.BobsQuestState.FINISHED;

                                m_Giver.Say("Wow, you've done it, here is your surprise!");
                                m_Giver.Say("A bear!");

                                Point3D pt = m_Giver.Location;

                                pt.X += Utility.Random(6) - 3;
                                pt.Y += Utility.Random(6) - 3;

                                BlackBear bear = new BlackBear();
                                bear.MoveToWorld(pt, Map.Trammel);
                                break;
                            case BobsQuestMarker.BobsQuestState.FINISHED:
                                m_Giver.Say("You've already had fun, now go away");
                                break;
                            case BobsQuestMarker.BobsQuestState.IGNORE:
                                m_Giver.Say("You refused the quest, it is too late now");
                                break;
                            default:
                                break;
                        }
                        
                    }
                }

            }
        }
    }
}
**edit daat99: removed quote**
 

fearyourself

Wanderer
Here is the third part :

Code file : startquest.cs

Ok, here's the third file I created for this excuse of a quest : startquest.cs.

I'll skip the includes and go to the definition of the class. We are making a GUMP so we are of course in the namespace Server.Gumps :

Code:
namespace Server.Gumps
{
	public class StartQuest : Gump
	{

Ok, so we define a class that inherits of the base class Gump. Nothing really new. This class doesn't have any private members, I just put it a constant number that will define how many rabbits will be created (this could be random too!) :

Code:
        const int RABBITNBR = 10;

Next comes the constructor which basically defines the way the gump will react : not closable, disposable, dragable, not resizable... And then we add buttons and a html area. I used a program called Gump Designer to make it so I'm not too informed of all the options you can pass. What I do know, and will interest us here is the button handling.

Code:
		public StartQuest()
			: base( 0, 0 )
		{
			this.Closable=false;
			this.Disposable=true;
			this.Dragable=true;
			this.Resizable=false;
			this.AddPage(0);
			this.AddImage(106, 68, 7);
        		this.AddHtml(139, 103, 230, 105, "Ahhh, want to play a little game ? <br> If you want, I'll take 10 rabbits out of my hat, if you kill them all, you'll get a surprise !", false, true);

Here is the creation of the buttons :
Code:
			this.AddButton(164, 214, 247, 248, 0, GumpButtonType.Reply, 0);
		        this.AddButton(284, 214, 242, 243, 1, GumpButtonType.Reply, 0);
		}

The first 2 parameters of the AddButton function are the relative (compared to the top left position of the gump) position of the button. Then comes the image of the button when it is not pressed (3rd parameter) and when it is pressed (4th parameter). The 5th parameter is what helps us handle button actions (later on). Just to say, if buttons should have different actions (generally this is the case), you'll need to put a different integer here. The rest of the parameters, I left as is. I think GumpButtonType.Reply calls the OnResponse function when the button is pressed.

This means we'll be defining the function OnResponse next !

In our case, we have a OK Button (ID 0) and a Cancel Button (ID 1).

Code:
        public override void OnResponse(NetState state, RelayInfo info)
        {
            Mobile from = state.Mobile;

First thing we do is find the Mobile (the player) that generated this action. This is done going in the state object passed as first parameter. Then, we use the ButtonID in the info parameter. Using a switch, we can handle the different button actions. This ID is the fifth parameter you passed to the AddButton function. In our case, we have a OK Button (ID 0) and a Cancel Button (ID 0), so our switch will have 2 cases :

Code:
            switch (info.ButtonID)
            {
                case 0:

The code of the OK situation is simple : the player has accepted the quest. We must generate the rabbits and add the marker to the player. We'll add a "Good luck" to show the SendMessage function ;)

Code:
                    {
                        from.SendMessage("Good luck!");
                        //Add the marker
                        BobsQuestMarker bqm = new BobsQuestMarker();

                        bqm.Counter = RABBITNBR;

First we create the marker and set the counter to the constant RABBITNBR. Then we add it to the backpack :

Code:
                        from.AddToBackpack(bqm);

Finally, using a loop and the position of the player as a center for the rabbits, we create them :

Code:
                        //Create rabbits
                        int i;
                        Point3D pt = from.Location, 
                            rabbitpos = new Point3D();

                        for (i = 0; i < RABBITNBR; i++)
                        {
                            rabbitpos.X = pt.X + Utility.Random(6) - 3;
                            rabbitpos.Y = pt.Y + Utility.Random(6) - 3;
                            rabbitpos.Z = pt.Z;

                            BobsRabbit br = new BobsRabbit();
                            br.Home = pt;
                            br.RangeHome = 4;
                            br.MoveToWorld(rabbitpos, Map.Trammel);
                        }
                        break;
                    }

As you see, we use the Random function again to randomize the starting position. The rabbit we create is a special breed called BobsRabbit. We'll see that breed's definition next! The last thing we do is set the home of the rabbit, a range and we set them in the world.

This makes the rabbits stay in place, otherwise they go away too fast and it's difficult to get them all !

The next case is if you cancel the quest, we then add the marker but set its state to IGNORE :

Code:
                case 1:
                    {
                        from.SendMessage("Fine don't, it doesn't really matter!");
                        BobsQuestMarker bqm = new BobsQuestMarker();

                        bqm.QuestState = BobsQuestMarker.BobsQuestState.IGNORE;
                        from.AddToBackpack(bqm);
                        break;
                    }
            }

This will make Bob ignore the player next time he talks to him !

Finally, we close the gump :

Code:
            from.CloseGump(typeof(StartQuest));


There that's done ! The gump is easy to do once you have the basic layout made by Gump Designer.


Once again, any remarks, critics or whatever are welcome !
Jc

Here's the full code :

Code:
using System;
using Server;
using Server.Accounting;
using Server.Gumps;
using Server.Items;
using Server.Menus;
using Server.Menus.Questions;
using Server.Mobiles;
using Server.Network;
using Server.Prompts;
using Server.Regions;
using Server.Misc;

namespace Server.Gumps
{
	public class StartQuest : Gump
	{
        const int RABBITNBR = 10;

		public StartQuest()
			: base( 0, 0 )
		{
			this.Closable=false;
			this.Disposable=true;
			this.Dragable=true;
			this.Resizable=false;
			this.AddPage(0);
			this.AddImage(106, 68, 7);
            this.AddHtml(139, 103, 230, 105, "Ahhh, want to play a little game ? <br> If you want, I'll take 10 rabbits 

out of my hat, if you kill them all, you'll get a surprise !", false, true);
			this.AddButton(164, 214, 247, 248, 0, GumpButtonType.Reply, 0);
            this.AddButton(284, 214, 242, 243, 1, GumpButtonType.Reply, 0);
		}

        public override void OnResponse(NetState state, RelayInfo info)
        {
            Mobile from = state.Mobile;

            switch (info.ButtonID)
            {
                case 0:
                    {
                        from.SendMessage("Good luck!");
                        //Add the marker
                        BobsQuestMarker bqm = new BobsQuestMarker();

                        bqm.Counter = RABBITNBR;
                        from.AddToBackpack(bqm);

                        //Create rabbits
                        int i;
                        Point3D pt = from.Location, 
                            rabbitpos = new Point3D();

                        for (i = 0; i < RABBITNBR; i++)
                        {
                            rabbitpos.X = pt.X + Utility.Random(6) - 3;
                            rabbitpos.Y = pt.Y + Utility.Random(6) - 3;
                            rabbitpos.Z = pt.Z;

                            BobsRabbit br = new BobsRabbit();
                            br.Home = pt;
                            br.RangeHome = 4;
                            br.MoveToWorld(rabbitpos, Map.Trammel);
                        }
                        break;
                    }
                case 1:
                    {
                        from.SendMessage("Fine don't, it doesn't really matter!");
                        BobsQuestMarker bqm = new BobsQuestMarker();

                        bqm.QuestState = BobsQuestMarker.BobsQuestState.IGNORE;
                        from.AddToBackpack(bqm);
                        break;
                    }
            }

            from.CloseGump(typeof(StartQuest));
        }
	}
}
**edit daat99: removed quote**
 

fearyourself

Wanderer
Fourth part :

Introduction
Ok last post and I hope you're still all following ;)

We now have a definition of Bob, we've got the quest marker AND we have a gump that asks if the player wants the quest.

All we need is the rabbits that the player has to kill and we'll be done !

Bob's Rabbits are basically extentions to the normal Rabbit class. We just add a little bit of handling to check if the player has the BobsQuestMarker before doing anything. This means that a player can only damage and kill a rabbit if he/she has the marker, otherwise the rabbit will say something (virtual worlds let rabbits speak!)

Code file : BobsRabbit.cs
The code starts out saying that BobsRabbit inherits from the basic rabbit :

Code:
namespace Server.Mobiles
{
	[CorpseName( "a BobsRabbit corpse" )]
	public class BobsRabbit : Rabbit
	{

In the constructor, we just change the name of the rabbit and we don't do anything special in the serialization or deserialization so we'll let the base class do it...

Code:
        public BobsRabbit() : base()
		{
                Name = "Bob's rabbit";
		}

First thing we do is handle the damage that can be done to our nice bunnies. When damage is done to a mobile, the Damage function is called, by overriding it we can personnalize it like we want.

First thing we'll be doing is checking if there is a player involved or is the rabbit being attacked by a random
creature :

Code:
        public override void Damage(int amount, Mobile from)
        {
            PlayerMobile pm = null;
            if (m is PlayerMobile)
            {
                pm = m as PlayerMobile;
            }
            else if (m is BaseCreature)
            {
                BaseCreature bc = m as BaseCreature;
                if (bc.Controlled == true)
                {
                    pm = bc.ControlMaster as PlayerMobile;
                }
            }

Basically, if it's a player, we store the player's reference in the pm variable, otherwise it's a creature. We then have to check if the creature is controlled and, if so, get the player that controlls the creature.

Next, we check if we have the player has the marker, if he does, we'll let the damage function continue by calling the Damage from the class base. Otherwise, we'll say something and not damage the rabbit !

Code:
            if (pm != null)
            {
                BobsQuestMarker bqm = pm.Backpack.FindItemByType(typeof(BobsQuestMarker)) as BobsQuestMarker;

                if (bqm)
                {
                    base.Damage(amount, from);
                }
                else
                {
                    this.Say("Stop it, go see bob if you want to play!");
                }
            }    
        }


Ok, now we also have a little bit of work when the rabbit dies. Sometimes and in some games, it's possible to kill a mobile without giving damage. Seems a contradiction ? Not really, Death spells do exist in some games so it's generally a good idea to handle damage in both functions just to be sure...

When a mobile dies, it calls the OnDeath method, we'll also customize this function to make it do what we want :


Code:
        public override void OnDeath(Container c)
        {
            Mobile m = this.FindMostRecentDamager(false);
            PlayerMobile pm = null;
            BobsQuestMarker bqm = null;
            bool ressurect = true;

Ok, we have four local variables : the mobile m is the most recent damager so the one that will get the credit for the kill. pm and bqm are variables to check if a player is involved and if he has th marker.

The ressurect boolean will tell the end of the function if the rabbit should be ressurected because it was killed by the wrong person...

We start off by filling in pm and bqm :

Code:
            if (m is PlayerMobile)
            {
                pm = m as PlayerMobile;
            }
            else if (m is BaseCreature)
            {
                BaseCreature bc = m as BaseCreature;

                if (bc.Controlled == true)
                {
                    pm = bc.ControlMaster as PlayerMobile;
                }
            }

	    if(pm != null)
            {
                bqm = pm.Backpack.FindItemByType(typeof(BobsQuestMarker)) as BobsQuestMarker;
            }

As you see, this is like the Damage function. We check for a player or a controlled creature. Then we fill in the bqm variable.


Now we see if the player has a marker :

Code:
            //Ok rabbit died by someone who's got the marker
            if (bqm != null)
            {
                bqm.Counter--;

If the player had the marker, we decrement it but we check if the player has killed enough. A few possibilities must be handled :

- The player hasn't yet finished the quest
- If he's finished it then he should stop killing the rabbits

We first handle the first case :

Code:
if (bqm.QuestState < BobsQuestMarker.BobsQuestState.GETREWARD)
                {
                    if (bqm.Counter > 0) //Still doing the quest
                    {
                        pm.SendMessage("You've got " + bqm.Counter + " rabbit(s) to kill");
                    }
                    else //Finished quest
                    {
                        pm.SendMessage("All done, you can go back to Bob");

                        bqm.QuestState = BobsQuestMarker.BobsQuestState.GETREWARD;
                    }

                    ressurect = false;
                }

There that's done ! If the state is inferior to the GETREWARD state (so it can only be START ;) ) then we go in the if.

We then check the counter, if we haven't killed enough rabbits, we add a message telling how many are left.

If we have killed enough, then we change the state to GETREWARD and tell the player to go back to Bob...

After that, we put the boolean ressurect to false, that way we won't ressurect the rabbit, it's been killed by the right player.

The second case is that we should not have killed the player. Whether it was killed by a random animal or a player not in the quest, the boolean ressurect will remain at true, thus we can create a new rabbit :

Code:
            if (ressurect) //No marker or finished quest
            {
                BobsRabbit newrabbit = new BobsRabbit();
                                
                Point3D nrpos = this.Location;

                nrpos.X += Utility.Random(8) - 4;
                nrpos.Y += Utility.Random(8) - 4;
                newrabbit.MoveToWorld(nrpos, Map.Trammel);

By now, you must be used to this kind of code. We get the location of the old rabbit (soon to be deceased) and we randomize the position to create a new one.

Once this is done, we check if the player has the marker or not. If he does, that means that he finished the quest, can go get the reward or ignored the quest. The rabbit will remind him of each case :


Code:
 	   if (bqm != null)
                {
                    switch (bqm.QuestState)
                    {
                        case BobsQuestMarker.BobsQuestState.GETREWARD:
                            newrabbit.Say("Your quest is finished, go get your reward!");
                            break;

                        case BobsQuestMarker.BobsQuestState.IGNORE:
                            newrabbit.Say("Your ignored the quest, stop it!");
                            break;

                        case BobsQuestMarker.BobsQuestState.FINISHED:
                            newrabbit.Say("Your finished the quest, stop it!");
                            break;

                        default:
                            break;
                    }
                }
                else
                {
                    newrabbit.Say("Do not interfere with someone else's quest, go see Bob !");
                }

Then we kill the current rabbit, hoping a new one has taken his place ;)

Code:
	    }
            base.OnDeath(c);
        }
    }
}

There it's done, again hoping this will help someone,
Have fun with it and I attached the four files, all you have to do is unzip it in your Custom directory...

Jc

PS: I wrote this in one go so there might be mistakes a little bit everywhere but I sure hope not too many...
PPS: I hope this kind of thing hasn't been done 50 times, I looked around and apart from the 8-ball and the Timer tutorial, I hadn't found much...

Here's the code of this file :

Code:
using System;
using Server;
using Server.Items;
using Server.Mobiles;

namespace Server.Mobiles
{
	[CorpseName( "a hare corpse" )]
	public class BobsRabbit : Rabbit
	{
        public BobsRabbit() : base()
		{
            Name = "Bob's rabbit";
		}

		public BobsRabbit(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 Damage(int amount, Mobile from)
        {
            PlayerMobile pm = null;
            if (from is PlayerMobile)
            {
                pm = from as PlayerMobile;
            }
            else if (from is BaseCreature)
            {
                BaseCreature bc = from as BaseCreature;
                if (bc.Controlled == true)
                {
                    pm = bc.ControlMaster as PlayerMobile;
                }
            }

            if (pm != null)
            {
                BobsQuestMarker bqm = pm.Backpack.FindItemByType(typeof(BobsQuestMarker)) as BobsQuestMarker;

                if (bqm != null)
                {
                    base.Damage(amount, from);
                }
                else
                {
                    this.Say("Stop it, go see bob if you want to play!");
                }
            }    
        }

        public override void OnDeath(Container c)
        {
            Mobile m = this.FindMostRecentDamager(false);
            PlayerMobile pm = null;
            BobsQuestMarker bqm = null;
            bool ressurect = true;

            if (m is PlayerMobile)
            {
                pm = m as PlayerMobile;
            }
            else if (m is BaseCreature)
            {
                BaseCreature bc = m as BaseCreature;

                if (bc.Controlled == true)
                {
                    pm = bc.ControlMaster as PlayerMobile;
                }
            }

            if (pm != null)
            {
                bqm = pm.Backpack.FindItemByType(typeof(BobsQuestMarker)) as BobsQuestMarker;
            }

            //Ok rabbit died by someone who's got the marker
            if (bqm != null)
            {
                bqm.Counter--;

                if (bqm.QuestState < BobsQuestMarker.BobsQuestState.GETREWARD)
                {
                    if (bqm.Counter > 0) //Still doing the quest
                    {
                        pm.SendMessage("You've got " + bqm.Counter + " rabbit(s) to kill");
                    }
                    else //Finished quest
                    {
                        pm.SendMessage("All done, you can go back to Bob");

                        bqm.QuestState = BobsQuestMarker.BobsQuestState.GETREWARD;
                    }

                    ressurect = false;
                }
            }

            if (ressurect) //No marker or finished quest
            {
                BobsRabbit newrabbit = new BobsRabbit();
                                
                Point3D nrpos = this.Location;

                nrpos.X += Utility.Random(8) - 4;
                nrpos.Y += Utility.Random(8) - 4;
                newrabbit.MoveToWorld(nrpos, Map.Trammel);

               if (bqm != null)
                {
                    switch (bqm.QuestState)
                    {
                        case BobsQuestMarker.BobsQuestState.GETREWARD:
                            newrabbit.Say("Your quest is finished, go get the reward!");
                            break;

                        case BobsQuestMarker.BobsQuestState.IGNORE:
                            newrabbit.Say("Your ignored the quest, stop it!");
                            break;

                        case BobsQuestMarker.BobsQuestState.FINISHED:
                            newrabbit.Say("Your finished the quest, stop it!");
                            break;

                        default:
                            break;
                    }
                }
                else
                {
                    newrabbit.Say("Do not interfere with someone else's quest, go see Bob !");
                }
            }

            base.OnDeath(c);
        }
	}
}
**edit daat99: removed quote**
 
Top