DynRPG
v0.20
RM2k3 Plugin SDK
|
The DynRPG SDK works on a very low level. This means, small differences between compilers can break everything. The DynRPG was developed and tested using GCC, thus you should use GCC too. This DynRPG version only works with GCC version 4.7.1 and higher. You might use the Code::Blocks IDE, for example.
Installing the SDK is easy - all you need to do is copying the files from the include
and lib
directories of the download archive into the corresponding folders of your compiler environment.
In order to use the DynRPG in your C++ project, you need to include DynRPG/DynRPG.h
:
#include <DynRPG/DynRPG.h>
#define NOT_MAIN_MODULE
DynRPG automatically creates a DllMain
function for you which stores the plugin's instance handle in a global variable called hInstance
. You can suppress this behaviour by defining CUSTOM_DLLMAIN
before including the header file.
The header file automatically includes the headers string
, map
, windows.h
because they are needed for the declarations.
You also need to add the library DynRPG
(libDynRPG.a
) to your project. See your IDE's help file if you don't know how to do this. (Normally you need to add the parameter -lDynRPG
to the linker's command line.)
Together with the Game objects, callbacks are important ways to interface your plugin with the game. Callbacks are functions which you can define in your plugin. On certain events in the game (for example, a picture is drawn - see onDrawPicture) certain callbacks will be called by DynRPG. Callbacks either return void
or bool
. If you return false
in bool
callbacks, this will prevent other plugins from receiveing the same notification and sometimes it will also prevent some event from happening in the game. For example, if you return false
from your onDrawPicture
callback, the picture won't be drawn and other plugins (after yours) won't receive onDrawPicture
for that picture.
You only need to define those callback functions which you actually use.
For a list of available callbacks, click here: Callbacks
The game objects are the second important way to interface your plugin with the game. Most of the RPG classes are tied to a game object and should only be used through it. For example, the game object tied to RPG::Actor is called RPG::actors.
Game objects represent certain "things" in the game. For example, the RPG::map object (the corresponding class is called RPG::Map) represents the current map. So, to get the width of the current map, you may call the RPG::Map::getWidth function like this:
For a list of available game objects, click here: Game objects
It's time to create our first plugin. It's going to be really simple - all it is going to do is displaying a message box asking whether we really want to play before actually starting the game. We'll call it are_you_sure
.
First, create an RPG Maker 2003 project to test the plugin and apply The Patch to it. Then, create a new C++ project in your favorite IDE and select DLL as project type. It might be convinient to store the source code in a subfolder of the DynPlugins
directory of your test project and set the compiler output directory to the DynPlugins
directory.
Documents
folder. Otherwise make sure you run both RPG Maker 2003 and your IDE (e.g. Code::Blocks) with administrator privileges!Okay, I'll be give a bit more step-by-step instructions now - of course you can do it differently if you know how, but this should help people who are not familiar with Code::Blocks to get started:
C:\Program Files (x86)\CodeBlocks
)! MinGW
. Open it. There should be folders called include
and lib
inside, among others. include
and lib
folders from your DynRPG download (in the sdk
subfolder) into the MinGW
folder. Confirm merging the folders. Create new project...
. Dynamic Link Library
and click Go
. Next
. are_you_sure
). To avoid problems later, do not use spaces. DynPlugins
folder of your RM2k3 test project. The actual filename of the Code::Blocks project file (shown in the last field) should be similar to this: C:\Users\
My Username
\Documents\
My Test Project
\DynPlugins\are_you_sure\are_you_sure.cbp
Next
. GNU GCC Compiler
is selected in the first field. Create "Debug" configuration
. Output dir.
at the "Release" options
to ..\
(two dots and then a backslash). This will create the plugin DLL directly in the DynRPG
folder if you have followed step 11 correctly. Finish
. Project
menu, select Build options...
. Linker settings
tab. Link libraries
section, click Add
. DynRPG
and click OK
. cmd /c del ..\*.a & del ..\*.def
OK
again. Project
menu, select Set programs' arguments...
. Host application
field and select your game's RPG_RT.exe
file. This will allow you to use the Run
and Build and run
buttons in Code::Blocks to run your game. TestPlay ShowTitle Window
into the Program arguments
field. OK
. main.cpp
(find it in the Sources
folder of the tree view) and main.h
(find it in the Headers
folder of the tree view). Delete the contents of both of them. In our examples, we don't need the main.h
file at all, you may right-click it and choose Remove file from project
if you want. In main.cpp
, copy the code provided below:#include <DynRPG/DynRPG.h> // Handler called on startup bool onStartup(char *pluginName) { if(MessageBox( NULL, // We don't need a window handle "This is such a haaaaard game. Are you SURE you want to play it now?", // Text "The Are You Sure Plugin", // Title MB_YESNO | MB_ICONQUESTION // Flags (yes/no buttons and question icon) ) == IDYES) { // The user clicked "Yes", we may continue return true; } else { // The user clicked "No", so we need to abort return false; } }
Okay, that's really simple. It uses Windows' MessageBox function to display a message. If the user clicked "Yes", the game will continue, otherwise it will not start.
This simple plugin uses the onStartup callback. This callback is called before the game starts. If you return false
in this callback, the game won't start. (You only need to define those callbacks which you want to use.)
So, if you compile this plugin, make sure the DLL file (are_you_sure.dll
) was put into the DynPlugins
directory and your test project was successfully patched using The Patch, you should see the DynRPG logo when you start the game, followed by our message box.
By the way, the DynRPG logo cannot be removed. Yes, that's intentional.
Enough of nonsense. Now we are going to create something useful. We are going to create a plugin which displays the conditions of actors and monsters over their head, as icons.
These are its features:
First, in the onStartup handler, we will load the configuration and store it in a global variable called configuration
.
Then, in the onBattlerDrawn handler, we will iterate through all conditions the battler has, check if the images we need are loaded, if not, load them. We will also calculate the total width of all icons for that battler here. Then we will draw all the icons and finally clear the RPG::Battler::displayedConditions array so that no info window is displayed when the target selection is active.
Finally, in the onExit handler, we will unload all images.
This is the content of our condition_icons.cpp
file:
#include <DynRPG/DynRPG.h> #include <sstream> // For std::stringstream // We store the configuration here std::map<std::string, std::string> configuration; // We store the images of the conditions here std::map<int, RPG::Image *> images; // This handler is called on startup bool onStartup(char *pluginName) { // We load the configuration from the DynRPG.ini file here configuration = RPG::loadConfiguration(pluginName); return true; // Don't forget to return true so that the start of the game will continue! } // This handler is called after a battler is drawn bool onBattlerDrawn(RPG::Battler *battler, bool isMonster, int id) { int totalWidth = 0; // We store the total width of the condition icons here // We loop through all the elements of the battler's "conditions" array here // Note that the "condition" array is one-based, thus we start at index 1 for(int i = 1; i <= battler->conditions.size; i++) { // If the battler has the condition (see documentation)... if(battler->conditions[i] > 0) { // If the image isn't loaded yet... if(!images[i]) { // First, we create an RPG::Image object images[i] = RPG::Image::create(); // Yes, we want to use the transparent color images[i]->useMaskColor = true; // Now, we put the key name for the configuration entry together // It should be "Condition12" for condition 12, for example std::stringstream keyName; keyName << "Condition" << i; // Now, we try to load the image. If loading the image fails, // nothing special will happen (because of the "false" at the // "showErrors" parameter), the image will just be empty. images[i]->loadFromFile(configuration[keyName.str()], false); } // We add the image's width to the total width totalWidth += images[i]->width; } } // Now we need to know the Y coordinate of the top of the battler int topY; if(isMonster) { // It is a monster. We just use the monster's image to find out // its size. RPG::Monster *monster = (RPG::Monster *)battler; // First, we cast the "battler" to a "Monster" topY = monster->y - monster->image->height / 2; // Now we calculate the top position } else { // Okay, since we don't have a way to find out the size of an actor's // battle graphic, we will just "guess" that it's a normal BattleCharSet // which is 48 pixels tall. // In a "good" plugin, there would be a way to set the actual height // for each actor in the configuration, but this would be too much // for this tutorial. topY = battler->y - 48 / 2; } // We will use this variable to store the current X coordinate while we // draw the images. We will increase this variable every time we draw an // image. int currentX = battler->x - totalWidth / 2; // Okay, let's loop again through the conditions. This is necessary // because we first had to find out the total width before we could // start drawing the images. Now we will draw them. for(int i = 1; i <= battler->conditions.size; i++) { // If the battler has the condition (see documentation)... if(battler->conditions[i] > 0) { // Okay, here we actually draw the image on the screen! RPG::screen->canvas->draw(currentX, topY - images[i]->height, images[i]); // And we increase the current X coordinate. currentX += images[i]->width; } } // Clear the condition cache which is normally used to determine which // conditions should be shown in the "info window" on top of the screen // during target selection (we don't need to display the conditions twice!) for(int i = 0; i < 5; i++) { battler->displayedConditions[i] = 0; } return true; // It's okay that the battler is drawn, so we return true. Don't forget that! } // This handler is called when the game exits void onExit() { // We will unload all images here. // If you are not familiar with C++ iterators: This "for" loop just iterates // through all items in "images". for(std::map<int, RPG::Image *>::const_iterator iter = images.begin(); iter != images.end(); ++iter ) { // The reason we don't use images[iter->first] instead of iter->second // is that RPG::Image::destroy also takes a reference and sets the // parameter to zero at the end. RPG::Image::destroy(images[iter->first]); } }
To try it, we just need to put the compiled DLL into the DynPlugins
folder (if it isn't already there). Then we need to get some icons. For testing, I made some very simple 16x16 icons and put them into the Picture
folder.
Then we need to open our DynRPG.ini and put the filenames in it, like this:
[condition_icons] Condition2=Picture\poison.png Condition3=Picture\blind.png Condition5=Picture\berserk.png Condition6=Picture\confused.png Condition7=Picture\sleep.png
And hey presto, this is what we get:
You can download the source code as well as the finished plugin here.
You may now start developing your own plugins. You may first take a look at the Callbacks, the Game objects, and the members of the RPG namespace, to get an overview of what is possible and how to get to the result you want. Then, you might start with something easy (like a DynRPG-based version of the PicPointerPatch - hint: take a look at onEventCommand, RPG::EventScriptLine and RPG::variables), then expand your knowledge until you can do nearly everything you want... or something like that. :-)
Yes, I know, I am just throwing you out into the wilderness. Sorry for not providing you more examples how to use all the features. I just had no time for it yet. If you are clever, you will probably be able to create something useful with what you got in this documentation anyway. If not, more examples will come. I will create nice plugins and publish them with source code, etc.
Oh, and don't forget to read and follow the Rules and guidelines for plugin developers!
By the way: If you want to publish your plugin, it would be nice if you would publish the source code too.
Have fun!
Best regards, Cherry