DynRPG v0.32 Unofficial
Plugin SDK
Rules and guidelines for plugin developers

Rules

The RPG Maker is written in Delphi (and I didn't have access to the source code of its classes, etc.), while my SDK uses C++. Thus, many things are not working the way you might expect.

There is a set of rules which you must follow under all circumstances when you are developing a DynRPG Plugin:

  • Do not try to use members which are not documented. They are either unknown or used internally and dangerous. (Of course, if you know what a member does, it's a different story.)
  • Do not instantiate RPG classes. Always use pointers to existing instances you get from DynRPG. It is especially dangerous to use these "home-made" objects with functions from the RPG namespace. It might seem to work, but will most likely corrupt data. This will cause the game to behave strangely or suddenly crash some minutes later. An exception to this rule are the RPG::Music and RPG::Sound classes.
  • Do not assign RPG objects. The result is mostly undefined. An exception are pointers to RPG::Image objects, as long as you don't forget to destroy the old object using RPG::Image::destroy (unless you want to use it before).
  • Do not randomly return false from a callback function. This will "lock out" all plugins which are called after yours. Only return false if you really want this behavior.
  • Never change the vTable member of a class. This will make the game crash sooner or later (probably sooner).
  • Never assign a char * to an RPG::DString pointer. It will appear to work, but it will cause the game to crash with an "Invalid pointer operation" error when the RPG Maker tries to free the string. Also, do not store RPG::DStringPtr objects or RPG::DString pointers inside your plugin, but copy their content to a std::string instead, since RPG::DString objects may suddenly vanish.
  • Do not change the current directory.
  • Do not do stuff every frame which takes longer than one millisecond. This is alredy the very maximum. The less time you use, the better. If you need to do something which takes longer, do it in another thread. An exception are things which happen only rarely, like when a game is loaded or saved, or once when a battle starts, etc. If something should intentionally take longer than one frame, you could use the RPG::Screen::update or RPG::updateBattle function, respectively.

Guidelines

There is also a set of guidelines which you are strongly advised to follow, but there might be cases in which there is a better solution.

Event comments

Comments in event scripts are a great way to let events scripts invoke functions of your plugin. Please follow the following guidelines:

  • Use the following pattern for "special comments":
    @command parameter1, parameter2, parameter3, ...
  • New-line characters should be generally ignored.
  • The comment has to start with an @ sign, immediately followed by the command name.
  • The command name is case-insensitive.
  • There should be three possible types of parameters:
    • Number: A simple number. Can also use the decimal point and the scientific notation (e.g. 5.5e+6 for 5.5 million).
    • String: A simple string. Must be put between doublequotes. To use a doublequote in a string, it is written twice (e.g. He said ""hello"" and smiled)
    • Token: Some identifier. Tokens are not put between quotes, and they may not contain spaces (spaces are removed). Tokens are case-insensitive. They may be used for keywords.
    There are special tokens for referencing variables and actor names:
    • Variables: To reference a variable, the user should be able to write a V character prior to the variable ID. This should also work with multiple levels of dereferencing.
    • Actor names: To reference an actor's name, the user should be able to write a N character prior the the actor ID. This should also work together with V.
  • The command name and tokens should be case-insensitive.
  • Always return false from onComment when you found a known command, regardless whether the parameters were valid or not.
  • Always return true from onComment when you didn't find a known command, even though you may have found an @ sign at the beginning of the comment.

Use the parsedData parameter of your onComment handler to get the comment data in an already nicely parsed form!

Note
The maximum number of parameters is 100. The maximum number of characters per parameter (or command name) is 200. You have to parse the comment yourself if you need more.

Example for a "special" comment:

@FooBar 123, "abc", V55, VV66, N7, NV8, Nothing

The command name is foobar.
The first parameter is numerical.
The second parameter is a string.
The third parameter is a numerical value read from variable #55.
The fourth parameter is a numerical value read from the variable whose ID is stored in variable #66.
The fifth parameter is a string, read from the name of actor #7.
The sixth parameter is a string, read from the name of the actor whose ID is stored in variable #8.
The seventh parameter is a token named nothing.

You might advise users to download RPG Maker 2009 Ultimate if they need to enter comments longer than 4 lines.

Configuration

Many plugins need some kind of configuration. An important rule is: Make as many things configurable as possible.

If possible, store configuration in a DynRPG.ini file. Also, you should use your plugin's name which you get as parameter to the onStartup function as section name. If you need several sections, you can append an underscore and an additional identifier to the name and use it as section name. This will prevent conflicts with other plugins while still combining all relevant configuration of a game in one file.

You may use the RPG::loadConfiguration function as a convenient way to load configuration data to a std::map<std::string, std::string> in the onStartup function.

If you need more or more complex configuration, like XML data, use a filename containing your plugin's name.

In-game data

Your plugin may also use data which is changed in-game and needs to be preserved. Savestate-independent data (like a highscore) should be stored in the DynRPG.ini file together with configuration (use the WinAPI function WritePrivateProfileString).

Savestate-related data (data which should be saved when the user saves the game and loaded when the use loads a saved game) should be saved using the function passed as savePluginData parameter to the onSaveGame function. When the user loads the savestate again, you will get the same data back, in the parameters to the onLoadGame function. Internally, this data is saved in a file called SaveXX.dyn where XX is the savestate ID.

An example usage of savestate-related plugin data is shown here:

// Plugin-related data
int score;
int level;
// ...
void onLoadGame(int id, char *data, int length) {
if(length == sizeof(int) * 2) { // make sure it is valid data
int *dataArray = (int *)data;
score = dataArray[0];
level = dataArray[1];
}
}
void onSaveGame(int id, void __cdecl (*savePluginData)(char *data, int length)) {
int[2] dataArray;
dataArray[0] = score;
dataArray[1] = level;
savePluginData((char *)dataArray, sizeof(dataArray));
}
void onSaveGame(int id, void(*savePluginData)(char *data, int length))
Called before the player saves a game.
void onLoadGame(int id, char *data, int length)
Called before the player loads a game from a savestate.

(Of course, the same result could have been achieved by saving score and level in in-game variables which are automatically saved.)

Optimization

Time is a very important factor. Especially with many plugins, it is important to use as little time as possible in your callback handlers, otherwise the game will eventually start lagging. Thus, try to optimize your code where you can. If possible, always test your plugin with several other plugins in a "real-life situation" to see whether your plugin slows the game down too much. Remember that most of your code will be executed a minimum of 60 times per second (many parts more often than that, for example onCheckEventVisibility will be called 900 times per second if there are 150 events on the map).

Here is a bit of advice how to optimize your plugin code:

  • Do not allocate and deallocate memory or objects over and over again. Try to use static variables wherever possible.
  • Try to use functions in the RPG::Image and RPG::Canvas classes as little as possible. The slowest functions are RPG::Image::drawText and RPG::Canvas::draw.
  • Try to cache text in an RPG::Image if possible, only update it if necessary.
  • Try to cache as much with the same palette as possible on the same RPG::Image so that you don't have to call RPG::Canvas::draw too often.
  • If possible, don't use a transparent color in RPG::Image::draw (set maskColor to RPG::MASK_NONE). The same rule applies for RPG::Canvas::draw, see also RPG::Image::useMaskColor.
  • Try to skip frames if possible. This means: Try to update some things only every 2 or 3 frames if possible.
  • If you are "WinAPI-literate", you can use the RPG::Canvas::bitmap member and the RPG::DBitmap::getHBITMAP and RPG::DBitmap::getHDC functions to get handles to the corresponding GDI objects and manipulate them directly.
  • If you need to calculate something more complex (like a 3D image, etc.) you better do this in a new thread, cache its graphics and draw them to the screen only after calculation was finished, without letting the main thread wait.