Writing Modules for Hyperspace

From ASSS Wiki
Revision as of 10:32, 9 May 2005 by Cyan~Fire (talk | contribs) (changing Category:Tutorial to Category:Guides...)
Jump to: navigation, search

Getting Started

The first thing you should do is check with Dr Brain to make sure someone else isn't already making a similar module. The best way to contact him is in SSCX-A Hyperspace via PM, ?message or with AIM/ICQ (check the Hyperspace news file for AIM name and ICQ number).

General Info

In general, hard coding values is frowned upon. Please try to use the arena and global configs wherever possible. Put a sensible default into the GetInt so that not every config entry needs to be present for the module to work.

Naming

Because of module naming conflicts, all official Hyperspace related modules are prefixed with hs_. So, for example, a kill spree module becomes hs_spree. This is not required for module submissions. You are free to name your module whatever you wish.

Style

There is a Hyperspace standard style for formatting code, but it is completely optional. You should make your interface headers easy to read, though.

Here is an example of the Hyperspace official style:

hs_twowords.h:

#ifndef HS_TWOWORDS_H
#define HS_TWOWORDS_H

#define I_HS_TWOWORDS "hs_twowords-1"

typedef struct Ihstwowords
{
	INTERFACE_HEAD_DECL

	int (*interfaceFunction)(Player *p);
} Ihstwowords;

#endif //HS_TWOWORDS_H

hs_twowords.c:

#include <string.h> //optional. others added as needed

#include "asss.h"
#include "hscore.h"
#include "hs_twowords.h"

//modules
local Imodman *mm;
local Ilogman *lm;
local Ichat *chat;
local Iconfig *cfg;
local Icmdman *cmd;
local Iplayerdata *pd;

//interface prototypes
local int interfaceFunction(Player *p);

local helptext_t twoWordsHelp =
"Targets: player\n"
"Args: none\n"
"Who knows? This is just a template file.\n";

local void twoWordsCommand(const char *command, const char *params, Player *p, const Target *target)
{
	if (target->type == T_PLAYER) //private command
	{
		Player *t = target->u.p;

		//do something
	}
	else //not private
	{
		chat->SendMessage(p, "You must target a player.");
	}
}

local int interfaceFunction(Player *p)
{
	if (something) //space between if and ()
	{
		//do something
	}
}

local Ihstwowords interface =
{
	INTERFACE_HEAD_INIT(I_HS_TWOWORDS, "hs_twowords")
	interfaceFunction
};

EXPORT int MM_hs_twowords(int action, Imodman *_mm, Arena *arena)
{
	if (action == MM_LOAD)
	{
		mm = _mm;

		lm = mm->GetInterface(I_LOGMAN, ALLARENAS);
		chat = mm->GetInterface(I_CHAT, ALLARENAS);
		cfg = mm->GetInterface(I_CONFIG, ALLARENAS);
		cmd = mm->GetInterface(I_CMDMAN, ALLARENAS);
		pd = mm->GetInterface(I_PLAYERDATA, ALLARENAS);

		if (!lm || !chat || !cfg || !cmd || !pd)
		{
			mm->ReleaseInterface(lm);
			mm->ReleaseInterface(chat);
			mm->ReleaseInterface(cfg);
			mm->ReleaseInterface(cmd);
			mm->ReleaseInterface(pd);

			return MM_FAIL;
		}

		mm->RegInterface(&interface, ALLARENAS);

		cmd->AddCommand("twowords", twoWordsCommand, ALLARENAS, twoWordsHelp);

		return MM_OK;
	}
	else if (action == MM_UNLOAD)
	{
		if (mm->UnregInterface(&interface, ALLARENAS))
		{
			return MM_FAIL;
		}

		cmd->RemoveCommand("twowords", twoWordsCommand, ALLARENAS);

		mm->ReleaseInterface(lm);
		mm->ReleaseInterface(chat);
		mm->ReleaseInterface(cfg);
		mm->ReleaseInterface(cmd);
		mm->ReleaseInterface(pd);

		return MM_OK;
	}
	return MM_FAIL;
}

Required Files

Downloadable versions of the files can be found here.

Here is hscore.h:

#include "hscore_types.h"

#include "hscore_money.h"
#include "hscore_items.h"

Here is hscore_types.h:

#ifndef HSCORE_TYPES_H
#define HSCORE_TYPES_H

