Discretion Commands and Displaying Images

From ASSS Wiki
Revision as of 19:07, 22 February 2007 by BaK (talk | contribs) (content)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

In this example I will show how to listen for chat commands using Discretion's Chat Module as well as how to load and display images on the screen using UniqueImage and Graphics.

Topics covered: Listening for Callbacks, Using Interfaces, UniqueImage, Graphics

We will make two commands, ?show and ?hide which will cause a graphic to appear and disappear on the screen.

The first thing we'll do is make the graphic we want to display and put it in our Discretion/graphics folder. This can be done in a progam such as Paint. Here we saved the file as my_testimage.png. File:Cg 1.png

Now onto the code, make a module as specified in the [Simple Module Creation Tutorial]. The first thing we're going to do is listen to the callback when commands are sent through the chat. Callbacks in Discretion are similar to callbacks in AS3. They represent events that occur that you can react to. Callbacks are registered through the module manager.

/**
  * -	tells the module manager that you want to receive a callback
  * @param myVersion the version of the module requresting this callback, this is useful in
  * debugging if we were to cause a crash while we're in a callback
  * @param callbackName is a name string that will tell the module manager which callback you want 
  * to receive 
  * @param callbackVersion is the version string for the callback. If you use an antiquated version, 
  * and a different version of the callback is called, an error will be recorded
  * @param func is a function pointer to a function that should be called when the callback occurs, 
  * note that all callback functions are of the same form, but you can cast the parameter to a more 
  * usable form. Don’t change the param, as it must be used to call the other functions too
  */
void (*regCallback)(const char* myVersion, const char* callbackName, const char* callbackVersion, callbackFunc func);

Here we pass in myVersion, which is used only if there's a problem with your callback handler, so make it something that you will be able to recognise when it's printed to the console, such as "show-hide graphic module" or the interface handle if you have an interface defined, or even the C preprocessor macro __FILE__ will work. callbackName and callbackVersion are unique to the callback, more on that in a moment, func is the function to call when the callback occurs. callbackFunc is typedefed to a function pointer:

typedef void (*callbackFunc)(void* param);

In general, you can figure out information about the callback in the file where it's defined. For the command callback we're interested in, we look in Chat.h:

// occurs when a possible internal command is sent, if you're processing this command set processed to TRUE
#define CB_INTERNALCOMMAND				"InternalCommand"
#define CB_INTERNALCOMMAND_VERSION		"1"
// the parameter is as follows, set processed to TRUE if you process it
struct InternalCommandParam
{
	const char* command; // the entire command line, with parameters and everything, but no command character
	BOOL processed; // set this to true if you process the command

	// you can use this function to check if this command is yours, sets processed for you
	// setToParams points to whatever's after the command if successful, can be NULL if you don't care
	BOOL isCommand(const char* what, const char** setToParams)
	{
	...
	}
};

Here the callback name is defined to CB_INTERNALCOMMAND and callback version is defined to CB_INTERNALCOMMAND_VERSION. The parameter to the callback, which is a anonymous pointer (void*) can be cast to a InternalCommandParam*. We then use the isCommand to check if this is the command we're interested in.

So now to register the internal command callback with the module manager and print out a message to the console when we receive "?show" or "?hide". Note that we have to #include "Chat/Chat.h" because that's where the callback is defined.

// Commands - Graphics Tutorial Discretion Module
#include "Module.h"
#include "Chat/Chat.h"

ModuleManager* mm = 0;

void gotInternalCommand(void* param)
{
	InternalCommandParam* icp = (InternalCommandParam*)param;

	if (icp->isCommand("show",0))
	{
		printf("Received Command: show\n");
	}
	else if (icp->isCommand("hide",0))
	{
		printf("Received Command: hide\n");
	}
	
}

void* getClassInstance(void* _mm, LoadMode lm)
{
	mm = (ModuleManager*)_mm;

	if (lm == LOADMODE_Init)
	{
		mm->regCallback(__FILE__, CB_INTERNALCOMMAND, CB_INTERNALCOMMAND_VERSION, gotInternalCommand);
	}

	return 0;
}

extern "C" 
{
	EXPORT void* getInstance(void* mm, LoadMode lm)
	{
		return getClassInstance(mm,lm);
	}
}

We should be able to make this module and see the effect when we type ?show and ?hide and press enter in the chat.

File:Cg 2.png

