/** * The Forgotten Server - a free and open-source MMORPG server emulator * Copyright (C) 2016 Mark Samman * * This program 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 * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include "combat.h" #include "configmanager.h" #include "game.h" #include "monster.h" #include "pugicast.h" #include "spells.h" extern Game g_game; extern Spells* g_spells; extern Monsters g_monsters; extern Vocations g_vocations; extern ConfigManager g_config; extern LuaEnvironment g_luaEnvironment; Spells::Spells(): scriptInterface("Spell Interface") { scriptInterface.initState(); } Spells::~Spells() { clear(); } TalkActionResult_t Spells::playerSaySpell(Player* player, std::string& words) { std::string str_words = words; //strip trailing spaces trimString(str_words); InstantSpell* instantSpell = getInstantSpell(str_words); if (!instantSpell) { return TALKACTION_CONTINUE; } std::string param; size_t spellLen = instantSpell->getWords().length(); size_t paramLen = str_words.length() - spellLen; std::string paramText = str_words.substr(spellLen, paramLen); if (!paramText.empty() && paramText.front() == ' ') { size_t loc1 = paramText.find('"', 1); if (loc1 != std::string::npos) { size_t loc2 = paramText.find('"', loc1 + 1); if (loc2 == std::string::npos) { loc2 = paramText.length(); } else if (paramText.find_last_not_of(' ') != loc2) { return TALKACTION_CONTINUE; } param = paramText.substr(loc1 + 1, loc2 - loc1 - 1); } else { trimString(paramText); loc1 = paramText.find(' ', 0); if (loc1 == std::string::npos) { param = paramText; } else { return TALKACTION_CONTINUE; } } } if (instantSpell->playerCastInstant(player, param)) { words = str_words.substr(0, spellLen); if (!param.empty()) { words += " \"" + param.substr(0, 30); } return TALKACTION_BREAK; } return TALKACTION_FAILED; } void Spells::clear() { for (const auto& it : runes) { delete it.second; } runes.clear(); for (const auto& it : instants) { delete it.second; } instants.clear(); scriptInterface.reInitState(); } LuaScriptInterface& Spells::getScriptInterface() { return scriptInterface; } std::string Spells::getScriptBaseName() const { return "spells"; } Event* Spells::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "rune") == 0) { return new RuneSpell(&scriptInterface); } else if (strcasecmp(nodeName.c_str(), "instant") == 0) { return new InstantSpell(&scriptInterface); } else if (strcasecmp(nodeName.c_str(), "conjure") == 0) { return new ConjureSpell(&scriptInterface); } return nullptr; } bool Spells::registerEvent(Event* event, const pugi::xml_node&) { InstantSpell* instant = dynamic_cast(event); if (instant) { auto result = instants.emplace(instant->getWords(), instant); if (!result.second) { std::cout << "[Warning - Spells::registerEvent] Duplicate registered instant spell with words: " << instant->getWords() << std::endl; } return result.second; } RuneSpell* rune = dynamic_cast(event); if (rune) { auto result = runes.emplace(rune->getRuneItemId(), rune); if (!result.second) { std::cout << "[Warning - Spells::registerEvent] Duplicate registered rune with id: " << rune->getRuneItemId() << std::endl; } return result.second; } return false; } Spell* Spells::getSpellByName(const std::string& name) { Spell* spell = getRuneSpellByName(name); if (!spell) { spell = getInstantSpellByName(name); } return spell; } RuneSpell* Spells::getRuneSpell(uint32_t id) { auto it = runes.find(id); if (it == runes.end()) { return nullptr; } return it->second; } RuneSpell* Spells::getRuneSpellByName(const std::string& name) { for (const auto& it : runes) { if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { return it.second; } } return nullptr; } InstantSpell* Spells::getInstantSpell(const std::string& words) { InstantSpell* result = nullptr; for (const auto& it : instants) { InstantSpell* instantSpell = it.second; const std::string& instantSpellWords = instantSpell->getWords(); size_t spellLen = instantSpellWords.length(); if (strncasecmp(instantSpellWords.c_str(), words.c_str(), spellLen) == 0) { if (!result || spellLen > result->getWords().length()) { result = instantSpell; if (words.length() == spellLen) { break; } } } } if (result) { const std::string& resultWords = result->getWords(); if (words.length() > resultWords.length()) { size_t spellLen = resultWords.length(); size_t paramLen = words.length() - spellLen; if (paramLen < 2 || words[spellLen] != ' ') { return nullptr; } } return result; } return nullptr; } uint32_t Spells::getInstantSpellCount(const Player* player) const { uint32_t count = 0; for (const auto& it : instants) { InstantSpell* instantSpell = it.second; if (instantSpell->canCast(player)) { ++count; } } return count; } InstantSpell* Spells::getInstantSpellByIndex(const Player* player, uint32_t index) { uint32_t count = 0; for (const auto& it : instants) { InstantSpell* instantSpell = it.second; if (instantSpell->canCast(player)) { if (count == index) { return instantSpell; } ++count; } } return nullptr; } InstantSpell* Spells::getInstantSpellByName(const std::string& name) { for (const auto& it : instants) { if (strcasecmp(it.second->getName().c_str(), name.c_str()) == 0) { return it.second; } } return nullptr; } Position Spells::getCasterPosition(Creature* creature, Direction dir) { return getNextPosition(dir, creature->getPosition()); } CombatSpell::CombatSpell(Combat* _combat, bool _needTarget, bool _needDirection) : Event(&g_spells->getScriptInterface()) { combat = _combat; needTarget = _needTarget; needDirection = _needDirection; } CombatSpell::~CombatSpell() { if (!scripted) { delete combat; } } bool CombatSpell::loadScriptCombat() { combat = g_luaEnvironment.getCombatObject(g_luaEnvironment.lastCombatId); return combat != nullptr; } bool CombatSpell::castSpell(Creature* creature) { if (scripted) { LuaVariant var; var.type = VARIANT_POSITION; if (needDirection) { var.pos = Spells::getCasterPosition(creature, creature->getDirection()); } else { var.pos = creature->getPosition(); } return executeCastSpell(creature, var); } Position pos; if (needDirection) { pos = Spells::getCasterPosition(creature, creature->getDirection()); } else { pos = creature->getPosition(); } combat->doCombat(creature, pos); return true; } bool CombatSpell::castSpell(Creature* creature, Creature* target) { if (scripted) { LuaVariant var; if (combat->hasArea()) { var.type = VARIANT_POSITION; if (needTarget) { var.pos = target->getPosition(); } else if (needDirection) { var.pos = Spells::getCasterPosition(creature, creature->getDirection()); } else { var.pos = creature->getPosition(); } } else { var.type = VARIANT_NUMBER; var.number = target->getID(); } return executeCastSpell(creature, var); } if (combat->hasArea()) { if (needTarget) { combat->doCombat(creature, target->getPosition()); } else { return castSpell(creature); } } else { combat->doCombat(creature, target); } return true; } bool CombatSpell::executeCastSpell(Creature* creature, const LuaVariant& var) { //onCastSpell(creature, var) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CombatSpell::executeCastSpell] Call stack overflow" << std::endl; return false; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(scriptId, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(scriptId); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); LuaScriptInterface::pushVariant(L, var); return scriptInterface->callFunction(2); } Spell::Spell() { spellId = 0; level = 0; magLevel = 0; mana = 0; manaPercent = 0; soul = 0; range = -1; cooldown = 1000; needTarget = false; needWeapon = false; selfTarget = false; blockingSolid = false; blockingCreature = false; premium = false; enabled = true; aggressive = true; learnable = false; } bool Spell::configureSpell(const pugi::xml_node& node) { pugi::xml_attribute nameAttribute = node.attribute("name"); if (!nameAttribute) { std::cout << "[Error - Spell::configureSpell] Spell without name" << std::endl; return false; } name = nameAttribute.as_string(); static const char* reservedList[] = { "melee", "physical", "poison", "fire", "energy", "drown", "lifedrain", "manadrain", "healing", "speed", "outfit", "invisible", "drunk", "firefield", "poisonfield", "energyfield", "firecondition", "poisoncondition", "energycondition", "drowncondition", }; //static size_t size = sizeof(reservedList) / sizeof(const char*); //for (size_t i = 0; i < size; ++i) { for (const char* reserved : reservedList) { if (strcasecmp(reserved, name.c_str()) == 0) { std::cout << "[Error - Spell::configureSpell] Spell is using a reserved name: " << reserved << std::endl; return false; } } pugi::xml_attribute attr; if ((attr = node.attribute("spellid"))) { spellId = pugi::cast(attr.value()); } if ((attr = node.attribute("lvl"))) { level = pugi::cast(attr.value()); } if ((attr = node.attribute("maglv"))) { magLevel = pugi::cast(attr.value()); } if ((attr = node.attribute("mana"))) { mana = pugi::cast(attr.value()); } if ((attr = node.attribute("manapercent"))) { manaPercent = pugi::cast(attr.value()); } if ((attr = node.attribute("soul"))) { soul = pugi::cast(attr.value()); } if ((attr = node.attribute("exhaustion")) || (attr = node.attribute("cooldown"))) { cooldown = pugi::cast(attr.value()); } if ((attr = node.attribute("range"))) { range = pugi::cast(attr.value()); } if ((attr = node.attribute("prem"))) { premium = attr.as_bool(); } if ((attr = node.attribute("enabled"))) { enabled = attr.as_bool(); } if ((attr = node.attribute("needtarget"))) { needTarget = attr.as_bool(); } if ((attr = node.attribute("needweapon"))) { needWeapon = attr.as_bool(); } if ((attr = node.attribute("selftarget"))) { selfTarget = attr.as_bool(); } if ((attr = node.attribute("needlearn"))) { learnable = attr.as_bool(); } if ((attr = node.attribute("blocking"))) { blockingSolid = attr.as_bool(); blockingCreature = blockingSolid; } if ((attr = node.attribute("blocktype"))) { std::string tmpStrValue = asLowerCaseString(attr.as_string()); if (tmpStrValue == "all") { blockingSolid = true; blockingCreature = true; } else if (tmpStrValue == "solid") { blockingSolid = true; } else if (tmpStrValue == "creature") { blockingCreature = true; } else { std::cout << "[Warning - Spell::configureSpell] Blocktype \"" << attr.as_string() << "\" does not exist." << std::endl; } } if ((attr = node.attribute("aggressive"))) { aggressive = booleanString(attr.as_string()); } for (auto vocationNode : node.children()) { if (!(attr = vocationNode.attribute("name"))) { continue; } int32_t vocationId = g_vocations.getVocationId(attr.as_string()); if (vocationId != -1) { attr = vocationNode.attribute("showInDescription"); vocSpellMap[vocationId] = !attr || attr.as_bool(); } else { std::cout << "[Warning - Spell::configureSpell] Wrong vocation name: " << attr.as_string() << std::endl; } } return true; } bool Spell::playerSpellCheck(Player* player) const { if (player->hasFlag(PlayerFlag_CannotUseSpells)) { return false; } if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { return true; } if (!enabled) { return false; } if (aggressive && !player->hasFlag(PlayerFlag_IgnoreProtectionZone) && player->getZone() == ZONE_PROTECTION) { player->sendCancelMessage(RETURNVALUE_ACTIONNOTPERMITTEDINPROTECTIONZONE); return false; } if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { bool exhaust = false; if (aggressive) { if (player->hasCondition(CONDITION_EXHAUST_COMBAT)) { exhaust = true; } } else { if (player->hasCondition(CONDITION_EXHAUST_HEAL)) { exhaust = true; } } if (exhaust) { player->sendCancelMessage(RETURNVALUE_YOUAREEXHAUSTED); if (isInstant()) { g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); } return false; } } if (player->getLevel() < level) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHLEVEL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (player->getMagicLevel() < magLevel) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHMAGICLEVEL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (player->getMana() < getManaCost(player) && !player->hasFlag(PlayerFlag_HasInfiniteMana)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (player->getSoul() < soul && !player->hasFlag(PlayerFlag_HasInfiniteSoul)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHSOUL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (isInstant() && isLearnable()) { if (!player->hasLearnedInstantSpell(getName())) { player->sendCancelMessage(RETURNVALUE_YOUNEEDTOLEARNTHISSPELL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } } else if (!vocSpellMap.empty() && vocSpellMap.find(player->getVocationId()) == vocSpellMap.end()) { player->sendCancelMessage(RETURNVALUE_YOURVOCATIONCANNOTUSETHISSPELL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (needWeapon) { switch (player->getWeaponType()) { case WEAPON_SWORD: case WEAPON_CLUB: case WEAPON_AXE: break; default: { player->sendCancelMessage(RETURNVALUE_YOUNEEDAWEAPONTOUSETHISSPELL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } } } if (isPremium() && !player->isPremium()) { player->sendCancelMessage(RETURNVALUE_YOUNEEDPREMIUMACCOUNT); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } return true; } bool Spell::playerInstantSpellCheck(Player* player, const Position& toPos) { if (toPos.x == 0xFFFF) { return true; } const Position& playerPos = player->getPosition(); if (playerPos.z > toPos.z) { player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } else if (playerPos.z < toPos.z) { player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } Tile* tile = g_game.map.getTile(toPos); if (!tile) { tile = new StaticTile(toPos.x, toPos.y, toPos.z); g_game.map.setTile(toPos, tile); } ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (blockingCreature && tile->getBottomVisibleCreature(player) != nullptr) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } return true; } bool Spell::playerRuneSpellCheck(Player* player, const Position& toPos) { if (!playerSpellCheck(player)) { return false; } if (toPos.x == 0xFFFF) { return true; } const Position& playerPos = player->getPosition(); if (playerPos.z > toPos.z) { player->sendCancelMessage(RETURNVALUE_FIRSTGOUPSTAIRS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } else if (playerPos.z < toPos.z) { player->sendCancelMessage(RETURNVALUE_FIRSTGODOWNSTAIRS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } Tile* tile = g_game.map.getTile(toPos); if (!tile) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (range != -1 && !g_game.canThrowObjectTo(playerPos, toPos, true, range, range)) { player->sendCancelMessage(RETURNVALUE_DESTINATIONOUTOFREACH); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } ReturnValue ret = Combat::canDoCombat(player, tile, aggressive); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } const Creature* topVisibleCreature = tile->getBottomVisibleCreature(player); if (blockingCreature && topVisibleCreature) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } else if (blockingSolid && tile->hasFlag(TILESTATE_BLOCKSOLID)) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (needTarget && !topVisibleCreature) { player->sendCancelMessage(RETURNVALUE_CANONLYUSETHISRUNEONCREATURES); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (aggressive && needTarget && topVisibleCreature && player->hasSecureMode()) { const Player* targetPlayer = topVisibleCreature->getPlayer(); if (targetPlayer && targetPlayer != player && player->getSkullClient(targetPlayer) == SKULL_NONE && !Combat::isInPvpZone(player, targetPlayer)) { player->sendCancelMessage(RETURNVALUE_TURNSECUREMODETOATTACKUNMARKEDPLAYERS); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } } return true; } void Spell::postCastSpell(Player* player, bool finishedCast /*= true*/, bool payCost /*= true*/) const { if (finishedCast) { if (!player->hasFlag(PlayerFlag_HasNoExhaustion)) { if (cooldown > 0) { if (aggressive) { player->addCombatExhaust(cooldown); } else { player->addHealExhaust(cooldown); } } if (!player->hasFlag(PlayerFlag_NotGainInFight)) { if (aggressive) { player->addInFightTicks(); } } } } if (payCost) { Spell::postCastSpell(player, getManaCost(player), getSoulCost()); } } void Spell::postCastSpell(Player* player, uint32_t manaCost, uint32_t soulCost) { if (manaCost > 0) { player->addManaSpent(manaCost); player->changeMana(-static_cast(manaCost)); } if (!player->hasFlag(PlayerFlag_HasInfiniteSoul)) { if (soulCost > 0) { player->changeSoul(-static_cast(soulCost)); } } } uint32_t Spell::getManaCost(const Player* player) const { if (mana != 0) { return mana; } if (manaPercent != 0) { uint32_t maxMana = player->getMaxMana(); uint32_t manaCost = (maxMana * manaPercent) / 100; return manaCost; } return 0; } ReturnValue Spell::CreateIllusion(Creature* creature, const Outfit_t& outfit, int32_t time) { ConditionOutfit* outfitCondition = new ConditionOutfit(CONDITIONID_COMBAT, CONDITION_OUTFIT, time); outfitCondition->setOutfit(outfit); creature->addCondition(outfitCondition); return RETURNVALUE_NOERROR; } ReturnValue Spell::CreateIllusion(Creature* creature, const std::string& name, int32_t time) { const auto mType = g_monsters.getMonsterType(name); if (mType == nullptr) { return RETURNVALUE_CREATUREDOESNOTEXIST; } Player* player = creature->getPlayer(); if (player && !player->hasFlag(PlayerFlag_CanIllusionAll)) { if (!mType->isIllusionable) { return RETURNVALUE_NOTPOSSIBLE; } } return CreateIllusion(creature, mType->outfit, time); } ReturnValue Spell::CreateIllusion(Creature* creature, uint32_t itemId, int32_t time) { const ItemType& it = Item::items[itemId]; if (it.id == 0) { return RETURNVALUE_NOTPOSSIBLE; } Outfit_t outfit; outfit.lookTypeEx = itemId; return CreateIllusion(creature, outfit, time); } InstantSpell::InstantSpell(LuaScriptInterface* _interface) : TalkAction(_interface) { needDirection = false; hasParam = false; hasPlayerNameParam = false; checkLineOfSight = true; casterTargetOrDirection = false; function = nullptr; } std::string InstantSpell::getScriptEventName() const { return "onCastSpell"; } bool InstantSpell::configureEvent(const pugi::xml_node& node) { if (!Spell::configureSpell(node)) { return false; } if (!TalkAction::configureEvent(node)) { return false; } pugi::xml_attribute attr; if ((attr = node.attribute("params"))) { hasParam = attr.as_bool(); } if ((attr = node.attribute("playernameparam"))) { hasPlayerNameParam = attr.as_bool(); } if ((attr = node.attribute("direction"))) { needDirection = attr.as_bool(); } else if ((attr = node.attribute("casterTargetOrDirection"))) { casterTargetOrDirection = attr.as_bool(); } if ((attr = node.attribute("blockwalls"))) { checkLineOfSight = attr.as_bool(); } return true; } bool InstantSpell::loadFunction(const pugi::xml_attribute& attr) { const char* functionName = attr.as_string(); if (strcasecmp(functionName, "edithouseguest") == 0) { function = HouseGuestList; } else if (strcasecmp(functionName, "edithousesubowner") == 0) { function = HouseSubOwnerList; } else if (strcasecmp(functionName, "edithousedoor") == 0) { function = HouseDoorList; } else if (strcasecmp(functionName, "housekick") == 0) { function = HouseKick; } else if (strcasecmp(functionName, "searchplayer") == 0) { function = SearchPlayer; } else if (strcasecmp(functionName, "levitate") == 0) { function = Levitate; } else if (strcasecmp(functionName, "illusion") == 0) { function = Illusion; } else if (strcasecmp(functionName, "summonmonster") == 0) { function = SummonMonster; } else { std::cout << "[Warning - InstantSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; return false; } scripted = false; return true; } bool InstantSpell::playerCastInstant(Player* player, std::string& param) { if (!playerSpellCheck(player)) { return false; } LuaVariant var; if (selfTarget) { var.type = VARIANT_NUMBER; var.number = player->getID(); } else if (needTarget || casterTargetOrDirection) { Creature* target = nullptr; bool useDirection = false; if (hasParam) { Player* playerTarget = nullptr; ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); if (playerTarget && playerTarget->isAccessPlayer() && !player->isAccessPlayer()) { playerTarget = nullptr; } target = playerTarget; if (!target || target->getHealth() <= 0) { if (!casterTargetOrDirection) { if (cooldown > 0) { if (aggressive) { player->addCombatExhaust(cooldown); } else { player->addHealExhaust(cooldown); } } player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } useDirection = true; } if (playerTarget) { param = playerTarget->getName(); } } else { target = player->getAttackedCreature(); if (!target || target->getHealth() <= 0) { if (!casterTargetOrDirection) { player->sendCancelMessage(RETURNVALUE_YOUCANONLYUSEITONCREATURES); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } useDirection = true; } } if (!useDirection) { if (!canThrowSpell(player, target)) { player->sendCancelMessage(RETURNVALUE_CREATUREISNOTREACHABLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } var.type = VARIANT_NUMBER; var.number = target->getID(); } else { var.type = VARIANT_POSITION; var.pos = Spells::getCasterPosition(player, player->getDirection()); if (!playerInstantSpellCheck(player, var.pos)) { return false; } } } else if (hasParam) { var.type = VARIANT_STRING; if (getHasPlayerNameParam()) { Player* playerTarget = nullptr; ReturnValue ret = g_game.getPlayerByNameWildcard(param, playerTarget); if (ret != RETURNVALUE_NOERROR) { if (cooldown > 0) { if (aggressive) { player->addCombatExhaust(cooldown); } else { player->addHealExhaust(cooldown); } } player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (playerTarget && (!playerTarget->isAccessPlayer() || player->isAccessPlayer())) { param = playerTarget->getName(); } } var.text = param; } else { var.type = VARIANT_POSITION; if (needDirection) { var.pos = Spells::getCasterPosition(player, player->getDirection()); } else { var.pos = player->getPosition(); } if (!playerInstantSpellCheck(player, var.pos)) { return false; } } bool result = internalCastSpell(player, var); if (result) { postCastSpell(player); } return result; } bool InstantSpell::canThrowSpell(const Creature* creature, const Creature* target) const { const Position& fromPos = creature->getPosition(); const Position& toPos = target->getPosition(); if (fromPos.z != toPos.z || (range == -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight)) || (range != -1 && !g_game.canThrowObjectTo(fromPos, toPos, checkLineOfSight, range, range))) { return false; } return true; } bool InstantSpell::castSpell(Creature* creature) { LuaVariant var; if (casterTargetOrDirection) { Creature* target = creature->getAttackedCreature(); if (target && target->getHealth() > 0) { if (!canThrowSpell(creature, target)) { return false; } var.type = VARIANT_NUMBER; var.number = target->getID(); return internalCastSpell(creature, var); } return false; } else if (needDirection) { var.type = VARIANT_POSITION; var.pos = Spells::getCasterPosition(creature, creature->getDirection()); } else { var.type = VARIANT_POSITION; var.pos = creature->getPosition(); } return internalCastSpell(creature, var); } bool InstantSpell::castSpell(Creature* creature, Creature* target) { if (needTarget) { LuaVariant var; var.type = VARIANT_NUMBER; var.number = target->getID(); return internalCastSpell(creature, var); } else { return castSpell(creature); } } bool InstantSpell::internalCastSpell(Creature* creature, const LuaVariant& var) { if (scripted) { return executeCastSpell(creature, var); } else if (function) { return function(this, creature, var.text); } return false; } bool InstantSpell::executeCastSpell(Creature* creature, const LuaVariant& var) { //onCastSpell(creature, var) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - InstantSpell::executeCastSpell] Call stack overflow" << std::endl; return false; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(scriptId, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(scriptId); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); LuaScriptInterface::pushVariant(L, var); return scriptInterface->callFunction(2); } House* InstantSpell::getHouseFromPos(Creature* creature) { if (!creature) { return nullptr; } Player* player = creature->getPlayer(); if (!player) { return nullptr; } HouseTile* houseTile = dynamic_cast(player->getTile()); if (!houseTile) { return nullptr; } House* house = houseTile->getHouse(); if (!house) { return nullptr; } return house; } bool InstantSpell::HouseGuestList(const InstantSpell*, Creature* creature, const std::string&) { House* house = getHouseFromPos(creature); if (!house) { return false; } Player* player = creature->getPlayer(); if (house->canEditAccessList(GUEST_LIST, player)) { player->setEditHouse(house, GUEST_LIST); player->sendHouseWindow(house, GUEST_LIST); } else { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); } return true; } bool InstantSpell::HouseSubOwnerList(const InstantSpell*, Creature* creature, const std::string&) { House* house = getHouseFromPos(creature); if (!house) { return false; } Player* player = creature->getPlayer(); if (house->canEditAccessList(SUBOWNER_LIST, player)) { player->setEditHouse(house, SUBOWNER_LIST); player->sendHouseWindow(house, SUBOWNER_LIST); } else { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); } return true; } bool InstantSpell::HouseDoorList(const InstantSpell*, Creature* creature, const std::string&) { House* house = getHouseFromPos(creature); if (!house) { return false; } Player* player = creature->getPlayer(); Position pos = Spells::getCasterPosition(player, player->getDirection()); Door* door = house->getDoorByPosition(pos); if (door && house->canEditAccessList(door->getDoorId(), player)) { player->setEditHouse(house, door->getDoorId()); player->sendHouseWindow(house, door->getDoorId()); } else { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); } return true; } bool InstantSpell::HouseKick(const InstantSpell*, Creature* creature, const std::string& param) { Player* player = creature->getPlayer(); Player* targetPlayer = g_game.getPlayerByName(param); if (!targetPlayer) { targetPlayer = player; } House* house = getHouseFromPos(targetPlayer); House* owner = getHouseFromPos(player); if (!house || !owner || (house->getId() != owner->getId())) { g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return false; } if (!house->kickPlayer(player, targetPlayer)) { g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); return false; } return true; } bool InstantSpell::SearchPlayer(const InstantSpell*, Creature* creature, const std::string& param) { //a. From 1 to 4 sq's [Person] is standing next to you. //b. From 5 to 100 sq's [Person] is to the south, north, east, west. //c. From 101 to 274 sq's [Person] is far to the south, north, east, west. //d. From 275 to infinite sq's [Person] is very far to the south, north, east, west. //e. South-west, s-e, n-w, n-e (corner coordinates): this phrase appears if the player you're looking for has moved five squares in any direction from the south, north, east or west. //f. Lower level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. //g. Higher level to the (direction): this phrase applies if the person you're looking for is from 1-25 squares up/down the actual floor you're in. Player* player = creature->getPlayer(); if (!player) { return false; } enum distance_t { DISTANCE_BESIDE, DISTANCE_CLOSE, DISTANCE_FAR, DISTANCE_VERYFAR, }; enum direction_t { DIR_N, DIR_S, DIR_E, DIR_W, DIR_NE, DIR_NW, DIR_SE, DIR_SW, }; enum level_t { LEVEL_HIGHER, LEVEL_LOWER, LEVEL_SAME, }; Player* playerExiva = g_game.getPlayerByName(param); if (!playerExiva) { return false; } if (playerExiva->isAccessPlayer() && !player->isAccessPlayer()) { player->sendCancelMessage(RETURNVALUE_PLAYERWITHTHISNAMEISNOTONLINE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } const Position& lookPos = player->getPosition(); const Position& searchPos = playerExiva->getPosition(); int32_t dx = Position::getOffsetX(lookPos, searchPos); int32_t dy = Position::getOffsetY(lookPos, searchPos); int32_t dz = Position::getOffsetZ(lookPos, searchPos); distance_t distance; direction_t direction; level_t level; //getting floor if (dz > 0) { level = LEVEL_HIGHER; } else if (dz < 0) { level = LEVEL_LOWER; } else { level = LEVEL_SAME; } //getting distance if (std::abs(dx) < 4 && std::abs(dy) < 4) { distance = DISTANCE_BESIDE; } else { int32_t distance2 = dx * dx + dy * dy; if (distance2 < 10000) { distance = DISTANCE_CLOSE; } else if (distance2 < 75076) { distance = DISTANCE_FAR; } else { distance = DISTANCE_VERYFAR; } } //getting direction float tan; if (dx != 0) { tan = static_cast(dy) / dx; } else { tan = 10.; } if (std::abs(tan) < 0.4142) { if (dx > 0) { direction = DIR_W; } else { direction = DIR_E; } } else if (std::abs(tan) < 2.4142) { if (tan > 0) { if (dy > 0) { direction = DIR_NW; } else { direction = DIR_SE; } } else { if (dx > 0) { direction = DIR_SW; } else { direction = DIR_NE; } } } else { if (dy > 0) { direction = DIR_N; } else { direction = DIR_S; } } std::ostringstream ss; ss << playerExiva->getName(); if (distance == DISTANCE_BESIDE) { if (level == LEVEL_SAME) { ss << " is standing next to you."; } else if (level == LEVEL_HIGHER) { ss << " is above you."; } else if (level == LEVEL_LOWER) { ss << " is below you."; } } else { switch (distance) { case DISTANCE_CLOSE: if (level == LEVEL_SAME) { ss << " is to the "; } else if (level == LEVEL_HIGHER) { ss << " is on a higher level to the "; } else if (level == LEVEL_LOWER) { ss << " is on a lower level to the "; } break; case DISTANCE_FAR: ss << " is far to the "; break; case DISTANCE_VERYFAR: ss << " is very far to the "; break; default: break; } switch (direction) { case DIR_N: ss << "north."; break; case DIR_S: ss << "south."; break; case DIR_E: ss << "east."; break; case DIR_W: ss << "west."; break; case DIR_NE: ss << "north-east."; break; case DIR_NW: ss << "north-west."; break; case DIR_SE: ss << "south-east."; break; case DIR_SW: ss << "south-west."; break; } } player->sendTextMessage(MESSAGE_INFO_DESCR, ss.str()); g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); return true; } bool InstantSpell::SummonMonster(const InstantSpell* spell, Creature* creature, const std::string& param) { Player* player = creature->getPlayer(); if (!player) { return false; } MonsterType* mType = g_monsters.getMonsterType(param); if (!mType) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (!player->hasFlag(PlayerFlag_CanSummonAll)) { if (!mType->isSummonable) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (player->getMana() < mType->manaCost) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (player->getSummonCount() >= 2) { player->sendCancelMessage("You cannot summon more creatures."); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } } Monster* monster = Monster::createMonster(param); if (!monster) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } // Place the monster creature->addSummon(monster); if (!g_game.placeCreature(monster, creature->getPosition(), true)) { creature->removeSummon(monster); player->sendCancelMessage(RETURNVALUE_NOTENOUGHROOM); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } Spell::postCastSpell(player, mType->manaCost, spell->getSoulCost()); g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_BLUE); g_game.addMagicEffect(monster->getPosition(), CONST_ME_TELEPORT); return true; } bool InstantSpell::Levitate(const InstantSpell*, Creature* creature, const std::string& param) { Player* player = creature->getPlayer(); if (!player) { return false; } const Position& currentPos = creature->getPosition(); const Position& destPos = Spells::getCasterPosition(creature, creature->getDirection()); ReturnValue ret = RETURNVALUE_NOTPOSSIBLE; if (strcasecmp(param.c_str(), "up") == 0) { if (currentPos.z != 8) { Tile* tmpTile = g_game.map.getTile(currentPos.x, currentPos.y, currentPos.getZ() - 1); if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID))) { tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.getZ() - 1); if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID | TILESTATE_FLOORCHANGE)) { ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); } } } } else if (strcasecmp(param.c_str(), "down") == 0) { if (currentPos.z != 7) { Tile* tmpTile = g_game.map.getTile(destPos); if (tmpTile == nullptr || (tmpTile->getGround() == nullptr && !tmpTile->hasFlag(TILESTATE_BLOCKSOLID))) { tmpTile = g_game.map.getTile(destPos.x, destPos.y, destPos.z + 1); if (tmpTile && tmpTile->getGround() && !tmpTile->hasFlag(TILESTATE_IMMOVABLEBLOCKSOLID | TILESTATE_FLOORCHANGE)) { ret = g_game.internalMoveCreature(*player, *tmpTile, FLAG_IGNOREBLOCKITEM | FLAG_IGNOREBLOCKCREATURE); } } } } if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } g_game.addMagicEffect(player->getPosition(), CONST_ME_TELEPORT); return true; } bool InstantSpell::Illusion(const InstantSpell*, Creature* creature, const std::string& param) { Player* player = creature->getPlayer(); if (!player) { return false; } ReturnValue ret = CreateIllusion(creature, param, 180000); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); return true; } bool InstantSpell::canCast(const Player* player) const { if (player->hasFlag(PlayerFlag_CannotUseSpells)) { return false; } if (player->hasFlag(PlayerFlag_IgnoreSpellCheck)) { return true; } if (isLearnable()) { if (player->hasLearnedInstantSpell(getName())) { return true; } } else { if (vocSpellMap.empty() || vocSpellMap.find(player->getVocationId()) != vocSpellMap.end()) { return true; } } return false; } ConjureSpell::ConjureSpell(LuaScriptInterface* _interface) : InstantSpell(_interface) { aggressive = false; conjureId = 0; conjureCount = 1; reagentId = 0; } std::string ConjureSpell::getScriptEventName() const { return "onCastSpell"; } bool ConjureSpell::configureEvent(const pugi::xml_node& node) { if (!InstantSpell::configureEvent(node)) { return false; } pugi::xml_attribute attr; if ((attr = node.attribute("conjureId"))) { conjureId = pugi::cast(attr.value()); } if ((attr = node.attribute("conjureCount"))) { conjureCount = pugi::cast(attr.value()); } else if (conjureId != 0) { // load default charges from items.xml const ItemType& it = Item::items[conjureId]; if (it.charges != 0) { conjureCount = it.charges; } } if ((attr = node.attribute("reagentId"))) { reagentId = pugi::cast(attr.value()); } return true; } bool ConjureSpell::loadFunction(const pugi::xml_attribute&) { scripted = false; return true; } bool ConjureSpell::conjureItem(Creature* creature) const { Player* player = creature->getPlayer(); if (!player) { return false; } if (reagentId != 0 && !player->removeItemOfType(reagentId, 1, -1)) { player->sendCancelMessage(RETURNVALUE_YOUNEEDAMAGICITEMTOCASTSPELL); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } Item* newItem = Item::CreateItem(conjureId, conjureCount); if (!newItem) { return false; } ReturnValue ret = g_game.internalPlayerAddItem(player, newItem); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); delete newItem; return false; } g_game.startDecay(newItem); postCastSpell(player); g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); return true; } bool ConjureSpell::playerCastInstant(Player* player, std::string& param) { if (!playerSpellCheck(player)) { return false; } if (scripted) { LuaVariant var; var.type = VARIANT_STRING; var.text = param; return executeCastSpell(player, var); } return conjureItem(player); } RuneSpell::RuneSpell(LuaScriptInterface* _interface) : Action(_interface) { hasCharges = true; runeId = 0; runeFunction = nullptr; allowFarUse = true; } std::string RuneSpell::getScriptEventName() const { return "onCastSpell"; } bool RuneSpell::configureEvent(const pugi::xml_node& node) { if (!Spell::configureSpell(node)) { return false; } if (!Action::configureEvent(node)) { return false; } pugi::xml_attribute attr; if (!(attr = node.attribute("id"))) { std::cout << "[Error - RuneSpell::configureSpell] Rune spell without id." << std::endl; return false; } runeId = pugi::cast(attr.value()); uint32_t charges; if ((attr = node.attribute("charges"))) { charges = pugi::cast(attr.value()); } else { charges = 0; } hasCharges = (charges > 0); if (magLevel != 0 || level != 0) { //Change information in the ItemType to get accurate description ItemType& iType = Item::items.getItemType(runeId); iType.runeMagLevel = magLevel; iType.runeLevel = level; iType.charges = charges; } return true; } bool RuneSpell::loadFunction(const pugi::xml_attribute& attr) { const char* functionName = attr.as_string(); if (strcasecmp(functionName, "chameleon") == 0) { runeFunction = Illusion; } else if (strcasecmp(functionName, "convince") == 0) { runeFunction = Convince; } else { std::cout << "[Warning - RuneSpell::loadFunction] Function \"" << functionName << "\" does not exist." << std::endl; return false; } scripted = false; return true; } bool RuneSpell::Illusion(const RuneSpell*, Player* player, const Position& posTo) { Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_MOVE); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } Item* illusionItem = thing->getItem(); if (!illusionItem || !illusionItem->isMoveable()) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } ReturnValue ret = CreateIllusion(player, illusionItem->getID(), 200000); if (ret != RETURNVALUE_NOERROR) { player->sendCancelMessage(ret); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); return true; } bool RuneSpell::Convince(const RuneSpell* spell, Player* player, const Position& posTo) { if (!player->hasFlag(PlayerFlag_CanConvinceAll)) { if (player->getSummonCount() >= 2) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } } Thing* thing = g_game.internalGetThing(player, posTo, 0, 0, STACKPOS_LOOK); if (!thing) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } Creature* convinceCreature = thing->getCreature(); if (!convinceCreature) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } uint32_t manaCost = 0; if (convinceCreature->getMonster()) { manaCost = convinceCreature->getMonster()->getManaCost(); } if (!player->hasFlag(PlayerFlag_HasInfiniteMana) && player->getMana() < manaCost) { player->sendCancelMessage(RETURNVALUE_NOTENOUGHMANA); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } if (!convinceCreature->convinceCreature(player)) { player->sendCancelMessage(RETURNVALUE_NOTPOSSIBLE); g_game.addMagicEffect(player->getPosition(), CONST_ME_POFF); return false; } Spell::postCastSpell(player, manaCost, spell->getSoulCost()); g_game.addMagicEffect(player->getPosition(), CONST_ME_MAGIC_RED); return true; } ReturnValue RuneSpell::canExecuteAction(const Player* player, const Position& toPos) { if (player->hasFlag(PlayerFlag_CannotUseSpells)) { return RETURNVALUE_CANNOTUSETHISOBJECT; } ReturnValue ret = Action::canExecuteAction(player, toPos); if (ret != RETURNVALUE_NOERROR) { return ret; } if (toPos.x == 0xFFFF) { if (needTarget) { return RETURNVALUE_CANONLYUSETHISRUNEONCREATURES; } else if (!selfTarget) { return RETURNVALUE_NOTENOUGHROOM; } } return RETURNVALUE_NOERROR; } bool RuneSpell::executeUse(Player* player, Item* item, const Position&, Thing* target, const Position& toPosition, bool isHotkey) { if (!playerRuneSpellCheck(player, toPosition)) { return false; } bool result = false; if (scripted) { LuaVariant var; if (needTarget) { var.type = VARIANT_NUMBER; if (target == nullptr) { Tile* toTile = g_game.map.getTile(toPosition); if (toTile) { const Creature* visibleCreature = toTile->getBottomVisibleCreature(player); if (visibleCreature) { var.number = visibleCreature->getID(); } } } else { var.number = target->getCreature()->getID(); } } else { var.type = VARIANT_POSITION; var.pos = toPosition; } result = internalCastSpell(player, var, isHotkey); } else if (runeFunction) { result = runeFunction(this, player, toPosition); } if (!result) { return false; } postCastSpell(player); if (hasCharges && item && g_config.getBoolean(ConfigManager::REMOVE_RUNE_CHARGES) && !player->getTile()->hasFlag(TILESTATE_PVPZONE)) { int32_t newCount = std::max(0, item->getItemCount() - 1); g_game.transformItem(item, item->getID(), newCount); } return true; } bool RuneSpell::castSpell(Creature* creature) { LuaVariant var; var.type = VARIANT_NUMBER; var.number = creature->getID(); return internalCastSpell(creature, var, false); } bool RuneSpell::castSpell(Creature* creature, Creature* target) { LuaVariant var; var.type = VARIANT_NUMBER; var.number = target->getID(); return internalCastSpell(creature, var, false); } bool RuneSpell::internalCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey) { bool result; if (scripted) { result = executeCastSpell(creature, var, isHotkey); } else { result = false; } return result; } bool RuneSpell::executeCastSpell(Creature* creature, const LuaVariant& var, bool isHotkey) { //onCastSpell(creature, var, isHotkey) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - RuneSpell::executeCastSpell] Call stack overflow" << std::endl; return false; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(scriptId, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(scriptId); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); LuaScriptInterface::pushVariant(L, var); LuaScriptInterface::pushBoolean(L, isHotkey); return scriptInterface->callFunction(3); }