typedef enum EventAction
{
	//removes event->data amount of the items from the ship's inventory
	ACTION_REMOVE_ITEM = 0,

	//removes event->data amount of the item's ammo type from inventory
	ACTION_REMOVE_ITEM_AMMO,

	//sends prize #event->data to the player
	ACTION_PRIZE,

	//sets the item's inventory data to event->data. This is useful with
	//the "purchace" event.
	ACTION_SET_INVENTORY_DATA,

	//does a ++ on inventory data.
	ACTION_INCREMENT_INVENTORY_DATA,

	//does a -- on inventory data. A "datazero" event may be generated as a result.
	ACTION_DECREMENT_INVENTORY_DATA,

	//Specs the player.
	ACTION_SPEC,

	//sends a shipreset packet and reprizes all items (antideath, really)
	ACTION_SHIP_RESET

	//we need a lot more
} EventAction;

typedef struct Event
{
	char event[16]; //something like "death" or "datazero"
	EventAction action;

	int data; //action dependent

	char message[200]; //if == to "" then nothing will be sent.
} Event;

typedef struct Property
{
	char name[16];
	int value;
} Property;

typedef struct ItemType
{
	char name[32];
	int max; //maximum total of this item type on a ship before ?buy denies purchace

	int id; //MySQL use only
} ItemType;

typedef struct Item
{
	char name[16];
	char shortDesc[32]; //displayed inline in the ?buy menu
	char longDesc[200]; //displayed as part of ?iteminfo
	int buyPrice;
	int sellPrice;

	int expRequired; //requirement to own

	int shipsAllowed; //bit positions represent each ship. bit 0 = warbird.

	LinkedList propertyList;

	LinkedList eventList;

	ItemType *type1, *type2;
	int typeDelta1, typeDelta2;

	int max;

	//if changes to this item should be delayed until a complete save (like on exit).
	//This is a necessity when dealing with ammo. We don't want to update MySQL every
	//time a gun is fired.
	int delayStatusWrite;

	struct Item *ammo; //can be NULL, only for use by events.
	int ammoID; //used for post processing ONLY

	int id; //MySQL use only
} Item;

typedef struct InventoryEntry
{
	Item *item;
	int count;

	int data; //persistent int for use by the event system.
} InventoryEntry;

typedef struct ShipHull
{
	LinkedList inventoryEntryList;

	//NOTE: no need for ship #, as it's defined by the array index (when loaded by hscore_database)

	//if we compile a hashmap of properties, it can go in here.

	int id; //MySQL use only
} ShipHull;

typedef struct Category
{
	char name[32]; //displayed on ?buy
	char description[64]; //displayed inline on the ?buy menu

	LinkedList itemList; //a list of member items that are displayed

	int id; //mysql use
} Category;

typedef struct Store
{
	char name[32]; //displayed in ?buysell location errors
	char description[200]; //displayed in ?storeinfo
	char region[16]; //region that defines the store

	LinkedList itemList; //a list of items that can be purchaced here

	int id; //mysql use
} Store;

#endif //HSCORE_TYPES_H

Here is the hscore_money.h:

#ifndef HSCORE_MONEY_H
#define HSCORE_MONEY_H

#define I_HSCORE_MONEY "hscore_money-1"

typedef enum MoneyType
{
	//for /?give
	MONEY_TYPE_GIVE = 0,

	//for /?grant
	MONEY_TYPE_GRANT,

	//for ?buy and ?sell
	MONEY_TYPE_BUYSELL,

	//for money from kills
	MONEY_TYPE_KILL,

	//for money from flag games
	MONEY_TYPE_FLAG,

	//for money from soccer games
	MONEY_TYPE_BALL,

	//for money from module driven events
	MONEY_TYPE_EVENT
} MoneyType;

#define MONEY_TYPE_COUNT 7

typedef struct Ihscoremoney
{
	INTERFACE_HEAD_DECL

	void (*giveMoney)(Player *p, int amount, MoneyType type);
	void (*setMoney)(Player *p, int amount, MoneyType type); //beware. know what you're doing

	int (*getMoney)(Player *p);
	int (*getMoneyType)(Player *p, MoneyType type); //used only for /?money -d

	void (*giveExp)(Player *p, int amount);
	void (*setExp)(Player *p, int amount); //beware. know what you're doing

	int (*getExp)(Player *p);
} Ihscoremoney;

#endif //HSCORE_MONEY_H

Here is hscore_item.h:

#ifndef HSCORE_ITEMS_H
#define HSCORE_ITEMS_H

#define I_HSCORE_ITEMS "hscore_items-3"

