eme version 0.2a - seme version 0.2
The code you write will be called "plugin", even if it is not a true plugin (it cannot be dynamically loaded). The main file will be called plugin.cc.
You need the Allegro library, the Allegro Theme (altheme) library and the source version of eme.
The map file will contain a description. Each tile will have an image and can be either blocked or not. The tiles are squares of 32x32 pixels. The map file format will be:
map width (32 bits) map height (32 bits) map description length (32 bits) map description string (length bits) tile[0 0] image index (32 bits) blocked flag (8 bits) tile[0 1] ... ...
The map description will be a map property, called "Description". There will be two layers. The first one, at index 0, will be an Image layer, called "Image". The second one will be a Boolean layer, called "Blocked".
You must define eight functions in the plugin. Some of these functions must return an error code. The error codes are defined in src/plugin.h.
LS_NO_ERROR No error was encountered LS_ERROR_MEM Unable to allocate memory LS_ERROR_FILE Unable to open file, file not found, etc. LS_ERROR_READ Unable to read file, file too short, etc. LS_ERROR_WRITE Unable to write file LS_ERROR_OPTION Unknown option LS_ERROR_INVALID Invalid input LS_ERROR_UNKNOWN Any other type of error
int plugin_encoding(void)
Should return the character encoding you want to use. It can be either U_ASCII, U_UTF8 or U_UNICODE (they are defined in allegro.h). This function is one of the first called, you should assume that nothing has been initialized, you should not attempt to do anything but return the encoding format.
const char *plugin_help(void)
Should return an ASCII string describing the command line options specific to your plugin. Return an empty string if you don't have command line options. When this function is called, Allegro has been initialized, but neither the graphic mode, nor the mouse, nor keyboard has been.
const char *plugin_about(void)
Should return a string in the current encoding to be displayed when the user calls the menu Help->About.
int plugin_init(int argc, char **argv)
Should process argc and argv for the command line options. Can also be used to set some GUI options and execute any initialization code you need. This function is called only one time, after Allegro initializations (Allegro itself, the graphic mode, the mouse and the keyboard) but before any map was created or loaded. Should return one of the LS_... error codes.
void plugin_exit(void)
Can execute any clean-up code you want. This function is called one time, at the end, before Allegro has exited and before switching to text mode.
int plugin_new(void)
Should create a map, and return one of the LS_... error codes. Depending on the returned value, an error message will be displayed.
int plugin_load(const char *filename)
Should load the map from filename, and return one of the LS_... error codes. Depending on the returned value, an error message will be displayed.
int plugin_save(const char *filename, const Map *map)
Should save the map map to the file filename, and return one of the LS_... error codes. Depending on the returned value, an error message will be displayed.
The backbone of the plugin will look like this.
#include <allegro.h> #include "plugin.h" int plugin_encoding(void) { return U_ASCII; } const char *plugin_help(void) { return ""; } const char *plugin_about(void) { return EMPTY_STRING; /* An empty string in the current encoding */ } int plugin_init(int argc, const char **argv) { int error = LS_NO_ERROR; /* Do some initializations, and set 'error' if needed */ return error; } void plugin_exit(void) { /* Do some cleanup */ } int plugin_new(void) { int error = LS_NO_ERROR; /* Create a map, and set 'error' if needed */ return error; } int plugin_load(const char *filename) { int error = LS_NO_ERROR; /* Load the map from 'filename', and set 'error' if needed */ return error; } int plugin_save(const char *filename, const Map *map) { int error = LS_NO_ERROR; /* Save 'map' in 'filename', and set 'error' if needed */ return error; }
The plugin_init function should specify which GUI features you want. For this you need to access the global variable GUI which represents the GUI. Include both gui.h and globals.h.
#include "gui.h" #include "globals.h" /* Define the variable GUI */
The function AllowFeature from the class GraphicalUserInterface allows you to specify which features you want.
void GraphicalUserInterface::AllowFeature(bool yesno, Features::Group::Groups group)
We're going to allow all features, but the ones allowing the user to change the number or order of the layers. This because it's far more difficult to save a map with layers in any order. So we call AllowFeature two times in plugin_init, one for allowing everything, the second for disallowing part of the funtionnalities.
/* First, allow every features */ GUI.AllowFeature(true, Features::Group::Everyting); /* Then, disallow some */ GUI.AllowFeature(false, Features::Group::VariableLayerCount);
The code can be found here.
To compile the plugin, use the emetool script. The following command compiles plugin.cc and links it with the relevant libraries (including libeme.a) to build the executable test_eme:
emetool --build test_eme plugin.cc
The emetool script is found in the eme directory.
See the emetool reference.
If, after compiling, you run test_eme, you'll see that everything is here: the menu, the tool box, etc. but that nothing happens when you try to create a new map or load one. We are now going to write the new map function. Recall that its backbone is:
int plugin_new(void) { int error = LS_NO_ERROR; /* Create a map, and set 'error' if needed */ return error; }
First we'll define some values that will be needed. We want to have one map property: its description, and two layers: an image and a blocked flag.
typedef enum { DESCRIPTION_INDEX = 0, PROPERTY_COUNT } PROPERTY_INDEX; typedef enum { IMAGE_INDEX = 0, BLOCKED_INDEX, LAYER_COUNT } LAYER_INDEX;
Using an enum instead of a #define is useful because if you want to add another property, you can just insert it before PROPERTY_COUNT.
Since we are going to use three property types: String for the map description, DatafileObject for the image layer and Boolean for the blocked layer, we must include these files:
#include "cstring.h" #include "cdataf.h" #include "cbool.h"
You will note that a 'c' is appended to the property type name in the filename. This 'c' stands for 'Creator'. By including these files, you have access to the following classes: String::Creator, String::Property, DatafileObject::Creator, DatafileObject::Property, Boolean::Creator and Boolean::Property. The creator classes creates and manages the corresponding properties. [For those who know SmallTalk, IIRC the creators are somewhat equivalent to the super classes.]
Unless all the maps in your game have the same size, you want to ask the map designer for the map size. For this, you can use the function:
int popup_size( const char *width_title, int *width, const char *height_title, int *height )defined in src/emepopup.h. It pops up two dialog boxes asking for the map width and height. It returns TRUE if both popups where OK'ed, and FALSE if any of them was canceled. It also set the chosen size in *width and *height.
To ask for the map size, call popup_size and check its return value. Then you'll have to check that the width and height are valid. If the popups are canceled, you should return LS_NO_ERROR because it is not really an error (remember that if the function returns an error a message will be displayed).
int width, height; if (popup_size("Map Width", &width, "Map Height", &height) == TRUE) { if (width>0 && height>0) { /* Create the map */ } else { error = LS_ERROR_INVALID; } }
The Wrapper class hides eme internals. You will use it to create and access the map. If you really want to write hackish code, you can shunt most of its functionalities (but I would suggest not to do that). To use the wrapper:
#include "wrapper.h"
You can create a wrapper object by either passing a map to it or by passing the map width, height, number of properties and number of layers and it will create the map for you.
Wrapper wrapper(width, height, LAYER_COUNT, PROPERTY_COUNT);
Then, you have to specify some informations: the map shape, the tiles shape and size, and the tile offset (see Not done yet for more informations about these things).
wrapper.SetMapShape(MAP_SHAPE_RECT); wrapper.SetTileShape(TILE_SHAPE_RECT, 32, 32); wrapper.SetTileOffset(32, 32, ODD_NONE);
To specify the map property, we need its index (defined with the enum PROPERTY_INDEX), its name ("Description"), its default value ("No description", for example) and, since it is a string, the maximum number of characters.
#define DESCRIPTION_NAME "Description" #define DESCRIPTION_DEFAULT_VALUE "No description" #define DESCRIPTION_LENGTH 256
With these defines, we can call the template function to set the property.
wrapper.SetProperty<String>( DESCRIPTION_INDEX, DESCRIPTION_NAME, DESCRIPTION_DEFAULT_VALUE, DESCRIPTION_LENGTH );
If you don't know what is a template in C++. It is a function or a class to which you pass type parameter between < and >. These types will be used to type either some parameters or some internal data. In the present case String is used to tell the wrapper which type of property to create. [In fact there is much more to say about templates, but this should be enough to write a plugin for eme.]
To specify the layers, we need again their index, their name and their default value.
#define IMAGE_NAME "Image" #define IMAGE_DEFAULT_VALUE 0 #define IMAGE_DATAFILE "images.dat" #define BLOCKED_NAME "Blocked" #define BLOCKED_DEFAULT_VALUE 0
The default value for the image layer is the first object in the datafile (index 0).
Then we set the layers:
#include "sttcstr.h" /*...*/ wrapper.SetLayer<DatafileObject>( IMAGE_INDEX, IMAGE_NAME, FULL_LAYER, IMAGE_DEFAULT_VALUE, new StaticString(IMAGE_DATAFILE) ); wrapper.SetLayer<Boolean>( BLOCKED_INDEX, BLOCKED_NAME, FULL_LAYER, BLOCKED_DEFAULT_VALUE );
The FULL_LAYER parameter tells the wrapper that the layer will not be allowed to have holes. If you want a layer with holes, pass the value SPARSE_LAYER.
You will note that the two calls to SetLayer don't have the same number of parameters. It is because some layer and property types need an additional data. The string type needs the maximum number of characters, the image type needs the datafile name, the list type needs a list of strings.
The layers has been specified. But you still need to fill them because they are of type FULL_LAYER.
wrapper.FillLayer(IMAGE_INDEX); wrapper.FillLayer(BLOCKED_INDEX);
You can call FillLayer on both full and sparse layers. When called on full layers (layers without holes), the layer will be filled. When called on sparse layers (layers that can have holes), this function will do nothing, so you can switch between full and sparse layer without having to comment and uncomment FillLayer call.
Last, but not least, you need to tell the wrapper that you are done with the map:
wrapper.SetMap();
Be careful not to access the wrapper after calling SetMap because it is not guaranteed that the map will still be valid.
The code can be found here. You can compile it, and play with it. Next section, we will save the map.
The backbone of the save function is:
int plugin_save(const char *filename, const Map *map) { int error = LS_NO_ERROR; /* Save 'map' in 'filename', and set 'error' if needed */ return error; }
You don't need to check the existence of the file, because it is done before plugin_save is called and this function is called only if the user accepted to erase the file.
We are going to use Allegro pack files. The file layout will be:
map width (32 bits) map height (32 bits) map description length (32 bits) map description string (length bits) tile[0 0] image index (32 bits) blocked flag (8 bits) tile[0 1] ... ...
To access the map, we will again use the Wrapper class. You can create a wrapper for the map like this:
const Wrapper wrapper(map);
The wrapper variable is declared constant because we won't modify the map.
Then we open the file and check it.
PACKFILE *file = pack_fopen(filename, "w"); if (file) { /* Save the map */ } else { error = LS_ERROR_FILE; }
First we save the map size. You can get it from the wrapper by the GetMapWidth and GetMapHeight functions.
int width = wrapper.GetMapWidth(); int height = wrapper.GetMapHeight(); if (pack_iputl(width, file) == EOF) { pack_fclose(file); return LS_ERROR_WRITE; } if (pack_iputl(height, file) == EOF) { pack_fclose(file); return LS_ERROR_WRITE; }
Then we save the map description. To get its value, we use again a template function specifying the type of the property.
#include <string.h> /*...*/ const char *description = wrapper.GetProperty<String>(DESCRIPTION_INDEX); int length = strlen(description) + 1; if (pack_iputl(length, file) == EOF) { pack_fclose(file); return LS_ERROR_WRITE; } if (pack_fwrite(description, length, file) < length) { pack_fclose(file); return LS_ERROR_WRITE; }
Next, we'll save all the tiles:
for (int i=0; i<width; i++) { for (int j=0; j<height; j++) { /* Save the tile */ } }
For each tile, we save the image index (32 bits) and the blocked flag (8 bits).
int image = wrapper.GetTileValue<DatafileObject>(IMAGE_INDEX, i, j); int blocked = wrapper.GetTileValue<Boolean>(BLOCKED_INDEX, i, j); if (pack_iputl(image, file) == EOF) { pack_fclose(file); return LS_ERROR_WRITE; } if (pack_putc(blocked, file) == EOF) { pack_fclose(file); return LS_ERROR_WRITE; }
Now, we are done, close the file.
pack_fclose(file);
The code can be found here. You can compile it, and play with it. Next section, we will load the map.
The backbone of the load function is:
int plugin_load(const char *filename) { int error = LS_NO_ERROR; /* Load the map from 'filename', and set 'error' if needed */ return error; }
First we open the file
PACKFILE *file = pack_fopen(filename, "r"); if (file) { /* Load the map */ } else { error = LS_ERROR_FILE; }
We need to load the map size before creating the wrapper (and the map at the same time).
int width = pack_igetl(file); int height = pack_igetl(file); if (width == EOF || height == EOF) { pack_fclose(file); return LS_ERROR_READ; }
Now we can create the wrapper, since we already know the map property count and the layer count (they are constant).
Wrapper wrapper(width, height, LAYER_COUNT, PROPERTY_COUNT);
We have to set some informations about the map and tile shape (since this code is the same than in the new function, we should factorize it).
wrapper.SetMapShape(MAP_SHAPE_RECT); wrapper.SetTileShape(TILE_SHAPE_RECT, 32, 32); wrapper.SetTileOffset(32, 32, ODD_NONE);
Now we can load the map description and set it in the wrapper. The property is set like in the new function.
int length = pack_igetl(file); char description[DESCRIPTION_LENGTH]; if (length == EOF) { pack_fclose(file); return LS_ERROR_READ; } if (pack_fread(description, length, file) < length) { pack_fclose(file); return LS_ERROR_READ; } wrapper.SetProperty<String>( DESCRIPTION_INDEX, DESCRIPTION_NAME, description, DESCRIPTION_LENGTH );
Then we must create the layers, this creation is done like in the new function, so a good programmer would factorize the code (did I hear you saying I'm not a good programmer ? ;)
wrapper.SetLayer<DatafileObject>( IMAGE_INDEX, IMAGE_NAME, FULL_LAYER, IMAGE_DEFAULT_VALUE, new StaticString(IMAGE_DATAFILE) ); wrapper.SetLayer<Boolean>( BLOCKED_INDEX, BLOCKED_NAME, FULL_LAYER, BLOCKED_DEFAULT_VALUE );
And now we can load the tiles.
for (int i=0; i<width; i++) { for (int j=0; j<height; j++) { int image = pack_igetl(file); int blocked = pack_getc(file); if (image == EOF || blocked == EOF) { pack_fclose(file); return LS_ERROR_READ; } wrapper.SetTileValue(IMAGE_INDEX, i, j, image); wrapper.SetTileValue<Boolean>(BLOCKED_INDEX, i, j, blocked); } }
And we close the file and tell the wrapper it's done:
pack_fclose(file); wrapper.SetMap();
And it works! You can find the code here. Enjoy !
We are going to add a menu entry to select all tiles of a given value. This function is not useful since the tool Select by property already do that, but it is an example.
We must first define the function to be called by the menu. Its prototype should be void foo(Map *). So we define a static function:
#include "map.h" /* ... */ /* Menu callback */ static void my_select(Map *map) { }
Then we need to tell eme to plug it in the menu. For that, we need to access the GUI, so we include the files globals.h and gui.h, which define the variable GUI of type GraphicalUserInterface.
#include "gui.h" #include "globals.h" /* Define the variable GUI */
In the plugin_init, we call the function SetUserMenu. The first parameter is the index of the menu (beginning by 0), the second is the menu entry name and the third the callback.
/* Add a user menu */ GUI.SetUserMenu(0, "Select", my_select);
We now need to write the callback my_select.
We are going to work on the active layer. The layers are of type Tiles, defined in tiles.h. So we get the active layer and execute the command only if it exists.
#include "tiles.h" /* ... */ /* Check if the map exists */ if (!map) return; /* Create a wrapper to access the map */ Wrapper wrapper(map); /* Get the active layer */ int active_layer = wrapper.GetActiveLayerIndex(); const Tiles *layer = wrapper.GetLayer(active_layer); if (layer) { /* Select the tiles */ }
We are going to select all tiles that are equal to the default property value. So we get the reference property.
/* Get the reference property */ const BaseProperty *ref = wrapper.GetLayerReference(active_layer);
We create a SelectedTiles object to contain the tiles we are going to select. And we check all tiles.
#include "selected.h" /* ... */ /* Select the tiles */ SelectedTiles *sel = new SelectedTiles(); for (int i=layer->begini(); i<layer->endi(); ++i) { for (int j=layer->beginj(); j<layer->endj(); ++j) { /* If the tile exists and is equal to the reference */ if (layer->get(i, j) && ref->IsEqualTo(layer->get(i, j))) { /* Select the tile */ sel->Add(i, j); } } }
We now have all the selected tiles in a local structure. Since we want to be able to undo the execution, we are going to use the undo stack. We include cmdselect.h for the selection commands.
/* To be able to undo the menu */ #include "commands.h" #include "cmdselec.h"
We create a command to replace the current selection in the map by the one we've just build and we execute this command.
/* Create the command and execute it */ Command *cmd = new CommandSelectionReplace(map, sel); wrapper.Execute(cmd);
Note that we do not destroy the SelectedTiles we've created because it now belongs to cmd. And we do not destroy cmd because it now belongs to commands_manager.
You can find the code here.