Jump to content

GmOcean

Members
  • Posts

    666
  • Joined

  • Last visited

  • Days Won

    4

Posts posted by GmOcean

  1. Yeah, sorry, when I copy + pasted it, the code tags vanished. Just finished adding them in =P

    @ Idea to save posts and such, I don't really mind using just 1 post for the whole content, but it's up to you moderators on what you think would be best. I'm just trying to submit helpful stuff for the community.

    Edit:

    If you decide that multiple posts are better for this topic, then perhaps just limiting it to 4 posts.

    1. Basic Scripts

    2. Intermediate Scripts

    3. Advanced Scripts

    4. Expert Scripts

  2. Scripting Techniques, Tutorials and Guides

    I'm going to attempt to help everyone here with their scripts while not being directly involved in helping you with them xD.
    This will also help future/current scripters get some fresh info and maybe inspire innovative ideas towards scripting.
    So that we can prove that us " Scripters " are the superior ragnarok emulation race! Take that Source Code writers!! Jk lol, we need you too biggrin.png.

    What I'm going to do, is write detailed tutorials on how to write a few scripts. Starting from the basics to more advanced scripts.
    This way everyone can follow along. And hopefully this will help everyone understand how to write a few scripts and even troubleshoot their own scripts.

     

    During these guides, I'll be using my own, scripting form, but i'll try not to deviate too far from the norm biggrin.png.

     // A list of <sprite id>s can be found here: Sprite_IDs Credits: Ai4rei

    - Beginner Scripts -

    Scripts for complete beginners and novice scripters.

    Basic Healer & Buffer Script - Complete ( 5 Parts )

    A Basic Healer Script. Here we will cover most, if not all of the basics of scripting.

    Basic Healer - Your First Script ( Part 1 )


     Okay, for this healer were going to create one that heals you 100%HP&SP when you click on it and choose heal me.
     So let's start off by writing the NPC header.

    <map name>,<x>,<y>,<facing>%TAB%script%TAB%<NPC Name>%TAB%<sprite id>,{<code>}
    // Facing Directions, each number is the equivalent to a point on a compass.
    [1][0][7]
    [2] [6]
    [3][4][5]

    Alright time to begin, starting with the NPC header:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{

    Now that we have the header out of the way it's time write a little bit of dialogue.
     To do that, were going to use the command: mes "<text>";

    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";

    Okay, we got the little bit of dialogue out of the way. Now let's give them the choice of whether or not they want to be healed.

    To do so, were going to be using a combination of 2 commands.

    First being: if( <condition> ) { <statement> };

    and Second being: select("<option>"{,"<option>",...})

    *Note - Options can be grouped together, separated by the character ' : '.*

    If you are unsure what I mean by " condition " take a look at this: Operators

    But we are also going to end the script early, if they decided to choose: ' No thank you. '  by using the command: close;

    The reason we are using the " close " command, is because when we used: " mes ", it caused a text window to open up. And we need to " close " that window. Simple right?

    if( select("Yes, heal me.:No thank you.") == 2 ){
    close;
    }

    Now that we have that finished, it is time to heal them for 100%HP&SP using the command: percentheal <hp>,<sp>;

    And also end the script. Again, using the " close " command.

    percentheal 100,100;
    close;

    Now, all we need to do is add the last } to the end of our script so that it knows there is nothing more to add.
    Once your done your script should look something like this:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    if( select("Yes, heal me.:No thank you.") == 2 ){
    close;
    }
    percentheal 100,100;
    close;
    }

    And that's it for your first script. You just created a healer npc.

    Basic Healer with a Zeny Cost - ( Part 2 )

    Okay, firstly you should have already completed the, " Basic Healer - Your First Script " guide. If not you need to do so to follow along. Unless of course your proficient enough in scripting you don't need to. In which case you should be heading down to Intermediate Scripts you show-off you!

     

    This is going to be a small edit to our script to let player's know it cost zeny to use. Then we are going to check if they have enough zeny. If they do, we'll take it away and heal them.

    Let's start by opening up the script we made in " Part 1 ".

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    if( select("Yes, heal me.:No thank you.") == 2 ){
    close;
    }
    percentheal 100,100;
    close;
    }

    Okay, lets add the following line above the menu options:

    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";

    Next, directly above: percentheal 100,100;   were going to be adding our check to see if they have enough zeny. If they do not, we'll be telling them that they don't have enough zeny. Then we'll end the script.

    To do so, we're going to be using the command: if( <condition> ) { <statement> };

    If you are unsure what I mean by " condition " take a look at this: Operators

    if( Zeny < 1000 ){
    mes "I'm sorry, but you don't have enough zeny.";
    close;
    }

    Okay we got the check out of the way. Now we just need to take the zeny away from them if they do have enough zeny.

    To do that we'll be setting the constant variable Zeny to a value of Zeny - 1000. To see how this is done look here: Assigning variables

    Now then, lets add it in:

    Zeny -= 1000;

    And that's all there is to edit. When your done it should look something like this:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    if( select("Yes, heal me.:No thank you.") == 2 ){
    close;
    }
    if( Zeny < 1000 ){
    mes "I'm sorry, but you don't have enough zeny.";
    close;
    }
    Zeny -= 1000;
    percentheal 100,100;
    close;
    }

    And that's it, you just finished editing your script, and now it costs players 1,000 zeny to get healed.

    Basic Healer & Buffer with Zeny cost - ( Part 3 )

    Alright, to start this off. I'm assuming you already finished Part 1 & 2. If not, I recommend you do so,
    since we will be using those scripts to make this one. I will attempt to continue this pattern until we deviate
    away from the healer and move on to a different type of script.

     

    So first, let's open up the script we made in ( Part 2 ).

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    if( select("Yes, heal me.:No thank you.") == 2 ){
    close;
    }
    if( Zeny < 1000 ){
    mes "I'm sorry, but you don't have enough zeny.";
    close;
    }
    Zeny -= 1000;
    percentheal 100,100;
    close;
    }

    Let's start by adding a MES to show how much they cost. For this script were going to make it nice and cheap.
    5,000z to get both AGI-UP & BLESSING Lvl 10. Meh, what can I say, " I'm a generous god. " - The movie 300.
    And were going to add this right above our menu options.

    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";

    Now that people know we offer buffs, we need to give them the option to choose to get buffed.
    So lets modify our menu to have a 3rd option.

    if( select("Yes, heal me.:I want buffs:No thank you.") == 3 ) {
        close;
    }

    Now that we got that out of the way. It's time to modify our zeny check. It's going to be customized a little bit.
    Were now going to use | | and && operators in our checks.
    So go ahead and find our zeny check and change it to the below:

    if( (Zeny < 1000 && @menu == 1) || (Zeny < 5000 && @menu == 2) ) {
        mes "I'm sorry, but you don't have enough zeny.";
        close;
    }

    Your probably wondering why were using @menu == 1 or @menu == 2. This is because when ever a menu option is
    selected, the variable @menu is set to that option. So, the first option is equal to 1, and the second is equal to 2,
    so on and so forth.

     

    At anyrate, since we are still in the beginner section of scripting, were going to be adding 1 more check to our script.
    So go ahead and add the following to your script:

    if( @menu == 1 ) {
        Zeny -= 1000;
        percentheal 100,100;
    }

    However, this isn't complete. We still need to add the last part, which deals with our buffs.
    To do that we're going to be using the command: sc_start <effect type>,<ticks>,<value 1>{,<rate>,<flag>{,<GID>}};
    Where, effect type is the buff we want to give them, ticks is how long it will last in 1/1000ths of a second, and value is the level of the buff.
    In our case, we are using Blessing Level 10 and AGI-UP Level 10. And to keep things simple, we're going to make it last for 5mins each.
    If you wish to add other status-effects then look in this file: const.txt
    It will be the things starting with:  SC_ .

    So, let's start by finding our previous check and find:  } . And add the following next to it:

    else {
        Zeny -= 5000;
        sc_start SC_BLESSING,300000,10;
        sc_start SC_INC_AGI,300000,10;
    }

    So your probably looking at the 300000 and wondering why it's like that. Well the answer is simple.
    Ticks are 1/1,000ths of a second. So to find 5 minutes we do the following math.
    1 * 1,000 = 1 second * 60 = 60,000 OR 1 Minute * 5 = 300,000 OR 5 Minutes. So now that we have our number we just put it in.
    Okay, now that it's done. Your script should look like the following:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";
    if( select("Yes, heal me.:I want buffs:No thank you.") == 3 ) {
        close;
    }
    if( (Zeny < 1000 && @menu == 1) || (Zeny < 5000 && @menu == 2) ) {
        mes "I'm sorry, but you don't have enough zeny.";
        close;
    }
    if( @menu == 1 ) {
        Zeny -= 1000;
        percentheal 100,100;
    } else {
        Zeny -= 5000;
        sc_start SC_BLESSING,300000,10;
        sc_start SC_INC_AGI,300000,10;
    }
    Zeny -= 1000;
    percentheal 100,100;
    close;
    }

    Now were just going to delete those last 2 lines above:  close;
    When your finished deleting them, your script will be done. And it should look something like the below:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";
    if( select("Yes, heal me.:I want buffs:No thank you.") == 3 ) {
        close;
    }
    if( (Zeny < 1000 && @menu == 1) || (Zeny < 5000 && @menu == 2) ) {
        mes "I'm sorry, but you don't have enough zeny.";
        close;
    }
    if( @menu == 1 ) {
        Zeny -= 1000;
        percentheal 100,100;
    } else {
        Zeny -= 5000;
        sc_start SC_BLESSING,300000,10;
        sc_start SC_INC_AGI,300000,10;
    }
    close;
    }

    And that's it. You just finished editing your script and now players have the option to receive Increased AGI and Blessing Level 10 at the cost of 5,000 zeny.

    Basic Healer & Buffer - Cleaning Up Script ( Part 4 )

    Okay, so by now, you've learned how to make a pretty standard healing npc. But, it looks kind of messy. So in this lesson, we're going to clean it up, by getting rid of some of those if( stantements ).

    So as always, let's start by opening up the previous script:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";
    if( select("Yes, heal me.:I want buffs:No thank you.") == 3 ) {
        close;
    }
    if( (Zeny < 1000 && @menu == 1) || (Zeny < 5000 && @menu == 2) ) {
        mes "I'm sorry, but you don't have enough zeny.";
        close;
    }
    if( @menu == 1 ) {
        Zeny -= 1000;
        percentheal 100,100;
    } else {
        Zeny -= 5000;
        sc_start SC_BLESSING,300000,10;
        sc_start SC_INC_AGI,300000,10;
    }
    close;
    }

    What were going to do here is just delete everything after the last mes command. So highlight everything beneath that and just press DELETE or BACKSPACE ( your choice =P ).

    When your done your script should look like this:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";

    Looks like we went back to the drawing board. But don't worry, you'll be doing this a lot down the line when writing your own scripts. Finding better ways to write it and such. So, let's start by adding the following line beneath it. This will serve as our new menu:

    switch( select("Yes, heal me.", "I want buffs", "No thank you") ) {

    I'm sure you've noticed the ' switch ' which took the place of ' if '. We also got rid of the ' == 3 ' part. This is because switch is much cleaner and easier to read. But, first let's explain what it does. Simply, all it does is cycle through labels called:  case 1:   case 2:  etc... depending on the value of a variable or number. In our case, it's the variable @menu which will have either a value of 1, 2 or 3 because of our menu.

    If you want a further explanation, you can click the spoiler below for a bit more detail. Otherwise, let's continue to the next step.

    switch( select("Yes, heal me.", "I want buffs.", "No thank you.") ) { }
    // When you select either of the above, it will assign the variable: @menu to it's corresponding slot in the menu.
    // In our case:
    // "Yes, heal me." = 1 | "I want buffs." = 2 | "No thank you." = 3
    
    

    Utilizing the switch() command in combination with our menu we can clean up our scripts to do this:

    switch( select("Yes, heal me.", "I want buffs.", "No thank you.") ) {
        case 1:
            mes "You chose option 1, ' Yes, heal me. ' ";
            break;
        case 2:
            mes "You chose option 2, ' I want buffs. ' ";
            break;
        case 3:
            break;
    }
    close; // <--- This will end the script for all 3 options.
    
    // Additionally the above is the exact same as this:
    
    select("Yes, heal me.", "I want buffs.", "No thank you.");
    switch( @menu ) {
        case 1:
            mes "You chose option 1, ' Yes, heal me. ' ";
            break;
        case 2:
            mes "You chose option 2, ' I want buffs. ' ";
            break;
        case 3:
            break;
    }
    close;

    // However, both of those have an unnecessary part. And that part is:

    case 3:
        break;
    

    // We can remove it since it does nothing, or we just want the to end the script with close. So we can make it look like this, which will be the final product:

    switch( select("Yes, heal me.", "I want buffs.", "No thank you.") ) {
        case 1:
            mes "You chose option 1, ' Yes, heal me. ' ";
            break;
        case 2:
            mes "You chose option 2, ' I want buffs. ' ";
            break;
    }
    close; // <--- This will end the script for all 3 options.

    Okay, let's add the sub-label:  case 1:  to our script. This will deal with players click on " Yes, heal me. ". So let's also add the zeny check & percentheal in there as well. So lets add the following:

    case 1:
        if( Zeny < 1000 ) {
            mes "I'm sorry, you don't have enough zeny.";
            close;
        }
        Zeny -= 1000;
        percentheal 100,100;
        break;

    That's done. I'm sure you see the command: break; there. What this does is allow us to ' exit ', a pair of { }. Doing so, will allow us to continue on with the script if there is some other things to do. You can take a look at the spoiler above to see how we utilize this. Other wise, let's go a head and make the second label:  case 2: beneath all of that. This one will deal with when they click on " I want buffs. ", so we are going

    to be adding the zeny check for that, along with the buffs:

    case 2:
        if( Zeny < 5000 ) {
            mes "I'm sorry, but you don't have enough zeny.";
            close;
        }
        Zeny -= 5000;
        sc_start SC_BLESSING,300000,10;
        sc_start SC_INC_AGI,300000,10;
        break;

    Now that we got that out of the way. Let's add } to the end of that, to let the script know were done with the: switch() command. Then directly beneath that, were going to add: close; To let the script knows that were ending it. Followed by the final } so that the script knows that there is nothing more to do.

    }
    close;
    }

    Once your done with that, your script should look something like the below:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";
    switch( select("Yes, heal me.", "I want buffs", "No thank you") ) {
        case 1:
            if( Zeny < 1000 ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= 1000;
            percentheal 100,100;
            break;
        
        case 2:
            if( Zeny < 5000 ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= 5000;
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            break;
    }
    close;
    }

    And there you go, you just finished cleaning up your script. It looks much better, and it still does the exact same thing! Who would of thought biggrin.png

    Basic Healer & Buffer - Introduction to variables ( Part 5 )

    Alright, so far we've completed Parts 1 -> 4. And we have a working script because of it. Now, what we need to do is, make it easily configurable. Sure, we could scroll through the script, and change the zeny prices. But if we do that, we might make a mistake. So what were going to do is, add a label called:  OnInit: in our script. This label will active each and every time the server starts up, or scripts are reloaded.

    And under this label we'll be adding our variables, which will be our focus for when we want to configure things in our script.

    For a list of all variable types look here: Variables

    So go ahead and open up the script from the last lesson:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";
    switch( select("Yes, heal me.", "I want buffs", "No thank you") ) {
        case 1:
            if( Zeny < 1000 ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= 1000;
            percentheal 100,100;
            break;
        
        case 2:
            if( Zeny < 5000 ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= 5000;
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            break;
    }
    close;
    }

    We are going to add the: OnInit: label beneath the final:  close;  command. But let's also add a couple of npc_variables there as well. And let's name those variables:  heal_price   and   buff_price .

    Remeber, those are NPC variables. So let's go ahead and add the following:

    OnInit:
    .heal_price = 1000;
    .buff_price = 5000;

    Here you can see we used the set command. This is because we are telling the script to set those variables, to those values. In this case, .heal_price to 1,000 and .buff_price to 5,000 since that is what our script uses as prices for healing and buffing. Now, we really should add this command beneath that: end; . Go ahead and click that link to find out why we need to add it.

    end;

    Alright, we have officially created variables. However as they are now, they aren't doing anything and just taking up space. So now were going to put them to use. Let's comb through our script and find all of our zeny checks. Let's replace all those: 1000  and 5000 with the corresponding variables. When your done, your script should look like this so far:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs 1,000 zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs 5,000 zeny.* ^000000";
    switch( select("Yes, heal me.", "I want buffs", "No thank you") ) {
        case 1:
            if( Zeny < .heal_price ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= .heal_price;
            percentheal 100,100;
            break;
        
        case 2:
            if( Zeny < .buff_price ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= .buff_price;
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            break;
    }
    close;
    
    OnInit:
    .heal_price = 1000;
    .buff_price = 5000;
    end;
    }

    Now, there being put to use! But wait, if you look at the mes commands, they still say 1,000 and 5,000 in them. This isn't good, because if we change the values of our variables, those mes commands won't show the proper amount. And we don't want to type it out EACH and EVERY time we make changes. So let's edit that text. So go over to the line that tells players how much healing costs. And were going to make the following changes:

    mes "^FF0000 *Note - It costs "+ .heal_price +" zeny to get healed.* ^000000";

    So, as your looking at this, you'll see the following was removed:  1,000  and the following was added in it's place:  "+ .heal_price +" pay close attention to this, as it's very important. Take a close look at these: "+  and  +" . I'm going to go a bit into detail on why we need these in this spoiler. If you already know why we used them, continue on with the lesson.

    Firstly, when ever we use the mes command, we need to make a string of text keyword here is:  string. All strings begin with a quotation mark " and ends with a quotation mark ". Everything in between those 2 quotation marks is considered text. And as such will be written out like a sentence. But when we add those "+ and +" we tell the script that we are going to be putting something in the middle of this string. In our case it's the variable .heal_price . The server will then look at this variable and then put down it's data value in stead of its name. Which, we set to 1000. When it's all said and done, this is what a player will see:

    *Note - It costs 1000 zeny to get healed.* Not bad huh.

    Okay, now let's make the same changes to line where we tell players how much it costs to get buffed, but using the other variable this time:

    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs "+ .buff_price +" zeny.* ^000000";

    After that we are done! So, your script should look something like this:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs "+ .heal_price +" zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs "+ .buff_price +" zeny.* ^000000";
    switch( select("Yes, heal me.", "I want buffs", "No thank you") ) {
        case 1:
            if( Zeny < .heal_price ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= .heal_price;
            percentheal 100,100;
            break;
        
        case 2:
            if( Zeny < .buff_price ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= .buff_price;
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            break;
    }
    close;
    
    OnInit:
    .heal_price = 1000;
    .buff_price = 5000;
    end;
    }

    Now, every time we want to change how much getting healed or buffed costs, we just change the values of those variables.

     

    Basic Item Trader Script - Complete ( 2 Parts )

    A basic item trader script. Here we will learn how to give players an item in exchange for other items.

    Basic Item Trader Script - Introduction to arrays ( Part 1 )

    Alright, since your here, I'm going to assume you completed the previous guide and created a working Healer & Buffer script with a zeny cost. Or, you already know all that.

    So let's start off by making our npc header. Don't forget to use those constants for facing direction, and sprite_id. For a list of sprite IDs click the link near the top of my post.

    For this npc, I'm going to be using the vending machine sprite and I'm going to name my npc Vending Machine. Your free to choose your own sprite and name.

    prontera,152,180,4    script    Vending Machine    2_VENDING_MACHINE1,{

    So let's start off making the variables we'll be using for this script. And were going to place those under the label:  OnInit:  again. We are also going to be using npc_variables as well.

    The only difference is that this time were going to be using arrays via the command: setarray <array name>[<first value>],<value>{,<value>...<value>};

    Now, our script is going to give a player 1 Valkyrie_Helm for 5 different items. So let's add it in:

    OnInit:
    .reward_item = Valkyrie_Helm;
    .reward_amount = 1;
    setarray .required_items[0], Red_Potion, White_Potion, Blue_Potion, Jellopy, Poring_Card;
    setarray .required_amount[0], 10, 5, 1, 1000, 1;
    end;

    This tells us, that we're going to be giving a player 1 Valkyrie helm for 10 Red Potions, 5 White Potions, 1 Blue Potions, 1,000 Jellopy and 1 Poring Card. Now that's easy!

    Now we need to add in all of our mes commands to give our npc some dialogue. But, since this npc is gonna do a bit of talking lets save it's name in a temporary scope_variable:

    .@npc_name$ = "[^FF0000 Vending Machine ^000000]";

    Now the variable: .@npc_name$ holds all that information inside of it. And it's definitely a lot easier to type. So let's add the mes command beneath that with our variable:

    mes .@npc_name$;

    You'll notice I didn't use any quotation marks for that. This is because we used a string variable. And our string variable already has those quotation marks saved inside of it. Now then, let's add in the rest of the mes commands to let player's know how much 1 valkyrie helm costs. To do that were going to be utilizing the command: getitemname(<item id>) and where it asks for <item_id> were going to be using our variables. So let's add the following lines in:

    mes "I will give you "+ .reward_amount +" "+ getitemname(.reward_item) +",";
    mes "for the following items:";
    mes ""+ getitemname(.required_items[0]) +" x"+ .required_amount[0] +"";
    mes ""+ getitemname(.required_items[1]) +" x"+ .required_amount[1] +"";
    mes ""+ getitemname(.required_items[2]) +" x"+ .required_amount[2] +"";
    mes ""+ getitemname(.required_items[3]) +" x"+ .required_amount[3] +"";
    mes ""+ getitemname(.required_items[4]) +" x"+ .required_amount[4] +"";

    See, that wasn't too hard. It's practically the same thing as the reward variables, just with the extra [#] added. Okay, next let's add in our menu this time were going to keep it to 2 options, so were going to make it that if they choose option 2, end the script with close; since we used mes earlier. BUT there's a catch, we're not going to use the if() command. We're going to use switch() since it's cleaner. We're also going to be adding case 1: followed by a break;

    switch( select("Trade in items", "That's a rip off!") ) {
        case 1:
            break;
    }
    close;

    Okay, now that we got that taken care of. We need to add a few checks, the first check being whether or not they have those items. To do that were going to use the command: countitem(<item id>)

    Keep in mind that since we have 5 items to check for, we are going to be utilizing the | | operator. Now, this is where things get a little technical, after each | | were going to add the next check on the line beneath.

    We're doing this to keep the script nice and neat. Would you rather look at 5 short lines, or 1 really long line that makes you turn your head? So, let's stick to 5 short lines.

    Let's add our check directly above the break; we put in earlier. It should look something like this:

    if( (countitem(.required_items[0]) < .required_amount[0]) ||
    (countitem(.required_items[1]) < .required_amount[1]) ||
    (countitem(.required_items[2]) < .required_amount[2]) ||
    (countitem(.required_items[3]) < .required_amount[3]) ||
    (countitem(.required_items[4]) < .required_amount[4]) ){
        mes "You don't have all the required items.";
        close;
    }

    Now that we added the check to see if they have all the items, it's time to delete those items, and give them the reward! The commands that let us do that are the following respectively:

    delitem <item id>,<amount>{,<account ID>};  and  getitem <item id>,<amount>{,<account ID>};. So let's add that in below our check, and remember to use those variables:

    delitem .required_items[0], .required_amount[0];
    delitem .required_items[1], .required_amount[1];
    delitem .required_items[2], .required_amount[2];
    delitem .required_items[3], .required_amount[3];
    delitem .required_items[4], .required_amount[4];
    getitem .reward_item, .reward_amount;

    Okay, now there's only 1 more thing to do, and that's add the last } to the very bottom of our script. After you add that your done. You now have a working item trader script. It should look like the following:

    prontera,154,180,4    script    Vending Machine    2_VENDING_MACHINE1,{
    .@npc_name$ = "[^FF0000 Vending Machine ^000000]";
    mes .@npc_name$;
    mes "I will give you "+ .reward_amount +" "+ getitemname(.reward_item) +",";
    mes "for the following items:";
    mes ""+ getitemname(.required_items[0]) +" x"+ .required_amount[0] +"";
    mes ""+ getitemname(.required_items[1]) +" x"+ .required_amount[1] +"";
    mes ""+ getitemname(.required_items[2]) +" x"+ .required_amount[2] +"";
    mes ""+ getitemname(.required_items[3]) +" x"+ .required_amount[3] +"";
    mes ""+ getitemname(.required_items[4]) +" x"+ .required_amount[4] +"";
    switch( select("Trade in items", "That's a rip off!") ) {
        case 1:
            if( (countitem(.required_items[0]) < .required_amount[0]) ||
                (countitem(.required_items[1]) < .required_amount[1]) ||
                (countitem(.required_items[2]) < .required_amount[2]) ||
                (countitem(.required_items[3]) < .required_amount[3]) ||
                (countitem(.required_items[4]) < .required_amount[4]) ){
                    mes "You don't have all the required items.";
                    close;
            }
            delitem .required_items[0], .required_amount[0];
            delitem .required_items[1], .required_amount[1];
            delitem .required_items[2], .required_amount[2];
            delitem .required_items[3], .required_amount[3];
            delitem .required_items[4], .required_amount[4];
            getitem .reward_item, .reward_amount;
            break;
    }
    close;
    
    
    OnInit:
    .reward_item = Valkyrie_Helm;
    .reward_amount = 1;
    setarray .required_items[0], Red_Potion, White_Potion, Blue_Potion, Jellopy, Poring_Card;
    setarray .required_amount[0], 10, 5, 1, 1000, 1;
    end;
    }

    You just finished making a working item trader npc. But, it's not 100% done yet. We need to do a few more things. We'll deal with those things in the next lesson, for now look at it, and be proud!!

    Basic Item Trader Script - Introduction to loops ( Part 2 )

    Okay, last lesson I mentioned how the script wasn't 100% done yet, because there were a few things we need to take care of, so let's knock those out. Firstly, let's start by opening the script back up:

    prontera,154,180,4    script    Vending Machine    2_VENDING_MACHINE1,{
    .@npc_name$ = "[^FF0000 Vending Machine ^000000]";
    mes .@npc_name$;
    mes "I will give you "+ .reward_amount +" "+ getitemname(.reward_item) +",";
    mes "for the following items:";
    mes ""+ getitemname(.required_items[0]) +" x"+ .required_amount[0] +"";
    mes ""+ getitemname(.required_items[1]) +" x"+ .required_amount[1] +"";
    mes ""+ getitemname(.required_items[2]) +" x"+ .required_amount[2] +"";
    mes ""+ getitemname(.required_items[3]) +" x"+ .required_amount[3] +"";
    mes ""+ getitemname(.required_items[4]) +" x"+ .required_amount[4] +"";
    switch( select("Trade in items", "That's a rip off!") ) {
        case 1:
            if( (countitem(.required_items[0]) < .required_amount[0]) ||
                (countitem(.required_items[1]) < .required_amount[1]) ||
                (countitem(.required_items[2]) < .required_amount[2]) ||
                (countitem(.required_items[3]) < .required_amount[3]) ||
                (countitem(.required_items[4]) < .required_amount[4]) ){
                    mes "You don't have all the required items.";
                    close;
            }
            delitem .required_items[0], .required_amount[0];
            delitem .required_items[1], .required_amount[1];
            delitem .required_items[2], .required_amount[2];
            delitem .required_items[3], .required_amount[3];
            delitem .required_items[4], .required_amount[4];
            getitem .reward_item, .reward_amount;
            break;
    }
    close;
    
    
    OnInit:
    .reward_item = Valkyrie_Helm;
    .reward_amount = 1;
    setarray .required_items[0], Red_Potion, White_Potion, Blue_Potion, Jellopy, Poring_Card;
    setarray .required_amount[0], 10, 5, 1, 1000, 1;
    end;
    }

    Alright, now that we opened this up, we need to add a check to make sure the player has enough inventory space, and free-weight to carry the reward.

    To do so we need to use the command: checkweight(<item id>,<amount>{,<item id>,<amount>,<item id>,<amount>,...}); Only thing is, we're going to check to see if the command fails using this operator: !

    So let's go ahead and add that above the first delitem command:

    if( !checkweight(.reward_item, .reward_amount) ){

    This tells us that, if true ( if the command fails ) to do something. In our case, we want to just tell the player they don't have enough freespace/weight to receive the item. Then were going to add a close; command to end the script early. And lastly close the check with }, so let's go ahead and do that, it should look like this when it's done:

        mes "You don't have enough free inventory space or weight to receive this item.";
        close;
    }

    Okay, now the script is finished, and you technically don't need to do anything else to it. But, we're here to learn! So we're going to clean up the script a bit using a loop.

    The loop we will be using is called: while (<condition>) <statement>; There are other loops that can get the job done, but I feel like this one is best suited since it's simple. Alright, so go ahead and find the following and delete it.

    mes ""+ getitemname(.required_items[0]) +" x"+ .required_amount[0] +"";
    mes ""+ getitemname(.required_items[1]) +" x"+ .required_amount[1] +"";
    mes ""+ getitemname(.required_items[2]) +" x"+ .required_amount[2] +"";
    mes ""+ getitemname(.required_items[3]) +" x"+ .required_amount[3] +"";
    mes ""+ getitemname(.required_items[4]) +" x"+ .required_amount[4] +"";

    Okay, now where those mes commands used to be were going to add our loop. Were going to be using a scope_variable ' .@ ' and were going to compare it to a value we obtain with this command: getarraysize(<array name>). So go ahead and add the following:

    while( .@i < getarraysize(.required_items) ){

    Just a note, .@i can be any variable name. It doesn't even need to be a scope_variable, but since we only need the variable for this 1 loop, we use a scope so its memory isn't saved once the script ends. However, .@i is most commonly used in cases like these. Now then, let's continue. Beneath that were going to be adding 1 mes command followed by setting the variable .@i + 1. On the line after that we're going to close the loop with }, always need to make sure too close a command with a right-curly bracket when ever a left-curly bracket is used.

    Now, the mes command we're going to use is VERY similar to the ones we deleted. The only difference is, instead of using [0] or [1] or [2], we'll be replacing that with [.@i] as such:

        mes ""+ getitemname(.required_items[.@i]) +" x"+ .required_amount[.@i] +"";
        .@i++;
    }

    Okay since that's done were about 80% done making this script nice and clean. Your script should look something like this:

    prontera,154,180,4    script    Vending Machine    2_VENDING_MACHINE1,{
    .@npc_name$ = "[^FF0000 Vending Machine ^000000]";
    mes .@npc_name$;
    mes "I will give you "+ .reward_amount +" "+ getitemname(.reward_item) +",";
    mes "for the following items:";
    while( .@i < getarraysize(.required_items) ){
        mes ""+ getitemname(.required_items[.@i]) +" x"+ .required_amount[.@i] +"";
        .@i++;
    }
    switch( select("Trade in items", "That's a rip off!") ) {
        case 1:
            if( (countitem(.required_items[0]) < .required_amount[0]) ||
                (countitem(.required_items[1]) < .required_amount[1]) ||
                (countitem(.required_items[2]) < .required_amount[2]) ||
                (countitem(.required_items[3]) < .required_amount[3]) ||
                (countitem(.required_items[4]) < .required_amount[4]) ){
                    mes "You don't have all the required items.";
                    close;
            }
            if( !checkweight(.reward_item, .reward_amount) ){
                mes "You don't have enough free inventory space or weight to receive this item.";
                close;
            }
            delitem .required_items[0], .required_amount[0];
            delitem .required_items[1], .required_amount[1];
            delitem .required_items[2], .required_amount[2];
            delitem .required_items[3], .required_amount[3];
            delitem .required_items[4], .required_amount[4];
            getitem .reward_item, .reward_amount;
            break;
    }
    close;
    
    
    OnInit:
    .reward_item = Valkyrie_Helm;
    .reward_amount = 1;
    setarray .required_items[0], Red_Potion, White_Potion, Blue_Potion, Jellopy, Poring_Card;
    setarray .required_amount[0], 10, 5, 1, 1000, 1;
    end;
    }

    Okay lastly, we're going to add 1 more while() loop, but this time, we're are going to be replacing those delitem() commands with the loop. The difference is, instead of using .@i we're going to switch it to .@j it is a lower-case J. If your having a hard time seeing that. So, taking a look at our previous loop as an example. Let's replace those delitem commands with a loop:

    while( .@j < getarraysize(.required_items) ){
        delitem .required_items[.@j], .required_amount[.@j];
        .@j++;
    }

    Okay, so now your script should look like mine, or something close to it:

    prontera,154,180,4    script    Vending Machine    2_VENDING_MACHINE1,{
    .@npc_name$ = "[^FF0000 Vending Machine ^000000]";
    mes .@npc_name$;
    mes "I will give you "+ .reward_amount +" "+ getitemname(.reward_item) +",";
    mes "for the following items:";
    while( .@i < getarraysize(.required_items) ){
    mes ""+ getitemname(.required_items[.@i]) +" x"+ .required_amount[.@i] +"";
    .@i++;
    }
    switch( select("Trade in items", "That's a rip off!") ) {
        case 1:
            if( (countitem(.required_items[0]) < .required_amount[0]) ||
             (countitem(.required_items[1]) < .required_amount[1]) ||
             (countitem(.required_items[2]) < .required_amount[2]) ||
             (countitem(.required_items[3]) < .required_amount[3]) ||
             (countitem(.required_items[4]) < .required_amount[4]) ){
                mes "You don't have all the required items.";
                close;
            }
            if( !checkweight(.reward_item, .reward_amount) ){
                mes "You don't have enough free inventory space or weight to receive this item.";
                close;
            }
            while( .@j < getarraysize(.required_items) ){
                delitem .required_items[.@j], .required_amount[.@j];
                .@j++;
            }
            getitem .reward_item, .reward_amount;
            break;
    }
    close;
    
    
    OnInit:
    .reward_item = Valkyrie_Helm;
    .reward_amount = 1;
    setarray .required_items[0], Red_Potion, White_Potion, Blue_Potion, Jellopy, Poring_Card;
    setarray .required_amount[0], 10, 5, 1, 1000, 1;
    end;
    }

    Go ahead and save that, because we are done.

    Basic Floating Rates Script - Complete ( 2 Parts )

    Here we will create a " floating " npc. And have it adjust base & job experience rates.

    Basic Floating Rates Script - Introduction to floating NPCs ( Part 1 )

    Alright, so we're going to start off by making a floating npc header. It's the same as a regular npc header, with the exception of replacing the <map_name>,<x>,<y>,<facing> with a simple hyphen ( - ), and the <sprite_id> with -1. So let's make our floating npc header and call our npc " floating_rates ":

    -    script    floating_rates    -1,{

    Alright, since this is our third script in the beginner's series, we're going to speed things up a bit by not taking so much time to explain things ( this sentence being the exception =P ).

    Now, since we want our script to activate everyday of the week, and change daily, we are going to use a label called:  OnClock<hour><minute>:. The time we are going to use is 0000:

    OnClock0000:

    Okay, now it's time to start adding in where we change the experience rates, but first we need to set 2 variables to a random value between 150 & 200.

     To do so we use this command: rand(<number>{,<number>}); We're going to use scope variables, and we're going to call them base_exp & job_exp, so go ahead and add that beneath our OnClock label:

    .@base_exp = rand(150,200);
    .@job_exp = rand(150,200);

    So, we got that taken care of. Now we are going to actually change the base & job experience rates. To do that we need to use this command: setbattleflag "<battle flag>",<value>;

    A list of battle flags can be found in the files listed here: Battle Flags. But we are only going to be using the following flags: base_exp_rate & job_exp_rate .

    setbattleflag "base_exp_rate",.@base_exp;
    setbattleflag "job_exp_rate",.@job_exp;
    Alright, that's everything we need to do, so let's end the script and close it up:
    end;
    }

    When your done, your script should look something like this:

    -    script    floating_rates    -1,{
    OnClock0000:
    .@base_exp = rand(150,200);
    .@job_exp = rand(150,200);
    setbattleflag "base_exp_rate",.@base_exp;
    setbattleflag "job_exp_rate",.@job_exp;
    end;
    }

    And, now when ever your players kill a monster they will get that modified experience rate, that changes every day at midnight.

    Basic Floating Rates Script - Fine tuning ( Part 2 )

    Okay, so last lesson, we made a very basic floating rates npc that changes the rates every day. However, now we are going to change that npc to only change the rates during weekends.

    Let's start by opening up our script:

    -    script    floating_rates    -1,{
    OnClock0000:
    .@base_exp = rand(150,200);
    .@job_exp = rand(150,200);
    setbattleflag "base_exp_rate",.@base_exp;
    setbattleflag "job_exp_rate",.@job_exp;
    end;
    }

    So, to make it only work for Friday, Saturday, and sunday. We are going to add a check for the current day of the week. To do so we will be using this command: gettime(<type>), and we'll be using type 4. To be more specific, we are going to change the rates Friday at midnight, and change them back on Monday at midnight. So let's add the following:

    if( gettime(4) == 5 ) {

    Now let's put all the data inside that check except for end; and close it up. After that, we are going to add another check using else if () and check to see if it's Monday. Should look like this:

    if( gettime(4) == 5 ) {
    .@base_exp = rand(150,200);
    .@job_exp = rand(150,200);
    setbattleflag "base_exp_rate",.@base_exp;
    setbattleflag "job_exp_rate",.@job_exp;
    } else if( gettime(4) == 1 ) {

    Okay, now in the second check, we are just going to set the base & job experience rates back to normal, to do that just use 100 for the value, don't forget to close it up:

    setbattleflag "base_exp_rate",100;
    setbattleflag "job_exp_rate",100;
    }

    Right now your script should look something like this:

    -    script    floating_rates    -1,{
    OnClock0000:
    if( gettime(4) == 5 ) {
        .@base_exp = rand(150,200);
        .@job_exp = rand(150,200);
        setbattleflag "base_exp_rate",.@base_exp;
        setbattleflag "job_exp_rate",.@job_exp;
    } else if( gettime(4) == 1 ) {
        setbattleflag "base_exp_rate",100;
        setbattleflag "job_exp_rate",100;
    }
    end;
    }

    Alright, as of now it's almost done. Let's just edit it a bit, so that we let player's know when the experience rates change and what they changed to.

    To do that we are going to use this command: announce "<text>",<flag>{,<fontColor>{,<fontType>{,<fontSize>{,<fontAlign>{,<fontY>}}}}}; in combination with this command: getbattleflag("<battle flag>")

    The flag we'll be using is bc_all with 0x00FFFF as the color. For other flags look here: Broadcast Flags additionally, you can use other hex color codes for the broadcast color in this format:  0xRRGGBB

    Now then let's add 1 announcement when we increase the rates, and 1 adjustment when we set the rates back:

    announce "Bonus Experience Weekend has just started! Base Exp: +"+ (getbattleflag("base_exp_rate") - 100) +"% | Job Exp: +"+ (getbattleflag("job_exp_rate") - 100) +"% !!",bc_all,0x00FFFF;

    and

    announce "Bonus Experience Weekend has ended.",bc_all,0x00FFFF;

    Your script should look something like this:

    -    script    floating_rates    -1,{
    OnClock0000:
    if( gettime(4) == 5 ) {
        .@base_exp = rand(150,200);
        .@job_exp = rand(150,200);
        setbattleflag "base_exp_rate",.@base_exp;
        setbattleflag "job_exp_rate",.@job_exp;
        announce "Bonus Experience Weekend has just started! Base Exp: +"+ (getbattleflag("base_exp_rate") - 100) +"% | Job Exp: +"+ (getbattleflag("job_exp_rate") - 100) +"% !!",bc_all,0x00FFFF;
    } else if( gettime(4) == 1 ) {
        setbattleflag "base_exp_rate",100;
        setbattleflag "job_exp_rate",100;
        announce "Bonus Experience Weekend has ended.",bc_all,0x00FFFF;
    }
    end;
    }

    We are almost done. The last thing were going to do, is add 1 more announcement that occurs every hour, to let player's know it is bonus exp weekend.

    To do that we're going to use the label: OnMinute<minute>: and we are going to use 00 as the minute:

    OnMinute00:
    if( !gettime(4) || gettime(4) >= 5 ){
        announce "Bonus Experience Weekend in progress! Base Exp: +"+ (getbattleflag("base_exp_rate") - 100) +"% | Job Exp: +"+ (getbattleflag("job_exp_rate") - 100) +"% !!",bc_all,0x00FFFF;
    }
    end;

    When your all done your script should look like this or close to it:

    -    script    floating_rates    -1,{
    OnClock0000:
    if( gettime(4) == 5 ) {
        .@base_exp = rand(150,200);
        .@job_exp = rand(150,200);
        setbattleflag "base_exp_rate",.@base_exp;
        setbattleflag "job_exp_rate",.@job_exp;
        announce "Bonus Experience Weekend has just started! Base Exp: +"+ (getbattleflag("base_exp_rate") - 100) +"% | Job Exp: +"+ (getbattleflag("job_exp_rate") - 100) +"% !!",bc_all,0x00FFFF;
    } else if( gettime(4) == 1 ) {
        setbattleflag "base_exp_rate",100;
        setbattleflag "job_exp_rate",100;
        announce "Bonus Experience Weekend has ended.",bc_all,0x00FFFF;
    }
    end;
        
    OnMinute00:
    if( !gettime(4) || gettime(4) >= 5 ){
    announce "Bonus Experience Weekend in progress! Base Exp: +"+ (getbattleflag("base_exp_rate") - 100) +"% | Job Exp: +"+ (getbattleflag("job_exp_rate") - 100) +"% !!",bc_all,0x00FFFF;
        }
    end;
    }

    Save it and we are done. You just finished creating a Floating Rates NPC.

     

    - Intermediate Scripts -

    Scripts for intermediate level scripters. If you completed my beginner script series, then you are ready for this section.

    Advanced Healer & Buffer Script - Complete ( 2 Parts )

    An advanced version of the Basic Healer & Buffer script.

    Advanced Healer & Buffer Script - Introduction to Conditional Operators & String/Menu manipulation ( Part 1 )

    Okay, so let's start by opening up the final version of our Basic Healer & Buffer Script.

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    mes "^FF0000 *Note - It costs "+ .heal_price +" zeny to get healed.* ^000000";
    mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs "+ .buff_price +" zeny.* ^000000";
    switch( select("Yes, heal me.", "I want buffs", "No thank you") ) {
        case 1:
            if( Zeny < .heal_price ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= .heal_price;
            percentheal 100,100;
            break;
        
        case 2:
            if( Zeny < .buff_price ) {
                mes "I'm sorry, but you don't have enough zeny.";
                close;
            }
            Zeny -= .buff_price;
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            break;
    }
    close;
    
    OnInit:
    .heal_price = 1000;
    .buff_price = 5000;
    end;
    }

    So what we're going to do here is edit our script quite a bit. But firstly, we are going to make the zeny price for healing / buffing optional. To do that is simple. We just need to put our current zeny checks and zeny payment lines, into another if(statement) that simply checks to see if we set a variable above 0. The variable we are going to use is our .heal_price and .buff_price. So let's make those changes:

    if( .heal_price ){
    if( Zeny < .heal_price ) {
    mes "I'm sorry, but you don't have enough zeny.";
    close;
    }
    Zeny -= .heal_price;
    }

    and

    if( .buff_price ){
    if( Zeny < .buff_price ) {
        mes "I'm sorry, but you don't have enough zeny.";
        close;
    }
    Zeny -= .buff_price;
    }

    That takes care of the first part. Now this is where we get a little technical. We are going to add another variable that gets set every time the server starts / reloads. We are going to call it .include_buffs. And let's just set it to 1 since it's going to be a simple toggle on/off setting. Also, make sure to place it above the other variables we set beforehand.

    .include_buffs = 1;

    Now this may not seem obvious, but this setting is going to let us decide whether or not players receive buffs when they pay for heals ( free of charge ). Assuming we are all kind admins. So let's add a check in case 1:. It's going to check if the variable is set to 0. And then simply wrap the existing break; command inside that check:

    if( !.include_buffs ){ break; }

    Doing this will let the script run on through case 1: to case 2:. Next is a bit tricky, we are going to modify the way we set our .buff_price variable. To be more specific, we are going to use a (?: ) check, in combination with the command: atoi("<string>"). For more information regarding (?: ) look here: Conditional Operator. Otherwise let's make the following changes:

    .buff_price = ( (.include_buffs)?0:5000 );

    Okay so this little bit of code is in short, just going to set the variable to 0. If we include buffs when they buy heals, otherwise set the variable to 5000 which is what we said buffs costs normally.

    Alright, so we only have 2 more change to make, and that is simply 1 more check to determine whether or not we show them the buff price. Obviously, if buffs come with heals, we are going to just not show it so lets simply wrap the mes command dealing with showing buff pricing, into a check to see if .include_buffs is 0, additionally let's make that check also check to see if .buff_price is above 0.

    if( !.include_buffs && .buff_price ){ mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs "+ .buff_price +" zeny.* ^000000"; }

    While we're at it, let's add a similar check for the mes command dealing with showing us the price for heals, but without the .include_buffs part:

    if( .heal_price ){ mes "^FF0000 *Note - It costs "+ .heal_price +" zeny to get healed.* ^000000"; }

    And lastly, we are going to edit our menu. Again, this time we are going to use a (?: )check. We are going to do a little trick where we push menu options down 1 slot. So let's edit our menu:

    switch( select("Yes, heal me.", ( .include_buffs?"::":"I want buffs" ), "No thank you") ) {

    What this effectively allows us to do, is hide our option to choose " I want buffs " while still making sure the option " No thank you ", which is now shown as option 2, doesn't get directed to case 2: since it is used for buffs only. And that's it. Your script should look something like this when you're done:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    if( .heal_price ){ mes "^FF0000 *Note - It costs "+ .heal_price +" zeny to get healed.* ^000000"; }
    if( !.include_buffs && .buff_price ){ mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs "+ .buff_price +" zeny.* ^000000"; }
    switch( select("Yes, heal me.", ( .include_buffs?"::":"I want buffs" ), "No thank you") ) {
        case 1:
            if( .heal_price ){
                if( Zeny < .heal_price ) {
                    mes "I'm sorry, but you don't have enough zeny.";
                    close;
                }
                Zeny -= .heal_price;
            }
            percentheal 100,100;
            if( !.include_buffs ){ break; }
        
        case 2:
            if( .buff_price ){
                if( Zeny < .buff_price ) {
                    mes "I'm sorry, but you don't have enough zeny.";
                    close;
                }
                Zeny -= .buff_price;
            }
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            break;
    }
    close;
    
    OnInit:
    .include_buffs = 1;
    .heal_price = 1000;
    .buff_price = ( (.include_buffs)?0:5000 );
    end;
    }

    So there we go, we now just made the Zeny Price for Heals & Buffs optional. As well as made it so buffs can be included with heals. Lastly, this will work for free should you choose to =P

    Advanced Healer & Buffer Script - ( Part 2 )

    Okay, here we're going to make it so, players in a guild receive additional buffs, at no extra cost. So let's open up the script from before:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    if( .heal_price ){ mes "^FF0000 *Note - It costs "+ .heal_price +" zeny to get healed.* ^000000"; }
    if( !.include_buffs && .buff_price ){ mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs "+ .buff_price +" zeny.* ^000000"; }
    switch( select("Yes, heal me.", ( .include_buffs?"::":"I want buffs" ), "No thank you") ) {
        case 1:
            if( .heal_price ){
                if( Zeny < .heal_price ) {
                    mes "I'm sorry, but you don't have enough zeny.";
                    close;
                }
                Zeny -= .heal_price;
            }
            percentheal 100,100;
            if( !.include_buffs ){ break; }
        
        case 2:
            if( .buff_price ){
                if( Zeny < .buff_price ) {
                    mes "I'm sorry, but you don't have enough zeny.";
                    close;
                }
                Zeny -= .buff_price;
            }
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            break;
    }
    close;
    
    OnInit:
    .include_buffs = 1;
    .heal_price = 0;
    .buff_price = ( (.include_buffs)?0:5000 );
    end;
    }

    Alright, now let's add a mes command near the top that lets players know that they get more buffs if they are in a guild, also let's use ^0000FF as the color so it stands out:

    mes "^0000FF *Note - Players in a guild receive additional buffs at no extra charge.* ^000000";

    Next, beneath where we give them Increase AGI and Blessing, we are going to add a check to see if they are in a guild. Any guild doesn't matter so long as they are in one. To do that we will need to use this command: getcharid(<type>{,"<character name>"})

    if( getcharid(2) ){
    Now we just need to give them more buffs. You can add as many as you like, but for this tutorial we're just going to give them Imposito Manus Level 5 and Assumptio Level 5:
        sc_start SC_IMPOSITIO,300000,5;
        sc_start SC_ASSUMPTIO,300000,5;
    }

    And that's it. We didn't do much here, but that's because the brunt of the work was done a long time ago. Now we are just expanding it with additional features. So your script should look something like this:

    prontera,150,180,4    script    Healer    4_F_ARUNA_POP,{
    mes "[^0000FF Healer ^000000]";
    mes "Would you like me to heal you?";
    if( .heal_price ){ mes "^FF0000 *Note - It costs "+ .heal_price +" zeny to get healed.* ^000000"; }
    if( !.include_buffs && .buff_price ){ mes "^FF0000 *Note - AGI-UP + BLESSING Level 10 costs "+ .buff_price +" zeny.* ^000000"; }
    mes "^0000FF *Note - Players in a guild receive additional buffs at no extra charge.* ^000000";
    switch( select("Yes, heal me.", ( .include_buffs?"::":"I want buffs" ), "No thank you") ) {
        case 1:
            if( .heal_price ){
                if( Zeny < .heal_price ) {
                    mes "I'm sorry, but you don't have enough zeny.";
                    close;
                }
                Zeny -= .heal_price;
            }
            percentheal 100,100;
            if( !.include_buffs ){ break; }
        
        case 2:
            if( .buff_price ){
                if( Zeny < .buff_price ) {
                    mes "I'm sorry, but you don't have enough zeny.";
                    close;
                }
                Zeny -= .buff_price;
            }
            sc_start SC_BLESSING,300000,10;
            sc_start SC_INC_AGI,300000,10;
            if( getcharid(2) ){
                sc_start SC_IMPOSITIO,300000,5;
                sc_start SC_ASSUMPTIO,300000,5;
            }
            break;
    }
    close;
    
    OnInit:
    .include_buffs = 1;
    .heal_price = 0;
    .buff_price = ( (.include_buffs)?0:5000 );
    end;
    }

    That's it for our healer, we probably won't need to come back to this ever again =P.

     

    *Note - In most of these tutorials, I will be using the script from the previous lesson, unless the scripts deviate from each other. For instance jumping from Healer -> Quest NPC will result in a blank script. But each lesson from then on will be towards the quest NPC and will use the script from the previous lesson. *

     

    The idea behind this topic, is for new users, and current ones, to have a (second)place they can go to for reference when trying to write a script if they can't figure it out with script_commands.txt file. It will also help people learn how to write scripts. While hopefully, keeping script writing techniques to a ' very ' similar structure!

     

    !! This topic will be updated at Hercules first and then here once a script is fully complete. Some scripts may not be here right away. If this is the case it is because scripts are incompatible and need to be adjusted. !!

    • Upvote 2
  3. Sorry, I forgot to update to version 0.7 on rAthena @.@; I've submitted an upload just waiting for approval by an admin.

    
        v0.1 - Original Script Created.
        v0.2 - Added Option for Triple Slot Machine with animations.
        v0.3 - Added support for item pricing &/or zeny pricing.
        v0.4 - Cleaned up script variables for easy editing.
        v0.5 - Tested out some new scripting methods with IF(THEN).
        v0.6 - Added option to change slot machine modes ingame.
        v0.7 - Added optional sound effects to slot machines. - NOTE
    
    
  4. Are you having issues where a single entry in the sql db has an amount greater than max int? If so then theres nothing we can do unless you increase the size of max_int in the src by maybe converting it to uint64 or something. Edit: Althought this shouldn't be the issue as zenylog's amount is: int(11) which limits it to the max_int you specified.

     

    If your having issues finding a way to actually calculate the sum of ALL the sql entries together, then there is a way to do that. However to do so requires a loop and only pulling 1 query at a time. Let me know which is the issue D:

    • Upvote 1
  5. Something like this?

    -	script	autoloottimer	-1,{
    OnInit:
    bindatcmd "@autoloot",strnpcinfo(3)+"::OnAutoloot";
    .timer = 20; //Seconds timer lasts.
    end;
    
    OnAutoloot:
    if( autoloottimer > gettimetick(2) ){ dispbottom "You can't use this yet."; }
    atcommand "@autoloot";
    addtimer 1000,strnpcinfo(3)+"::OnTimer";
    announce "You have "+ .timer +" seconds to use @autoloot",bc_blue|bc_self;
    end;
    
    OnTimer:
    altimer++;
    if( altimer == 10 ) {
    	announce "10 seconds left.",bc_blue|bc_self; }
    if( altimer == 15 ) {
    	announce "15 seconds left.",bc_blue|bc_self; }
    if( autoloottimer < gettimetick(2) || altimer >= .timer ) {
    	altimer = 0;
    	autoloottimer = 0;
    	atcommand "@autoloot";
    	announce "Timeout.",bc_blue|bc_self;
    	end; }
    
    addtimercount strnpcinfo(3)+"::OnTimer",1000;	
    end;
    
    OnPCLoginEvent:
    if( autoloottimer > gettimetick(2) ) {
    	addtimer 1000,strnpcinfo(3)+"::OnTimer";
    	}
    end;
    }
    

    It'd be better to use addtimer in this case, since it'll eliminate the need to attach/deatch/reinitiate the timer over and over again.

    I made a sample script that should do what you wanted, but using the command: @autoloot   to trigger it.

  6. The issue doesn't look like it's in the item, but rather in the function. Show us that so we can see if there is a problem with it. Because from the information your giving us all we can deduct is that:

    1. The item exists.

    2. Your using it.

    3. Nothing happens.

     

    By elimination your function is most likely the problem.

  7. To disable all npc's in rA, is the same as when you disable

    scripts_athena.conf *Note - This file contains the list for NPCs like Merchants
    scripts_custom.conf
    scripts_guild.conf *Note - Disabling these scripts may cause unwanted issues regarding WoE
    scripts_jobs.conf *Note - Disabling these scripts will remove the OFFICIAL job quests needed to change jobs on official servers.
    scripts_test.conf
    

    Ultimately there will be issues if you don't pay attention to what your disabling. But for the most part, 90% of the scripts can be disabled without losing function. It's just alot of those scripts are, npc's people use on a daily basis.

    any of your NPCs. Just //comment them out of these files:

  8. 
    

    mapname,x,y,z script npc_name sprite_id,{

    mes "Would you like to trade "+ getitemname(.itemid) +" x"+ .amount +" for VIP time?";

    mes "Each set of "+ .amount +" will earn you "+ .vip_time_minutes +" VIP minutes.";

    if( select( "YES:NO" ) == 2 || countitem(.itemid) < .amount ) { close; } //Not enough item / clicked close;

    next;

    mes "How many sets would you like to turn in?";

    menu "1 set: 5 sets: 10 sets",-;

    switch( @menu ) {

    case 1:

    delitem .itemid, .amount;

    vip_time .vip_time_minutes;

    break;

    case 5:

    if( countitem( .itemid ) < .amount * 5 ){ mes "Sorry, you don't have that much"; close; }

    delitem .itemid, .amount*5;

    vip_time ( .vip_time_minutes * 5 );

    break;

    case 10:

    if( countitem(.itemid) < .amount * 10 ){ mes "Sorry, you don't have that much"; close; }

    delitem .itemid, .amount*10;

    vip_time ( .vip_time_minutes * 10 );

    break;

    }

    mes "Thank you. Your VIP status has been updated.";

    mes "Your remaining VIP time is: "+ vip_status(3) +"";

    close;

    }

  9. Your able to walk on the ship to view the city from above, but is the ship located within the city parameters, or is it off to the side? Because if it's inside the city, then you would have a large area where they are unable to walk right?

     

    But this map looks very interesting, nicely done.

  10. It works like this.

    1. give each guild you add to the list a guild map of their own.

    2. Then, when ever a player of a different guild enters a guild map that doesn't belong to them, it will announce it.

    3. It currently only supports 128 guilds.

     

    ex:

    Guild A has it's own map.

    Guild B has it's own map.

    If a member of Guild A enters Guild B's map, then the server will announce that Guild B's map has been invaded by a member of Guild A.

  11. The Below should make it back to normal heal damage. Not sure though, didn't test it.

     

    In Battle.c find:

    switch (skill_id)
    		{	//Calc base damage according to skill
    			case AL_HEAL:
    			case PR_BENEDICTIO:
    			case PR_SANCTUARY:
    			/**
    			 * Arch Bishop
    			 **/
    			case AB_HIGHNESSHEAL:
    				ad.damage = skill_calc_heal(src, target, skill_id, skill_lv, false);
    				break;
    

    And then add

    ad.damage = ( skill_calc_heal(src, target, skill_id, skill_lv, false) / 5);
    
    

    Below AL_HEAL, so should look like:

    switch (skill_id)
    		{	//Calc base damage according to skill
    			case AL_HEAL:
                                    ad.damage = ( skill_calc_heal(src, target, skill_id, skill_lv, false) / 2 );
    			case PR_BENEDICTIO:
    			case PR_SANCTUARY:
    			/**
    			 * Arch Bishop
    			 **/
    			case AB_HIGHNESSHEAL:
    				ad.damage = skill_calc_heal(src, target, skill_id, skill_lv, false);
    				break;
    
    • Upvote 1
  12. File Name: Slot Machine

    File Submitter: GmOcean

    File Submitted: 13 Sep 2014

    File Category: Games, Events, Quests

    Content Author: GmOcean


    This script will allow users to spend zeny &/or an item for a chance to win a prize from the slot machine. Currently there are 2 versions. First is a Single Slot Machine, where only 1 slot is rolled. Second is the Triple Slot machine, where 3 slots are rolled. For either version, SUCCESS must be the only thing displayed in order to win.

    To add the cutins, just place them in: data/texture/À¯ÀúÀÎÅÍÆäÀ̽º/illust

    /* =============================================================
    /* NOTE - If using soundeffects you must add the ".wav" files
    /* provided in the ".rar" file to your: data/wav folder located
    /* in either your: ( Ragnarok folder ) OR ( .grf file )
    /* =============================================================



    Click here to download this file

    • Upvote 2
  13. Not that it's impossible or anything but, this is useless in the result of a server crash as you would need to be connected to the map server. So essentially you'd need to run the software anyways in that case. So, there really a need. Especially when it takes only 10seconds - 2mins to restart. Either way your players are kicked off. So might as well just launch the programs.

  14. Yea, colored text does make it easier IMO to read, but, that is why I added showdigit as it only pops up for 5seconds. Most butters from what I can tell periodically monitor their bots. And this way, even they might miss it. Not sure if a bot can detect showdigit either.

  15. Okay, so here is what you were asking for I guess. You can increase the strength of this by using methods that Euphy mentioned in addition to the showdigit time method. But for now this is in itself pretty strong D: ( i guess lol).

     

    Okay, so the way this script works is the following:

    1. It shows a time at the top of the screen for 5 seconds. (Same format as used when your #1 in PvP).

    2. It generates 3 random strings of text each different in length depending on what you specify.

    3. The FIRST and LAST string of text is highlighted red, and are what the player needs to type in.

    4. It is case sensitive.

    5. Format inputted is:  string1+string2+numbers.

    ex: asdf234asdfqwer5678qwer012345678

    6. The player has a limited amount of time to input the password. (This can be specified in the script).

    7. After the player logs in there is a random amount of time until the player is given the test. ( between 1min -> 15min by default ).

    8. Lastly the length of the text is random from 10->48 (by default. can be changed).

    ex: Ae16455934     OR    ex: 1234AsDf5678qwERgh34awTGTJhiiL9ku8765Fta14593402

    So, yeah it's up to you on what you wanna change it to.

    First off, change this to a number higher than those your players are. This prevents them from using commands to bypass the bot check.

    // Only group with level more than or equal this value can use atcommand while talking with NPC.
    atcommand_enable_npc: 0
    

    Then simply install this script and make the changes as needed.

    -	script	bot_test	-1,{
    OnInit:
    set .mim,1; //Minimum Wait time before starting bot test.
    set .mxm,15; //Maximum Wait time before starting bot test.
    set .often,60; //How often a bot test is given in minutes.
    set .timetest,30; //Seconds for players to input test before they auto-fail.
    set .minstr,1; //DO NOT CHANGE
    //Max Length each section of the string will be.
    set .maxstr,20; // ( .maxstr * 2 ) + 8 = Password Length.
    //DO NOT CHANGE THE BELOW//
    .day = 86400; .hour = 3600; .min = 60;
    setarray .0$[0],"a","b","c","d","e","f","g","h","i","j","k","L","m","n","o","p","q","r","s","t","u","v","w","x","y","z";
    setarray .1$[0],"A","B","C","D","E","F","G","H","ii","J","K","LL","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z";
    setarray .2$[0],"0","1","2","3","4","5","6","7","8","9";
    setarray .day$[1],"01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30";
    setarray .hour$[0],"00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23";
    setarray .min$[0],"00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29",
    			      "30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59";
    setarray .second$[0],"00","01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29",
    			         "30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59";
    OnPCLoginEvent:
    addtimer (60000 *rand(.@min,.@mxm) ), strnpcinfo(3)"+::OnBotTest"; //Starts timer to trigger bot timer
    end;
    
    OnBotTest:
    set @pass,0;
    addtimer .timetest * 1000, strnpcinfo(3)"+::OnTestCheck";
    set .@d,rand(1,30);
    set .@h,rand(1,23);
    set .@m,rand(60);
    set .@s,rand(60);
    
    set .@a,rand(20);
    set .@b,rand(20);
    set .@c,rand(20);
    freeloop(1);
    while( .@i <= .@a ){ 
    	.@d = rand(3);
    	if( .@d == 2 ){ 
    		set .@str1$,.@str1$ + getd(".@"+ .@d +"$["+ rand(10) +"]"); 
    		}
    	if( .@d == 0 || .@d == 1 ){ 
    		set .@str1$,.@str1$ + getd(".@"+ .@d +"$["+ rand(26) +"]"); 
    		}
    	.@i++;
    	}
    while( .@j <= .@a ){ 
    	.@e = rand(3);
    	if( .@e == 2 ){ 
    		set .@str2$,.@str2$ + getd(".@"+ .@e +"$["+ rand(10) +"]"); 
    		}
    	if( .@e == 0 || .@e == 1 ){ 
    		set .@str2$,.@str2$ + getd(".@"+ .@e +"$["+ rand(26) +"]"); 
    		}
    	.@j++;
    	}
    while( .@l <= .@a ){ 
    	.@f = rand(3);
    	if( .@f == 2 ){ 
    		set .@str3$,.@str3$ + getd(".@"+ .@f +"$["+ rand(10) +"]"); 
    		}
    	if( .@f == 0 || .@f == 1 ){ 
    		set .@str3$,.@str3$ + getd(".@"+ .@f +"$["+ rand(26) +"]"); 
    		}
    	.@l++;
    	}
    freeloop(0);
    showdigit ( (.@day * .@d) + (.@hour * .@h) + (.@min * .@m) + (.@s) );
    mes "Input the ^FF0000TEXT^000000 only + the numbers above";
    mes "^FF0000"+ .@str1$ +"^0000FF"+ .@str2$ +"^FF0000"+ .@str3$ +"^000000";
    
    //For Debug purposes
    //mes ""+ .@day$[.@d]+""+ .@hour$[.@h] +""+ .@min$[.@m] +""+ .@second$[.@s]+"";
    
    input .@password$;
    if( .@password$ != ""+ .@str1$ +""+ .@str3$ +""+ .@day$[.@d] +""+ .@hour$[.@h] +""+ .@min$[.@m] +""+ .@second$[.@s] +"" ){
    	mes "^FF0000 FAILED"; 
    	//Punishment 
    	close;
    	}
    addtimer (60000 * .often), strnpcinfo+"::OnBotTest";
    set @pass,1;
    close;
    
    OnTestCheck:
    if( @pass ){end;}
    //Punishment
    end;
    }
    

    Right now, this script does not provide any form of punishment. To punish a person (ban/kick or other wise), simply search for //Punishment and replace with your punishment method.

    • Upvote 1
×
×
  • Create New...