diff options
author | Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> | 2012-03-02 23:17:07 +0100 |
---|---|---|
committer | Thorbjørn Lindeijer <thorbjorn@lindeijer.nl> | 2012-03-03 21:24:45 +0100 |
commit | 84c87cc99be29a694f0ffe83ab7a06ae433bb0cd (patch) | |
tree | 9180b67970e62707ee0fe2fee19cbe2e0b829122 /src | |
parent | f872528771f0b71741fb36ddf70f2ae23f54c1e3 (diff) | |
download | manaserv-84c87cc99be29a694f0ffe83ab7a06ae433bb0cd.tar.gz manaserv-84c87cc99be29a694f0ffe83ab7a06ae433bb0cd.tar.bz2 manaserv-84c87cc99be29a694f0ffe83ab7a06ae433bb0cd.tar.xz manaserv-84c87cc99be29a694f0ffe83ab7a06ae433bb0cd.zip |
Use callbacks for items, monsters and status effects
Previously, global function names were defined in the respective XML
definitions of items, monsters and status effects. This was reasonable when
they all had the same state, but now they're sharing the single global
Lua state.
Now the Lua API provides access to the ItemClass, MonsterClass and
StatusEffect instances, on which callbacks for both standard and custom
events can be explicitly set.
Reviewed-by: Erik Schilling
Diffstat (limited to 'src')
-rw-r--r-- | src/game-server/item.cpp | 20 | ||||
-rw-r--r-- | src/game-server/item.h | 31 | ||||
-rw-r--r-- | src/game-server/itemmanager.cpp | 51 | ||||
-rw-r--r-- | src/game-server/monster.cpp | 34 | ||||
-rw-r--r-- | src/game-server/monster.h | 27 | ||||
-rw-r--r-- | src/game-server/monstermanager.cpp | 5 | ||||
-rw-r--r-- | src/game-server/statuseffect.cpp | 17 | ||||
-rw-r--r-- | src/game-server/statuseffect.h | 8 | ||||
-rw-r--r-- | src/game-server/statusmanager.cpp | 31 | ||||
-rw-r--r-- | src/game-server/statusmanager.h | 5 | ||||
-rw-r--r-- | src/scripting/lua.cpp | 111 | ||||
-rw-r--r-- | src/scripting/luautil.h | 23 | ||||
-rw-r--r-- | src/utils/string.h | 5 |
13 files changed, 241 insertions, 127 deletions
diff --git a/src/game-server/item.cpp b/src/game-server/item.cpp index 979a1bc3..888c90ad 100644 --- a/src/game-server/item.cpp +++ b/src/game-server/item.cpp @@ -64,13 +64,17 @@ ItemEffectScript::~ItemEffectScript() bool ItemEffectScript::apply(Being *itemUser) { - if (!mActivateFunctionName.empty()) + if (mActivateEventName.empty()) + return false; + + Script::Ref function = mItemClass->getEventCallback(mActivateEventName); + if (function.isValid()) { Script *script = ScriptManager::currentState(); script->setMap(itemUser->getMap()); - script->prepare(mActivateFunctionName); + script->prepare(function); script->push(itemUser); - script->push(mItemId); + script->push(mItemClass->getDatabaseID()); script->execute(); // TODO return depending on script execution success. return true; } @@ -79,13 +83,17 @@ bool ItemEffectScript::apply(Being *itemUser) void ItemEffectScript::dispell(Being *itemUser) { - if (!mDispellFunctionName.empty()) + if (mDispellEventName.empty()) + return; + + Script::Ref function = mItemClass->getEventCallback(mDispellEventName); + if (function.isValid()) { Script *script = ScriptManager::currentState(); script->setMap(itemUser->getMap()); - script->prepare(mDispellFunctionName); + script->prepare(function); script->push(itemUser); - script->push(mItemId); + script->push(mItemClass->getDatabaseID()); script->execute(); } } diff --git a/src/game-server/item.h b/src/game-server/item.h index 8cd3ce64..2677589f 100644 --- a/src/game-server/item.h +++ b/src/game-server/item.h @@ -24,8 +24,10 @@ #include <vector> #include "game-server/actor.h" +#include "scripting/script.h" class Being; +class ItemClass; // Indicates the equip slot "cost" to equip an item. struct ItemEquipRequirement { @@ -143,12 +145,12 @@ class ItemEffectConsumes : public ItemEffectInfo class ItemEffectScript : public ItemEffectInfo { public: - ItemEffectScript(int itemId, - const std::string& activateFunctionName, - const std::string& dispellFunctionName): - mItemId(itemId), - mActivateFunctionName(activateFunctionName), - mDispellFunctionName(dispellFunctionName) + ItemEffectScript(ItemClass *itemClass, + const std::string &activateEventName, + const std::string &dispellEventName): + mItemClass(itemClass), + mActivateEventName(activateEventName), + mDispellEventName(dispellEventName) {} ~ItemEffectScript(); @@ -157,9 +159,9 @@ class ItemEffectScript : public ItemEffectInfo void dispell(Being *itemUser); private: - int mItemId; - std::string mActivateFunctionName; - std::string mDispellFunctionName; + ItemClass *mItemClass; + std::string mActivateEventName; + std::string mDispellEventName; }; @@ -234,6 +236,12 @@ class ItemClass const ItemEquipRequirement &getItemEquipRequirement() const { return mEquipReq; } + void setEventCallback(const std::string &event, Script *script) + { script->assignCallback(mEventCallbacks[event]); } + + Script::Ref getEventCallback(const std::string &event) const + { return mEventCallbacks.value(event); } + private: /** * Add an effect to a trigger @@ -282,6 +290,11 @@ class ItemClass */ ItemEquipRequirement mEquipReq; + /** + * Named event callbacks. Can be used in custom item effects. + */ + utils::NameMap<Script::Ref> mEventCallbacks; + friend class ItemManager; }; diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index b334760a..8c74680e 100644 --- a/src/game-server/itemmanager.cpp +++ b/src/game-server/itemmanager.cpp @@ -404,50 +404,25 @@ void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) { item->addEffect(new ItemEffectConsumes, triggerTypes.first); } - else if (xmlStrEqual(subNode->name, BAD_CAST "script")) + else if (xmlStrEqual(subNode->name, BAD_CAST "scriptevent")) { - std::string activateFunctionName = XML::getProperty(subNode, - "function", - std::string()); - if (activateFunctionName.empty()) - { - LOG_WARN("Item Manager: Empty function definition " - "for script effect, skipping!"); - continue; - } - - std::string src = XML::getProperty(subNode, "src", std::string()); - if (src.empty()) - { - LOG_WARN("Item Manager: Empty src definition for script effect," - " skipping!"); - continue; - } - std::stringstream filename; - filename << "scripts/items/" << src; - if (!ResourceManager::exists(filename.str())) - { - LOG_WARN("Could not find script file \"" << filename.str() - << "\" for item #" << item->mDatabaseID); - continue; - } - - LOG_INFO("Loading item script: " << filename.str()); - Script *script = ScriptManager::currentState(); - if (!script->loadFile(filename.str())) + std::string activateEventName = XML::getProperty(subNode, + "activate", + std::string()); + if (activateEventName.empty()) { - LOG_WARN("Could not load script file \"" << filename.str() - << "\" for item #" << item->mDatabaseID); + LOG_WARN("Item Manager: Empty name for 'activate' item script " + "event, skipping effect!"); continue; } - std::string dispellFunctionName = XML::getProperty(subNode, - "dispell-function", - std::string()); + std::string dispellEventName = XML::getProperty(subNode, + "dispell", + std::string()); - item->addEffect(new ItemEffectScript(item->mDatabaseID, - activateFunctionName, - dispellFunctionName), + item->addEffect(new ItemEffectScript(item, + activateEventName, + dispellEventName), triggerTypes.first, triggerTypes.second); } diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp index 21eeea7c..b82f4638 100644 --- a/src/game-server/monster.cpp +++ b/src/game-server/monster.cpp @@ -28,7 +28,6 @@ #include "game-server/item.h" #include "game-server/mapcomposite.h" #include "game-server/state.h" -#include "scripting/script.h" #include "scripting/scriptmanager.h" #include "utils/logger.h" #include "utils/speedconv.h" @@ -140,16 +139,20 @@ void Monster::perform() int hit = performAttack(mTarget, dmg); - if (! mCurrentAttack->scriptFunction.empty() + if (! mCurrentAttack->scriptEvent.empty() && hit > -1) { - Script *script = ScriptManager::currentState(); - script->setMap(getMap()); - script->prepare(mCurrentAttack->scriptFunction); - script->push(this); - script->push(mTarget); - script->push(hit); - script->execute(); + Script::Ref function = mSpecy->getEventCallback(mCurrentAttack->scriptEvent); + if (function.isValid()) + { + Script *script = ScriptManager::currentState(); + script->setMap(getMap()); + script->prepare(function); + script->push(this); + script->push(mTarget); + script->push(hit); + script->execute(); + } } } } @@ -180,11 +183,14 @@ void Monster::update() return; } - Script *script = ScriptManager::currentState(); - script->setMap(getMap()); - script->prepare("update_monster"); - script->push(this); - script->execute(); + if (mSpecy->getUpdateCallback().isValid()) + { + Script *script = ScriptManager::currentState(); + script->setMap(getMap()); + script->prepare(mSpecy->getUpdateCallback()); + script->push(this); + script->execute(); + } // Cancel the rest when we are currently performing an attack if (isTimerRunning(T_M_ATTACK_TIME)) diff --git a/src/game-server/monster.h b/src/game-server/monster.h index 37bbe355..5ccabfa9 100644 --- a/src/game-server/monster.h +++ b/src/game-server/monster.h @@ -28,6 +28,8 @@ #include "game-server/being.h" #include "game-server/eventlistener.h" #include "common/defines.h" +#include "scripting/script.h" +#include "utils/string.h" class ItemClass; class Script; @@ -57,7 +59,7 @@ struct MonsterAttack int preDelay; int aftDelay; int range; - std::string scriptFunction; + std::string scriptEvent; }; typedef std::vector< MonsterAttack *> MonsterAttacks; @@ -197,6 +199,18 @@ class MonsterClass /** Returns script filename */ const std::string &getScript() const { return mScript; } + void setUpdateCallback(Script *script) + { script->assignCallback(mUpdateCallback); } + + void setEventCallback(const std::string &event, Script *script) + { script->assignCallback(mEventCallbacks[event]); } + + Script::Ref getUpdateCallback() const + { return mUpdateCallback; } + + Script::Ref getEventCallback(const std::string &event) const + { return mEventCallbacks.value(event); } + private: unsigned short mId; std::string mName; @@ -217,6 +231,17 @@ class MonsterClass MonsterAttacks mAttacks; std::string mScript; + /** + * A reference to the script function that is called each update. + */ + Script::Ref mUpdateCallback; + + /** + * Named event callbacks. Currently only used for custom attack + * callbacks. + */ + utils::NameMap<Script::Ref> mEventCallbacks; + friend class MonsterManager; friend class Monster; }; diff --git a/src/game-server/monstermanager.cpp b/src/game-server/monstermanager.cpp index bebb74a3..7612ddc1 100644 --- a/src/game-server/monstermanager.cpp +++ b/src/game-server/monstermanager.cpp @@ -243,9 +243,8 @@ void MonsterManager::initialize() att->preDelay = XML::getProperty(subnode, "pre-delay", 1); att->aftDelay = XML::getProperty(subnode, "aft-delay", 0); att->range = XML::getProperty(subnode, "range", 0); - att->scriptFunction = XML::getProperty(subnode, - "script-function", - std::string()); + att->scriptEvent = XML::getProperty(subnode, "script-event", + std::string()); std::string sElement = XML::getProperty(subnode, "element", "neutral"); att->element = elementFromString(sElement); diff --git a/src/game-server/statuseffect.cpp b/src/game-server/statuseffect.cpp index 32e0d621..c101bc02 100644 --- a/src/game-server/statuseffect.cpp +++ b/src/game-server/statuseffect.cpp @@ -20,9 +20,8 @@ #include "game-server/statuseffect.h" -#include "scripting/script.h" -#include "scripting/scriptmanager.h" #include "game-server/being.h" +#include "scripting/scriptmanager.h" StatusEffect::StatusEffect(int id): mId(id) @@ -35,13 +34,13 @@ StatusEffect::~StatusEffect() void StatusEffect::tick(Being *target, int count) { - if (!mTickFunction.empty()) + if (mTickCallback.isValid()) { - Script *script = ScriptManager::currentState(); - script->setMap(target->getMap()); - script->prepare(mTickFunction); - script->push(target); - script->push(count); - script->execute(); + Script *s = ScriptManager::currentState(); + s->setMap(target->getMap()); + s->prepare(mTickCallback); + s->push(target); + s->push(count); + s->execute(); } } diff --git a/src/game-server/statuseffect.h b/src/game-server/statuseffect.h index 2b7a36f8..7da5fdf9 100644 --- a/src/game-server/statuseffect.h +++ b/src/game-server/statuseffect.h @@ -21,7 +21,7 @@ #ifndef STATUSEFFECT_H #define STATUSEFFECT_H -#include <string> +#include "scripting/script.h" class Being; @@ -36,12 +36,12 @@ class StatusEffect int getId() const { return mId; } - void setTickFunction(const std::string &tickFunction) - { mTickFunction = tickFunction; } + void setTickCallback(Script *script) + { script->assignCallback(mTickCallback); } private: int mId; - std::string mTickFunction; + Script::Ref mTickCallback; }; #endif diff --git a/src/game-server/statusmanager.cpp b/src/game-server/statusmanager.cpp index 15203d65..7769ef0f 100644 --- a/src/game-server/statusmanager.cpp +++ b/src/game-server/statusmanager.cpp @@ -33,6 +33,7 @@ typedef std::map< int, StatusEffect * > StatusEffectsMap; static StatusEffectsMap statusEffects; +static utils::NameMap<StatusEffect*> statusEffectsByName; static std::string statusReferenceFile; void StatusManager::initialize(const std::string &file) @@ -59,7 +60,7 @@ void StatusManager::reload() if (!xmlStrEqual(node->name, BAD_CAST "status-effect")) continue; - int id = XML::getProperty(node, "id", 0); + const int id = XML::getProperty(node, "id", 0); if (id < 1) { LOG_WARN("Status Manager: The status ID: " << id << " in " @@ -68,9 +69,24 @@ void StatusManager::reload() continue; } + StatusEffect *statusEffect = new StatusEffect(id); + + const std::string name = XML::getProperty(node, "name", + std::string()); + if (!name.empty()) + { + if (statusEffectsByName.contains(name)) + { + LOG_WARN("StatusManager: name not unique for status effect " + << id); + } + else + { + statusEffectsByName.insert(name, statusEffect); + } + } + std::string scriptFile = XML::getProperty(node, "script", std::string()); - std::string tickFunction = XML::getProperty(node, "tick-function", - std::string()); //TODO: Get these modifiers /* modifiers.setAttributeValue(BASE_ATTR_PHY_ATK_MIN, XML::getProperty(node, "attack-min", 0)); @@ -84,8 +100,6 @@ void StatusManager::reload() modifiers.setAttributeValue(CHAR_ATTR_INTELLIGENCE, XML::getProperty(node, "intelligence", 0)); modifiers.setAttributeValue(CHAR_ATTR_WILLPOWER, XML::getProperty(node, "willpower", 0)); */ - StatusEffect *statusEffect = new StatusEffect(id); - statusEffect->setTickFunction(tickFunction); if (!scriptFile.empty()) { std::stringstream filename; @@ -112,11 +126,16 @@ void StatusManager::deinitialize() delete i->second; } statusEffects.clear(); + statusEffectsByName.clear(); } StatusEffect *StatusManager::getStatus(int statusId) { StatusEffectsMap::const_iterator i = statusEffects.find(statusId); - return i != statusEffects.end() ? i->second : NULL; + return i != statusEffects.end() ? i->second : 0; } +StatusEffect *StatusManager::getStatusByName(const std::string &name) +{ + return statusEffectsByName.value(name); +} diff --git a/src/game-server/statusmanager.h b/src/game-server/statusmanager.h index fc09adb5..8ab321d1 100644 --- a/src/game-server/statusmanager.h +++ b/src/game-server/statusmanager.h @@ -46,6 +46,11 @@ namespace StatusManager * Gets the status having the given ID. */ StatusEffect *getStatus(int statusId); + + /** + * Gets the status having the given name. + */ + StatusEffect *getStatusByName(const std::string &name); } #endif // STATUSMANAGER_H diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp index 3d59ab98..c6a75d90 100644 --- a/src/scripting/lua.cpp +++ b/src/scripting/lua.cpp @@ -46,6 +46,8 @@ extern "C" { #include "game-server/postman.h" #include "game-server/quest.h" #include "game-server/state.h" +#include "game-server/statuseffect.h" +#include "game-server/statusmanager.h" #include "game-server/trigger.h" #include "net/messageout.h" #include "scripting/luautil.h" @@ -210,6 +212,27 @@ static int on_get_special_recharge_cost(lua_State *s) return 0; } +static int get_item_class(lua_State *s) +{ + const char *name = luaL_checkstring(s, 1); + LuaItemClass::push(s, itemManager->getItemByName(name)); + return 1; +} + +static int get_monster_class(lua_State *s) +{ + const char *name = luaL_checkstring(s, 1); + LuaMonsterClass::push(s, monsterManager->getMonsterByName(name)); + return 1; +} + +static int get_status_effect(lua_State *s) +{ + const char *name = luaL_checkstring(s, 1); + LuaStatusEffect::push(s, StatusManager::getStatusByName(name)); + return 1; +} + /** * mana.npc_message(NPC*, Character*, string): void * Callback for sending a NPC_MESSAGE. @@ -1507,6 +1530,23 @@ static int posY(lua_State *s) return 0; } +static int monster_class_on_update(lua_State *s) +{ + MonsterClass *monsterClass = LuaMonsterClass::check(s, 1); + luaL_checktype(s, 2, LUA_TFUNCTION); + monsterClass->setUpdateCallback(getScript(s)); + return 0; +} + +static int monster_class_on(lua_State *s) +{ + MonsterClass *monsterClass = LuaMonsterClass::check(s, 1); + const char *event = luaL_checkstring(s, 2); + luaL_checktype(s, 3, LUA_TFUNCTION); + monsterClass->setEventCallback(event, getScript(s)); + return 0; +} + /** * mana.monster_create(int id || string name, int x, int y): Monster* * Callback for creating a monster on the current map. @@ -1620,32 +1660,6 @@ static int monster_remove(lua_State *s) } /** - * mana.monster_load_script(Monster*, string script_filename): void - * loads a LUA script for the given monster. - */ -static int monster_load_script(lua_State *s) -{ - Monster *m = getMonster(s, 1); - if (!m) - { - raiseScriptError(s, "monster_load_script called " - "for a nonexistent monster."); - return 0; - } - - const char *scriptName = luaL_checkstring(s, 2); - if (scriptName[0] == 0) - { - raiseScriptError(s, "monster_load_script called " - "with empty script file name."); - return 0; - } - - m->loadScript(scriptName); - return 0; -} - -/** * mana.chr_get_chest(Character*, string): nil or string * Callback for getting a quest variable. Starts a recovery and returns * immediatly, if the variable is not known yet. @@ -1959,9 +1973,8 @@ static int chr_get_post(lua_State *s) /** * mana.being_register(Being*): void - * Makes the server call the lua functions deathEvent - * and removeEvent when the being dies or is removed - * from the map. + * Makes the server call the on_being_death and on_being_remove callbacks + * when the being dies or is removed from the map. */ static int being_register(lua_State *s) { @@ -2453,6 +2466,15 @@ static int is_walkable(lua_State *s) return 1; } +static int item_class_on(lua_State *s) +{ + ItemClass *itemClass = LuaItemClass::check(s, 1); + const char *event = luaL_checkstring(s, 2); + luaL_checktype(s, 3, LUA_TFUNCTION); + itemClass->setEventCallback(event, getScript(s)); + return 0; +} + /** * mana.drop_item(int x, int y, int id || string name[, int number]): bool * Creates an item stack on the floor. @@ -2651,6 +2673,14 @@ static int map_object_get_type(lua_State *s) return 1; } +static int status_effect_on_tick(lua_State *s) +{ + StatusEffect *statusEffect = LuaStatusEffect::check(s, 1); + luaL_checktype(s, 2, LUA_TFUNCTION); + statusEffect->setTickCallback(getScript(s)); + return 0; +} + /** * mana.announce(text [, sender]) * Does a global announce @@ -2719,6 +2749,9 @@ LuaScript::LuaScript(): { "on_craft", &on_craft }, { "on_use_special", &on_use_special }, { "on_get_special_recharge_cost", &on_get_special_recharge_cost }, + { "get_item_class", &get_item_class }, + { "get_monster_class", &get_monster_class }, + { "get_status_effect", &get_status_effect }, { "npc_create", &npc_create }, { "npc_message", &npc_message }, { "npc_choice", &npc_choice }, @@ -2761,7 +2794,6 @@ LuaScript::LuaScript(): { "monster_get_name", &monster_get_name }, { "monster_change_anger", &monster_change_anger }, { "monster_remove", &monster_remove }, - { "monster_load_script", &monster_load_script }, { "being_apply_status", &being_apply_status }, { "being_remove_status", &being_remove_status }, { "being_has_status", &being_has_status }, @@ -2809,6 +2841,11 @@ LuaScript::LuaScript(): luaL_register(mState, "mana", callbacks); lua_pop(mState, 1); // pop the 'mana' table + static luaL_Reg const members_ItemClass[] = { + { "on", &item_class_on }, + { NULL, NULL } + }; + static luaL_Reg const members_MapObject[] = { { "property", &map_object_get_property }, { "bounds", &map_object_get_bounds }, @@ -2817,7 +2854,21 @@ LuaScript::LuaScript(): { NULL, NULL } }; + static luaL_Reg const members_MonsterClass[] = { + { "on_update", &monster_class_on_update }, + { "on", &monster_class_on }, + { NULL, NULL } + }; + + static luaL_Reg const members_StatusEffect[] = { + { "on_tick", &status_effect_on_tick }, + { NULL, NULL } + }; + + LuaItemClass::registerType(mState, "ItemClass", members_ItemClass); LuaMapObject::registerType(mState, "MapObject", members_MapObject); + LuaMonsterClass::registerType(mState, "MonsterClass", members_MonsterClass); + LuaStatusEffect::registerType(mState, "StatusEffect", members_StatusEffect); // Make script object available to callback functions. lua_pushlightuserdata(mState, const_cast<char *>(®istryKey)); diff --git a/src/scripting/luautil.h b/src/scripting/luautil.h index 049276fe..2fc7543a 100644 --- a/src/scripting/luautil.h +++ b/src/scripting/luautil.h @@ -31,12 +31,14 @@ extern "C" { #include <set> #include <vector> -#include "game-server/map.h" - class Being; -class NPC; class Character; +class ItemClass; +class MapObject; class Monster; +class MonsterClass; +class NPC; +class StatusEffect; class Thing; // Report script errors and interrupt the script. @@ -105,10 +107,16 @@ public: /** * Pushes a userdata reference to the given object on the stack. Either by * creating one, or reusing an existing one. + * + * When a null-pointer is passed for \a object, the value 'nil' is pushed. */ - static int push(lua_State *s, T *object) + static void push(lua_State *s, T *object) { - if (!UserDataCache::retrieve(s, object)) + if (!object) + { + lua_pushnil(s); + } + else if (!UserDataCache::retrieve(s, object)) { void *userData = lua_newuserdata(s, sizeof(T*)); * static_cast<T**>(userData) = object; @@ -118,8 +126,6 @@ public: UserDataCache::insert(s, object); } - - return 1; } /** @@ -138,7 +144,10 @@ private: template <typename T> const char * LuaUserData<T>::mTypeName; +typedef LuaUserData<ItemClass> LuaItemClass; typedef LuaUserData<MapObject> LuaMapObject; +typedef LuaUserData<MonsterClass> LuaMonsterClass; +typedef LuaUserData<StatusEffect> LuaStatusEffect; NPC *getNPC(lua_State *s, int p); diff --git a/src/utils/string.h b/src/utils/string.h index a21081d5..882b1b09 100644 --- a/src/utils/string.h +++ b/src/utils/string.h @@ -105,6 +105,11 @@ namespace utils return result != mMap.end() ? result->second : mDefault; } + T &operator[](const std::string &name) + { + return mMap[toLower(name)]; + } + bool contains(const std::string &name) const { return mMap.find(toLower(name)) != mMap.end(); |