mirror of
https://github.com/luanti-org/luanti.git
synced 2025-08-21 18:11:11 +00:00
Dehardcode the death formspec (#15155)
Co-authored-by: Paul Ouellette <oue.paul18@gmail.com>
This commit is contained in:
parent
3c48671076
commit
526a2f7b8c
79 changed files with 242 additions and 172 deletions
|
@ -1359,9 +1359,9 @@ void Client::sendDamage(u16 damage)
|
|||
Send(&pkt);
|
||||
}
|
||||
|
||||
void Client::sendRespawn()
|
||||
void Client::sendRespawnLegacy()
|
||||
{
|
||||
NetworkPacket pkt(TOSERVER_RESPAWN, 0);
|
||||
NetworkPacket pkt(TOSERVER_RESPAWN_LEGACY, 0);
|
||||
Send(&pkt);
|
||||
}
|
||||
|
||||
|
|
|
@ -194,7 +194,7 @@ public:
|
|||
void handleCommand_Breath(NetworkPacket* pkt);
|
||||
void handleCommand_MovePlayer(NetworkPacket* pkt);
|
||||
void handleCommand_MovePlayerRel(NetworkPacket* pkt);
|
||||
void handleCommand_DeathScreen(NetworkPacket* pkt);
|
||||
void handleCommand_DeathScreenLegacy(NetworkPacket* pkt);
|
||||
void handleCommand_AnnounceMedia(NetworkPacket* pkt);
|
||||
void handleCommand_Media(NetworkPacket* pkt);
|
||||
void handleCommand_NodeDef(NetworkPacket* pkt);
|
||||
|
@ -249,7 +249,7 @@ public:
|
|||
void sendChangePassword(const std::string &oldpassword,
|
||||
const std::string &newpassword);
|
||||
void sendDamage(u16 damage);
|
||||
void sendRespawn();
|
||||
void sendRespawnLegacy();
|
||||
void sendReady();
|
||||
void sendHaveMedia(const std::vector<u32> &tokens);
|
||||
void sendUpdateClientInfo(const ClientDynamicInfo &info);
|
||||
|
|
|
@ -35,7 +35,7 @@ enum ClientEventType : u8
|
|||
CE_NONE,
|
||||
CE_PLAYER_DAMAGE,
|
||||
CE_PLAYER_FORCE_MOVE,
|
||||
CE_DEATHSCREEN,
|
||||
CE_DEATHSCREEN_LEGACY,
|
||||
CE_SHOW_FORMSPEC,
|
||||
CE_SHOW_LOCAL_FORMSPEC,
|
||||
CE_SPAWN_PARTICLE,
|
||||
|
@ -96,13 +96,6 @@ struct ClientEvent
|
|||
f32 yaw;
|
||||
} player_force_move;
|
||||
struct
|
||||
{
|
||||
bool set_camera_point_target;
|
||||
f32 camera_point_target_x;
|
||||
f32 camera_point_target_y;
|
||||
f32 camera_point_target_z;
|
||||
} deathscreen;
|
||||
struct
|
||||
{
|
||||
std::string *formspec;
|
||||
std::string *formname;
|
||||
|
|
|
@ -186,7 +186,7 @@ struct LocalFormspecHandler : public TextDest
|
|||
assert(m_client != nullptr);
|
||||
|
||||
if (fields.find("quit") != fields.end())
|
||||
m_client->sendRespawn();
|
||||
m_client->sendRespawnLegacy();
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -837,7 +837,7 @@ private:
|
|||
bool disable_camera_update = false;
|
||||
};
|
||||
|
||||
void showDeathFormspec();
|
||||
void showDeathFormspecLegacy();
|
||||
void showPauseMenu();
|
||||
|
||||
void pauseAnimation();
|
||||
|
@ -847,7 +847,7 @@ private:
|
|||
void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
|
||||
void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
|
||||
void handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientation *cam);
|
||||
void handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam);
|
||||
void handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam);
|
||||
void handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam);
|
||||
void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam);
|
||||
void handleClientEvent_HandleParticleEvent(ClientEvent *event,
|
||||
|
@ -2854,7 +2854,7 @@ const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
|
|||
{&Game::handleClientEvent_None},
|
||||
{&Game::handleClientEvent_PlayerDamage},
|
||||
{&Game::handleClientEvent_PlayerForceMove},
|
||||
{&Game::handleClientEvent_Deathscreen},
|
||||
{&Game::handleClientEvent_DeathscreenLegacy},
|
||||
{&Game::handleClientEvent_ShowFormSpec},
|
||||
{&Game::handleClientEvent_ShowLocalFormSpec},
|
||||
{&Game::handleClientEvent_HandleParticleEvent},
|
||||
|
@ -2910,20 +2910,9 @@ void Game::handleClientEvent_PlayerForceMove(ClientEvent *event, CameraOrientati
|
|||
cam->camera_pitch = event->player_force_move.pitch;
|
||||
}
|
||||
|
||||
void Game::handleClientEvent_Deathscreen(ClientEvent *event, CameraOrientation *cam)
|
||||
void Game::handleClientEvent_DeathscreenLegacy(ClientEvent *event, CameraOrientation *cam)
|
||||
{
|
||||
// If client scripting is enabled, deathscreen is handled by CSM code in
|
||||
// builtin/client/init.lua
|
||||
if (client->modsLoaded())
|
||||
client->getScript()->on_death();
|
||||
else
|
||||
showDeathFormspec();
|
||||
|
||||
/* Handle visualization */
|
||||
LocalPlayer *player = client->getEnv().getLocalPlayer();
|
||||
runData.damage_flash = 0;
|
||||
player->hurt_tilt_timer = 0;
|
||||
player->hurt_tilt_strength = 0;
|
||||
showDeathFormspecLegacy();
|
||||
}
|
||||
|
||||
void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation *cam)
|
||||
|
@ -4468,7 +4457,7 @@ void Game::readSettings()
|
|||
****************************************************************************/
|
||||
/****************************************************************************/
|
||||
|
||||
void Game::showDeathFormspec()
|
||||
void Game::showDeathFormspecLegacy()
|
||||
{
|
||||
static std::string formspec_str =
|
||||
std::string("formspec_version[1]") +
|
||||
|
|
|
@ -81,7 +81,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
|
|||
{ "TOCLIENT_MOVE_PLAYER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MovePlayer }, // 0x34
|
||||
{ "TOCLIENT_ACCESS_DENIED_LEGACY", TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AccessDenied }, // 0x35
|
||||
{ "TOCLIENT_FOV", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Fov }, // 0x36
|
||||
{ "TOCLIENT_DEATHSCREEN", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreen }, // 0x37
|
||||
{ "TOCLIENT_DEATHSCREEN_LEGACY", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreenLegacy }, // 0x37
|
||||
{ "TOCLIENT_MEDIA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Media }, // 0x38
|
||||
null_command_handler,
|
||||
{ "TOCLIENT_NODEDEF", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_NodeDef }, // 0x3a
|
||||
|
@ -198,7 +198,7 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] =
|
|||
{ "TOSERVER_DAMAGE", 0, true }, // 0x35
|
||||
null_command_factory, // 0x36
|
||||
{ "TOSERVER_PLAYERITEM", 0, true }, // 0x37
|
||||
{ "TOSERVER_RESPAWN", 0, true }, // 0x38
|
||||
{ "TOSERVER_RESPAWN_LEGACY", 0, true }, // 0x38
|
||||
{ "TOSERVER_INTERACT", 0, true }, // 0x39
|
||||
{ "TOSERVER_REMOVED_SOUNDS", 2, true }, // 0x3a
|
||||
{ "TOSERVER_NODEMETA_FIELDS", 0, true }, // 0x3b
|
||||
|
|
|
@ -653,20 +653,10 @@ void Client::handleCommand_MovePlayerRel(NetworkPacket *pkt)
|
|||
player->addPosition(added_pos);
|
||||
}
|
||||
|
||||
void Client::handleCommand_DeathScreen(NetworkPacket* pkt)
|
||||
void Client::handleCommand_DeathScreenLegacy(NetworkPacket* pkt)
|
||||
{
|
||||
bool set_camera_point_target;
|
||||
v3f camera_point_target;
|
||||
|
||||
*pkt >> set_camera_point_target;
|
||||
*pkt >> camera_point_target;
|
||||
|
||||
ClientEvent *event = new ClientEvent();
|
||||
event->type = CE_DEATHSCREEN;
|
||||
event->deathscreen.set_camera_point_target = set_camera_point_target;
|
||||
event->deathscreen.camera_point_target_x = camera_point_target.X;
|
||||
event->deathscreen.camera_point_target_y = camera_point_target.Y;
|
||||
event->deathscreen.camera_point_target_z = camera_point_target.Z;
|
||||
event->type = CE_DEATHSCREEN_LEGACY;
|
||||
m_client_event_queue.push(event);
|
||||
}
|
||||
|
||||
|
|
|
@ -231,6 +231,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
Move default hotbar from client-side C++ to server-side builtin Lua
|
||||
Add shadow tint to Lighting packets
|
||||
Add shadow color to CloudParam packets
|
||||
Move death screen to server and make it a regular formspec
|
||||
The server no longer triggers the hardcoded client-side death
|
||||
formspec, but the client still supports it for compatibility with
|
||||
old servers.
|
||||
Rename TOCLIENT_DEATHSCREEN to TOCLIENT_DEATHSCREEN_LEGACY
|
||||
Rename TOSERVER_RESPAWN to TOSERVER_RESPAWN_LEGACY
|
||||
[scheduled bump for 5.10.0]
|
||||
*/
|
||||
|
||||
|
@ -391,10 +397,10 @@ enum ToClientCommand : u16
|
|||
f32 transition_time
|
||||
*/
|
||||
|
||||
TOCLIENT_DEATHSCREEN = 0x37,
|
||||
TOCLIENT_DEATHSCREEN_LEGACY = 0x37,
|
||||
/*
|
||||
u8 bool set camera point target
|
||||
v3f1000 camera point target (to point the death cause or whatever)
|
||||
u8 bool unused
|
||||
v3f1000 unused
|
||||
*/
|
||||
|
||||
TOCLIENT_MEDIA = 0x38,
|
||||
|
@ -1002,10 +1008,7 @@ enum ToServerCommand : u16
|
|||
[2] u16 item
|
||||
*/
|
||||
|
||||
TOSERVER_RESPAWN = 0x38,
|
||||
/*
|
||||
u16 TOSERVER_RESPAWN
|
||||
*/
|
||||
TOSERVER_RESPAWN_LEGACY = 0x38,
|
||||
|
||||
TOSERVER_INTERACT = 0x39,
|
||||
/*
|
||||
|
|
|
@ -82,7 +82,7 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] =
|
|||
{ "TOSERVER_DAMAGE", TOSERVER_STATE_INGAME, &Server::handleCommand_Damage }, // 0x35
|
||||
null_command_handler, // 0x36
|
||||
{ "TOSERVER_PLAYERITEM", TOSERVER_STATE_INGAME, &Server::handleCommand_PlayerItem }, // 0x37
|
||||
{ "TOSERVER_RESPAWN", TOSERVER_STATE_INGAME, &Server::handleCommand_Respawn }, // 0x38
|
||||
null_command_handler, // 0x38
|
||||
{ "TOSERVER_INTERACT", TOSERVER_STATE_INGAME, &Server::handleCommand_Interact }, // 0x39
|
||||
{ "TOSERVER_REMOVED_SOUNDS", TOSERVER_STATE_INGAME, &Server::handleCommand_RemovedSounds }, // 0x3a
|
||||
{ "TOSERVER_NODEMETA_FIELDS", TOSERVER_STATE_INGAME, &Server::handleCommand_NodeMetaFields }, // 0x3b
|
||||
|
@ -181,7 +181,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
|
|||
{ "TOCLIENT_MOVE_PLAYER", 0, true }, // 0x34
|
||||
null_command_factory, // 0x35
|
||||
{ "TOCLIENT_FOV", 0, true }, // 0x36
|
||||
{ "TOCLIENT_DEATHSCREEN", 0, true }, // 0x37
|
||||
null_command_factory, // 0x37
|
||||
{ "TOCLIENT_MEDIA", 2, true }, // 0x38
|
||||
null_command_factory, // 0x39
|
||||
{ "TOCLIENT_NODEDEF", 0, true }, // 0x3A
|
||||
|
|
|
@ -858,33 +858,6 @@ void Server::handleCommand_PlayerItem(NetworkPacket* pkt)
|
|||
playersao->getPlayer()->setWieldIndex(item);
|
||||
}
|
||||
|
||||
void Server::handleCommand_Respawn(NetworkPacket* pkt)
|
||||
{
|
||||
session_t peer_id = pkt->getPeerId();
|
||||
RemotePlayer *player = m_env->getPlayer(peer_id);
|
||||
if (player == NULL) {
|
||||
errorstream <<
|
||||
"Server::ProcessData(): Canceling: No player for peer_id=" <<
|
||||
peer_id << " disconnecting peer!" << std::endl;
|
||||
DisconnectPeer(peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerSAO *playersao = player->getPlayerSAO();
|
||||
assert(playersao);
|
||||
|
||||
if (!playersao->isDead())
|
||||
return;
|
||||
|
||||
RespawnPlayer(peer_id);
|
||||
|
||||
actionstream << player->getName() << " respawns at "
|
||||
<< (playersao->getBasePosition() / BS) << std::endl;
|
||||
|
||||
// ActiveObject is added to environment in AsyncRunStep after
|
||||
// the previous addition has been successfully removed
|
||||
}
|
||||
|
||||
bool Server::checkInteractDistance(RemotePlayer *player, const f32 d, const std::string &what)
|
||||
{
|
||||
ItemStack selected_item, hand_item;
|
||||
|
|
|
@ -125,21 +125,6 @@ void ScriptApiClient::on_hp_modification(int32_t newhp)
|
|||
}
|
||||
}
|
||||
|
||||
void ScriptApiClient::on_death()
|
||||
{
|
||||
SCRIPTAPI_PRECHECKHEADER
|
||||
|
||||
// Get registered shutdown hooks
|
||||
lua_getglobal(L, "core");
|
||||
lua_getfield(L, -1, "registered_on_death");
|
||||
// Call callbacks
|
||||
try {
|
||||
runCallbacks(0, RUN_CALLBACKS_MODE_FIRST);
|
||||
} catch (LuaError &e) {
|
||||
getClient()->setFatalError(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptApiClient::environment_step(float dtime)
|
||||
{
|
||||
SCRIPTAPI_PRECHECKHEADER
|
||||
|
|
|
@ -49,7 +49,6 @@ public:
|
|||
|
||||
void on_damage_taken(int32_t damage_amount);
|
||||
void on_hp_modification(int32_t newhp);
|
||||
void on_death();
|
||||
void environment_step(float dtime);
|
||||
void on_formspec_input(const std::string &formname, const StringMap &fields);
|
||||
|
||||
|
|
|
@ -157,13 +157,6 @@ int ModApiClient::l_show_formspec(lua_State *L)
|
|||
return 1;
|
||||
}
|
||||
|
||||
// send_respawn()
|
||||
int ModApiClient::l_send_respawn(lua_State *L)
|
||||
{
|
||||
getClient(L)->sendRespawn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// disconnect()
|
||||
int ModApiClient::l_disconnect(lua_State *L)
|
||||
{
|
||||
|
@ -348,7 +341,6 @@ void ModApiClient::Initialize(lua_State *L, int top)
|
|||
API_FCT(clear_out_chat_queue);
|
||||
API_FCT(get_player_names);
|
||||
API_FCT(show_formspec);
|
||||
API_FCT(send_respawn);
|
||||
API_FCT(gettext);
|
||||
API_FCT(get_node_or_nil);
|
||||
API_FCT(disconnect);
|
||||
|
|
|
@ -51,9 +51,6 @@ private:
|
|||
// show_formspec(name, formspec)
|
||||
static int l_show_formspec(lua_State *L);
|
||||
|
||||
// send_respawn()
|
||||
static int l_send_respawn(lua_State *L);
|
||||
|
||||
// disconnect()
|
||||
static int l_disconnect(lua_State *L);
|
||||
|
||||
|
|
|
@ -2693,11 +2693,11 @@ int ObjectRef::l_respawn(lua_State *L)
|
|||
{
|
||||
NO_MAP_LOCK_REQUIRED;
|
||||
ObjectRef *ref = checkObject<ObjectRef>(L, 1);
|
||||
RemotePlayer *player = getplayer(ref);
|
||||
if (player == nullptr)
|
||||
auto *psao = getplayersao(ref);
|
||||
if (psao == nullptr)
|
||||
return 0;
|
||||
|
||||
getServer(L)->RespawnPlayer(player->getPeerId());
|
||||
psao->respawn();
|
||||
lua_pushboolean(L, true);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -1159,10 +1159,6 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
|
|||
// Send HP
|
||||
SendPlayerHP(playersao, false);
|
||||
|
||||
// Send death screen
|
||||
if (playersao->isDead())
|
||||
SendDeathscreen(peer_id, false, v3f(0,0,0));
|
||||
|
||||
// Send Breath
|
||||
SendPlayerBreath(playersao);
|
||||
|
||||
|
@ -1405,14 +1401,6 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason,
|
|||
Send(&pkt);
|
||||
}
|
||||
|
||||
void Server::SendDeathscreen(session_t peer_id, bool set_camera_point_target,
|
||||
v3f camera_point_target)
|
||||
{
|
||||
NetworkPacket pkt(TOCLIENT_DEATHSCREEN, 1 + sizeof(v3f), peer_id);
|
||||
pkt << set_camera_point_target << camera_point_target;
|
||||
Send(&pkt);
|
||||
}
|
||||
|
||||
void Server::SendItemDef(session_t peer_id,
|
||||
IItemDefManager *itemdef, u16 protocol_version)
|
||||
{
|
||||
|
@ -2803,32 +2791,8 @@ void Server::HandlePlayerDeath(PlayerSAO *playersao, const PlayerHPChangeReason
|
|||
|
||||
// Trigger scripted stuff
|
||||
m_script->on_dieplayer(playersao, reason);
|
||||
|
||||
SendDeathscreen(playersao->getPeerID(), false, v3f(0,0,0));
|
||||
}
|
||||
|
||||
void Server::RespawnPlayer(session_t peer_id)
|
||||
{
|
||||
PlayerSAO *playersao = getPlayerSAO(peer_id);
|
||||
assert(playersao);
|
||||
|
||||
infostream << "Server::RespawnPlayer(): Player "
|
||||
<< playersao->getPlayer()->getName()
|
||||
<< " respawns" << std::endl;
|
||||
|
||||
const auto *prop = playersao->accessObjectProperties();
|
||||
playersao->setHP(prop->hp_max,
|
||||
PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN));
|
||||
playersao->setBreath(prop->breath_max);
|
||||
|
||||
bool repositioned = m_script->on_respawnplayer(playersao);
|
||||
if (!repositioned) {
|
||||
// setPos will send the new position to client
|
||||
playersao->setPos(findSpawnPos());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Server::DenySudoAccess(session_t peer_id)
|
||||
{
|
||||
NetworkPacket pkt(TOCLIENT_DENY_SUDO_MODE, 0, peer_id);
|
||||
|
|
|
@ -193,7 +193,6 @@ public:
|
|||
void handleCommand_ChatMessage(NetworkPacket* pkt);
|
||||
void handleCommand_Damage(NetworkPacket* pkt);
|
||||
void handleCommand_PlayerItem(NetworkPacket* pkt);
|
||||
void handleCommand_Respawn(NetworkPacket* pkt);
|
||||
void handleCommand_Interact(NetworkPacket* pkt);
|
||||
void handleCommand_RemovedSounds(NetworkPacket* pkt);
|
||||
void handleCommand_NodeMetaFields(NetworkPacket* pkt);
|
||||
|
@ -356,8 +355,6 @@ public:
|
|||
|
||||
void setLighting(RemotePlayer *player, const Lighting &lighting);
|
||||
|
||||
void RespawnPlayer(session_t peer_id);
|
||||
|
||||
/* con::PeerHandler implementation. */
|
||||
void peerAdded(con::IPeer *peer);
|
||||
void deletingPeer(con::IPeer *peer, bool timeout);
|
||||
|
@ -486,8 +483,6 @@ private:
|
|||
void SendBreath(session_t peer_id, u16 breath);
|
||||
void SendAccessDenied(session_t peer_id, AccessDeniedCode reason,
|
||||
std::string_view custom_reason, bool reconnect = false);
|
||||
void SendDeathscreen(session_t peer_id, bool set_camera_point_target,
|
||||
v3f camera_point_target);
|
||||
void SendItemDef(session_t peer_id, IItemDefManager *itemdef, u16 protocol_version);
|
||||
void SendNodeDef(session_t peer_id, const NodeDefManager *nodedef,
|
||||
u16 protocol_version);
|
||||
|
|
|
@ -126,7 +126,7 @@ class EmergeManager;
|
|||
| TOCLIENT_INVENTORY | | | |
|
||||
| TOCLIENT_HP (opt) | \-----------------/ |
|
||||
| TOCLIENT_BREATH | |
|
||||
| TOCLIENT_DEATHSCREEN | |
|
||||
| TOCLIENT_DEATHSCREEN_LEGACY | |
|
||||
+-----------------------------+ |
|
||||
| |
|
||||
v |
|
||||
|
|
|
@ -558,6 +558,21 @@ void PlayerSAO::setBreath(const u16 breath, bool send)
|
|||
m_env->getGameDef()->SendPlayerBreath(this);
|
||||
}
|
||||
|
||||
void PlayerSAO::respawn()
|
||||
{
|
||||
infostream << "PlayerSAO::respawn(): Player " << m_player->getName()
|
||||
<< " respawns" << std::endl;
|
||||
|
||||
setHP(m_prop.hp_max, PlayerHPChangeReason(PlayerHPChangeReason::RESPAWN));
|
||||
setBreath(m_prop.breath_max);
|
||||
|
||||
bool repositioned = m_env->getScriptIface()->on_respawnplayer(this);
|
||||
if (!repositioned) {
|
||||
// setPos will send the new position to client
|
||||
setPos(m_env->getGameDef()->findSpawnPos());
|
||||
}
|
||||
}
|
||||
|
||||
Inventory *PlayerSAO::getInventory() const
|
||||
{
|
||||
return m_player ? &m_player->inventory : nullptr;
|
||||
|
|
|
@ -124,6 +124,7 @@ public:
|
|||
void setHPRaw(u16 hp) { m_hp = hp; }
|
||||
u16 getBreath() const { return m_breath; }
|
||||
void setBreath(const u16 breath, bool send = true);
|
||||
void respawn();
|
||||
|
||||
/*
|
||||
Inventory interface
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue