Difference between revisions of "Listening for Key Presses, Displaying Text, and Per Player Data"

From ASSS Wiki
Jump to: navigation, search
(content)
(No difference)

Revision as of 13:38, 13 April 2007

The purpose of this tutorial is to explain how to 1) display text on the screen with the Text module, 2) show how to access shared per player data, 3) listen and act upon key presses in Discretion, and 4) show how to register and use local per player data. You are assumed to know how to create and load a basic module, as well as how to use interfaces and callbacks.

We shall construct a module that displays the currently ticked (in the playerlist) player's name, squad, or frequency. The user will be able to press the ALT key to cycle between these attributes. The module will also keep track of how many times the user has pressed alt for each player.

So for example, consider a case where there's two players in the arena, tux(the user) on freq 0 and squad "Killers", and BaK on freq 1 on squad "BakAttak". Initially the player himself is ticked. The screen will say "Name: tux (Switched 0 times)". The player can then select the next player on the playerlist using page down. After the user presses page down, the screen says "Name: BaK (Switched 0 times)". The user can then press the alt key and the screen switches to "Squad: BakAttak (Switched 1 time)". If the user than presses alt again it switched the text to "Freq: 1 (Switched 2 times)". If the user presses page up it goes back to "Name: tux (Switched 0 times)". Alt will then yield the text "Squad: Killers (Switched 1 time)". Page down will go back to "Freq: 1 (Switched 2 times)". Alt again will give us "Name: Bak (Switched 3 times)".

So let's work off a basic module template:

// Discretion Tutorial, goals:
// 1) display text on the screen with the Text module 
// 2) show how to access shared per player data
// 3) listen and act upon key presses in Discretion
// 4) show how to register and use local per player data.
// April 2007

#include "Module.h"

ModuleManager* mm = 0;

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

	if (lm == LOADMODE_Init)
	{
		
	}

	return 0;
}

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

Let's display some text on the screen using the Text module. The text module can display text on the screen or on the map, on any layer. These layers are the same layers that are used to display images. The function we are interested in is displayTextScreen:

/**
 * Display this text for one frame, call during prerender, this in is screen coordinates
 * @param line the line of text to display
 * @param color the color string to display
 * @param layer the layer to display it on
 * @param topLeft the top left corner of where we should start drawing
 * @param blend how much to blend the text, 0 = transparent, 100 = opaque
 */
void (*displayTextScreen)(const char* line, const char* color, int layer, const Point* topLeft, int blend);
void (*displayTextScreen2)(const char* line, const char* color, int layer, const Point* topLeft);

So as in the images tutorial, we register a callback to CB_PRERENDER, which is when we call displayTextScreen2 to display the actual text. We also get the Text interface. Then, during the prerender callback, we call displayTextScreen2, which will display the text we want on the screen.

// Discretion Tutorial, goals:
// 1) display text on the screen with the Text module 
// 2) show how to access shared per player data
// 3) listen and act upon key presses in Discretion
// 4) show how to register and use local per player data.
// April 2007

#include "Module.h"
#include "Text/Text.h"
#include "Graphics/Graphics.h"

ModuleManager* mm = 0;
Text* t = 0;

void preRender(void* param)
{
	Point topLeft(400,200);

	t->displayTextScreen2("test string", "yellow", L_Chat, &topLeft);
}

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

	if (lm == LOADMODE_LoadInterfaces)
	{
		t = (Text*)mm->getInterface(__FILE__, I_TEXT);

		mm->regCallback(__FILE__,CB_PRERENDER, CB_PRERENDER_VERSION, &preRender);
	}

	return 0;
}

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

You can see how we could use the text module to display various strings at various layers. Running this code makes the module display the string "text string" at position 400, 200:

[[Image::Ss1.PNG]]


/**
  * Get data that was shared by other modules with registerSharedData
  * @param pid the player's id we're concerned with
  * @param dataName the unique name of the data we want
  * @return a void* to the data we want. See the module documentation for the correct name and type
  */
void* (*getSharedData)(int pid, const char* dataName);

The parameter dataName refers to the name of the data, in this case "name", "squad", and "freq". There currently isn't a global list of common shared data anywhere, but such a list probably will be made at some point.

We can get the ticked player's id number using the PlayerList interface.

/**
 * Get the Pid of the currently targeted player
 * @return the pid of the player currently "ticked" in the player list box
 */
int (*getTargetPID)();

Now once we get the shared data, we receive a void*, which is a pointer that can point to any type of data. Again, a global list of common shared data types isn't yet available, so trust me that getting "name" will give you data of type char*, "squad" will get you data of type char*, and "freq" gets you data of type int*. With a pointer to the data you can modify the data, and this is the mechanism for doing that. You should cast the void* to the proper type of pointer, then use that pointer to get/set the data as one would with a normal pointer.

