/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 Sabrehaven and 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 "creatureevent.h" #include "tools.h" #include "player.h" CreatureEvents::CreatureEvents() : scriptInterface("CreatureScript Interface") { scriptInterface.initState(); } CreatureEvents::~CreatureEvents() { for (const auto& it : creatureEvents) { delete it.second; } } void CreatureEvents::clear() { //clear creature events for (const auto& it : creatureEvents) { it.second->clearEvent(); } //clear lua state scriptInterface.reInitState(); } LuaScriptInterface& CreatureEvents::getScriptInterface() { return scriptInterface; } std::string CreatureEvents::getScriptBaseName() const { return "creaturescripts"; } Event* CreatureEvents::getEvent(const std::string& nodeName) { if (strcasecmp(nodeName.c_str(), "event") != 0) { return nullptr; } return new CreatureEvent(&scriptInterface); } bool CreatureEvents::registerEvent(Event* event, const pugi::xml_node&) { CreatureEvent* creatureEvent = static_cast(event); //event is guaranteed to be a CreatureEvent if (creatureEvent->getEventType() == CREATURE_EVENT_NONE) { std::cout << "Error: [CreatureEvents::registerEvent] Trying to register event without type!" << std::endl; return false; } CreatureEvent* oldEvent = getEventByName(creatureEvent->getName(), false); if (oldEvent) { //if there was an event with the same that is not loaded //(happens when realoading), it is reused if (!oldEvent->isLoaded() && oldEvent->getEventType() == creatureEvent->getEventType()) { oldEvent->copyEvent(creatureEvent); } return false; } else { //if not, register it normally creatureEvents[creatureEvent->getName()] = creatureEvent; return true; } } CreatureEvent* CreatureEvents::getEventByName(const std::string& name, bool forceLoaded /*= true*/) { auto it = creatureEvents.find(name); if (it != creatureEvents.end()) { if (!forceLoaded || it->second->isLoaded()) { return it->second; } } return nullptr; } bool CreatureEvents::playerLogin(Player* player) const { //fire global event if is registered for (const auto& it : creatureEvents) { if (it.second->getEventType() == CREATURE_EVENT_LOGIN) { if (!it.second->executeOnLogin(player)) { return false; } } } return true; } bool CreatureEvents::playerLogout(Player* player) const { //fire global event if is registered for (const auto& it : creatureEvents) { if (it.second->getEventType() == CREATURE_EVENT_LOGOUT) { if (!it.second->executeOnLogout(player)) { return false; } } } return true; } bool CreatureEvents::playerAdvance(Player* player, skills_t skill, uint32_t oldLevel, uint32_t newLevel) { for (const auto& it : creatureEvents) { if (it.second->getEventType() == CREATURE_EVENT_ADVANCE) { if (!it.second->executeAdvance(player, skill, oldLevel, newLevel)) { return false; } } } return true; } ///////////////////////////////////// CreatureEvent::CreatureEvent(LuaScriptInterface* interface) : Event(interface), type(CREATURE_EVENT_NONE), loaded(false) {} bool CreatureEvent::configureEvent(const pugi::xml_node& node) { // Name that will be used in monster xml files and // lua function to register events to reference this event pugi::xml_attribute nameAttribute = node.attribute("name"); if (!nameAttribute) { std::cout << "[Error - CreatureEvent::configureEvent] Missing name for creature event" << std::endl; return false; } eventName = nameAttribute.as_string(); pugi::xml_attribute typeAttribute = node.attribute("type"); if (!typeAttribute) { std::cout << "[Error - CreatureEvent::configureEvent] Missing type for creature event: " << eventName << std::endl; return false; } std::string tmpStr = asLowerCaseString(typeAttribute.as_string()); if (tmpStr == "login") { type = CREATURE_EVENT_LOGIN; } else if (tmpStr == "logout") { type = CREATURE_EVENT_LOGOUT; } else if (tmpStr == "think") { type = CREATURE_EVENT_THINK; } else if (tmpStr == "preparedeath") { type = CREATURE_EVENT_PREPAREDEATH; } else if (tmpStr == "death") { type = CREATURE_EVENT_DEATH; } else if (tmpStr == "kill") { type = CREATURE_EVENT_KILL; } else if (tmpStr == "advance") { type = CREATURE_EVENT_ADVANCE; } else if (tmpStr == "healthchange") { type = CREATURE_EVENT_HEALTHCHANGE; } else if (tmpStr == "manachange") { type = CREATURE_EVENT_MANACHANGE; } else if (tmpStr == "extendedopcode") { type = CREATURE_EVENT_EXTENDED_OPCODE; } else { std::cout << "[Error - CreatureEvent::configureEvent] Invalid type for creature event: " << eventName << std::endl; return false; } loaded = true; return true; } std::string CreatureEvent::getScriptEventName() const { //Depending on the type script event name is different switch (type) { case CREATURE_EVENT_LOGIN: return "onLogin"; case CREATURE_EVENT_LOGOUT: return "onLogout"; case CREATURE_EVENT_THINK: return "onThink"; case CREATURE_EVENT_PREPAREDEATH: return "onPrepareDeath"; case CREATURE_EVENT_DEATH: return "onDeath"; case CREATURE_EVENT_KILL: return "onKill"; case CREATURE_EVENT_ADVANCE: return "onAdvance"; case CREATURE_EVENT_HEALTHCHANGE: return "onHealthChange"; case CREATURE_EVENT_MANACHANGE: return "onManaChange"; case CREATURE_EVENT_EXTENDED_OPCODE: return "onExtendedOpcode"; case CREATURE_EVENT_NONE: default: return std::string(); } } void CreatureEvent::copyEvent(CreatureEvent* creatureEvent) { scriptId = creatureEvent->scriptId; scriptInterface = creatureEvent->scriptInterface; scripted = creatureEvent->scripted; loaded = creatureEvent->loaded; } void CreatureEvent::clearEvent() { scriptId = 0; scriptInterface = nullptr; scripted = false; loaded = false; } bool CreatureEvent::executeOnLogin(Player* player) { //onLogin(player) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnLogin] 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, player); LuaScriptInterface::setMetatable(L, -1, "Player"); return scriptInterface->callFunction(1); } bool CreatureEvent::executeOnLogout(Player* player) { //onLogout(player) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnLogout] 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, player); LuaScriptInterface::setMetatable(L, -1, "Player"); return scriptInterface->callFunction(1); } bool CreatureEvent::executeOnThink(Creature* creature, uint32_t interval) { //onThink(creature, interval) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnThink] 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); lua_pushnumber(L, interval); return scriptInterface->callFunction(2); } bool CreatureEvent::executeOnPrepareDeath(Creature* creature, Creature* killer) { //onPrepareDeath(creature, killer) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnPrepareDeath] 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); if (killer) { LuaScriptInterface::pushUserdata(L, killer); LuaScriptInterface::setCreatureMetatable(L, -1, killer); } else { lua_pushnil(L); } return scriptInterface->callFunction(2); } bool CreatureEvent::executeOnDeath(Creature* creature, Item* corpse, Creature* killer, Creature* mostDamageKiller, bool lastHitUnjustified, bool mostDamageUnjustified) { //onDeath(creature, corpse, lasthitkiller, mostdamagekiller, lasthitunjustified, mostdamageunjustified) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnDeath] 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::pushThing(L, corpse); if (killer) { LuaScriptInterface::pushUserdata(L, killer); LuaScriptInterface::setCreatureMetatable(L, -1, killer); } else { lua_pushnil(L); } if (mostDamageKiller) { LuaScriptInterface::pushUserdata(L, mostDamageKiller); LuaScriptInterface::setCreatureMetatable(L, -1, mostDamageKiller); } else { lua_pushnil(L); } LuaScriptInterface::pushBoolean(L, lastHitUnjustified); LuaScriptInterface::pushBoolean(L, mostDamageUnjustified); return scriptInterface->callFunction(6); } bool CreatureEvent::executeAdvance(Player* player, skills_t skill, uint32_t oldLevel, uint32_t newLevel) { //onAdvance(player, skill, oldLevel, newLevel) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeAdvance] 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, player); LuaScriptInterface::setMetatable(L, -1, "Player"); lua_pushnumber(L, static_cast(skill)); lua_pushnumber(L, oldLevel); lua_pushnumber(L, newLevel); return scriptInterface->callFunction(4); } void CreatureEvent::executeOnKill(Creature* creature, Creature* target) { //onKill(creature, target) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeOnKill] Call stack overflow" << std::endl; return; } 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::pushUserdata(L, target); LuaScriptInterface::setCreatureMetatable(L, -1, target); scriptInterface->callVoidFunction(2); } void CreatureEvent::executeHealthChange(Creature* creature, Creature* attacker, CombatDamage& damage) { //onHealthChange(creature, attacker, value, type, min, max, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeHealthChange] Call stack overflow" << std::endl; return; } 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); if (attacker) { LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); } else { lua_pushnil(L); } LuaScriptInterface::pushCombatDamage(L, damage); if (scriptInterface->protectedCall(L, 7, 4) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); } else { damage.value = std::abs(LuaScriptInterface::getNumber(L, -4)); damage.type = LuaScriptInterface::getNumber(L, -3); damage.min = std::abs(LuaScriptInterface::getNumber(L, -2)); damage.max = LuaScriptInterface::getNumber(L, -1); lua_pop(L, 4); if (damage.type != COMBAT_HEALING) { damage.value = -damage.value; } } scriptInterface->resetScriptEnv(); } void CreatureEvent::executeManaChange(Creature* creature, Creature* attacker, CombatDamage& damage) { //onManaChange(creature, attacker, value, type, min, max, origin) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeManaChange] Call stack overflow" << std::endl; return; } 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); if (attacker) { LuaScriptInterface::pushUserdata(L, attacker); LuaScriptInterface::setCreatureMetatable(L, -1, attacker); } else { lua_pushnil(L); } LuaScriptInterface::pushCombatDamage(L, damage); if (scriptInterface->protectedCall(L, 7, 4) != 0) { LuaScriptInterface::reportError(nullptr, LuaScriptInterface::popString(L)); } else { damage = LuaScriptInterface::getCombatDamage(L); } scriptInterface->resetScriptEnv(); } void CreatureEvent::executeExtendedOpcode(Player* player, uint8_t opcode, const std::string& buffer) { //onExtendedOpcode(player, opcode, buffer) if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - CreatureEvent::executeExtendedOpcode] Call stack overflow" << std::endl; return; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(scriptId, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(scriptId); LuaScriptInterface::pushUserdata(L, player); LuaScriptInterface::setMetatable(L, -1, "Player"); lua_pushnumber(L, opcode); LuaScriptInterface::pushString(L, buffer); scriptInterface->callVoidFunction(3); }