Disguise Event Script ( 3 Parts )
Due, to the possibility of this script being removed from Hercules' main repository, I'm going to write a guide for this script. Well, that and I lack the imagination to write up a new script from scratch right now @.@;
Because this post is tremendously long, each part has been wrapped in spoiler tags to help make it easier to follow.
Part 1
Part 2
Part 3
Due, to the possibility of this script being removed from Hercules' main repository, I'm going to write a guide for this script. Well, that and I lack the imagination to write up a new script from scratch right now @.@;
Because this post is tremendously long, each part has been wrapped in spoiler tags to help make it easier to follow.
Part 1
Spoiler
Okay, so this script is going to be our first event tutorial, and advanced script (Am I being biased? Lol).
This event, will have an NPC disguise itself at certain times during the day, and players simply need to shout out the monsters name.
Get it correct, they get a prize. Get it wrong, just keep trying! Sounds simple right? Well, it's a bit harder than it seems, hence the reason it's an advanced script.
Now, through out this guide, I'll be adding little notations within the scripting-code. This will help keep track of why we set something to what. And what exactly we're trying to do.
So let's get started. Let's start off with a normal NPC header:
Alright, before we do anything in the script, we are going to add a check. This check will check to see if the event is currently on and if the player is a GM or not. Followed by an abrupt end;, if the player isn't a GM or the event is on.
To get the GM level of the player we'll use this command: getgmlevel(), and we'll use it to check against the variable .gm_level, which we haven't set yet. But that's okay because we will. Also, to check whether the even is on or off, we'll simply check if the variable .event_onoff is 0 or not.
Again, this variable hasn't been set yet, but we will later. So let's add the check in:
Okay, we did that because we didn't want player's talking to the NPC during the event, because it would give them an unfair disadvantage to have the dialogue box up.
So now that we got the initial check out of the way. Let's start off by adding 2 lines of dialogue, the first saying the npc's name, which we will store into a variable at the same time. And the second, being a simple "Welcome. How may I help you?"
So let's do that:
Alright, now we are going to use the command: next; so we can start with more dialogue on the next page. Followed by another mes command, with the NPC's name again:
Alright, the reason we did that, is because we are going to prompt the player with a menu now. And we don't want to have to type next; mes .@n$; after each and every option they choose. So we do it before, so all options will take advantage of it. Saves us time.
Now then, this menu will have 2 options, 4 if your a GM. The first option will be "Information", the second & third options will only be available to GMs, which are to "Turn the Event ON/OFF" and "Event Settings" and the fourth option will be "Nothing, just passing by."
We'll be using switch(select) for our menu, and to separate the GM options from the normal player options, we'll be using a conditional check (?: ) to see if they have a high enough GM level to see those options. Also, since the option to turn the event on or off, can do both, we want to properly display whether or not to turn it off, if it's on. Or on if it's off. To do that we'll use another conditional check (?: ), again checking if the variable .event_onoff is 0 or not.
So let's do it:
Okay, that was a little long winded. But at least we did it. Now then, let's start off with case 1: and tell the players about the event. Once you're done telling them what the event does, we'll use the command break; to bring them out of that code block.
So let's do it:
Alright, that was simple. Now we're going to start working on case 2: which is only for GMs and deals with turning the event on or off.
So we'll start with a dialogue that asks the GM if they want to turn the event OFF if it's on, or ON if it's off. To do so we'll need to use another conditional check (?: ) to check against the variable .event_onoff again.
So let's do it:
Alright, now we'll prompt them with a menu of either Yes or No. And if the say No, we'll close the script.
So yet again, let's do it:
Well, that was easy! But we're not done yet. Now it's time to actually turn the event on or off.
So we'll start by adding a check to see if the event is currently on or not. To do that, we'll see if the variable .event_onoff is set to anything besides 0. Simple enough:
Now, this means the event is on. So we'll proceed to turn it off and announce to the whole server that a GM turned the event off:
Let's get to it:
Okay, now the next part probably won't make much sense to you since you don't know how the event works at it's core, and I do. But I'll try my best to explain. Since the script works by having players use global chat to answer, we need to delete that answer from working.
To do that, we need to use this command: deletepset <set number>; and we'll use the number 1.
additionally, we need to change the NPC's display back to normal. To do that we use this command: setnpcdisplay("<npc name>", "<display name>", <class id>, <size>)
Also, the event uses a timer to manage when it disguises into a new monster. So we need to stop that timer since we turned the event off.
To do so we need to use this command: stopnpctimer { "<NPC name>" {, <Detach Flag>} };
lastly, we'll close the script and the check.
So let's add those in:
Okay, now we finished turning off the event. Now we need to turn the event on. Thankfully, we'll be handling that else where. For now, we are just going to make the GM click a close button, which will start the event for us later.
To do so we need to use this command: close2;
as for turning the event on, we'll do that somewhere else in the script, so let's just make them go to it, using the command: callsub <label>{,<argument>,...<argument>}; for the label, we'll use iStartEvent:
lastly, we'll end the script with end; this time we can do this, because we used close2; first.
So lets add that in:
Awesome, we just finished case 2:, let's just jump straight into case 3:
We'll start by having the npc say "Pick a setting to modify.", followed by a next; command, then by the npc saying it's name:
Now, we need to make a menu with 3 options inside. Those options are: "Monster Display", "Number of Rounds" and "Prize Settings", again using switch(). I'd say it's pretty safe for you to assume by now, that if the menu has more than 2 options, we are using switch(), else we'd just use if(select())
So let's add it in:
Well, I know I said we were almost done with our cases, but looks like we just got 3 more. No matter we'll just take care of them 1by1. We'll start with case 1: (obviously), which deals with choosing a disguise rule. For now we'll, simply have a dialogue saying "Choose a disguise rule", followed by a next; command:
Okay, now this script by default only has 2 disguise options, All monsters or MVPs only, but the way I wrote the script will allow for more options should you decide to add more options. So, instead of a normal if(select()) for 2 options, we're going to use this command: implode(<string_array>{,<glue>}) in combination with the select; command. For the string array we'll use this variable: .disguise_desc$, for the glue we'll use ":" just as I wrote it, quotations too.
Additionally, we'll be setting the variable: .disguise_option to whatever the GM selects, -1. This is because arrays start at the 0th element, while menu's start at the 1st.
So let's add this in:
Okay, now the last thing we need to do, before we move on to case 2: again, is tell the Gm that they the disguise rule has been set. Followed by a break; to get them out of the code block. To get the exact rule they chose, we'll use our variable we set above, as the element for the array, we used for the menu.
Alright, now we can start on case 2: (again). This will be a quick one. We'll, start by having the npc tell the GM to input how many rounds they want the event to last, and how many rounds the even currently will last for, which we'll determine by using the variable .rounds
Okay, now we need to have the GM input how much rounds they want the event to last. But, we are going to limit how much rounds it can have maximium as well as minimum.
To do that we need to use this command: input(<variable>{,<min>{,<max>}})
we'll use a simple scope variable for the variable. For min value, we'll use the variable .min and for the max value, we'll use the variable .max. Of course these will be set later.
Now, for us we don't care if the GM sets it to anything within that range, but if they try to put something higher or lower, we'll simply set it to the minimum in both cases. This makes it easier for us.
Now, the last thing we need to do before we can work on case 3: for the second time, is set the variable .rounds, to the number the GM inputted. Then tell the GM what the rounds have been changed to, followed by break; to get them out of the code block.
Real quick, let's do it:
Okay, now it's time for the second iteration of case 3:. Here, we'll be handling the prize settings. So we'll start off with telling the GM to input the item id of the prize, along with what the current prize is, which we can find by using the command: getitemname(<item id>) in combination with the variable .prize_item.
Followed by a next; command, and lastly top it off with the actual input of the item id, this time, with no minimum or maximum setting.
Let's do this, scripting time:
Now, let's have the npc say it's name again. Followed, by checking to see if the item they entered exists or not. To do that we'll use this command: getitemname(<item id>) and we'll check it against "null". Because if an item doesn't exist that command will return null as it's name. If the name is null, we'll have the npc tell them the item doesn't exist, please try again. And then close the script and check:
Now, since the item does exist, we're going to set the variable .prize_item to what they entered. Then we're going to have the npc tell them to input the amount they want to give away, followed by the next; command. Have the npc say it's name yet again. Then, we'll have them input the amount they want to give away, but keep it limited between 1 and 10,000. And if it's lower or higher, we'll set it to 1. We'll also tell them that the amount they used is invalid, and we set it to 1. Then to top it all off, we'll use the next; command again, followed by the npc saying it's name yet another time, finally closing off that check.
Whoo picking up the pace! Let's do this, scripting time:
Awesome, wasn't that easy? Now then, let's set the variable .prize_amount to what they entered. Then we'll have the npc tell them that the amount was success fully changed. Followed by what the new prize is and how much of that prize players will get. And put a break; there to get out of the code block.
Let's do it:
Alright, now let's close that switch() segment, and follow it with a break; since that's the end of our first case 3:
Simple enough:
Lastly, we'll close off the first switch() segment we used and then end it with close; because all of our cases are going to end up here. Including if the player chose, "Nothing, just passing by.". Forgot about that one didn't you? Well, we're going to care of it now:
Alright, that takes care of the main body! Right now your script should look something like this:
Okay, so this script is going to be our first event tutorial, and advanced script (Am I being biased? Lol).
This event, will have an NPC disguise itself at certain times during the day, and players simply need to shout out the monsters name.
Get it correct, they get a prize. Get it wrong, just keep trying! Sounds simple right? Well, it's a bit harder than it seems, hence the reason it's an advanced script.
Now, through out this guide, I'll be adding little notations within the scripting-code. This will help keep track of why we set something to what. And what exactly we're trying to do.
So let's get started. Let's start off with a normal NPC header:
prontera,160,155,4 script Disguise Event 4_M_NFDEADMAN,{
Alright, before we do anything in the script, we are going to add a check. This check will check to see if the event is currently on and if the player is a GM or not. Followed by an abrupt end;, if the player isn't a GM or the event is on.
To get the GM level of the player we'll use this command: getgmlevel(), and we'll use it to check against the variable .gm_level, which we haven't set yet. But that's okay because we will. Also, to check whether the even is on or off, we'll simply check if the variable .event_onoff is 0 or not.
Again, this variable hasn't been set yet, but we will later. So let's add the check in:
if (getgmlevel() < .gm_level && .event_onoff) { end; }
Okay, we did that because we didn't want player's talking to the NPC during the event, because it would give them an unfair disadvantage to have the dialogue box up.
So now that we got the initial check out of the way. Let's start off by adding 2 lines of dialogue, the first saying the npc's name, which we will store into a variable at the same time. And the second, being a simple "Welcome. How may I help you?"
So let's do that:
mes .@n$ = "[^0000FFDisguise NPC^000000]", "Welcome. How may I help you?";
Alright, now we are going to use the command: next; so we can start with more dialogue on the next page. Followed by another mes command, with the NPC's name again:
next; mes .@n$;
Alright, the reason we did that, is because we are going to prompt the player with a menu now. And we don't want to have to type next; mes .@n$; after each and every option they choose. So we do it before, so all options will take advantage of it. Saves us time.
Now then, this menu will have 2 options, 4 if your a GM. The first option will be "Information", the second & third options will only be available to GMs, which are to "Turn the Event ON/OFF" and "Event Settings" and the fourth option will be "Nothing, just passing by."
We'll be using switch(select) for our menu, and to separate the GM options from the normal player options, we'll be using a conditional check (?: ) to see if they have a high enough GM level to see those options. Also, since the option to turn the event on or off, can do both, we want to properly display whether or not to turn it off, if it's on. Or on if it's off. To do that we'll use another conditional check (?: ), again checking if the variable .event_onoff is 0 or not.
So let's do it:
switch( select("Information", ((getgmlevel() >= .gm_level)?"Turn Event ["+(.event_onoff?"^FF0000OFF":"^0000FFON")+"^000000]:Event Settings":":"), "Nothing, just passing by.") ) {
Okay, that was a little long winded. But at least we did it. Now then, let's start off with case 1: and tell the players about the event. Once you're done telling them what the event does, we'll use the command break; to bring them out of that code block.
So let's do it:
case 1: mes "This event is quite simple. At the ", "start of the event, I will disguise", "myself as a random monster.", "You have to shout that monster's", "name out loud."; next; mes "If you are correct, you will", "receive a prize. If not, keep ", "trying! That's all that there is to ", "this event."; break;
Alright, that was simple. Now we're going to start working on case 2: which is only for GMs and deals with turning the event on or off.
So we'll start with a dialogue that asks the GM if they want to turn the event OFF if it's on, or ON if it's off. To do so we'll need to use another conditional check (?: ) to check against the variable .event_onoff again.
So let's do it:
case 2: mes "Are you sure you want to turn the ", "event ["+ (.event_onoff?"^FF0000OFF":"^0000FFON") +"^000000]";
Alright, now we'll prompt them with a menu of either Yes or No. And if the say No, we'll close the script.
So yet again, let's do it:
if (select("Yes", "No") == 2) { close; }
Well, that was easy! But we're not done yet. Now it's time to actually turn the event on or off.
So we'll start by adding a check to see if the event is currently on or not. To do that, we'll see if the variable .event_onoff is set to anything besides 0. Simple enough:
if (.event_onoff) {
Now, this means the event is on. So we'll proceed to turn it off and announce to the whole server that a GM turned the event off:
Let's get to it:
.event_onoff = 0; announce "A GM has decided to turn the Disguise Event off. As a result no further prizes will be rewarded.",bc_all | bc_blue;
Okay, now the next part probably won't make much sense to you since you don't know how the event works at it's core, and I do. But I'll try my best to explain. Since the script works by having players use global chat to answer, we need to delete that answer from working.
To do that, we need to use this command: deletepset <set number>; and we'll use the number 1.
additionally, we need to change the NPC's display back to normal. To do that we use this command: setnpcdisplay("<npc name>", "<display name>", <class id>, <size>)
Also, the event uses a timer to manage when it disguises into a new monster. So we need to stop that timer since we turned the event off.
To do so we need to use this command: stopnpctimer { "<NPC name>" {, <Detach Flag>} };
lastly, we'll close the script and the check.
So let's add those in:
deletepset 1; stenpcdisplay "Disguise Event", 4_M_NFDEADMAN; stopnpctimer; close; }
Okay, now we finished turning off the event. Now we need to turn the event on. Thankfully, we'll be handling that else where. For now, we are just going to make the GM click a close button, which will start the event for us later.
To do so we need to use this command: close2;
as for turning the event on, we'll do that somewhere else in the script, so let's just make them go to it, using the command: callsub <label>{,<argument>,...<argument>}; for the label, we'll use iStartEvent:
lastly, we'll end the script with end; this time we can do this, because we used close2; first.
So lets add that in:
close2; callsub iStartEvent; end;
Awesome, we just finished case 2:, let's just jump straight into case 3:
We'll start by having the npc say "Pick a setting to modify.", followed by a next; command, then by the npc saying it's name:
case 3: mes "Pick a setting to modify."; next; mes .@n$;
Now, we need to make a menu with 3 options inside. Those options are: "Monster Display", "Number of Rounds" and "Prize Settings", again using switch(). I'd say it's pretty safe for you to assume by now, that if the menu has more than 2 options, we are using switch(), else we'd just use if(select())
So let's add it in:
switch(select("Monster Display", "Number of Rounds", "Prize Settings")) {
Well, I know I said we were almost done with our cases, but looks like we just got 3 more. No matter we'll just take care of them 1by1. We'll start with case 1: (obviously), which deals with choosing a disguise rule. For now we'll, simply have a dialogue saying "Choose a disguise rule", followed by a next; command:
case 1: mes "Choose a disguise rule."; next;
Okay, now this script by default only has 2 disguise options, All monsters or MVPs only, but the way I wrote the script will allow for more options should you decide to add more options. So, instead of a normal if(select()) for 2 options, we're going to use this command: implode(<string_array>{,<glue>}) in combination with the select; command. For the string array we'll use this variable: .disguise_desc$, for the glue we'll use ":" just as I wrote it, quotations too.
Additionally, we'll be setting the variable: .disguise_option to whatever the GM selects, -1. This is because arrays start at the 0th element, while menu's start at the 1st.
So let's add this in:
.disguise_option = select(implode(.disguise_desc$, ":")) - 1;
Okay, now the last thing we need to do, before we move on to case 2: again, is tell the Gm that they the disguise rule has been set. Followed by a break; to get them out of the code block. To get the exact rule they chose, we'll use our variable we set above, as the element for the array, we used for the menu.
mes .@n$, "The Disguise Rule has been set:", "----> ["^0000FF"+ .disguise_desc$[.disguise_option] +"^000000]"; break;
Alright, now we can start on case 2: (again). This will be a quick one. We'll, start by having the npc tell the GM to input how many rounds they want the event to last, and how many rounds the even currently will last for, which we'll determine by using the variable .rounds
case 2: mes "Input the number of rounds you want", "the event to last.", "Current number of rounds: [^0000FF"+ .rounds +"^000000]"; next;
Okay, now we need to have the GM input how much rounds they want the event to last. But, we are going to limit how much rounds it can have maximium as well as minimum.
To do that we need to use this command: input(<variable>{,<min>{,<max>}})
we'll use a simple scope variable for the variable. For min value, we'll use the variable .min and for the max value, we'll use the variable .max. Of course these will be set later.
Now, for us we don't care if the GM sets it to anything within that range, but if they try to put something higher or lower, we'll simply set it to the minimum in both cases. This makes it easier for us.
if (input(.@rounds,.min,.max)) { .@rounds = .min; }
Now, the last thing we need to do before we can work on case 3: for the second time, is set the variable .rounds, to the number the GM inputted. Then tell the GM what the rounds have been changed to, followed by break; to get them out of the code block.
Real quick, let's do it:
.rounds = .@rounds; mes .@n$, "The number of rounds has been", "changed to "+ .rounds +"."; break;
Okay, now it's time for the second iteration of case 3:. Here, we'll be handling the prize settings. So we'll start off with telling the GM to input the item id of the prize, along with what the current prize is, which we can find by using the command: getitemname(<item id>) in combination with the variable .prize_item.
Followed by a next; command, and lastly top it off with the actual input of the item id, this time, with no minimum or maximum setting.
Let's do this, scripting time:
case 3: mes "Tell me the Item ID of the prize ", "to be given each round.", "Current item: [^0000FF"+ getitemname(.prize_item) +"^000000]"; next; input .@prize;
Now, let's have the npc say it's name again. Followed, by checking to see if the item they entered exists or not. To do that we'll use this command: getitemname(<item id>) and we'll check it against "null". Because if an item doesn't exist that command will return null as it's name. If the name is null, we'll have the npc tell them the item doesn't exist, please try again. And then close the script and check:
if (getitemname(.@prize) == "null") { mes "That item does not exist.", "Please try again."; close; }
Now, since the item does exist, we're going to set the variable .prize_item to what they entered. Then we're going to have the npc tell them to input the amount they want to give away, followed by the next; command. Have the npc say it's name yet again. Then, we'll have them input the amount they want to give away, but keep it limited between 1 and 10,000. And if it's lower or higher, we'll set it to 1. We'll also tell them that the amount they used is invalid, and we set it to 1. Then to top it all off, we'll use the next; command again, followed by the npc saying it's name yet another time, finally closing off that check.
Whoo picking up the pace! Let's do this, scripting time:
.prize_item = .@prize; mes "Input the amount to be given."; next; mes .@n$; if (input(.@amount,1,10000)) { mes "That amount is invalid.", "Using a default amount of 1."; .@amount = 1; next; mes .@n$; }
Awesome, wasn't that easy? Now then, let's set the variable .prize_amount to what they entered. Then we'll have the npc tell them that the amount was success fully changed. Followed by what the new prize is and how much of that prize players will get. And put a break; there to get out of the code block.
Let's do it:
.prize_amount = .@amount; mes "Successfully changed prize.", "Prize: "+ .prize_amount +"x [^0000FF"+ getitemname(.prize_item) +"^000000]"; break;
Alright, now let's close that switch() segment, and follow it with a break; since that's the end of our first case 3:
Simple enough:
} break;
Lastly, we'll close off the first switch() segment we used and then end it with close; because all of our cases are going to end up here. Including if the player chose, "Nothing, just passing by.". Forgot about that one didn't you? Well, we're going to care of it now:
} close;
Alright, that takes care of the main body! Right now your script should look something like this:
prontera,160,155,4 script Disguise Event 4_M_NFDEADMAN,{ if (getgmlevel() < .gm_level && .event_onoff) { end; } mes .@n$ = "[^0000FFDisguise NPC^000000]", "Welcome. How may I help you?"; next; mes .@n$; switch(select("Information", ((getgmlevel() >= .gm_level)?"Turn Event ["+(.event_onoff?"^FF0000OFF":"^0000FFON")+"^000000]:Event Settings":":"), "Nothing, just passing by.")) { case 1: mes "This event is quite simple. At the ", "start of the event, I will disguise", "myself as a random monster.", "You have to shout that monster's", "name out loud."; next; mes "If you are correct, you will", "receive a prize. If not, keep ", "trying! That's all that there is to ", "this event."; break; case 2: mes "Are you sure you want to turn the ", "event ["+(.event_onoff?"^FF0000OFF":"^0000FFON")+"^000000]"; if (select("Yes", "No") == 2) { close; } if (.event_onoff) { .event_onoff = 0; announce "A GM has decided to turn the Disguise Event off. As a result no further prizes will be rewarded.",bc_all | bc_blue; deletepset 1; setnpcdisplay "Disguise Event", 4_M_NFDEADMAN; stopnpctimer; close; } close2; callsub iStartEvent; end; case 3: mes "Pick a setting to modify."; next; mes .@n$; switch(select("Monster Display", "Number of Rounds", "Prize Settings")) { case 1: mes "Choose a disguise rule."; next; .disguise_option = select(implode(.disguise_desc$), ":")) - 1; mes .@n$; "The Disguise Rule has been set:", "----> [^0000FF"+ .disguise_desc$[.disguise_option] +"^000000]"; break; case 2: mes "Input the number of rounds you want", "the event to last.", "Current number of rounds: [^0000FF"+ .rounds +"^000000]"; next; if (input(.@rounds,.min,.max)) { .@rounds = .min; } .rounds = .@rounds; mes .@n$, "The number of rounds has been", "changed to "+ .rounds +"."; break; case 3: mes "Tell me the Item ID of the prize ", "to be given each round.", "Current item: [^0000FF"+ getitemname(.prize_item) +"^000000]"; next; input .@prize; if (getitemname(.@prize) == "null") { mes "That item does not exist.", "Please try again."; close; } .prize_item = .@prize; mes "Input the amount to be given."; next; mes .@n$; if (input(.@amount,1,10000)) { mes "That amount is invalid.", "Using a default amount of 1."; .@amount = 1; next; mes .@n$; } .prize_amount = .@amount; mes "Successfully changed prize.", "Prize: "+ .prize_amount +"x [^0000FF"+ getitemname(.prize_item) +"^000000]"; break; } break; } close;And that's it for the main body of our script as well as Part 1. In Part 2, we'll work on the configuration part of the script. This is mainly going to be under the OnInit: label.
Part 2
Spoiler
Alright, so in Part 1, we did the main body of the script, which mainly involved when a player talks to the npc and when a GM talks to the npc. However, we used quite a few variables that haven't been set yet. In Part 2, the configuration part, we'll actually set those. So let's get to it.
Now, as with most configurations let's create our OnInit: label.
In here we are going to set up quite a few things. It's an event script after all. So let's jump into this.
Firstly, we want this script to only allow GMs level 60 or higher to manually turn the event on. So we'll make a variable for that. We also want the event to last 10 rounds, so we'll make a variable for that as well:
Now, since this is a tutorial, we are going to set the prize they earn for getting the answer correct, to 10 Red Potions:
Okay, so now we are going to set 2 variables, called .min and .max to 3 and 10 respectively, this will serve as the amount the GM is able to choose in the main body of the script:
Now, since this event involves the NPC transforming into all kinds of monsters, let's setup a toggle-able option. That will allow the NPC to either transform into all monsters, or just MVPs. The reason being, not everyone knows the names of every single monster, and for good reason. There's like 2 thousand of them. However, chances are they know most of the MVPs. So, let's set a variable up to determine what we will use. Let's also create an array with the description for those options:
Now, you'll see I set the option to 1, this means that I've set the script to only disguise as MVPs.
Okay, now since player's need to be able to see what the NPC transform into, let's add an option to make an area surrounding the NPC un-walkable. This will prevent players from smothering the NPC and blocking it's view from other people:
Alright, we're almost done with adding configurations, but we've still got a few more.
Now, let's add 2 variables that deal with timing. The first being, how long does the NPC wait before officially starting the event. For this example, we're going to go with 3 minutes. And the second variable, determining the length of intermission before the NPC transform into another monster, 10 seconds.
Let's add those in:
Alright, now there are just 2 more configurations to add. The first being an array, that stores all the MVPs that we want the NPC to use as a disguise.
So let's create that array:
Okay, now keep in mind there are a few MVPs that only spawn in Renewal Ragnarok. And since I couldn't possibly know what version of RO you're running, let's add a check to add those MVPs in, only if the server is in renewal mode.
To do that we need to use this command: checkre(<type>). We are going to be checking against type 0. Now, when we add the MVPs to the array, we need to add them starting at the end of the array. So we'll use the command: getarraysize(<array name>) to determine where the end is. Beats having to count all those MVPs above.
So let's add it in:
Alright, now it's time to add the last and final configuration for the script. The monster blacklist. This will serve as a list of monster's you don't want the NPC to disguise as. We need to do this, because there are just some monsters that exist which don't spawn naturally. And we are going to weed those out! So what we're going to do, to put it simply, is we are going to loop through all the existing monsters, and check to see if they have any drops. This is because 90%+ of the monsters in ragnarok that spawn naturally have drops.
But, since there are well over a thousand monsters, we need to prevent an infinity loop. To do so we need to use this command: freeloop(<toggle>) with a value of 1 before we actually start the loop.
Lastly, to check to see if the monster drops any items we'll be using this command: getmobdrops(<mob id>). So let's create that loop:
1. We set a variable .last_mobid to 5000. This is well above the final monster in the game. This is just so we don't have update the script every time Ragnarok releases new content. This will give you years of leeway, before you have to update the script.
2. We started the loop off at SCORPION, because he is the first mob.
3. We checked to see if the mob_id we were on had any drops. If it didn't it got added to the black list.
4. Since, each monster has a unique ID, we simply used their ID number as the element of the array, and set it to itself.
5. We used freeloop(0); to stop the free looping, since it's no longer needed.
6. end; because we are FINALLY done with the OnInit: label.
That's the end of Part 2, right now your script should look something like this:
Alright, all of that and we only just finished the OnInit: label. Let's stop here for now, and continue in Part 3, the core of our script.
Alright, so in Part 1, we did the main body of the script, which mainly involved when a player talks to the npc and when a GM talks to the npc. However, we used quite a few variables that haven't been set yet. In Part 2, the configuration part, we'll actually set those. So let's get to it.
Now, as with most configurations let's create our OnInit: label.
OnInit:
In here we are going to set up quite a few things. It's an event script after all. So let's jump into this.
Firstly, we want this script to only allow GMs level 60 or higher to manually turn the event on. So we'll make a variable for that. We also want the event to last 10 rounds, so we'll make a variable for that as well:
.gm_level = 60; .rounds = 10;
Now, since this is a tutorial, we are going to set the prize they earn for getting the answer correct, to 10 Red Potions:
.prize_item = Red_Potion; .prize_amount = 10;
Okay, so now we are going to set 2 variables, called .min and .max to 3 and 10 respectively, this will serve as the amount the GM is able to choose in the main body of the script:
.min = 3; .max = 10;
Now, since this event involves the NPC transforming into all kinds of monsters, let's setup a toggle-able option. That will allow the NPC to either transform into all monsters, or just MVPs. The reason being, not everyone knows the names of every single monster, and for good reason. There's like 2 thousand of them. However, chances are they know most of the MVPs. So, let's set a variable up to determine what we will use. Let's also create an array with the description for those options:
// What disguise rule, the NPC should follow. // 0 = Disguise as all monsters | 1 = Disguise as MVPs only .disguise_option = 1; setarray .disguise_desc$[0],"Disguise as all monsters.", "Disguise as MVPs only.";
Now, you'll see I set the option to 1, this means that I've set the script to only disguise as MVPs.
Okay, now since player's need to be able to see what the NPC transform into, let's add an option to make an area surrounding the NPC un-walkable. This will prevent players from smothering the NPC and blocking it's view from other people:
// Whether or not to block surrounding cells from players during event. // 0 = Disabled, 1 = 1x1 | 2 = 2x2 | 9 = 9x9 // Default: 0 (Disabled). .block_cells = 3;
Alright, we're almost done with adding configurations, but we've still got a few more.
Now, let's add 2 variables that deal with timing. The first being, how long does the NPC wait before officially starting the event. For this example, we're going to go with 3 minutes. And the second variable, determining the length of intermission before the NPC transform into another monster, 10 seconds.
Let's add those in:
// Minutes before event starts .start_delay = 3; // Seconds of intermission before transforming again .change_delay = 10;
Alright, now there are just 2 more configurations to add. The first being an array, that stores all the MVPs that we want the NPC to use as a disguise.
So let's create that array:
// MVP List setarray .MVP[0], OSIRIS, BAPHOMET, DOPPELGANGER, MISTRESS, GOLDEN_BUG, ORK_HERO, DRAKE, EDDGA, MAYA, MOONLIGHT, PHARAOH, PHREEONI, ORC_LORD, KNIGHT_OF_WINDSTORM, GARM, DARK_LORD, TURTLE_GENERAL, LORD_OF_DEATH, DRACULA, EVENT_BAPHO, DARK_SNAKE_LORD, INCANTATION_SAMURAI, PORING_V, AMON_RA, TAO_GUNKA, RSX_0806, BACSOJIN_, B_SEYREN, B_EREMES, B_HARWORD, B_MAGALETA, B_SHECIL, B_KATRINN, B_YGNIZEM, APOCALIPS_H, LADY_TANEE, THANATOS, DETALE, KIEL_, RANDGRIS, GLOOMUNDERNIGHT, KTULLANUX, ATROCE, G_MAGALETA_, IFRIT, FALLINGBISHOP, BEELZEBUB_, GOPINICH, MOROCC_, KUBLIN, S_NYDHOG, BOITATA;
Okay, now keep in mind there are a few MVPs that only spawn in Renewal Ragnarok. And since I couldn't possibly know what version of RO you're running, let's add a check to add those MVPs in, only if the server is in renewal mode.
To do that we need to use this command: checkre(<type>). We are going to be checking against type 0. Now, when we add the MVPs to the array, we need to add them starting at the end of the array. So we'll use the command: getarraysize(<array name>) to determine where the end is. Beats having to count all those MVPs above.
So let's add it in:
if(checkre(0)){ setarray .MVP[getarraysize(.MVP)], QUEEN_SCARABA, LOST_DRAGON, LEAK, I_QUEEN_SCARABA; }
Alright, now it's time to add the last and final configuration for the script. The monster blacklist. This will serve as a list of monster's you don't want the NPC to disguise as. We need to do this, because there are just some monsters that exist which don't spawn naturally. And we are going to weed those out! So what we're going to do, to put it simply, is we are going to loop through all the existing monsters, and check to see if they have any drops. This is because 90%+ of the monsters in ragnarok that spawn naturally have drops.
But, since there are well over a thousand monsters, we need to prevent an infinity loop. To do so we need to use this command: freeloop(<toggle>) with a value of 1 before we actually start the loop.
Lastly, to check to see if the monster drops any items we'll be using this command: getmobdrops(<mob id>). So let's create that loop:
// DO NOT CHANGE THE BELOW !! // Since this is a custom event, it should be made to support custom monsters !! // To increase mosnter range, simply increase the value of .last_mobid // Default: 5000 .last_mobid = 5000; freeloop(1); for( .@i = SCORPION; .@i < .last_mobid; .@i++ ) { if( !getmobdrops( ( .@i ) ) ) { .blacklist[.@i] = .@i; } } freeloop(0); end;Now, this is probably still confusing so let me break down what we just did:
1. We set a variable .last_mobid to 5000. This is well above the final monster in the game. This is just so we don't have update the script every time Ragnarok releases new content. This will give you years of leeway, before you have to update the script.
2. We started the loop off at SCORPION, because he is the first mob.
3. We checked to see if the mob_id we were on had any drops. If it didn't it got added to the black list.
4. Since, each monster has a unique ID, we simply used their ID number as the element of the array, and set it to itself.
5. We used freeloop(0); to stop the free looping, since it's no longer needed.
6. end; because we are FINALLY done with the OnInit: label.
That's the end of Part 2, right now your script should look something like this:
prontera,160,155,4 script Disguise Event 4_M_NFDEADMAN,{ if (getgmlevel() < .gm_level && .event_onoff) { end; } mes .@n$ = "[^0000FFDisguise NPC^000000]", "Welcome. How may I help you?"; next; mes .@n$; switch(select("Information", ((getgmlevel() >= .gm_level)?"Turn Event ["+(.event_onoff?"^FF0000OFF":"^0000FFON")+"^000000]:Event Settings":":"), "Nothing, just passing by.")) { case 1: mes "This event is quite simple. At the ", "start of the event, I will disguise", "myself as a random monster.", "You have to shout that monster's", "name out loud."; next; mes "If you are correct, you will", "receive a prize. If not, keep ", "trying! That's all that there is to ", "this event."; break; case 2: mes "Are you sure you want to turn the ", "event ["+(.event_onoff?"^FF0000OFF":"^0000FFON")+"^000000]"; if (select("Yes", "No") == 2) { close; } if (.event_onoff) { .event_onoff = 0; announce "A GM has decided to turn the Disguise Event off. As a result no further prizes will be rewarded.",bc_all | bc_blue; deletepset 1; setnpcdisplay "Disguise Event", 4_M_NFDEADMAN; stopnpctimer; close; } close2; callsub iStartEvent; end; case 3: mes "Pick a setting to modify."; next; mes .@n$; switch(select("Monster Display", "Number of Rounds", "Prize Settings")) { case 1: mes "Choose a disguise rule."; next; .disguise_option = select(implode(.disguise_desc$), ":")) - 1; mes .@n$; "The Disguise Rule has been set:", "----> [^0000FF"+ .disguise_desc$[.disguise_option] +"^000000]"; break; case 2: mes "Input the number of rounds you want", "the event to last.", "Current number of rounds: [^0000FF"+ .rounds +"^000000]"; next; if (input(.@rounds,.min,.max)) { .@rounds = .min; } .rounds = .@rounds; mes .@n$, "The number of rounds has been", "changed to "+ .rounds +"."; break; case 3: mes "Tell me the Item ID of the prize ", "to be given each round.", "Current item: [^0000FF"+ getitemname(.prize_item) +"^000000]"; next; input .@prize; if (getitemname(.@prize) == "null") { mes "That item does not exist.", "Please try again."; close; } .prize_item = .@prize; mes "Input the amount to be given."; next; mes .@n$; if (input(.@amount,1,10000)) { mes "That amount is invalid.", "Using a default amount of 1."; .@amount = 1; next; mes .@n$; } .prize_amount = .@amount; mes "Successfully changed prize.", "Prize: "+ .prize_amount +"x [^0000FF"+ getitemname(.prize_item) +"^000000]"; break; } break; } close; OnInit: .gm_level = 60; .rounds = 10; .prize_item = Red_Potion; .prize_amount = 10; .min = 3; .max = 10; // What disguise rule, the NPC should follow by default. // 0 = Disguise as all monsters | 1 = Disguise as MVPs only // Default: 1 .disguise_option = 1; setarray .disguise_desc$[0],"Disguise as all monsters.", "Disguise as MVPs only."; // Whether or not to block surrounding cells from players during event. // 0 = Disabled, 1 = 1x1 | 2 = 2x2 | 9 = 9x9 // Default: 0 (Disabled). .block_cells = 3; // Minutes before event starts .start_delay = 3; // Seconds of intermission before transforming again .change_delay = 10; // MVP List setarray .MVP[0], OSIRIS, BAPHOMET, DOPPELGANGER, MISTRESS, GOLDEN_BUG, ORK_HERO, DRAKE, EDDGA, MAYA, MOONLIGHT, PHARAOH, PHREEONI, ORC_LORD, KNIGHT_OF_WINDSTORM, GARM, DARK_LORD, TURTLE_GENERAL, LORD_OF_DEATH, DRACULA, EVENT_BAPHO, DARK_SNAKE_LORD, INCANTATION_SAMURAI, PORING_V, AMON_RA, TAO_GUNKA, RSX_0806, BACSOJIN_, B_SEYREN, B_EREMES, B_HARWORD, B_MAGALETA, B_SHECIL, B_KATRINN, B_YGNIZEM, APOCALIPS_H, LADY_TANEE, THANATOS, DETALE, KIEL_, RANDGRIS, GLOOMUNDERNIGHT, KTULLANUX, ATROCE, G_MAGALETA_, IFRIT, FALLINGBISHOP, BEELZEBUB_, GOPINICH, MOROCC_, KUBLIN, S_NYDHOG, BOITATA; if(checkre(0)){ setarray .MVP[getarraysize(.MVP)], QUEEN_SCARABA, LOST_DRAGON, LEAK, I_QUEEN_SCARABA; } // DO NOT CHANGE THE BELOW !! // Since this is a custom event, it should be made to support custom monsters !! // To increase mosnter range, simply increase the value of .last_mobid // Default: 5000 .last_mobid = 5000; freeloop(1); for( .@i = SCORPION; .@i < .last_mobid; .@i++ ) { if( !getmobdrops( ( .@i ) ) ) { .blacklist[.@i] = .@i; } } freeloop(0); end;
Alright, all of that and we only just finished the OnInit: label. Let's stop here for now, and continue in Part 3, the core of our script.
Part 3
Spoiler
Alright, now we are on the final part of this script, the core. Here is where all the technical bits of the script are.
Okay so to start the core off, this script starts every 2 hours of the day. So we're going to just add a bunch of OnClock lablels, that will represent every 2 hours:
Simple right? Alright, now when ever the server reaches any one of those times, they are all going to end up at the same spot. Which is where we start the event. However, we also made the script start when a GM chooses to do so, so we're going to add that label here as well:
Alright, now all the starting event callers are taken care of. However, if we leave it as is, there'll be an issue where a GM could have started the event, and then the server tries to start it, because it's that time of the day. So we are going to add a check, that will end the script abruptly if the event is already running:
Okay, that takes care of that issue. Now it's time to get down to business. There are 3 variables we are going to set, .change, .round_count and .event_onoff. We are going to set them to 0, 0 and 1 respectively.
So let's do it:
The reason we set those variables to 0, is because they are set to other values throughout the event. However, instead of changing them to 0 when the event ends, we are just simply doing it before the event starts. This is to ensure a fresh event start.
Alright, now next we are going to add 2 announcements. The first, letting players know when the event is going to officially start, and the second letting them know where the event is being held, in this case the map the NPC is on, to get that we'll need to use this command: strnpcinfo(<type>)
Also, we'll be editing our first announcement to show the proper wording depending on how many minutes are left, so we'll need to use a conditional check (?: ) in side.
Let's do it:
Okay, with that taken care of, it's now time to do the announcements that happen every 1 minute until the event starts. In the old version, this was done through an npc_timer. However, I've changed that around to use a generic_loop. This just means I'm going to call a single label over and over again, until it's fulfilled it's purpose, in our case it's just until the event starts.
So we'll start by adding the label, iSleepTimer:
Okay, now we need to have the script wait 1 minute before it does anything. To do that we need to use this command: sleep {<milliseconds>}; So, since those are milliseconds, we'll just use the value of 60,000:
Okay, so now we need to count how many minutes pass by. To do that, we'll just set a scope_variable to +1 after every minute we wait:
Next, we are going to add a simple check to see if the event is still on or not. If it isn't on, we'll end the script, else we'd just continue on with what it's got to do. The reason we do this check here, is because a GM may have decided to turn the event off early, so we need to anticipate that.
Let's add it in:
Alright, now we need to add a check, that sees if our variable .@minute is equal to .start_delay or not. This will serve as our guide to see if we are going to loop back or not. If it isn't the same, we want to put our announcements here. The same as above, but we want it to update the time left, as well as using proper wording. Then after those 2 announcements, we'll use the callsub; command.
So let's do it:
Alright, with that taken care of, we need to run 1 more check to see if the GM turned the event off. Darn those GMs making it harder for us. So let's just add that in really quick:
Next, we'll just add 2 more announcements, this time letting them know the event has started, and where it's being located:
Okay, now we are going to have another generic loop here. This loop, will be used whenever the npc needs to change it's disguise. So let's add a label here called iDisguise:, I know I lack originality. After that we'll add 1 more check to see if the event is on or not, yet again.
So let's just add this in really quick:
Alright, with that out of the way, we now need to check if the npc is currently disguising itself by seeing if the variable .change is set to 1 or not. If it is disguising itself we'll simply make the npc pause for .change_delay seconds. Using the sleep; command.
Let's add it in:
Next, we need to set .change to 0 again. Even though we set it to 0 when the event starts, we need to set it to 0 after the npc disguises itself again. Then we'll also set the variable .winner to 0 as well. Because, if the npc is disguising itself, then someone probably already guessed the right answer and it's moved on to the next round:
Alright, now it's time to make the npc disguise into a monster, although, different from the last disguised monster if it previously had a disguise. To do this, we'll need to first switch between the disguise options we chose.
So let's use a switch based on .disguise_option:
Now since we only have 2 options, we're only going to have 2 cases here. The first case deals with the npc being able to disguise as all monsters. So we'll start with that. Firstly, let's make the npc choose a random monster ID between SCORPION and .last_mobid, then we'll check to see if the ID chosen is in the blacklist we created. If it is, then we'll simply call the label iDisguise again, and make the npc choose another disguise until it's a monster that's not on the black list.
To do that we'll need to use the command: compare(<string>,<substring>) to check if it's apart of the black list.
As well as the command: rand(<number>{,<number>}); for choosing the monster id.
So let's add it in:
Alright, now we need to check if the monster chosen, is the same monster that it was disguised as last time. To do that, we'll check the monster name of the ID chosen vs the variable .last_monster$ which contains the name of the monster we last chose. If it is the same, we'll call the label iDisguise again, until the npc chooses a completely unique monster to disguise as.
To do that we'll need to use this command: getmonsterinfo(<mob ID>,<type>) for <mob_ID> use the variable .@monster, and for <type> we'll be using 0 to obtain it's name.
So let's add it in:
Okay so now the npc should have chosen a unique monster. So now we are going to set 2 variables. The first being, .last_monster$ and we are going to set that to the variable .monster_name$ which in turn, is going to be set to the monsters name of the id chosen. Then we're just going to use the command break; to get out of the switch command.
Simple right? Let's do it:
And that's it for case 1. Now it's time to do case 2, which is very similar to case 1, with the exception we'll be choosing a random MVP monster for the array we created. Which means we don't need to check if it's on the blacklist. So let's start by choosing a random element of the MVP array we created. and then checking to see if it's the same MVP we chose last time. If it is, call the label iDisguise:
Let's add it in:
Alright, now we just need to set .last_monster$ to .monster_name$ which is set to the name of the MVP chosen. After that we need to set the variable, .@monster to the id chosen so the NPC knows what monster to disguise as, then we'll end it with a break; command and close out the switch command:
Okay, so by now the NPC has successfully chosen a unique monster regardless of the disguise option. What we need to do now is have it do the following, change it's display to the monster chosen. To do that we'll use this command: setnpcdisplay("<npc name>", <class id>) for <class_id> we'll use the monster id we chose.
In combination with the command strnpcinfo(3); Then we need to set an npc timer to 0, and start it. This will serve to make the npc change it's disguise every 30seconds, meaning player's have that long to guess what the NPC currently is before it switches disguises again.
To do that we'll need to use these 2 commands: setnpctimer <tick>{,"<NPC name>"}; and initnpctimer { "<NPC name>" {, <Attach Flag>} }
So let's add it in:
Okay, the next thing we need to do is call the label iSetPset: while passing the argument of 1. After that we need to end; the script.
So let's do that really quick:
Alright that takes care of the label iDisguise:. Now we need to work on the label, iSetPset: that we just called. This label, will serve to change the answer players need to input to receive a prize from the NPC and be declared the rounds winner. So let's just make the label before anything:
Okay, so before we set a new answer, we first need to delete the old one.
To do that we need to use this command: deletepset <set number>; for <set_number> we'll use 1.
So let's add that in:
Next, we need to check if we passed an argument or not, this is because this label will be called when we change disguises, and we a player wins a round and if they do, we need to delete the previous answer or people will just spam that as fast as they can to try and get a prize too.
So let's just check if getarg(0) has any value other than 0.
Now, since it does have a value of 1 in this case, we'll need to define the pattern the npc will look for when players shout out the monsters name. To do that we'll need to use this command: defpattern <set number>,"<regular expression pattern>","<event label>"; for <set_number> use 1, and for "<event_label>" we'll use the label iCorrect:
Now, I would love to break down the "<regular_expression_pattern>" for you, but sadly I don't remember how I came upon this being the correct expression pattern to be used. All you need to know is that it checks to see if what the player says, matches what the NPC is looking for.
So, just copy what I have below for the correct usage:
See, it's too confusing for me to try and explain it to you. All I can say is, it's looking for the monster name of the monster we chose, which is stored in the variable .monster_name$.
Alright, now we need to activate that answer. To do that we need to use this command: activatepset <set number>; for <set_number> we'll use 1 again. After that we need to use the command: return {<value>}; which will allow us to return to the part of the script where we called this label followed by an obligatory end; command, because we should always take precautions.
So let's add it in:
Alright now remember this script uses a timer for how long the npc will stay disguised before it's going to change it into a different monster. That time is only 30seconds, so let's create that label.
OnTimer30000: then, immediately after that label is called we need to stop the npc timer.
To do that we need to use this command: stopnpctimer { "<NPC name>" {, <Detach Flag>} }
Then we'll set the npc's display back to normal. Meaning turn back into the regular npc it was before hand. We'll also add a small special effect as well, so it's not boring when it undisguises.
To do that we need to use this command: specialeffect <effect number>{,<send_target>{,"<NPC Name>"}}; the effect we'll use is EF_DETECT2
For a list of other effects, look here: Effect List they'll begin with EF_
So let's do that:
Okay, now we're going to have the npc tell the players in the surrounding area, that they took too long to guess what it was disguised as. And to please wait .change_delay seconds while it disguises again. Make sure to use proper labeling for second/seconds if you made it more than 1second long. To do that we'll need to use this command: npctalk "<message>";
So let's just add that in:
Next we need to reset the variable .monster_name$, then we'll call the label iSetPset again, this time without an argument. After that we need to set the variable .change to 1 so the script knows that it's changing it's disguise and will wait the proper time before it actually chooses a disguise. Then lastly call the label iDisguise: and end; the script early.
So let's do that:
Perfect, now we're finally on the last part of the script. The label iCorrect: which deals with when a player correctly guesses the monster's name. We'll start off by checking to see if the variable .winner is set to anything other than 0. If it is, we'll use the command dispbottom; to have the npc say, Someone has already won this round. Then end; the script early. After that we'll actually set the variable .winner to 1. This will prevent multiple people being a winner in the same round if the server lags.
Lastly, we'll set the variable .round_count +1. So that the npc knows the round is over.
Let's add it in:
Okay, now we need to call the label iSetPset without an argument yet again. This will delete the current answer and set it to nothing, which is impossible for players to enter. Then we'll give the winner the prize we chose along with the amount we chose:
Next we'll have the npc announce that strcharinfo(0) is correct, and tell them that the NPC was disguised as .monster_name$. This time we'll only announce it to those on the current map. Then have the NPC change it's display back to a normal npc. Because before this label was called the npc was disguised as a monster:
Now we'll check to see if the we finished all the rounds that we said the npc would last for. If it is, have the npc tell those in the surrounding area, "Thank you all for playing. That was the last round of the Disguise Event. Come play again later."
Then we'll turn the event off by setting the variable .event_onoff to 0. Lastly stop the npctimer, and end; the script early.
Let's add that in:
Okay now for the final stretch. Have the npc tell the player's to wait .change_delay second(s if more than 1 second) while it disguises again. Then set the npctimer to 0, and stop the timer. This is to prevent the timer going to 30seconds again while it tries to find a disguise. Next, we'll set the variable .change to 1, so the NPC will wait the proper time before finding a suitable disguise. Lastly call the label iDisguise: and end; the script completely.
This is it, add it in:
Alright, now we are on the final part of this script, the core. Here is where all the technical bits of the script are.
Okay so to start the core off, this script starts every 2 hours of the day. So we're going to just add a bunch of OnClock lablels, that will represent every 2 hours:
OnClock0000: OnClock0200: OnClock0400: OnClock0600: OnClock0800: OnClock1000: OnClock1200: OnClock1400: OnClock1600: OnClock1800: OnClock2000: OnClock2200:
Simple right? Alright, now when ever the server reaches any one of those times, they are all going to end up at the same spot. Which is where we start the event. However, we also made the script start when a GM chooses to do so, so we're going to add that label here as well:
iStartEvent:
Alright, now all the starting event callers are taken care of. However, if we leave it as is, there'll be an issue where a GM could have started the event, and then the server tries to start it, because it's that time of the day. So we are going to add a check, that will end the script abruptly if the event is already running:
if (.event_onoff) { end; }
Okay, that takes care of that issue. Now it's time to get down to business. There are 3 variables we are going to set, .change, .round_count and .event_onoff. We are going to set them to 0, 0 and 1 respectively.
So let's do it:
.change = .round_count = 0; .event_onoff = 1;
The reason we set those variables to 0, is because they are set to other values throughout the event. However, instead of changing them to 0 when the event ends, we are just simply doing it before the event starts. This is to ensure a fresh event start.
Alright, now next we are going to add 2 announcements. The first, letting players know when the event is going to officially start, and the second letting them know where the event is being held, in this case the map the NPC is on, to get that we'll need to use this command: strnpcinfo(<type>)
Also, we'll be editing our first announcement to show the proper wording depending on how many minutes are left, so we'll need to use a conditional check (?: ) in side.
Let's do it:
announce "The Disguise Event will begin in "+ .start_delay +" minute"+ ( (.start_delay > 1)?"s":"") +".",bc_all | bc_blue; announce "The Event is being held in "+ strnpcinfo(4) +".",bc_all | bc_blue;
Okay, with that taken care of, it's now time to do the announcements that happen every 1 minute until the event starts. In the old version, this was done through an npc_timer. However, I've changed that around to use a generic_loop. This just means I'm going to call a single label over and over again, until it's fulfilled it's purpose, in our case it's just until the event starts.
So we'll start by adding the label, iSleepTimer:
iSleepTimer:
Okay, now we need to have the script wait 1 minute before it does anything. To do that we need to use this command: sleep {<milliseconds>}; So, since those are milliseconds, we'll just use the value of 60,000:
sleep 60000;
Okay, so now we need to count how many minutes pass by. To do that, we'll just set a scope_variable to +1 after every minute we wait:
++.@minute;
Next, we are going to add a simple check to see if the event is still on or not. If it isn't on, we'll end the script, else we'd just continue on with what it's got to do. The reason we do this check here, is because a GM may have decided to turn the event off early, so we need to anticipate that.
Let's add it in:
if (!.event_onoff) { end; }
Alright, now we need to add a check, that sees if our variable .@minute is equal to .start_delay or not. This will serve as our guide to see if we are going to loop back or not. If it isn't the same, we want to put our announcements here. The same as above, but we want it to update the time left, as well as using proper wording. Then after those 2 announcements, we'll use the callsub; command.
So let's do it:
if( .@minute != .start_delay ) { announce "The Disguise Event will begin in "+ ( .start_delay - .@minute ) +" minute"+ ( (.start_delay - .@minute > 1)?"s":"") +".",bc_all | bc_blue; announce "The Event is being held in "+ strnpcinfo(4) +".",bc_all | bc_blue; callsub iSleepTimer; }
Alright, with that taken care of, we need to run 1 more check to see if the GM turned the event off. Darn those GMs making it harder for us. So let's just add that in really quick:
if (!.event_onoff) { end; }
Next, we'll just add 2 more announcements, this time letting them know the event has started, and where it's being located:
announce "The Disguise Event has begun!",bc_all | bc_blue; announce "The Event is being held in "+ strnpcinfo(4) +".",bc_all | bc_blue;
Okay, now we are going to have another generic loop here. This loop, will be used whenever the npc needs to change it's disguise. So let's add a label here called iDisguise:, I know I lack originality. After that we'll add 1 more check to see if the event is on or not, yet again.
So let's just add this in really quick:
iDisguise: if (!.event_onoff) { end; }
Alright, with that out of the way, we now need to check if the npc is currently disguising itself by seeing if the variable .change is set to 1 or not. If it is disguising itself we'll simply make the npc pause for .change_delay seconds. Using the sleep; command.
Let's add it in:
if (.change) { sleep (.change_delay * 1000); }
Next, we need to set .change to 0 again. Even though we set it to 0 when the event starts, we need to set it to 0 after the npc disguises itself again. Then we'll also set the variable .winner to 0 as well. Because, if the npc is disguising itself, then someone probably already guessed the right answer and it's moved on to the next round:
.change = .winner = 0;
Alright, now it's time to make the npc disguise into a monster, although, different from the last disguised monster if it previously had a disguise. To do this, we'll need to first switch between the disguise options we chose.
So let's use a switch based on .disguise_option:
switch(.disguise_option) {
Now since we only have 2 options, we're only going to have 2 cases here. The first case deals with the npc being able to disguise as all monsters. So we'll start with that. Firstly, let's make the npc choose a random monster ID between SCORPION and .last_mobid, then we'll check to see if the ID chosen is in the blacklist we created. If it is, then we'll simply call the label iDisguise again, and make the npc choose another disguise until it's a monster that's not on the black list.
To do that we'll need to use the command: compare(<string>,<substring>) to check if it's apart of the black list.
As well as the command: rand(<number>{,<number>}); for choosing the monster id.
So let's add it in:
case 1: .@monster = rand(SCORPION, .last_mobid); if (compare(.blacklist$, ","+ .@monster +",")) { callsub iDisguise; }
Alright, now we need to check if the monster chosen, is the same monster that it was disguised as last time. To do that, we'll check the monster name of the ID chosen vs the variable .last_monster$ which contains the name of the monster we last chose. If it is the same, we'll call the label iDisguise again, until the npc chooses a completely unique monster to disguise as.
To do that we'll need to use this command: getmonsterinfo(<mob ID>,<type>) for <mob_ID> use the variable .@monster, and for <type> we'll be using 0 to obtain it's name.
So let's add it in:
if (getmonsterinfo(.@monster, 0) == .last_monster$) { callsub iDisguise; }
Okay so now the npc should have chosen a unique monster. So now we are going to set 2 variables. The first being, .last_monster$ and we are going to set that to the variable .monster_name$ which in turn, is going to be set to the monsters name of the id chosen. Then we're just going to use the command break; to get out of the switch command.
Simple right? Let's do it:
.last_monster$ = .monster_name$ = getmonsterinfo(.@monster, 0); break;
And that's it for case 1. Now it's time to do case 2, which is very similar to case 1, with the exception we'll be choosing a random MVP monster for the array we created. Which means we don't need to check if it's on the blacklist. So let's start by choosing a random element of the MVP array we created. and then checking to see if it's the same MVP we chose last time. If it is, call the label iDisguise:
Let's add it in:
case 2: .@i = rand(getarraysize(.MVP)); if (getmonsterinfo(.MVP[.@i], 0) == .last_monster$) { callsub iDisguise; }
Alright, now we just need to set .last_monster$ to .monster_name$ which is set to the name of the MVP chosen. After that we need to set the variable, .@monster to the id chosen so the NPC knows what monster to disguise as, then we'll end it with a break; command and close out the switch command:
.last_monster$ = .monster_name$ = getmonsterinfo(.MVP[.@i], 0); .@monster = .MVP[.@i]; break; }
Okay, so by now the NPC has successfully chosen a unique monster regardless of the disguise option. What we need to do now is have it do the following, change it's display to the monster chosen. To do that we'll use this command: setnpcdisplay("<npc name>", <class id>) for <class_id> we'll use the monster id we chose.
In combination with the command strnpcinfo(3); Then we need to set an npc timer to 0, and start it. This will serve to make the npc change it's disguise every 30seconds, meaning player's have that long to guess what the NPC currently is before it switches disguises again.
To do that we'll need to use these 2 commands: setnpctimer <tick>{,"<NPC name>"}; and initnpctimer { "<NPC name>" {, <Attach Flag>} }
So let's add it in:
setnpcidsplay strnpcinfo(3), .@monster; setnpctimer 0; initnpctimer;
Okay, the next thing we need to do is call the label iSetPset: while passing the argument of 1. After that we need to end; the script.
So let's do that really quick:
callsub iSetPset,1; end;
Alright that takes care of the label iDisguise:. Now we need to work on the label, iSetPset: that we just called. This label, will serve to change the answer players need to input to receive a prize from the NPC and be declared the rounds winner. So let's just make the label before anything:
iSetPset:
Okay, so before we set a new answer, we first need to delete the old one.
To do that we need to use this command: deletepset <set number>; for <set_number> we'll use 1.
So let's add that in:
deletepset 1;
Next, we need to check if we passed an argument or not, this is because this label will be called when we change disguises, and we a player wins a round and if they do, we need to delete the previous answer or people will just spam that as fast as they can to try and get a prize too.
So let's just check if getarg(0) has any value other than 0.
if (getarg(0)) {
Now, since it does have a value of 1 in this case, we'll need to define the pattern the npc will look for when players shout out the monsters name. To do that we'll need to use this command: defpattern <set number>,"<regular expression pattern>","<event label>"; for <set_number> use 1, and for "<event_label>" we'll use the label iCorrect:
Now, I would love to break down the "<regular_expression_pattern>" for you, but sadly I don't remember how I came upon this being the correct expression pattern to be used. All you need to know is that it checks to see if what the player says, matches what the NPC is looking for.
So, just copy what I have below for the correct usage:
defpattern 1, "([^:]+):.\\s*"+ .monster_name$ +".*", "iCorrect"; }
See, it's too confusing for me to try and explain it to you. All I can say is, it's looking for the monster name of the monster we chose, which is stored in the variable .monster_name$.
Alright, now we need to activate that answer. To do that we need to use this command: activatepset <set number>; for <set_number> we'll use 1 again. After that we need to use the command: return {<value>}; which will allow us to return to the part of the script where we called this label followed by an obligatory end; command, because we should always take precautions.
So let's add it in:
activatepset 1; return; end;
Alright now remember this script uses a timer for how long the npc will stay disguised before it's going to change it into a different monster. That time is only 30seconds, so let's create that label.
OnTimer30000: then, immediately after that label is called we need to stop the npc timer.
To do that we need to use this command: stopnpctimer { "<NPC name>" {, <Detach Flag>} }
Then we'll set the npc's display back to normal. Meaning turn back into the regular npc it was before hand. We'll also add a small special effect as well, so it's not boring when it undisguises.
To do that we need to use this command: specialeffect <effect number>{,<send_target>{,"<NPC Name>"}}; the effect we'll use is EF_DETECT2
For a list of other effects, look here: Effect List they'll begin with EF_
So let's do that:
OnTimer30000: stopnpctimer; setnpcdisplay strnpcinfo(3), 4_M_NFDEADMAN; specialeffect EF_DETECT2;
Okay, now we're going to have the npc tell the players in the surrounding area, that they took too long to guess what it was disguised as. And to please wait .change_delay seconds while it disguises again. Make sure to use proper labeling for second/seconds if you made it more than 1second long. To do that we'll need to use this command: npctalk "<message>";
So let's just add that in:
npctalk "You took too long to guess what I was. Please wait "+ .change_delay +" second"+((.change_delay > 1)"s":"")+" while I disguise again.";
Next we need to reset the variable .monster_name$, then we'll call the label iSetPset again, this time without an argument. After that we need to set the variable .change to 1 so the script knows that it's changing it's disguise and will wait the proper time before it actually chooses a disguise. Then lastly call the label iDisguise: and end; the script early.
So let's do that:
.monster_name$ = ""; callsub iSetPset; .change = 1; callsub iDisguise; end;
Perfect, now we're finally on the last part of the script. The label iCorrect: which deals with when a player correctly guesses the monster's name. We'll start off by checking to see if the variable .winner is set to anything other than 0. If it is, we'll use the command dispbottom; to have the npc say, Someone has already won this round. Then end; the script early. After that we'll actually set the variable .winner to 1. This will prevent multiple people being a winner in the same round if the server lags.
Lastly, we'll set the variable .round_count +1. So that the npc knows the round is over.
Let's add it in:
iCorrect: if (.winner) { dispbottom "Someone has already won this round."; end; } .winner = 1; ++.round_count;
Okay, now we need to call the label iSetPset without an argument yet again. This will delete the current answer and set it to nothing, which is impossible for players to enter. Then we'll give the winner the prize we chose along with the amount we chose:
callsub iSetPset; getitem .prize_item, .prize_amount;
Next we'll have the npc announce that strcharinfo(0) is correct, and tell them that the NPC was disguised as .monster_name$. This time we'll only announce it to those on the current map. Then have the NPC change it's display back to a normal npc. Because before this label was called the npc was disguised as a monster:
announce strcharinfo(0) +" is correct!! I was disguised as: "+ .monster_name$ +"",bc_map | bc_blue; setnpcdisplay strnpcinfo(3), 4_M_NFDEADMAN;
Now we'll check to see if the we finished all the rounds that we said the npc would last for. If it is, have the npc tell those in the surrounding area, "Thank you all for playing. That was the last round of the Disguise Event. Come play again later."
Then we'll turn the event off by setting the variable .event_onoff to 0. Lastly stop the npctimer, and end; the script early.
Let's add that in:
if (.round_count >= .rounds) { npctalk "Thank you all for playing. That was the last round of the Disguise Event. Come play again later."; .event_onoff = 0; stopnpctimer; end; }
Okay now for the final stretch. Have the npc tell the player's to wait .change_delay second(s if more than 1 second) while it disguises again. Then set the npctimer to 0, and stop the timer. This is to prevent the timer going to 30seconds again while it tries to find a disguise. Next, we'll set the variable .change to 1, so the NPC will wait the proper time before finding a suitable disguise. Lastly call the label iDisguise: and end; the script completely.
This is it, add it in:
npctalk "Please wait "+ .change_delay +" second"+((.change_delay > 1)?"s":"")+", while I disguise again."; setnpctimer 0; stopnpctimer; .change = 1; callsub iDisguise; end; }And that's it. The disguise event is done. It should look something like this when you're finished: Disguise Event (Have to use link as this post has gotten too long to paste the script in a code block).