/* * The Mana Server * Copyright (C) 2007-2010 The Mana World Development Team * Copyright (C) 2010-2013 The Mana Developers * * This file is part of The Mana Server. * * The Mana Server is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * The Mana Server is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with The Mana Server. If not, see . */ #include extern "C" { #include #include } #include "common/defines.h" #include "common/resourcemanager.h" #include "game-server/accountconnection.h" #include "game-server/buysell.h" #include "game-server/character.h" #include "game-server/collisiondetection.h" #include "game-server/effect.h" #include "game-server/gamehandler.h" #include "game-server/inventory.h" #include "game-server/item.h" #include "game-server/itemmanager.h" #include "game-server/map.h" #include "game-server/mapcomposite.h" #include "game-server/mapmanager.h" #include "game-server/monster.h" #include "game-server/monstermanager.h" #include "game-server/npc.h" #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/triggerareacomponent.h" #include "net/messageout.h" #include "scripting/luautil.h" #include "scripting/luascript.h" #include "scripting/scriptmanager.h" #include "utils/logger.h" #include "utils/speedconv.h" #include #include /* * This file includes all script bindings available to LUA scripts. * When you add or change a script binding please run the update script in the * docs repository! * * http://doc.manasource.org/scripting */ /** LUA_CATEGORY Callbacks (callbacks) * **Note:** You can only assign a **single** function as callback. * When setting a new function the old one will not be called anymore. * Some of this callbacks are already used for the libmana.lua. Be careful when * using those since they will most likely break your code in other places. */ /** LUA on_update_derived_attribute (callbacks) * on_update_derived_attribute(function ref) ** * Will call the function ''ref'' when an attribute changed and other attributes * need recalculation. The function is expected to recalculate those then. * * **See:** [[attributes.xml]] for more info. * */ static int on_update_derived_attribute(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); BeingComponent::setUpdateDerivedAttributesCallback(getScript(s)); return 0; } /** LUA on_recalculate_base_attribute (callbacks) * on_recalculate_base_attribute(function ref) ** * Will call the function ''ref'' when an attribute base needs to be recalculated. * The function is expected to do this recalculation then. The engine only * triggers this for characters. However you can use the same function for * recalculating derived attributes in the * [[scripting#on_update_derived_attribute|on_update_derived_attribute]] callback. * * **See:** [[attributes.xml]] for more info. */ static int on_recalculate_base_attribute(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); BeingComponent::setRecalculateBaseAttributeCallback(getScript(s)); return 0; } /** LUA on_character_death (callbacks) * on_character_death(function ref) ** * on_character_death( function(Character*) ): void * Sets a listener function to the character death event. */ static int on_character_death(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); CharacterComponent::setDeathCallback(getScript(s)); return 0; } /** LUA on_character_death_accept (callbacks) * on_character_death_accept(function ref) ** * Will make sure that the function ''ref'' gets called with the character * as argument as soon a character either pressed the ok dialouge in the death * message or left the game while being dead. */ static int on_character_death_accept(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); CharacterComponent::setDeathAcceptedCallback(getScript(s)); return 0; } /** LUA on_character_login (callbacks) * on_character_login(function ref) ** * Will make sure that function ''ref'' gets called with the character * as argument as soon a character logged in. */ static int on_character_login(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); CharacterComponent::setLoginCallback(getScript(s)); return 0; } /** LUA on_being_death (callbacks) * on_being_death(function ref) ** * Will make sure that the function ''ref'' gets called with the being * as argument as soon a being dies. */ static int on_being_death(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); LuaScript::setDeathNotificationCallback(getScript(s)); return 0; } /** LUA on_entity_remove (callbacks) * on_entity_remove(function ref) ** * Will make sure that the function ''ref'' gets called with the being * as argument as soon a being gets removed from a map. */ static int on_entity_remove(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); LuaScript::setRemoveNotificationCallback(getScript(s)); return 0; } /** LUA on_update (callbacks) * on_update(function ref) ** * Will make sure that the function ''ref'' gets called every game tick. */ static int on_update(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); Script::setUpdateCallback(getScript(s)); return 0; } /** LUA on_create_npc_delayed (callbacks) * on_create_npc_delayed(function ref) ** * Will make sure that the function ''ref'' gets called with the * name, id, gender, x and y values as arguments of the npc when a npc should * be created at map init (Npcs defined directly in the map files use this). */ static int on_create_npc_delayed(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); Script::setCreateNpcDelayedCallback(getScript(s)); return 0; } /** LUA on_map_initialize (callbacks) * on_map_initialize(function ref) ** * Will make sure that the function ''ref'' gets called with the initialized * map as current map when the map is initialized. */ static int on_map_initialize(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); MapComposite::setInitializeCallback(getScript(s)); return 0; } /** LUA on_craft (callbacks) * on_craft(function ref) ** * Will make sure that the function ''ref'' gets called with the the crafting * character and a table with the recipes {(id, amount}) when a character * performs crafting. */ static int on_craft(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); ScriptManager::setCraftCallback(getScript(s)); return 0; } /** LUA on_mapupdate (callbacks) * on_mapupdate(function ref) ** * Will make sure that the function ''ref'' gets called with the the map id * as argument for each game tick and map. */ static int on_mapupdate(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); MapComposite::setUpdateCallback(getScript(s)); return 0; } /** LUA_CATEGORY Creation and removal of stuff (creation) */ /** LUA npc_create (creation) * npc_create(string name, int spriteID, int gender, int x, int y, * function talkfunct, function updatefunct) ** * **Return value:** A handle to the created NPC. * * Creates a new NPC with the name ''name'' at the coordinates ''x'':''y'' * which appears to the players with the appearence listed in their npcs.xml * under ''spriteID'' and the gender ''gender''. Every game tick the function * ''updatefunct'' is called with the handle of the NPC. When a character talks * to the NPC the function ''talkfunct'' is called with the NPC handle and the * character handle. * * For setting the gender you can use the constants defined in the * libmana-constants.lua: * * | 0 | GENDER_MALE | * | 1 | GENDER_FEMALE | * | 2 | GENDER_UNSPECIFIED | */ static int npc_create(lua_State *s) { const char *name = luaL_checkstring(s, 1); const int id = luaL_checkint(s, 2); const int gender = luaL_checkint(s, 3); const int x = luaL_checkint(s, 4); const int y = luaL_checkint(s, 5); if (!lua_isnoneornil(s, 6)) luaL_checktype(s, 6, LUA_TFUNCTION); if (!lua_isnoneornil(s, 7)) luaL_checktype(s, 7, LUA_TFUNCTION); MapComposite *m = checkCurrentMap(s); NpcComponent *npcComponent = new NpcComponent(id); Entity *npc = new Entity(OBJECT_NPC); auto *actorComponent = new ActorComponent(*npc); npc->addComponent(actorComponent); auto *beingComponent = new BeingComponent(*npc); npc->addComponent(beingComponent); npc->addComponent(npcComponent); // some health so it doesn't spawn dead beingComponent->setAttribute(*npc, ATTR_MAX_HP, 100); beingComponent->setAttribute(*npc, ATTR_HP, 100); beingComponent->setName(name); beingComponent->setGender(getGender(gender)); actorComponent->setWalkMask(Map::BLOCKMASK_WALL | Map::BLOCKMASK_MONSTER | Map::BLOCKMASK_CHARACTER); npc->setMap(m); actorComponent->setPosition(*npc, Point(x, y)); if (lua_isfunction(s, 6)) { lua_pushvalue(s, 6); npcComponent->setTalkCallback(luaL_ref(s, LUA_REGISTRYINDEX)); } if (lua_isfunction(s, 7)) { lua_pushvalue(s, 7); npcComponent->setUpdateCallback(luaL_ref(s, LUA_REGISTRYINDEX)); } GameState::enqueueInsert(npc); push(s, npc); return 1; } /** LUA npc_enable (creation) * npc_disable(handle npc) ** * Re-enables an NPC that got disabled before. */ static int npc_enable(lua_State *s) { Entity *npc = checkNpc(s, 1); npc->getComponent()->setEnabled(true); GameState::enqueueInsert(npc); return 0; } /** LUA npc_disable (creation) * npc_disable(handle npc) ** * Disable an NPC. */ static int npc_disable(lua_State *s) { Entity *npc = checkNpc(s, 1); npc->getComponent()->setEnabled(false); GameState::remove(npc); return 0; } /** LUA monster_create (creation) * monster_create(int monsterID, int x, int y) * monster_create(string monstername, int x, int y) ** * **Return value:** A handle to the created monster. * * Spawns a new monster of type ''monsterID'' or ''monstername'' on the current * map on the pixel coordinates ''x'':''y''. */ static int monster_create(lua_State *s) { MonsterClass *monsterClass = checkMonsterClass(s, 1); const int x = luaL_checkint(s, 2); const int y = luaL_checkint(s, 3); MapComposite *m = checkCurrentMap(s); Entity *monster = new Entity(OBJECT_MONSTER); auto *actorComponent = new ActorComponent(*monster); monster->addComponent(actorComponent); monster->addComponent(new BeingComponent(*monster)); monster->addComponent(new MonsterComponent(*monster, monsterClass)); monster->setMap(m); actorComponent->setPosition(*monster, Point(x, y)); GameState::enqueueInsert(monster); push(s, monster); return 1; } /** LUA entity:remove (creation) * entity:remove() ** * Removes the entity from its current map. */ static int entity_remove(lua_State *s) { GameState::remove(LuaEntity::check(s, 1)); return 0; } /** LUA trigger_create (creation) * trigger_create(int x, int y, int width, int height, * function trigger_function, int arg, bool once) ** * Creates a new trigger area with the given ''height'' and ''width'' in pixels * at the map position ''x'':''y'' in pixels. When a being steps into this area * the function ''trigger_function'' is called with the being handle and * ''arg'' as arguments. When ''once'' is false the function is called every * game tick the being is inside the area. When ''once'' is true it is only * called again when the being leaves and reenters the area. */ static int trigger_create(lua_State *s) { const int x = luaL_checkint(s, 1); const int y = luaL_checkint(s, 2); const int width = luaL_checkint(s, 3); const int height = luaL_checkint(s, 4); luaL_checktype(s, 5, LUA_TFUNCTION); const int id = luaL_checkint(s, 6); if (!lua_isboolean(s, 7)) { luaL_error(s, "trigger_create called with incorrect parameters."); return 0; } Script *script = getScript(s); MapComposite *m = checkCurrentMap(s, script); const bool once = lua_toboolean(s, 7); Script::Ref function; lua_pushvalue(s, 5); script->assignCallback(function); lua_pop(s, 1); Entity *triggerEntity = new Entity(OBJECT_OTHER, m); ScriptAction *action = new ScriptAction(script, function, id); Rectangle r = { x, y, width, height }; TriggerAreaComponent *area = new TriggerAreaComponent(r, action, once); triggerEntity->addComponent(area); LOG_INFO("Created script trigger at " << x << "," << y << " (" << width << "x" << height << ") id: " << id); bool ret = GameState::insertOrDelete(triggerEntity); lua_pushboolean(s, ret); return 1; } /** LUA effect_create (creation) * effect_create(int id, int x, int y) * effect_create(int id, being b) ** * Triggers the effect ''id'' from the clients effects.xml * (particle and/or sound) at map location ''x'':''y'' or on being ''b''. * This has no effect on gameplay. * * **Warning:** Remember that clients might switch off particle effects for * performance reasons. Thus you should not use this for important visual * input. */ static int effect_create(lua_State *s) { const int id = luaL_checkint(s, 1); if (lua_isuserdata(s, 2)) { // being mode Entity *b = checkBeing(s, 2); Effects::show(id, b); } else { // positional mode int x = luaL_checkint(s, 2); int y = luaL_checkint(s, 3); MapComposite *m = checkCurrentMap(s); Effects::show(id, m, Point(x, y)); } return 0; } /** LUA drop_item (creation) * drop_item(int x, int y, int id [, int number]) * drop_item(int x, int y, string name[, int number]) ** * Creates an item stack on the floor. */ static int item_drop(lua_State *s) { const int x = luaL_checkint(s, 1); const int y = luaL_checkint(s, 2); ItemClass *ic = checkItemClass(s, 3); const int number = luaL_optint(s, 4, 1); MapComposite *map = checkCurrentMap(s); Entity *item = Item::create(map, Point(x, y), ic, number); GameState::enqueueInsert(item); return 0; } /** LUA_CATEGORY Input and output (input) */ /** LUA say (input) * say(string message) ** * **Warning:** May only be called from an NPC talk function. * * Shows an NPC dialog box on the screen of displaying the string ''message''. * Idles the current thread until the user click "OK". */ static int say(lua_State *s) { const char *m = luaL_checkstring(s, 1); Script::Thread *thread = checkCurrentThread(s); Entity *npc = thread->getContext().npc; Entity *character = thread->getContext().character; if (!(npc && character)) luaL_error(s, "not in npc conversation"); MessageOut msg(GPMSG_NPC_MESSAGE); msg.writeInt16(npc->getComponent()->getPublicID()); msg.writeString(m); gameHandler->sendTo(character, msg); thread->mState = Script::ThreadPaused; return lua_yield(s, 0); } /** LUA ask (input) * ask(item1, item2, ... itemN) ** * **Return value:** Number of the option the player selected (starting with 1). * * **Warning:** May only be called from an NPC talk function. * * Shows an NPC dialog box on the users screen with a number of dialog options * to choose from. Idles the current thread until the user selects one or * aborts the current thread when the user clicks "cancel". * * Items are either strings or tables of strings (indices are ignored, * but presumed to be taken in order). So, * ''ask("A", {"B", "C", "D"}, "E")'' is the same as * ''ask("A", "B", "C", "D", "E")''. */ static int ask(lua_State *s) { Script::Thread *thread = checkCurrentThread(s); Entity *npc = thread->getContext().npc; Entity *character = thread->getContext().character; if (!(npc && character)) luaL_error(s, "not in npc conversation"); MessageOut msg(GPMSG_NPC_CHOICE); msg.writeInt16(npc->getComponent()->getPublicID()); for (int i = 1, i_end = lua_gettop(s); i <= i_end; ++i) { if (lua_isstring(s, i)) { msg.writeString(lua_tostring(s, i)); } else if (lua_istable(s, i)) { lua_pushnil(s); while (lua_next(s, i) != 0) { if (lua_isstring(s, -1)) { msg.writeString(lua_tostring(s, -1)); } else { luaL_error(s, "ask called with incorrect parameters."); return 0; } lua_pop(s, 1); } } else { luaL_error(s, "ask called with incorrect parameters."); return 0; } } gameHandler->sendTo(character, msg); thread->mState = Script::ThreadExpectingNumber; return lua_yield(s, 0); } /** LUA ask_number (input) * ask_number(min_num, max_num, [default_num]) ** * **Return value:** The number the player entered into the field. * * **Warning:** May only be called from an NPC talk function. * * Shows a dialog box to the user which allows him to choose a number between * ''min_num'' and ''max_num''. If ''default_num'' is set this number will be * uses as default. Otherwise ''min_num'' will be the default. */ static int ask_number(lua_State *s) { int min = luaL_checkint(s, 1); int max = luaL_checkint(s, 2); int defaultValue = luaL_optint(s, 3, min); Script::Thread *thread = checkCurrentThread(s); Entity *npc = thread->getContext().npc; Entity *character = thread->getContext().character; if (!(npc && character)) luaL_error(s, "not in npc conversation"); MessageOut msg(GPMSG_NPC_NUMBER); msg.writeInt16(npc->getComponent()->getPublicID()); msg.writeInt32(min); msg.writeInt32(max); msg.writeInt32(defaultValue); gameHandler->sendTo(character, msg); thread->mState = Script::ThreadExpectingNumber; return lua_yield(s, 0); } /** LUA ask_string (input) * ask_string() ** * **Return value:** The string the player entered. * * **Warning:** May only be called from an NPC talk function. * * Shows a dialog box to a user which allows him to enter a text. */ static int ask_string(lua_State *s) { Script::Thread *thread = checkCurrentThread(s); Entity *npc = thread->getContext().npc; Entity *character = thread->getContext().character; if (!(npc && character)) luaL_error(s, "not in npc conversation"); MessageOut msg(GPMSG_NPC_STRING); msg.writeInt16(npc->getComponent()->getPublicID()); gameHandler->sendTo(character, msg); thread->mState = Script::ThreadExpectingString; return lua_yield(s, 0); } /** LUA npc_post (input) * npc_post() ** * Starts retrieving post. Better not try to use it so far. */ static int npc_post(lua_State *s) { const Script::Context *context = getScript(s)->getContext(); Entity *npc = context->npc; Entity *character = context->character; MessageOut msg(GPMSG_NPC_POST); msg.writeInt16(npc->getComponent()->getPublicID()); gameHandler->sendTo(character, msg); return 0; } /** LUA entity:say (input) * entity:say(string message) ** * Makes this entity (which can be a character, monster or NPC), speak the * string ''message'' as if it was entered by a player in the chat bar. */ static int entity_say(lua_State *s) { Entity *actor = checkActor(s, 1); const char *message = luaL_checkstring(s, 2); GameState::sayAround(actor, message); return 0; } /** LUA entity:message (input) * entity:message(string message) ** * Delivers the string ''message'' to this entity (which needs to be a * character). It will appear in the chatlog as a private message from * "Server". */ static int entity_message(lua_State *s) { Entity *character = checkCharacter(s, 1); const char *message = luaL_checkstring(s, 2); GameState::sayTo(character, nullptr, message); return 0; } /** LUA announce (input) * announce(string message [, string sender]) ** * Sends a global announce with the given ''message'' and ''sender''. If no * ''sender'' is passed "Server" will be used as sender. */ static int announce(lua_State *s) { const char *message = luaL_checkstring(s, 1); const char *sender = luaL_optstring(s, 2, "Server"); MessageOut msg(GAMSG_ANNOUNCE); msg.writeString(message); msg.writeInt16(0); // Announce from server so id = 0 msg.writeString(sender); accountHandler->send(msg); return 0; } /** LUA_CATEGORY Inventory interaction (inventory) */ /** LUA trade (inventory) * trade(bool mode, * { int item1id, int item1amount, int item1cost }, ..., * { int itemNid, int itemNamount, int itemNcost }) * trade(bool mode, * { string item1name, int item1amount, int item1cost }, ..., * { string itemNname, int itemNamount, int itemNcost }) ** * FIXME: Move into a seperate file * Opens a trade window from an NPC conversation. ''mode'' * is true for selling and false for buying. You have to set each items the NPC * is buying/selling, the cost and the maximum amount in {}. * * **Note:** If the fourth parameters (table type) is omitted or invalid, and * the mode set to sell (true), * the whole player inventory is then sellable. * * **N.B.:** Be sure to put a ''value'' (item cost) parameter in your items.xml * to permit the player to sell it when using this option. * * **Return values:** * * **0** when a trade has been started * * **1** when there is no buy/sellable items * * **2** in case of errors. * * **Examples:** * * -- "A buy sample." * local buycase = trade(false, { * {"Sword", 10, 20}, * {"Bow", 10, 30}, * {"Dagger", 10, 50} * }) * if buycase == 0 then * say("What do you want to buy?") * elseif buycase == 1 then * say("I've got no items to sell.") * else * say("Hmm, something went wrong... Ask a scripter to * fix the buying mode!") * end * * -- ... * * -- "Example: Let the player sell only pre-determined items." * local sellcase = trade(true, { * {"Sword", 10, 20}, * {"Bow", 10, 30}, * {"Dagger", 10, 200}, * {"Knife", 10, 300}, * {"Arrow", 10, 500}, * {"Cactus Drink", 10, 25} * }) * if sellcase == 0 then * say("Here we go:") * elseif sellcase == 1 then * say("I'm not interested by your items.") * else * say("Hmm, something went wrong...") * say("Ask a scripter to fix me!") * end * * -- ... * * -- "Example: Let the player sell every item with a 'value' parameter in * the server's items.xml file * local sellcase = trade(true) * if sellcase == 0 then * say("Ok, what do you want to sell:") * elseif sellcase == 1 then * say("I'm not interested by any of your items.") * else * say("Hmm, something went wrong...") * say("Ask a scripter to fix me!") * end * */ static int trade(lua_State *s) { const Script::Context *context = getScript(s)->getContext(); Entity *npc = context->npc; Entity *character = context->character; luaL_argcheck(s, lua_isboolean(s, 1), 1, "boolean expected"); bool sellMode = lua_toboolean(s, 1); BuySell *t = new BuySell(character, sellMode); if (!lua_istable(s, 2)) { if (sellMode) { // Can sell everything if (!t->registerPlayerItems()) { // No items to sell in player inventory t->cancel(); lua_pushinteger(s, 1); return 1; } if (t->start(npc)) { lua_pushinteger(s, 0); return 1; } else { lua_pushinteger(s, 1); return 1; } } else { raiseWarning(s, "trade[Buy] called with invalid " "or empty items table parameter."); t->cancel(); lua_pushinteger(s, 2); return 1; } } int nbItems = 0; lua_pushnil(s); while (lua_next(s, 2)) { if (!lua_istable(s, -1)) { raiseWarning(s, "trade called with invalid " "or empty items table parameter."); t->cancel(); lua_pushinteger(s, 2); return 1; } int v[3]; for (int i = 0; i < 3; ++i) { lua_rawgeti(s, -1, i + 1); if (i == 0) // item id or name { ItemClass *it = getItemClass(s, -1); if (!it) { raiseWarning(s, "trade called with incorrect " "item id or name."); t->cancel(); lua_pushinteger(s, 2); return 1; } v[0] = it->getDatabaseID(); } else if (!lua_isnumber(s, -1)) { raiseWarning(s, "trade called with incorrect parameters " "in item table."); t->cancel(); lua_pushinteger(s, 2); return 1; } else { v[i] = lua_tointeger(s, -1); } lua_pop(s, 1); } if (t->registerItem(v[0], v[1], v[2])) nbItems++; lua_pop(s, 1); } if (nbItems == 0) { t->cancel(); lua_pushinteger(s, 1); return 1; } if (t->start(npc)) { lua_pushinteger(s, 0); return 1; } else { lua_pushinteger(s, 1); return 1; } } /** LUA entity:inv_count (inventory) * entity:inv_count(bool inInventory, bool inEquipment, * int id1, ..., int idN) * entity:inv_count(bool inInventory, bool inEquipment, * string name1, ..., string nameN) ** * Valid only for character entities. * * The boolean values ''inInventory'' and ''inEquipment'' make possible to * select whether equipped or carried items must be counted. * * **Return values:** A number of integers with the amount of items ''id'' or * ''name'' carried or equipped by the character. */ static int entity_inv_count(lua_State *s) { Entity *q = checkCharacter(s, 1); if (!lua_isboolean(s, 2) || !lua_isboolean(s, 3)) { luaL_error(s, "inv_count called with incorrect parameters."); return 0; } bool inInventory = lua_toboolean(s, 2); bool inEquipment = lua_toboolean(s, 3); int nb_items = lua_gettop(s) - 3; Inventory inv(q); int nb = 0; for (int i = 4; i < nb_items + 4; ++i) { ItemClass *it = checkItemClass(s, i); nb = inv.count(it->getDatabaseID(), inInventory, inEquipment); lua_pushinteger(s, nb); } return nb_items; } /** LUA entity:inv_change (inventory) * entity:inv_change(int id1, int number1, ..., int idN, numberN) * entity:inv_change(string name1, int number1, ..., string nameN, numberN) ** * Valid only for character entities. * * Changes the number of items with the item ID ''id'' or ''name'' owned by * this character by ''number''. You can change any number of items with this * function by passing multiple ''id'' or ''name'' and ''number'' pairs. * A failure can be caused by trying to take items the character doesn't possess. * * **Return value:** Boolean true on success, boolean false on failure. * * **Warning:** When one of the operations fails the following operations are * ignored but these before are executed. For that reason you should always * check if the character possesses items you are taking away using * entity:inv_count. */ static int entity_inv_change(lua_State *s) { Entity *q = checkCharacter(s, 1); int nb_items = (lua_gettop(s) - 1) / 2; Inventory inv(q); for (int i = 0; i < nb_items; ++i) { if (!lua_isnumber(s, i * 2 + 3)) { luaL_error(s, "inv_change called with " "incorrect parameters."); return 0; } int nb = lua_tointeger(s, i * 2 + 3); ItemClass *ic = checkItemClass(s, i * 2 + 2); int id = ic->getDatabaseID(); if (nb < 0) { // Removing too much item is a success as for the scripter's // point of view. We log it anyway. nb = inv.remove(id, -nb); if (nb) { LOG_WARN("inv_change removed more items than owned: " << "character: " << q->getComponent()->getName() << " item id: " << id); } } else { nb = inv.insert(id, nb); if (nb) { const Point &position = q->getComponent()->getPosition(); Entity *item = Item::create(q->getMap(), position, ic, nb); GameState::enqueueInsert(item); } } } lua_pushboolean(s, 1); return 1; } /** LUA entity:inventory (inventory) * entity:inventory(): table[]{slot, item id, name, amount} ** * Valid only for character entities. * * Used to get a full view of a character's inventory. * This is not the preferred way to know whether an item is in the character's * inventory: * Use entity:inv_count for simple cases. * * **Return value:** A table containing all the info about the character's * inventory. Empty slots are not listed. * * **Example of use:** * * local inventory_table = ch:inventory() * for i = 1, #inventory_table do * item_message = item_message.."\n"..inventory_table[i].slot..", " * ..inventory_table[i].id..", "..inventory_table[i].name..", " * ..inventory_table[i].amount * end * */ static int entity_get_inventory(lua_State *s) { Entity *q = checkCharacter(s, 1); // Create a lua table with the inventory ids. const InventoryData invData = q->getComponent() ->getPossessions().getInventory(); lua_newtable(s); int firstTableStackPosition = lua_gettop(s); int tableIndex = 1; for (InventoryData::const_iterator it = invData.begin(), it_end = invData.end(); it != it_end; ++it) { if (!it->second.itemId || !it->second.amount) continue; // Create the sub-table (value of the main one) lua_createtable(s, 0, 4); int subTableStackPosition = lua_gettop(s); // Stores the item info in it. lua_pushliteral(s, "slot"); lua_pushinteger(s, it->first); // The slot id lua_settable(s, subTableStackPosition); lua_pushliteral(s, "id"); lua_pushinteger(s, it->second.itemId); lua_settable(s, subTableStackPosition); lua_pushliteral(s, "name"); push(s, itemManager->getItem(it->second.itemId)->getName()); lua_settable(s, subTableStackPosition); lua_pushliteral(s, "amount"); lua_pushinteger(s, it->second.amount); lua_settable(s, subTableStackPosition); // Add the sub-table as value of the main one. lua_rawseti(s, firstTableStackPosition, tableIndex); ++tableIndex; } return 1; } /** LUA entity:equipment (inventory) * entity:equipment(): table[](slot, item id, name)} ** * Valid only for character entities. * * Used to get a full view of a character's equipment. * This is not the preferred way to know whether an item is equipped: * Use entity:inv_count for simple cases. * * **Return value:** A table containing all the info about the character's * equipment. Empty slots are not listed. * * **Example of use:** * * local equipment_table = ch:equipment() * for i = 1, #equipment_table do * item_message = item_message.."\n"..equipment_table[i].slot..", " * ..equipment_table[i].id..", "..equipment_table[i].name * end * */ static int entity_get_equipment(lua_State *s) { Entity *q = checkCharacter(s, 1); // Create a lua table with the inventory ids. const EquipData equipData = q->getComponent() ->getPossessions().getEquipment(); lua_newtable(s); int firstTableStackPosition = lua_gettop(s); int tableIndex = 1; std::set itemInstances; for (EquipData::const_iterator it = equipData.begin(), it_end = equipData.end(); it != it_end; ++it) { if (!it->second.itemId || !it->second.itemInstance) continue; // Only count multi-slot items once. if (!itemInstances.insert(it->second.itemInstance).second) continue; // Create the sub-table (value of the main one) lua_createtable(s, 0, 3); int subTableStackPosition = lua_gettop(s); // Stores the item info in it. lua_pushliteral(s, "slot"); lua_pushinteger(s, it->first); // The slot id lua_settable(s, subTableStackPosition); lua_pushliteral(s, "id"); lua_pushinteger(s, it->second.itemId); lua_settable(s, subTableStackPosition); lua_pushliteral(s, "name"); push(s, itemManager->getItem(it->second.itemId)->getName()); lua_settable(s, subTableStackPosition); // Add the sub-table as value of the main one. lua_rawseti(s, firstTableStackPosition, tableIndex); ++tableIndex; } return 1; } /** LUA entity:equip_slot (inventory) * entity:equip_slot(int slot) ** * Valid only for character entities. * * Makes the character equip the item in the given inventory slot. */ static int entity_equip_slot(lua_State *s) { Entity *ch = checkCharacter(s, 1); int inventorySlot = luaL_checkint(s, 2); Inventory inv(ch); lua_pushboolean(s, inv.equip(inventorySlot)); return 1; } /** LUA entity:equip_item (inventory) * entity:equip_item(int item_id) * entity:equip_item(string item_name) ** * Valid only for character entities. * * Makes the character equip the item id when it exists in the player's * inventory. * * **Return value:** true if equipping suceeded. false otherwise. */ static int entity_equip_item(lua_State *s) { Entity *ch = checkCharacter(s, 1); ItemClass *it = checkItemClass(s, 2); Inventory inv(ch); int inventorySlot = inv.getFirstSlot(it->getDatabaseID()); bool success = false; if (inventorySlot > -1) success = inv.equip(inventorySlot); lua_pushboolean(s, success); return 1; } /** LUA entity:unequip_slot (inventory) * entity:unequip_slot(int slot) ** * Valid only for character entities. * * Makes the character unequip the item in the given equipment slot. * * **Return value:** true upon success. false otherwise. */ static int entity_unequip_slot(lua_State *s) { Entity *ch = checkCharacter(s, 1); int equipmentSlot = luaL_checkint(s, 2); Inventory inv(ch); lua_pushboolean(s, inv.unequip(inv.getSlotItemInstance(equipmentSlot))); return 1; } /** LUA entity:unequip_item (inventory) * entity:unequip_item(int item_id) * entity:unequip_item(string item_name) ** * Valid only for character entities. * * Makes the character unequip the item(s) corresponding to the id when it * exists in the player's equipment. * * **Return value:** true when every item were unequipped from equipment. */ static int entity_unequip_item(lua_State *s) { Entity *ch = checkCharacter(s, 1); ItemClass *it = checkItemClass(s, 2); Inventory inv(ch); lua_pushboolean(s, inv.unequipItem(it->getDatabaseID())); return 1; } /** LUA_CATEGORY Character and being interaction (being) */ /** LUA chr_get_quest (being) * chr_get_quest(handle character, string name) ** * **Return value:** The quest variable named ''name'' for the given character. * * **Warning:** May only be called from an NPC talk function. * */ static int chr_get_quest(lua_State *s) { Entity *q = checkCharacter(s, 1); const char *name = luaL_checkstring(s, 2); luaL_argcheck(s, name[0] != 0, 2, "empty variable name"); Script::Thread *thread = checkCurrentThread(s); std::string value; bool res = getQuestVar(q, name, value); if (res) { push(s, value); return 1; } QuestCallback *f = new QuestThreadCallback(&LuaScript::getQuestCallback, getScript(s)); recoverQuestVar(q, name, f); thread->mState = Script::ThreadExpectingString; return lua_yield(s, 0); } /** LUA chr_set_quest (being) * chr_set_quest(handle character, string name, string value) ** * Sets the quest variable named ''name'' for the given character to the value * ''value''. */ static int chr_set_quest(lua_State *s) { Entity *q = checkCharacter(s, 1); const char *name = luaL_checkstring(s, 2); const char *value = luaL_checkstring(s, 3); luaL_argcheck(s, name[0] != 0, 2, "empty variable name"); setQuestVar(q, name, value); return 0; } /** LUA entity:set_ability_mana (being) * entity:set_ability_mana(int abilityid, int new_mana) * entity:set_ability_mana(string abilityname, int new_mana) ** * Valid only for character entities. * * Sets the mana (recharge status) of the ability to a new value for the * character. * * **Note:** When passing the ''abilityname'' as parameter make sure that it is * formatted in this way: _ (for eg. "Magic_Healingspell"). */ static int entity_set_ability_mana(lua_State *s) { Entity *c = checkCharacter(s, 1); auto *abilityInfo = checkAbility(s, 2); const int mana = luaL_checkint(s, 3); if (!c->getComponent()->setAbilityMana(abilityInfo->id, mana)) { luaL_error(s, "set_ability_mana called with ability " "that is not owned by character."); } return 0; } /** LUA entity:ability_mana (being) * entity:ability_mana(int abilityid) * entity:ability_mana(string abilityname) ** * **Return value:** The mana (recharge status) of the ability that is owned by * the character. * * **Note:** When passing the ''abilityname'' as parameter make sure that it is * formatted in this way: _ (for eg. "Magic_Healingspell"). */ static int entity_get_ability_mana(lua_State *s) { Entity *c = checkCharacter(s, 1); auto *abilityComponent = c->getComponent(); auto *abilityInfo = checkAbility(s, 2); AbilityMap::iterator it = abilityComponent->findAbility(abilityInfo->id); luaL_argcheck(s, it != abilityComponent->getAbilities().end(), 2, "character does not have ability"); lua_pushinteger(s, it->second.currentPoints); return 1; } /** LUA entity:cooldown_ability (being) * entity:cooldown_ability(int abilityid) * entity:cooldown_ability(string abilityname) ** * Starts the cooldown of the passed ability. No other ability will be useable * in this time. * * You do not need to call this if the attribute is set to ''autoconsume''. * * **Note:** When passing the ''abilityname'' as parameter make sure that it is * formatted in this way: _ (for eg. "Magic_Healingspell"). */ static int entity_cooldown_ability(lua_State *s) { Entity *c = checkCharacter(s, 1); auto *abilityComponent = c->getComponent(); auto *abilityInfo = checkAbility(s, 2); abilityComponent->startCooldown(*c, abilityInfo); return 0; } /** LUA entity:walk (being) * entity:walk(int pixelX, int pixelY [, int walkSpeed]) ** * Valid only for being entities. * * Set the desired destination in pixels for the being. * * The optional **'WalkSpeed'** is to be given in tiles per second. The average * speed is 6.0 tiles per second. If no speed is given the default speed of the * being is used. */ static int entity_walk(lua_State *s) { Entity *being = checkBeing(s, 1); const int x = luaL_checkint(s, 2); const int y = luaL_checkint(s, 3); auto *beingComponent = being->getComponent(); beingComponent->setDestination(*being, Point(x, y)); if (lua_gettop(s) >= 4) { const double speedTps = luaL_checknumber(s, 4); beingComponent->setAttribute(*being, ATTR_MOVE_SPEED_TPS, speedTps); const double modifiedSpeedTps = beingComponent->getModifiedAttribute(ATTR_MOVE_SPEED_TPS); beingComponent->setAttribute(*being, ATTR_MOVE_SPEED_RAW, utils::tpsToRawSpeed(modifiedSpeedTps)); } return 0; } /** LUA entity:destination (being) * local x, y = entity:destination() ** * Valid only for being entities. * * **Return value:** The x and y coordinates of the destination. */ static int entity_destination(lua_State *s) { Entity *being = checkBeing(s, 1); auto *beingComponent = being->getComponent(); const Point &point = beingComponent->getDestination(); lua_pushinteger(s, point.x); lua_pushinteger(s, point.y); return 2; } /** LUA entity:heal (being) * entity:heal([int value]) ** * Valid only for being entities. * * Restores ''value'' lost hit points to the being. Value can be omitted to * restore the being to full hit points. * * While you can (ab)use this function to hurt a being by using a negative * value you should rather use entity:damage for this purpose. */ static int entity_heal(lua_State *s) { Entity *being = checkBeing(s, 1); if (lua_gettop(s) == 1) // when there is only one argument being->getComponent()->heal(*being); else being->getComponent()->heal(*being, luaL_checkint(s, 2)); return 0; } /** LUA entity:name (being) * entity:name() ** * Valid only for being entities. * * **Return value:** Name of the being. */ static int entity_get_name(lua_State *s) { Entity *being = checkBeing(s, 1); push(s, being->getComponent()->getName()); return 1; } /** LUA entity:type (being) * entity:type() ** * **Return value:** Type of the given entity. These type constants are defined * in libmana-constants.lua: * * | 0 | TYPE_ITEM | * | 1 | TYPE_ACTOR | * | 2 | TYPE_NPC | * | 3 | TYPE_MONSTER | * | 4 | TYPE_CHARACTER | * | 5 | TYPE_EFFECT | * | 6 | TYPE_OTHER | */ static int entity_get_type(lua_State *s) { Entity *entity = LuaEntity::check(s, 1); lua_pushinteger(s, entity->getType()); return 1; } /** LUA entity:action (being) * entity:action() ** * Valid only for being entities. * * **Return value:** Current action of the being. These action constants are * defined in libmana-constants.lua: * * | 0 | ACTION_STAND | * | 1 | ACTION_WALK | * | 2 | ACTION_SIT | * | 3 | ACTION_DEAD | * | 4 | ACTION_HURT | */ static int entity_get_action(lua_State *s) { Entity *being = checkBeing(s, 1); lua_pushinteger(s, being->getComponent()->getAction()); return 1; } /** LUA entity:set_action (being) * entity:set_action(int action) ** * Valid only for being entities. * * Sets the current action for the being. */ static int entity_set_action(lua_State *s) { Entity *being = checkBeing(s, 1); BeingAction act = static_cast(luaL_checkint(s, 2)); being->getComponent()->setAction(*being, act); return 0; } /** LUA entity:direction (being) * entity:direction() ** * Valid only for being entities. * * **Return value:** Current direction of the being. These direction constants * are defined in libmana-constants.lua: * * | 0 | DIRECTION_DEFAULT | * | 1 | DIRECTION_UP | * | 2 | DIRECTION_DOWN | * | 3 | DIRECTION_LEFT | * | 4 | DIRECTION_RIGHT | * | 5 | DIRECTION_INVALID | */ static int entity_get_direction(lua_State *s) { Entity *being = checkBeing(s, 1); lua_pushinteger(s, being->getComponent()->getDirection()); return 1; } /** LUA entity:set_direction (being) * entity:set_direction(int direction) ** * Valid only for being entities. * * Sets the current direction of the given being. Directions are same as in * ''entity:direction''. */ static int entity_set_direction(lua_State *s) { Entity *being = checkBeing(s, 1); BeingDirection dir = static_cast(luaL_checkint(s, 2)); being->getComponent()->setDirection(*being, dir); return 0; } /** LUA entity:set_walkmask (being) * entity:set_walkmask(string mask) ** * Valid only for actor entities. * * Sets the walkmasks of an actor. The mask is a set of characters which stand * for different collision types. * * | w | Wall | * | c | Character | * | m | Monster | * * This means entity:set_walkmask("wm") will prevent the being from walking * over walls and monsters. */ static int entity_set_walkmask(lua_State *s) { Entity *being = checkActor(s, 1); unsigned char walkmask = checkWalkMask(s, 2); being->getComponent()->setWalkMask(walkmask); return 0; } /** LUA entity:walkmask (being) * entity:walkmask() ** * Valid only for actor entities. * * **Return value:** The walkmask of the actor formatted as string. (See * [[scripting#entityset_walkmask|entity:set_walkmask]]) */ static int entity_get_walkmask(lua_State *s) { Entity *being = checkBeing(s, 1); const unsigned char mask = being->getComponent()->getWalkMask(); luaL_Buffer buffer; luaL_buffinit(s, &buffer); if (mask & Map::BLOCKMASK_WALL) luaL_addstring(&buffer, "w"); if (mask & Map::BLOCKMASK_CHARACTER) luaL_addstring(&buffer, "c"); if (mask & Map::BLOCKMASK_MONSTER) luaL_addstring(&buffer, "m"); luaL_pushresult(&buffer); return 1; } /** LUA entity:warp (being) * entity:warp(int mapID, int posX, int posY) * entity:warp(string mapName, int posX, int posY) ** * Valid only for character entities. * * Teleports the character to the position ''posX'':''posY'' on the map * with the ID number ''mapID'' or name ''mapName''. The ''mapID'' can be * substituted by ''nil'' to warp the character to a new position on the * current map. */ static int entity_warp(lua_State *s) { Entity *character = checkCharacter(s, 1); int x = luaL_checkint(s, 3); int y = luaL_checkint(s, 4); bool b = lua_isnil(s, 2); if (!(b || lua_isnumber(s, 2) || lua_isstring(s, 2))) { luaL_error(s, "warp called with incorrect parameters."); return 0; } MapComposite *m; if (b) { m = checkCurrentMap(s); } else if (lua_isnumber(s, 2)) { m = MapManager::getMap(lua_tointeger(s, 2)); luaL_argcheck(s, m, 2, "invalid map id"); } else { m = MapManager::getMap(lua_tostring(s, 2)); luaL_argcheck(s, m, 2, "invalid map name"); } Map *map = m->getMap(); // If the wanted warp place is unwalkable if (!map->getWalk(x / map->getTileWidth(), y / map->getTileHeight())) { int c = 50; LOG_INFO("warp called with a non-walkable place."); do { x = rand() % map->getWidth(); y = rand() % map->getHeight(); } while (!map->getWalk(x, y) && --c); x *= map->getTileWidth(); y *= map->getTileHeight(); } GameState::enqueueWarp(character, m, Point(x, y)); return 0; } /** LUA entity:position (being) * entity:position() ** * Valid only for actor entities. * * **Return value:** The x and y position of the actor in pixels, measured from * the top-left corner of the map it is currently on. */ static int entity_get_position(lua_State *s) { Entity *being = checkActor(s, 1); const Point &p = being->getComponent()->getPosition(); lua_pushinteger(s, p.x); lua_pushinteger(s, p.y); return 2; } /** LUA entity:x (being) * entity:x() ** * Valid only for actor entities. * * **Return value:** The x position of the actor in pixels, measured from * the left border of the map it is currently on. */ static int entity_get_x(lua_State *s) { Entity *being = checkActor(s, 1); const Point &p = being->getComponent()->getPosition(); lua_pushinteger(s, p.x); return 1; } /** LUA entity:y (being) * entity:y() ** * Valid only for actor entities. * * **Return value:** The y position of the actor in pixels, measured from * the top border of the map it is currently on. */ static int entity_get_y(lua_State *s) { Entity *being = checkActor(s, 1); const Point &p = being->getComponent()->getPosition(); lua_pushinteger(s, p.y); return 1; } /** LUA entity:base_attribute (being) * entity:base_attribute(int attribute_id) ** * Valid only for being entities. * * **Return value:** Returns the value of the being's ''base attribute''. */ static int entity_get_base_attribute(lua_State *s) { Entity *being = checkBeing(s, 1); int attr = luaL_checkint(s, 2); luaL_argcheck(s, attr > 0, 2, "invalid attribute id"); lua_pushinteger(s, being->getComponent()->getAttributeBase(attr)); return 1; } /** LUA entity:set_base_attribute (being) * entity:set_base_attribute(int attribute_id, double new_value) ** * Valid only for being entities. * * Set the value of the being's ''base attribute'' to the 'new_value' parameter * given. (It can be negative). */ static int entity_set_base_attribute(lua_State *s) { Entity *being = checkBeing(s, 1); int attr = luaL_checkint(s, 2); double value = luaL_checknumber(s, 3); being->getComponent()->setAttribute(*being, attr, value); return 0; } /** entity:modified_attribute (being) * entity:modified_attribute(int attribute_id) ** * Valid only for being entities. * * *Return value:** Returns the value of the being's ''modified attribute''. * * The modified attribute is equal to the base attribute + currently applied * modifiers. * * To get to know how to configure and create modifiers, you can have a look at * the [[attributes.xml]] file and at the [[#entityapply_attribute_modifier]]() * and [[#entityremove_attribute_modifier]]() lua functions. * * Note also that items, equipment, and monsters attacks can cause attribute * modifiers. * * FIXME: This functions about applying and removing modifiers are still WIP, * because some simplifications and renaming could occur. */ static int entity_get_modified_attribute(lua_State *s) { Entity *being = checkBeing(s, 1); int attr = luaL_checkint(s, 2); luaL_argcheck(s, attr > 0, 2, "invalid attribute id"); const double value = being->getComponent()->getModifiedAttribute(attr); lua_pushinteger(s, value); return 1; } /** LUA entity:apply_attribute_modifier (being) * entity:apply_attribute_modifier(int attribute_id, double value, * unsigned int layer, [unsigned short duration, * [unsigned int effect_id]]) ** * Valid only for being entities. * * **Parameters description:** \\ * * **value** (double): The modifier value (can be negative). * * **layer** (unsigned int): The layer or level of the modifier. * As modifiers are stacked on an attribute, the layer determines * where the modifier will be inserted. Also, when adding a modifier, * all the modifiers with an higher ayer value will also be recalculated. * * **duration** (unsigned short): The modifier duration in ticks((A tick is * equal to 100ms.)). If set to 0, the modifier is permanent. * * **effect_id** (unsigned int): Set and keep that parameter when you want * to retrieve the exact layer later. (FIXME: Check this.) */ static int entity_apply_attribute_modifier(lua_State *s) { Entity *being = checkBeing(s, 1); int attr = luaL_checkint(s,2); double value = luaL_checknumber(s, 3); int layer = luaL_checkint(s, 4); int duration = luaL_optint(s, 5, 0); int effectId = luaL_optint(s, 6, 0); being->getComponent()->applyModifier(*being, attr, value, layer, duration, effectId); return 0; } /** LUA entity:remove_attribute_modifier (being) * entity:remove_attribute_modifier(int attribute_id, * double value, unsigned int layer) ** * Valid only for being entities. * * Permits to remove an attribute modifier by giving its value and its layer. */ static int entity_remove_attribute_modifier(lua_State *s) { Entity *being = checkBeing(s, 1); int attr = luaL_checkint(s, 2); double value = luaL_checknumber(s, 3); int layer = luaL_checkint(s, 4); int effectId = luaL_optint(s, 5, 0); being->getComponent()->removeModifier(*being, attr, value, layer, effectId); return 0; } /** LUA entity:gender (being) * entity:gender() ** * Valid only for being entities. * * **Return value:** The gender of the being. These gender constants are * defined in libmana-constants.lua: * * | 0 | GENDER_MALE | * | 1 | GENDER_FEMALE | * | 2 | GENDER_UNSPECIFIED | */ static int entity_get_gender(lua_State *s) { Entity *b = checkBeing(s, 1); lua_pushinteger(s, b->getComponent()->getGender()); return 1; } /** LUA entity:set_gender (being) * entity:set_gender(int gender) ** * Valid only for being entities. * * Sets the gender of the being. * * The gender constants are defined in libmana-constants.lua: * * | 0 | GENDER_MALE | * | 1 | GENDER_FEMALE | * | 2 | GENDER_UNSPECIFIED | */ static int entity_set_gender(lua_State *s) { Entity *b = checkBeing(s, 1); const int gender = luaL_checkinteger(s, 2); b->getComponent()->setGender(getGender(gender)); return 0; } /** LUA entity:level (being) * entity:level() * entity:level(int skill_id) * entity:level(string skill_name) ** * Valid only for character entities. * * **Return value:** Returns the level of the character. If a skill is passed * (either by name or id) the level of this skill is returned. * * **Note:** If the skill is provided as string (''skill_name'') you have to * use this format: _. So for example: "Weapons_Unarmed". */ static int entity_get_level(lua_State *s) { Entity *ch = checkCharacter(s, 1); auto *characterComponent = ch->getComponent(); if (lua_gettop(s) > 1) { int skillId = checkSkill(s, 2); lua_pushinteger(s, characterComponent->levelForExp( characterComponent->getExperience(skillId))); } else { lua_pushinteger(s, characterComponent->getLevel()); } return 1; } /** LUA entity:xp (being) * entity:xp(int skill_id) * entity:xp(string skill_name) ** * Valid only for character entities. * * **Return value:** The total experience collected by the character in * ''skill''. * * If the skill is provided as string (''skillname'') you have to use this * format: _. So for example: "Weapons_Unarmed". */ static int entity_get_xp(lua_State *s) { Entity *c = checkCharacter(s, 1); int skill = checkSkill(s, 2); const int exp = c->getComponent()->getExperience(skill); lua_pushinteger(s, exp); return 1; } /** LUA entity:give_xp (being) * entity:give_xp(int skill, int amount [, int optimalLevel]) * entity:give_xp(string skillname, int amount [, int optimalLevel]) ** * Valid only for character entities. * * Gives the character ''amount'' experience in skill ''skill''. When an * optimal level is set (over 0), the experience is reduced when the characters * skill level is beyond this. If the skill is provided as string * (''skillname'') you have to use this format: _. * So for example: "Weapons_Unarmed". */ static int entity_give_xp(lua_State *s) { Entity *c = checkCharacter(s, 1); int skill = checkSkill(s, 2); const int exp = luaL_checkint(s, 3); const int optimalLevel = luaL_optint(s, 4, 0); c->getComponent()->receiveExperience(skill, exp, optimalLevel); return 0; } /** LUA xp_for_level (being) * xp_for_level(int level) ** * **Return value:** Returns the total experience necessary (counted from * level 0) for reaching ''level'' in any skill. */ static int xp_for_level(lua_State *s) { const int level = luaL_checkint(s, 1); lua_pushinteger(s, CharacterComponent::expForLevel(level)); return 1; } /** LUA entity:add_hit_taken (being) * add_hit_taken(int damage) ** * Adds a damage value to the taken hits of a being. This list will be send to * all clients in the view range in order to allow to display the hit particles. */ static int entity_add_hit_taken(lua_State *s) { Entity *c = checkBeing(s, 1); const int damage = luaL_checkinteger(s, 2); c->getComponent()->addHitTaken(damage); return 0; } /** LUA entity:hair_color (being) * entity:hair_color() ** * Valid only for character entities. * * **Return value:** The hair color ID of the character. */ static int entity_get_hair_color(lua_State *s) { Entity *c = checkCharacter(s, 1); lua_pushinteger(s, c->getComponent()->getHairColor()); return 1; } /** LUA entity:set_hair_color (being) * entity:set_hair_color(int color) ** * Valid only for character entities. * * Sets the hair color ID of the character to ''color''. */ static int entity_set_hair_color(lua_State *s) { Entity *c = checkCharacter(s, 1); const int color = luaL_checkint(s, 2); luaL_argcheck(s, color >= 0, 2, "invalid color id"); c->getComponent()->setHairColor(color); c->getComponent()->raiseUpdateFlags( UPDATEFLAG_LOOKSCHANGE); return 0; } /** LUA entity:hair_style (being) * entity:hair_style() ** * Valid only for character entities. * * **Return value:** The hair style ID of the character. */ static int entity_get_hair_style(lua_State *s) { Entity *c = checkCharacter(s, 1); lua_pushinteger(s, c->getComponent()->getHairStyle()); return 1; } /** LUA entity:set_hair_style (being) * entity:set_hair_style(int style) ** * Valid only for character entities. * * Sets the hair style ID of the character to ''style''. */ static int entity_set_hair_style(lua_State *s) { Entity *c = checkCharacter(s, 1); const int style = luaL_checkint(s, 2); luaL_argcheck(s, style >= 0, 2, "invalid style id"); c->getComponent()->setHairStyle(style); c->getComponent()->raiseUpdateFlags( UPDATEFLAG_LOOKSCHANGE); return 0; } /** LUA entity:kill_count (being) * entity:kill_count(int monsterId) * entity:kill_count(string monsterName) * entity:kill_count(MonsterClass monsterClass) ** * Valid only for character entities. * * **Return value:** The total number of monsters of the specy (passed either * as monster id, monster name or monster class) the character has killed * during its career. */ static int entity_get_kill_count(lua_State *s) { Entity *c = checkCharacter(s, 1); MonsterClass *monster = checkMonsterClass(s, 2); lua_pushinteger(s, c->getComponent()->getKillCount(monster->getId())); return 1; } /** LUA entity:rights (being) * entity:rights() ** * Valid only for character entities. * * **Return value:** The access level of the account of the character. */ static int entity_get_rights(lua_State *s) { Entity *c = checkCharacter(s, 1); lua_pushinteger(s, c->getComponent()->getAccountLevel()); return 1; } /** LUA entity:kick (being) * entity:kick() ** * Valid only for character entities. * * Kicks the character. */ static int entity_kick(lua_State *s) { Entity *ch = checkCharacter(s, 1); MessageOut kickmsg(GPMSG_CONNECT_RESPONSE); kickmsg.writeInt8(ERRMSG_ADMINISTRATIVE_LOGOFF); ch->getComponent()->getClient()->disconnect(kickmsg); return 0; } /** LUA entity:mapid (being) * entity:mapid() ** * **Return value:** the id of the map where the entity is located or nil if * there is none. */ static int entity_get_mapid(lua_State *s) { Entity *entity = LuaEntity::check(s, 1); if (MapComposite *map = entity->getMap()) lua_pushinteger(s, map->getID()); else lua_pushnil(s); return 1; } /** LUA chr_request_quest (being) * chr_request_quest(handle character, string questvariable, Ref function) ** * Requests the questvar from the account server. This will make it available in * the quest cache after some time. The passed function will be called back as * soon the quest var is available. */ static int chr_request_quest(lua_State *s) { Entity *ch = checkCharacter(s, 1); const char *name = luaL_checkstring(s, 2); luaL_argcheck(s, name[0] != 0, 2, "empty variable name"); luaL_checktype(s, 3, LUA_TFUNCTION); std::string value; bool res = getQuestVar(ch, name, value); if (res) { // Already cached, call passed callback immediately Script *script = getScript(s); Script::Ref callback; script->assignCallback(callback); script->prepare(callback); script->push(ch); script->push(name); script->push(value); script->execute(ch->getMap()); return 0; } QuestCallback *f = new QuestRefCallback(getScript(s), name); recoverQuestVar(ch, name, f); return 0; } /** LUA chr_try_get_quest (being) * chr_try_get_quest(handle character, string questvariable) ** * Callback for checking if a quest variable is available in cache. * * **Return value:** It will return the variable if it is or nil * if it is not in cache. */ static int chr_try_get_quest(lua_State *s) { Entity *q = checkCharacter(s, 1); const char *name = luaL_checkstring(s, 2); luaL_argcheck(s, name[0] != 0, 2, "empty variable name"); std::string value; bool res = getQuestVar(q, name, value); if (res) push(s, value); else lua_pushnil(s); return 1; } /** LUA get_character_by_name (being) * get_character_by_name(string name) ** * Tries to find an online character by name. * * **Return value** the character handle or nil if there is none. */ static int get_character_by_name(lua_State *s) { const char *name = luaL_checkstring(s, 1); push(s, gameHandler->getCharacterByNameSlow(name)); return 1; } /** LUA chr_get_post (being) * chr_get_post(handle character) ** * Gets the post for the character. */ static int chr_get_post(lua_State *s) { Entity *c = checkCharacter(s, 1); Script *script = getScript(s); Script::Thread *thread = checkCurrentThread(s, script); PostCallback f = { &LuaScript::getPostCallback, script }; postMan->getPost(c, f); thread->mState = Script::ThreadExpectingTwoStrings; return lua_yield(s, 0); } /** LUA entity:register (being) * entity:register() ** * Makes the server call the on_being_death and on_entity_remove callbacks * when the being dies or the entity is removed from the map. * * **Note:** You should never need to call this in most situations. It is * handeled by the libmana.lua */ static int entity_register(lua_State *s) { Entity *entity = LuaEntity::check(s, 1); Script *script = getScript(s); entity->signal_removed.connect(sigc::mem_fun(script, &Script::processRemoveEvent)); if (BeingComponent *bc = entity->findComponent()) bc->signal_died.connect(sigc::mem_fun(script, &Script::processDeathEvent)); return 0; } /** LUA entity:shake_screen (being) * entity:shake_screen(int x, int y[, float strength, int radius]) ** * Valid only for character entities. * * Shakes the screen for a given character. */ static int entity_shake_screen(lua_State *s) { Entity *c = checkCharacter(s, 1); const int x = luaL_checkint(s, 2); const int y = luaL_checkint(s, 3); MessageOut msg(GPMSG_SHAKE); msg.writeInt16(x); msg.writeInt16(y); if (lua_isnumber(s, 4)) msg.writeInt16((int) (lua_tonumber(s, 4) * 10000)); if (lua_isnumber(s, 5)) msg.writeInt16(lua_tointeger(s, 5)); c->getComponent()->getClient()->send(msg); return 0; } /** LUA entity:show_text_particle (being) * entity:show_text_particle(string text) ** * Valid only for character entities. * * Shows a text particle on a client. This effect is only visible for the * character. */ static int entity_show_text_particle(lua_State *s) { Entity *c = checkCharacter(s, 1); const char *text = luaL_checkstring(s, 2); MessageOut msg(GPMSG_CREATE_TEXT_PARTICLE); msg.writeString(text); c->getComponent()->getClient()->send(msg); return 0; } /** LUA entity:give_ability (being) * entity:give_ability(int ability) ** * Valid only for character and monster entities. * * Enables a ability for a character. */ static int entity_give_ability(lua_State *s) { // cost_type is ignored until we have more than one cost type Entity *b = checkBeing(s, 1); auto *abilityInfo = checkAbility(s, 2); const int currentMana = luaL_optint(s, 3, 0); b->getComponent()->giveAbility(abilityInfo->id, currentMana); return 0; } /** LUA entity:has_ability (being) * entity:has_ability(int ability) ** * Valid only for character and monster entities. * * **Return value:** True if the character has the ability, false otherwise. */ static int entity_has_ability(lua_State *s) { Entity *b = checkBeing(s, 1); const int ability = luaL_checkint(s, 2); lua_pushboolean(s, b->getComponent()->hasAbility(ability)); return 1; } /** LUA entity:take_ability (being) * entity:take_ability(int ability) ** * Valid only for character and monster entities. * * Removes a ability from a entity. * * **Return value:** True if removal was successful, false otherwise (in case * the character did not have the ability). */ static int entity_take_ability(lua_State *s) { Entity *b = checkBeing(s, 1); const int ability = luaL_checkint(s, 2); auto *abilityComponent = b->getComponent(); lua_pushboolean(s, abilityComponent->hasAbility(ability)); abilityComponent->takeAbility(ability); return 1; } /** LUA entity:use_ability (being) * entity:use_ability(int ability) ** * Valid only for character and monster entities. * * Makes the entity using the given ability if it is available and recharged. * * **Return value:** True if the ability was used successfully. False otherwise * (if the ability is not available for the entity or was not recharged). */ static int entity_use_ability(lua_State *s) { Entity *b = checkBeing(s, 1); const int ability = luaL_checkint(s, 2); bool targetIsBeing = lua_gettop(s) == 3; auto *abilityComponent = b->getComponent(); if (targetIsBeing) { Entity *target = checkBeing(s, 3); lua_pushboolean(s, abilityComponent->useAbilityOnBeing(*b, ability, target)); } else { const int x = luaL_checkint(s, 3); const int y = luaL_checkint(s, 4); lua_pushboolean(s, abilityComponent->useAbilityOnPoint(*b, ability, x, y)); } return 1; } /** LUA_CATEGORY Monster (monster) */ /** LUA entity:monster_id (monster) * entity:monster_id() ** * Valid only for monster entities. * * **Return value:** The id of the monster class. */ static int entity_get_monster_id(lua_State *s) { Entity *monster = checkMonster(s, 1); MonsterComponent *monsterComponent = monster->getComponent(); lua_pushinteger(s, monsterComponent->getSpecy()->getId()); return 1; } /** LUA_CATEGORY Status effects (statuseffects) */ /** LUA entity:apply_status (statuseffects) * entity:apply_status(int status_id, int time) ** * Valid only for being entities. * * Gives a being a status effect ''status_id'', status effects don't work on * NPCs. ''time'' is in game ticks. */ static int entity_apply_status(lua_State *s) { Entity *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); const int time = luaL_checkint(s, 3); being->getComponent()->applyStatusEffect(id, time); return 0; } /** LUA entity:remove_status (statuseffects) * entity:remove_status(int status_id) ** * Valid only for being entities. * * Removes a given status effect from a being. */ static int entity_remove_status(lua_State *s) { Entity *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); being->getComponent()->removeStatusEffect(id); return 0; } /** LUA entity:has_status (statuseffects) * entity:has_status(int status_id) ** * Valid only for being entities. * * **Return value:** True if the being has a given status effect. */ static int entity_has_status(lua_State *s) { Entity *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); lua_pushboolean(s, being->getComponent()->hasStatusEffect(id)); return 1; } /** LUA entity:status_time (statuseffects) * entity:status_time(int status_id) ** * Valid only for being entities. * * **Return Value:** Number of ticks remaining on a status effect. */ static int entity_get_status_time(lua_State *s) { Entity *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); lua_pushinteger(s, being->getComponent()->getStatusEffectTime(id)); return 1; } /** LUA entity:set_status_time (statuseffects) * entity:set_status_time(int status_id, int time) ** * Valid only for being entities. * * Sets the time on a status effect a target being already has. */ static int entity_set_status_time(lua_State *s) { Entity *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); const int time = luaL_checkint(s, 3); being->getComponent()->setStatusEffectTime(id, time); return 0; } /** LUA_CATEGORY Map information (mapinformation) */ /** LUA get_map_id (mapinformation) * get_map_id() ** * **Return value:** The ID number of the map the script runs on. */ static int get_map_id(lua_State *s) { Script *script = getScript(s); if (MapComposite *mapComposite = script->getContext()->map) lua_pushinteger(s, mapComposite->getID()); else lua_pushnil(s); return 1; } /** LUA get_map_property (mapinformation) * get_map_property(string key) ** * **Return value:** The value of the property ''key'' of the current map. The * string is empty if the property ''key'' does not exist. */ static int get_map_property(lua_State *s) { const char *property = luaL_checkstring(s, 1); Map *map = checkCurrentMap(s)->getMap(); push(s, map->getProperty(property)); return 1; } /** LUA is_walkable (mapinformation) * is_walkable(int x, int y) ** * **Return value:** True if ''x'':''y'' is a walkable pixel * on the current map. */ static int is_walkable(lua_State *s) { const int x = luaL_checkint(s, 1); const int y = luaL_checkint(s, 2); Map *map = checkCurrentMap(s)->getMap(); // If the wanted warp place is unwalkable if (map->getWalk(x / map->getTileWidth(), y / map->getTileHeight())) lua_pushboolean(s, 1); else lua_pushboolean(s, 0); return 1; } /** LUA get_path_length (mapinformation) * get_path_lenght(int startX, int startY, int destX, int destY, int maxRange) * get_path_lenght(int startX, int startY, int destX, int destY, int maxRange, * string walkmask) ** * Tries to find a path from the start coordinates to the target ones with a * maximum of ''maxRange'' steps (in tiles). * * If no ''walkmask'' is passed '''w''' is used. * * **Return value:** The number of steps (in tiles) are required to reach * the target or 0 if no path was found. */ static int get_path_length(lua_State *s) { const int startX = luaL_checkint(s, 1); const int startY = luaL_checkint(s, 2); const int destX = luaL_checkint(s, 3); const int destY = luaL_checkint(s, 4); unsigned maxRange = luaL_checkint(s, 5); unsigned char walkmask = BLOCKTYPE_WALL; if (lua_gettop(s) > 5) walkmask = checkWalkMask(s, 6); Map *map = checkCurrentMap(s)->getMap(); Path path = map->findPath(startX / map->getTileWidth(), startY / map->getTileHeight(), destX / map->getTileWidth(), destY / map->getTileHeight(), walkmask, maxRange); lua_pushinteger(s, path.size()); return 1; } /** LUA map_get_pvp (mapinformation) * map_get_pvp() ** * **Return value:** The pvp situation of the map. * * There are constants for the different pvp situations in the libmana-constants.lua: * * | 0 | PVP_NONE | * | 1 | PVP_FREE | */ static int map_get_pvp(lua_State *s) { MapComposite *m = checkCurrentMap(s); lua_pushinteger(s, m->getPvP()); return 1; } /** LUA_CATEGORY Persistent variables (variables) */ /** LUA on_mapvar_changed (variables) * on_mapvar_changed(string key, function func) ** * Registers a callback to the key. This callback will be called with the key * and value of the changed variable. * * **Example:** * on_mapvar_changed(key, function(key, value) * log(LOG_DEBUG, "mapvar " .. key .. " has new value " .. value) * end) */ static int on_mapvar_changed(lua_State *s) { const char *key = luaL_checkstring(s, 1); luaL_checktype(s, 2, LUA_TFUNCTION); luaL_argcheck(s, key[0] != 0, 2, "empty variable name"); MapComposite *m = checkCurrentMap(s); m->setMapVariableCallback(key, getScript(s)); return 0; } /** LUA on_worldvar_changed (variables) * on_worldvar_changed(string key, function func) ** * Registers a callback to the key. This callback will be called with the key * and value of the changed variable. * * **Example:** * on_worldvar_changed(key, function(key, value) * log(LOG_DEBUG, "worldvar " .. key .. " has new value " .. value) * end) */ static int on_worldvar_changed(lua_State *s) { const char *key = luaL_checkstring(s, 1); luaL_checktype(s, 2, LUA_TFUNCTION); luaL_argcheck(s, key[0] != 0, 2, "empty variable name"); MapComposite *m = checkCurrentMap(s); m->setWorldVariableCallback(key, getScript(s)); return 0; } /** LUA getvar_map (variables) * getvar_map(string variablename) ** * **Return value:** the value of a persistent map variable. * * **See:** [[scripting#map|map[]]] for an easier way to get a map variable. */ static int getvar_map(lua_State *s) { const char *name = luaL_checkstring(s, 1); luaL_argcheck(s, name[0] != 0, 1, "empty variable name"); MapComposite *map = checkCurrentMap(s); push(s, map->getVariable(name)); return 1; } /** LUA setvar_map (variables) * setvar_map(string variablename, string value) ** * Sets the value of a persistent map variable. * * **See:** [[scripting#map|map[]]] for an easier way to get a map variable. */ static int setvar_map(lua_State *s) { const char *name = luaL_checkstring(s, 1); const char *value = luaL_checkstring(s, 2); luaL_argcheck(s, name[0] != 0, 1, "empty variable name"); MapComposite *map = checkCurrentMap(s); map->setVariable(name, value); return 0; } /** LUA getvar_world (variables) * getvar_world(string variablename) ** * Gets the value of a persistent global variable. * * **See:** [[scripting#world|world[]]] for an easier way to get a map variable. */ static int getvar_world(lua_State *s) { const char *name = luaL_checkstring(s, 1); luaL_argcheck(s, name[0] != 0, 1, "empty variable name"); push(s, GameState::getVariable(name)); return 1; } /** LUA setvar_world (variables) * setvar_world(string variablename, string value) ** * Sets the value of a persistent global variable. * * **See:** [[scripting#world|world[]]] for an easier way to get a map variable. */ static int setvar_world(lua_State *s) { const char *name = luaL_checkstring(s, 1); const char *value = luaL_checkstring(s, 2); luaL_argcheck(s, name[0] != 0, 1, "empty variable name"); GameState::setVariable(name, value); return 0; } /** LUA_CATEGORY Logging (logging) */ /** LUA log (logging) * log(int log_level, string message) ** * Log something at the specified log level. The available log levels are: * | 0 | LOG_FATAL | * | 1 | LOG_ERROR | * | 2 | LOG_WARNING | * | 3 | LOG_INFO | * | 4 | LOG_DEBUG | */ static int log(lua_State *s) { using utils::Logger; const int loglevel = luaL_checkint(s, 1); luaL_argcheck(s, loglevel >= Logger::Fatal && loglevel <= Logger::Debug, 1, "invalid log level"); const std::string message = luaL_checkstring(s, 2); Logger::output(message, (Logger::Level) loglevel); return 0; } /** LUA_CATEGORY Area of Effect (area) * In order to easily use area of effects in your items or in your scripts, * the following functions are available: */ /** LUA get_beings_in_circle (area) * get_beings_in_circle(int x, int y, int radius) * get_beings_in_circle(handle actor, int radius) ** * **Return value:** This function returns a lua table of all beings in a * circle of radius (in pixels) ''radius'' centered either at the pixel at * (''x'', ''y'') or at the position of ''being''. */ static int get_beings_in_circle(lua_State *s) { int x, y, r; if (lua_isuserdata(s, 1)) { Entity *b = checkActor(s, 1); const Point &pos = b->getComponent()->getPosition(); x = pos.x; y = pos.y; r = luaL_checkint(s, 2); } else { x = luaL_checkint(s, 1); y = luaL_checkint(s, 2); r = luaL_checkint(s, 3); } MapComposite *m = checkCurrentMap(s); //create a lua table with the beings in the given area. lua_newtable(s); int tableStackPosition = lua_gettop(s); int tableIndex = 1; for (BeingIterator i(m->getAroundPointIterator(Point(x, y), r)); i; ++i) { Entity *b = *i; char t = b->getType(); if (t == OBJECT_NPC || t == OBJECT_CHARACTER || t == OBJECT_MONSTER) { auto *actorComponent = b->getComponent(); if (Collision::circleWithCircle(actorComponent->getPosition(), actorComponent->getSize(), Point(x, y), r)) { push(s, b); lua_rawseti(s, tableStackPosition, tableIndex); tableIndex++; } } } return 1; } /** LUA get_beings_in_rectangle (area) * get_beings_in_rectangle(int x, int y, int width, int height) ** * **Return value:** An table of being entities within the rectangle. * All parameters have to be passed as pixels. */ static int get_beings_in_rectangle(lua_State *s) { const int x = luaL_checkint(s, 1); const int y = luaL_checkint(s, 2); const int w = luaL_checkint(s, 3); const int h = luaL_checkint(s, 4); MapComposite *m = checkCurrentMap(s); //create a lua table with the beings in the given area. lua_newtable(s); int tableStackPosition = lua_gettop(s); int tableIndex = 1; Rectangle rect = {x, y ,w, h}; for (BeingIterator i(m->getInsideRectangleIterator(rect)); i; ++i) { Entity *b = *i; char t = b->getType(); if ((t == OBJECT_NPC || t == OBJECT_CHARACTER || t == OBJECT_MONSTER) && rect.contains(b->getComponent()->getPosition())) { push(s, b); lua_rawseti(s, tableStackPosition, tableIndex); tableIndex++; } } return 1; } /** LUA get_distance (area) * get_distance(handle being1, handle being2) * get_distance(int x1, int y1, int x2, int y2) ** * **Return value:** The distance between the two beings or the two points * in pixels. */ static int get_distance(lua_State *s) { int x1, y1, x2, y2; if (lua_gettop(s) == 2) { Entity *being1 = checkBeing(s, 1); Entity *being2 = checkBeing(s, 2); x1 = being1->getComponent()->getPosition().x; y1 = being1->getComponent()->getPosition().y; x2 = being2->getComponent()->getPosition().x; y2 = being2->getComponent()->getPosition().y; } else { x1 = luaL_checkint(s, 1); y1 = luaL_checkint(s, 2); x2 = luaL_checkint(s, 3); y2 = luaL_checkint(s, 4); } const int dx = x1 - x2; const int dy = y1 - y2; const float dist = sqrt((dx * dx) + (dy * dy)); lua_pushinteger(s, dist); return 1; } /** LUA_CATEGORY Ability info class (abilityinfo) * See the [[abilitys.xml#A script example|abilitys Documentation]] for a * script example */ /** LUA get_ability_info (abilityinfo) * get_ability_info(int abilityId) * get_ability_info(string abilityName) ** * **Return value:** This function returns a object of the abilityinfo class. * See below for usage of that object. * * **Note:** When passing the ''abilityName'' as parameter make sure that it is * formatted in this way: _ (for eg. "Magic_Healingspell"). */ static int get_ability_info(lua_State *s) { auto *abilityInfo = checkAbility(s, 1); LuaAbilityInfo::push(s, abilityInfo); return 1; } /** LUA abilityinfo:name (abilityinfo) * abilityinfo:name() ** * ** Return value:** The name of the abilityinfo object. * * **Note:** See [[scripting#get_ability_info|get_ability_info]] for getting a * abilityinfo object. */ static int abilityinfo_get_name(lua_State *s) { auto *info = LuaAbilityInfo::check(s, 1); push(s, info->name); return 1; } /** LUA abilityinfo:needed_mana (abilityinfo) * abilityinfo:needed_mana() ** * ** Return value:** The mana that is needed to use the ability * * **Note:** See [[scripting#get_ability_info|get_ability_info]] for getting a * abilityinfo object. */ static int abilityinfo_get_needed_mana(lua_State *s) { auto *info = LuaAbilityInfo::check(s, 1); lua_pushinteger(s, info->neededPoints); return 1; } /** LUA abilityinfo:rechargeable (abilityinfo) * abilityinfo:rechargeable() ** * ** Return value:** A boolean value that indicates whether the ability is * rechargeable or usuable without recharge. * * **Note:** See [[scripting#get_ability_info|get_ability_info]] for getting * a abilityinfo object. */ static int abilityinfo_is_rechargeable(lua_State *s) { auto *info = LuaAbilityInfo::check(s, 1); lua_pushboolean(s, info->rechargeable); return 1; } /** LUA abilityinfo:on_use (abilityinfo) * abilityinfo:on_use(function callback) ** * Assigns the ''callback'' as callback for the use event. This function will * be called everytime a character uses a ability. * * **Note:** See [[scripting#get_ability_info|get_ability_info]] for getting * a abilityinfo object. */ static int abilityinfo_on_use(lua_State *s) { auto *info = LuaAbilityInfo::check(s, 1); Script *script = getScript(s); luaL_checktype(s, 2, LUA_TFUNCTION); script->assignCallback(info->useCallback); return 0; } /** LUA abilityinfo:on_recharged (abilityinfo) * abilityinfo:on_recharged(function callback) ** * Assigns the ''callback'' as callback for the recharged event. This function * will be called everytime when the ability is fully recharged. * * **Note:** See [[scripting#get_ability_info|get_ability_info]] for getting * a abilityinfo object. */ static int abilityinfo_on_recharged(lua_State *s) { auto *info = LuaAbilityInfo::check(s, 1); Script *script = getScript(s); luaL_checktype(s, 2, LUA_TFUNCTION); script->assignCallback(info->rechargedCallback); return 0; } /** LUA abilityinfo:category (abilityinfo) * abilityinfo:category(function callback) ** * **Return value:** The set-name of the ability as defined in the * [[abilities.xml]] * * **Note:** See [[scripting#get_ability_info|get_ability_info]] for getting * a abilityinfo object. */ static int abilitiyinfo_get_category(lua_State *s) { auto *info = LuaAbilityInfo::check(s, 1); push(s, info->categoryName); return 1; } /** LUA_CATEGORY Status effect class (statuseffectclass) */ /** LUA get_status_effect (statuseffectclass) * get_status_effect(string name) ** * **Return value:** This function returns a object of the statuseffect class. * See below for usage of that object. */ static int get_status_effect(lua_State *s) { const char *name = luaL_checkstring(s, 1); LuaStatusEffect::push(s, StatusManager::getStatusByName(name)); return 1; } /** LUA statuseffect:on_tick (statuseffectclass) * statuseffect:on_tick(function callback) ** * Sets the callback that gets called for every tick when the status effect * is active. * * **Note:** See [[scripting#get_status_effect|get_status_effect]] for getting * a statuseffect object. */ 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; } /** LUA_CATEGORY Monster class (monsterclass) */ /** LUA get_monster_class (monsterclass) * get_monster_class(int monsterid) * get_monster_class(string monstername) ** * **Return value:** This function returns a object of the monster class. * See below for usage of that object. */ static int get_monster_class(lua_State *s) { LuaMonsterClass::push(s, checkMonsterClass(s, 1)); return 1; } /** LUA get_monster_classes (monsterclass) * get_monster_classes() ** * **Return value:** A Table with all monster classes. The id of the monster * is the key. The monster class itself the value. See below for the usage of * this object. */ static int get_monster_classes(lua_State *s) { pushSTLContainer(s, monsterManager->getMonsterClasses()); return 1; } /** LUA monsterclass:on_update (monsterclass) * monsterclass:on_update(function callback) ** * Assigns the ''callback'' as callback for the monster update event. This * callback will be called every tick for each monster of that class. * * **Note:** See [[scripting#get_monster_class|get_monster_class]] for getting * a monsterclass object. */ 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; } /** LUA monsterclass:name (monsterclass) * monsterclass:name() ** * **Return value:** The name of the monster class. */ static int monster_class_get_name(lua_State *s) { MonsterClass *monsterClass = LuaMonsterClass::check(s, 1); push(s, monsterClass->getName()); return 1; } /** LUA_CATEGORY Map object class (mapobjectclass) */ /** LUA map_get_objects (mapobjectclass) * map_get_objects() * map_get_objects(string type) ** * **Return value:** A table of all objects or a table of all objects of the * given ''type''. See below for usage of these objects. */ static int map_get_objects(lua_State *s) { const bool filtered = (lua_gettop(s) == 1); const char *filter; if (filtered) { filter = luaL_checkstring(s, 1); } MapComposite *m = checkCurrentMap(s); const std::vector &objects = m->getMap()->getObjects(); if (!filtered) pushSTLContainer(s, objects); else { std::vector filteredObjects; for (std::vector::const_iterator it = objects.begin(); it != objects.end(); ++it) { if (utils::compareStrI((*it)->getType(), filter) == 0) { filteredObjects.push_back(*it); } } pushSTLContainer(s, filteredObjects); } return 1; } /** LUA mapobject:property (mapobjectclass) * mapobject:property(string key) ** * **Return value:** The value of the property of the key ''key'' or nil if * the property does not exists. * * **Note:** See [[scripting#map_get_objects|map_get_objects]] for getting a * monsterclass object. */ static int map_object_get_property(lua_State *s) { const char *key = luaL_checkstring(s, 2); MapObject *obj = LuaMapObject::check(s, 1); std::string property = obj->getProperty(key); if (!property.empty()) { push(s, property); return 1; } else { // scripts can check for nil return 0; } } /** LUA mapobject:bounds (mapobjectclass) * mapobject:bounds() ** * **Return value:** x, y position and height, width of the ''mapobject''. * * **Example use:** * local x, y, width, height = my_mapobject:bounds() * * **Note:** See [[scripting#map_get_objects|map_get_objects]] for getting a * mapobject object. */ static int map_object_get_bounds(lua_State *s) { MapObject *obj = LuaMapObject::check(s, 1); const Rectangle &bounds = obj->getBounds(); lua_pushinteger(s, bounds.x); lua_pushinteger(s, bounds.y); lua_pushinteger(s, bounds.w); lua_pushinteger(s, bounds.h); return 4; } /** LUA mapobject:name (mapobjectclass) * mapobject:name() ** * **Return value:** Name as set in the mapeditor of the ''mapobject''. * * **Note:** See [[scripting#map_get_objects|map_get_objects]] for getting * a mapobject object. */ static int map_object_get_name(lua_State *s) { MapObject *obj = LuaMapObject::check(s, 1); push(s, obj->getName()); return 1; } /** LUA mapobject:type (mapobjectclass) * mapobject:type() ** * **Return value:** Type as set in the mapeditor of the ''mapobject''. * * **Note:** See [[scripting#map_get_objects|map_get_objects]] for getting * a mapobject object. */ static int map_object_get_type(lua_State *s) { MapObject *obj = LuaMapObject::check(s, 1); push(s, obj->getType()); return 1; } /** LUA_CATEGORY Item class (itemclass) */ /** LUA get_item_class (itemclass) * get_item_class(int itemid) * get_item_class(string itemname) ** * **Return value:** This function returns a object of the item class. * See below for usage of that object. */ static int get_item_class(lua_State *s) { LuaItemClass::push(s, checkItemClass(s, 1)); return 1; } /** LUA itemclass:on (itemclass) * itemclass:on(string event, function callback) ** * Assigns ''callback'' as callback for the ''event'' event. * * **Note:** See [[scripting#get_item_class|get_item_class]] for getting * a itemclass object. */ 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; } /** LUA itemclass:name (itemclass) * itemclass:name() ** * **Return value:** The name of the item class. */ static int item_class_get_name(lua_State *s) { ItemClass *itemClass = LuaItemClass::check(s, 1); push(s, itemClass->getName()); return 1; } /** * Returns four useless tables for testing the STL container push wrappers. * This function can be removed when there are more useful functions which use * them. */ static int test_tableget(lua_State *s) { std::list list; std::vector svector; std::vector ivector; std::map map; std::set set; LOG_INFO("Pushing Float List"); list.push_back(12.636); list.push_back(0.0000000045656); list.push_back(185645445634566.346); list.push_back(7835458.11); pushSTLContainer(s, list); LOG_INFO("Pushing String Vector"); svector.push_back("All"); svector.push_back("your"); svector.push_back("base"); svector.push_back("are"); svector.push_back("belong"); svector.push_back("to"); svector.push_back("us!"); pushSTLContainer(s, svector); LOG_INFO("Pushing Integer Vector"); ivector.resize(10); for (int i = 1; i < 10; i++) ivector[i - 1] = i * i; pushSTLContainer(s, ivector); LOG_INFO("Pushing String/String Map"); map["Apple"] = "red"; map["Banana"] = "yellow"; map["Lime"] = "green"; map["Plum"] = "blue"; pushSTLContainer(s, map); LOG_INFO("Pushing Integer Set"); set.insert(12); set.insert(8); set.insert(14); set.insert(10); pushSTLContainer(s, set); return 5; } static int require_loader(lua_State *s) { // Add .lua extension (maybe only do this when it doesn't have it already) const char *file = luaL_checkstring(s, 1); std::string filename = file; filename.append(".lua"); const std::string path = ResourceManager::resolve(filename); if (!path.empty()) luaL_loadfile(s, path.c_str()); else lua_pushliteral(s, "File not found"); return 1; } LuaScript::LuaScript(): nbArgs(-1) { mRootState = luaL_newstate(); mCurrentState = mRootState; luaL_openlibs(mRootState); // Register package loader that goes through the resource manager // package.loaders[2] = require_loader lua_getglobal(mRootState, "package"); #if LUA_VERSION_NUM < 502 lua_getfield(mRootState, -1, "loaders"); #else lua_getfield(mRootState, -1, "searchers"); #endif lua_pushcfunction(mRootState, require_loader); lua_rawseti(mRootState, -2, 2); lua_pop(mRootState, 2); // Put the callback functions in the scripting environment. static luaL_Reg const callbacks[] = { { "on_update_derived_attribute", on_update_derived_attribute }, { "on_recalculate_base_attribute", on_recalculate_base_attribute }, { "on_character_death", on_character_death }, { "on_character_death_accept", on_character_death_accept }, { "on_character_login", on_character_login }, { "on_being_death", on_being_death }, { "on_entity_remove", on_entity_remove }, { "on_update", on_update }, { "on_create_npc_delayed", on_create_npc_delayed }, { "on_map_initialize", on_map_initialize }, { "on_craft", on_craft }, { "on_mapvar_changed", on_mapvar_changed }, { "on_worldvar_changed", on_worldvar_changed }, { "on_mapupdate", on_mapupdate }, { "get_item_class", get_item_class }, { "get_monster_class", get_monster_class }, { "get_monster_classes", get_monster_classes }, { "get_status_effect", get_status_effect }, { "npc_create", npc_create }, { "say", say }, { "ask", ask }, { "ask_number", ask_number }, { "ask_string", ask_string }, { "trade", trade }, { "npc_post", npc_post }, { "npc_enable", npc_enable }, { "npc_disable", npc_disable }, { "chr_get_quest", chr_get_quest }, { "chr_set_quest", chr_set_quest }, { "chr_request_quest", chr_request_quest }, { "chr_try_get_quest", chr_try_get_quest }, { "getvar_map", getvar_map }, { "setvar_map", setvar_map }, { "getvar_world", getvar_world }, { "setvar_world", setvar_world }, { "chr_get_post", chr_get_post }, { "xp_for_level", xp_for_level }, { "monster_create", monster_create }, { "trigger_create", trigger_create }, { "get_beings_in_circle", get_beings_in_circle }, { "get_beings_in_rectangle", get_beings_in_rectangle }, { "get_character_by_name", get_character_by_name }, { "effect_create", effect_create }, { "test_tableget", test_tableget }, { "get_map_id", get_map_id }, { "get_map_property", get_map_property }, { "is_walkable", is_walkable }, { "get_path_length", get_path_length }, { "map_get_pvp", map_get_pvp }, { "item_drop", item_drop }, { "log", log }, { "get_distance", get_distance }, { "map_get_objects", map_get_objects }, { "announce", announce }, { "get_ability_info", get_ability_info }, { nullptr, nullptr } }; #if LUA_VERSION_NUM < 502 lua_pushvalue(mRootState, LUA_GLOBALSINDEX); luaL_register(mRootState, nullptr, callbacks); #else lua_pushglobaltable(mRootState); luaL_setfuncs(mRootState, callbacks, 0); #endif lua_pop(mRootState, 1); // pop the globals table static luaL_Reg const members_Entity[] = { { "remove", entity_remove }, { "say", entity_say }, { "message", entity_message }, { "inventory", entity_get_inventory }, { "inv_change", entity_inv_change }, { "inv_count", entity_inv_count }, { "equipment", entity_get_equipment }, { "equip_slot", entity_equip_slot }, { "equip_item", entity_equip_item }, { "unequip_slot", entity_unequip_slot }, { "unequip_item", entity_unequip_item }, { "set_ability_mana", entity_set_ability_mana }, { "ability_mana", entity_get_ability_mana }, { "cooldown_ability", entity_cooldown_ability }, { "walk", entity_walk }, { "destination", entity_destination }, { "heal", entity_heal }, { "name", entity_get_name }, { "type", entity_get_type }, { "action", entity_get_action }, { "set_action", entity_set_action }, { "direction", entity_get_direction }, { "set_direction", entity_set_direction }, { "set_walkmask", entity_set_walkmask }, { "walkmask", entity_get_walkmask }, { "warp", entity_warp }, { "position", entity_get_position }, { "x", entity_get_x }, { "y", entity_get_y }, { "base_attribute", entity_get_base_attribute }, { "set_base_attribute", entity_set_base_attribute }, { "modified_attribute", entity_get_modified_attribute }, { "apply_attribute_modifier", entity_apply_attribute_modifier }, { "remove_attribute_modifier", entity_remove_attribute_modifier }, { "gender", entity_get_gender }, { "set_gender", entity_set_gender }, { "level", entity_get_level }, { "xp", entity_get_xp }, { "give_xp", entity_give_xp }, { "hair_color", entity_get_hair_color }, { "set_hair_color", entity_set_hair_color }, { "hair_style", entity_get_hair_style }, { "set_hair_style", entity_set_hair_style }, { "kill_count", entity_get_kill_count }, { "rights", entity_get_rights }, { "kick", entity_kick }, { "mapid", entity_get_mapid }, { "register", entity_register }, { "shake_screen", entity_shake_screen }, { "show_text_particle", entity_show_text_particle }, { "give_ability", entity_give_ability }, { "has_ability", entity_has_ability }, { "take_ability", entity_take_ability }, { "use_ability", entity_use_ability }, { "monster_id", entity_get_monster_id }, { "apply_status", entity_apply_status }, { "remove_status", entity_remove_status }, { "has_status", entity_has_status }, { "status_time", entity_get_status_time }, { "set_status_time", entity_set_status_time }, { "add_hit_taken", entity_add_hit_taken }, { nullptr, nullptr } }; static luaL_Reg const members_ItemClass[] = { { "on", item_class_on }, { "name", item_class_get_name }, { nullptr, nullptr } }; static luaL_Reg const members_MapObject[] = { { "property", map_object_get_property }, { "bounds", map_object_get_bounds }, { "name", map_object_get_name }, { "type", map_object_get_type }, { nullptr, nullptr } }; static luaL_Reg const members_MonsterClass[] = { { "on_update", monster_class_on_update }, { "name", monster_class_get_name }, { nullptr, nullptr } }; static luaL_Reg const members_StatusEffect[] = { { "on_tick", status_effect_on_tick }, { nullptr, nullptr } }; static luaL_Reg const members_AbilityInfo[] = { { "name", abilityinfo_get_name }, { "needed_mana", abilityinfo_get_needed_mana }, { "rechargeable", abilityinfo_is_rechargeable }, { "on_use", abilityinfo_on_use }, { "on_recharged", abilityinfo_on_recharged }, { "category", abilitiyinfo_get_category }, { nullptr, nullptr} }; LuaEntity::registerType(mRootState, "Entity", members_Entity); LuaItemClass::registerType(mRootState, "ItemClass", members_ItemClass); LuaMapObject::registerType(mRootState, "MapObject", members_MapObject); LuaMonsterClass::registerType(mRootState, "MonsterClass", members_MonsterClass); LuaStatusEffect::registerType(mRootState, "StatusEffect", members_StatusEffect); LuaAbilityInfo::registerType(mRootState, "AbilityInfo", members_AbilityInfo); // Make script object available to callback functions. lua_pushlightuserdata(mRootState, const_cast(®istryKey)); lua_pushlightuserdata(mRootState, this); lua_rawset(mRootState, LUA_REGISTRYINDEX); // Push the error handler to first index of the stack lua_getglobal(mRootState, "debug"); lua_getfield(mRootState, -1, "traceback"); lua_remove(mRootState, 1); // remove the 'debug' table loadFile("scripts/lua/libmana.lua"); }