From dfc81983491417c5cd1c99d7db05e421c409379d Mon Sep 17 00:00:00 2001 From: Andrew Ward Date: Wed, 28 Mar 2018 16:05:18 +0100 Subject: [PATCH] Add reasons to on_dieplayer and on_hpchange --- builtin/game/register.lua | 6 +-- doc/lua_api.txt | 30 +++++++++----- games/minimal/mods/test/init.lua | 29 +++++++++++++ src/content_sao.cpp | 17 ++++---- src/content_sao.h | 63 ++++++++++++++++++++++++++++- src/network/serverpackethandler.cpp | 11 +++-- src/script/cpp_api/s_base.cpp | 23 ++++++++++- src/script/cpp_api/s_base.h | 3 ++ src/script/cpp_api/s_player.cpp | 20 ++++++--- src/script/cpp_api/s_player.h | 6 ++- src/script/lua_api/l_object.cpp | 39 +++++++++++++----- src/server.cpp | 16 ++++---- src/server.h | 5 ++- src/serverobject.h | 3 +- 14 files changed, 219 insertions(+), 52 deletions(-) diff --git a/builtin/game/register.lua b/builtin/game/register.lua index 5ce8caf3a..7b2b149e3 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -529,11 +529,11 @@ end core.registered_on_player_hpchanges = { modifiers = { }, loggers = { } } -function core.registered_on_player_hpchange(player, hp_change) +function core.registered_on_player_hpchange(player, hp_change, reason) local last = false for i = #core.registered_on_player_hpchanges.modifiers, 1, -1 do local func = core.registered_on_player_hpchanges.modifiers[i] - hp_change, last = func(player, hp_change) + hp_change, last = func(player, hp_change, reason) if type(hp_change) ~= "number" then local debuginfo = debug.getinfo(func) error("The register_on_hp_changes function has to return a number at " .. @@ -544,7 +544,7 @@ function core.registered_on_player_hpchange(player, hp_change) end end for i, func in ipairs(core.registered_on_player_hpchanges.loggers) do - func(player, hp_change) + func(player, hp_change, reason) end return hp_change end diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 730e6de9a..413990f61 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -1136,7 +1136,7 @@ previous. This combination results in noise varying very roughly between -2.0 and 2.0 and with an average value of 0.0, so `scale` and `offset` are then used to multiply -and offset the noise variation. +and offset the noise variation. The final perlin noise variation is created as follows: @@ -2779,8 +2779,6 @@ Call these functions only at load time! is a bit faster than usually. * `minetest.register_on_newplayer(func(ObjectRef))` * Called after a new player has been created -* `minetest.register_on_dieplayer(func(ObjectRef))` - * Called when a player dies * `minetest.register_on_punchplayer(func(player, hitter, time_from_last_punch, tool_capabilities, dir, damage))` * Called when a player is punched * `player` - ObjectRef - Player that was punched @@ -2792,15 +2790,28 @@ Call these functions only at load time! the puncher to the punched. * `damage` - number that represents the damage calculated by the engine * should return `true` to prevent the default damage mechanism -* `minetest.register_on_player_hpchange(func(player, hp_change), modifier)` +* `minetest.register_on_player_hpchange(func(player, hp_change, reason), modifier)` * Called when the player gets damaged or healed * `player`: ObjectRef of the player * `hp_change`: the amount of change. Negative when it is damage. + * `reason`: a PlayerHPChangeReason table. + * The `type` field will have one of the following values: + * `set_hp` - A mod or the engine called `set_hp` without + giving a type - use this for custom damage types. + * `punch` - Was punched. `reason.object` will hold the puncher, or nil if none. + * `fall` + * `node_damage` - damage_per_second from a neighbouring node. + * `drown` + * `respawn` + * Any of the above types may have additional fields from mods. + * `reason.from` will be `mod` or `engine`. * `modifier`: when true, the function should return the actual `hp_change`. - Note: modifiers only get a temporary hp_change that can be modified by - later modifiers. modifiers can return true as a second argument to stop - the execution of further functions. Non-modifiers receive the final hp - change calculated by the modifiers. + Note: modifiers only get a temporary hp_change that can be modified by later modifiers. + modifiers can return true as a second argument to stop the execution of further functions. + Non-modifiers receive the final hp change calculated by the modifiers. +* `minetest.register_on_dieplayer(func(ObjectRef, reason))` + * Called when a player dies + * `reason`: a PlayerHPChangeReason table, see register_on_player_hpchange * `minetest.register_on_respawnplayer(func(ObjectRef))` * Called when player is to be respawned * Called _before_ repositioning of player occurs @@ -3939,7 +3950,8 @@ This is basically a reference to a C++ `ServerActiveObject` * `direction`: can be `nil` * `right_click(clicker)`; `clicker` is another `ObjectRef` * `get_hp()`: returns number of hitpoints (2 * number of hearts) -* `set_hp(hp)`: set number of hitpoints (2 * number of hearts) +* `set_hp(hp, reason)`: set number of hitpoints (2 * number of hearts). + * See reason in register_on_player_hpchange * `get_inventory()`: returns an `InvRef` * `get_wield_list()`: returns the name of the inventory list the wielded item is in. diff --git a/games/minimal/mods/test/init.lua b/games/minimal/mods/test/init.lua index 051b47906..b08ddfa94 100644 --- a/games/minimal/mods/test/init.lua +++ b/games/minimal/mods/test/init.lua @@ -9,3 +9,32 @@ assert(pseudo:next() == 22290) assert(pseudo:next() == 13854) +-- +-- HP Change Reasons +-- +local expect = nil +minetest.register_on_joinplayer(function(player) + expect = { type = "set_hp", from = "mod" } + player:set_hp(3) + assert(expect == nil) + + expect = { a = 234, type = "set_hp", from = "mod" } + player:set_hp(10, { a= 234 }) + assert(expect == nil) + + expect = { df = 3458973454, type = "fall", from = "mod" } + player:set_hp(10, { type = "fall", df = 3458973454 }) + assert(expect == nil) +end) + +minetest.register_on_player_hpchange(function(player, hp, reason) + for key, value in pairs(reason) do + assert(expect[key] == value) + end + + for key, value in pairs(expect) do + assert(reason[key] == value) + end + + expect = nil +end) diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 816f47b72..f2cd0ff11 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -921,8 +921,9 @@ void PlayerSAO::step(float dtime, bool send_recommended) // No more breath, damage player if (m_breath == 0) { - setHP(m_hp - c.drowning); - m_env->getGameDef()->SendPlayerHPOrDie(this); + PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); + setHP(m_hp - c.drowning, reason); + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } } @@ -961,8 +962,9 @@ void PlayerSAO::step(float dtime, bool send_recommended) if (damage_per_second != 0 && m_hp > 0) { s16 newhp = ((s32) damage_per_second > m_hp ? 0 : m_hp - damage_per_second); - setHP(newhp); - m_env->getGameDef()->SendPlayerHPOrDie(this); + PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE); + setHP(newhp, reason); + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); } } @@ -1208,7 +1210,8 @@ int PlayerSAO::punch(v3f dir, hitparams.hp); if (!damage_handled) { - setHP(getHP() - hitparams.hp); + setHP(getHP() - hitparams.hp, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); } else { // override client prediction if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { std::string str = gob_cmd_punched(0, getHP()); @@ -1238,11 +1241,11 @@ s16 PlayerSAO::readDamage() return damage; } -void PlayerSAO::setHP(s16 hp) +void PlayerSAO::setHP(s16 hp, const PlayerHPChangeReason &reason) { s16 oldhp = m_hp; - s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp); + s16 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason); if (hp_change == 0) return; hp = oldhp + hp_change; diff --git a/src/content_sao.h b/src/content_sao.h index 683a2fb5f..10630014c 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -245,7 +245,7 @@ public: ServerActiveObject *puncher, float time_from_last_punch); void rightClick(ServerActiveObject *clicker) {} - void setHP(s16 hp); + void setHP(s16 hp, const PlayerHPChangeReason &reason); void setHPRaw(s16 hp) { m_hp = hp; } s16 readDamage(); u16 getBreath() const { return m_breath; } @@ -417,3 +417,64 @@ public: bool m_physics_override_new_move = true; bool m_physics_override_sent = false; }; + + +struct PlayerHPChangeReason { + enum Type : u8 { + SET_HP, + PLAYER_PUNCH, + FALL, + NODE_DAMAGE, + DROWNING, + RESPAWN + }; + + Type type = SET_HP; + ServerActiveObject *object; + bool from_mod = false; + int lua_reference = -1; + + bool setTypeFromString(const std::string &typestr) + { + if (typestr == "set_hp") + type = SET_HP; + else if (typestr == "punch") + type = PLAYER_PUNCH; + else if (typestr == "fall") + type = FALL; + else if (typestr == "node_damage") + type = NODE_DAMAGE; + else if (typestr == "drown") + type = DROWNING; + else if (typestr == "respawn") + type = RESPAWN; + else + return false; + + return true; + } + + std::string getTypeAsString() const + { + switch (type) { + case PlayerHPChangeReason::SET_HP: + return "set_hp"; + case PlayerHPChangeReason::PLAYER_PUNCH: + return "punch"; + case PlayerHPChangeReason::FALL: + return "fall"; + case PlayerHPChangeReason::NODE_DAMAGE: + return "node_damage"; + case PlayerHPChangeReason::DROWNING: + return "drown"; + case PlayerHPChangeReason::RESPAWN: + return "respawn"; + default: + return "?"; + } + } + + PlayerHPChangeReason(Type type, ServerActiveObject *object=NULL): + type(type), object(object) + {} +}; diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 76a2dc3c2..2d810836b 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -812,8 +812,9 @@ void Server::handleCommand_Damage(NetworkPacket* pkt) << (int)damage << " hp at " << PP(playersao->getBasePosition() / BS) << std::endl; - playersao->setHP(playersao->getHP() - damage); - SendPlayerHPOrDie(playersao); + PlayerHPChangeReason reason(PlayerHPChangeReason::FALL); + playersao->setHP(playersao->getHP() - damage, reason); + SendPlayerHPOrDie(playersao, reason); } } @@ -1175,12 +1176,14 @@ void Server::handleCommand_Interact(NetworkPacket* pkt) // If the object is a player and its HP changed if (src_original_hp != pointed_object->getHP() && pointed_object->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - SendPlayerHPOrDie((PlayerSAO *)pointed_object); + SendPlayerHPOrDie((PlayerSAO *)pointed_object, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, playersao)); } // If the puncher is a player and its HP changed if (dst_origin_hp != playersao->getHP()) - SendPlayerHPOrDie(playersao); + SendPlayerHPOrDie(playersao, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, pointed_object)); } } // action == 0 diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index f451156bd..571bac611 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -43,6 +43,7 @@ extern "C" { #include #include #include "script/common/c_content.h" +#include "content_sao.h" #include @@ -151,7 +152,7 @@ void ScriptApiBase::clientOpenLibs(lua_State *L) { LUA_JITLIBNAME, luaopen_jit }, #endif }; - + for (const std::pair &lib : m_libs) { lua_pushcfunction(L, lib.second); lua_pushstring(L, lib.first.c_str()); @@ -381,6 +382,26 @@ void ScriptApiBase::objectrefGetOrCreate(lua_State *L, } } +void ScriptApiBase::pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason &reason) +{ + if (reason.lua_reference >= 0) { + lua_rawgeti(L, LUA_REGISTRYINDEX, reason.lua_reference); + luaL_unref(L, LUA_REGISTRYINDEX, reason.lua_reference); + } else + lua_newtable(L); + + lua_pushstring(L, reason.getTypeAsString().c_str()); + lua_setfield(L, -2, "type"); + + lua_pushstring(L, reason.from_mod ? "mod" : "engine"); + lua_setfield(L, -2, "from"); + + if (reason.object) { + objectrefGetOrCreate(L, reason.object); + lua_setfield(L, -2, "object"); + } +} + Server* ScriptApiBase::getServer() { return dynamic_cast(m_gamedef); diff --git a/src/script/cpp_api/s_base.h b/src/script/cpp_api/s_base.h index 4837f4f99..dc7b581e3 100644 --- a/src/script/cpp_api/s_base.h +++ b/src/script/cpp_api/s_base.h @@ -72,6 +72,7 @@ class IGameDef; class Environment; class GUIEngine; class ServerActiveObject; +class PlayerHPChangeReason; class ScriptApiBase { public: @@ -139,6 +140,8 @@ protected: void objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj); + void pushPlayerHPChangeReason(lua_State *L, const PlayerHPChangeReason& reason); + std::recursive_mutex m_luastackmutex; std::string m_last_run_mod; bool m_secure = false; diff --git a/src/script/cpp_api/s_player.cpp b/src/script/cpp_api/s_player.cpp index 578298e24..cc2b96d5e 100644 --- a/src/script/cpp_api/s_player.cpp +++ b/src/script/cpp_api/s_player.cpp @@ -36,16 +36,20 @@ void ScriptApiPlayer::on_newplayer(ServerActiveObject *player) runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); } -void ScriptApiPlayer::on_dieplayer(ServerActiveObject *player) +void ScriptApiPlayer::on_dieplayer(ServerActiveObject *player, const PlayerHPChangeReason &reason) { SCRIPTAPI_PRECHECKHEADER - // Get core.registered_on_dieplayers + // Get callback table lua_getglobal(L, "core"); lua_getfield(L, -1, "registered_on_dieplayers"); - // Call callbacks + + // Push arguments objectrefGetOrCreate(L, player); - runCallbacks(1, RUN_CALLBACKS_MODE_FIRST); + pushPlayerHPChangeReason(L, reason); + + // Run callbacks + runCallbacks(2, RUN_CALLBACKS_MODE_FIRST); } bool ScriptApiPlayer::on_punchplayer(ServerActiveObject *player, @@ -71,7 +75,7 @@ bool ScriptApiPlayer::on_punchplayer(ServerActiveObject *player, } s16 ScriptApiPlayer::on_player_hpchange(ServerActiveObject *player, - s16 hp_change) + s16 hp_change, const PlayerHPChangeReason &reason) { SCRIPTAPI_PRECHECKHEADER @@ -82,9 +86,13 @@ s16 ScriptApiPlayer::on_player_hpchange(ServerActiveObject *player, lua_getfield(L, -1, "registered_on_player_hpchange"); lua_remove(L, -2); + // Push arguments objectrefGetOrCreate(L, player); lua_pushnumber(L, hp_change); - PCALL_RES(lua_pcall(L, 2, 1, error_handler)); + pushPlayerHPChangeReason(L, reason); + + // Call callbacks + PCALL_RES(lua_pcall(L, 3, 1, error_handler)); hp_change = lua_tointeger(L, -1); lua_pop(L, 2); // Pop result and error handler return hp_change; diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index 562805695..59e1505ae 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/string.h" struct ToolCapabilities; +struct PlayerHPChangeReason; class ScriptApiPlayer : virtual public ScriptApiBase { @@ -31,7 +32,7 @@ public: virtual ~ScriptApiPlayer() = default; void on_newplayer(ServerActiveObject *player); - void on_dieplayer(ServerActiveObject *player); + void on_dieplayer(ServerActiveObject *player, const PlayerHPChangeReason &reason); bool on_respawnplayer(ServerActiveObject *player); bool on_prejoinplayer(const std::string &name, const std::string &ip, std::string *reason); @@ -42,7 +43,8 @@ public: bool on_punchplayer(ServerActiveObject *player, ServerActiveObject *hitter, float time_from_last_punch, const ToolCapabilities *toolcap, v3f dir, s16 damage); - s16 on_player_hpchange(ServerActiveObject *player, s16 hp_change); + s16 on_player_hpchange(ServerActiveObject *player, s16 hp_change, + const PlayerHPChangeReason &reason); void on_playerReceiveFields(ServerActiveObject *player, const std::string &formname, const StringMap &fields); void on_auth_failure(const std::string &name, const std::string &ip); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 5fc6f9d3c..e4c478df7 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -194,13 +194,14 @@ int ObjectRef::l_punch(lua_State *L) // If the punched is a player, and its HP changed if (src_original_hp != co->getHP() && co->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co); + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co, PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); } // If the puncher is a player, and its HP changed if (dst_origin_hp != puncher->getHP() && puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher); + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)puncher, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, co)); } return 0; } @@ -226,17 +227,36 @@ int ObjectRef::l_right_click(lua_State *L) int ObjectRef::l_set_hp(lua_State *L) { NO_MAP_LOCK_REQUIRED; + + // Get Object ObjectRef *ref = checkobject(L, 1); luaL_checknumber(L, 2); ServerActiveObject *co = getobject(ref); - if (co == NULL) return 0; + if (co == NULL) + return 0; + + // Get HP int hp = lua_tonumber(L, 2); - /*infostream<<"ObjectRef::l_set_hp(): id="<getId() - <<" hp="<setHP(hp); + co->setHP(hp, reason); if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co); + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co, reason); // Return return 0; @@ -729,9 +749,10 @@ int ObjectRef::l_set_properties(lua_State *L) return 0; read_object_properties(L, 2, prop, getServer(L)->idef()); if (prop->hp_max < co->getHP()) { - co->setHP(prop->hp_max); + PlayerHPChangeReason reason(PlayerHPChangeReason::SET_HP); + co->setHP(prop->hp_max, reason); if (co->getType() == ACTIVEOBJECT_TYPE_PLAYER) - getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co); + getServer(L)->SendPlayerHPOrDie((PlayerSAO *)co, reason); } co->notifyObjectPropertiesModified(); return 0; diff --git a/src/server.cpp b/src/server.cpp index 8be223f74..ca2743ad2 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1029,7 +1029,8 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) if (playersao->isDead()) SendDeathscreen(peer_id, false, v3f(0,0,0)); else - SendPlayerHPOrDie(playersao); + SendPlayerHPOrDie(playersao, + PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); // Send Breath SendPlayerBreath(playersao); @@ -1392,7 +1393,7 @@ void Server::SendMovement(session_t peer_id) Send(&pkt); } -void Server::SendPlayerHPOrDie(PlayerSAO *playersao) +void Server::SendPlayerHPOrDie(PlayerSAO *playersao, const PlayerHPChangeReason &reason) { if (!g_settings->getBool("enable_damage")) return; @@ -1403,7 +1404,7 @@ void Server::SendPlayerHPOrDie(PlayerSAO *playersao) if (is_alive) SendPlayerHP(peer_id); else - DiePlayer(peer_id); + DiePlayer(peer_id, reason); } void Server::SendHP(session_t peer_id, u16 hp) @@ -2493,7 +2494,7 @@ void Server::sendDetachedInventories(session_t peer_id) Something random */ -void Server::DiePlayer(session_t peer_id) +void Server::DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason) { PlayerSAO *playersao = getPlayerSAO(peer_id); // In some rare cases this can be NULL -- if the player is disconnected @@ -2505,10 +2506,10 @@ void Server::DiePlayer(session_t peer_id) << playersao->getPlayer()->getName() << " dies" << std::endl; - playersao->setHP(0); + playersao->setHP(0, reason); // Trigger scripted stuff - m_script->on_dieplayer(playersao); + m_script->on_dieplayer(playersao, reason); SendPlayerHP(peer_id); SendDeathscreen(peer_id, false, v3f(0,0,0)); @@ -2523,7 +2524,8 @@ void Server::RespawnPlayer(session_t peer_id) << playersao->getPlayer()->getName() << " respawns" << std::endl; - playersao->setHP(playersao->accessObjectProperties()->hp_max); + playersao->setHP(playersao->accessObjectProperties()->hp_max, + PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN)); playersao->setBreath(playersao->accessObjectProperties()->breath_max); bool repositioned = m_script->on_respawnplayer(playersao); diff --git a/src/server.h b/src/server.h index 0820753e0..2d813630c 100644 --- a/src/server.h +++ b/src/server.h @@ -53,6 +53,7 @@ class Inventory; class ModChannelMgr; class RemotePlayer; class PlayerSAO; +struct PlayerHPChangeReason; class IRollbackManager; struct RollbackAction; class EmergeManager; @@ -328,7 +329,7 @@ public: void printToConsoleOnly(const std::string &text); - void SendPlayerHPOrDie(PlayerSAO *player); + void SendPlayerHPOrDie(PlayerSAO *player, const PlayerHPChangeReason &reason); void SendPlayerBreath(PlayerSAO *sao); void SendInventory(PlayerSAO* playerSAO); void SendMovePlayer(session_t peer_id); @@ -451,7 +452,7 @@ private: Something random */ - void DiePlayer(session_t peer_id); + void DiePlayer(session_t peer_id, const PlayerHPChangeReason &reason); void RespawnPlayer(session_t peer_id); void DeleteClient(session_t peer_id, ClientDeletionReason reason); void UpdateCrafting(RemotePlayer *player); diff --git a/src/serverobject.h b/src/serverobject.h index 304281093..77b701464 100644 --- a/src/serverobject.h +++ b/src/serverobject.h @@ -46,6 +46,7 @@ class ServerEnvironment; struct ItemStack; struct ToolCapabilities; struct ObjectProperties; +struct PlayerHPChangeReason; class ServerActiveObject : public ActiveObject { @@ -139,7 +140,7 @@ public: { return 0; } virtual void rightClick(ServerActiveObject *clicker) {} - virtual void setHP(s16 hp) + virtual void setHP(s16 hp, const PlayerHPChangeReason &reason) {} virtual s16 getHP() const { return 0; } -- 2.25.1