Tutorial: Making an RPG Maker MV Plugin

So there I was, thinking about things that I wish were a bit different in RPG Maker MV, when I started thinking about the drop table system. You know, the thing that looks like this:

Shows three items in a drop table.

I personally find this to be entirely insufficient for three reasons. One, I can only have three items in the list; two, I can only have an item quantity of one drop per thing; and three, the drop probabilities are in the form 1/N (where N is a whole number). This last one is notably annoying because it means I can't have drop chances between 100% and 50%.

So, like I said, I got to thinking about this, and that's when it hit me: I could easily write a plugin to solve these problems! Well, to easily solve two of these problems, anyway...changing the number of entries in the list is not so easy.

So I'll do just that: write a plugin to change how drop chances work and to enable drop quantities of more than one. I decided I wanted to share this process, primarily my methodology and how I approach figuring out how to write a plugin. Hopefully I do that clearly enough that you can follow along and get an idea of how to make your own plugins! Note: RPG Maker MV is the version I have and use. It uses Javascript for its coding language. Other versions may use other languages, which is something I won't be covering here.

So, I have two things I want to address. The first is that I want to be able to have drop probabilities work in such a way that I can have, say, 75% as a drop chance. The second is wanting to be able to have enemies drop quantities of a piece of loot greater than 1.

The first one is pretty straightforward to address, at least conceptually, by changing the meaning of the number in the drop rate from 1/N to N/X. If I want the maximum drop chance to be 100%, this means I first need to figure out what the biggest possible value is that I can put into the drop chance field. This will be X (because X/X = 1 = 100%). Time to do some testing!

Shows a Probability of 1 / 1000

As expected, the maximum value is 1000 (which gives a drop chance of 0.1%). If I change it to a system where the field instead represents N / 1000, that means I can have drop chances between 100% and...drum roll please...0.1%. Yes, that's right! The chance range between the two methods is exactly the same. (I know because I triple checked it.)

