/* * The Mana Server * Copyright (C) 2007-2010 The Mana World Development Team * Copyright (C) 2010 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/trigger.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 document it on * * http://doc.manasource.org/scripting */ /** * 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); Character::setDeathCallback(getScript(s)); return 0; } /** * on_character_death_accept( function(Character*) ): void * Sets a listener function that is called when the player clicks on the OK * button after the death message appeared. It should be used to implement the * respawn mechanic. */ static int on_character_death_accept(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); Character::setDeathAcceptedCallback(getScript(s)); return 0; } static int on_being_death(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); LuaScript::setDeathNotificationCallback(getScript(s)); return 0; } static int on_being_remove(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); LuaScript::setRemoveNotificationCallback(getScript(s)); return 0; } static int on_update(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); Script::setUpdateCallback(getScript(s)); return 0; } static int on_create_npc_delayed(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); Script::setCreateNpcDelayedCallback(getScript(s)); return 0; } static int on_map_initialize(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); MapComposite::setInitializeCallback(getScript(s)); return 0; } static int on_craft(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); ScriptManager::setCraftCallback(getScript(s)); return 0; } static int on_use_special(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); ScriptManager::setSpecialCallback(getScript(s)); return 0; } static int on_get_special_recharge_cost(lua_State *s) { luaL_checktype(s, 1, LUA_TFUNCTION); ScriptManager::setGetSpecialRechargeCostCallback(getScript(s)); return 0; } 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; } 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; } static int get_item_class(lua_State *s) { LuaItemClass::push(s, checkItemClass(s, 1)); return 1; } static int get_monster_class(lua_State *s) { LuaMonsterClass::push(s, checkMonsterClass(s, 1)); 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; } /** * npc_message(NPC*, Character*, string): void * Callback for sending a NPC_MESSAGE. */ static int npc_message(lua_State *s) { NPC *p = checkNPC(s, 1); Character *q = checkCharacter(s, 2); size_t l; const char *m = luaL_checklstring(s, 3, &l); Script::Thread *thread = checkCurrentThread(s); MessageOut msg(GPMSG_NPC_MESSAGE); msg.writeInt16(p->getPublicID()); msg.writeString(m, l); gameHandler->sendTo(q, msg); thread->mState = Script::ThreadPaused; return lua_yield(s, 0); } /** * npc_choice(NPC*, Character*, string...): void * Callback for sending a NPC_CHOICE. */ static int npc_choice(lua_State *s) { NPC *p = checkNPC(s, 1); Character *q = checkCharacter(s, 2); Script::Thread *thread = checkCurrentThread(s); MessageOut msg(GPMSG_NPC_CHOICE); msg.writeInt16(p->getPublicID()); for (int i = 3, 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 { raiseScriptError(s, "npc_choice called " "with incorrect parameters."); return 0; } lua_pop(s, 1); } } else { raiseScriptError(s, "npc_choice called with incorrect parameters."); return 0; } } gameHandler->sendTo(q, msg); thread->mState = Script::ThreadExpectingNumber; return lua_yield(s, 0); } /** * npc_integer(NPC*, Character*, int min, int max, int default = min): void * Callback for sending a NPC_INTEGER. */ static int npc_ask_integer(lua_State *s) { NPC *p = checkNPC(s, 1); Character *q = checkCharacter(s, 2); int min = luaL_checkint(s, 3); int max = luaL_checkint(s, 4); int defaultValue = luaL_optint(s, 5, min); Script::Thread *thread = checkCurrentThread(s); MessageOut msg(GPMSG_NPC_NUMBER); msg.writeInt16(p->getPublicID()); msg.writeInt32(min); msg.writeInt32(max); msg.writeInt32(defaultValue); gameHandler->sendTo(q, msg); thread->mState = Script::ThreadExpectingNumber; return lua_yield(s, 0); } /** * npc_ask_string(NPC*, Character*): void * Callback for sending a NPC_STRING. */ static int npc_ask_string(lua_State *s) { NPC *p = checkNPC(s, 1); Character *q = checkCharacter(s, 2); Script::Thread *thread = checkCurrentThread(s); MessageOut msg(GPMSG_NPC_STRING); msg.writeInt16(p->getPublicID()); gameHandler->sendTo(q, msg); thread->mState = Script::ThreadExpectingString; return lua_yield(s, 0); } /** * npc_create(string name, int id, int gender, int x, int y, * function talk, function update): NPC* * * Callback for creating a NPC on the current map. */ 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); NPC *q = new NPC(name, id); q->setGender(getGender(gender)); q->setMap(m); q->setPosition(Point(x, y)); if (lua_isfunction(s, 6)) { lua_pushvalue(s, 6); q->setTalkCallback(luaL_ref(s, LUA_REGISTRYINDEX)); } if (lua_isfunction(s, 7)) { lua_pushvalue(s, 7); q->setUpdateCallback(luaL_ref(s, LUA_REGISTRYINDEX)); } GameState::enqueueInsert(q); lua_pushlightuserdata(s, q); return 1; } /** * npc_post(NPC*, Character*): void * Callback for sending a NPC_POST. */ static int npc_post(lua_State *s) { NPC *p = checkNPC(s, 1); Character *q = checkCharacter(s, 2); MessageOut msg(GPMSG_NPC_POST); msg.writeInt16(p->getPublicID()); gameHandler->sendTo(q, msg); return 0; } /** * npc_enable(NPC*): void * Enable a NPC if it has previously disabled */ static int npc_enable(lua_State *s) { NPC *p = checkNPC(s, 1); p->setEnabled(true); GameState::enqueueInsert(p); return 0; } /** * npc_disable(NPC*): void * Disable a NPC. */ static int npc_disable(lua_State *s) { NPC *p = checkNPC(s, 1); p->setEnabled(false); GameState::remove(p); return 0; } /** * chr_warp(Character*, nil/int map, int x, int y): void * Callback for warping a player to another place. */ static int chr_warp(lua_State *s) { Character *q = 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))) { raiseScriptError(s, "chr_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("chr_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(q, m, x, y); return 0; } /** * Callback for gathering inventory information. * chr_get_inventory(character): table[]{slot, item id, name, amount} * Returns in the inventory slots order, the slot id, the item ids, * name and amount. Only slots not empty are returned. * @Example * To get a piece of information, you can do something like this: * -- This will print the 2nd non-empty slot id. * local my_table = chr_get_inventory(character) * print(my_table[2].slot) */ static int chr_get_inventory(lua_State *s) { Character *q = checkCharacter(s, 1); // Create a lua table with the inventory ids. const InventoryData invData = q->getPossessions().getInventory(); lua_newtable(s); int firstTableStackPosition = lua_gettop(s); int tableIndex = 1; std::string itemName = ""; 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_pushstring(s, "slot"); lua_pushinteger(s, it->first); // The slot id lua_settable(s, subTableStackPosition); lua_pushstring(s, "id"); lua_pushinteger(s, it->second.itemId); lua_settable(s, subTableStackPosition); lua_pushstring(s, "name"); itemName = itemManager->getItem(it->second.itemId)->getName(); lua_pushstring(s, itemName.c_str()); lua_settable(s, subTableStackPosition); lua_pushstring(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; } /** * Callback for gathering equipment information. * chr_get_inventory(character): table[](slot, item id, name) * Returns in the inventory slots order, the slot id, the item ids, and name. * Only slots not empty are returned. * @Example * To get a piece of information, you can do something like this: * -- This will print the 2nd non-empty slot id. * local my_table = chr_get_equipment(character) * print(my_table[2].slot) */ static int chr_get_equipment(lua_State *s) { Character *q = checkCharacter(s, 1); // Create a lua table with the inventory ids. const EquipData equipData = q->getPossessions().getEquipment(); lua_newtable(s); int firstTableStackPosition = lua_gettop(s); int tableIndex = 1; std::string itemName; 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_pushstring(s, "slot"); lua_pushinteger(s, it->first); // The slot id lua_settable(s, subTableStackPosition); lua_pushstring(s, "id"); lua_pushinteger(s, it->second.itemId); lua_settable(s, subTableStackPosition); lua_pushstring(s, "name"); itemName = itemManager->getItem(it->second.itemId)->getName(); lua_pushstring(s, itemName.c_str()); lua_settable(s, subTableStackPosition); // Add the sub-table as value of the main one. lua_rawseti(s, firstTableStackPosition, tableIndex); ++tableIndex; } return 1; } /** * chr_inv_change(Character*, (int id || string name, * int nb)...): bool success * Callback for inserting/removing items in inventory. * The function can be called several times in a row, but it is better to * perform all the changes at once, so as to reduce bandwidth. Removals * (negative amount) should be passed first, then insertions (positive amount). * If a removal fails, all the previous operations are canceled (except for * items dropped on the floor, hence why removals should be passed first), and * the function returns false. Otherwise the function will return true. * Note that previously when the item identifier was zero, money was modified; * however currency is now handled through attributes. This breaks backwards * compatibility with old scripts, and so logs a warning. * Note: If an insertion fails, extra items are dropped on the floor. */ static int chr_inv_change(lua_State *s) { Character *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 + 2) || lua_isstring(s, i * 2 + 2)) || !lua_isnumber(s, i * 2 + 3)) { raiseScriptError(s, "chr_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("chr_inv_change() removed more items than owned: " << "character: " << q->getName() << " item id: " << id); } } else { nb = inv.insert(id, nb); if (nb) { Item *item = new Item(ic, nb); item->setMap(q->getMap()); item->setPosition(q->getPosition()); GameState::enqueueInsert(item); } } } lua_pushboolean(s, 1); return 1; } /** * chr_inv_count(Character*, bool in inventory, bool in equipment, * int item_id...): int count... * Callback for counting items in inventory. * The function can search in inventory and/or in the equipment. */ static int chr_inv_count(lua_State *s) { Character *q = checkCharacter(s, 1); if (!lua_isboolean(s, 2) || !lua_isboolean(s, 3)) { raiseScriptError(s, "chr_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; } /** * chr_equip_slot(Character*, int inventorySlot): bool success * Makes the character equip the item in the given inventory slot. */ static int chr_equip_slot(lua_State *s) { Character *ch = checkCharacter(s, 1); int inventorySlot = luaL_checkint(s, 2); Inventory inv(ch); lua_pushboolean(s, inv.equip(inventorySlot)); return 1; } /** * chr_equip_item(Character*, int itemId || string itemName): bool success * Makes the character equip the item id when it's existing * in the player's inventory. */ static int chr_equip_item(lua_State *s) { Character *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; } /** * chr_unequip_slot(Character*, int inventorySlot): bool success * Makes the character unequip the item in the given equipment slot. */ static int chr_unequip_slot(lua_State *s) { Character *ch = checkCharacter(s, 1); int equipmentSlot = luaL_checkint(s, 2); Inventory inv(ch); lua_pushboolean(s, inv.unequip(inv.getSlotItemInstance(equipmentSlot))); return 1; } /** * chr_unequip_item(Character*, int itemId || string itemName): bool success * Makes the character unequip the item(s) corresponding to the id * when it's existing in the player's equipment. * Returns true when every item were unequipped from equipment. */ static int chr_unequip_item(lua_State *s) { Character *ch = checkCharacter(s, 1); ItemClass *it = checkItemClass(s, 2); Inventory inv(ch); lua_pushboolean(s, inv.unequipItem(it->getDatabaseID())); return 1; } /** * chr_get_level(Character*): int level * Tells the character current level. */ static int chr_get_level(lua_State *s) { Character *ch = checkCharacter(s, 1); lua_pushinteger(s, ch->getLevel()); return 1; } /** * npc_trade(NPC*, Character*, bool sell, table items): int * Callback for trading between a player and an NPC. * Let the player buy or sell only a subset of predeterminded items. * @param table items: a subset of buyable/sellable items. * When selling, if the 4 parameter is omitted or invalid, * everything in the player inventory can be sold. * @return 0 if something to buy/sell, 1 if no items, 2 in case of errors. */ static int npc_trade(lua_State *s) { NPC *p = checkNPC(s, 1); Character *q = checkCharacter(s, 2); if (!lua_isboolean(s, 3)) { raiseWarning(s, "npc_trade called with incorrect parameters."); lua_pushinteger(s, 2); // return value return 1; // Returns 1 parameter } bool sellMode = lua_toboolean(s, 3); BuySell *t = new BuySell(q, sellMode); if (!lua_istable(s, 4)) { 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(p)) { lua_pushinteger(s, 0); return 1; } else { lua_pushinteger(s, 1); return 1; } } else { raiseWarning(s, "npc_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, 4)) { if (!lua_istable(s, -1)) { raiseWarning(s, "npc_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, "npc_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, "npc_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(p)) { lua_pushinteger(s, 0); return 1; } else { lua_pushinteger(s, 1); return 1; } } /** * being_apply_status(Being*, int id, int time): void * Applies a status effect with id to the being given for a amount of time. */ static int being_apply_status(lua_State *s) { Being *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); const int time = luaL_checkint(s, 3); being->applyStatusEffect(id, time); return 0; } /** * being_remove_status(Being*, int id): void * Removes the given status effect. */ static int being_remove_status(lua_State *s) { Being *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); being->removeStatusEffect(id); return 0; } /** * being_has_status(Being*, int id): bool * Returns whether a being has the given status effect. */ static int being_has_status(lua_State *s) { Being *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); lua_pushboolean(s, being->hasStatusEffect(id)); return 1; } /** * being_get_status_time(Being*, int id): int * Returns the time left on the given status effect. */ static int being_get_status_time(lua_State *s) { Being *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); lua_pushinteger(s, being->getStatusEffectTime(id)); return 1; } /** * being_set_status_time(Being*, int id, int time): void * Sets the time left on the given status effect. */ static int being_set_status_time(lua_State *s) { Being *being = checkBeing(s, 1); const int id = luaL_checkint(s, 2); const int time = luaL_checkint(s, 3); being->setStatusEffectTime(id, time); return 0; } /** * being_type(Being*): ThingType * Returns the Thing type of the given being. */ static int being_type(lua_State *s) { Being *being = checkBeing(s, 1); lua_pushinteger(s, being->getType()); return 1; } /** * being_walk(Being *, int x, int y[, float speed]): void * Function for making a being walk to a position. * The speed is in tile per second. */ static int being_walk(lua_State *s) { Being *being = checkBeing(s, 1); const int x = luaL_checkint(s, 2); const int y = luaL_checkint(s, 3); being->setDestination(Point(x, y)); if (lua_gettop(s) >= 4) { being->setAttribute(ATTR_MOVE_SPEED_TPS, luaL_checknumber(s, 4)); being->setAttribute(ATTR_MOVE_SPEED_RAW, utils::tpsToRawSpeed( being->getModifiedAttribute(ATTR_MOVE_SPEED_TPS))); } return 0; } /** * being_say(Being* source, string message): void * Makes the being say something. */ static int being_say(lua_State *s) { Being *being = checkBeing(s, 1); const char *message = luaL_checkstring(s, 2); GameState::sayAround(being, message); return 0; } /** * being_damage(Being* victim, int value, int delta, int cth, int type, * int element [, Being* source [, int skill]]): void * Applies combat damage to a being. */ static int being_damage(lua_State *s) { Being *being = checkBeing(s, 1); if (!being->canFight()) { raiseScriptError(s, "being_damage called with " "victim that cannot fight"); return 0; } Damage dmg; dmg.base = luaL_checkint(s, 2); dmg.delta = luaL_checkint(s, 3); dmg.cth = luaL_checkint(s, 4); dmg.type = (DamageType)luaL_checkint(s, 5); dmg.element = luaL_checkint(s, 6); Being *source = 0; if (lua_gettop(s) >= 7) { source = checkBeing(s, 7); if (!source->canFight()) { raiseScriptError(s, "being_damage called with " "source that cannot fight"); return 0; } } dmg.skill = luaL_optint(s, 8, 0); being->damage(source, dmg); return 0; } /** * being_heal(Being* [, int value]): void * Restores hit points of a being. * Without a value the being is fully healed. */ static int being_heal(lua_State *s) { Being *being = checkBeing(s, 1); if (lua_gettop(s) == 1) // when there is only one argument { being->heal(); } else { being->heal(luaL_checkint(s, 2)); } return 0; } /** * being_get_base_attribute(Being*, int attribute): int * Gets the base attribute of a being. */ static int being_get_base_attribute(lua_State *s) { Being *being = checkBeing(s, 1); int attr = luaL_checkint(s, 2); luaL_argcheck(s, attr > 0, 2, "invalid attribute id"); lua_pushinteger(s, being->getAttribute(attr)); return 1; } /** * being_get_modified_attribute(Being*, int attribute): int * Gets the modified attribute of a being. */ static int being_get_modified_attribute(lua_State *s) { Being *being = checkBeing(s, 1); int attr = luaL_checkint(s, 2); luaL_argcheck(s, attr > 0, 2, "invalid attribute id"); lua_pushinteger(s, being->getModifiedAttribute(attr)); return 1; } /** * being_set_base_attribute(Being*, int attribute, double value): void * Sets the base attribute of a being. */ static int being_set_base_attribute(lua_State *s) { Being *being = checkBeing(s, 1); int attr = luaL_checkint(s, 2); double value = luaL_checknumber(s, 3); being->setAttribute(attr, value); return 0; } /** * being_apply_attribute_modifier(Being*, int attribute, double value, * int layer, int [duration, int [effect-id]]): void * Applies an attribute modifier to a being. */ static int being_apply_attribute_modifier(lua_State *s) { Being *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->applyModifier(attr, value, layer, duration, effectId); return 0; } /** * being_remove_attribute_modifier(Being*, int attribute, double value, * int layer, int [effect-id]]): void * Removes an attribute modifier to a being. */ static int being_remove_attribute_modifier(lua_State *s) { Being *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->removeModifier(attr, value, layer, effectId); return 0; } /** * being_get_name(Being*): string * Gets the being's name. */ static int being_get_name(lua_State *s) { Being *being = checkBeing(s, 1); lua_pushstring(s, being->getName().c_str()); return 1; } /** * being_get_action(Being*): BeingAction * Gets the being's current action. */ static int being_get_action(lua_State *s) { Being *being = checkBeing(s, 1); lua_pushinteger(s, being->getAction()); return 1; } /** * being_set_action(Being*, BeingAction): void * Sets the being's current action. */ static int being_set_action(lua_State *s) { Being *being = checkBeing(s, 1); int act = luaL_checkint(s, 2); being->setAction((BeingAction) act); return 0; } /** * being_get_direction(Being*): BeingDirection * Gets the being's current direction. */ static int being_get_direction(lua_State *s) { Being *being = checkBeing(s, 1); lua_pushinteger(s, being->getDirection()); return 1; } /** * being_set_direction(Being*, BeingDirection): void * Sets the being's current direction. */ static int being_set_direction(lua_State *s) { Being *being = checkBeing(s, 1); BeingDirection dir = (BeingDirection) luaL_checkint(s, 2); being->setDirection(dir); return 0; } /* * being_set_walkmask(Being*, string mask) * Sets the walkmask of a being */ static int being_set_walkmask(lua_State *s) { Being *being = checkBeing(s, 1); const char *stringMask = luaL_checkstring(s, 2); unsigned char mask = 0x00; if (strchr(stringMask, 'w')) mask |= Map::BLOCKMASK_WALL; else if (strchr(stringMask, 'c')) mask |= Map::BLOCKMASK_CHARACTER; else if (strchr(stringMask, 'm')) mask |= Map::BLOCKMASK_MONSTER; being->setWalkMask(mask); return 0; } /** * being_get_walkmask(Being*): string * Returns the walkmask of a being */ static int being_get_walkmask(lua_State *s) { Being *being = checkBeing(s, 1); const unsigned char mask = being->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; } /** * posX(Being*): int xcoord * Function for getting the x-coordinate of the position of a being. */ static int posX(lua_State *s) { Being *being = checkBeing(s, 1); lua_pushinteger(s, being->getPosition().x); return 1; } /** * posY(Being*): int ycoord * Function for getting the y-coordinate of the position of a being. */ static int posY(lua_State *s) { Being *being = checkBeing(s, 1); lua_pushinteger(s, being->getPosition().y); return 1; } 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; } /** * monster_create(int id || string name, int x, int y): Monster* * Callback for creating a monster on the current map. */ 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); Monster *q = new Monster(monsterClass); q->setMap(m); q->setPosition(Point(x, y)); GameState::enqueueInsert(q); lua_pushlightuserdata(s, q); return 1; } /** * monster_get_name(int monster_id): string monster_name * Returns the name of the monster with the given id. */ static int monster_get_name(lua_State *s) { const int id = luaL_checkint(s, 1); MonsterClass *spec = monsterManager->getMonster(id); if (!spec) { raiseScriptError(s, "monster_get_name " "called with unknown monster id."); return 0; } lua_pushstring(s, spec->getName().c_str()); return 1; } /** * monster_change_anger(Monster*, Being*, int anger) * Makes a monster angry at a being */ static int monster_change_anger(lua_State *s) { Monster *monster = checkMonster(s, 1); Being *being = checkBeing(s, 2); const int anger = luaL_checkint(s, 3); monster->changeAnger(being, anger); return 0; } /** * monster_remove(Monster*): bool success * Remove a monster object without kill event. * return whether the monster was enqueued for removal. */ static int monster_remove(lua_State *s) { bool monsterRemoved = false; if (Monster *m = getMonster(s, 1)) { GameState::remove(m); monsterRemoved = true; } lua_pushboolean(s, monsterRemoved); return 1; } /** * chr_get_quest(Character*, string): nil or string * Callback for getting a quest variable. Starts a recovery and returns * immediatly, if the variable is not known yet. */ static int chr_get_quest(lua_State *s) { Character *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) { lua_pushstring(s, value.c_str()); return 1; } QuestCallback f = { &LuaScript::getQuestCallback, getScript(s) }; recoverQuestVar(q, name, f); thread->mState = Script::ThreadExpectingString; return lua_yield(s, 0); } /** * getvar_map(string): string * Gets the value of a persistent 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); std::string value = map->getVariable(name); lua_pushstring(s, value.c_str()); return 1; } /** * setvar_map(string, string): void * Sets the value of a persistent 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; } /** * getvar_world(string): string * Gets the value of a persistent global 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"); std::string value = GameState::getVariable(name); lua_pushstring(s, value.c_str()); return 1; } /** * setvar_world(string, string): void * Sets the value of a persistent global 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; } /** * chr_set_quest(Character*, string, string): void * Callback for setting a quest variable. */ static int chr_set_quest(lua_State *s) { Character *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; } /** * trigger_create(int x, int y, int width, int height, function, int id, * boolean once) * Creates a trigger area. Whenever an actor enters this area, the given Lua * function is called. */ 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)) { raiseScriptError(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); ScriptAction *action = new ScriptAction(script, function, id); Rectangle r = { x, y, width, height }; TriggerArea *area = new TriggerArea(m, r, action, once); LOG_INFO("Created script trigger at " << x << "," << y << " (" << width << "x" << height << ") id: " << id); bool ret = GameState::insert(area); lua_pushboolean(s, ret); return 1; } /** * private message: chat_message(Being* recipent, string message): void * Creates a chat message in the users chatlog(s). */ static int chat_message(lua_State *s) { Being *being = checkBeing(s, 1); const char *message = luaL_checkstring(s, 2); GameState::sayTo(being, NULL, message); return 0; } /** * get_beings_in_circle(int x, int y, int radius): table of Being* * get_beings_in_circle(handle centerBeing, int radius): table of Being* * Gets a LUA table with the Being* pointers of all beings * inside of a circular area of the current map. */ static int get_beings_in_circle(lua_State *s) { int x, y, r; if (lua_islightuserdata(s, 1)) { Being *b = checkBeing(s, 1); const Point &pos = b->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) { Being *b = *i; char t = b->getType(); if (t == OBJECT_NPC || t == OBJECT_CHARACTER || t == OBJECT_MONSTER) { if (Collision::circleWithCircle(b->getPosition(), b->getSize(), Point(x, y), r)) { lua_pushlightuserdata(s, b); lua_rawseti(s, tableStackPosition, tableIndex); tableIndex++; } } } return 1; } /** * get_beings_in_rectangle(int x, int y, int width, int height): table of Being* * Gets a LUA table with the Being* pointers of all beings * inside of a rectangular area of the current map. */ 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) { Being *b = *i; char t = b->getType(); if (t == OBJECT_NPC || t == OBJECT_CHARACTER || t == OBJECT_MONSTER) { lua_pushlightuserdata(s, b); lua_rawseti(s, tableStackPosition, tableIndex); tableIndex++; } } return 1; } /** * chr_get_post(Character*): void * Gets the post for the character. */ static int chr_get_post(lua_State *s) { Character *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); } /** * being_register(Being*): void * 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) { Being *being = checkBeing(s, 1); being->addListener(getScript(s)->getScriptListener()); return 0; } /** * effect_create (int id, int x, int y): void * effect_create (int id, Being*): void * Triggers a special effect from the clients effects.xml. */ static int effect_create(lua_State *s) { const int id = luaL_checkint(s, 1); if (lua_isuserdata(s, 2)) { // being mode Being *b = checkBeing(s, 2); Effects::show(id, b->getMap(), 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; } /** * chr_shake_screen(Character*, int x, int y[, float strength, int radius]): void * Shake the screen for a given character. */ static int chr_shake_screen(lua_State *s) { Character *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->getClient()->send(msg); return 0; } /** * chr_get_exp(Character*, int skill): int * Gets the exp total in a skill of a specific character */ static int chr_get_exp(lua_State *s) { Character *c = checkCharacter(s, 1); const int skill = luaL_checkint(s, 2); const int exp = c->getExperience(skill); lua_pushinteger(s, exp); return 1; } /** * chr_give_exp(Character*, int skill, int amount[, int optimal_level]): void * Gives the character a certain amount of experience points * in a skill. Can also be used to reduce the exp amount when * desired. */ static int chr_give_exp(lua_State *s) { Character *c = checkCharacter(s, 1); const int skill = luaL_checkint(s, 2); const int exp = luaL_checkint(s, 3); const int optimalLevel = luaL_optint(s, 4, 0); c->receiveExperience(skill, exp, optimalLevel); return 0; } /** * chr_set_hair_style(Character*, int style_id): void * Sets the given character's hair style to the given style id. */ static int chr_set_hair_style(lua_State *s) { Character *c = checkCharacter(s, 1); const int style = luaL_checkint(s, 2); luaL_argcheck(s, style >= 0, 2, "invalid style id"); c->setHairStyle(style); c->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); return 0; } /** * chr_get_hair_style(Character*): int hair_style * Gets the hair style of the given character. */ static int chr_get_hair_style(lua_State *s) { Character *c = checkCharacter(s, 1); lua_pushinteger(s, c->getHairStyle()); return 1; } /** * chr_set_hair_color(Character*, int color_id): void * Set the hair color of the given character to the given color id. */ static int chr_set_hair_color(lua_State *s) { Character *c = checkCharacter(s, 1); const int color = luaL_checkint(s, 2); luaL_argcheck(s, color >= 0, 2, "invalid color id"); c->setHairColor(color); c->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); return 0; } /** * chr_get_hair_color(Character*): int hair_color * Get the hair color of the given character. */ static int chr_get_hair_color(lua_State *s) { Character *c = checkCharacter(s, 1); lua_pushinteger(s, c->getHairColor()); return 1; } /** * chr_get_kill_count(Character*, int monster_type): int * Get the number of monsters the player killed of a type. */ static int chr_get_kill_count(lua_State *s) { Character *c = checkCharacter(s, 1); const int id = luaL_checkint(s, 2); lua_pushinteger(s, c->getKillCount(id)); return 1; } /** * being_get_gender(Being*): int * Get the gender of the being. */ static int being_get_gender(lua_State *s) { Being *b = checkBeing(s, 1); lua_pushinteger(s, b->getGender()); return 1; } /** * being_set_gender(Being*, int gender): void * Set the gender of the being. */ static int being_set_gender(lua_State *s) { Being *b = checkBeing(s, 1); const int gender = luaL_checkinteger(s, 2); b->setGender(getGender(gender)); return 0; } /** * chr_give_special(Character*, int special): void * Enables a special for a character. */ static int chr_give_special(lua_State *s) { // cost_type is ignored until we have more than one cost type Character *c = checkCharacter(s, 1); const int special = luaL_checkint(s, 2); c->giveSpecial(special); return 0; } /** * chr_has_special(Character*, int special): bool * Checks whether a character has a given special. */ static int chr_has_special(lua_State *s) { Character *c = checkCharacter(s, 1); const int special = luaL_checkint(s, 2); lua_pushboolean(s, c->hasSpecial(special)); return 1; } /** * chr_take_special(Character*, int special): bool success * Removes a special from a character. */ static int chr_take_special(lua_State *s) { Character *c = checkCharacter(s, 1); const int special = luaL_checkint(s, 2); lua_pushboolean(s, c->hasSpecial(special)); c->takeSpecial(special); return 1; } /** * chr_get_rights(Character*): int * Returns the rights level of a character. */ static int chr_get_rights(lua_State *s) { Character *c = checkCharacter(s, 1); lua_pushinteger(s, c->getAccountLevel()); return 1; } /** * exp_for_level(int level): int * Returns the exp total necessary to reach a specific skill level. */ static int exp_for_level(lua_State *s) { const int level = luaL_checkint(s, 1); lua_pushinteger(s, Character::expForLevel(level)); 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; } /** * get_map_id(): int * Returns the id of the current map. */ static int get_map_id(lua_State *s) { MapComposite *m = checkCurrentMap(s); lua_pushinteger(s, m->getID()); return 1; } /** * get_map_property(string property): string * Returns the value of a map property. */ static int get_map_property(lua_State *s) { const char *property = luaL_checkstring(s, 1); Map *map = checkCurrentMap(s)->getMap(); std::string value = map->getProperty(property); lua_pushstring(s, value.c_str()); return 1; } /** * is_walkable(int x, int y): bool * Returns whether the pixel on the map is walkable. */ 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; } static int map_get_pvp(lua_State *s) { MapComposite *m = checkCurrentMap(s); lua_pushinteger(s, m->getPvP()); 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; } /** * drop_item(int x, int y, int id || string name[, int number]): bool * Creates an item stack on the floor. * @Returns whether the insertion was successful. */ 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); Item *i = new Item(ic, number); i->setMap(map); Point pos(x, y); i->setPosition(pos); lua_pushboolean(s, GameState::insertOrDelete(i)); return 1; } /** * item_get_name(int item_id): string item_name * Returns the name of the item with the given id. */ static int item_get_name(lua_State *s) { const int id = luaL_checkint(s, 1); ItemClass *it = itemManager->getItem(id); if (!it) { raiseScriptError(s, "item_get_name called with unknown item id."); return 0; } lua_pushstring(s, it->getName().c_str()); return 1; } /** * log(int log_level, string log_message): void * Logs the given message to the log. */ 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; } /** * get_distance(Being*, Being*): int * get_distance(int x1, int y1, int x2, int y2): int * Gets the distance between two beings or two points. */ static int get_distance(lua_State *s) { int x1, y1, x2, y2; if (lua_gettop(s) == 2) { Being *being1 = checkBeing(s, 1); Being *being2 = checkBeing(s, 2); x1 = being1->getPosition().x; y1 = being1->getPosition().y; x2 = being2->getPosition().x; y2 = being2->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; } /** * map_get_objects(): table of all objects * map_get_objects(string type): table of all objects of type * Gets the objects of a map. */ 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; } /** * map_object_get_property(handle object, string key) * Returns the value of the object property 'key'. */ 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()) { lua_pushstring(s, property.c_str()); return 1; } else { // scripts can check for nil return 0; } } /** * map_object_get_bounds(object) * Returns 4 int: x/y/width/height of 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; } /** * map_object_get_name(object) * Returns the name of the object. */ static int map_object_get_name(lua_State *s) { MapObject *obj = LuaMapObject::check(s, 1); lua_pushstring(s, obj->getName().c_str()); return 1; } /** * map_object_get_type(object) * Returns the type of the object. */ static int map_object_get_type(lua_State *s) { MapObject *obj = LuaMapObject::check(s, 1); lua_pushstring(s, obj->getType().c_str()); 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; } /** * announce(text [, sender]) * Does a global announce */ 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; } 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_pushstring(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 // table.insert(package.loaders, 2, require_loader) lua_getglobal(mRootState, "package"); lua_getfield(mRootState, -1, "loaders"); 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_character_death", &on_character_death }, { "on_character_death_accept", &on_character_death_accept }, { "on_being_death", &on_being_death }, { "on_being_remove", &on_being_remove }, { "on_update", &on_update }, { "on_create_npc_delayed", &on_create_npc_delayed }, { "on_map_initialize", &on_map_initialize }, { "on_craft", &on_craft }, { "on_use_special", &on_use_special }, { "on_get_special_recharge_cost", &on_get_special_recharge_cost }, { "on_mapvar_changed", &on_mapvar_changed }, { "on_worldvar_changed", &on_worldvar_changed }, { "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 }, { "npc_trade", &npc_trade }, { "npc_post", &npc_post }, { "npc_enable", &npc_enable }, { "npc_disable", &npc_disable }, { "chr_warp", &chr_warp }, { "chr_get_inventory", &chr_get_inventory }, { "chr_inv_change", &chr_inv_change }, { "chr_inv_count", &chr_inv_count }, { "chr_get_equipment", &chr_get_equipment }, { "chr_equip_slot", &chr_equip_slot }, { "chr_equip_item", &chr_equip_item }, { "chr_unequip_slot", &chr_unequip_slot }, { "chr_unequip_item", &chr_unequip_item }, { "chr_get_level", &chr_get_level }, { "chr_get_quest", &chr_get_quest }, { "chr_set_quest", &chr_set_quest }, { "getvar_map", &getvar_map }, { "setvar_map", &setvar_map }, { "getvar_world", &getvar_world }, { "setvar_world", &setvar_world }, { "chr_get_post", &chr_get_post }, { "chr_get_exp", &chr_get_exp }, { "chr_give_exp", &chr_give_exp }, { "chr_get_rights", &chr_get_rights }, { "chr_set_hair_style", &chr_set_hair_style }, { "chr_get_hair_style", &chr_get_hair_style }, { "chr_set_hair_color", &chr_set_hair_color }, { "chr_get_hair_color", &chr_get_hair_color }, { "chr_get_kill_count", &chr_get_kill_count }, { "being_get_gender", &being_get_gender }, { "being_set_gender", &being_set_gender }, { "chr_give_special", &chr_give_special }, { "chr_has_special", &chr_has_special }, { "chr_take_special", &chr_take_special }, { "exp_for_level", &exp_for_level }, { "monster_create", &monster_create }, { "monster_get_name", &monster_get_name }, { "monster_change_anger", &monster_change_anger }, { "monster_remove", &monster_remove }, { "being_apply_status", &being_apply_status }, { "being_remove_status", &being_remove_status }, { "being_has_status", &being_has_status }, { "being_set_status_time", &being_set_status_time }, { "being_get_status_time", &being_get_status_time }, { "being_type", &being_type }, { "being_walk", &being_walk }, { "being_say", &being_say }, { "being_damage", &being_damage }, { "being_heal", &being_heal }, { "being_get_name", &being_get_name }, { "being_get_action", &being_get_action }, { "being_set_action", &being_set_action }, { "being_get_direction", &being_get_direction }, { "being_set_direction", &being_set_direction }, { "being_apply_attribute_modifier", &being_apply_attribute_modifier }, { "being_remove_attribute_modifier", &being_remove_attribute_modifier }, { "being_set_base_attribute", &being_set_base_attribute }, { "being_get_modified_attribute", &being_get_modified_attribute }, { "being_get_base_attribute", &being_get_base_attribute }, { "being_set_walkmask", &being_set_walkmask }, { "being_get_walkmask", &being_get_walkmask }, { "posX", &posX }, { "posY", &posY }, { "trigger_create", &trigger_create }, { "chat_message", &chat_message }, { "get_beings_in_circle", &get_beings_in_circle }, { "get_beings_in_rectangle", &get_beings_in_rectangle }, { "being_register", &being_register }, { "effect_create", &effect_create }, { "chr_shake_screen", &chr_shake_screen }, { "test_tableget", &test_tableget }, { "get_map_id", &get_map_id }, { "get_map_property", &get_map_property }, { "is_walkable", &is_walkable }, { "map_get_pvp", &map_get_pvp }, { "item_drop", &item_drop }, { "item_get_name", &item_get_name }, { "npc_ask_integer", &npc_ask_integer }, { "npc_ask_string", &npc_ask_string }, { "log", &log }, { "get_distance", &get_distance }, { "map_get_objects", &map_get_objects }, { "announce", &announce }, { NULL, NULL } }; lua_pushvalue(mRootState, LUA_GLOBALSINDEX); luaL_register(mRootState, NULL, callbacks); lua_pop(mRootState, 1); // pop the globals 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 }, { "name", &map_object_get_name }, { "type", &map_object_get_type }, { 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(mRootState, "ItemClass", members_ItemClass); LuaMapObject::registerType(mRootState, "MapObject", members_MapObject); LuaMonsterClass::registerType(mRootState, "MonsterClass", members_MonsterClass); LuaStatusEffect::registerType(mRootState, "StatusEffect", members_StatusEffect); // 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"); }