So in our tutorial, in our prerender callback, we get the ticked player's id from the playerlist, use that with PerPlayerData to get the name of the ticked player, and display that on the screen using the Text Module.

// Discretion Tutorial, goals:
// 1) display text on the screen with the Text module 
// 2) show how to access shared per player data
// 3) listen and act upon key presses in Discretion
// 4) show how to register and use local per player data.
// April 2007

#include "Module.h"
#include "Text/Text.h"
#include "Graphics/Graphics.h"
#include "PerPlayerData/PerPlayerData.h"
#include "PlayerList/PlayerList.h"

ModuleManager* mm = 0;
PerPlayerData* ppd = 0;
PlayerList* pl = 0;
Text* t = 0;

void preRender(void* param)
{
	Point topLeft(400,200);
	int ticked_pid = pl->getTargetPID();
	char* playerName = (char*)ppd->getSharedData(ticked_pid, "name");

	t->displayTextScreen2(playerName, "yellow", L_Chat, &topLeft);
}

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

	if (lm == LOADMODE_LoadInterfaces)
	{
		t = (Text*)mm->getInterface(__FILE__, I_TEXT);
		pl = (PlayerList*)mm->getInterface(__FILE__, I_PLAYERLIST);
		ppd = (PerPlayerData*)mm->getInterface(__FILE__, I_PERPLAYERDATA);

		mm->regCallback(__FILE__,CB_PRERENDER, CB_PRERENDER_VERSION, &preRender);
	}

	return 0;
}

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