typedef struct Ihscoreitems
{
	INTERFACE_HEAD_DECL

	int (*getItemCount)(Player *p, Item *item, int ship);
	void (*addItem)(Player *p, Item *item, int ship, int amount);

	Item * (*getItemByName)(const char *name, Arena *arena);

	int (*getPropertySum)(Player *p, int ship, const char *prop); //properties ARE case sensitive

	void (*triggerEvent)(Player *p, int ship, const char *event);
	void (*triggerEventOnItem)(Player *p, Item *item, int ship, const char *event);

	int (*getFreeItemTypeSpots)(Player *p, ItemType *type, int ship);

	//more required, i'm sure
} Ihscoreitems;

#endif //HSCORE_ITEMS_H

Optional Files

Here is hscore_moneystub.c:

#include "asss.h"
#include "hscore.h"

//modules
local Imodman *mm;
local Ilogman *lm;
local Ichat *chat;
local Iplayerdata *pd;

//interface prototypes
local void giveMoney(Player *p, int amount, MoneyType type);
local void setMoney(Player *p, int amount, MoneyType type);
local int getMoney(Player *p);
local int getMoneyType(Player *p, MoneyType type);
local void giveExp(Player *p, int amount);
local void setExp(Player *p, int amount);
local int getExp(Player *p);

local void giveMoney(Player *p, int amount, MoneyType type)
{
	chat->SendMessage(p, "hscore_moneystub.c: giveMoney(%i) called.", amount);
}

local void setMoney(Player *p, int amount, MoneyType type)
{
	chat->SendMessage(p, "setMoney should not be used.");
}

local int getMoney(Player *p)
{
	return 0; //change for testing
}

local int getMoneyType(Player *p, MoneyType type)
{
	chat->SendMessage(p, "getMoneyType should not be used.");
	return 0;
}

local void giveExp(Player *p, int amount)
{
	chat->SendMessage(p, "hscore_moneystub.c: giveExp(%i) called.", amount);
}

local void setExp(Player *p, int amount)
{
	chat->SendMessage(p, "setExp should not be used.");
}

local int getExp(Player *p)
{
	return 0; //change for testing
}

local Ihscoremoney interface =
{
	INTERFACE_HEAD_INIT(I_HSCORE_MONEY, "hscore_moneystub")
	giveMoney, setMoney, getMoney, getMoneyType,
	giveExp, setExp, getExp,
};

EXPORT int MM_hscore_moneystub(int action, Imodman *_mm, Arena *arena)
{
	if (action == MM_LOAD)
	{
		mm = _mm;

		lm = mm->GetInterface(I_LOGMAN, ALLARENAS);
		chat = mm->GetInterface(I_CHAT, ALLARENAS);
		pd = mm->GetInterface(I_PLAYERDATA, ALLARENAS);

		if (!lm || !chat || !pd)
		{
			mm->ReleaseInterface(lm);
			mm->ReleaseInterface(chat);
			mm->ReleaseInterface(pd);

			return MM_FAIL;
		}

		mm->RegInterface(&interface, ALLARENAS);

		return MM_OK;
	}
	else if (action == MM_UNLOAD)
	{
		if (mm->UnregInterface(&interface, ALLARENAS))
		{
			return MM_FAIL;
		}

		mm->ReleaseInterface(lm);
		mm->ReleaseInterface(chat);
		mm->ReleaseInterface(pd);

		return MM_OK;
	}
	return MM_FAIL;
}

Here is hscore_itemsstub.c:

#include "asss.h"
#include "hscore.h"

//modules
local Imodman *mm;
local Ilogman *lm;
local Ichat *chat;
local Iplayerdata *pd;

//interface prototypes
local int getItemCount(Player *p, Item *item, int ship);
local void addItem(Player *p, Item *item, int ship, int amount);
local Item * getItemByName(const char *name, Arena *arena);
local int getPropertySum(Player *p, int ship, const char *prop);
local void triggerEvent(Player *p, int ship, const char *event);
local void triggerEventOnItem(Player *p, Item *item, int ship, const char *event);
local int getFreeItemTypeSpots(Player *p, ItemType *type, int ship);


local int getItemCount(Player *p, Item *item, int ship)
{
	chat->SendMessage(p, "getItemCount should not be used by non-core modules!");

	return 0;
}

local void addItem(Player *p, Item *item, int ship, int amount)
{
	chat->SendMessage(p, "addItem should not be used by non-core modules!");
}

local Item * getItemByName(const char *name, Arena *arena)
{
	chat->SendArenaMessage(arena, "Tried to getItemByName(%s)", name);

	return NULL;
}

local int getPropertySum(Player *p, int ship, const char *propString)
{
	if (propString == NULL)
	{
		lm->LogP(L_ERROR, "hscore_itemsstub", p, "asked to get props for NULL string.");
		return 0;
	}

	if (ship < 0 || 7 < ship)
	{
		lm->LogP(L_ERROR, "hscore_itemsstub", p, "asked to get props on ship %i", ship);
		return 0;
	}

	//change this value for testing
	return 0;
}

