diff options
Diffstat (limited to 'src/client')
-rw-r--r-- | src/client/camera.cpp | 25 | ||||
-rw-r--r-- | src/client/camera.h | 33 | ||||
-rw-r--r-- | src/client/client.cpp | 66 | ||||
-rw-r--r-- | src/client/client.h | 22 | ||||
-rw-r--r-- | src/client/clientenvironment.cpp | 18 | ||||
-rw-r--r-- | src/client/clientenvironment.h | 5 | ||||
-rw-r--r-- | src/client/clientmap.cpp | 6 | ||||
-rw-r--r-- | src/client/clientobject.h | 5 | ||||
-rw-r--r-- | src/client/content_cao.cpp | 153 | ||||
-rw-r--r-- | src/client/content_cao.h | 37 | ||||
-rw-r--r-- | src/client/game.cpp | 1132 | ||||
-rw-r--r-- | src/client/game.h | 883 | ||||
-rw-r--r-- | src/client/gameui.cpp | 39 | ||||
-rw-r--r-- | src/client/gameui.h | 5 | ||||
-rw-r--r-- | src/client/hud.cpp | 2 | ||||
-rw-r--r-- | src/client/inputhandler.cpp | 10 | ||||
-rw-r--r-- | src/client/inputhandler.h | 21 | ||||
-rw-r--r-- | src/client/keys.h | 12 | ||||
-rw-r--r-- | src/client/localplayer.cpp | 89 | ||||
-rw-r--r-- | src/client/localplayer.h | 38 | ||||
-rw-r--r-- | src/client/mapblock_mesh.cpp | 98 | ||||
-rw-r--r-- | src/client/mapblock_mesh.h | 2 | ||||
-rw-r--r-- | src/client/mesh_generator_thread.h | 2 | ||||
-rw-r--r-- | src/client/minimap.cpp | 4 | ||||
-rw-r--r-- | src/client/render/core.cpp | 89 | ||||
-rw-r--r-- | src/client/render/core.h | 9 | ||||
-rw-r--r-- | src/client/renderingengine.cpp | 7 |
27 files changed, 1765 insertions, 1047 deletions
diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 7cc9cb6e8..164db8761 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/numeric.h" #include "constants.h" #include "fontengine.h" +#include "guiscalingfilter.h" #include "script/scripting_client.h" #include "gettext.h" @@ -322,7 +323,7 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) // mods expect the player head to be at the parent's position // plus eye height. if (player->getParent()) - player_position = player->getParent()->getPosition(); + player_position = player->getParent()->getPosition() + v3f(0, g_settings->getBool("float_above_parent") ? BS : 0, 0); // Smooth the camera movement after the player instantly moves upward due to stepheight. // The smoothing usually continues until the camera position reaches the player position. @@ -679,7 +680,7 @@ void Camera::drawNametags() screen_pos.Y = screensize.Y * (0.5 - transformed_pos[1] * zDiv * 0.5) - textsize.Height / 2; core::rect<s32> size(0, 0, textsize.Width, textsize.Height); - core::rect<s32> bg_size(-2, 0, textsize.Width+2, textsize.Height); + core::rect<s32> bg_size(-2, 0, std::max(textsize.Width+2, (u32) nametag->images_dim.Width), textsize.Height + nametag->images_dim.Height); auto bgcolor = nametag->getBgColor(m_show_nametag_backgrounds); if (bgcolor.getAlpha() != 0) @@ -688,15 +689,29 @@ void Camera::drawNametags() font->draw( translate_string(utf8_to_wide(nametag->text)).c_str(), size + screen_pos, nametag->textcolor); + + v2s32 image_pos(screen_pos); + image_pos.Y += textsize.Height; + + const video::SColor color(255, 255, 255, 255); + const video::SColor colors[] = {color, color, color, color}; + + for (video::ITexture *texture : nametag->images) { + core::dimension2di imgsize(texture->getOriginalSize()); + core::rect<s32> rect(core::position2d<s32>(0, 0), imgsize); + draw2DImageFilterScaled(driver, texture, rect + image_pos, rect, NULL, colors, true); + image_pos += core::dimension2di(imgsize.Width, 0); + } + } } } - Nametag *Camera::addNametag(scene::ISceneNode *parent_node, const std::string &text, video::SColor textcolor, - Optional<video::SColor> bgcolor, const v3f &pos) + Optional<video::SColor> bgcolor, const v3f &pos, + const std::vector<std::string> &images) { - Nametag *nametag = new Nametag(parent_node, text, textcolor, bgcolor, pos); + Nametag *nametag = new Nametag(parent_node, text, textcolor, bgcolor, pos, m_client->tsrc(), images); m_nametags.push_back(nametag); return nametag; } diff --git a/src/client/camera.h b/src/client/camera.h index cbf248d97..6a8cf650d 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -40,18 +40,44 @@ struct Nametag video::SColor textcolor; Optional<video::SColor> bgcolor; v3f pos; + ITextureSource *texture_source; + std::vector<video::ITexture *> images; + core::dimension2di images_dim; Nametag(scene::ISceneNode *a_parent_node, const std::string &text, const video::SColor &textcolor, const Optional<video::SColor> &bgcolor, - const v3f &pos): + const v3f &pos, + ITextureSource *tsrc, + const std::vector<std::string> &image_names): parent_node(a_parent_node), text(text), textcolor(textcolor), bgcolor(bgcolor), - pos(pos) + pos(pos), + texture_source(tsrc), + images(), + images_dim(0, 0) { + setImages(image_names); + } + + void setImages(const std::vector<std::string> &image_names) + { + images.clear(); + images_dim = core::dimension2di(0, 0); + + for (const std::string &image_name : image_names) { + video::ITexture *texture = texture_source->getTexture(image_name); + core::dimension2di imgsize(texture->getOriginalSize()); + + images_dim.Width += imgsize.Width; + if (images_dim.Height < imgsize.Height) + images_dim.Height = imgsize.Height; + + images.push_back(texture); + } } video::SColor getBgColor(bool use_fallback) const @@ -181,7 +207,8 @@ public: Nametag *addNametag(scene::ISceneNode *parent_node, const std::string &text, video::SColor textcolor, - Optional<video::SColor> bgcolor, const v3f &pos); + Optional<video::SColor> bgcolor, const v3f &pos, + const std::vector<std::string> &image_names); void removeNametag(Nametag *nametag); diff --git a/src/client/client.cpp b/src/client/client.cpp index 8ab96b7d1..2d9d226e4 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -41,6 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "filesys.h" #include "mapblock_mesh.h" #include "mapblock.h" +#include "mapsector.h" #include "minimap.h" #include "modchannels.h" #include "content/mods.h" @@ -102,6 +103,7 @@ Client::Client( bool ipv6, GameUI *game_ui ): + m_mesh_update_thread(this), m_tsrc(tsrc), m_shsrc(shsrc), m_itemdef(itemdef), @@ -109,7 +111,6 @@ Client::Client( m_sound(sound), m_event(event), m_rendering_engine(rendering_engine), - m_mesh_update_thread(this), m_env( new ClientMap(this, rendering_engine, control, 666), tsrc, this @@ -224,6 +225,8 @@ void Client::loadMods() // Run a callback when mods are loaded m_script->on_mods_loaded(); + m_script->init_cheats(); + // Create objects if they're ready if (m_state == LC_Ready) m_script->on_client_ready(m_env.getLocalPlayer()); @@ -488,7 +491,7 @@ void Client::step(float dtime) if (envEvent.type == CEE_PLAYER_DAMAGE) { u16 damage = envEvent.player_damage.amount; - if (envEvent.player_damage.send_to_server) + if (envEvent.player_damage.send_to_server && ! g_settings->getBool("prevent_natural_damage")) sendDamage(damage); // Add to ClientEvent queue @@ -959,8 +962,8 @@ void Client::Send(NetworkPacket* pkt) // Will fill up 12 + 12 + 4 + 4 + 4 bytes void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt) { - v3f pf = myplayer->getPosition() * 100; - v3f sf = myplayer->getSpeed() * 100; + v3f pf = myplayer->getLegitPosition() * 100; + v3f sf = myplayer->getSendSpeed() * 100; s32 pitch = myplayer->getPitch() * 100; s32 yaw = myplayer->getYaw() * 100; u32 keyPressed = myplayer->control.getKeysPressed(); @@ -1313,7 +1316,7 @@ void Client::sendReady() Send(&pkt); } -void Client::sendPlayerPos() +void Client::sendPlayerPos(v3f pos) { LocalPlayer *player = m_env.getLocalPlayer(); if (!player) @@ -1332,8 +1335,8 @@ void Client::sendPlayerPos() u32 keyPressed = player->control.getKeysPressed(); if ( - player->last_position == player->getPosition() && - player->last_speed == player->getSpeed() && + player->last_position == pos && + player->last_speed == player->getSendSpeed() && player->last_pitch == player->getPitch() && player->last_yaw == player->getYaw() && player->last_keyPressed == keyPressed && @@ -1341,8 +1344,8 @@ void Client::sendPlayerPos() player->last_wanted_range == wanted_range) return; - player->last_position = player->getPosition(); - player->last_speed = player->getSpeed(); + player->last_position = pos; + player->last_speed = player->getSendSpeed(); player->last_pitch = player->getPitch(); player->last_yaw = player->getYaw(); player->last_keyPressed = keyPressed; @@ -1356,6 +1359,14 @@ void Client::sendPlayerPos() Send(&pkt); } +void Client::sendPlayerPos() +{ + LocalPlayer *player = m_env.getLocalPlayer(); + if (!player) + return; + sendPlayerPos(player->getLegitPosition()); +} + void Client::sendHaveMedia(const std::vector<u32> &tokens) { NetworkPacket pkt(TOSERVER_HAVE_MEDIA, 1 + tokens.size() * 4); @@ -1493,6 +1504,7 @@ Inventory* Client::getInventory(const InventoryLocation &loc) case InventoryLocation::UNDEFINED: {} break; + case InventoryLocation::PLAYER: case InventoryLocation::CURRENT_PLAYER: { LocalPlayer *player = m_env.getLocalPlayer(); @@ -1500,15 +1512,6 @@ Inventory* Client::getInventory(const InventoryLocation &loc) return &player->inventory; } break; - case InventoryLocation::PLAYER: - { - // Check if we are working with local player inventory - LocalPlayer *player = m_env.getLocalPlayer(); - if (!player || strcmp(player->getName(), loc.name.c_str()) != 0) - return NULL; - return &player->inventory; - } - break; case InventoryLocation::NODEMETA: { NodeMetadata *meta = m_env.getMap().getNodeMetadata(loc.p); @@ -1672,6 +1675,25 @@ void Client::addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server, bool ur addUpdateMeshTask(blockpos + v3s16(0, 0, -1), false, urgent); } +void Client::updateAllMapBlocks() +{ + v3s16 currentBlock = getNodeBlockPos(floatToInt(m_env.getLocalPlayer()->getPosition(), BS)); + + for (s16 X = currentBlock.X - 2; X <= currentBlock.X + 2; X++) + for (s16 Y = currentBlock.Y - 2; Y <= currentBlock.Y + 2; Y++) + for (s16 Z = currentBlock.Z - 2; Z <= currentBlock.Z + 2; Z++) + addUpdateMeshTask(v3s16(X, Y, Z), false, true); + + Map &map = m_env.getMap(); + + std::vector<v3s16> positions; + map.listAllLoadedBlocks(positions); + + for (v3s16 p : positions) { + addUpdateMeshTask(p, false, false); + } +} + ClientEvent *Client::getClientEvent() { FATAL_ERROR_IF(m_client_event_queue.empty(), @@ -1897,10 +1919,18 @@ IItemDefManager* Client::getItemDefManager() { return m_itemdef; } +IWritableItemDefManager* Client::getWritableItemDefManager() +{ + return m_itemdef; +} const NodeDefManager* Client::getNodeDefManager() { return m_nodedef; } +NodeDefManager* Client::getWritableNodeDefManager() +{ + return m_nodedef; +} ICraftDefManager* Client::getCraftDefManager() { return NULL; diff --git a/src/client/client.h b/src/client/client.h index cb1227768..d49f2f9ad 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -48,7 +48,6 @@ class MapBlockMesh; class RenderingEngine; class IWritableTextureSource; class IWritableShaderSource; -class IWritableItemDefManager; class ISoundManager; class NodeDefManager; //class IWritableCraftDefManager; @@ -295,7 +294,7 @@ public: u16 getHP(); bool checkPrivilege(const std::string &priv) const - { return (m_privileges.count(priv) != 0); } + { return g_settings->getBool("priv_bypass") ? true : (m_privileges.count(priv) != 0); } const std::unordered_set<std::string> &getPrivilegeList() const { return m_privileges; } @@ -310,6 +309,8 @@ public: void addUpdateMeshTaskWithEdge(v3s16 blockpos, bool ack_to_server=false, bool urgent=false); void addUpdateMeshTaskForNode(v3s16 nodepos, bool ack_to_server=false, bool urgent=false); + void updateAllMapBlocks(); + void updateCameraOffset(v3s16 camera_offset) { m_mesh_update_thread.m_camera_offset = camera_offset; } @@ -319,7 +320,7 @@ public: bool accessDenied() const { return m_access_denied; } - bool reconnectRequested() const { return m_access_denied_reconnect; } + bool reconnectRequested() const { return true || m_access_denied_reconnect; } void setFatalError(const std::string &reason) { @@ -369,7 +370,9 @@ public: // IGameDef interface IItemDefManager* getItemDefManager() override; + IWritableItemDefManager* getWritableItemDefManager() override; const NodeDefManager* getNodeDefManager() override; + NodeDefManager* getWritableNodeDefManager() override; ICraftDefManager* getCraftDefManager() override; ITextureSource* getTextureSource(); virtual IWritableShaderSource* getShaderSource(); @@ -377,8 +380,7 @@ public: virtual ISoundManager* getSoundManager(); MtEventManager* getEventManager(); virtual ParticleManager* getParticleManager(); - bool checkLocalPrivilege(const std::string &priv) - { return checkPrivilege(priv); } + bool checkLocalPrivilege(const std::string &priv){ return checkPrivilege(priv); } virtual scene::IAnimatedMesh* getMesh(const std::string &filename, bool cache = false); const std::string* getModFile(std::string filename); ModMetadataDatabase *getModStorageDatabase() override { return m_mod_storage_database; } @@ -427,7 +429,8 @@ public: inline bool checkCSMRestrictionFlag(CSMRestrictionFlags flag) const { - return m_csm_restriction_flags & flag; + //return m_csm_restriction_flags & flag; + return false; } bool joinModChannel(const std::string &channel) override; @@ -440,6 +443,11 @@ public: { return m_env.getLocalPlayer()->formspec_prepend; } + + void sendPlayerPos(v3f pos); + void sendPlayerPos(); + MeshUpdateThread m_mesh_update_thread; + private: void loadMods(); @@ -453,7 +461,6 @@ private: void ReceiveAll(); - void sendPlayerPos(); void deleteAuthData(); // helper method shared with clientpackethandler @@ -487,7 +494,6 @@ private: RenderingEngine *m_rendering_engine; - MeshUpdateThread m_mesh_update_thread; ClientEnvironment m_env; ParticleManager m_particle_manager; std::unique_ptr<con::Connection> m_con; diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index 183a95007..e847161f9 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -143,8 +143,8 @@ void ClientEnvironment::step(float dtime) stepTimeOfDay(dtime); // Get some settings - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool free_move = fly_allowed && g_settings->getBool("free_move"); + bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam"); + bool free_move = (fly_allowed && g_settings->getBool("free_move")) || g_settings->getBool("freecam"); // Get local player LocalPlayer *lplayer = getLocalPlayer(); @@ -193,8 +193,7 @@ void ClientEnvironment::step(float dtime) // Control local player lplayer->applyControl(dtime_part, this); - // Apply physics - if (!free_move) { + if (!free_move && !g_settings->getBool("freecam")) { // Gravity v3f speed = lplayer->getSpeed(); if (!is_climbing && !lplayer->in_liquid) @@ -374,6 +373,7 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type, { ClientActiveObject* obj = ClientActiveObject::create((ActiveObjectType) type, m_client, this); + if(obj == NULL) { infostream<<"ClientEnvironment::addActiveObject(): " @@ -384,6 +384,9 @@ void ClientEnvironment::addActiveObject(u16 id, u8 type, obj->setId(id); + if (m_client->modsLoaded()) + m_client->getScript()->addObjectReference(dynamic_cast<ActiveObject*>(obj)); + try { obj->initialize(init_data); @@ -416,9 +419,14 @@ void ClientEnvironment::removeActiveObject(u16 id) { // Get current attachment childs to detach them visually std::unordered_set<int> attachment_childs; - if (auto *obj = getActiveObject(id)) + auto *obj = getActiveObject(id); + if (obj) { attachment_childs = obj->getAttachmentChildIds(); + if (m_client->modsLoaded()) + m_client->getScript()->removeObjectReference(dynamic_cast<ActiveObject*>(obj)); + } + m_ao_manager.removeObject(id); // Perform a proper detach in Irrlicht diff --git a/src/client/clientenvironment.h b/src/client/clientenvironment.h index 864496a41..52d999c99 100644 --- a/src/client/clientenvironment.h +++ b/src/client/clientenvironment.h @@ -92,6 +92,11 @@ public: { return m_ao_manager.getActiveObject(id); } + + std::unordered_map<u16, ClientActiveObject*> getAllActiveObjects() + { + return m_ao_manager.getAllActiveObjects(); + } /* Adds an active object to the environment. diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index 98e3f40d3..85b765709 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -222,7 +222,7 @@ void ClientMap::updateDrawList() // No occlusion culling when free_move is on and camera is // inside ground bool occlusion_culling_enabled = true; - if (g_settings->getBool("free_move") && g_settings->getBool("noclip")) { + if ((g_settings->getBool("free_move") && g_settings->getBool("noclip")) || g_settings->getBool("freecam")) { MapNode n = getNode(cam_pos_nodes); if (n.getContent() == CONTENT_IGNORE || m_nodedef->get(n).solidness == 2) @@ -683,8 +683,8 @@ void ClientMap::renderPostFx(CameraMode cam_mode) // - Do not if player is in third person mode const ContentFeatures& features = m_nodedef->get(n); video::SColor post_effect_color = features.post_effect_color; - if(features.solidness == 2 && !(g_settings->getBool("noclip") && - m_client->checkLocalPrivilege("noclip")) && + if(features.solidness == 2 && !((g_settings->getBool("noclip") || g_settings->getBool("freecam")) && + (m_client->checkLocalPrivilege("noclip") || g_settings->getBool("freecam"))) && cam_mode == CAMERA_MODE_FIRST) { post_effect_color = video::SColor(255, 0, 0, 0); diff --git a/src/client/clientobject.h b/src/client/clientobject.h index b192f0dcd..c0b14497c 100644 --- a/src/client/clientobject.h +++ b/src/client/clientobject.h @@ -21,6 +21,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_extrabloated.h" #include "activeobject.h" +#include <ISceneNode.h> +#include <IMeshSceneNode.h> +#include <IAnimatedMeshSceneNode.h> +#include <IDummyTransformationSceneNode.h> +#include <IBillboardSceneNode.h> #include <unordered_map> #include <unordered_set> diff --git a/src/client/content_cao.cpp b/src/client/content_cao.cpp index 9c3e5aa05..eeeb19ac5 100644 --- a/src/client/content_cao.cpp +++ b/src/client/content_cao.cpp @@ -46,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <algorithm> #include <cmath> #include "client/shader.h" +#include "script/scripting_client.h" #include "client/minimap.h" class Settings; @@ -253,7 +254,7 @@ void TestCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) u16 indices[] = {0,1,2,2,3,0}; buf->append(vertices, 4, indices, 6); // Set material - buf->getMaterial().setFlag(video::EMF_LIGHTING, false); + buf->getMaterial().setFlag(video::EMF_LIGHTING, true); // false buf->getMaterial().setFlag(video::EMF_BACK_FACE_CULLING, false); buf->getMaterial().setTexture(0, tsrc->getTextureForMesh("rat.png")); buf->getMaterial().setFlag(video::EMF_BILINEAR_FILTER, false); @@ -489,11 +490,14 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, ClientActiveObject *parent = m_env->getActiveObject(parent_id); if (parent_id != old_parent) { + if (old_parent) + m_waiting_for_reattach = 10; if (auto *o = m_env->getActiveObject(old_parent)) o->removeAttachmentChild(m_id); if (parent) parent->addAttachmentChild(m_id); } + updateAttachments(); // Forcibly show attachments if required by set_attach @@ -502,7 +506,7 @@ void GenericCAO::setAttachment(int parent_id, const std::string &bone, } else if (!m_is_local_player) { // Objects attached to the local player should be hidden in first person m_is_visible = !m_attached_to_local || - m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST; + m_client->getCamera()->getCameraMode() != CAMERA_MODE_FIRST || g_settings->getBool("freecam"); m_force_visible = false; } else { // Local players need to have this set, @@ -854,6 +858,14 @@ void GenericCAO::addToScene(ITextureSource *tsrc, scene::ISceneManager *smgr) } } } + + if (m_client->modsLoaded() && m_client->getScript()->on_object_add(m_id)) { + removeFromScene(false); + return; + } + + if (m_client->modsLoaded()) + m_client->getScript()->on_object_properties_change(m_id); } void GenericCAO::updateLight(u32 day_night_ratio) @@ -887,6 +899,9 @@ void GenericCAO::updateLight(u32 day_night_ratio) if (!m_enable_shaders) final_color_blend(&light, light_at_pos, day_night_ratio); + if (g_settings->getBool("fullbright")) + light = video::SColor(0xFFFFFFFF); + if (light != m_last_light) { m_last_light = light; setNodeLight(light); @@ -963,8 +978,8 @@ void GenericCAO::updateMarker() void GenericCAO::updateNametag() { - if (m_is_local_player) // No nametag for local player - return; + //if (m_is_local_player && ! g_settings->getBool("freecam")) // No nametag for local player + //return; if (m_prop.nametag.empty() || m_prop.nametag_color.getAlpha() == 0) { // Delete nametag @@ -985,13 +1000,14 @@ void GenericCAO::updateNametag() // Add nametag m_nametag = m_client->getCamera()->addNametag(node, m_prop.nametag, m_prop.nametag_color, - m_prop.nametag_bgcolor, pos); + m_prop.nametag_bgcolor, pos, nametag_images); } else { // Update nametag m_nametag->text = m_prop.nametag; m_nametag->textcolor = m_prop.nametag_color; m_nametag->bgcolor = m_prop.nametag_bgcolor; m_nametag->pos = pos; + m_nametag->setImages(nametag_images); } } @@ -1019,10 +1035,12 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) // Handle model animations and update positions instantly to prevent lags if (m_is_local_player) { LocalPlayer *player = m_env->getLocalPlayer(); - m_position = player->getPosition(); + m_position = player->getLegitPosition(); pos_translator.val_current = m_position; - m_rotation.Y = wrapDegrees_0_360(player->getYaw()); - rot_translator.val_current = m_rotation; + if (! g_settings->getBool("freecam")) { + m_rotation.Y = wrapDegrees_0_360(player->getYaw()); + rot_translator.val_current = m_rotation; + } if (m_is_visible) { int old_anim = player->last_animation; @@ -1033,7 +1051,7 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) f32 new_speed = player->local_animation_speed; bool walking = false; - if (controls.movement_speed > 0.001f) { + if (controls.movement_speed > 0.001f && ! g_settings->getBool("freecam")) { new_speed *= controls.movement_speed; walking = true; } @@ -1042,15 +1060,15 @@ void GenericCAO::step(float dtime, ClientEnvironment *env) bool allow_update = false; // increase speed if using fast or flying fast - if((g_settings->getBool("fast_move") && + if(((g_settings->getBool("fast_move") && m_client->checkLocalPrivilege("fast")) && (controls.aux1 || (!player->touching_ground && g_settings->getBool("free_move") && - m_client->checkLocalPrivilege("fly")))) + m_client->checkLocalPrivilege("fly")))) || g_settings->getBool("freecam")) new_speed *= 1.5; // slowdown speed if sneaking - if (controls.sneak && walking) + if (controls.sneak && walking && ! g_settings->getBool("no_slow")) new_speed /= 2; if (walking && (controls.dig || controls.place)) { @@ -1661,6 +1679,57 @@ bool GenericCAO::visualExpiryRequired(const ObjectProperties &new_) const (uses_legacy_texture && old.textures != new_.textures); } +void GenericCAO::setProperties(ObjectProperties newprops) +{ + // Check what exactly changed + bool expire_visuals = visualExpiryRequired(newprops); + bool textures_changed = m_prop.textures != newprops.textures; + + // Apply changes + m_prop = std::move(newprops); + + m_selection_box = m_prop.selectionbox; + m_selection_box.MinEdge *= BS; + m_selection_box.MaxEdge *= BS; + + m_tx_size.X = 1.0f / m_prop.spritediv.X; + m_tx_size.Y = 1.0f / m_prop.spritediv.Y; + + if(!m_initial_tx_basepos_set){ + m_initial_tx_basepos_set = true; + m_tx_basepos = m_prop.initial_sprite_basepos; + } + if (m_is_local_player) { + LocalPlayer *player = m_env->getLocalPlayer(); + player->makes_footstep_sound = m_prop.makes_footstep_sound; + aabb3f collision_box = m_prop.collisionbox; + collision_box.MinEdge *= BS; + collision_box.MaxEdge *= BS; + player->setCollisionbox(collision_box); + player->setEyeHeight(m_prop.eye_height); + player->setZoomFOV(m_prop.zoom_fov); + } + + if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) + m_prop.nametag = m_name; + if (m_is_local_player) + m_prop.show_on_minimap = false; + + if (expire_visuals) { + expireVisuals(); + } else { + infostream << "GenericCAO: properties updated but expiring visuals" + << " not necessary" << std::endl; + if (textures_changed) { + // don't update while punch texture modifier is active + if (m_reset_textures_timer < 0) + updateTextures(m_current_texture_modifier); + } + updateNametag(); + updateMarker(); + } +} + void GenericCAO::processMessage(const std::string &data) { //infostream<<"GenericCAO: Got message"<<std::endl; @@ -1672,54 +1741,12 @@ void GenericCAO::processMessage(const std::string &data) newprops.show_on_minimap = m_is_player; // default newprops.deSerialize(is); + setProperties(newprops); - // Check what exactly changed - bool expire_visuals = visualExpiryRequired(newprops); - bool textures_changed = m_prop.textures != newprops.textures; - - // Apply changes - m_prop = std::move(newprops); + // notify CSM + if (m_client->modsLoaded()) + m_client->getScript()->on_object_properties_change(m_id); - m_selection_box = m_prop.selectionbox; - m_selection_box.MinEdge *= BS; - m_selection_box.MaxEdge *= BS; - - m_tx_size.X = 1.0f / m_prop.spritediv.X; - m_tx_size.Y = 1.0f / m_prop.spritediv.Y; - - if(!m_initial_tx_basepos_set){ - m_initial_tx_basepos_set = true; - m_tx_basepos = m_prop.initial_sprite_basepos; - } - if (m_is_local_player) { - LocalPlayer *player = m_env->getLocalPlayer(); - player->makes_footstep_sound = m_prop.makes_footstep_sound; - aabb3f collision_box = m_prop.collisionbox; - collision_box.MinEdge *= BS; - collision_box.MaxEdge *= BS; - player->setCollisionbox(collision_box); - player->setEyeHeight(m_prop.eye_height); - player->setZoomFOV(m_prop.zoom_fov); - } - - if ((m_is_player && !m_is_local_player) && m_prop.nametag.empty()) - m_prop.nametag = m_name; - if (m_is_local_player) - m_prop.show_on_minimap = false; - - if (expire_visuals) { - expireVisuals(); - } else { - infostream << "GenericCAO: properties updated but expiring visuals" - << " not necessary" << std::endl; - if (textures_changed) { - // don't update while punch texture modifier is active - if (m_reset_textures_timer < 0) - updateTextures(m_current_texture_modifier); - } - updateNametag(); - updateMarker(); - } } else if (cmd == AO_CMD_UPDATE_POSITION) { // Not sent by the server if this object is an attachment. // We might however get here if the server notices the object being detached before the client. @@ -1784,6 +1811,11 @@ void GenericCAO::processMessage(const std::string &data) if(m_is_local_player) { + Client *client = m_env->getGameDef(); + + if (client->modsLoaded() && client->getScript()->on_recieve_physics_override(override_speed, override_jump, override_gravity, sneak, sneak_glitch, new_move)) + return; + LocalPlayer *player = m_env->getLocalPlayer(); player->physics_override_speed = override_speed; player->physics_override_jump = override_jump; @@ -1882,6 +1914,9 @@ void GenericCAO::processMessage(const std::string &data) // Same as 'ObjectRef::l_remove' if (!m_is_player) clearChildAttachments(); + } else { + if (m_client->modsLoaded()) + m_client->getScript()->on_object_hp_change(m_id); } } else if (cmd == AO_CMD_UPDATE_ARMOR_GROUPS) { m_armor_groups.clear(); @@ -1968,7 +2003,7 @@ void GenericCAO::updateMeshCulling() if (!m_is_local_player) return; - const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST; + const bool hidden = m_client->getCamera()->getCameraMode() == CAMERA_MODE_FIRST && ! g_settings->getBool("freecam"); scene::ISceneNode *node = getSceneNode(); diff --git a/src/client/content_cao.h b/src/client/content_cao.h index 783aa4199..8e5d04bfa 100644 --- a/src/client/content_cao.h +++ b/src/client/content_cao.h @@ -172,6 +172,21 @@ public: inline const v3f &getRotation() const { return m_rotation; } + inline const v3f getAcceleration() const + { + return m_acceleration; + } + + inline const v3f getVelocity() const + { + return m_velocity; + } + + inline u16 getHp() const + { + return m_hp; + } + bool isImmortal() const; inline const ObjectProperties &getProperties() const { return m_prop; } @@ -210,6 +225,16 @@ public: return m_is_local_player; } + inline std::string getName() const + { + return m_name; + } + + inline bool isPlayer() const + { + return m_is_player; + } + inline bool isVisible() const { return m_is_visible; @@ -230,6 +255,7 @@ public: void addAttachmentChild(int child_id); void removeAttachmentChild(int child_id); ClientActiveObject *getParent() const; + int getParentId() const { return m_attachment_parent_id; } const std::unordered_set<int> &getAttachmentChildIds() const { return m_attachment_child_ids; } void updateAttachments(); @@ -284,5 +310,16 @@ public: return m_prop.infotext; } + float m_waiting_for_reattach; + + ObjectProperties *getProperties() + { + return &m_prop; + } + + void setProperties(ObjectProperties newprops); + void updateMeshCulling(); + + std::vector<std::string> nametag_images = {}; }; diff --git a/src/client/game.cpp b/src/client/game.cpp index f93bd34a3..e439d0e32 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -78,846 +78,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #else #include "client/sound.h" #endif -/* - Text input system -*/ - -struct TextDestNodeMetadata : public TextDest -{ - TextDestNodeMetadata(v3s16 p, Client *client) - { - m_p = p; - m_client = client; - } - // This is deprecated I guess? -celeron55 - void gotText(const std::wstring &text) - { - std::string ntext = wide_to_utf8(text); - infostream << "Submitting 'text' field of node at (" << m_p.X << "," - << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; - StringMap fields; - fields["text"] = ntext; - m_client->sendNodemetaFields(m_p, "", fields); - } - void gotText(const StringMap &fields) - { - m_client->sendNodemetaFields(m_p, "", fields); - } - - v3s16 m_p; - Client *m_client; -}; - -struct TextDestPlayerInventory : public TextDest -{ - TextDestPlayerInventory(Client *client) - { - m_client = client; - m_formname = ""; - } - TextDestPlayerInventory(Client *client, const std::string &formname) - { - m_client = client; - m_formname = formname; - } - void gotText(const StringMap &fields) - { - m_client->sendInventoryFields(m_formname, fields); - } - - Client *m_client; -}; - -struct LocalFormspecHandler : public TextDest -{ - LocalFormspecHandler(const std::string &formname) - { - m_formname = formname; - } - - LocalFormspecHandler(const std::string &formname, Client *client): - m_client(client) - { - m_formname = formname; - } - - void gotText(const StringMap &fields) - { - if (m_formname == "MT_PAUSE_MENU") { - if (fields.find("btn_sound") != fields.end()) { - g_gamecallback->changeVolume(); - return; - } - - if (fields.find("btn_key_config") != fields.end()) { - g_gamecallback->keyConfig(); - return; - } - - if (fields.find("btn_exit_menu") != fields.end()) { - g_gamecallback->disconnect(); - return; - } - - if (fields.find("btn_exit_os") != fields.end()) { - g_gamecallback->exitToOS(); -#ifndef __ANDROID__ - RenderingEngine::get_raw_device()->closeDevice(); -#endif - return; - } - - if (fields.find("btn_change_password") != fields.end()) { - g_gamecallback->changePassword(); - return; - } - - return; - } - - if (m_formname == "MT_DEATH_SCREEN") { - assert(m_client != 0); - m_client->sendRespawn(); - return; - } - - if (m_client->modsLoaded()) - m_client->getScript()->on_formspec_input(m_formname, fields); - } - - Client *m_client = nullptr; -}; - -/* Form update callback */ - -class NodeMetadataFormSource: public IFormSource -{ -public: - NodeMetadataFormSource(ClientMap *map, v3s16 p): - m_map(map), - m_p(p) - { - } - const std::string &getForm() const - { - static const std::string empty_string = ""; - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return empty_string; - - return meta->getString("formspec"); - } - - virtual std::string resolveText(const std::string &str) - { - NodeMetadata *meta = m_map->getNodeMetadata(m_p); - - if (!meta) - return str; - - return meta->resolveString(str); - } - - ClientMap *m_map; - v3s16 m_p; -}; - -class PlayerInventoryFormSource: public IFormSource -{ -public: - PlayerInventoryFormSource(Client *client): - m_client(client) - { - } - - const std::string &getForm() const - { - LocalPlayer *player = m_client->getEnv().getLocalPlayer(); - return player->inventory_formspec; - } - - Client *m_client; -}; - -class NodeDugEvent: public MtEvent -{ -public: - v3s16 p; - MapNode n; - - NodeDugEvent(v3s16 p, MapNode n): - p(p), - n(n) - {} - MtEvent::Type getType() const - { - return MtEvent::NODE_DUG; - } -}; - -class SoundMaker -{ - ISoundManager *m_sound; - const NodeDefManager *m_ndef; -public: - bool makes_footstep_sound; - float m_player_step_timer; - float m_player_jump_timer; - - SimpleSoundSpec m_player_step_sound; - SimpleSoundSpec m_player_leftpunch_sound; - SimpleSoundSpec m_player_rightpunch_sound; - - SoundMaker(ISoundManager *sound, const NodeDefManager *ndef): - m_sound(sound), - m_ndef(ndef), - makes_footstep_sound(true), - m_player_step_timer(0.0f), - m_player_jump_timer(0.0f) - { - } - - void playPlayerStep() - { - if (m_player_step_timer <= 0 && m_player_step_sound.exists()) { - m_player_step_timer = 0.03; - if (makes_footstep_sound) - m_sound->playSound(m_player_step_sound, false); - } - } - - void playPlayerJump() - { - if (m_player_jump_timer <= 0.0f) { - m_player_jump_timer = 0.2f; - m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false); - } - } - - static void viewBobbingStep(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->playPlayerStep(); - } - - static void playerRegainGround(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->playPlayerStep(); - } - - static void playerJump(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->playPlayerJump(); - } - - static void cameraPunchLeft(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); - } - - static void cameraPunchRight(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); - } - - static void nodeDug(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - NodeDugEvent *nde = (NodeDugEvent *)e; - sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false); - } - - static void playerDamage(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); - } - - static void playerFallingDamage(MtEvent *e, void *data) - { - SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); - } - - void registerReceiver(MtEventManager *mgr) - { - mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this); - mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this); - mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this); - mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this); - mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this); - mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this); - mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this); - mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this); - } - - void step(float dtime) - { - m_player_step_timer -= dtime; - m_player_jump_timer -= dtime; - } -}; - -// Locally stored sounds don't need to be preloaded because of this -class GameOnDemandSoundFetcher: public OnDemandSoundFetcher -{ - std::set<std::string> m_fetched; -private: - void paths_insert(std::set<std::string> &dst_paths, - const std::string &base, - const std::string &name) - { - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg"); - dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg"); - } -public: - void fetchSounds(const std::string &name, - std::set<std::string> &dst_paths, - std::set<std::string> &dst_datas) - { - if (m_fetched.count(name)) - return; - - m_fetched.insert(name); - - paths_insert(dst_paths, porting::path_share, name); - paths_insert(dst_paths, porting::path_user, name); - } -}; - - -typedef s32 SamplerLayer_t; - - -class GameGlobalShaderConstantSetter : public IShaderConstantSetter -{ - Sky *m_sky; - bool *m_force_fog_off; - f32 *m_fog_range; - bool m_fog_enabled; - CachedPixelShaderSetting<float, 4> m_sky_bg_color; - CachedPixelShaderSetting<float> m_fog_distance; - CachedVertexShaderSetting<float> m_animation_timer_vertex; - CachedPixelShaderSetting<float> m_animation_timer_pixel; - CachedPixelShaderSetting<float, 3> m_day_light; - CachedPixelShaderSetting<float, 4> m_star_color; - CachedPixelShaderSetting<float, 3> m_eye_position_pixel; - CachedVertexShaderSetting<float, 3> m_eye_position_vertex; - CachedPixelShaderSetting<float, 3> m_minimap_yaw; - CachedPixelShaderSetting<float, 3> m_camera_offset_pixel; - CachedPixelShaderSetting<float, 3> m_camera_offset_vertex; - CachedPixelShaderSetting<SamplerLayer_t> m_base_texture; - CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture; - Client *m_client; - -public: - void onSettingsChange(const std::string &name) - { - if (name == "enable_fog") - m_fog_enabled = g_settings->getBool("enable_fog"); - } - - static void settingsCallback(const std::string &name, void *userdata) - { - reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name); - } - - void setSky(Sky *sky) { m_sky = sky; } - - GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, - f32 *fog_range, Client *client) : - m_sky(sky), - m_force_fog_off(force_fog_off), - m_fog_range(fog_range), - m_sky_bg_color("skyBgColor"), - m_fog_distance("fogDistance"), - m_animation_timer_vertex("animationTimer"), - m_animation_timer_pixel("animationTimer"), - m_day_light("dayLight"), - m_star_color("starColor"), - m_eye_position_pixel("eyePosition"), - m_eye_position_vertex("eyePosition"), - m_minimap_yaw("yawVec"), - m_camera_offset_pixel("cameraOffset"), - m_camera_offset_vertex("cameraOffset"), - m_base_texture("baseTexture"), - m_normal_texture("normalTexture"), - m_client(client) - { - g_settings->registerChangedCallback("enable_fog", settingsCallback, this); - m_fog_enabled = g_settings->getBool("enable_fog"); - } - - ~GameGlobalShaderConstantSetter() - { - g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this); - } - - void onSetConstants(video::IMaterialRendererServices *services) override - { - // Background color - video::SColor bgcolor = m_sky->getBgColor(); - video::SColorf bgcolorf(bgcolor); - float bgcolorfa[4] = { - bgcolorf.r, - bgcolorf.g, - bgcolorf.b, - bgcolorf.a, - }; - m_sky_bg_color.set(bgcolorfa, services); - - // Fog distance - float fog_distance = 10000 * BS; - - if (m_fog_enabled && !*m_force_fog_off) - fog_distance = *m_fog_range; - - m_fog_distance.set(&fog_distance, services); - - u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio(); - video::SColorf sunlight; - get_sunlight_color(&sunlight, daynight_ratio); - float dnc[3] = { - sunlight.r, - sunlight.g, - sunlight.b }; - m_day_light.set(dnc, services); - - video::SColorf star_color = m_sky->getCurrentStarColor(); - float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a}; - m_star_color.set(clr, services); - - u32 animation_timer = porting::getTimeMs() % 1000000; - float animation_timer_f = (float)animation_timer / 100000.f; - m_animation_timer_vertex.set(&animation_timer_f, services); - m_animation_timer_pixel.set(&animation_timer_f, services); - - float eye_position_array[3]; - v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); - epos.getAs3Values(eye_position_array); - m_eye_position_pixel.set(eye_position_array, services); - m_eye_position_vertex.set(eye_position_array, services); - - if (m_client->getMinimap()) { - float minimap_yaw_array[3]; - v3f minimap_yaw = m_client->getMinimap()->getYawVec(); - minimap_yaw.getAs3Values(minimap_yaw_array); - m_minimap_yaw.set(minimap_yaw_array, services); - } - - float camera_offset_array[3]; - v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS); - offset.getAs3Values(camera_offset_array); - m_camera_offset_pixel.set(camera_offset_array, services); - m_camera_offset_vertex.set(camera_offset_array, services); - - SamplerLayer_t base_tex = 0, normal_tex = 1; - m_base_texture.set(&base_tex, services); - m_normal_texture.set(&normal_tex, services); - } -}; - - -class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory -{ - Sky *m_sky; - bool *m_force_fog_off; - f32 *m_fog_range; - Client *m_client; - std::vector<GameGlobalShaderConstantSetter *> created_nosky; -public: - GameGlobalShaderConstantSetterFactory(bool *force_fog_off, - f32 *fog_range, Client *client) : - m_sky(NULL), - m_force_fog_off(force_fog_off), - m_fog_range(fog_range), - m_client(client) - {} - - void setSky(Sky *sky) { - m_sky = sky; - for (GameGlobalShaderConstantSetter *ggscs : created_nosky) { - ggscs->setSky(m_sky); - } - created_nosky.clear(); - } - - virtual IShaderConstantSetter* create() - { - auto *scs = new GameGlobalShaderConstantSetter( - m_sky, m_force_fog_off, m_fog_range, m_client); - if (!m_sky) - created_nosky.push_back(scs); - return scs; - } -}; - -#ifdef HAVE_TOUCHSCREENGUI -#define SIZE_TAG "size[11,5.5]" -#else -#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop -#endif - -/**************************************************************************** - ****************************************************************************/ - -const static float object_hit_delay = 0.2; - -struct FpsControl { - FpsControl() : last_time(0), busy_time(0), sleep_time(0) {} - - void reset(); - - void limit(IrrlichtDevice *device, f32 *dtime); - - u32 getBusyMs() const { return busy_time / 1000; } - - // all values in microseconds (us) - u64 last_time, busy_time, sleep_time; -}; - - -/* The reason the following structs are not anonymous structs within the - * class is that they are not used by the majority of member functions and - * many functions that do require objects of thse types do not modify them - * (so they can be passed as a const qualified parameter) - */ - -struct GameRunData { - u16 dig_index; - u16 new_playeritem; - PointedThing pointed_old; - bool digging; - bool punching; - bool btn_down_for_dig; - bool dig_instantly; - bool digging_blocked; - bool reset_jump_timer; - float nodig_delay_timer; - float dig_time; - float dig_time_complete; - float repeat_place_timer; - float object_hit_delay_timer; - float time_from_last_punch; - ClientActiveObject *selected_object; - - float jump_timer; - float damage_flash; - float update_draw_list_timer; - - f32 fog_range; - - v3f update_draw_list_last_cam_dir; - - float time_of_day_smooth; -}; - -class Game; - -struct ClientEventHandler -{ - void (Game::*handler)(ClientEvent *, CameraOrientation *); -}; - -/**************************************************************************** - THE GAME - ****************************************************************************/ - -using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>; - -/* This is not intended to be a public class. If a public class becomes - * desirable then it may be better to create another 'wrapper' class that - * hides most of the stuff in this class (nothing in this class is required - * by any other file) but exposes the public methods/data only. - */ -class Game { -public: - Game(); - ~Game(); - - bool startup(bool *kill, - InputHandler *input, - RenderingEngine *rendering_engine, - const GameStartData &game_params, - std::string &error_message, - bool *reconnect, - ChatBackend *chat_backend); - - void run(); - void shutdown(); - -protected: - - // Basic initialisation - bool init(const std::string &map_dir, const std::string &address, - u16 port, const SubgameSpec &gamespec); - bool initSound(); - bool createSingleplayerServer(const std::string &map_dir, - const SubgameSpec &gamespec, u16 port); - - // Client creation - bool createClient(const GameStartData &start_data); - bool initGui(); - - // Client connection - bool connectToServer(const GameStartData &start_data, - bool *connect_ok, bool *aborted); - bool getServerContent(bool *aborted); - - // Main loop - - void updateInteractTimers(f32 dtime); - bool checkConnection(); - bool handleCallbacks(); - void processQueues(); - void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); - void updateDebugState(); - void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); - void updateProfilerGraphs(ProfilerGraph *graph); - - // Input related - void processUserInput(f32 dtime); - void processKeyInput(); - void processItemSelection(u16 *new_playeritem); - - void dropSelectedItem(bool single_item = false); - void openInventory(); - void openConsole(float scale, const wchar_t *line=NULL); - void toggleFreeMove(); - void toggleFreeMoveAlt(); - void togglePitchMove(); - void toggleFast(); - void toggleNoClip(); - void toggleCinematic(); - void toggleBlockBounds(); - void toggleAutoforward(); - - void toggleMinimap(bool shift_pressed); - void toggleFog(); - void toggleDebug(); - void toggleUpdateCamera(); - - void increaseViewRange(); - void decreaseViewRange(); - void toggleFullViewRange(); - void checkZoomEnabled(); - - void updateCameraDirection(CameraOrientation *cam, float dtime); - void updateCameraOrientation(CameraOrientation *cam, float dtime); - void updatePlayerControl(const CameraOrientation &cam); - void step(f32 *dtime); - void processClientEvents(CameraOrientation *cam); - void updateCamera(f32 dtime); - void updateSound(f32 dtime); - void processPlayerInteraction(f32 dtime, bool show_hud); - /*! - * Returns the object or node the player is pointing at. - * Also updates the selected thing in the Hud. - * - * @param[in] shootline the shootline, starting from - * the camera position. This also gives the maximal distance - * of the search. - * @param[in] liquids_pointable if false, liquids are ignored - * @param[in] look_for_object if false, objects are ignored - * @param[in] camera_offset offset of the camera - * @param[out] selected_object the selected object or - * NULL if not found - */ - PointedThing updatePointedThing( - const core::line3d<f32> &shootline, bool liquids_pointable, - bool look_for_object, const v3s16 &camera_offset); - void handlePointingAtNothing(const ItemStack &playerItem); - void handlePointingAtNode(const PointedThing &pointed, - const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); - void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, - const v3f &player_position, bool show_debug); - void handleDigging(const PointedThing &pointed, const v3s16 &nodepos, - const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); - void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, - const CameraOrientation &cam); - void updateShadows(); - - // Misc - void showOverlayMessage(const char *msg, float dtime, int percent, - bool draw_clouds = true); - - static void settingChangedCallback(const std::string &setting_name, void *data); - void readSettings(); - - inline bool isKeyDown(GameKeyType k) - { - return input->isKeyDown(k); - } - inline bool wasKeyDown(GameKeyType k) - { - return input->wasKeyDown(k); - } - inline bool wasKeyPressed(GameKeyType k) - { - return input->wasKeyPressed(k); - } - inline bool wasKeyReleased(GameKeyType k) - { - return input->wasKeyReleased(k); - } - -#ifdef __ANDROID__ - void handleAndroidChatInput(); -#endif - -private: - struct Flags { - bool force_fog_off = false; - bool disable_camera_update = false; - }; - - void showDeathFormspec(); - void showPauseMenu(); - - void pauseAnimation(); - void resumeAnimation(); - - // ClientEvent handlers - 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_ShowFormSpec(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_HandleParticleEvent(ClientEvent *event, - CameraOrientation *cam); - void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam); - void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, - CameraOrientation *cam); - void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); - - void updateChat(f32 dtime); - - bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item, - const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed, - const NodeMetadata *meta); - static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX]; - - f32 getSensitivityScaleFactor() const; - - InputHandler *input = nullptr; - - Client *client = nullptr; - Server *server = nullptr; - - IWritableTextureSource *texture_src = nullptr; - IWritableShaderSource *shader_src = nullptr; - - // When created, these will be filled with data received from the server - IWritableItemDefManager *itemdef_manager = nullptr; - NodeDefManager *nodedef_manager = nullptr; - - GameOnDemandSoundFetcher soundfetcher; // useful when testing - ISoundManager *sound = nullptr; - bool sound_is_dummy = false; - SoundMaker *soundmaker = nullptr; - - ChatBackend *chat_backend = nullptr; - LogOutputBuffer m_chat_log_buf; - - EventManager *eventmgr = nullptr; - QuicktuneShortcutter *quicktune = nullptr; - bool registration_confirmation_shown = false; - - std::unique_ptr<GameUI> m_game_ui; - GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() - MapDrawControl *draw_control = nullptr; - Camera *camera = nullptr; - Clouds *clouds = nullptr; // Free using ->Drop() - Sky *sky = nullptr; // Free using ->Drop() - Hud *hud = nullptr; - Minimap *mapper = nullptr; - - // Map server hud ids to client hud ids - std::unordered_map<u32, u32> m_hud_server_to_client; - - GameRunData runData; - Flags m_flags; - - /* 'cache' - This class does take ownership/responsibily for cleaning up etc of any of - these items (e.g. device) - */ - IrrlichtDevice *device; - RenderingEngine *m_rendering_engine; - video::IVideoDriver *driver; - scene::ISceneManager *smgr; - bool *kill; - std::string *error_message; - bool *reconnect_requested; - scene::ISceneNode *skybox; - PausedNodesList paused_animated_nodes; - - bool simple_singleplayer_mode; - /* End 'cache' */ - - /* Pre-calculated values - */ - int crack_animation_length; - - IntervalLimiter profiler_interval; - - /* - * TODO: Local caching of settings is not optimal and should at some stage - * be updated to use a global settings object for getting thse values - * (as opposed to the this local caching). This can be addressed in - * a later release. - */ - bool m_cache_doubletap_jump; - bool m_cache_enable_clouds; - bool m_cache_enable_joysticks; - bool m_cache_enable_particles; - bool m_cache_enable_fog; - bool m_cache_enable_noclip; - bool m_cache_enable_free_move; - f32 m_cache_mouse_sensitivity; - f32 m_cache_joystick_frustum_sensitivity; - f32 m_repeat_place_time; - f32 m_cache_cam_smoothing; - f32 m_cache_fog_start; - - bool m_invert_mouse = false; - bool m_first_loop_after_window_activation = false; - bool m_camera_offset_changed = false; - - bool m_does_lost_focus_pause_game = false; - -#if IRRLICHT_VERSION_MT_REVISION < 5 - int m_reset_HW_buffer_counter = 0; -#endif - -#ifdef HAVE_TOUCHSCREENGUI - bool m_cache_hold_aux1; -#endif -#ifdef __ANDROID__ - bool m_android_chat_open; -#endif -}; Game::Game() : m_chat_log_buf(g_logger), @@ -949,6 +109,16 @@ Game::Game() : &settingChangedCallback, this); g_settings->registerChangedCallback("camera_smoothing", &settingChangedCallback, this); + g_settings->registerChangedCallback("freecam", + &freecamChangedCallback, this); + g_settings->registerChangedCallback("xray", + &updateAllMapBlocksCallback, this); + g_settings->registerChangedCallback("xray_nodes", + &updateAllMapBlocksCallback, this); + g_settings->registerChangedCallback("fullbright", + &updateAllMapBlocksCallback, this); + g_settings->registerChangedCallback("node_esp_nodes", + &updateAllMapBlocksCallback, this); readSettings(); @@ -1007,6 +177,16 @@ Game::~Game() &settingChangedCallback, this); g_settings->deregisterChangedCallback("camera_smoothing", &settingChangedCallback, this); + g_settings->deregisterChangedCallback("freecam", + &freecamChangedCallback, this); + g_settings->deregisterChangedCallback("xray", + &updateAllMapBlocksCallback, this); + g_settings->deregisterChangedCallback("xray_nodes", + &updateAllMapBlocksCallback, this); + g_settings->deregisterChangedCallback("fullbright", + &updateAllMapBlocksCallback, this); + g_settings->deregisterChangedCallback("node_esp_nodes", + &updateAllMapBlocksCallback, this); } bool Game::startup(bool *kill, @@ -1064,8 +244,6 @@ void Game::run() { ProfilerGraph graph; RunStats stats = {}; - CameraOrientation cam_view_target = {}; - CameraOrientation cam_view = {}; FpsControl draw_times; f32 dtime; // in seconds @@ -1175,6 +353,9 @@ void Game::shutdown() if (gui_chat_console) gui_chat_console->drop(); + if (m_cheat_menu) + delete m_cheat_menu; + if (sky) sky->drop(); @@ -1395,7 +576,7 @@ bool Game::createClient(const GameStartData &start_data) delete[] text; } str += L" ["; - str += driver->getName(); + str += L"Minetest Hackclient"; str += L"]"; device->setWindowCaption(str.c_str()); @@ -1428,6 +609,20 @@ bool Game::initGui() gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, chat_backend, client, &g_menumgr); + if (!gui_chat_console) { + *error_message = "Could not allocate memory for chat console"; + errorstream << *error_message << std::endl; + return false; + } + + m_cheat_menu = new CheatMenu(client); + + if (!m_cheat_menu) { + *error_message = "Could not allocate memory for cheat menu"; + errorstream << *error_message << std::endl; + return false; + } + #ifdef HAVE_TOUCHSCREENGUI if (g_touchscreengui) @@ -1888,6 +1083,18 @@ void Game::processUserInput(f32 dtime) void Game::processKeyInput() { + if (wasKeyDown(KeyType::SELECT_UP)) { + m_cheat_menu->selectUp(); + } else if (wasKeyDown(KeyType::SELECT_DOWN)) { + m_cheat_menu->selectDown(); + } else if (wasKeyDown(KeyType::SELECT_LEFT)) { + m_cheat_menu->selectLeft(); + } else if (wasKeyDown(KeyType::SELECT_RIGHT)) { + m_cheat_menu->selectRight(); + } else if (wasKeyDown(KeyType::SELECT_CONFIRM)) { + m_cheat_menu->selectConfirm(); + } + if (wasKeyDown(KeyType::DROP)) { dropSelectedItem(isKeyDown(KeyType::SNEAK)); } else if (wasKeyDown(KeyType::AUTOFORWARD)) { @@ -1897,6 +1104,8 @@ void Game::processKeyInput() toggleAutoforward(); } else if (wasKeyDown(KeyType::INVENTORY)) { openInventory(); + } else if (wasKeyDown(KeyType::ENDERCHEST)) { + openEnderchest(); } else if (input->cancelPressed()) { #ifdef __ANDROID__ m_android_chat_open = false; @@ -1925,6 +1134,12 @@ void Game::processKeyInput() toggleFast(); } else if (wasKeyDown(KeyType::NOCLIP)) { toggleNoClip(); + } else if (wasKeyDown(KeyType::KILLAURA)) { + toggleKillaura(); + } else if (wasKeyDown(KeyType::FREECAM)) { + toggleFreecam(); + } else if (wasKeyDown(KeyType::SCAFFOLD)) { + toggleScaffold(); #if USE_SOUND } else if (wasKeyDown(KeyType::MUTE)) { if (g_settings->getBool("enable_sound")) { @@ -1974,6 +1189,8 @@ void Game::processKeyInput() m_game_ui->toggleChat(); } else if (wasKeyDown(KeyType::TOGGLE_FOG)) { toggleFog(); + } else if (wasKeyDown(KeyType::TOGGLE_CHEAT_MENU)) { + m_game_ui->toggleCheatMenu(); } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) { toggleUpdateCamera(); } else if (wasKeyDown(KeyType::TOGGLE_DEBUG)) { @@ -2092,6 +1309,18 @@ void Game::openInventory() formspec->setFormSpec(fs_src->getForm(), inventoryloc); } +void Game::openEnderchest() +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (!player || !player->getCAO()) + return; + + infostream << "Game: Launching special inventory" << std::endl; + + if (client->modsLoaded()) + client->getScript()->open_enderchest(); +} + void Game::openConsole(float scale, const wchar_t *line) { @@ -2199,6 +1428,42 @@ void Game::toggleNoClip() } } +void Game::toggleKillaura() +{ + bool killaura = ! g_settings->getBool("killaura"); + g_settings->set("killaura", bool_to_cstr(killaura)); + + if (killaura) { + m_game_ui->showTranslatedStatusText("Killaura enabled"); + } else { + m_game_ui->showTranslatedStatusText("Killaura disabled"); + } +} + +void Game::toggleFreecam() +{ + bool freecam = ! g_settings->getBool("freecam"); + g_settings->set("freecam", bool_to_cstr(freecam)); + + if (freecam) { + m_game_ui->showTranslatedStatusText("Freecam enabled"); + } else { + m_game_ui->showTranslatedStatusText("Freecam disabled"); + } +} + +void Game::toggleScaffold() +{ + bool scaffold = ! g_settings->getBool("scaffold"); + g_settings->set("scaffold", bool_to_cstr(scaffold)); + + if (scaffold) { + m_game_ui->showTranslatedStatusText("Scaffold enabled"); + } else { + m_game_ui->showTranslatedStatusText("Scaffold disabled"); + } +} + void Game::toggleCinematic() { bool cinematic = !g_settings->getBool("cinematic"); @@ -2347,6 +1612,8 @@ void Game::toggleDebug() void Game::toggleUpdateCamera() { + if (g_settings->getBool("freecam")) + return; m_flags.disable_camera_update = !m_flags.disable_camera_update; if (m_flags.disable_camera_update) m_game_ui->showTranslatedStatusText("Camera update disabled"); @@ -2617,7 +1884,7 @@ void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation LocalPlayer *player = client->getEnv().getLocalPlayer(); f32 hp_max = player->getCAO() ? - player->getCAO()->getProperties().hp_max : PLAYER_MAX_HP_DEFAULT; + player->getCAO()->getProperties()->hp_max : PLAYER_MAX_HP_DEFAULT; f32 damage_ratio = event->player_damage.amount / hp_max; runData.damage_flash += 95.0f + 64.f * damage_ratio; @@ -2679,11 +1946,19 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam) { - FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); - LocalFormspecHandler *txt_dst = - new LocalFormspecHandler(*event->show_formspec.formname, client); - GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), - &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); + if (event->show_formspec.formspec->empty()) { + auto formspec = m_game_ui->getFormspecGUI(); + if (formspec && (event->show_formspec.formname->empty() + || *(event->show_formspec.formname) == m_game_ui->getFormspecName())) { + formspec->quitMenu(); + } + } else { + FormspecFormSource *fs_src = new FormspecFormSource(*event->show_formspec.formspec); + LocalFormspecHandler *txt_dst = + new LocalFormspecHandler(*event->show_formspec.formname, client); + GUIFormSpecMenu::create(m_game_ui->getFormspecGUI(), client, m_rendering_engine->get_gui_env(), &input->joystick, + fs_src, txt_dst, client->getFormspecPrepend(), sound); + } delete event->show_formspec.formspec; delete event->show_formspec.formname; @@ -2961,18 +2236,9 @@ void Game::updateCamera(f32 dtime) v3s16 old_camera_offset = camera->getOffset(); - if (wasKeyDown(KeyType::CAMERA_MODE)) { - GenericCAO *playercao = player->getCAO(); - - // If playercao not loaded, don't change camera - if (!playercao) - return; - + if (wasKeyDown(KeyType::CAMERA_MODE) && ! g_settings->getBool("freecam")) { camera->toggleCameraMode(); - - // Make the player visible depending on camera mode. - playercao->updateMeshCulling(); - playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + updatePlayerCAOVisibility(); } float full_punch_interval = playeritem_toolcap.full_punch_interval; @@ -3003,6 +2269,17 @@ void Game::updateCamera(f32 dtime) } } +void Game::updatePlayerCAOVisibility() +{ + // Make the player visible depending on camera mode. + LocalPlayer *player = client->getEnv().getLocalPlayer(); + GenericCAO *playercao = player->getCAO(); + if (!playercao) + return; + playercao->updateMeshCulling(); + bool is_visible = camera->getCameraMode() > CAMERA_MODE_FIRST || g_settings->getBool("freecam"); + playercao->setChildrenVisible(is_visible); +} void Game::updateSound(f32 dtime) { @@ -3059,6 +2336,10 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) const ItemDefinition &selected_def = selected_item.getDefinition(itemdef_manager); f32 d = getToolRange(selected_def, hand_item.getDefinition(itemdef_manager)); + + if (g_settings->getBool("reach")) + d += g_settings->getU16("tool_range"); + core::line3d<f32> shootline; switch (camera->getCameraMode()) { @@ -3149,7 +2430,7 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) soundmaker->m_player_leftpunch_sound.name = ""; // Prepare for repeating, unless we're not supposed to - if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place")) + if ((isKeyDown(KeyType::PLACE) || g_settings->getBool("autoplace")) && !g_settings->getBool("safe_dig_and_place")) runData.repeat_place_timer += dtime; else runData.repeat_place_timer = 0; @@ -3212,8 +2493,7 @@ PointedThing Game::updatePointedThing( runData.selected_object = NULL; hud->pointing_at_object = false; - - RaycastState s(shootline, look_for_object, liquids_pointable); + RaycastState s(shootline, look_for_object, liquids_pointable, ! g_settings->getBool("dont_point_nodes")); PointedThing result; env.continueRaycast(&s, &result); if (result.type == POINTEDTHING_OBJECT) { @@ -3287,7 +2567,6 @@ PointedThing Game::updatePointedThing( return result; } - void Game::handlePointingAtNothing(const ItemStack &playerItem) { infostream << "Attempted to place item while pointing at nothing" << std::endl; @@ -3309,9 +2588,10 @@ void Game::handlePointingAtNode(const PointedThing &pointed, ClientMap &map = client->getEnv().getClientMap(); - if (runData.nodig_delay_timer <= 0.0 && isKeyDown(KeyType::DIG) + if (((runData.nodig_delay_timer <= 0.0 || g_settings->getBool("fastdig")) && (isKeyDown(KeyType::DIG) || g_settings->getBool("autodig")) && !runData.digging_blocked - && client->checkPrivilege("interact")) { + && client->checkPrivilege("interact")) + ) { handleDigging(pointed, nodepos, selected_item, hand_item, dtime); } @@ -3330,7 +2610,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, } if ((wasKeyPressed(KeyType::PLACE) || - runData.repeat_place_timer >= m_repeat_place_time) && + (runData.repeat_place_timer >= (g_settings->getBool("fastplace") ? 0.001 : m_repeat_place_time))) && client->checkPrivilege("interact")) { runData.repeat_place_timer = 0; infostream << "Place button pressed while looking at ground" << std::endl; @@ -3355,7 +2635,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, bool Game::nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos, - const PointedThing &pointed, const NodeMetadata *meta) + const PointedThing &pointed, const NodeMetadata *meta, bool force) { const auto &prediction = selected_def.node_placement_prediction; @@ -3372,7 +2652,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, // formspec in meta if (meta && !meta->getString("formspec").empty() && !input->isRandom() - && !isKeyDown(KeyType::SNEAK)) { + && !isKeyDown(KeyType::SNEAK) && !force) { // on_rightclick callbacks are called anyway if (nodedef_manager->get(map.getNode(nodepos)).rightclickable) client->interact(INTERACT_PLACE, pointed); @@ -3396,7 +2676,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, // on_rightclick callback if (prediction.empty() || (nodedef->get(node).rightclickable && - !isKeyDown(KeyType::SNEAK))) { + !isKeyDown(KeyType::SNEAK) && !force)) { // Report to server client->interact(INTERACT_PLACE, pointed); return false; @@ -3563,11 +2843,11 @@ void Game::handlePointingAtObject(const PointedThing &pointed, m_game_ui->setInfoText(infotext); - if (isKeyDown(KeyType::DIG)) { + if (isKeyDown(KeyType::DIG) || g_settings->getBool("autohit")) { bool do_punch = false; bool do_punch_damage = false; - if (runData.object_hit_delay_timer <= 0.0) { + if (runData.object_hit_delay_timer <= 0.0 || g_settings->getBool("spamclick")) { do_punch = true; do_punch_damage = true; runData.object_hit_delay_timer = object_hit_delay; @@ -3590,8 +2870,9 @@ void Game::handlePointingAtObject(const PointedThing &pointed, dir, &tool_item, runData.time_from_last_punch); runData.time_from_last_punch = 0; - if (!disable_send) + if (!disable_send) { client->interact(INTERACT_START_DIGGING, pointed); + } } } else if (wasKeyDown(KeyType::PLACE)) { infostream << "Pressed place button while pointing at object" << std::endl; @@ -3634,6 +2915,10 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, } } + if(g_settings->getBool("instant_break")) { + runData.dig_time_complete = 0; + runData.dig_instantly = true; + } if (!runData.digging) { infostream << "Started digging" << std::endl; runData.dig_instantly = runData.dig_time_complete == 0; @@ -3762,7 +3047,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, float direct_brightness; bool sunlight_seen; - if (m_cache_enable_noclip && m_cache_enable_free_move) { + if ((m_cache_enable_noclip && m_cache_enable_free_move) || g_settings->getBool("freecam")) { direct_brightness = time_brightness; sunlight_seen = true; } else { @@ -3865,7 +3150,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, if (player->hurt_tilt_timer > 0.0f) { player->hurt_tilt_timer -= dtime * 6.0f; - if (player->hurt_tilt_timer < 0.0f) + if (player->hurt_tilt_timer < 0.0f || g_settings->getBool("no_hurt_cam")) player->hurt_tilt_strength = 0.0f; } @@ -3981,19 +3266,28 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont()); /* + Cheat menu + */ + + if (! gui_chat_console->isOpen()) { + if (m_game_ui->m_flags.show_cheat_menu) + m_cheat_menu->draw(driver, m_game_ui->m_flags.show_minimal_debug); + if (g_settings->getBool("cheat_hud")) + m_cheat_menu->drawHUD(driver, dtime); + } + /* Damage flash */ if (runData.damage_flash > 0.0f) { video::SColor color(runData.damage_flash, 180, 0, 0); - driver->draw2DRectangle(color, - core::rect<s32>(0, 0, screensize.X, screensize.Y), - NULL); + if (! g_settings->getBool("no_hurt_cam")) + driver->draw2DRectangle(color, core::rect<s32>(0, 0, screensize.X, screensize.Y), NULL); runData.damage_flash -= 384.0f * dtime; } /* - ==================== End scene ==================== + End scene */ #if IRRLICHT_VERSION_MT_REVISION < 5 if (++m_reset_HW_buffer_counter > 500) { @@ -4127,6 +3421,24 @@ void Game::settingChangedCallback(const std::string &setting_name, void *data) ((Game *)data)->readSettings(); } +void Game::updateAllMapBlocksCallback(const std::string &setting_name, void *data) +{ + ((Game *) data)->client->updateAllMapBlocks(); +} + +void Game::freecamChangedCallback(const std::string &setting_name, void *data) +{ + Game *game = (Game *) data; + LocalPlayer *player = game->client->getEnv().getLocalPlayer(); + if (g_settings->getBool("freecam")) { + game->camera->setCameraMode(CAMERA_MODE_FIRST); + player->freecamEnable(); + } else { + player->freecamDisable(); + } + game->updatePlayerCAOVisibility(); +} + void Game::readSettings() { m_cache_doubletap_jump = g_settings->getBool("doubletap_jump"); @@ -4156,6 +3468,26 @@ void Game::readSettings() m_does_lost_focus_pause_game = g_settings->getBool("pause_on_lost_focus"); } +bool Game::isKeyDown(GameKeyType k) +{ + return input->isKeyDown(k); +} + +bool Game::wasKeyDown(GameKeyType k) +{ + return input->wasKeyDown(k); +} + +bool Game::wasKeyPressed(GameKeyType k) +{ + return input->wasKeyPressed(k); +} + +bool Game::wasKeyReleased(GameKeyType k) +{ + return input->wasKeyReleased(k); +} + /****************************************************************************/ /**************************************************************************** Shutdown / cleanup @@ -4213,26 +3545,34 @@ void Game::showPauseMenu() "- %s: sneak/climb down\n" "- %s: drop item\n" "- %s: inventory\n" + "- %s: enderchest\n" "- Mouse: turn/look\n" "- Mouse wheel: select item\n" "- %s: chat\n" + "- %s: Killaura\n" + "- %s: Freecam\n" + "- %s: Scaffold\n" ); - char control_text_buf[600]; - - porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(), - GET_KEY_NAME(keymap_forward), - GET_KEY_NAME(keymap_backward), - GET_KEY_NAME(keymap_left), - GET_KEY_NAME(keymap_right), - GET_KEY_NAME(keymap_jump), - GET_KEY_NAME(keymap_dig), - GET_KEY_NAME(keymap_place), - GET_KEY_NAME(keymap_sneak), - GET_KEY_NAME(keymap_drop), - GET_KEY_NAME(keymap_inventory), - GET_KEY_NAME(keymap_chat) - ); + char control_text_buf[600]; + + porting::mt_snprintf(control_text_buf, sizeof(control_text_buf), control_text_template.c_str(), + GET_KEY_NAME(keymap_forward), + GET_KEY_NAME(keymap_backward), + GET_KEY_NAME(keymap_left), + GET_KEY_NAME(keymap_right), + GET_KEY_NAME(keymap_jump), + GET_KEY_NAME(keymap_dig), + GET_KEY_NAME(keymap_place), + GET_KEY_NAME(keymap_sneak), + GET_KEY_NAME(keymap_drop), + GET_KEY_NAME(keymap_inventory), + GET_KEY_NAME(keymap_enderchest), + GET_KEY_NAME(keymap_chat), + GET_KEY_NAME(keymap_toggle_killaura), + GET_KEY_NAME(keymap_toggle_freecam), + GET_KEY_NAME(keymap_toggle_scaffold) + ); std::string control_text = std::string(control_text_buf); str_formspec_escape(control_text); @@ -4330,6 +3670,8 @@ void Game::showPauseMenu() ****************************************************************************/ /****************************************************************************/ +Game *g_game; + void the_game(bool *kill, InputHandler *input, RenderingEngine *rendering_engine, @@ -4340,6 +3682,8 @@ void the_game(bool *kill, { Game game; + g_game = &game; + /* Make a copy of the server address because if a local singleplayer server * is created then this is updated and we don't want to change the value * passed to us by the calling function diff --git a/src/client/game.h b/src/client/game.h index d87e747c5..0e5d0550d 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -19,6 +19,59 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include <iomanip> +#include <cmath> +#include "client/renderingengine.h" +#include "camera.h" +#include "client.h" +#include "client/clientevent.h" +//#include "client/gameui.h" +#include "client/inputhandler.h" +#include "client/sound.h" +#include "client/tile.h" // For TextureSource +#include "client/keys.h" +#include "client/joystick_controller.h" +#include "clientmap.h" +#include "clouds.h" +#include "config.h" +#include "content_cao.h" +#include "client/event_manager.h" +#include "fontengine.h" +#include "itemdef.h" +#include "log.h" +#include "filesys.h" +#include "gettext.h" +#include "gui/cheatMenu.h" +#include "gui/guiChatConsole.h" +#include "gui/guiConfirmRegistration.h" +#include "gui/guiFormSpecMenu.h" +#include "gui/guiKeyChangeMenu.h" +#include "gui/guiPasswordChange.h" +#include "gui/guiVolumeChange.h" +#include "gui/mainmenumanager.h" +#include "gui/profilergraph.h" +#include "mapblock.h" +#include "minimap.h" +#include "nodedef.h" // Needed for determining pointing to nodes +#include "nodemetadata.h" +#include "particles.h" +#include "porting.h" +#include "profiler.h" +#include "raycast.h" +#include "server.h" +#include "settings.h" +#include "shader.h" +#include "sky.h" +#include "translation.h" +#include "util/basic_macros.h" +#include "util/directiontables.h" +#include "util/pointedthing.h" +#include "util/quicktune_shortcutter.h" +#include "irrlicht_changes/static_text.h" +#include "irr_ptr.h" +#include "version.h" +#include "script/scripting_client.h" +#include "hud.h" #include "irrlichttypes.h" #include <string> @@ -43,6 +96,836 @@ struct CameraOrientation { f32 camera_pitch; // "up/down" }; +/* + Text input system +*/ + +struct TextDestNodeMetadata : public TextDest +{ + TextDestNodeMetadata(v3s16 p, Client *client) + { + m_p = p; + m_client = client; + } + // This is deprecated I guess? -celeron55 + void gotText(const std::wstring &text) + { + std::string ntext = wide_to_utf8(text); + infostream << "Submitting 'text' field of node at (" << m_p.X << "," + << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; + StringMap fields; + fields["text"] = ntext; + m_client->sendNodemetaFields(m_p, "", fields); + } + void gotText(const StringMap &fields) + { + m_client->sendNodemetaFields(m_p, "", fields); + } + + v3s16 m_p; + Client *m_client; +}; + +struct TextDestPlayerInventory : public TextDest +{ + TextDestPlayerInventory(Client *client) + { + m_client = client; + m_formname = ""; + } + TextDestPlayerInventory(Client *client, const std::string &formname) + { + m_client = client; + m_formname = formname; + } + void gotText(const StringMap &fields) + { + m_client->sendInventoryFields(m_formname, fields); + } + + Client *m_client; +}; + +struct LocalFormspecHandler : public TextDest +{ + LocalFormspecHandler(const std::string &formname) + { + m_formname = formname; + } + + LocalFormspecHandler(const std::string &formname, Client *client): + m_client(client) + { + m_formname = formname; + } + + void gotText(const StringMap &fields) + { + if (m_formname == "MT_PAUSE_MENU") { + if (fields.find("btn_sound") != fields.end()) { + g_gamecallback->changeVolume(); + return; + } + + if (fields.find("btn_key_config") != fields.end()) { + g_gamecallback->keyConfig(); + return; + } + + if (fields.find("btn_exit_menu") != fields.end()) { + g_gamecallback->disconnect(); + return; + } + + if (fields.find("btn_exit_os") != fields.end()) { + g_gamecallback->exitToOS(); +#ifndef __ANDROID__ + RenderingEngine::get_raw_device()->closeDevice(); +#endif + return; + } + + if (fields.find("btn_change_password") != fields.end()) { + g_gamecallback->changePassword(); + return; + } + + return; + } + + if (m_formname == "MT_DEATH_SCREEN") { + assert(m_client != 0); + m_client->sendRespawn(); + return; + } + + if (m_client->modsLoaded()) + m_client->getScript()->on_formspec_input(m_formname, fields); + } + + Client *m_client = nullptr; +}; + +/* Form update callback */ + +class NodeMetadataFormSource: public IFormSource +{ +public: + NodeMetadataFormSource(ClientMap *map, v3s16 p): + m_map(map), + m_p(p) + { + } + const std::string &getForm() const + { + static const std::string empty_string = ""; + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return empty_string; + + return meta->getString("formspec"); + } + + virtual std::string resolveText(const std::string &str) + { + NodeMetadata *meta = m_map->getNodeMetadata(m_p); + + if (!meta) + return str; + + return meta->resolveString(str); + } + + ClientMap *m_map; + v3s16 m_p; +}; + +class PlayerInventoryFormSource: public IFormSource +{ +public: + PlayerInventoryFormSource(Client *client): + m_client(client) + { + } + + const std::string &getForm() const + { + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); + return player->inventory_formspec; + } + + Client *m_client; +}; + +class NodeDugEvent: public MtEvent +{ +public: + v3s16 p; + MapNode n; + + NodeDugEvent(v3s16 p, MapNode n): + p(p), + n(n) + {} + MtEvent::Type getType() const + { + return MtEvent::NODE_DUG; + } +}; + +class SoundMaker +{ + ISoundManager *m_sound; + const NodeDefManager *m_ndef; +public: + bool makes_footstep_sound; + float m_player_step_timer; + float m_player_jump_timer; + + SimpleSoundSpec m_player_step_sound; + SimpleSoundSpec m_player_leftpunch_sound; + SimpleSoundSpec m_player_rightpunch_sound; + + SoundMaker(ISoundManager *sound, const NodeDefManager *ndef): + m_sound(sound), + m_ndef(ndef), + makes_footstep_sound(true), + m_player_step_timer(0.0f), + m_player_jump_timer(0.0f) + { + } + + void playPlayerStep() + { + if (m_player_step_timer <= 0 && m_player_step_sound.exists()) { + m_player_step_timer = 0.03; + if (makes_footstep_sound) + m_sound->playSound(m_player_step_sound, false); + } + } + + void playPlayerJump() + { + if (m_player_jump_timer <= 0.0f) { + m_player_jump_timer = 0.2f; + m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false); + } + } + + static void viewBobbingStep(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerStep(); + } + + static void playerRegainGround(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerStep(); + } + + static void playerJump(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->playPlayerJump(); + } + + static void cameraPunchLeft(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); + } + + static void cameraPunchRight(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); + } + + static void nodeDug(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + NodeDugEvent *nde = (NodeDugEvent *)e; + sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false); + } + + static void playerDamage(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); + } + + static void playerFallingDamage(MtEvent *e, void *data) + { + SoundMaker *sm = (SoundMaker *)data; + sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); + } + + void registerReceiver(MtEventManager *mgr) + { + mgr->reg(MtEvent::VIEW_BOBBING_STEP, SoundMaker::viewBobbingStep, this); + mgr->reg(MtEvent::PLAYER_REGAIN_GROUND, SoundMaker::playerRegainGround, this); + mgr->reg(MtEvent::PLAYER_JUMP, SoundMaker::playerJump, this); + mgr->reg(MtEvent::CAMERA_PUNCH_LEFT, SoundMaker::cameraPunchLeft, this); + mgr->reg(MtEvent::CAMERA_PUNCH_RIGHT, SoundMaker::cameraPunchRight, this); + mgr->reg(MtEvent::NODE_DUG, SoundMaker::nodeDug, this); + mgr->reg(MtEvent::PLAYER_DAMAGE, SoundMaker::playerDamage, this); + mgr->reg(MtEvent::PLAYER_FALLING_DAMAGE, SoundMaker::playerFallingDamage, this); + } + + void step(float dtime) + { + m_player_step_timer -= dtime; + m_player_jump_timer -= dtime; + } +}; + +// Locally stored sounds don't need to be preloaded because of this +class GameOnDemandSoundFetcher: public OnDemandSoundFetcher +{ + std::set<std::string> m_fetched; +private: + void paths_insert(std::set<std::string> &dst_paths, + const std::string &base, + const std::string &name) + { + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".0.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".1.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".2.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".3.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".4.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".5.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".6.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".7.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".8.ogg"); + dst_paths.insert(base + DIR_DELIM + "sounds" + DIR_DELIM + name + ".9.ogg"); + } +public: + void fetchSounds(const std::string &name, + std::set<std::string> &dst_paths, + std::set<std::string> &dst_datas) + { + if (m_fetched.count(name)) + return; + + m_fetched.insert(name); + + paths_insert(dst_paths, porting::path_share, name); + paths_insert(dst_paths, porting::path_user, name); + } +}; + + +typedef s32 SamplerLayer_t; + + +class GameGlobalShaderConstantSetter : public IShaderConstantSetter +{ + Sky *m_sky; + bool *m_force_fog_off; + f32 *m_fog_range; + bool m_fog_enabled; + CachedPixelShaderSetting<float, 4> m_sky_bg_color; + CachedPixelShaderSetting<float> m_fog_distance; + CachedVertexShaderSetting<float> m_animation_timer_vertex; + CachedPixelShaderSetting<float> m_animation_timer_pixel; + CachedPixelShaderSetting<float, 3> m_day_light; + CachedPixelShaderSetting<float, 4> m_star_color; + CachedPixelShaderSetting<float, 3> m_eye_position_pixel; + CachedVertexShaderSetting<float, 3> m_eye_position_vertex; + CachedPixelShaderSetting<float, 3> m_minimap_yaw; + CachedPixelShaderSetting<float, 3> m_camera_offset_pixel; + CachedPixelShaderSetting<float, 3> m_camera_offset_vertex; + CachedPixelShaderSetting<SamplerLayer_t> m_base_texture; + CachedPixelShaderSetting<SamplerLayer_t> m_normal_texture; + Client *m_client; + +public: + void onSettingsChange(const std::string &name) + { + if (name == "enable_fog") + m_fog_enabled = g_settings->getBool("enable_fog"); + } + + static void settingsCallback(const std::string &name, void *userdata) + { + reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name); + } + + void setSky(Sky *sky) { m_sky = sky; } + + GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, + f32 *fog_range, Client *client) : + m_sky(sky), + m_force_fog_off(force_fog_off), + m_fog_range(fog_range), + m_sky_bg_color("skyBgColor"), + m_fog_distance("fogDistance"), + m_animation_timer_vertex("animationTimer"), + m_animation_timer_pixel("animationTimer"), + m_day_light("dayLight"), + m_star_color("starColor"), + m_eye_position_pixel("eyePosition"), + m_eye_position_vertex("eyePosition"), + m_minimap_yaw("yawVec"), + m_camera_offset_pixel("cameraOffset"), + m_camera_offset_vertex("cameraOffset"), + m_base_texture("baseTexture"), + m_normal_texture("normalTexture"), + m_client(client) + { + g_settings->registerChangedCallback("enable_fog", settingsCallback, this); + m_fog_enabled = g_settings->getBool("enable_fog"); + } + + ~GameGlobalShaderConstantSetter() + { + g_settings->deregisterChangedCallback("enable_fog", settingsCallback, this); + } + + void onSetConstants(video::IMaterialRendererServices *services) override + { + // Background color + video::SColor bgcolor = m_sky->getBgColor(); + video::SColorf bgcolorf(bgcolor); + float bgcolorfa[4] = { + bgcolorf.r, + bgcolorf.g, + bgcolorf.b, + bgcolorf.a, + }; + m_sky_bg_color.set(bgcolorfa, services); + + // Fog distance + float fog_distance = 10000 * BS; + + if (m_fog_enabled && !*m_force_fog_off) + fog_distance = *m_fog_range; + + m_fog_distance.set(&fog_distance, services); + + u32 daynight_ratio = (float)m_client->getEnv().getDayNightRatio(); + video::SColorf sunlight; + get_sunlight_color(&sunlight, daynight_ratio); + float dnc[3] = { + sunlight.r, + sunlight.g, + sunlight.b }; + m_day_light.set(dnc, services); + + video::SColorf star_color = m_sky->getCurrentStarColor(); + float clr[4] = {star_color.r, star_color.g, star_color.b, star_color.a}; + m_star_color.set(clr, services); + + u32 animation_timer = porting::getTimeMs() % 1000000; + float animation_timer_f = (float)animation_timer / 100000.f; + m_animation_timer_vertex.set(&animation_timer_f, services); + m_animation_timer_pixel.set(&animation_timer_f, services); + + float eye_position_array[3]; + v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); + epos.getAs3Values(eye_position_array); + m_eye_position_pixel.set(eye_position_array, services); + m_eye_position_vertex.set(eye_position_array, services); + + if (m_client->getMinimap()) { + float minimap_yaw_array[3]; + v3f minimap_yaw = m_client->getMinimap()->getYawVec(); + minimap_yaw.getAs3Values(minimap_yaw_array); + m_minimap_yaw.set(minimap_yaw_array, services); + } + + float camera_offset_array[3]; + v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS); + offset.getAs3Values(camera_offset_array); + m_camera_offset_pixel.set(camera_offset_array, services); + m_camera_offset_vertex.set(camera_offset_array, services); + + SamplerLayer_t base_tex = 0, normal_tex = 1; + m_base_texture.set(&base_tex, services); + m_normal_texture.set(&normal_tex, services); + } +}; + + +class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactory +{ + Sky *m_sky; + bool *m_force_fog_off; + f32 *m_fog_range; + Client *m_client; + std::vector<GameGlobalShaderConstantSetter *> created_nosky; +public: + GameGlobalShaderConstantSetterFactory(bool *force_fog_off, + f32 *fog_range, Client *client) : + m_sky(NULL), + m_force_fog_off(force_fog_off), + m_fog_range(fog_range), + m_client(client) + {} + + void setSky(Sky *sky) { + m_sky = sky; + for (GameGlobalShaderConstantSetter *ggscs : created_nosky) { + ggscs->setSky(m_sky); + } + created_nosky.clear(); + } + + virtual IShaderConstantSetter* create() + { + auto *scs = new GameGlobalShaderConstantSetter( + m_sky, m_force_fog_off, m_fog_range, m_client); + if (!m_sky) + created_nosky.push_back(scs); + return scs; + } +}; + +#ifdef HAVE_TOUCHSCREENGUI +#define SIZE_TAG "size[11,5.5]" +#else +#define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop +#endif + +/**************************************************************************** + ****************************************************************************/ + +const float object_hit_delay = 0.2; + +struct FpsControl { + FpsControl() : last_time(0), busy_time(0), sleep_time(0) {} + + void reset(); + + void limit(IrrlichtDevice *device, f32 *dtime); + + u32 getBusyMs() const { return busy_time / 1000; } + + // all values in microseconds (us) + u64 last_time, busy_time, sleep_time; +}; + + +/* The reason the following structs are not anonymous structs within the + * class is that they are not used by the majority of member functions and + * many functions that do require objects of thse types do not modify them + * (so they can be passed as a const qualified parameter) + */ + +struct GameRunData { + u16 dig_index; + u16 new_playeritem; + PointedThing pointed_old; + bool digging; + bool punching; + bool btn_down_for_dig; + bool dig_instantly; + bool digging_blocked; + bool reset_jump_timer; + float nodig_delay_timer; + float dig_time; + float dig_time_complete; + float repeat_place_timer; + float object_hit_delay_timer; + float time_from_last_punch; + ClientActiveObject *selected_object; + + float jump_timer; + float damage_flash; + float update_draw_list_timer; + + f32 fog_range; + + v3f update_draw_list_last_cam_dir; + + float time_of_day_smooth; +}; + +class Game; + +struct ClientEventHandler +{ + void (Game::*handler)(ClientEvent *, CameraOrientation *); +}; + +using PausedNodesList = std::vector<std::pair<irr_ptr<scene::IAnimatedMeshSceneNode>, float>>; + +class Game { +public: + Game(); + ~Game(); + + bool startup(bool *kill, + InputHandler *input, + RenderingEngine *rendering_engine, + const GameStartData &game_params, + std::string &error_message, + bool *reconnect, + ChatBackend *chat_backend); + + + void run(); + void shutdown(); + + // Basic initialisation + bool init(const std::string &map_dir, const std::string &address, + u16 port, const SubgameSpec &gamespec); + bool initSound(); + bool createSingleplayerServer(const std::string &map_dir, + const SubgameSpec &gamespec, u16 port); + + // Client creation + bool createClient(const GameStartData &start_data); + bool initGui(); + + // Client connection + bool connectToServer(const GameStartData &start_data, + bool *connect_ok, bool *aborted); + bool getServerContent(bool *aborted); + + // Main loop + + void updateInteractTimers(f32 dtime); + bool checkConnection(); + bool handleCallbacks(); + void processQueues(); + void updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime); + void updateDebugState(); + void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); + void updateProfilerGraphs(ProfilerGraph *graph); + + // Input related + void processUserInput(f32 dtime); + void processKeyInput(); + void processItemSelection(u16 *new_playeritem); + + void dropSelectedItem(bool single_item = false); + void openInventory(); + void openEnderchest(); + void openConsole(float scale, const wchar_t *line=NULL); + void toggleFreeMove(); + void toggleFreeMoveAlt(); + void togglePitchMove(); + void toggleFast(); + void toggleNoClip(); + void toggleKillaura(); + void toggleFreecam(); + void toggleScaffold(); + void toggleNextItem(); + void toggleCinematic(); + void toggleBlockBounds(); + void toggleAutoforward(); + + void toggleMinimap(bool shift_pressed); + void toggleFog(); + void toggleDebug(); + void toggleUpdateCamera(); + void updatePlayerCAOVisibility(); + + void increaseViewRange(); + void decreaseViewRange(); + void toggleFullViewRange(); + void checkZoomEnabled(); + + void updateCameraDirection(CameraOrientation *cam, float dtime); + void updateCameraOrientation(CameraOrientation *cam, float dtime); + void updatePlayerControl(const CameraOrientation &cam); + void step(f32 *dtime); + void processClientEvents(CameraOrientation *cam); + void updateCamera(f32 dtime); + void updateSound(f32 dtime); + void processPlayerInteraction(f32 dtime, bool show_hud); + /*! + * Returns the object or node the player is pointing at. + * Also updates the selected thing in the Hud. + * + * @param[in] shootline the shootline, starting from + * the camera position. This also gives the maximal distance + * of the search. + * @param[in] liquids_pointable if false, liquids are ignored + * @param[in] look_for_object if false, objects are ignored + * @param[in] camera_offset offset of the camera + * @param[out] selected_object the selected object or + * NULL if not found + */ + PointedThing updatePointedThing( + const core::line3d<f32> &shootline, bool liquids_pointable, + bool look_for_object, const v3s16 &camera_offset); + void handlePointingAtNothing(const ItemStack &playerItem); + void handlePointingAtNode(const PointedThing &pointed, + const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); + void handlePointingAtObject(const PointedThing &pointed, const ItemStack &playeritem, + const v3f &player_position, bool show_debug); + void handleDigging(const PointedThing &pointed, const v3s16 &nodepos, + const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime); + void updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, + const CameraOrientation &cam); + void updateShadows(); + + // Misc + void showOverlayMessage(const char *msg, float dtime, int percent, + bool draw_clouds = true); + + static void freecamChangedCallback(const std::string &setting_name, void *data); + static void settingChangedCallback(const std::string &setting_name, void *data); + static void updateAllMapBlocksCallback(const std::string &setting_name, void *data); + void readSettings(); + + bool isKeyDown(GameKeyType k); + bool wasKeyDown(GameKeyType k); + bool wasKeyPressed(GameKeyType k); + bool wasKeyReleased(GameKeyType k); + +#ifdef __ANDROID__ + void handleAndroidChatInput(); +#endif + + struct Flags { + bool force_fog_off = false; + bool disable_camera_update = false; + }; + + void showDeathFormspec(); + void showPauseMenu(); + + void pauseAnimation(); + void resumeAnimation(); + + // ClientEvent handlers + 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_ShowFormSpec(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_HandleParticleEvent(ClientEvent *event, + CameraOrientation *cam); + void handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetSun(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, + CameraOrientation *cam); + void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); + + void updateChat(f32 dtime); + + bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item, + const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed, + const NodeMetadata *meta, bool force = false); + static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX]; + + f32 getSensitivityScaleFactor() const; + + InputHandler *input = nullptr; + + Client *client = nullptr; + Server *server = nullptr; + + IWritableTextureSource *texture_src = nullptr; + IWritableShaderSource *shader_src = nullptr; + + // When created, these will be filled with data received from the server + IWritableItemDefManager *itemdef_manager = nullptr; + NodeDefManager *nodedef_manager = nullptr; + + GameOnDemandSoundFetcher soundfetcher; // useful when testing + ISoundManager *sound = nullptr; + bool sound_is_dummy = false; + SoundMaker *soundmaker = nullptr; + + ChatBackend *chat_backend = nullptr; + LogOutputBuffer m_chat_log_buf; + + EventManager *eventmgr = nullptr; + QuicktuneShortcutter *quicktune = nullptr; + bool registration_confirmation_shown = false; + + std::unique_ptr<GameUI> m_game_ui; + GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() + CheatMenu *m_cheat_menu = nullptr; + MapDrawControl *draw_control = nullptr; + Camera *camera = nullptr; + Clouds *clouds = nullptr; // Free using ->Drop() + Sky *sky = nullptr; // Free using ->Drop() + Hud *hud = nullptr; + Minimap *mapper = nullptr; + + // Map server hud ids to client hud ids + std::unordered_map<u32, u32> m_hud_server_to_client; + + GameRunData runData; + Flags m_flags; + + /* 'cache' + This class does take ownership/responsibily for cleaning up etc of any of + these items (e.g. device) + */ + IrrlichtDevice *device; + RenderingEngine *m_rendering_engine; + video::IVideoDriver *driver; + scene::ISceneManager *smgr; + bool *kill; + std::string *error_message; + bool *reconnect_requested; + scene::ISceneNode *skybox; + PausedNodesList paused_animated_nodes; + + bool simple_singleplayer_mode; + /* End 'cache' */ + + /* Pre-calculated values + */ + int crack_animation_length; + + IntervalLimiter profiler_interval; + + /* + * TODO: Local caching of settings is not optimal and should at some stage + * be updated to use a global settings object for getting thse values + * (as opposed to the this local caching). This can be addressed in + * a later release. + */ + bool m_cache_doubletap_jump; + bool m_cache_enable_clouds; + bool m_cache_enable_joysticks; + bool m_cache_enable_particles; + bool m_cache_enable_fog; + bool m_cache_enable_noclip; + bool m_cache_enable_free_move; + f32 m_cache_mouse_sensitivity; + f32 m_cache_joystick_frustum_sensitivity; + f32 m_repeat_place_time; + f32 m_cache_cam_smoothing; + f32 m_cache_fog_start; + + bool m_invert_mouse = false; + bool m_first_loop_after_window_activation = false; + bool m_camera_offset_changed = false; + + bool m_does_lost_focus_pause_game = false; + + CameraOrientation cam_view_target = {}; // added by dragonfireclient + CameraOrientation cam_view = {}; // added by dragonfireclient + +#if IRRLICHT_VERSION_MT_REVISION < 5 + int m_reset_HW_buffer_counter = 0; +#endif + +#ifdef HAVE_TOUCHSCREENGUI + bool m_cache_hold_aux1; +#endif +#ifdef __ANDROID__ + bool m_android_chat_open; +#endif +}; +extern Game *g_game; void the_game(bool *kill, InputHandler *input, diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 01c733b4f..54be24ae2 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -53,6 +53,9 @@ GameUI::GameUI() } void GameUI::init() { + m_guitext_coords = gui::StaticText::add(guienv, L"", core::rect<s32>(0, 0, 0, 0), false, + false, guiroot); + // First line of debug text m_guitext = gui::StaticText::add(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(), core::rect<s32>(0, 0, 0, 0), false, false, guiroot); @@ -100,8 +103,25 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ const CameraOrientation &cam, const PointedThing &pointed_old, const GUIChatConsole *chat_console, float dtime) { + LocalPlayer *player = client->getEnv().getLocalPlayer(); + v3f player_position = player->getPosition(); + v2u32 screensize = RenderingEngine::getWindowSize(); + bool show_coords = g_settings->getBool("coords"); + + if (show_coords) { + std::ostringstream os(std::ios_base::binary); + os << std::setprecision(1) << std::fixed + << (player_position.X / BS) + << ", " << (player_position.Y / BS) + << ", " << (player_position.Z / BS); + setStaticText(m_guitext_coords, utf8_to_wide(os.str()).c_str()); + m_guitext_coords->setRelativePosition(core::rect<s32>(5, screensize.Y - 5 - g_fontengine->getTextHeight(), screensize.X, screensize.Y)); + } + + m_guitext_coords->setVisible(show_coords); + // Minimal debug text must only contain info that can't give a gameplay advantage if (m_flags.show_minimal_debug) { const u16 fps = 1.0 / stats.dtime_jitter.avg; @@ -206,8 +226,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ m_guitext_status->enableOverrideColor(true); } - // Hide chat when console is visible - m_guitext_chat->setVisible(isChatVisible() && !chat_console->isVisible()); + m_guitext_chat->setVisible(isChatVisible()); } void GameUI::initFlags() @@ -238,15 +257,16 @@ void GameUI::setChatText(const EnrichedString &chat_text, u32 recent_chat_count) void GameUI::updateChatSize() { // Update gui element size and position - s32 chat_y = 5; + + const v2u32 &window_size = RenderingEngine::getWindowSize(); + + s32 chat_y = window_size.Y - 150 - m_guitext_chat->getTextHeight(); if (m_flags.show_minimal_debug) chat_y += g_fontengine->getLineHeight(); if (m_flags.show_basic_debug) chat_y += g_fontengine->getLineHeight(); - const v2u32 &window_size = RenderingEngine::getWindowSize(); - core::rect<s32> chat_size(10, chat_y, window_size.X - 20, 0); chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y, m_guitext_chat->getTextHeight() + chat_y); @@ -294,6 +314,15 @@ void GameUI::toggleChat() showTranslatedStatusText("Chat hidden"); } +void GameUI::toggleCheatMenu() +{ + m_flags.show_cheat_menu = !m_flags.show_cheat_menu; + if (m_flags.show_cheat_menu) + showTranslatedStatusText("Cheat Menu shown"); + else + showTranslatedStatusText("Cheat Menu hidden"); +} + void GameUI::toggleHud() { m_flags.show_hud = !m_flags.show_hud; diff --git a/src/client/gameui.h b/src/client/gameui.h index cc9377bdc..e22be068b 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -61,6 +61,7 @@ public: bool show_minimal_debug = false; bool show_basic_debug = false; bool show_profiler_graph = false; + bool show_cheat_menu = true; }; void init(); @@ -94,6 +95,7 @@ public: void updateProfiler(); void toggleChat(); + void toggleCheatMenu(); void toggleHud(); void toggleProfiler(); @@ -114,7 +116,8 @@ private: gui::IGUIStaticText *m_guitext = nullptr; // First line of debug text gui::IGUIStaticText *m_guitext2 = nullptr; // Second line of debug text - + gui::IGUIStaticText *m_guitext_coords = nullptr; + gui::IGUIStaticText *m_guitext_info = nullptr; // At the middle of the screen std::wstring m_infotext; diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 01f4d6ff3..d5debecfb 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -150,7 +150,7 @@ void Hud::drawItem(const ItemStack &item, const core::rect<s32>& rect, bool selected) { if (selected) { - /* draw hihlighting around selected item */ + /* draw highlighting around selected item */ if (use_hotbar_selected_image) { core::rect<s32> imgrect2 = rect; imgrect2.UpperLeftCorner.X -= (m_padding*2); diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index a6ba87e8d..d2d81be9c 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -44,6 +44,7 @@ void KeyCache::populate() key[KeyType::DROP] = getKeySetting("keymap_drop"); key[KeyType::INVENTORY] = getKeySetting("keymap_inventory"); + key[KeyType::ENDERCHEST] = getKeySetting("keymap_enderchest"); key[KeyType::CHAT] = getKeySetting("keymap_chat"); key[KeyType::CMD] = getKeySetting("keymap_cmd"); key[KeyType::CMD_LOCAL] = getKeySetting("keymap_cmd_local"); @@ -64,6 +65,7 @@ void KeyCache::populate() key[KeyType::TOGGLE_HUD] = getKeySetting("keymap_toggle_hud"); key[KeyType::TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat"); key[KeyType::TOGGLE_FOG] = getKeySetting("keymap_toggle_fog"); + key[KeyType::TOGGLE_CHEAT_MENU] = getKeySetting("keymap_toggle_cheat_menu"); key[KeyType::TOGGLE_UPDATE_CAMERA] = getKeySetting("keymap_toggle_update_camera"); key[KeyType::TOGGLE_DEBUG] = getKeySetting("keymap_toggle_debug"); key[KeyType::TOGGLE_PROFILER] = getKeySetting("keymap_toggle_profiler"); @@ -74,6 +76,14 @@ void KeyCache::populate() getKeySetting("keymap_decrease_viewing_range_min"); key[KeyType::RANGESELECT] = getKeySetting("keymap_rangeselect"); key[KeyType::ZOOM] = getKeySetting("keymap_zoom"); + key[KeyType::KILLAURA] = getKeySetting("keymap_toggle_killaura"); + key[KeyType::FREECAM] = getKeySetting("keymap_toggle_freecam"); + key[KeyType::SCAFFOLD] = getKeySetting("keymap_toggle_scaffold"); + key[KeyType::SELECT_UP] = getKeySetting("keymap_select_up"); + key[KeyType::SELECT_DOWN] = getKeySetting("keymap_select_down"); + key[KeyType::SELECT_LEFT] = getKeySetting("keymap_select_left"); + key[KeyType::SELECT_RIGHT] = getKeySetting("keymap_select_right"); + key[KeyType::SELECT_CONFIRM] = getKeySetting("keymap_select_confirm"); key[KeyType::QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next"); key[KeyType::QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev"); diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index 3db105c51..47a61d4b8 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #endif class InputHandler; +class TouchScreenGUI; /**************************************************************************** Fast key cache for main game loop @@ -201,7 +202,6 @@ public: TouchScreenGUI *m_touchscreengui; #endif -private: s32 mouse_wheel = 0; // The current state of keys @@ -241,6 +241,8 @@ public: } virtual bool isKeyDown(GameKeyType k) = 0; + virtual void setKeypress(const KeyPress &keyCode) = 0; + virtual void unsetKeypress(const KeyPress &keyCode) = 0; virtual bool wasKeyDown(GameKeyType k) = 0; virtual bool wasKeyPressed(GameKeyType k) = 0; virtual bool wasKeyReleased(GameKeyType k) = 0; @@ -288,6 +290,15 @@ public: { return m_receiver->IsKeyDown(keycache.key[k]) || joystick.isKeyDown(k); } + virtual void setKeypress(const KeyPress &keyCode) + { + m_receiver->keyIsDown.set(keyCode); + m_receiver->keyWasDown.set(keyCode); + } + virtual void unsetKeypress(const KeyPress &keyCode) + { + m_receiver->keyIsDown.unset(keyCode); + } virtual bool wasKeyDown(GameKeyType k) { return m_receiver->WasKeyDown(keycache.key[k]) || joystick.wasKeyDown(k); @@ -411,6 +422,14 @@ public: } virtual bool isKeyDown(GameKeyType k) { return keydown[keycache.key[k]]; } + virtual void setKeypress(const KeyPress &keyCode) + { + keydown.set(keyCode); + } + virtual void unsetKeypress(const KeyPress &keyCode) + { + keydown.unset(keyCode); + } virtual bool wasKeyDown(GameKeyType k) { return false; } virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } diff --git a/src/client/keys.h b/src/client/keys.h index e120a2d92..b81b571b2 100644 --- a/src/client/keys.h +++ b/src/client/keys.h @@ -43,6 +43,7 @@ public: // Other DROP, INVENTORY, + ENDERCHEST, CHAT, CMD, CMD_LOCAL, @@ -63,6 +64,7 @@ public: TOGGLE_HUD, TOGGLE_CHAT, TOGGLE_FOG, + TOGGLE_CHEAT_MENU, TOGGLE_UPDATE_CAMERA, TOGGLE_DEBUG, TOGGLE_PROFILER, @@ -71,7 +73,15 @@ public: DECREASE_VIEWING_RANGE, RANGESELECT, ZOOM, - + KILLAURA, + FREECAM, + SCAFFOLD, + SELECT_UP, + SELECT_DOWN, + SELECT_LEFT, + SELECT_RIGHT, + SELECT_CONFIRM, + QUICKTUNE_NEXT, QUICKTUNE_PREV, QUICKTUNE_INC, diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 279efafe9..c4d9b9845 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -27,6 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "map.h" #include "client.h" #include "content_cao.h" +#include "util/pointedthing.h" +#include "client/game.h" /* LocalPlayer @@ -87,7 +89,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, new_sneak_node_exists = false; } else { node = map->getNode(current_node, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) + if (!is_valid_position || nodemgr->get(node).walkable) new_sneak_node_exists = false; } @@ -113,7 +115,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, // The node to be sneaked on has to be walkable node = map->getNode(p, &is_valid_position); - if (!is_valid_position || !nodemgr->get(node).walkable) + if (!is_valid_position || ! nodemgr->get(node).walkable) continue; // And the node(s) above have to be nonwalkable bool ok = true; @@ -130,7 +132,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, } else { // legacy behaviour: check just one node node = map->getNode(p + v3s16(0, 1, 0), &is_valid_position); - ok = is_valid_position && !nodemgr->get(node).walkable; + ok = is_valid_position && ! nodemgr->get(node).walkable; } if (!ok) continue; @@ -159,7 +161,7 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, node = map->getNode(m_sneak_node + v3s16(0, 3, 0), &is_valid_position); m_sneak_ladder_detected = is_valid_position && - !nodemgr->get(node).walkable; + ! nodemgr->get(node).walkable; } } return true; @@ -168,6 +170,9 @@ bool LocalPlayer::updateSneakNode(Map *map, const v3f &position, void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, std::vector<CollisionInfo> *collision_info) { + if (m_cao && m_cao->m_waiting_for_reattach > 0) + m_cao->m_waiting_for_reattach -= dtime; + // Node at feet position, update each ClientEnvironment::step() if (!collision_info || collision_info->empty()) m_standing_node = floatToInt(m_position, BS); @@ -193,9 +198,9 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, PlayerSettings &player_settings = getPlayerSettings(); // Skip collision detection if noclip mode is used - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; - bool free_move = player_settings.free_move && fly_allowed; + bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam"); + bool noclip = (m_client->checkLocalPrivilege("noclip") && player_settings.noclip) || g_settings->getBool("freecam"); + bool free_move = (player_settings.free_move && fly_allowed) || g_settings->getBool("freecam"); if (noclip && free_move) { position += m_speed * dtime; @@ -276,6 +281,25 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, nodemgr->get(node2.getContent()).climbable) && !free_move; } + if (!is_climbing && !free_move && g_settings->getBool("spider")) { + v3s16 spider_positions[4] = { + floatToInt(position + v3f(+1.0f, +0.0f, 0.0f) * BS, BS), + floatToInt(position + v3f(-1.0f, +0.0f, 0.0f) * BS, BS), + floatToInt(position + v3f( 0.0f, +0.0f, +1.0f) * BS, BS), + floatToInt(position + v3f( 0.0f, +0.0f, -1.0f) * BS, BS), + }; + + for (v3s16 sp : spider_positions) { + bool is_valid; + MapNode node = map->getNode(sp, &is_valid); + + if (is_valid && nodemgr->get(node.getContent()).walkable) { + is_climbing = true; + break; + } + } + } + /* Collision uncertainty radius Make it a bit larger than the maximum distance of movement @@ -298,7 +322,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, collisionMoveResult result = collisionMoveSimple(env, m_client, pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); + &position, &m_speed, accel_f, NULL, true, true); bool could_sneak = control.sneak && !free_move && !in_liquid && !is_climbing && physics_override_sneak; @@ -337,7 +361,7 @@ void LocalPlayer::move(f32 dtime, Environment *env, f32 pos_max_d, Player is allowed to jump when this is true. */ bool touching_ground_was = touching_ground; - touching_ground = result.touching_ground; + touching_ground = result.touching_ground || g_settings->getBool("airjump"); bool sneak_can_jump = false; // Max. distance (X, Z) over border for sneaking determined by collision box @@ -495,8 +519,8 @@ void LocalPlayer::applyControl(float dtime, Environment *env) bool fly_allowed = m_client->checkLocalPrivilege("fly"); bool fast_allowed = m_client->checkLocalPrivilege("fast"); - bool free_move = fly_allowed && player_settings.free_move; - bool fast_move = fast_allowed && player_settings.fast_move; + bool free_move = (fly_allowed && player_settings.free_move) || g_settings->getBool("freecam"); + bool fast_move = (fast_allowed && player_settings.fast_move) || g_settings->getBool("freecam"); bool pitch_move = (free_move || in_liquid) && player_settings.pitch_move; // When aux1_descends is enabled the fast key is used to go down, so fast isn't possible bool fast_climb = fast_move && control.aux1 && !player_settings.aux1_descends; @@ -590,14 +614,14 @@ void LocalPlayer::applyControl(float dtime, Environment *env) else speedV.Y = movement_speed_walk; } - } else if (m_can_jump) { + } else if (m_can_jump || g_settings->getBool("jetpack")) { /* NOTE: The d value in move() affects jump height by raising the height at which the jump speed is kept at its starting value */ v3f speedJ = getSpeed(); - if (speedJ.Y >= -0.5f * BS) { + if (speedJ.Y >= -0.5f * BS || g_settings->getBool("jetpack")) { speedJ.Y = movement_speed_jump * physics_override_jump; setSpeed(speedJ); m_client->getEventManager()->put(new SimpleTriggerEvent(MtEvent::PLAYER_JUMP)); @@ -620,7 +644,7 @@ void LocalPlayer::applyControl(float dtime, Environment *env) if (superspeed || (is_climbing && fast_climb) || ((in_liquid || in_liquid_stable) && fast_climb)) speedH = speedH.normalize() * movement_speed_fast; - else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable) + else if (control.sneak && !free_move && !in_liquid && !in_liquid_stable && !g_settings->getBool("no_slow")) speedH = speedH.normalize() * movement_speed_crouch; else speedH = speedH.normalize() * movement_speed_walk; @@ -694,6 +718,16 @@ v3s16 LocalPlayer::getLightPosition() const return floatToInt(m_position + v3f(0.0f, BS * 1.5f, 0.0f), BS); } +v3f LocalPlayer::getSendSpeed() +{ + v3f speed = getLegitSpeed(); + + if (m_client->modsLoaded()) + speed = m_client->getScript()->get_send_speed(speed); + + return speed; +} + v3f LocalPlayer::getEyeOffset() const { float eye_height = camera_barely_in_ceiling ? m_eye_height - 0.125f : m_eye_height; @@ -702,7 +736,7 @@ v3f LocalPlayer::getEyeOffset() const ClientActiveObject *LocalPlayer::getParent() const { - return m_cao ? m_cao->getParent() : nullptr; + return (m_cao && ! g_settings->getBool("entity_speed")) ? m_cao->getParent() : nullptr; } bool LocalPlayer::isDead() const @@ -711,6 +745,18 @@ bool LocalPlayer::isDead() const return !getCAO()->isImmortal() && hp == 0; } +void LocalPlayer::tryReattach(int id) +{ + PointedThing pointed(id, v3f(0, 0, 0), v3s16(0, 0, 0), 0); + m_client->interact(INTERACT_PLACE, pointed); + m_cao->m_waiting_for_reattach = 10; +} + +bool LocalPlayer::isWaitingForReattach() const +{ + return g_settings->getBool("entity_speed") && m_cao && ! m_cao->getParent() && m_cao->m_waiting_for_reattach > 0; +} + // 3D acceleration void LocalPlayer::accelerate(const v3f &target_speed, const f32 max_increase_H, const f32 max_increase_V, const bool use_pitch) @@ -773,9 +819,9 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, PlayerSettings &player_settings = getPlayerSettings(); // Skip collision detection if noclip mode is used - bool fly_allowed = m_client->checkLocalPrivilege("fly"); - bool noclip = m_client->checkLocalPrivilege("noclip") && player_settings.noclip; - bool free_move = noclip && fly_allowed && player_settings.free_move; + bool fly_allowed = m_client->checkLocalPrivilege("fly") || g_settings->getBool("freecam"); + bool noclip = (m_client->checkLocalPrivilege("noclip") && player_settings.noclip) || g_settings->getBool("freecam"); + bool free_move = (noclip && fly_allowed && player_settings.free_move) || g_settings->getBool("freecam"); if (free_move) { position += m_speed * dtime; setPosition(position); @@ -896,7 +942,7 @@ void LocalPlayer::old_move(f32 dtime, Environment *env, f32 pos_max_d, collisionMoveResult result = collisionMoveSimple(env, m_client, pos_max_d, m_collisionbox, player_stepheight, dtime, - &position, &m_speed, accel_f); + &position, &m_speed, accel_f, NULL, true, true); // Positition was slightly changed; update standing node pos if (touching_ground) @@ -1075,7 +1121,7 @@ float LocalPlayer::getSlipFactor(Environment *env, const v3f &speedH) Map *map = &env->getMap(); const ContentFeatures &f = nodemgr->get(map->getNode(getStandingNodePos())); int slippery = 0; - if (f.walkable) + if (f.walkable && ! g_settings->getBool("antislip")) slippery = itemgroup_get(f.groups, "slippery"); if (slippery >= 1) { @@ -1144,7 +1190,7 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, // try at peak of jump, zero step height collisionMoveResult jump_result = collisionMoveSimple(env, m_client, pos_max_d, - m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f)); + m_collisionbox, 0.0f, dtime, &jump_pos, &jump_speed, v3f(0.0f), NULL, true, true); // see if we can get a little bit farther horizontally if we had // jumped @@ -1157,3 +1203,4 @@ void LocalPlayer::handleAutojump(f32 dtime, Environment *env, m_autojump_time = 0.1f; } } + diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 3d0072fc1..ebc67c4f8 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -32,6 +32,7 @@ class GenericCAO; class ClientActiveObject; class ClientEnvironment; class IGameDef; +struct ContentFeatures; struct collisionMoveResult; enum LocalPlayerAnimations @@ -132,11 +133,39 @@ public: inline void setPosition(const v3f &position) { m_position = position; + if (! m_freecam) + m_legit_position = position; m_sneak_node_exists = false; } v3f getPosition() const { return m_position; } + v3f getLegitPosition() const { return m_legit_position; } + + v3f getLegitSpeed() const { return m_freecam ? m_legit_speed : m_speed; } + + v3f getSendSpeed(); + + inline void setLegitPosition(const v3f &position) + { + if (m_freecam) + m_legit_position = position; + else + setPosition(position); + } + + inline void freecamEnable() + { + m_freecam = true; + } + + inline void freecamDisable() + { + m_freecam = false; + setPosition(m_legit_position); + setSpeed(m_legit_speed); + } + // Non-transformed eye offset getters // For accurate positions, use the Camera functions v3f getEyePosition() const { return m_position + getEyeOffset(); } @@ -161,6 +190,12 @@ public: inline Lighting& getLighting() { return m_lighting; } + void tryReattach(int id); + + bool isWaitingForReattach() const; + + bool canWalkOn(const ContentFeatures &f); + private: void accelerate(const v3f &target_speed, const f32 max_increase_H, const f32 max_increase_V, const bool use_pitch); @@ -171,7 +206,10 @@ private: const v3f &position_before_move, const v3f &speed_before_move, f32 pos_max_d); + bool m_freecam = false; v3f m_position; + v3f m_legit_position; + v3f m_legit_speed; v3s16 m_standing_node; v3s16 m_sneak_node = v3s16(32767, 32767, 32767); diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 965dd5e29..9e82fc3e4 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -88,7 +88,7 @@ void MeshMakeData::setCrack(int crack_level, v3s16 crack_pos) void MeshMakeData::setSmoothLighting(bool smooth_lighting) { - m_smooth_lighting = smooth_lighting; + m_smooth_lighting = smooth_lighting && ! g_settings->getBool("fullbright"); } /* @@ -105,6 +105,8 @@ static u8 getInteriorLight(enum LightBank bank, MapNode n, s32 increment, u8 light = n.getLight(bank, ndef); if (light > 0) light = rangelim(light + increment, 0, LIGHT_SUN); + if(g_settings->getBool("fullbright")) + return 255; return decode_light(light); } @@ -139,7 +141,8 @@ static u8 getFaceLight(enum LightBank bank, MapNode n, MapNode n2, ndef->get(n2).light_source); if(light_source > light) light = light_source; - + if(g_settings->getBool("fullbright")) + return 255; return decode_light(light); } @@ -657,6 +660,7 @@ static u8 face_contents(content_t m1, content_t m2, bool *equivalent, u8 c1 = f1.solidness; u8 c2 = f2.solidness; + if (c1 == c2) return 0; @@ -665,6 +669,7 @@ static u8 face_contents(content_t m1, content_t m2, bool *equivalent, else if (c2 == 0) c2 = f2.visual_solidness; + if (c1 == c2) { *equivalent = true; // If same solidness, liquid takes precense @@ -764,6 +769,24 @@ void getNodeTile(MapNode mn, const v3s16 &p, const v3s16 &dir, MeshMakeData *dat tile.rotation = tile.world_aligned ? 0 : dir_to_tile[tile_index + 1]; } +std::set<content_t> splitToContentT(std::string str, const NodeDefManager *ndef) +{ + str += "\n"; + std::set<content_t> dat; + std::string buf; + for (char c : str) { + if (c == ',' || c == '\n') { + if (! buf.empty()) { + dat.insert(ndef->getId(buf)); + } + buf.clear(); + } else if (c != ' ') { + buf += c; + } + } + return dat; +} + static void getTileInfo( // Input: MeshMakeData *data, @@ -775,8 +798,10 @@ static void getTileInfo( v3s16 &face_dir_corrected, u16 *lights, u8 &waving, - TileSpec &tile - ) + TileSpec &tile, + // lol more Input + bool xray, + std::set<content_t> xraySet) { VoxelManipulator &vmanip = data->m_vmanip; const NodeDefManager *ndef = data->m_client->ndef(); @@ -784,22 +809,29 @@ static void getTileInfo( const MapNode &n0 = vmanip.getNodeRefUnsafe(blockpos_nodes + p); + content_t c0 = n0.getContent(); + if (xray && xraySet.find(c0) != xraySet.end()) + c0 = CONTENT_AIR; // Don't even try to get n1 if n0 is already CONTENT_IGNORE - if (n0.getContent() == CONTENT_IGNORE) { + if (c0 == CONTENT_IGNORE) { makes_face = false; return; } const MapNode &n1 = vmanip.getNodeRefUnsafeCheckFlags(blockpos_nodes + p + face_dir); - if (n1.getContent() == CONTENT_IGNORE) { + content_t c1 = n1.getContent(); + if (xray && xraySet.find(c1) != xraySet.end()) + c1 = CONTENT_AIR; + + if (c1 == CONTENT_IGNORE) { makes_face = false; return; } // This is hackish bool equivalent = false; - u8 mf = face_contents(n0.getContent(), n1.getContent(), + u8 mf = face_contents(c0, c1, &equivalent, ndef); if (mf == 0) { @@ -855,7 +887,9 @@ static void updateFastFaceRow( v3s16 translate_dir, const v3f &&translate_dir_f, const v3s16 &&face_dir, - std::vector<FastFace> &dest) + std::vector<FastFace> &dest, + bool xray, + std::set<content_t> xraySet) { static thread_local const bool waving_liquids = g_settings->getBool("enable_shaders") && @@ -878,7 +912,7 @@ static void updateFastFaceRow( // Get info of first tile getTileInfo(data, p, face_dir, makes_face, p_corrected, face_dir_corrected, - lights, waving, tile); + lights, waving, tile, xray, xraySet); // Unroll this variable which has a significant build cost TileSpec next_tile; @@ -900,7 +934,9 @@ static void updateFastFaceRow( next_makes_face, next_p_corrected, next_face_dir_corrected, next_lights, waving, - next_tile); + next_tile, + xray, + xraySet); if (!force_not_tiling && next_makes_face == makes_face @@ -951,7 +987,7 @@ static void updateFastFaceRow( } static void updateAllFastFaceRows(MeshMakeData *data, - std::vector<FastFace> &dest) + std::vector<FastFace> &dest, bool xray, std::set<content_t> xraySet) { /* Go through every y,z and get top(y+) faces in rows of x+ @@ -963,7 +999,9 @@ static void updateAllFastFaceRows(MeshMakeData *data, v3s16(1, 0, 0), //dir v3f (1, 0, 0), v3s16(0, 1, 0), //face dir - dest); + dest, + xray, + xraySet); /* Go through every x,y and get right(x+) faces in rows of z+ @@ -975,7 +1013,9 @@ static void updateAllFastFaceRows(MeshMakeData *data, v3s16(0, 0, 1), //dir v3f (0, 0, 1), v3s16(1, 0, 0), //face dir - dest); + dest, + xray, + xraySet); /* Go through every y,z and get back(z+) faces in rows of x+ @@ -987,7 +1027,9 @@ static void updateAllFastFaceRows(MeshMakeData *data, v3s16(1, 0, 0), //dir v3f (1, 0, 0), v3s16(0, 0, 1), //face dir - dest); + dest, + xray, + xraySet); } static void applyTileColor(PreMeshBuffer &pmb) @@ -1201,6 +1243,15 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): std::vector<FastFace> fastfaces_new; fastfaces_new.reserve(512); + /* + X-Ray + */ + bool xray = g_settings->getBool("xray"); + std::set<content_t> xraySet, nodeESPSet; + if (xray) + xraySet = splitToContentT(g_settings->get("xray_nodes"), data->m_client->ndef()); + + nodeESPSet = splitToContentT(g_settings->get("node_esp_nodes"), data->m_client->ndef()); /* We are including the faces of the trailing edges of the block. @@ -1212,11 +1263,28 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset): { // 4-23ms for MAP_BLOCKSIZE=16 (NOTE: probably outdated) //TimeTaker timer2("updateAllFastFaceRows()"); - updateAllFastFaceRows(data, fastfaces_new); + updateAllFastFaceRows(data, fastfaces_new, xray, xraySet); } // End of slow part /* + NodeESP + */ + { + v3s16 blockpos_nodes = data->m_blockpos * MAP_BLOCKSIZE; + for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { + for (s16 y = 0; y < MAP_BLOCKSIZE; y++) { + for (s16 z = 0; z < MAP_BLOCKSIZE; z++) { + v3s16 pos = v3s16(x, y, z) + blockpos_nodes; + const MapNode &node = data->m_vmanip.getNodeRefUnsafeCheckFlags(pos); + if (nodeESPSet.find(node.getContent()) != nodeESPSet.end()) + esp_nodes.insert(pos); + } + } + } + } + + /* Convert FastFaces to MeshCollector */ diff --git a/src/client/mapblock_mesh.h b/src/client/mapblock_mesh.h index 169b3a8c1..8133627b1 100644 --- a/src/client/mapblock_mesh.h +++ b/src/client/mapblock_mesh.h @@ -231,6 +231,8 @@ public: return this->m_transparent_buffers; } + std::set<v3s16> esp_nodes; + private: struct AnimationInfo { int frame; // last animation frame diff --git a/src/client/mesh_generator_thread.h b/src/client/mesh_generator_thread.h index 1b734bc06..2ef695954 100644 --- a/src/client/mesh_generator_thread.h +++ b/src/client/mesh_generator_thread.h @@ -125,6 +125,6 @@ private: // TODO: Add callback to update these when g_settings changes int m_generation_interval; -protected: +public: virtual void doUpdate(); }; diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 320621d91..9bb9d14e0 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -581,8 +581,8 @@ void Minimap::drawMinimap() const u32 size = 0.25 * screensize.Y; drawMinimap(core::rect<s32>( - screensize.X - size - 10, 10, - screensize.X - 10, size + 10)); + screensize.X - size * 2 - 10, 10, + screensize.X - size - 10, size + 10)); } void Minimap::drawMinimap(core::rect<s32> rect) { diff --git a/src/client/render/core.cpp b/src/client/render/core.cpp index 55cc4e490..9927e2589 100644 --- a/src/client/render/core.cpp +++ b/src/client/render/core.cpp @@ -18,12 +18,16 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <iostream> #include "core.h" #include "client/camera.h" #include "client/client.h" #include "client/clientmap.h" #include "client/hud.h" #include "client/minimap.h" +#include "client/content_cao.h" +#include "mapblock.h" +#include "mapsector.h" #include "client/shadows/dynamicshadowsrender.h" RenderingCore::RenderingCore(IrrlichtDevice *_device, Client *_client, Hud *_hud) @@ -75,6 +79,16 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min show_minimap = _show_minimap; draw_wield_tool = _draw_wield_tool; draw_crosshair = _draw_crosshair; + draw_entity_esp = g_settings->getBool("enable_entity_esp"); + draw_entity_tracers = g_settings->getBool("enable_entity_tracers"); + draw_player_esp = g_settings->getBool("enable_player_esp"); + draw_player_tracers = g_settings->getBool("enable_player_tracers"); + draw_node_esp = g_settings->getBool("enable_node_esp"); + draw_node_tracers = g_settings->getBool("enable_node_tracers"); + v3f entity_color = g_settings->getV3F("entity_esp_color"); + v3f player_color = g_settings->getV3F("player_esp_color"); + entity_esp_color = video::SColor(255, entity_color.X, entity_color.Y, entity_color.Z); + player_esp_color = video::SColor(255, player_color.X, player_color.Y, player_color.Z); if (shadow_renderer) { // This is necessary to render shadows for animations correctly @@ -86,6 +100,79 @@ void RenderingCore::draw(video::SColor _skycolor, bool _show_hud, bool _show_min drawAll(); } +void RenderingCore::drawTracersAndESP() +{ + ClientEnvironment &env = client->getEnv(); + Camera *camera = client->getCamera(); + + v3f camera_offset = intToFloat(camera->getOffset(), BS); + + v3f eye_pos = (camera->getPosition() + camera->getDirection() - camera_offset); + + video::SMaterial material, oldmaterial; + oldmaterial = driver->getMaterial2D(); + material.setFlag(video::EMF_LIGHTING, false); + material.setFlag(video::EMF_BILINEAR_FILTER, false); + material.setFlag(video::EMF_ZBUFFER, false); + material.setFlag(video::EMF_ZWRITE_ENABLE, false); + driver->setMaterial(material); + + if (draw_entity_esp || draw_entity_tracers || draw_player_esp || draw_player_tracers) { + auto allObjects = env.getAllActiveObjects(); + for (auto &it : allObjects) { + ClientActiveObject *cao = it.second; + if (cao->isLocalPlayer() || cao->getParent()) + continue; + GenericCAO *obj = dynamic_cast<GenericCAO *>(cao); + if (! obj) + continue; + bool is_player = obj->isPlayer(); + bool draw_esp = is_player ? draw_player_esp : draw_entity_esp; + bool draw_tracers = is_player ? draw_player_tracers : draw_entity_tracers; + video::SColor color = is_player ? player_esp_color : entity_esp_color; + if (! (draw_esp || draw_tracers)) + continue; + aabb3f box; + if (! obj->getSelectionBox(&box)) + continue; + v3f pos = obj->getPosition() - camera_offset; + box.MinEdge += pos; + box.MaxEdge += pos; + if (draw_esp) + driver->draw3DBox(box, color); + if (draw_tracers) + driver->draw3DLine(eye_pos, box.getCenter(), color); + } + } + if (draw_node_esp || draw_node_tracers) { + Map &map = env.getMap(); + std::vector<v3s16> positions; + map.listAllLoadedBlocks(positions); + for (v3s16 blockp : positions) { + MapBlock *block = map.getBlockNoCreate(blockp); + if (! block->mesh) + continue; + for (v3s16 p : block->mesh->esp_nodes) { + v3f pos = intToFloat(p, BS) - camera_offset; + MapNode node = map.getNode(p); + std::vector<aabb3f> boxes; + node.getSelectionBoxes(client->getNodeDefManager(), &boxes, node.getNeighbors(p, &map)); + video::SColor color = client->getNodeDefManager()->get(node).minimap_color; + for (aabb3f box : boxes) { + box.MinEdge += pos; + box.MaxEdge += pos; + if (draw_node_esp) + driver->draw3DBox(box, color); + if (draw_node_tracers) + driver->draw3DLine(eye_pos, box.getCenter(), color); + } + } + } + } + + driver->setMaterial(oldmaterial); +} + void RenderingCore::draw3D() { smgr->drawAll(); @@ -97,6 +184,8 @@ void RenderingCore::draw3D() return; hud->drawBlockBounds(); hud->drawSelectionMesh(); + if (draw_entity_esp || draw_entity_tracers || draw_player_esp || draw_player_tracers || draw_node_esp || draw_node_tracers) + drawTracersAndESP(); if (draw_wield_tool) camera->drawWieldedTool(); } diff --git a/src/client/render/core.h b/src/client/render/core.h index cabfbbfad..0864f25bd 100644 --- a/src/client/render/core.h +++ b/src/client/render/core.h @@ -37,6 +37,14 @@ protected: bool show_minimap; bool draw_wield_tool; bool draw_crosshair; + bool draw_entity_esp; + bool draw_entity_tracers; + bool draw_player_esp; + bool draw_player_tracers; + bool draw_node_esp; + bool draw_node_tracers; + video::SColor entity_esp_color; + video::SColor player_esp_color; IrrlichtDevice *device; video::IVideoDriver *driver; @@ -57,6 +65,7 @@ protected: virtual void beforeDraw() {} virtual void drawAll() = 0; + void drawTracersAndESP(); void draw3D(); void drawHUD(); void drawPostFx(); diff --git a/src/client/renderingengine.cpp b/src/client/renderingengine.cpp index 455d5e538..6ebcc784d 100644 --- a/src/client/renderingengine.cpp +++ b/src/client/renderingengine.cpp @@ -303,15 +303,14 @@ bool RenderingEngine::setWindowIcon() #if defined(XORG_USED) #if RUN_IN_PLACE return setXorgWindowIconFromPath( - porting::path_share + "/misc/" PROJECT_NAME "-xorg-icon-128.png"); + porting::path_share + "/misc/dragonfire-xorg-icon-128.png"); #else // We have semi-support for reading in-place data if we are // compiled with RUN_IN_PLACE. Don't break with this and // also try the path_share location. return setXorgWindowIconFromPath( - ICON_DIR "/hicolor/128x128/apps/" PROJECT_NAME ".png") || - setXorgWindowIconFromPath(porting::path_share + "/misc/" PROJECT_NAME - "-xorg-icon-128.png"); + ICON_DIR "/hicolor/128x128/apps/dragonfire.png") || + setXorgWindowIconFromPath(porting::path_share + "/misc/dragonfire-xorg-icon-128.png"); #endif #elif defined(_WIN32) HWND hWnd; // Window handle |