summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPhilipp Sehmisch <mana@crushnet.org>2010-07-09 15:21:50 +0200
committerPhilipp Sehmisch <mana@crushnet.org>2010-07-09 15:22:11 +0200
commit26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2 (patch)
tree6d7ea0ebe8be228a61315f72122eed3f2f995a0b /src
parent2627acefebc688d9d9733abe23ba5aae79f66ea0 (diff)
downloadmanaserv-26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2.tar.gz
manaserv-26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2.tar.xz
manaserv-26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2.zip
Added LUA script bindings for manipulating the specials available to a character.
Added script call for getting the cost of a special (recharge only for now) Deleting specials works server-sided but the client isn't informed about it properly. Specials without recharge cost don't appear for the player. Both of these features require an additional netcode message. Reviewed-by: Freeyorp
Diffstat (limited to 'src')
-rw-r--r--src/account-server/character.hpp22
-rw-r--r--src/account-server/storage.cpp59
-rw-r--r--src/game-server/character.cpp38
-rw-r--r--src/game-server/character.hpp32
-rw-r--r--src/game-server/main-game.cpp3
-rw-r--r--src/game-server/statusmanager.cpp8
-rw-r--r--src/scripting/lua.cpp76
-rw-r--r--src/scripting/luascript.cpp11
-rw-r--r--src/scripting/luascript.hpp1
-rw-r--r--src/scripting/script.cpp30
-rw-r--r--src/scripting/script.hpp5
-rw-r--r--src/serialize/characterdata.hpp16
-rw-r--r--src/sql/mysql/createTables.sql15
-rw-r--r--src/sql/mysql/updates/update_8_to_9.sql16
-rw-r--r--src/sql/sqlite/createTables.sql14
-rw-r--r--src/sql/sqlite/updates/update_8_to_9.sql16
16 files changed, 332 insertions, 30 deletions
diff --git a/src/account-server/character.hpp b/src/account-server/character.hpp
index acb4704..d96413d 100644
--- a/src/account-server/character.hpp
+++ b/src/account-server/character.hpp
@@ -32,6 +32,9 @@
class Account;
class MessageIn;
+/** placeholder type needed for include compatibility with game server*/
+typedef void Special;
+
class Character
{
public:
@@ -155,6 +158,24 @@ class Character
{ mKillCount[monsterId] = kills; }
/**
+ * Get / Set specials
+ */
+ int getSpecialSize() const
+ { return mSpecials.size(); }
+
+ const std::map<int, Special*>::const_iterator getSpecialBegin() const
+ { return mSpecials.begin(); }
+
+ const std::map<int, Special*>::const_iterator getSpecialEnd() const
+ { return mSpecials.end(); }
+
+ void clearSpecials()
+ { mSpecials.clear(); }
+
+ void giveSpecial(int id)
+ { mSpecials[id] = NULL; }
+
+ /**
* Gets the Id of the map that the character is on.
*/
int getMapId() const { return mMapId; }
@@ -212,6 +233,7 @@ class Character
std::map<int, int> mExperience; //!< Skill Experience.
std::map<int, int> mStatusEffects; //!< Status Effects
std::map<int, int> mKillCount; //!< Kill Count
+ std::map<int, Special*> mSpecials;
unsigned short mMapId; //!< Map the being is on.
unsigned char mGender; //!< Gender of the being.
unsigned char mHairStyle; //!< Hair style of the being.
diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp
index 4828e05..c104b4f 100644
--- a/src/account-server/storage.cpp
+++ b/src/account-server/storage.cpp
@@ -40,7 +40,7 @@ static const char *DEFAULT_ITEM_FILE = "items.xml";
// defines the supported db version
static const char *DB_VERSION_PARAMETER = "database_version";
-static const char *SUPPORTED_DB_VERSION = "8";
+static const char *SUPPORTED_DB_VERSION = "9";
/*
* MySQL specificities:
@@ -72,6 +72,7 @@ static const char *CHARACTERS_TBL_NAME = "mana_characters";
static const char *CHAR_SKILLS_TBL_NAME = "mana_char_skills";
static const char *CHAR_STATUS_EFFECTS_TBL_NAME = "mana_char_status_effects";
static const char *CHAR_KILL_COUNT_TBL_NAME = "mana_char_kill_stats";
+static const char *CHAR_SPECIALS_TBL_NAME = "mana_char_specials";
static const char *INVENTORIES_TBL_NAME = "mana_inventories";
static const char *ITEMS_TBL_NAME = "mana_items";
static const char *GUILDS_TBL_NAME = "mana_guilds";
@@ -433,11 +434,10 @@ Character *Storage::getCharacterBySQL(Account *owner)
// Load the kill stats
s.clear();
s.str("");
- // Load the status effect
s << "select monster_id, kills FROM " << CHAR_KILL_COUNT_TBL_NAME
<< " WHERE char_id = " << character->getDatabaseID();
const dal::RecordSet &killsInfo = mDb->execSql(s.str());
- if (!statusInfo.isEmpty())
+ if (!killsInfo.isEmpty())
{
const unsigned int nRows = killsInfo.rows();
for (unsigned int row = 0; row < nRows; row++)
@@ -447,6 +447,20 @@ Character *Storage::getCharacterBySQL(Account *owner)
toUint(killsInfo(row, 1))); // Kills
}
}
+ // load the special status
+ s.clear();
+ s.str("");
+ s << "select special_id FROM " << CHAR_SPECIALS_TBL_NAME
+ << " WHERE char_id = " << character->getDatabaseID();
+ const dal::RecordSet &specialsInfo = mDb->execSql(s.str());
+ if (!specialsInfo.isEmpty())
+ {
+ const unsigned int nRows = specialsInfo.rows();
+ for (unsigned int row = 0; row < nRows; row++)
+ {
+ character->giveSpecial(toUint(specialsInfo(row, 0)));
+ }
+ }
}
catch (const dal::DbSqlQueryExecFailure &e)
{
@@ -729,9 +743,44 @@ bool Storage::updateCharacter(Character *character,
{
mDb->rollbackTransaction();
}
- LOG_ERROR("(DALStorage::updateCharacter #2) SQL query failure: " << e.what());
+ LOG_ERROR("(DALStorage::updateCharacter #3) SQL query failure: " << e.what());
+ return false;
+ }
+ /**
+ * Character's special actions
+ */
+ try
+ {
+ // out with the old
+ std::ostringstream deleteSql("");
+ std::ostringstream insertSql;
+ deleteSql << "DELETE FROM " << CHAR_SPECIALS_TBL_NAME
+ << " WHERE char_id='" << character->getDatabaseID() << "';";
+ mDb->execSql(deleteSql.str());
+ // in with the new
+ std::map<int, Special*>::const_iterator special_it;
+ for (special_it = character->getSpecialBegin();
+ special_it != character->getSpecialEnd(); special_it++)
+ {
+ insertSql.str("");
+ insertSql << "INSERT INTO " << CHAR_SPECIALS_TBL_NAME
+ << " (char_id, special_id) VALUES ("
+ << " '" << character->getDatabaseID() << "',"
+ << " '" << special_it->first << "');";
+ mDb->execSql(insertSql.str());
+ }
+ }
+ catch (const dal::DbSqlQueryExecFailure& e)
+ {
+ // TODO: throw an exception.
+ if (startTransaction)
+ {
+ mDb->rollbackTransaction();
+ }
+ LOG_ERROR("(DALStorage::updateCharacter #4) SQL query failure: " << e.what());
return false;
}
+
/**
* Character's inventory
*/
@@ -752,7 +801,7 @@ bool Storage::updateCharacter(Character *character,
{
mDb->rollbackTransaction();
}
- LOG_ERROR("(DALStorage::updateCharacter #3) SQL query failure: " << e.what());
+ LOG_ERROR("(DALStorage::updateCharacter #5) SQL query failure: " << e.what());
return false;
}
diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp
index 258a702..46ffa05 100644
--- a/src/game-server/character.cpp
+++ b/src/game-server/character.cpp
@@ -232,14 +232,7 @@ void Character::useSpecial(int id)
//tell script engine to cast the spell
special->currentMana = 0;
- Script *script = getMap()->getScript();
- if (script) {
- script->prepare("cast");
- script->push(this);
- script->push(id);
- script->execute();
- }
-
+ Script::perform_special_action(id, this);
mSpecialUpdateNeeded = true;
return;
}
@@ -661,14 +654,29 @@ void Character::giveSpecial(int id)
{
if (mSpecials.find(id) == mSpecials.end())
{
- // TODO: get the needed mana from a SpecialDB
- int neededMana;
- if (id == 1) neededMana = 10;
- if (id == 2) neededMana = 100;
- if (id == 3) neededMana = 1000;
-
- Special *s = new Special(neededMana);
+ Special *s = new Special();
+ Script::addDataToSpecial(id, s);
mSpecials[id] = s;
mSpecialUpdateNeeded = true;
}
}
+
+void Character::takeSpecial(int id)
+{
+ std::map<int, Special*>::iterator i = mSpecials.find(id);
+ if (i != mSpecials.end())
+ {
+ delete i->second;
+ mSpecials.erase(i);
+ mSpecialUpdateNeeded = true;
+ }
+}
+
+void Character::clearSpecials()
+{
+ for(std::map<int, Special*>::iterator i = mSpecials.begin(); i != mSpecials.end(); i++)
+ {
+ delete i->second;
+ }
+ mSpecials.clear();
+}
diff --git a/src/game-server/character.hpp b/src/game-server/character.hpp
index 7b74c08..fdee364 100644
--- a/src/game-server/character.hpp
+++ b/src/game-server/character.hpp
@@ -39,10 +39,9 @@ class Trade;
struct Special
{
- Special(int needed)
+ Special()
{
currentMana = 0;
- neededMana = needed;
}
int currentMana;
int neededMana;
@@ -95,6 +94,21 @@ class Character : public Being
void giveSpecial(int id);
/**
+ * Removes all specials from character
+ */
+ void clearSpecials();
+
+ /**
+ * Checks if a character knows a special action
+ */
+ bool hasSpecial(int id) { return mSpecials.find(id) != mSpecials.end(); }
+
+ /**
+ * Removes an available special action
+ */
+ void takeSpecial(int id);
+
+ /**
* Gets client computer.
*/
GameClient *getClient() const
@@ -296,7 +310,7 @@ class Character : public Being
{ return mStatusEffects.end(); }
/**
- * used to serialized kill count
+ * used to serialize kill count
*/
int getKillCountSize() const
{ return mKillCount.size(); }
@@ -311,6 +325,18 @@ class Character : public Being
{ mKillCount[monsterId] = kills; }
/**
+ * used to serialize specials
+ */
+ int getSpecialSize() const
+ { return mSpecials.size(); }
+
+ const std::map<int, Special*>::const_iterator getSpecialBegin() const
+ { return mSpecials.begin(); }
+
+ const std::map<int, Special*>::const_iterator getSpecialEnd() const
+ { return mSpecials.end(); }
+
+ /**
* Gets total accumulated exp for skill
*/
int getExperience(int skill) const
diff --git a/src/game-server/main-game.cpp b/src/game-server/main-game.cpp
index d551035..0901ce1 100644
--- a/src/game-server/main-game.cpp
+++ b/src/game-server/main-game.cpp
@@ -69,6 +69,7 @@ using utils::Logger;
#define DEFAULT_STATUSDB_FILE "mana-status-effect.xml"
#define DEFAULT_PERMISSION_FILE "permissions.xml"
#define DEFAULT_GLOBAL_EVENT_SCRIPT_FILE "scripts/global_events.lua"
+#define DEFAULT_SPECIAL_ACTIONS_SCRIPT_FILE "scripts/special_actions.lua"
static int const WORLD_TICK_SKIP = 2; /** tolerance for lagging behind in world calculation) **/
@@ -186,6 +187,8 @@ void initialize()
PermissionManager::initialize(DEFAULT_PERMISSION_FILE);
// Initialize global event script
LuaScript::load_global_event_script(DEFAULT_GLOBAL_EVENT_SCRIPT_FILE);
+ // Initialize special action script
+ LuaScript::load_special_actions_script(DEFAULT_SPECIAL_ACTIONS_SCRIPT_FILE);
// --- Initialize the global handlers
// FIXME: Make the global handlers global vars or part of a bigger
diff --git a/src/game-server/statusmanager.cpp b/src/game-server/statusmanager.cpp
index 0e64df6..702fd10 100644
--- a/src/game-server/statusmanager.cpp
+++ b/src/game-server/statusmanager.cpp
@@ -30,8 +30,8 @@
#include <set>
#include <sstream>
-typedef std::map< int, StatusEffect * > StatusEffects;
-static StatusEffects statusEffects;
+typedef std::map< int, StatusEffect * > StatusEffectsMap;
+static StatusEffectsMap statusEffects;
static std::string statusReferenceFile;
void StatusManager::initialize(const std::string &file)
@@ -108,7 +108,7 @@ void StatusManager::reload()
void StatusManager::deinitialize()
{
- for (StatusEffects::iterator i = statusEffects.begin(),
+ for (StatusEffectsMap::iterator i = statusEffects.begin(),
i_end = statusEffects.end(); i != i_end; ++i)
{
delete i->second;
@@ -118,7 +118,7 @@ void StatusManager::deinitialize()
StatusEffect *StatusManager::getStatus(int statusId)
{
- StatusEffects::const_iterator i = statusEffects.find(statusId);
+ StatusEffectsMap::const_iterator i = statusEffects.find(statusId);
return i != statusEffects.end() ? i->second : NULL;
}
diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp
index ca91624..2d9562e 100644
--- a/src/scripting/lua.cpp
+++ b/src/scripting/lua.cpp
@@ -1454,6 +1454,79 @@ static int chr_get_kill_count(lua_State *s)
/**
+ * Enables a special for a character
+ * mana.chr_give_special (character, special)
+ */
+static int chr_give_special(lua_State *s)
+{
+ // cost_type is ignored until we have more than one cost type
+ Character *c = getCharacter(s, 1);
+ if (!c)
+ {
+ raiseScriptError(s, "chr_give_special called for nonexistent character.");
+ return 0;
+ }
+ if (!lua_isnumber(s, 2))
+ {
+ raiseScriptError(s, "chr_give_special called with incorect parameters");
+ return 0;
+ }
+ int special = lua_tointeger(s, 2);
+
+ c->giveSpecial(special);
+ return 0;
+}
+
+/**
+ * Checks if a character has a special and returns true or false
+ * mana.chr_has_special (character, special)
+ */
+static int chr_has_special(lua_State *s)
+{
+ Character *c = getCharacter(s, 1);
+ if (!c)
+ {
+ raiseScriptError(s, "chr_has_special called for nonexistent character.");
+ return 0;
+ }
+ if (!lua_isnumber(s, 2))
+ {
+ raiseScriptError(s, "chr_has_special called with incorect parameters");
+ return 0;
+ }
+ int special = lua_tointeger(s, 2);
+
+ lua_pushboolean(s, c->hasSpecial(special));
+ return 1;
+}
+
+/**
+ * Removes a special from a character
+ * mana.chr_take_special (character, special)
+ */
+static int chr_take_special(lua_State *s)
+{
+ Character *c = getCharacter(s, 1);
+ if (!c)
+ {
+ raiseScriptError(s, "chr_take_special called for nonexistent character.");
+ return 0;
+ }
+ if (!lua_isnumber(s, 2))
+ {
+ raiseScriptError(s, "chr_take_special called with incorect parameters");
+ return 0;
+ }
+ int special = lua_tointeger(s, 2);
+
+ lua_pushboolean(s, c->hasSpecial(special));
+ c->takeSpecial(special);
+ return 1;
+}
+
+
+
+/**
* Returns the rights level of a character.
* mana.chr_get_rights (being)
*/
@@ -1650,6 +1723,9 @@ LuaScript::LuaScript():
{ "chr_set_hair_color", &chr_set_hair_color },
{ "chr_get_hair_color", &chr_get_hair_color },
{ "chr_get_kill_count", &chr_get_kill_count },
+ { "chr_give_special", &chr_give_special },
+ { "chr_has_special", &chr_has_special },
+ { "chr_take_special", &chr_take_special },
{ "exp_for_level", &exp_for_level },
{ "monster_create", &monster_create },
{ "monster_load_script", &monster_load_script },
diff --git a/src/scripting/luascript.cpp b/src/scripting/luascript.cpp
index b48ec51..33145b5 100644
--- a/src/scripting/luascript.cpp
+++ b/src/scripting/luascript.cpp
@@ -167,3 +167,14 @@ bool LuaScript::load_global_event_script(const std::string &file)
}
return true;
}
+
+bool LuaScript::load_special_actions_script(const std::string &file)
+{
+ Script::special_actions_script = new LuaScript();
+ if (!Script::special_actions_script->loadFile(file))
+ {
+ Script::special_actions_script = NULL;
+ return false;
+ }
+ return true;
+}
diff --git a/src/scripting/luascript.hpp b/src/scripting/luascript.hpp
index 315c759..94851ac 100644
--- a/src/scripting/luascript.hpp
+++ b/src/scripting/luascript.hpp
@@ -71,6 +71,7 @@ class LuaScript: public Script
* Loads the global event script file
*/
static bool load_global_event_script(const std::string &file);
+ static bool load_special_actions_script(const std::string &file);
private:
diff --git a/src/scripting/script.cpp b/src/scripting/script.cpp
index fe3c5d8..359e94b 100644
--- a/src/scripting/script.cpp
+++ b/src/scripting/script.cpp
@@ -33,6 +33,7 @@ typedef std::map< std::string, Script::Factory > Engines;
static Engines *engines = NULL;
Script *Script::global_event_script = NULL;
+Script *Script::special_actions_script = NULL;
Script::Script():
mMap(NULL),
@@ -122,3 +123,32 @@ bool Script::execute_global_event_function(const std::string &function, Being* o
}
return isScriptHandled;
}
+
+
+void Script::addDataToSpecial(int id, Special* special)
+{
+ /* currently only gets the recharge cost.
+ TODO: get any other info in a similar way, but
+ first we have to agree on what other
+ info we actually want to provide.
+ */
+ Script *script = Script::special_actions_script;
+ script->prepare("get_special_recharge_cost");
+ script->push(id);
+ int scriptReturn = script->execute();
+ special->neededMana = scriptReturn;
+
+}
+
+bool Script::perform_special_action(int specialId, Being* caster)
+{
+ Script *script = Script::special_actions_script;
+ if (script)
+ {
+ script->prepare("use_special");
+ script->push(caster);
+ script->push(specialId);
+ script->execute();
+ }
+ return true;
+}
diff --git a/src/scripting/script.hpp b/src/scripting/script.hpp
index 96df085..0e87415 100644
--- a/src/scripting/script.hpp
+++ b/src/scripting/script.hpp
@@ -23,6 +23,7 @@
#include <string>
+#include "game-server/character.hpp"
#include "game-server/eventlistener.hpp"
class MapComposite;
@@ -135,11 +136,13 @@ class Script
* Runs a function from the global event script file
*/
static bool execute_global_event_function(const std::string &function, Being *obj);
-
+ static void addDataToSpecial(int specialId, Special *special);
+ static bool perform_special_action(int specialId, Being *caster);
protected:
static Script* global_event_script; // the global event script
+ static Script* special_actions_script; // the special actions script
std::string mScriptFile;
private:
diff --git a/src/serialize/characterdata.hpp b/src/serialize/characterdata.hpp
index 44e2c29..50acc8e 100644
--- a/src/serialize/characterdata.hpp
+++ b/src/serialize/characterdata.hpp
@@ -81,6 +81,14 @@ void serializeCharacterData(const T &data, MessageOut &msg)
msg.writeLong(kills_it->second);
}
+ // character specials
+ std::map<int, Special*>::const_iterator special_it;
+ msg.writeShort(data.getSpecialSize());
+ for (special_it = data.getSpecialBegin(); special_it != data.getSpecialEnd() ; special_it++)
+ {
+ msg.writeLong(special_it->first);
+ }
+
// inventory - must be last because size isn't transmitted
const Possessions &poss = data.getPossessions();
msg.writeLong(poss.money);
@@ -152,6 +160,14 @@ void deserializeCharacterData(T &data, MessageIn &msg)
data.setKillCount(monsterId, kills);
}
+ // character specials
+ int specialSize = msg.readShort();
+ data.clearSpecials();
+ for (int i = 0; i < specialSize; i++)
+ {
+ data.giveSpecial(msg.readLong());
+ }
+
// inventory - must be last because size isn't transmitted
Possessions &poss = data.getPossessions();
poss.money = msg.readLong();
diff --git a/src/sql/mysql/createTables.sql b/src/sql/mysql/createTables.sql
index 1f9bce3..a3b37ce 100644
--- a/src/sql/mysql/createTables.sql
+++ b/src/sql/mysql/createTables.sql
@@ -103,6 +103,19 @@ CREATE TABLE IF NOT EXISTS `mana_char_kill_stats`
) ENGINE=InnoDB
DEFAULT CHARSET=utf8;
+-- Create table 'mana_char_specials'
+
+CREATE TABLE mana_char_specials
+(
+ `char_id` int(10) unsigned NOT NULL,
+ `special_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`char_id`, `special_id`),
+ FOREIGN KEY (`char_id`)
+ REFERENCES `mana_characters` (`id`)
+ ON DELETE CASCADE
+) ENGINE=InnoDB
+DEFAULT CHARSET=utf8;
+
--
-- table: `mana_items`
@@ -383,7 +396,7 @@ AUTO_INCREMENT=0 ;
INSERT INTO mana_world_states VALUES('accountserver_startup',NULL,NULL, NOW());
INSERT INTO mana_world_states VALUES('accountserver_version',NULL,NULL, NOW());
-INSERT INTO mana_world_states VALUES('database_version', NULL,'8', NOW());
+INSERT INTO mana_world_states VALUES('database_version', NULL,'9', NOW());
-- all known transaction codes
diff --git a/src/sql/mysql/updates/update_8_to_9.sql b/src/sql/mysql/updates/update_8_to_9.sql
new file mode 100644
index 0000000..499cd73
--- /dev/null
+++ b/src/sql/mysql/updates/update_8_to_9.sql
@@ -0,0 +1,16 @@
+-- Create table 'mana_char_specials'
+
+CREATE TABLE mana_char_specials
+(
+ `char_id` int(10) unsigned NOT NULL,
+ `special_id` int(10) unsigned NOT NULL,
+ PRIMARY KEY (`char_id`, `special_id`),
+ FOREIGN KEY (`char_id`)
+ REFERENCES `mana_characters` (`id`)
+ ON DELETE CASCADE
+) ENGINE=InnoDB
+DEFAULT CHARSET=utf8;
+
+
+UPDATE mana_world_states SET value = '9' WHERE state_name = 'database_version';
+
diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql
index f4b4b1d..bc5eefb 100644
--- a/src/sql/sqlite/createTables.sql
+++ b/src/sql/sqlite/createTables.sql
@@ -110,6 +110,18 @@ CREATE INDEX mana_char_kill_stats_char on mana_char_status_effects ( char_id );
-----------------------------------------------------------------------------
+CREATE TABLE mana_char_specials
+(
+ char_id INTEGER NOT NULL,
+ special_id INTEGER NOT NULL,
+ PRIMARY KEY (char_id, special_id),
+ FOREIGN KEY (char_id) REFERENCES mana_characters(id)
+);
+
+CREATE INDEX mana_char_specials_char on mana_char_specials ( char_id );
+
+-----------------------------------------------------------------------------
+
CREATE TABLE mana_items
(
id INTEGER PRIMARY KEY,
@@ -374,7 +386,7 @@ AS
INSERT INTO mana_world_states VALUES('accountserver_startup',NULL,NULL, strftime('%s','now'));
INSERT INTO mana_world_states VALUES('accountserver_version',NULL,NULL, strftime('%s','now'));
-INSERT INTO mana_world_states VALUES('database_version', NULL,'8', strftime('%s','now'));
+INSERT INTO mana_world_states VALUES('database_version', NULL,'9', strftime('%s','now'));
-- all known transaction codes
diff --git a/src/sql/sqlite/updates/update_8_to_9.sql b/src/sql/sqlite/updates/update_8_to_9.sql
new file mode 100644
index 0000000..efc0d02
--- /dev/null
+++ b/src/sql/sqlite/updates/update_8_to_9.sql
@@ -0,0 +1,16 @@
+CREATE TABLE mana_char_specials
+(
+ char_id INTEGER NOT NULL,
+ special_id INTEGER NOT NULL,
+ PRIMARY KEY (char_id, special_id),
+ FOREIGN KEY (char_id) REFERENCES mana_characters(id)
+);
+
+CREATE INDEX mana_char_specials_char on mana_char_specials ( char_id );
+
+-- update the database version, and set date of update
+UPDATE mana_world_states
+ SET value = '9',
+ moddate = strftime('%s','now')
+ WHERE state_name = 'database_version';
+