"But wait," I hear you asking (or maybe that's just me), "how can the ranges be the same?"

The answer is that what values we have shifts. The method RPG Maker MV uses by default has a lot of very small possible values: everything after 1 / 100 is less than 1%. That means every value between 101 and 1000 is realistically pretty small. The new method I want to use, on the other hand, shifts things around pretty dramatically. For one thing, 1000 will now be the 100% drop chance and 1 the 0.1% drop chance (these were reversed with the previous method, if that was unclear). What this means is that 10 will be a 1% drop chance, with only the values less than 10 being a less than 1% drop chance. This means that all of the values above 10 will have a drop chance greater than 1%, which in my mind, is significantly more useful than a ton of smaller and smaller values between 1% and 0.1%. For example, I find value of 750 being a 75% drop chance significantly more useful than it being a 0.13% drop chance.

OK, so now I've got a method. How do I go about implementing it? Step one is to create the plugin file in the Plugins folder for your project (as a tip, you can copy it to the plugins folder for new projects in your RPG Maker MV directory if you want it automatically added for future projects). The plugins folder is in your project's js folder (i.e. "<project folder>\js\plugins"; you can open your project's folder from within RPG Maker MV by clicking on the "Game" menu item and selecting "Open Folder").

From there, right click and choose to make a new text document, then rename it something informative, like "Better Drops.js". Important: You need to change the extension to convert it into a Javascript file! You will get a warning from Windows about this, just tell it that changing the file extension is OK. If you can't see file extensions, there's a folder option in Windows to enable them.

Here's an example of what this renaming might look like.

A text document with the name changed to "Sientir_BetterDrops.js"

I like to prefix all of the plugins I write with my name, simply because it makes it easy to track that I was the one who wrote it. It's an old habit of mine at this point.

Once you've finished renaming it, you'll want to open it for editing. I personally use Notepad++ for this, since it supports having multiple files open with tabs, separated panes, the extremely useful feature of syntax highlighting, along with other useful features. It's free, too, which is nice. You can do this with regular Notepad, though, if you want.

Speaking of code, here's an example of the basic template for a plugin!

/*:
 * @plugindesc Short description
 * @author Sientir
 
 * @help Long description
 
 */
 
(function()
{
})();

If you don't know anything about Javascript, this is where I recommend you step away for a while and study up, as teaching it isn't the purpose of this article. If you're wondering what the code might look like in Notepad++, here's an example:

This is literally a screenshot of the code above.

The color coding and line numbers are both very useful features! Don't worry, though—all of the code I share in this article will be text, as I want you to be able to copy and paste it.

OK, now that we have an empty plugin document, let's start by filling in the short description. Maybe something like, "Changes how drop chances are calculated and enables multi-item drops." (Remember, we still want to add functionality for that—I just want to do the first thing first.) This short description shows up in the plugin list, and is very useful for letting you know what it is the plugin does! (Example of this display in the screenshot below.)


The help field for the long description will be useful for the UI for the plugin itself, since we can provide information there for the user about how to use the plugin. This is overall a pretty simple plugin, so we won't need a ton here. Oh, and don't forget to save your plugin file regularly!

OK, now that we've gotten that done, the next step is to figure out what to modify. In my experience, RPG Maker MV's plugin system primarily relies upon overriding existing functions with our own code; that's what we'll be doing here. (You can add your own code, too, for use all over the place, such as with the Script event action or in Conditionals, if you want. You'll definitely need to write your own code to make custom UI for your game—like, say, by making a new status screen or something—but that's beyond the scope of this article. I do find using RPG Maker MV's existing code as a template to modify very useful, though, especially for writing UI.)

To do this, we need to figure out what function we'll need to override. Clearly, it'll be one dealing with enemy drops. The core code files that RPG Maker MV games use can be found in the "js" folder above the "plugins" folder. These files are main.js, plugins.js, rpg_core.js, rpg_managers.js, rpg_objects.js, rpg_scenes.js, rpg_sprites.js, and rpg_windows.js. We'll be looking in them for the objects and functions we'll be modifying. If you're planning on writing a lot of plugins, it's useful to familiarize yourself with which file contains what stuff.

Because I've spent a lot of time digging through these, I know that the most likely place for the function I want will be rpg_objects.js, so I'm going to start by looking in there. Ah, here's the function I need: "makeDropItems" on line 4358. Yes, this is a big file! The function itself looks like this:

Game_Enemy.prototype.makeDropItems = function() {
    return this.enemy().dropItems.reduce(function(r, di) {
        if (di.kind > 0 && Math.random() * di.denominator < this.dropItemRate()) {
            return r.concat(this.itemObject(di.kind, di.dataId));
        } else {
            return r;
        }
    }.bind(this), []);
};

This might look a bit complicated, so I'll try to explain. This makes an array of the items this enemy is dropping. It does that by iterating over the three drop slots. For each one, it starts by first checking to make sure there is a drop there (the di.kind > 0 part does this), then it rolls a random number between 0 and the denominator. If the result is less than the dropItemRate(), then the item is added to the list of drops, otherwise nothing happens and it moves on to the next item.

This brings up the next question: What is this "dropItemRate()" function for? Well, it turns out that it's just below the makeDropItems() function. Here's its code:

Game_Enemy.prototype.dropItemRate = function(){
    return $gameParty.hasDropItemDouble() ? 2 : 1;
};

Ah, so this checks if the party has the doubled item drop rate value. If the party does, it doubles the chance for drops to happen by changing the numerator in the drop chance from a 1 to a 2. (For example, a 1/3 drop chance becomes a 2/3, going from 33% to 67%. A 1/2 drop chance becomes 2/2, or 100%.)

However we end up modifying this function, we'll want to make sure it respects this functionality by ensuring it still works! Now, for the game I'm making, I already have a plugin modifying this function for other reasons (so I could add other things to the drops). There are ways of making plugins that make them play nicely together (or at least increase the chances that they will!), but unfortunately, the way this function works means that method won't really work here. As such, while I will be using the code I'll show you here in this article in my project, it'll just become part of that plugin file instead of the simple one we're making today.

OK, so our next step is to copy that function into our file. I personally like to change the formatting a bit, as I generally find it easier to read code when the curly braces (these guys: { }) are on their own lines. Doing that leaves us with the following:

/*:
* @plugindesc Changes how drop chances are calculated and enables multi-item drops.
* @author Sientir
* @help Long description
*/
(function()
{
    Game_Enemy.prototype.makeDropItems = function()
    {
        return this.enemy().dropItems.reduce(function(r, di)
        {
            if (di.kind > 0 && Math.random() * di.denominator < this.dropItemRate())
            {
                return r.concat(this.itemObject(di.kind, di.dataId));
            }
            else
            {
                return r;
            }
        }.bind(this), []);
    };
})();

OK, now we need to figure out a few things. The first is to determine what we'll need to change. Looking over this code, it's clearly this portion of the conditional: "Math.random() * di.denominator < this.dropItemRate()". The rest of it does stuff we're happy to have it do! It's also clear looking at this that di.denominator is where the drop chance value is stored, so we're going to need that. For those unfamiliar with it, Math.random() is a function that returns a random number between 0 and 1.

Here's how I'm going to change that section of the conditional to produce the desired results: Math.random() < this.dropItemRate() * di.denominator / 1000

For those wondering how this works, by dividing di.denominator by 1000, we'll get a number between 0 and 1 (remember that di.denominator is the value we enter in the UI that maxes out at 1000). I multiply it by dropItemRate() to make sure to include that functionality. If the random number is less than that result, then the item was dropped, so for example, if our drop rate is 750, then we'd get 0.75 on the right side (or 1.5 if we have the item drop chance doubled flag on). Since the random number is between 0 and 1, that means approximately 75% of the time it will return a number less than 0.75, which will result in a drop. The other 25% of the time, it'll produce a number greater than 0.75, which results in no drop. Hopefully that makes sense! Of course, if the right side is 1 or greater, a drop is guaranteed—which would happen in this case if the double item drop chance flag is on.

This now brings us to the next step, which is to be able to adjust quantities of the item being dropped. This is something I'll want to do on a per-enemy basis, which means I need some way of getting this data into the enemy, which means using Note tags. These are tags you put in the notes field using the format <tag_name:value>. These will automatically get added to the object in the code in the meta field, which we'll see in the code shortly. Here's an example of Note tags in use.

The Note field for an enemy in RPG Maker MV that has a couple of note takes, such as <base_power:100>

This example Note field on an enemy has some tags already to define properties that aren't a part of the user interface. In this example, <base_power:100> defines the base power of the enemy's "weapon"—that is, the enemy's standard attack strength.

There are a lot of ways we could configure our note tags to work. My plan is to use separate note tags for each drop field, like so: <drop1Quantity:value>, <drop2Quantity:value>, and <drop3Quantity:value>. This will allow the user of the plugin to specify values for each drop. I'm also going to set the code up to be able to receive both single numbers (e.g. <drop2Quantity:5>) and ranges (e.g. <drop1Quantity:[1, 6]>).

To do this, I'm going to set up an array to hold the quantities for each drop. However, I'll also need to dramatically change how we're iterating over the drop items, since the current method will make it pretty hard to sync up with an array of quantities. I'll write out the code and make sure to include comments to explain everything!

/*:
 * @plugindesc Changes how drop chances are calculated and enables multi-item drops.
 * @author Sientir
 
 * @help Changes how drop chances are calculated so that the number
 * entered is N/1000 instead of 1/N. This means the number works
 * like a percentage (technically, a per thousand), where 750 is
 * 75%, 125 is 12.5%, and so forth.
 *
 * Also adds tags to allow for item drop quantities beyond 1.
 * These tags are <drop1Quantity:value>, <drop2Quantity:value>,
 * and <drop3Quantity:value>. They affect the item in the
 * corresponding drop item slot, where the top item is 1, the
 * middle item is 2, and the bottom item is 3.
 *
 * For these tags, "value" can be either a single number (e.g. 5)
 * or a range (e.g. [2, 5]). Ranges mean that if the item drops,
 * you can end up receiving any quantity between (and including)
 * the two values provided.
 
 */
 
(function()
{
    Game_Enemy.prototype.makeDropItems = function()
    {
        // Create an array to store the quantity of each drop:
        var dropQuantities = [];

        // We'll need to do this for each drop entry in the meta field of the enemy. Note that I'm using this.enemy()!
        //  We're in a Game_Enemy object, but these are for active information about the foe. Data, including the meta variables auto-
        //  generated from tags in the Note field, are stored in the $dataEnemies array. Using this.enemy() grabs us the entry in that
        //  array for this particular enemy unit.
        if(this.enemy().meta.drop1Quantity)
        {
            // This will convert the value into something we can use (by default, it's a string).
            //  Specifically, it will account for the value being either a number OR an array, which
            //  is very useful.
            var quantity = JSON.parse(this.enemy().meta.drop1Quantity);
            if(quantity.length) // This is an array!
            {
                // We are assuming an array of length 2 here--if it's smaller, this will potentially break. If it's bigger (3+ entries),
                //  all entries beyond the second will be ignored. You could do the algorithm differently, of course; for example, you
                //  could have it pick one of the numbers in the quantity array instead.
                var lower = Math.min(quantity[0], quantity[1]); // The smaller of the two numbers is the lower value in the random range.
                var upper = Math.max(quantity[0], quantity[1]); // The greater of the two numbers is the upper value in the random range.
                // randomInt() isn't a built-in JavaScript function, but rather one that comes with RPG Maker MV. It accepts one value,
                //  the max value, and returns a number between (and including) 0 and the max number, but it can't return the max number.
                //  That's why I have a +1 in the math here. The function itself is: return Math.floor(max * Math.random());
                dropQuantities[0] = Math.randomInt(upper - lower + 1) + lower; // Select a random quantity between lower and upper, inclusively.
            }
            else // Assuming this to be a single number!
            {
                dropQuantities[0] = quantity;
            }
        }
        else dropQuantities[0] = 1; // If a drop quantity isn't specified, then we'll use a default quantity of 1.

        // Do everything again for drop2Quantity...I copied + pasted, then carefully made sure I updated everything!
        if(this.enemy().meta.drop2Quantity)
        {
            // Making sure I changed this from drop1Quantity to drop2Quantity...
            var quantity = JSON.parse(this.enemy().meta.drop2Quantity);
            if(quantity.length) // This is an array!
            {
                var lower = Math.min(quantity[0], quantity[1]);
                var upper = Math.max(quantity[0], quantity[1]);
                // Making sure I update this from storing in index 0 (for the first item) to index 1 (the second item).
                dropQuantities[1] = Math.randomInt(upper - lower + 1) + lower; // Select a random quantity between lower and upper, inclusively.
            }
            else // Assuming this to be a single number!
            {
                // Making sure I update this from storing in index 0 (for the first item) to index 1 (the second item).
                dropQuantities[1] = quantity;
            }
        }
        // Making sure I update this from storing in index 0 (for the first item) to index 1 (the second item).
        else dropQuantities[1] = 1; // If a drop quantity isn't specified, then we'll use a default quantity of 1.

        // Do everything again for drop3Quantity...I copied + pasted, then carefully made sure I updated everything!
        if(this.enemy().meta.drop3Quantity)
        {
            // Making sure I changed this from drop2Quantity to drop3Quantity...
            var quantity = JSON.parse(this.enemy().meta.drop3Quantity);
            if(quantity.length) // This is an array!
            {
                var lower = Math.min(quantity[0], quantity[1]);
                var upper = Math.max(quantity[0], quantity[1]);
                // Making sure I update this from storing in index 1 (for the second item) to index 2 (the third item).
                dropQuantities[2] = Math.randomInt(upper - lower + 1) + lower; // Select a random quantity between lower and upper, inclusively.
            }
            else // Assuming this to be a single number!
            {
                // Making sure I update this from storing in index 1 (for the second item) to index 2 (the third item).
                dropQuantities[2] = quantity;
            }
        }
        // Making sure I update this from storing in index 1 (for the second item) to index 2 (the third item).
        else dropQuantities[2] = 1; // If a drop quantity isn't specified, then we'll use a default quantity of 1.

        // Grabbing the array of drop items from the $dataEnemies entry and storing it so I don't have to type as much each time.
        var dropItems = this.enemy().dropItems;

        // This will hold the drops:
        var drops = [];

        // I've changed the iteration method to a for-loop that iterates over the dropItems array.
        //  This makes it easy to sync up the dropQuantities array I created above with the corresponding entry
        //  in the dropItems array.
        for(var i = 0; i < dropItems.length; ++i)
        {
            // Storing this in a variable makes things shorter, but it also made it easier to convert.
            var di = dropItems[i];

            // Note that Math.random() can't actually return 1 so far as I know.
            if (di.kind > 0 && Math.random() < this.dropItemRate() * di.denominator / 1000)
            {
                // This is a slight optimization and not strictly necessary, but we're probably still gonna have drop quantities of 1 a lot,
                //  so it makes sense to single out this case to do more quickly.
                if(dropQuantities[i] == 1)
                {
                    // concat() will concatenate (that is, join together) an array and something else, such as another array (as below)
                    //  or just a value (as is done here). However, it doesn't modify the original array, it instead returns a new, joined
                    //  version, so I need to make sure to store the new array back into my variable.
                    drops = drops.concat(this.itemObject(di.kind, di.dataId));
                }
                else
                {
                    var dropBag = []; // We're filling a bag with this particular item!

                    // You may notice that I increment my for iterators with ++ in front (like the ++db here). You can use post increment
                    //  operators, too (e.g. db++)! I just have a habit of using the in front version from previous programming experience,
                    //  but it doesn't really matter. You could even do something like "db += 1" if you'd prefer!
                    for(var db = 0; db < dropQuantities[i]; ++db) // Note that we're incrementing to the quantity.
                    {
                        // Filling the bag with the item that this drop is. Note that "Kind" designates whether it's an item, armor, or weapon.
                        dropBag[db] = this.itemObject(di.kind, di.dataId);
                    }

                    // Add the contents of the bag to the drops array:
                    drops = drops.concat(dropBag);
                }
            }
        }

    // Now that we've created our drops, we need to return them so that the player can actually receive them!
    return drops;
    };

})();

And that's it! That's the code for the plugin. Note that this is after a bit of testing and bug fixing (which was definitely needed—I didn't fully understand how .concat() worked at first, which caused no drops to actually happen at all)! If you have any questions about it, feel free to ask me in my Discord; there's a link to the left.

Once you've gotten your plugin done, make sure to add it and set it to ON in the plugin manager. Here's a screenshot of its individual page:


A picture of the plugin in RPG Maker MV's plugin manager.

You can see what the Help field looks like here. I don't think this field has word wrap, so you'll need to make sure to check your formatting.

There's a lot more to plugins than I've gone over here (I haven't even touched on parameters!—you can learn more about those here). My goal here today was to cover how I've gone about writing my plugins for RPG Maker MV and the mindset I use when doing so. I hope this has been useful, but if not, perhaps you'd prefer the tutorial I read to learn how to make RPG Maker MV plugins?

One word of warning: Plugins are extremely powerful, and you can do a LOT with them, including making massive changes to how the game works. What you can't do is change the editor's interface. It's very easy to fall into the trap of thinking that, just because you can do something with a plugin that it's a good idea to do so. The danger here is in making your game extremely tedious to work on because you're effectively doing things through hacks and workarounds. I mention this because I've run into this exact trap.

Also, as a tip, you can bring up the Chrome debug menu in game by pressing F12.You can use the resulting window to set breakpoints, step through your code, diagnosis error messages (including getting far more information than the error messages that pop up in the game window), and check variables and syntax. I've found it to be an invaluable asset while working on my game, Psychopomps Are Missing (working title).

Thanks for reading!

Comments

Popular posts from this blog

Some Observations About OnlyFans

Looking Back On 2023, Looking Ahead To 2024