From 74febd5c31c20369a35a82e9a36e50f18562ce9f Mon Sep 17 00:00:00 2001 From: Perttu Ahola Date: Sat, 15 Oct 2011 14:46:59 +0300 Subject: [PATCH] Handle death and respawn better --- src/CMakeLists.txt | 1 + src/client.cpp | 32 +++++++- src/client.h | 10 ++- src/clientserver.h | 13 ++- src/game.cpp | 111 ++++++++++++++++++++----- src/guiDeathScreen.cpp | 179 +++++++++++++++++++++++++++++++++++++++++ src/guiDeathScreen.h | 60 ++++++++++++++ src/server.cpp | 154 ++++++++++++++++++++--------------- src/server.h | 5 ++ 9 files changed, 476 insertions(+), 89 deletions(-) create mode 100644 src/guiDeathScreen.cpp create mode 100644 src/guiDeathScreen.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ab50394f7..019f5ad7b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -152,6 +152,7 @@ set(minetest_SRCS guiInventoryMenu.cpp guiPauseMenu.cpp guiPasswordChange.cpp + guiDeathScreen.cpp client.cpp tile.cpp game.cpp diff --git a/src/client.cpp b/src/client.cpp index c54bf34ce..1af91703e 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -431,7 +431,7 @@ void Client::step(float dtime) snprintf((char*)&data[23], PASSWORD_SIZE, "%s", m_password.c_str()); // This should be incremented in each version - writeU16(&data[51], 2); + writeU16(&data[51], 3); // Send as unreliable Send(0, data, false); @@ -1477,6 +1477,22 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id) } } } + else if(command == TOCLIENT_DEATHSCREEN) + { + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + + bool set_camera_point_target = readU8(is); + v3f camera_point_target = readV3F1000(is); + + ClientEvent event; + 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; + m_client_event_queue.push_back(event); + } else { dout_client< data((u8*)s.c_str(), s.size()); + // Send as reliable + Send(0, data, true); +} + void Client::sendPlayerPos() { //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out diff --git a/src/client.h b/src/client.h index 85e85dcd6..07ac930c9 100644 --- a/src/client.h +++ b/src/client.h @@ -113,7 +113,8 @@ enum ClientEventType { CE_NONE, CE_PLAYER_DAMAGE, - CE_PLAYER_FORCE_MOVE + CE_PLAYER_FORCE_MOVE, + CE_DEATHSCREEN, }; struct ClientEvent @@ -129,6 +130,12 @@ struct ClientEvent f32 pitch; 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; }; }; @@ -191,6 +198,7 @@ public: void sendChangePassword(const std::wstring oldpassword, const std::wstring newpassword); void sendDamage(u8 damage); + void sendRespawn(); // locks envlock void removeNode(v3s16 p); diff --git a/src/clientserver.h b/src/clientserver.h index 9d3192754..b96cc61aa 100644 --- a/src/clientserver.h +++ b/src/clientserver.h @@ -172,6 +172,13 @@ enum ToClientCommand string serialized item } */ + + TOCLIENT_DEATHSCREEN = 0x37, + /* + u16 command + u8 bool set camera point target + v3f1000 camera point target (to point the death cause or whatever) + */ }; enum ToServerCommand @@ -320,7 +327,11 @@ enum ToServerCommand [0] u16 TOSERVER_PLAYERITEM [2] u16 item */ - + + TOSERVER_RESPAWN=0x38, + /* + u16 TOSERVER_RESPAWN + */ }; inline SharedBuffer makePacket_TOCLIENT_TIME_OF_DAY(u16 time) diff --git a/src/game.cpp b/src/game.cpp index 926a16977..16cbb4637 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "guiPasswordChange.h" #include "guiInventoryMenu.h" #include "guiTextInputMenu.h" +#include "guiDeathScreen.h" #include "materials.h" #include "config.h" #include "clouds.h" @@ -39,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "settings.h" #include "profiler.h" #include "mainmenumanager.h" +#include "gettext.h" /* TODO: Move content-aware stuff to separate file by adding properties @@ -129,6 +131,26 @@ struct TextDestSignNode : public TextDest Client *m_client; }; +/* Respawn menu callback */ + +class MainRespawnInitiator: public IRespawnInitiator +{ +public: + MainRespawnInitiator(bool *active, Client *client): + m_active(active), m_client(client) + { + *m_active = true; + } + void respawn() + { + *m_active = false; + m_client->sendRespawn(); + } +private: + bool *m_active; + Client *m_client; +}; + /* Hotbar draw routine */ @@ -916,6 +938,8 @@ void the_game( bool invert_mouse = g_settings->getBool("invert_mouse"); + bool respawn_menu_active = false; + /* Main loop */ @@ -1388,9 +1412,24 @@ void the_game( /* Player speed control - TODO: Cache the keycodes from getKeySetting */ + if(!noMenuActive() || !device->isWindowActive()) + { + PlayerControl control( + false, + false, + false, + false, + false, + false, + false, + camera_pitch, + camera_yaw + ); + client.setPlayerControl(control); + } + else { /*bool a_up, bool a_down, @@ -1435,24 +1474,56 @@ void the_game( //client.step(dtime_avg1); } - // Read client events - for(;;) { - ClientEvent event = client.getClientEvent(); - if(event.type == CE_NONE) - { - break; - } - else if(event.type == CE_PLAYER_DAMAGE) + // Read client events + for(;;) { - //u16 damage = event.player_damage.amount; - //dstream<<"Player damage: "<= 2){ + damage_flash_timer += 0.05 * event.player_damage.amount; + } + } + else if(event.type == CE_PLAYER_FORCE_MOVE) + { + camera_yaw = event.player_force_move.yaw; + camera_pitch = event.player_force_move.pitch; + } + else if(event.type == CE_DEATHSCREEN) + { + if(respawn_menu_active) + continue; + + /*bool set_camera_point_target = + event.deathscreen.set_camera_point_target; + v3f camera_point_target; + camera_point_target.X = event.deathscreen.camera_point_target_x; + camera_point_target.Y = event.deathscreen.camera_point_target_y; + camera_point_target.Z = event.deathscreen.camera_point_target_z;*/ + MainRespawnInitiator *respawner = + new MainRespawnInitiator( + &respawn_menu_active, &client); + GUIDeathScreen *menu = + new GUIDeathScreen(guienv, guiroot, -1, + &g_menumgr, respawner); + menu->drop(); + + /* Handle visualization */ + + damage_flash_timer = 0; + + /*LocalPlayer* player = client.getLocalPlayer(); + player->setPosition(player->getPosition() + v3f(0,-BS,0)); + camera.update(player, busytime, screensize);*/ + } } } @@ -1466,16 +1537,12 @@ void the_game( v3f camera_position = camera.getPosition(); v3f camera_direction = camera.getDirection(); f32 camera_fov = camera.getFovMax(); - + if(FIELD_OF_VIEW_TEST) - { client.updateCamera(v3f(0,0,0), v3f(0,0,1), camera_fov); - } else - { client.updateCamera(camera_position, camera_direction, camera_fov); - } //timer2.stop(); //TimeTaker //timer3("//timer3"); diff --git a/src/guiDeathScreen.cpp b/src/guiDeathScreen.cpp new file mode 100644 index 000000000..7a84126ea --- /dev/null +++ b/src/guiDeathScreen.cpp @@ -0,0 +1,179 @@ +/* +Minetest-c55 +Copyright (C) 2011 celeron55, Perttu Ahola + +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 "guiDeathScreen.h" +#include "debug.h" +#include "serialization.h" +#include +#include +#include +#include +#include +#include +#include "gettext.h" +#include "client.h" + +GUIDeathScreen::GUIDeathScreen(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, IRespawnInitiator *respawner +): + GUIModalMenu(env, parent, id, menumgr), + m_respawner(respawner), + m_screensize(1,1) +{ +} + +GUIDeathScreen::~GUIDeathScreen() +{ + removeChildren(); + delete m_respawner; +} + +void GUIDeathScreen::removeChildren() +{ + const core::list &children = getChildren(); + core::list children_copy; + for(core::list::ConstIterator + i = children.begin(); i != children.end(); i++) + { + children_copy.push_back(*i); + } + for(core::list::Iterator + i = children_copy.begin(); + i != children_copy.end(); i++) + { + (*i)->remove(); + } +} + +void GUIDeathScreen::regenerateGui(v2u32 screensize) +{ + m_screensize = screensize; + + /* + Remove stuff + */ + removeChildren(); + + /* + Calculate new sizes and positions + */ + core::rect rect( + screensize.X/2 - 500/2, + screensize.Y/2 - 200/2, + screensize.X/2 + 500/2, + screensize.Y/2 + 200/2 + ); + + DesiredRect = rect; + recalculateAbsolutePosition(false); + + v2s32 size = rect.getSize(); + + /* + Add stuff + */ + changeCtype(""); + { + core::rect rect(0, 0, 400, 50); + rect = rect + v2s32(size.X/2-400/2, size.Y/2-50/2-25); + Environment->addStaticText(wgettext("You died."), rect, false, + true, this, 256); + } + { + core::rect rect(0, 0, 140, 30); + rect = rect + v2s32(size.X/2-140/2, size.Y/2-30/2+25); + gui::IGUIElement *e = + Environment->addButton(rect, this, 257, + wgettext("Respawn")); + Environment->setFocus(e); + } + changeCtype("C"); +} + +void GUIDeathScreen::drawMenu() +{ + gui::IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + video::IVideoDriver* driver = Environment->getVideoDriver(); + + { + video::SColor color(180,50,0,0); + driver->draw2DRectangle(color, + core::rect(0,0,m_screensize.X,m_screensize.Y), NULL); + } + { + video::SColor bgcolor(50,0,0,0); + driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect); + } + + gui::IGUIElement::draw(); +} + +bool GUIDeathScreen::OnEvent(const SEvent& event) +{ + if(event.EventType==EET_KEY_INPUT_EVENT) + { + if(event.KeyInput.Key==KEY_ESCAPE && event.KeyInput.PressedDown) + { + respawn(); + quitMenu(); + return true; + } + if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown) + { + respawn(); + quitMenu(); + return true; + } + } + if(event.EventType==EET_GUI_EVENT) + { + if(event.GUIEvent.EventType==gui::EGET_ELEMENT_FOCUS_LOST + && isVisible()) + { + if(!canTakeFocus(event.GUIEvent.Element)) + { + dstream<<"GUIDeathScreen: Not allowing focus change." + <getID()) + { + case 257: + respawn(); + quitMenu(); + return true; + } + } + } + + return Parent ? Parent->OnEvent(event) : false; +} + +void GUIDeathScreen::respawn() +{ + m_respawner->respawn(); +} + diff --git a/src/guiDeathScreen.h b/src/guiDeathScreen.h new file mode 100644 index 000000000..10a97d7e8 --- /dev/null +++ b/src/guiDeathScreen.h @@ -0,0 +1,60 @@ +/* +Minetest-c55 +Copyright (C) 2011 celeron55, Perttu Ahola + +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. +*/ + +#ifndef GUIMESSAGEMENU_HEADER +#define GUIMESSAGEMENU_HEADER + +#include "common_irrlicht.h" +#include "modalMenu.h" +#include "utility.h" +#include + +class IRespawnInitiator +{ +public: + virtual void respawn() = 0; +}; + +class GUIDeathScreen : public GUIModalMenu +{ +public: + GUIDeathScreen(gui::IGUIEnvironment* env, + gui::IGUIElement* parent, s32 id, + IMenuManager *menumgr, IRespawnInitiator *respawner); + ~GUIDeathScreen(); + + void removeChildren(); + /* + Remove and re-add (or reposition) stuff + */ + void regenerateGui(v2u32 screensize); + + void drawMenu(); + + bool OnEvent(const SEvent& event); + + void respawn(); + +private: + IRespawnInitiator *m_respawner; + v2u32 m_screensize; +}; + +#endif + diff --git a/src/server.cpp b/src/server.cpp index ba4921dd0..59f7477a9 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -2026,16 +2026,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) // Get player Player *player = emergePlayer(playername, password, peer_id); - /*{ - // DEBUG: Test serialization - std::ostringstream test_os; - player->serialize(test_os); - dstream<<"Player serialization test: \""<deSerialize(test_is); - }*/ - // If failed, cancel if(player == NULL) { @@ -2044,32 +2034,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) return; } - /* - // If a client is already connected to the player, cancel - if(player->peer_id != 0) - { - derr_server<peer_id = peer_id; - */ - - // Check if player doesn't exist - if(player == NULL) - throw con::InvalidIncomingDataException - ("Server::ProcessData(): INIT: Player doesn't exist"); - - /*// update name if it was supplied - if(datasize >= 20+3) - { - data[20+3-1] = 0; - player->updateName((const char*)&data[3]); - }*/ - /* Answer with a TOCLIENT_INIT */ @@ -2146,10 +2110,18 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) } // Warnings about protocol version can be issued here - /*if(getClient(peer->id)->net_proto_version == 0) + if(getClient(peer->id)->net_proto_version < 3) { - SendChatMessage(peer_id, L"# Server: NOTE: YOUR CLIENT IS OLD AND DOES NOT WORK PROPERLY WITH THIS SERVER"); - }*/ + SendChatMessage(peer_id, L"# Server: WARNING: YOUR CLIENT IS OLD AND DOES NOT WORK PROPERLY WITH THIS SERVER"); + } + + /* + Check HP, respawn if necessary + */ + { + Player *player = m_env.getPlayer(peer_id); + HandlePlayerHP(player, 0); + } return; } @@ -3208,33 +3180,18 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) } else if(command == TOSERVER_DAMAGE) { + std::string datastring((char*)&data[2], datasize-2); + std::istringstream is(datastring, std::ios_base::binary); + u8 damage = readU8(is); + if(g_settings->getBool("enable_damage")) { - std::string datastring((char*)&data[2], datasize-2); - std::istringstream is(datastring, std::ios_base::binary); - u8 damage = readU8(is); - if(player->hp > damage) - { - player->hp -= damage; - } - else - { - player->hp = 0; - - dstream<<"TODO: Server: TOSERVER_HP_DECREMENT: Player dies" - <setPosition(pos); - player->hp = 20; - SendMovePlayer(player); - SendPlayerHP(player); - - //TODO: Throw items around - } + HandlePlayerHP(player, damage); + } + else + { + SendPlayerHP(player); } - - SendPlayerHP(player); } else if(command == TOSERVER_PASSWORD) { @@ -3296,7 +3253,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) <wieldItem(item); SendWieldedItem(player); } + else if(command == TOSERVER_RESPAWN) + { + if(player->hp != 0) + return; + + RespawnPlayer(player); + } else { derr_server<<"WARNING: Server::ProcessData(): Ignoring " @@ -3501,6 +3465,23 @@ void Server::SendAccessDenied(con::Connection &con, u16 peer_id, con.Send(peer_id, 0, data, true); } +void Server::SendDeathscreen(con::Connection &con, u16 peer_id, + bool set_camera_point_target, v3f camera_point_target) +{ + DSTACK(__FUNCTION_NAME); + std::ostringstream os(std::ios_base::binary); + + writeU16(os, TOCLIENT_DEATHSCREEN); + writeU8(os, set_camera_point_target); + writeV3F1000(os, camera_point_target); + + // Make data buffer + std::string s = os.str(); + SharedBuffer data((u8*)s.c_str(), s.size()); + // Send as reliable + con.Send(peer_id, 0, data, true); +} + /* Non-static send methods */ @@ -3955,6 +3936,51 @@ void Server::SendBlocks(float dtime) Something random */ +void Server::HandlePlayerHP(Player *player, s16 damage) +{ + if(player->hp > damage) + { + player->hp -= damage; + SendPlayerHP(player); + } + else + { + dstream<<"Server::HandlePlayerHP(): Player " + <getName()<<" dies"<hp = 0; + + //TODO: Throw items around + + // Handle players that are not connected + if(player->peer_id == PEER_ID_INEXISTENT){ + RespawnPlayer(player); + return; + } + + SendPlayerHP(player); + + RemoteClient *client = getClient(player->peer_id); + if(client->net_proto_version >= 3) + { + SendDeathscreen(m_con, player->peer_id, false, v3f(0,0,0)); + } + else + { + RespawnPlayer(player); + } + } +} + +void Server::RespawnPlayer(Player *player) +{ + v3f pos = findSpawnPos(m_env.getServerMap()); + player->setPosition(pos); + player->hp = 20; + SendMovePlayer(player); + SendPlayerHP(player); +} + void Server::UpdateCrafting(u16 peer_id) { DSTACK(__FUNCTION_NAME); diff --git a/src/server.h b/src/server.h index d51f91068..f65bd1957 100644 --- a/src/server.h +++ b/src/server.h @@ -488,6 +488,8 @@ private: static void SendHP(con::Connection &con, u16 peer_id, u8 hp); static void SendAccessDenied(con::Connection &con, u16 peer_id, const std::wstring &reason); + static void SendDeathscreen(con::Connection &con, u16 peer_id, + bool set_camera_point_target, v3f camera_point_target); /* Non-static send methods @@ -526,6 +528,9 @@ private: Something random */ + void HandlePlayerHP(Player *player, s16 damage); + void RespawnPlayer(Player *player); + void UpdateCrafting(u16 peer_id); // When called, connection mutex should be locked -- 2.25.1