Now onto making an image display on the screen. There are two steps to this, loading the image, and drawing the image on the screen. Loading the image can be done in a few ways, the easiest is to use the UniqueImage module. The UniqueImage module loads settings from conf files, so you can refer to the image by name in the code. UniqueImage also ensures the image is only loaded once (it's unique), so that you can use the same image in different modules without worrying about wasting memory. Unique image will give you an image handle from an image name. An image handle is just an int, which we can pass to Graphics when we're drawing the image. First we'll define the image in the conf files. Open up Image.conf in conf/visual/ and add <Image Name>Path = <path to image> and <Image Name>Frames = (1, 1) to the bottom of the file. In the picture we use CommandsGraphicsTutorial as the name of our image... this is what we'll refer to the image in the code, just make sure it isn't the same as any of the other names. The Frams setting defines how many frames are in the image, for example ships are typcially defined in a single file with 40 images aranged 10 wide and 4 tall, so we'd use (10,4) for the frames if we had such an image. For our image though, we only use a single frame so (1, 1) is correct.

File:Cg 3.png

Now we have to get the UniqueImage interface and use it. Interfaces are generally loaded during LOADMODE_LoadInterfaces in getClassInstance. They are gotten using the interface identifier and the module manager. The interface identifier is defined in UniqueImage.h

#define I_UNIQUEIMAGE 	"UniqueImage-1"

To use this constant we need to #include "UniqueImage/UniqueImage.h". In getClassInstance, if lm is LOADMODE_LoadInterfaces, we use the module manager to load the interface using the getInterface function.

/**
  * -	tells the module manager that you want an interface. If the interface is not loaded by the 
		module manager, an error will result which the module manager takes care of 
  * -	this should be called when getInstance(MODE_LoadInterfaces) is called
  * @param myVersion the version of the module calling this function
  * @param getVersion the version of the module we want to load
  * @return a void* pointing to the class interface of the loaded module
  */
void* (*getInterface)(const char* myVersion, const char *getVersion);

Here getVersion is I_UNIQUEIMAGE, myVersion, as in callbacks, is only used in case of errors. We can use __FILE__ or an identfying string like "commands-graphics tutorial". We save the pointer returned by getInterface. The question now is when should we call UniqueImage's getImageHandle function. We want to make sure that we do not call it too early, because the image loading system may not be initialized. This problem is solved through callbacks. For loading images, there is a callback that says "the image loading system is initialized, load your images now". This callback is in Graphics.h, CB_LoadImages.

/*
This LoadImage callback will be called when the images are to be loaded, there is no parameter
This is guarenteed to be in POSTLOAD_Interfaces
*/

#define CB_LOADIMAGES			"LoadImages"
#define CB_LOADIMAGES_VERSION	"1"

So we register a callback for this function (and #include "Graphics/Graphics.h"), and load our image and save the image handle UniqueImage gives us.

// Commands - Graphics Tutorial Discretion Module
#include "Module.h"
#include "Chat/Chat.h"
#include "UniqueImage/UniqueImage.h"
#include "Graphics/Graphics.h"

ModuleManager* mm = 0;
UniqueImage* ui = 0;

int imageHandle = 0;

void gotInternalCommand(void* param)
{
	InternalCommandParam* icp = (InternalCommandParam*)param;

	if (icp->isCommand("show",0))
	{
		printf("Received Command: show\n");
	}
	else if (icp->isCommand("hide",0))
	{
		printf("Received Command: hide\n");
	}
}

void loadImages(void* param)
{
	imageHandle = ui->getImageHandle("CommandsGraphicsTutorial");
}

void* getClassInstance(void* _mm, LoadMode lm)
{
	mm = (ModuleManager*)_mm;

	if (lm == LOADMODE_Init)
	{
		mm->regCallback(__FILE__, CB_INTERNALCOMMAND, CB_INTERNALCOMMAND_VERSION, gotInternalCommand);
		mm->regCallback(__FILE__, CB_LOADIMAGES, CB_LOADIMAGES_VERSION, loadImages);
	}
	else if (lm == LOADMODE_LoadInterfaces)
	{
		ui = (UniqueImage*)mm->getInterface(__FILE__,I_UNIQUEIMAGE);
	}

	return 0;
}

extern "C" 
{
	EXPORT void* getInstance(void* mm, LoadMode lm)
	{
		return getClassInstance(mm,lm);
	}
}

Now that we have the image, we need to draw it on the screen. This is done using the Graphics interface. We get this interface similar to how we got the UniqueImage interface (in getClassInstance where lm == LOADMODE_LoadInterfaces). The function we're interested is one that draws images on the screen:

/** Draw an image in the next frame, this is to be done in the prerender callback
  * This one draws relative to screen coordinates, rather than absolute coordinates, no bounds checking is done
  * @param imageHandle an image handle from the loadImage function
  * @param frame the frame of the image to draw on, for example the rotated ship graphic is 0-39
  * @param xPixel the xPixel to draw with, this in screen coordinates
  * @param yPixel the yPixel to draw with, this in screen coordinates
  * @param Layer the layer to draw on, probably from the enum Layer
  * @blend a number between 0 and 100 to determine how much blending is to be done for this image, 
  *        so 0 is completely transparent and 100 is opaque
  */
void (*drawScreenImage)(int imageHandle, int xPixel, int yPixel, int frame, int layer, int blend);
void (*drawScreenImage2)(int imageHandle, int xPixel, int yPixel, int layer);

imageHandle is the handle we got from UniqueImage, xPixel is the x pixel to use for the left part of the image, yPixel is the y pixel for the top of the image. (0, 0) is the upper left corner of the screen, with y increasing downward and x increasing rightward. Frame is the frame of the image to draw, for us this is 0 since we have only a single frame defined in our image. Layer is layer to draw on, some common ones are given in the enum Layer. Blend is the amount to blend the image, 100 for opaque images and fading to completely transparent when blend is 0. Here is the layer enum:

// The layer enum, note you can define your own layers between these ones if you want
enum Layer
{
	L_BelowAll = 50,
	L_Background = 100,
	L_AfterBackground = 150,
	L_Tiles = 200,
	L_AfterTiles = 250,
	L_Weapons = 300,
	L_AfterWeapons = 350,
	L_Ships = 400,
	L_AfterShips = 450,
	L_Gauges = 500,
	L_AfterGauges = 550,
	L_Chat = 600,
	L_AfterChat = 650,
	L_TopMost = 700
};

The question now is when we should call drawScreenImage. The answer is, once again, in a callback. The callback in this case is in Graphics.h, PreRender:

// The parameter to the prerender callback
struct PrerenderParam
{
	PrerenderParam(int m, Rect* s) : milDif(m) , screen(s) {} 

	int milDif; // milliseconds since last prerender callback, or 0 on the first one
	Rect* screen; // the screen bounds, in pixels
};

// The prerender callback
#define CB_PRERENDER			"Pre-render"
#define CB_PRERENDER_VERSION	"2"

This callback occurs every frame, when we call drawScreenImage, it will get drawn for a single frame. We register the callback like we did for LoadImages and InternalCommand. We then call drawScreenImage based on whether the user has used ?show or not. The final version the code is below:

// Commands - Graphics Tutorial Discretion Module
#include "Module.h"
#include "Chat/Chat.h"
#include "UniqueImage/UniqueImage.h"
#include "Graphics/Graphics.h"

ModuleManager* mm = 0;
UniqueImage* ui = 0;
Graphics* g = 0;

int imageHandle = 0;
bool showImage = false;

void gotInternalCommand(void* param)
{
	InternalCommandParam* icp = (InternalCommandParam*)param;

	if (icp->isCommand("show",0))
	{
		showImage = true;
	}
	else if (icp->isCommand("hide",0))
	{
		showImage = false;
	}
}

void loadImages(void* param)
{
	imageHandle = ui->getImageHandle("CommandsGraphicsTutorial");
}

void preRender(void* param)
{
	if (showImage)
	{
		g->drawScreenImage2(imageHandle, 200, 200, L_TopMost);
	}
}

void* getClassInstance(void* _mm, LoadMode lm)
{
	mm = (ModuleManager*)_mm;

	if (lm == LOADMODE_Init)
	{
		mm->regCallback(__FILE__, CB_INTERNALCOMMAND, CB_INTERNALCOMMAND_VERSION, gotInternalCommand);
		mm->regCallback(__FILE__, CB_LOADIMAGES, CB_LOADIMAGES_VERSION, loadImages);
		mm->regCallback(__FILE__, CB_PRERENDER, CB_PRERENDER_VERSION, preRender);
	}
	else if (lm == LOADMODE_LoadInterfaces)
	{
		ui = (UniqueImage*)mm->getInterface(__FILE__,I_UNIQUEIMAGE);
		g = (Graphics*)mm->getInterface(__FILE__,I_GRAPHICS);
	}

	return 0;
}

extern "C" 
{
	EXPORT void* getInstance(void* mm, LoadMode lm)
	{
		return getClassInstance(mm,lm);
	}
}

And when we run this module in Discretion we get the desired result:

File:Cg 4.png

Hopefully, by now you are comfortable with listening to callbacks and using interfaces as well as more familiar with the UniqueImage and Graphic modules. Here's a few things you can try to do:

- Instead of drawing the image on the screen, draw it on the map at pixel 8200,8200, and make it so that you can fly over it with your ship.

- Make your image have two frames instead of just a single one, and use a command "?switch" to change the frame of the image being displayed.

- Add a command "?blend <int>" where <int> is an integer specifying the blend amount to use. Observe the effects of different blend values.