When we run this code (you'll need to run a Discretion-compatible ASSS server to get a name), we get the expected result:

[[Image::Ss2.PNG]]

Now, in the tutorial, we will react to when the user presses alt to cycle through the name, squad, and freq. First, we modify our conf file to specify what keys correspond to our action. Throughout the code (and conf files) we refer to our action with a string. Let's use "cycle_data" as the name for our action. Then, we use the Controls module to specify a function to call when the key(s) for "cycle_data" are pressed. In this function, we will change the type of data we're displaying about the player in prerender.

So first let's edit conf/modules/Controls.conf. Open this file (in notepad, for example), and add "cycle_data" to the list of controls in Controls::Name. Also, we set Controls::cycle_data to RALT. In general, the names of the controls (like "RALT" for us) are equal to the SDLK_ constants for the keys, which you can google for. Alternately, if you're curious, examine initKeys() at the bottom of Modules/KeyControls/SDL Key Controls/SDL_KeyControls.cpp. Usually they're pretty obvious, as was the case with "RALT". If you wanted to allow either alt key to be pressed you could use "RALT, LALT", or if you wanted both, "RALT + LALT". There will eventually be a nice visual interface where the user can rebind all these keys to whatever values they want to use. Proceed to add the mentioned settings to the conf/modules/Controls.conf.

[[Image::Ss4.PNG]]

Now to figure out when the key is pressed, we need to tell the Controls module we're interested in a particular action (cycle_data). We first need to get the controls interface, and then register the callback CB_CONTROLSLOADED, which is when we should register key events.

// The controls loaded callback, parameter is null
// this callback occurs when we have loaded the controls bindings, at this point you can call regControlEvent
#define CB_CONTROLSLOADED			"CB_CONTROLSLOADED"
#define CB_CONTROLSLOADED_VERSION	"1"

In that callback, we use regControlEvent from the Controls module to tell the Controls module what function to call when your action occurs (the user presses alt).

/**
 * tell the Controls modules you want a function to be called when a certain control is pressed,
 * down is true iff we want the function to be called when the key is pressed down, if it’s false it will be
 * called iff the key combo is lifted up.
 *
 * call this function in your handler for the CB_CONTROLSLOADED callback
 *
 * @param keyString the keyString corresponding to the event, as defined in the settings (like "repel")
 * @param funcToCall the function to call when the event occurs, down = true iff we're pressing the key combination
 */
void (*regControlEvent)(const char* controlString, void (*funcToCall)(bool down, const char* controlString));

To do this, we need a function to call when the control is pressed down or released. The type of the function is void function taking parameters (bool down, const char* controlString), so we add such a function to our module (we named it cycleDataPressed). When this function is called, we change a global variable in our module which changes what we display.

Note that once we #include "Controls/Controls.h", we need to add Modules/Shared/StringManip.cpp to our project to avoid unresolved external symbol linker errors. This is done by going to File -> Add Existing Item, then selecting Modules/Shared/StringManip.cpp.

[[Image::Ss4.PNG]]

The resultant code should now be:


// Discretion Tutorial, goals:
// 1) display text on the screen with the Text module 
// 2) show how to access shared per player data
// 3) listen and act upon key presses in Discretion
// 4) show how to register and use local per player data.
// April 2007

#include "Module.h"
#include "Text/Text.h"
#include "Graphics/Graphics.h"
#include "PerPlayerData/PerPlayerData.h"
#include "PlayerList/PlayerList.h"
#include "Controls/Controls.h"

ModuleManager* mm = 0;
PerPlayerData* ppd = 0;
PlayerList* pl = 0;
Text* t = 0;
Controls* c = 0;

// 0 = name, 1 = squad, 2 = freq
int dataState = 0;

void cycleDataPressed(bool down, const char* controlString)
{
	// if the key is pressed down (rather than released)
	if (down)
		dataState = (dataState + 1) % 3;
}

// callback below
void keyControlsLoaded(void* param)
{
	c->regControlEvent("cycle_data",cycleDataPressed);
}

void preRender(void* param)
{
	Point topLeft(400,200);
	int ticked_pid = pl->getTargetPID();
	
	char buf[128];

	if (dataState == 0)
	{
		char* playerName = (char*)ppd->getSharedData(ticked_pid, "name");
		snprintf(buf,sizeof(buf),"Name: %s",playerName);
	}
	else if (dataState == 1)
	{
		char* squadName = (char*)ppd->getSharedData(ticked_pid, "squad");
		snprintf(buf,sizeof(buf),"Squad: %s",squadName);
	}
	else 
	{
		int freq = *(int*)ppd->getSharedData(ticked_pid, "freq");
		snprintf(buf,sizeof(buf),"Freq: %i",freq);
	}

	t->displayTextScreen2(buf, "yellow", L_Chat, &topLeft);
}

// interface functions below
void* getClassInstance(void* _mm, LoadMode lm)
{
	mm = (ModuleManager*)_mm;

	if (lm == LOADMODE_LoadInterfaces)
	{
		t = (Text*)mm->getInterface(__FILE__, I_TEXT);
		pl = (PlayerList*)mm->getInterface(__FILE__, I_PLAYERLIST);
		ppd = (PerPlayerData*)mm->getInterface(__FILE__, I_PERPLAYERDATA);
		c = (Controls*)mm->getInterface(__FILE__, I_CONTROLS);

		mm->regCallback(__FILE__,CB_PRERENDER, CB_PRERENDER_VERSION, &preRender);
		mm->regCallback(__FILE__,CB_CONTROLSLOADED, CB_CONTROLSLOADED_VERSION, &keyControlsLoaded);
	}

	return 0;
}

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

With this module, when we press the right alt key (assuming you used RALT in the conf file), it should cycle through the ticked player's data:

[[Image::Ss5.PNG]]

Finally, let's use some local per player data to count how many times we've toggled the state for each player. Our data will keep track of the current information we're displaying for each player, as well as how many times alt was pressed for the player.

Each module is allowed to allocate one structure of per player data for local use. We are interested in the number of times the player presses alt, as well as the current data we're displaying. We also provide the PerPlayerData module with two functions, one to initialize the data, and one to deinitialize the data. You can skip either of these functions. In our case, we want to set the initial number of times the user pressed alt to 0, and set the initial display to 0 (which is the name). The function we make has to accept a void* which we cast to be a pointer to our structure. Here is our structure and initialization function:


struct MyPlayerData
{
	int dataState;
	int count;
};

void initMyPlayerData(void* param)
{
	MyPlayerData* mpd = (MyPlayerData*)param;

	mpd->count = 0;
	mpd->dataState = 0;
}

We now have to tell the PerPlayerData module we want it to keep track of data for us. This is done during CB_LOADPERPLAYERDATA, which we listen for.

/*
When this callback occurs, you should register the per player data for your module
*/
#define CB_LOADPERPLAYERDATA		"Load Per Player Data"
#define CB_LOADPERPLAYERDATA_VERSION	"1"

During this callback we want to call the interface function in PerPlayerData, reserveData. This allocates some space for every player.

/**
  * Call this during CB_LOADPERPLAYERDATA to reserve some per player data for your module.
  * @param interfaceName the I_ interface name that you're using
  * @param size the number of bytes to allocate, use sizeof(YourDataStruct)
  * @param initer the function to call with the data as the argument after we allocate (can be null)
  * @param deiniter the function to call with the data as the argument before we free (can be null)
  * @return key the key to use in GetData when we want to change / use the data
  */
int (*reserveData)(const char* interfaceName, unsigned int size, 
								void (*initer)(void* data), void (*deiniter)(void* data));

In this function, the interface name is the name of the interface, as when we're registering callbacks. Using __FILE__ like we have been will work. The size parameter should be set to the size of our data structure, we'll use sizeof(MyPlayerData) here. This function returns a key which we keep track of in order to get the data later on. To get the data, we use the GetData interface function from PerPlayerData:

/**
  * Get the Data which we've previously reserved with ReserveData for a particular player
  * @param pid the player id of the player whose data we want (often sent in packets)
  * @param key the key returned by ReserveData which we use to tell your data apart from a different module's
  */
void* (*getData)(int pid, int key);

This returns a void* which we can cast to be a pointer to our structure. We put getData into our CB_PRERENDER to get the state and number of times alt was pressed for the targeted player, and display that. We also increment the number of times alt was pressed every time cycleDataPressed is called when a key is pressed down.

The final code is:

// Discretion Tutorial, goals:
// 1) display text on the screen with the Text module 
// 2) show how to access shared per player data
// 3) listen and act upon key presses in Discretion
// 4) show how to register and use local per player data.
// April 2007

#include "Module.h"
#include "Text/Text.h"
#include "Graphics/Graphics.h"
#include "PerPlayerData/PerPlayerData.h"
#include "PlayerList/PlayerList.h"
#include "Controls/Controls.h"

ModuleManager* mm = 0;
PerPlayerData* ppd = 0;
PlayerList* pl = 0;
Text* t = 0;
Controls* c = 0;

int myPlayerDataKey = 0;

struct MyPlayerData
{
	// 0 = name, 1 = squad, 2 = freq
	int dataState;
	int count;
};

void initMyPlayerData(void* param)
{
	MyPlayerData* mpd = (MyPlayerData*)param;

	mpd->count = 0;
	mpd->dataState = 0;
}

void cycleDataPressed(bool down, const char* controlString)
{
	// if the key is pressed down (rather than released)
	if (down)
	{
		int ticked_pid = pl->getTargetPID();

		MyPlayerData* data = (MyPlayerData*)ppd->getData(ticked_pid, myPlayerDataKey);

		data->dataState = (data->dataState + 1) % 3;
		++(data->count);
	}
}

// callbacks below
void keyControlsLoaded(void* param)
{
	c->regControlEvent("cycle_data",cycleDataPressed);
}

void loadPerPlayerData(void* param)
{
	myPlayerDataKey = ppd->reserveData(__FILE__, sizeof(MyPlayerData), initMyPlayerData, 0);
}

void preRender(void* param)
{
	Point topLeft(400,200);
	int ticked_pid = pl->getTargetPID();
	MyPlayerData* data = (MyPlayerData*)ppd->getData(ticked_pid, myPlayerDataKey);
	
	char buf[128];

	if (data->dataState == 0)
	{
		char* playerName = (char*)ppd->getSharedData(ticked_pid, "name");
		snprintf(buf,sizeof(buf),"Name: %s (Switched %i times)", playerName, data->count);
	}
	else if (data->dataState == 1)
	{
		char* squadName = (char*)ppd->getSharedData(ticked_pid, "squad");
		snprintf(buf,sizeof(buf),"Squad: %s  (Switched %i times)", squadName, data->count);
	}
	else 
	{
		int freq = *(int*)ppd->getSharedData(ticked_pid, "freq");
		snprintf(buf,sizeof(buf),"Freq: %i  (Switched %i times)", freq, data->count);
	}

	t->displayTextScreen2(buf, "yellow", L_Chat, &topLeft);
}

// interface functions below
void* getClassInstance(void* _mm, LoadMode lm)
{
	mm = (ModuleManager*)_mm;

	if (lm == LOADMODE_LoadInterfaces)
	{
		t = (Text*)mm->getInterface(__FILE__, I_TEXT);
		pl = (PlayerList*)mm->getInterface(__FILE__, I_PLAYERLIST);
		ppd = (PerPlayerData*)mm->getInterface(__FILE__, I_PERPLAYERDATA);
		c = (Controls*)mm->getInterface(__FILE__, I_CONTROLS);

		mm->regCallback(__FILE__,CB_PRERENDER, CB_PRERENDER_VERSION, &preRender);
		mm->regCallback(__FILE__,CB_CONTROLSLOADED, CB_CONTROLSLOADED_VERSION, &keyControlsLoaded);
		mm->regCallback(__FILE__,CB_LOADPERPLAYERDATA, CB_LOADPERPLAYERDATA_VERSION, &loadPerPlayerData);
	}

	return 0;
}

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

And we're done!

[[Image::Ss6.PNG]]