local void triggerEvent(Player *p, int ship, const char *event)
{
	if (ship < 0 || 7 < ship)
	{
		lm->LogP(L_ERROR, "hscore_itemsstub", p, "asked to get props on ship %i", ship);
	}

	if (event == NULL)
	{
		lm->LogP(L_ERROR, "hscore_itemsstub", p, "asked to trigger event with NULL string.");
		return;
	}

	chat->SendMessage(p, "triggered event %s!", event);
}

local void triggerEventOnItem(Player *p, Item *item, int ship, const char *event)
{
	chat->SendMessage(p, "triggerEventOnItem should not be used by non-core modules!");
}

local int getFreeItemTypeSpots(Player *p, ItemType *type, int ship)
{
	chat->SendMessage(p, "getFreeItemTypeSpots should not be used by non-core modules!");
	return 0;
}

local Ihscoreitems interface =
{
	INTERFACE_HEAD_INIT(I_HSCORE_ITEMS, "hscore_itemsstub")
	getItemCount, addItem, getItemByName, getPropertySum,
	triggerEvent, triggerEventOnItem, getFreeItemTypeSpots,
};

EXPORT int MM_hscore_itemsstub(int action, Imodman *_mm, Arena *arena)
{
	if (action == MM_LOAD)
	{
		mm = _mm;

		lm = mm->GetInterface(I_LOGMAN, ALLARENAS);
		chat = mm->GetInterface(I_CHAT, ALLARENAS);
		pd = mm->GetInterface(I_PLAYERDATA, ALLARENAS);

		if (!lm || !chat || !pd)
		{
			mm->ReleaseInterface(lm);
			mm->ReleaseInterface(chat);
			mm->ReleaseInterface(pd);

			return MM_FAIL;
		}

		mm->RegInterface(&interface, ALLARENAS);

		return MM_OK;
	}
	else if (action == MM_UNLOAD)
	{
		if (mm->UnregInterface(&interface, ALLARENAS))
		{
			return MM_FAIL;
		}

		mm->ReleaseInterface(lm);
		mm->ReleaseInterface(chat);
		mm->ReleaseInterface(pd);

		return MM_OK;
	}
	return MM_FAIL;
}

Money and Experience

Money and experience points (aka exp) are what make the Hyperspace world go 'round. Nearly every Hyperspace module has some dealings with money/exp transactions of some kind.

Giving exp

money->giveExp(p, 10); //gives player p 10 exp

Giving money

The first thing to decide is which money type to give. This is mainly used for tracking purposes. If in doubt, choose MONEY_TYPE_EVENT.

  • MONEY_TYPE_GIVE is for money moving from one player to another.
  • MONEY_TYPE_GRANT is for money coming from a grant command
  • MONEY_TYPE_BUYSELL is for ?buying and ?selling.
  • MONEY_TYPE_KILL is for money gained from kills.
  • MONEY_TYPE_FLAG is for money earned from winning flag games or other flag rewards.
  • MONEY_TYPE_BALL is for money received from goals.
  • MONEY_TYPE_EVENT is for subarena events.

Then you can simply call:

money->giveMoney(p, 1000, MONEY_TYPE_EVENT); //gives player p $1000

Using Items

Items are the most important part of Hyperspace after money and exp. There are two parts of items that addon modules will use. These are checking for properties and calling events. While module writers cannot directly create items, they can request item modification/addition from subarena owners.

Item properties

Most items define properties that are used to change the player's ship. Properties are totalled for each ship and are checked per ship rather than per item. Most properties are related to the spawning module. However, any item can define custom properties. For example, if you have a subway system, a module could check if a player has at least one subway token property. You can check if the property sum on the player's current ship for "subway" is greater than 0, and if it is, let them ride.

item->getPropertySum(p, p->p_ship, "someproperty"); //Beware of spectators.

Item events

Items define actions that happen on certain events. Any module can trigger an event. Some modules, for example, have actions on the death event. Triggering an event is as simple as making a function call to the hscore_item module. If no items have actions on the called event, nothing will happen (so make sure you don't typo the event name). Following on the previous example, when a player rides the subway, then you can call the "ridesubway" event. The Subway Token items can define an action of self removal on that event.

item->triggerEvent(p, p->p_ship, "someevent"); //Beware of spectators.

Submission

Once your module is working properly and seems to have no bugs, then you can submit the source to Dr Brain or MichaelG for a source check. Once that's completed, the module will be compiled for the Hyperspace server and installed.