X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fclient%2Fgame.cpp;h=edd0039e9240c259f7254b911164bf94dc6842fa;hb=d6be6682ec69706b765ab51863bcd5805a7cbd7d;hp=b092b95e2dd6613ff38f0f76085136ec4c30a35c;hpb=16e5b39e1dbe503684effb11f4b87a641c3e43e6;p=minetest.git diff --git a/src/client/game.cpp b/src/client/game.cpp index b092b95e2..edd0039e9 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -43,7 +43,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "gameparams.h" #include "gettext.h" #include "gui/guiChatConsole.h" -#include "gui/guiConfirmRegistration.h" #include "gui/guiFormSpecMenu.h" #include "gui/guiKeyChangeMenu.h" #include "gui/guiPasswordChange.h" @@ -72,6 +71,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "version.h" #include "script/scripting_client.h" #include "hud.h" +#include "clientdynamicinfo.h" #if USE_SOUND #include "client/sound_openal.h" @@ -113,7 +113,7 @@ struct TextDestPlayerInventory : public TextDest TextDestPlayerInventory(Client *client) { m_client = client; - m_formname = ""; + m_formname.clear(); } TextDestPlayerInventory(Client *client, const std::string &formname) { @@ -240,7 +240,7 @@ class PlayerInventoryFormSource: public IFormSource Client *m_client; }; -class NodeDugEvent: public MtEvent +class NodeDugEvent : public MtEvent { public: v3s16 p; @@ -250,16 +250,14 @@ class NodeDugEvent: public MtEvent p(p), n(n) {} - MtEvent::Type getType() const - { - return MtEvent::NODE_DUG; - } + Type getType() const { return NODE_DUG; } }; class SoundMaker { ISoundManager *m_sound; const NodeDefManager *m_ndef; + public: bool makes_footstep_sound; float m_player_step_timer; @@ -267,6 +265,8 @@ class SoundMaker SimpleSoundSpec m_player_step_sound; SimpleSoundSpec m_player_leftpunch_sound; + // Second sound made on left punch, currently used for item 'use' sound + SimpleSoundSpec m_player_leftpunch_sound2; SimpleSoundSpec m_player_rightpunch_sound; SoundMaker(ISoundManager *sound, const NodeDefManager *ndef): @@ -283,7 +283,7 @@ class SoundMaker 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); + m_sound->playSound(m_player_step_sound); } } @@ -291,7 +291,7 @@ class SoundMaker { if (m_player_jump_timer <= 0.0f) { m_player_jump_timer = 0.2f; - m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f), false); + m_sound->playSound(SimpleSoundSpec("player_jump", 0.5f)); } } @@ -316,32 +316,33 @@ class SoundMaker static void cameraPunchLeft(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); + sm->m_sound->playSound(sm->m_player_leftpunch_sound); + sm->m_sound->playSound(sm->m_player_leftpunch_sound2); } static void cameraPunchRight(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); + sm->m_sound->playSound(sm->m_player_rightpunch_sound); } 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); + sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug); } static void playerDamage(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); + sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5)); } static void playerFallingDamage(MtEvent *e, void *data) { SoundMaker *sm = (SoundMaker *)data; - sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); + sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5)); } void registerReceiver(MtEventManager *mgr) @@ -406,6 +407,7 @@ typedef s32 SamplerLayer_t; class GameGlobalShaderConstantSetter : public IShaderConstantSetter { Sky *m_sky; + Client *m_client; bool *m_force_fog_off; f32 *m_fog_range; bool m_fog_enabled; @@ -413,6 +415,8 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_fog_distance; CachedVertexShaderSetting m_animation_timer_vertex; CachedPixelShaderSetting m_animation_timer_pixel; + CachedVertexShaderSetting m_animation_timer_delta_vertex; + CachedPixelShaderSetting m_animation_timer_delta_pixel; CachedPixelShaderSetting m_day_light; CachedPixelShaderSetting m_star_color; CachedPixelShaderSetting m_eye_position_pixel; @@ -420,15 +424,36 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter CachedPixelShaderSetting m_minimap_yaw; CachedPixelShaderSetting m_camera_offset_pixel; CachedPixelShaderSetting m_camera_offset_vertex; - CachedPixelShaderSetting m_base_texture; - CachedPixelShaderSetting m_normal_texture; - Client *m_client; + CachedPixelShaderSetting m_texture0; + CachedPixelShaderSetting m_texture1; + CachedPixelShaderSetting m_texture2; + CachedPixelShaderSetting m_texture3; + CachedPixelShaderSetting m_texel_size0; + std::array m_texel_size0_values; + CachedStructPixelShaderSetting m_exposure_params_pixel; + float m_user_exposure_compensation; + bool m_bloom_enabled; + CachedPixelShaderSetting m_bloom_intensity_pixel; + float m_bloom_intensity; + CachedPixelShaderSetting m_bloom_strength_pixel; + float m_bloom_strength; + CachedPixelShaderSetting m_bloom_radius_pixel; + float m_bloom_radius; + CachedPixelShaderSetting m_saturation_pixel; public: void onSettingsChange(const std::string &name) { if (name == "enable_fog") m_fog_enabled = g_settings->getBool("enable_fog"); + if (name == "exposure_compensation") + m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f); + if (name == "bloom_intensity") + m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); + if (name == "bloom_strength_factor") + m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f); + if (name == "bloom_radius") + m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f); } static void settingsCallback(const std::string &name, void *userdata) @@ -441,12 +466,15 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, f32 *fog_range, Client *client) : m_sky(sky), + m_client(client), 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_animation_timer_delta_vertex("animationTimerDelta"), + m_animation_timer_delta_pixel("animationTimerDelta"), m_day_light("dayLight"), m_star_color("starColor"), m_eye_position_pixel("eyePosition"), @@ -454,12 +482,33 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter 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) + m_texture0("texture0"), + m_texture1("texture1"), + m_texture2("texture2"), + m_texture3("texture3"), + m_texel_size0("texelSize0"), + m_exposure_params_pixel("exposureParams", + std::array { + "luminanceMin", "luminanceMax", "exposureCorrection", + "speedDarkBright", "speedBrightDark", "centerWeightPower", "compensationFactor" + }), + m_bloom_intensity_pixel("bloomIntensity"), + m_bloom_strength_pixel("bloomStrength"), + m_bloom_radius_pixel("bloomRadius"), + m_saturation_pixel("saturation") { g_settings->registerChangedCallback("enable_fog", settingsCallback, this); + g_settings->registerChangedCallback("exposure_compensation", settingsCallback, this); + g_settings->registerChangedCallback("bloom_intensity", settingsCallback, this); + g_settings->registerChangedCallback("bloom_strength_factor", settingsCallback, this); + g_settings->registerChangedCallback("bloom_radius", settingsCallback, this); + g_settings->registerChangedCallback("saturation", settingsCallback, this); m_fog_enabled = g_settings->getBool("enable_fog"); + m_user_exposure_compensation = g_settings->getFloat("exposure_compensation", -1.0f, 1.0f); + m_bloom_enabled = g_settings->getBool("enable_bloom"); + m_bloom_intensity = g_settings->getFloat("bloom_intensity", 0.01f, 1.0f); + m_bloom_strength = RenderingEngine::BASE_BLOOM_STRENGTH * g_settings->getFloat("bloom_strength_factor", 0.1f, 10.0f); + m_bloom_radius = g_settings->getFloat("bloom_radius", 0.1f, 8.0f); } ~GameGlobalShaderConstantSetter() @@ -501,11 +550,15 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter 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; + u32 animation_timer = m_client->getEnv().getFrameTime() % 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 animation_timer_delta_f = (float)m_client->getEnv().getFrameTimeDelta() / 100000.f; + m_animation_timer_delta_vertex.set(&animation_timer_delta_f, services); + m_animation_timer_delta_pixel.set(&animation_timer_delta_f, services); + float eye_position_array[3]; v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition(); epos.getAs3Values(eye_position_array); @@ -525,9 +578,51 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter 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); + SamplerLayer_t tex_id; + tex_id = 0; + m_texture0.set(&tex_id, services); + tex_id = 1; + m_texture1.set(&tex_id, services); + tex_id = 2; + m_texture2.set(&tex_id, services); + tex_id = 3; + m_texture3.set(&tex_id, services); + + m_texel_size0.set(m_texel_size0_values.data(), services); + + const AutoExposure &exposure_params = m_client->getEnv().getLocalPlayer()->getLighting().exposure; + std::array exposure_buffer = { + std::pow(2.0f, exposure_params.luminance_min), + std::pow(2.0f, exposure_params.luminance_max), + exposure_params.exposure_correction, + exposure_params.speed_dark_bright, + exposure_params.speed_bright_dark, + exposure_params.center_weight_power, + powf(2.f, m_user_exposure_compensation) + }; + m_exposure_params_pixel.set(exposure_buffer.data(), services); + + if (m_bloom_enabled) { + m_bloom_intensity_pixel.set(&m_bloom_intensity, services); + m_bloom_radius_pixel.set(&m_bloom_radius, services); + m_bloom_strength_pixel.set(&m_bloom_strength, services); + } + float saturation = m_client->getEnv().getLocalPlayer()->getLighting().saturation; + m_saturation_pixel.set(&saturation, services); + } + + void onSetMaterial(const video::SMaterial &material) + { + video::ITexture *texture = material.getTexture(0); + if (texture) { + core::dimension2du size = texture->getSize(); + m_texel_size0_values[0] = 1.f / size.Width; + m_texel_size0_values[1] = 1.f / size.Height; + } + else { + m_texel_size0_values[0] = 0.f; + m_texel_size0_values[1] = 0.f; + } } }; @@ -566,7 +661,7 @@ class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactor } }; -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI #define SIZE_TAG "size[11,5.5]" #else #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop @@ -575,10 +670,19 @@ class GameGlobalShaderConstantSetterFactory : public IShaderConstantSetterFactor /**************************************************************************** ****************************************************************************/ -const float object_hit_delay = 0.2; +const static float object_hit_delay = 0.2; struct FpsControl { - u32 last_time, busy_time, sleep_time; + 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; }; @@ -606,9 +710,13 @@ struct GameRunData { float time_from_last_punch; ClientActiveObject *selected_object; - float jump_timer; + float jump_timer_up; // from key up until key down + float jump_timer_down; // since last key down + float jump_timer_down_before; // from key down until key down again + float damage_flash; float update_draw_list_timer; + float touch_blocks_timer; f32 fog_range; @@ -642,6 +750,7 @@ class Game { bool startup(bool *kill, InputHandler *input, + RenderingEngine *rendering_engine, const GameStartData &game_params, std::string &error_message, bool *reconnect, @@ -652,8 +761,6 @@ class Game { protected: - void extendedResourceCleanup(); - // Basic initialisation bool init(const std::string &map_dir, const std::string &address, u16 port, const SubgameSpec &gamespec); @@ -677,6 +784,7 @@ class Game { 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); @@ -694,6 +802,7 @@ class Game { void toggleFast(); void toggleNoClip(); void toggleCinematic(); + void toggleBlockBounds(); void toggleAutoforward(); void toggleMinimap(bool shift_pressed); @@ -709,11 +818,11 @@ class Game { void updateCameraDirection(CameraOrientation *cam, float dtime); void updateCameraOrientation(CameraOrientation *cam, float dtime); void updatePlayerControl(const CameraOrientation &cam); - void step(f32 *dtime); + void step(f32 dtime); void processClientEvents(CameraOrientation *cam); - void updateCamera(u32 busy_time, f32 dtime); + void updateCamera(f32 dtime); void updateSound(f32 dtime); - void processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug); + 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. @@ -739,10 +848,9 @@ class Game { 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 limitFps(FpsControl *fps_timings, f32 *dtime); - void showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds = true); @@ -802,20 +910,24 @@ class Game { CameraOrientation *cam); void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); - void updateChat(f32 dtime, const v2u32 &screensize); + void updateChat(f32 dtime); bool nodePlacement(const ItemDefinition &selected_def, const ItemStack &selected_item, - const v3s16 &nodepos, const v3s16 &neighbourpos, const PointedThing &pointed, + const v3s16 &nodepos, const v3s16 &neighborpos, const PointedThing &pointed, const NodeMetadata *meta); static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX]; f32 getSensitivityScaleFactor() const; + ClientDynamicInfo getCurrentDynamicInfo() const; InputHandler *input = nullptr; Client *client = nullptr; Server *server = nullptr; + ClientDynamicInfo client_display_info{}; + float dynamic_info_send_timer = 0; + IWritableTextureSource *texture_src = nullptr; IWritableShaderSource *shader_src = nullptr; @@ -833,7 +945,6 @@ class Game { EventManager *eventmgr = nullptr; QuicktuneShortcutter *quicktune = nullptr; - bool registration_confirmation_shown = false; std::unique_ptr m_game_ui; GUIChatConsole *gui_chat_console = nullptr; // Free using ->Drop() @@ -855,6 +966,7 @@ class Game { these items (e.g. device) */ IrrlichtDevice *device; + RenderingEngine *m_rendering_engine; video::IVideoDriver *driver; scene::ISceneManager *smgr; bool *kill; @@ -897,9 +1009,18 @@ class Game { bool m_does_lost_focus_pause_game = false; - int m_reset_HW_buffer_counter = 0; -#ifdef __ANDROID__ + // if true, (almost) the whole game is paused + // this happens in pause menu in singleplayer + bool m_is_paused = false; + +#ifdef HAVE_TOUCHSCREENGUI bool m_cache_hold_aux1; + bool m_touch_use_crosshair; + inline bool isNoCrosshairAllowed() { + return !m_touch_use_crosshair && camera->getCameraMode() == CAMERA_MODE_FIRST; + } +#endif +#ifdef __ANDROID__ bool m_android_chat_open; #endif }; @@ -937,7 +1058,7 @@ Game::Game() : readSettings(); -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI m_cache_hold_aux1 = false; // This is initialised properly later #endif @@ -968,7 +1089,7 @@ Game::~Game() delete itemdef_manager; delete draw_control; - extendedResourceCleanup(); + clearTextureNameCache(); g_settings->deregisterChangedCallback("doubletap_jump", &settingChangedCallback, this); @@ -996,6 +1117,7 @@ Game::~Game() bool Game::startup(bool *kill, InputHandler *input, + RenderingEngine *rendering_engine, const GameStartData &start_data, std::string &error_message, bool *reconnect, @@ -1003,21 +1125,21 @@ bool Game::startup(bool *kill, { // "cache" - this->device = RenderingEngine::get_raw_device(); + m_rendering_engine = rendering_engine; + device = m_rendering_engine->get_raw_device(); this->kill = kill; this->error_message = &error_message; - this->reconnect_requested = reconnect; + reconnect_requested = reconnect; this->input = input; this->chat_backend = chat_backend; - this->simple_singleplayer_mode = start_data.isSinglePlayer(); + simple_singleplayer_mode = start_data.isSinglePlayer(); input->keycache.populate(); driver = device->getVideoDriver(); - smgr = RenderingEngine::get_scene_manager(); + smgr = m_rendering_engine->get_scene_manager(); - RenderingEngine::get_scene_manager()->getParameters()-> - setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); + smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true); // Reinit runData runData = GameRunData(); @@ -1028,6 +1150,10 @@ bool Game::startup(bool *kill, m_invert_mouse = g_settings->getBool("invert_mouse"); m_first_loop_after_window_activation = true; +#ifdef HAVE_TOUCHSCREENGUI + m_touch_use_crosshair = g_settings->getBool("touch_use_crosshair"); +#endif + g_client_translations->clear(); // address can change if simple_singleplayer_mode @@ -1038,7 +1164,7 @@ bool Game::startup(bool *kill, if (!createClient(start_data)) return false; - RenderingEngine::initialize(client, hud); + m_rendering_engine->initialize(client, hud); return true; } @@ -1047,50 +1173,63 @@ bool Game::startup(bool *kill, void Game::run() { ProfilerGraph graph; - RunStats stats = { 0 }; - CameraOrientation cam_view_target = { 0 }; - CameraOrientation cam_view = { 0 }; - FpsControl draw_times = { 0 }; + RunStats stats = {}; + CameraOrientation cam_view_target = {}; + CameraOrientation cam_view = {}; + FpsControl draw_times; f32 dtime; // in seconds /* Clear the profiler */ Profiler::GraphValues dummyvalues; g_profiler->graphGet(dummyvalues); - draw_times.last_time = RenderingEngine::get_timer_time(); + draw_times.reset(); set_light_table(g_settings->getFloat("display_gamma")); -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI m_cache_hold_aux1 = g_settings->getBool("fast_move") && client->checkPrivilege("fast"); #endif - irr::core::dimension2d previous_screen_size(g_settings->getU16("screen_w"), + v2u32 previous_screen_size(g_settings->getU16("screen_w"), g_settings->getU16("screen_h")); - while (RenderingEngine::run() + while (m_rendering_engine->run() && !(*kill || g_gamecallback->shutdown_requested || (server && server->isShutdownRequested()))) { - const irr::core::dimension2d ¤t_screen_size = - RenderingEngine::get_video_driver()->getScreenSize(); + // Calculate dtime = + // m_rendering_engine->run() from this iteration + // + Sleep time until the wanted FPS are reached + draw_times.limit(device, &dtime); + + const auto current_dynamic_info = getCurrentDynamicInfo(); + if (!current_dynamic_info.equal(client_display_info)) { + client_display_info = current_dynamic_info; + dynamic_info_send_timer = 0.2f; + } + + if (dynamic_info_send_timer > 0) { + dynamic_info_send_timer -= dtime; + if (dynamic_info_send_timer <= 0) { + client->sendUpdateClientInfo(current_dynamic_info); + } + } + + const auto ¤t_screen_size = current_dynamic_info.render_target_size; + // Verify if window size has changed and save it if it's the case // Ensure evaluating settings->getBool after verifying screensize // First condition is cheaper if (previous_screen_size != current_screen_size && current_screen_size != irr::core::dimension2d(0,0) && g_settings->getBool("autosave_screensize")) { - g_settings->setU16("screen_w", current_screen_size.Width); - g_settings->setU16("screen_h", current_screen_size.Height); + g_settings->setU16("screen_w", current_screen_size.X); + g_settings->setU16("screen_h", current_screen_size.Y); previous_screen_size = current_screen_size; } - // Calculate dtime = - // RenderingEngine::run() from this iteration - // + Sleep time until the wanted FPS are reached - limitFps(&draw_times, &dtime); - // Prepare render data for next iteration updateStats(&stats, draw_times, dtime); @@ -1104,7 +1243,6 @@ void Game::run() processQueues(); m_game_ui->clearInfoText(); - hud->resizeHotbar(); updateProfilers(stats, draw_times, dtime); processUserInput(dtime); @@ -1115,12 +1253,26 @@ void Game::run() cam_view.camera_pitch += (cam_view_target.camera_pitch - cam_view.camera_pitch) * m_cache_cam_smoothing; updatePlayerControl(cam_view); - step(&dtime); + + { + bool was_paused = m_is_paused; + m_is_paused = simple_singleplayer_mode && g_menumgr.pausesGame(); + if (m_is_paused) + dtime = 0.0f; + + if (!was_paused && m_is_paused) + pauseAnimation(); + else if (was_paused && !m_is_paused) + resumeAnimation(); + } + + if (!m_is_paused) + step(dtime); processClientEvents(&cam_view_target); - updateCamera(draw_times.busy_time, dtime); + updateDebugState(); + updateCamera(dtime); updateSound(dtime); - processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud, - m_game_ui->m_flags.show_debug); + processPlayerInteraction(dtime, m_game_ui->m_flags.show_hud); updateFrame(&graph, &stats, dtime, cam_view); updateProfilerGraphs(&graph); @@ -1136,12 +1288,8 @@ void Game::run() void Game::shutdown() { - RenderingEngine::finalize(); -#if IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR <= 8 - if (g_settings->get("3d_mode") == "pageflip") { - driver->setRenderTarget(irr::video::ERT_STEREO_BOTH_BUFFERS); - } -#endif + m_rendering_engine->finalize(); + auto formspec = m_game_ui->getFormspecGUI(); if (formspec) formspec->quitMenu(); @@ -1274,9 +1422,8 @@ bool Game::createSingleplayerServer(const std::string &map_dir, } if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = "Unable to listen on " + - bind_addr.serializeString() + - " because IPv6 is disabled"; + *error_message = fmtgettext("Unable to listen on %s because IPv6 is disabled", + bind_addr.serializeString().c_str()); errorstream << *error_message << std::endl; return false; } @@ -1292,7 +1439,7 @@ bool Game::createClient(const GameStartData &start_data) { showOverlayMessage(N_("Creating client..."), 0, 10); - draw_control = new MapDrawControl; + draw_control = new MapDrawControl(); if (!draw_control) return false; @@ -1309,7 +1456,7 @@ bool Game::createClient(const GameStartData &start_data) if (!could_connect) { if (error_message->empty() && !connect_aborted) { // Should not happen if error messages are set properly - *error_message = "Connection failed for unknown reason"; + *error_message = gettext("Connection failed for unknown reason"); errorstream << *error_message << std::endl; } return false; @@ -1318,7 +1465,7 @@ bool Game::createClient(const GameStartData &start_data) if (!getServerContent(&connect_aborted)) { if (error_message->empty() && !connect_aborted) { // Should not happen if error messages are set properly - *error_message = "Connection failed for unknown reason"; + *error_message = gettext("Connection failed for unknown reason"); errorstream << *error_message << std::endl; } return false; @@ -1333,10 +1480,15 @@ bool Game::createClient(const GameStartData &start_data) /* Camera */ - camera = new Camera(*draw_control, client); - if (!camera->successfullyCreated(*error_message)) - return false; + camera = new Camera(*draw_control, client, m_rendering_engine); + if (client->modsLoaded()) + client->getScript()->on_camera_ready(camera); client->setCamera(camera); +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) { + g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed()); + } +#endif /* Clouds */ @@ -1345,7 +1497,7 @@ bool Game::createClient(const GameStartData &start_data) /* Skybox */ - sky = new Sky(-1, texture_src, shader_src); + sky = new Sky(-1, m_rendering_engine, texture_src, shader_src); scsf->setSky(sky); skybox = NULL; // This is used/set later on in the main run loop @@ -1376,7 +1528,7 @@ bool Game::createClient(const GameStartData &start_data) str += L" ["; str += text; str += L"]"; - delete text; + delete[] text; } str += L" ["; str += driver->getName(); @@ -1388,7 +1540,7 @@ bool Game::createClient(const GameStartData &start_data) player->hurt_tilt_timer = 0; player->hurt_tilt_strength = 0; - hud = new Hud(guienv, client, player, &player->inventory); + hud = new Hud(client, player, &player->inventory); mapper = client->getMinimap(); @@ -1437,7 +1589,6 @@ bool Game::connectToServer(const GameStartData &start_data, connect_address.Resolve(start_data.address.c_str()); if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY - //connect_address.Resolve("localhost"); if (connect_address.isIPv6()) { IPv6AddressBytes addr_bytes; addr_bytes.bytes[15] = 1; @@ -1448,29 +1599,36 @@ bool Game::connectToServer(const GameStartData &start_data, local_server_mode = true; } } catch (ResolveError &e) { - *error_message = std::string("Couldn't resolve address: ") + e.what(); + *error_message = fmtgettext("Couldn't resolve address: %s", e.what()); + errorstream << *error_message << std::endl; return false; } if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) { - *error_message = "Unable to connect to " + - connect_address.serializeString() + - " because IPv6 is disabled"; + *error_message = fmtgettext("Unable to connect to %s because IPv6 is disabled", connect_address.serializeString().c_str()); errorstream << *error_message << std::endl; return false; } - client = new Client(start_data.name.c_str(), - start_data.password, start_data.address, - *draw_control, texture_src, shader_src, - itemdef_manager, nodedef_manager, sound, eventmgr, - connect_address.isIPv6(), m_game_ui.get()); + try { + client = new Client(start_data.name.c_str(), + start_data.password, start_data.address, + *draw_control, texture_src, shader_src, + itemdef_manager, nodedef_manager, sound, eventmgr, + m_rendering_engine, connect_address.isIPv6(), m_game_ui.get(), + start_data.allow_login_or_register); + client->migrateModStorage(); + } catch (const BaseException &e) { + *error_message = fmtgettext("Error creating client: %s", e.what()); + errorstream << *error_message << std::endl; + return false; + } client->m_simple_singleplayer_mode = simple_singleplayer_mode; infostream << "Connecting to server at "; - connect_address.print(&infostream); + connect_address.print(infostream); infostream << std::endl; client->connect(connect_address, @@ -1483,15 +1641,15 @@ bool Game::connectToServer(const GameStartData &start_data, try { input->clear(); - FpsControl fps_control = { 0 }; + FpsControl fps_control; f32 dtime; f32 wait_time = 0; // in seconds - fps_control.last_time = RenderingEngine::get_timer_time(); + fps_control.reset(); - while (RenderingEngine::run()) { + while (m_rendering_engine->run()) { - limitFps(&fps_control, &dtime); + fps_control.limit(device, &dtime); // Update client and server client->step(dtime); @@ -1510,8 +1668,7 @@ bool Game::connectToServer(const GameStartData &start_data, break; if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); + *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str()); *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; break; @@ -1523,28 +1680,16 @@ bool Game::connectToServer(const GameStartData &start_data, break; } - if (client->m_is_registration_confirmation_state) { - if (registration_confirmation_shown) { - // Keep drawing the GUI - RenderingEngine::draw_menu_scene(guienv, dtime, true); - } else { - registration_confirmation_shown = true; - (new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1, - &g_menumgr, client, start_data.name, start_data.password, - connection_aborted, texture_src))->drop(); - } - } else { - wait_time += dtime; - // Only time out if we aren't waiting for the server we started - if (!start_data.address.empty() && wait_time > 10) { - *error_message = "Connection timed out."; - errorstream << *error_message << std::endl; - break; - } - - // Update status - showOverlayMessage(N_("Connecting to server..."), dtime, 20); + wait_time += dtime; + // Only time out if we aren't waiting for the server we started + if (!start_data.address.empty() && wait_time > 10) { + *error_message = gettext("Connection timed out."); + errorstream << *error_message << std::endl; + break; } + + // Update status + showOverlayMessage(N_("Connecting to server..."), dtime, 20); } } catch (con::PeerNotFoundException &e) { // TODO: Should something be done here? At least an info/error @@ -1559,14 +1704,14 @@ bool Game::getServerContent(bool *aborted) { input->clear(); - FpsControl fps_control = { 0 }; + FpsControl fps_control; f32 dtime; // in seconds - fps_control.last_time = RenderingEngine::get_timer_time(); + fps_control.reset(); - while (RenderingEngine::run()) { + while (m_rendering_engine->run()) { - limitFps(&fps_control, &dtime); + fps_control.limit(device, &dtime); // Update client and server client->step(dtime); @@ -1585,7 +1730,7 @@ bool Game::getServerContent(bool *aborted) return false; if (client->getState() < LC_Init) { - *error_message = "Client disconnected"; + *error_message = gettext("Client disconnected"); errorstream << *error_message << std::endl; return false; } @@ -1602,17 +1747,17 @@ bool Game::getServerContent(bool *aborted) if (!client->itemdefReceived()) { const wchar_t *text = wgettext("Item definitions..."); progress = 25; - RenderingEngine::draw_load_screen(text, guienv, texture_src, + m_rendering_engine->draw_load_screen(text, guienv, texture_src, dtime, progress); delete[] text; } else if (!client->nodedefReceived()) { const wchar_t *text = wgettext("Node definitions..."); progress = 30; - RenderingEngine::draw_load_screen(text, guienv, texture_src, + m_rendering_engine->draw_load_screen(text, guienv, texture_src, dtime, progress); delete[] text; } else { - std::stringstream message; + std::ostringstream message; std::fixed(message); message.precision(0); float receive = client->mediaReceiveProgress() * 100; @@ -1635,7 +1780,7 @@ bool Game::getServerContent(bool *aborted) } progress = 30 + client->mediaReceiveProgress() * 35 + 0.5; - RenderingEngine::draw_load_screen(utf8_to_wide(message.str()), guienv, + m_rendering_engine->draw_load_screen(utf8_to_wide(message.str()), guienv, texture_src, dtime, progress); } } @@ -1667,8 +1812,7 @@ inline void Game::updateInteractTimers(f32 dtime) inline bool Game::checkConnection() { if (client->accessDenied()) { - *error_message = "Access denied. Reason: " - + client->accessDeniedReason(); + *error_message = fmtgettext("Access denied. Reason: %s", client->accessDeniedReason().c_str()); *reconnect_requested = client->reconnectRequested(); errorstream << *error_message << std::endl; return false; @@ -1721,6 +1865,29 @@ void Game::processQueues() shader_src->processQueue(); } +void Game::updateDebugState() +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + // debug UI and wireframe + bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); + + if (m_game_ui->m_flags.show_basic_debug) { + if (!has_basic_debug) + m_game_ui->m_flags.show_basic_debug = false; + } else if (m_game_ui->m_flags.show_minimal_debug) { + if (has_basic_debug) + m_game_ui->m_flags.show_basic_debug = true; + } + if (!has_basic_debug) + hud->disableBlockBounds(); + if (!has_debug) + draw_control->show_wireframe = false; + + // noclip + draw_control->allow_noclip = m_cache_enable_noclip && client->checkPrivilege("noclip"); +} void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, f32 dtime) @@ -1745,10 +1912,10 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, } // Update update graphs - g_profiler->graphAdd("Time non-rendering [ms]", + g_profiler->graphAdd("Time non-rendering [us]", draw_times.busy_time - stats.drawtime); - g_profiler->graphAdd("Sleep [ms]", draw_times.sleep_time); + g_profiler->graphAdd("Sleep [us]", draw_times.sleep_time); g_profiler->graphAdd("FPS", 1.0f / dtime); } @@ -1781,9 +1948,9 @@ void Game::updateStats(RunStats *stats, const FpsControl &draw_times, /* Busytime average and jitter calculation */ jp = &stats->busy_time_jitter; - jp->avg = jp->avg + draw_times.busy_time * 0.02; + jp->avg = jp->avg + draw_times.getBusyMs() * 0.02; - jitter = draw_times.busy_time - jp->avg; + jitter = draw_times.getBusyMs() - jp->avg; if (jitter > jp->max) jp->max = jitter; @@ -1820,6 +1987,7 @@ void Game::processUserInput(f32 dtime) else if (g_touchscreengui) { /* on touchscreengui step may generate own input events which ain't * what we want in case we just did clear them */ + g_touchscreengui->show(); g_touchscreengui->step(dtime); } #endif @@ -1840,8 +2008,10 @@ void Game::processUserInput(f32 dtime) #endif // Increase timer for double tap of "keymap_jump" - if (m_cache_doubletap_jump && runData.jump_timer <= 0.2f) - runData.jump_timer += dtime; + if (m_cache_doubletap_jump && runData.jump_timer_up <= 0.2f) + runData.jump_timer_up += dtime; + if (m_cache_doubletap_jump && runData.jump_timer_down <= 0.4f) + runData.jump_timer_down += dtime; processKeyInput(); processItemSelection(&runData.new_playeritem); @@ -1874,7 +2044,7 @@ void Game::processKeyInput() if (client->modsLoaded()) openConsole(0.2, L"."); else - m_game_ui->showStatusText(wgettext("Client side scripting is disabled")); + m_game_ui->showTranslatedStatusText("Client side scripting is disabled"); } else if (wasKeyDown(KeyType::CONSOLE)) { openConsole(core::clamp(g_settings->getFloat("console_height"), 0.1f, 1.0f)); } else if (wasKeyDown(KeyType::FREEMOVE)) { @@ -1901,25 +2071,19 @@ void Game::processKeyInput() } } else if (wasKeyDown(KeyType::INC_VOLUME)) { if (g_settings->getBool("enable_sound")) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") + 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; + float new_volume = g_settings->getFloat("sound_volume", 0.0f, 0.9f) + 0.1f; g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); + m_game_ui->showStatusText(msg); } else { m_game_ui->showTranslatedStatusText("Sound system is disabled"); } } else if (wasKeyDown(KeyType::DEC_VOLUME)) { if (g_settings->getBool("enable_sound")) { - float new_volume = rangelim(g_settings->getFloat("sound_volume") - 0.1f, 0.0f, 1.0f); - wchar_t buf[100]; + float new_volume = g_settings->getFloat("sound_volume", 0.1f, 1.0f) - 0.1f; g_settings->setFloat("sound_volume", new_volume); - const wchar_t *str = wgettext("Volume changed to %d%%"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, myround(new_volume * 100)); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Volume changed to %d%%", myround(new_volume * 100)); + m_game_ui->showStatusText(msg); } else { m_game_ui->showTranslatedStatusText("Sound system is disabled"); } @@ -1933,13 +2097,13 @@ void Game::processKeyInput() } else if (wasKeyDown(KeyType::SCREENSHOT)) { client->makeScreenshot(); } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) { - hud->toggleBlockBounds(); + toggleBlockBounds(); } else if (wasKeyDown(KeyType::TOGGLE_HUD)) { m_game_ui->toggleHud(); } else if (wasKeyDown(KeyType::MINIMAP)) { toggleMinimap(isKeyDown(KeyType::SNEAK)); } else if (wasKeyDown(KeyType::TOGGLE_CHAT)) { - m_game_ui->toggleChat(); + m_game_ui->toggleChat(client); } else if (wasKeyDown(KeyType::TOGGLE_FOG)) { toggleFog(); } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) { @@ -1968,7 +2132,7 @@ void Game::processKeyInput() if (!isKeyDown(KeyType::JUMP) && runData.reset_jump_timer) { runData.reset_jump_timer = false; - runData.jump_timer = 0.0f; + runData.jump_timer_up = 0.0f; } if (quicktune->hasMessage()) { @@ -1983,7 +2147,6 @@ void Game::processItemSelection(u16 *new_playeritem) /* Item selection using mouse wheel */ *new_playeritem = player->getWieldIndex(); - s32 wheel = input->getMouseWheel(); u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, player->hud_hotbar_itemcount - 1); @@ -2010,6 +2173,9 @@ void Game::processItemSelection(u16 *new_playeritem) break; } } + + // Clamp selection again in case it wasn't changed but max_item was + *new_playeritem = MYMIN(*new_playeritem, max_item); } @@ -2042,15 +2208,22 @@ void Game::openInventory() InventoryLocation inventoryloc; inventoryloc.setCurrentPlayer(); - if (!client->modsLoaded() - || !client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { - TextDest *txt_dst = new TextDestPlayerInventory(client); - auto *&formspec = m_game_ui->updateFormspec(""); - GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend(), sound); + if (client->modsLoaded() && client->getScript()->on_inventory_open(fs_src->m_client->getInventory(inventoryloc))) { + delete fs_src; + return; + } - formspec->setFormSpec(fs_src->getForm(), inventoryloc); + if (fs_src->getForm().empty()) { + delete fs_src; + return; } + + TextDest *txt_dst = new TextDestPlayerInventory(client); + auto *&formspec = m_game_ui->updateFormspec(""); + GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), + &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); + + formspec->setFormSpec(fs_src->getForm(), inventoryloc); } @@ -2102,7 +2275,14 @@ void Game::toggleFreeMove() void Game::toggleFreeMoveAlt() { - if (m_cache_doubletap_jump && runData.jump_timer < 0.2f) + if (!runData.reset_jump_timer) { + runData.jump_timer_down_before = runData.jump_timer_down; + runData.jump_timer_down = 0.0f; + } + + // key down (0.2 s max.), then key up (0.2 s max.), then key down + if (m_cache_doubletap_jump && runData.jump_timer_up < 0.2f && + runData.jump_timer_down_before < 0.4f) // 0.2 + 0.2 toggleFreeMove(); runData.reset_jump_timer = true; @@ -2138,7 +2318,7 @@ void Game::toggleFast() m_game_ui->showTranslatedStatusText("Fast mode disabled"); } -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI m_cache_hold_aux1 = fast_move && has_fast_privs; #endif } @@ -2171,6 +2351,32 @@ void Game::toggleCinematic() m_game_ui->showTranslatedStatusText("Cinematic mode disabled"); } +void Game::toggleBlockBounds() +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + if (!(client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG))) { + m_game_ui->showTranslatedStatusText("Can't show block bounds (disabled by mod or game)"); + return; + } + enum Hud::BlockBoundsMode newmode = hud->toggleBlockBounds(); + switch (newmode) { + case Hud::BLOCK_BOUNDS_OFF: + m_game_ui->showTranslatedStatusText("Block bounds hidden"); + break; + case Hud::BLOCK_BOUNDS_CURRENT: + m_game_ui->showTranslatedStatusText("Block bounds shown for current block"); + break; + case Hud::BLOCK_BOUNDS_NEAR: + m_game_ui->showTranslatedStatusText("Block bounds shown for nearby blocks"); + break; + case Hud::BLOCK_BOUNDS_MAX: + m_game_ui->showTranslatedStatusText("Block bounds shown for all blocks"); + break; + default: + break; + } +} + // Autoforward by toggling continuous forward. void Game::toggleAutoforward() { @@ -2234,27 +2440,44 @@ void Game::toggleFog() void Game::toggleDebug() { - // Initial / 4x toggle: Chat only - // 1x toggle: Debug text with chat + LocalPlayer *player = client->getEnv().getLocalPlayer(); + bool has_debug = client->checkPrivilege("debug"); + bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); + // Initial: No debug info + // 1x toggle: Debug text // 2x toggle: Debug text with profiler graph - // 3x toggle: Debug text and wireframe - if (!m_game_ui->m_flags.show_debug) { - m_game_ui->m_flags.show_debug = true; + // 3x toggle: Debug text and wireframe (needs "debug" priv) + // Next toggle: Back to initial + // + // The debug text can be in 2 modes: minimal and basic. + // * Minimal: Only technical client info that not gameplay-relevant + // * Basic: Info that might give gameplay advantage, e.g. pos, angle + // Basic mode is used when player has the debug HUD flag set, + // otherwise the Minimal mode is used. + if (!m_game_ui->m_flags.show_minimal_debug) { + m_game_ui->m_flags.show_minimal_debug = true; + if (has_basic_debug) + m_game_ui->m_flags.show_basic_debug = true; m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; m_game_ui->showTranslatedStatusText("Debug info shown"); } else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { + if (has_basic_debug) + m_game_ui->m_flags.show_basic_debug = true; m_game_ui->m_flags.show_profiler_graph = true; m_game_ui->showTranslatedStatusText("Profiler graph shown"); } else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { + if (has_basic_debug) + m_game_ui->m_flags.show_basic_debug = true; m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = true; m_game_ui->showTranslatedStatusText("Wireframe shown"); } else { - m_game_ui->m_flags.show_debug = false; + m_game_ui->m_flags.show_minimal_debug = false; + m_game_ui->m_flags.show_basic_debug = false; m_game_ui->m_flags.show_profiler_graph = false; draw_control->show_wireframe = false; - if (client->checkPrivilege("debug")) { + if (has_debug) { m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden"); } else { m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden"); @@ -2278,20 +2501,13 @@ void Game::increaseViewRange() s16 range = g_settings->getS16("viewing_range"); s16 range_new = range + 10; - wchar_t buf[255]; - const wchar_t *str; if (range_new > 4000) { range_new = 4000; - str = wgettext("Viewing range is at maximum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); - + std::wstring msg = fwgettext("Viewing range is at maximum: %d", range_new); + m_game_ui->showStatusText(msg); } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range changed to %d", range_new); + m_game_ui->showStatusText(msg); } g_settings->set("viewing_range", itos(range_new)); } @@ -2302,19 +2518,13 @@ void Game::decreaseViewRange() s16 range = g_settings->getS16("viewing_range"); s16 range_new = range - 10; - wchar_t buf[255]; - const wchar_t *str; if (range_new < 20) { range_new = 20; - str = wgettext("Viewing range is at minimum: %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range is at minimum: %d", range_new); + m_game_ui->showStatusText(msg); } else { - str = wgettext("Viewing range changed to %d"); - swprintf(buf, sizeof(buf) / sizeof(wchar_t), str, range_new); - delete[] str; - m_game_ui->showStatusText(buf); + std::wstring msg = fwgettext("Viewing range changed to %d", range_new); + m_game_ui->showStatusText(msg); } g_settings->set("viewing_range", itos(range_new)); } @@ -2339,6 +2549,13 @@ void Game::checkZoomEnabled() void Game::updateCameraDirection(CameraOrientation *cam, float dtime) { +#ifndef __ANDROID__ + if (isMenuActive()) + device->getCursorControl()->setRelativeMode(false); + else + device->getCursorControl()->setRelativeMode(true); +#endif + if ((device->isWindowActive() && device->isWindowFocused() && !isMenuActive()) || input->isRandom()) { @@ -2384,6 +2601,19 @@ f32 Game::getSensitivityScaleFactor() const return tan(fov_y / 2.0f) * 1.3763818698f; } +ClientDynamicInfo Game::getCurrentDynamicInfo() const +{ + v2u32 screen_size = RenderingEngine::getWindowSize(); + f32 density = RenderingEngine::getDisplayDensity(); + f32 gui_scaling = g_settings->getFloat("gui_scaling") * density; + f32 hud_scaling = g_settings->getFloat("hud_scaling") * density; + + return { + screen_size, gui_scaling, hud_scaling, + ClientDynamicInfo::calculateMaxFSSize(screen_size) + }; +} + void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) { #ifdef HAVE_TOUCHSCREENGUI @@ -2411,7 +2641,7 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) if (m_cache_enable_joysticks) { f32 sens_scale = getSensitivityScaleFactor(); - f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime * sens_scale; + f32 c = m_cache_joystick_frustum_sensitivity * dtime * sens_scale; cam->camera_yaw -= input->joystick.getAxisWithoutDead(JA_FRUSTUM_HORIZONTAL) * c; cam->camera_pitch += input->joystick.getAxisWithoutDead(JA_FRUSTUM_VERTICAL) * c; } @@ -2422,18 +2652,16 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime) void Game::updatePlayerControl(const CameraOrientation &cam) { - //TimeTaker tt("update player control", NULL, PRECISION_NANO); + LocalPlayer *player = client->getEnv().getLocalPlayer(); - // DO NOT use the isKeyDown method for the forward, backward, left, right - // buttons, as the code that uses the controls needs to be able to - // distinguish between the two in order to know when to use joysticks. + //TimeTaker tt("update player control", NULL, PRECISION_NANO); PlayerControl control( - input->isKeyDown(KeyType::FORWARD), - input->isKeyDown(KeyType::BACKWARD), - input->isKeyDown(KeyType::LEFT), - input->isKeyDown(KeyType::RIGHT), - isKeyDown(KeyType::JUMP), + isKeyDown(KeyType::FORWARD), + isKeyDown(KeyType::BACKWARD), + isKeyDown(KeyType::LEFT), + isKeyDown(KeyType::RIGHT), + isKeyDown(KeyType::JUMP) || player->getAutojump(), isKeyDown(KeyType::AUX1), isKeyDown(KeyType::SNEAK), isKeyDown(KeyType::ZOOM), @@ -2441,73 +2669,42 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, - input->joystick.getAxisWithoutDead(JA_SIDEWARD_MOVE), - input->joystick.getAxisWithoutDead(JA_FORWARD_MOVE) + input->getMovementSpeed(), + input->getMovementDirection() ); - u32 keypress_bits = ( - ( (u32)(isKeyDown(KeyType::FORWARD) & 0x1) << 0) | - ( (u32)(isKeyDown(KeyType::BACKWARD) & 0x1) << 1) | - ( (u32)(isKeyDown(KeyType::LEFT) & 0x1) << 2) | - ( (u32)(isKeyDown(KeyType::RIGHT) & 0x1) << 3) | - ( (u32)(isKeyDown(KeyType::JUMP) & 0x1) << 4) | - ( (u32)(isKeyDown(KeyType::AUX1) & 0x1) << 5) | - ( (u32)(isKeyDown(KeyType::SNEAK) & 0x1) << 6) | - ( (u32)(isKeyDown(KeyType::DIG) & 0x1) << 7) | - ( (u32)(isKeyDown(KeyType::PLACE) & 0x1) << 8) | - ( (u32)(isKeyDown(KeyType::ZOOM) & 0x1) << 9) - ); + // autoforward if set: move at maximum speed + if (player->getPlayerSettings().continuous_forward && + client->activeObjectsReceived() && !player->isDead()) { + control.movement_speed = 1.0f; + // sideways movement only + float dx = sin(control.movement_direction); + control.movement_direction = atan2(dx, 1.0f); + } -#ifdef ANDROID - /* For Android, simulate holding down AUX1 (fast move) if the user has +#ifdef HAVE_TOUCHSCREENGUI + /* For touch, simulate holding down AUX1 (fast move) if the user has * the fast_move setting toggled on. If there is an aux1 key defined for - * Android then its meaning is inverted (i.e. holding aux1 means walk and + * touch then its meaning is inverted (i.e. holding aux1 means walk and * not fast) */ if (m_cache_hold_aux1) { control.aux1 = control.aux1 ^ true; - keypress_bits ^= ((u32)(1U << 5)); } #endif - LocalPlayer *player = client->getEnv().getLocalPlayer(); - - // autojump if set: simulate "jump" key - if (player->getAutojump()) { - control.jump = true; - keypress_bits |= 1U << 4; - } - - // autoforward if set: simulate "up" key - if (player->getPlayerSettings().continuous_forward && - client->activeObjectsReceived() && !player->isDead()) { - control.up = true; - keypress_bits |= 1U << 0; - } - client->setPlayerControl(control); - player->keyPressed = keypress_bits; //tt.stop(); } -inline void Game::step(f32 *dtime) +inline void Game::step(f32 dtime) { - bool can_be_and_is_paused = - (simple_singleplayer_mode && g_menumgr.pausesGame()); - - if (can_be_and_is_paused) { // This is for a singleplayer server - *dtime = 0; // No time passes - } else { - if (simple_singleplayer_mode && !paused_animated_nodes.empty()) - resumeAnimation(); - - if (server) - server->step(*dtime); + if (server) + server->step(dtime); - client->step(*dtime); - } + client->step(dtime); } static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) { @@ -2568,6 +2765,9 @@ void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation if (client->modsLoaded()) client->getScript()->on_damage_taken(event->player_damage.amount); + if (!event->player_damage.effect) + return; + // Damage flash and hurt tilt are not used at death if (client->getHP() > 0) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2625,8 +2825,8 @@ void Game::handleClientEvent_ShowFormSpec(ClientEvent *event, CameraOrientation new TextDestPlayerInventory(client, *(event->show_formspec.formname)); auto *&formspec = m_game_ui->updateFormspec(*(event->show_formspec.formname)); - GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend(), sound); + GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), + &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); } delete event->show_formspec.formspec; @@ -2638,8 +2838,8 @@ void Game::handleClientEvent_ShowLocalFormSpec(ClientEvent *event, CameraOrienta 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, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend(), sound); + 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; @@ -2679,6 +2879,7 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam) e->size = event->hudadd->size; e->z_index = event->hudadd->z_index; e->text2 = event->hudadd->text2; + e->style = event->hudadd->style; m_hud_server_to_client[server_id] = player->addHud(e); delete event->hudadd; @@ -2744,6 +2945,8 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca CASE_SET(HUD_STAT_Z_INDEX, z_index, data); CASE_SET(HUD_STAT_TEXT2, text2, sdata); + + CASE_SET(HUD_STAT_STYLE, style, data); } #undef CASE_SET @@ -2803,6 +3006,10 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam) "custom" ); } + + // Orbit Tilt: + sky->setBodyOrbitTilt(event->set_sky->body_orbit_tilt); + delete event->set_sky; } @@ -2829,9 +3036,10 @@ void Game::handleClientEvent_SetMoon(ClientEvent *event, CameraOrientation *cam) void Game::handleClientEvent_SetStars(ClientEvent *event, CameraOrientation *cam) { sky->setStarsVisible(event->star_params->visible); - sky->setStarCount(event->star_params->count, false); + sky->setStarCount(event->star_params->count); sky->setStarColor(event->star_params->starcolor); sky->setStarScale(event->star_params->scale); + sky->setStarDayOpacity(event->star_params->day_opacity); delete event->star_params; } @@ -2866,7 +3074,7 @@ void Game::processClientEvents(CameraOrientation *cam) } } -void Game::updateChat(f32 dtime, const v2u32 &screensize) +void Game::updateChat(f32 dtime) { // Get new messages from error log buffer while (!m_chat_log_buf.empty()) @@ -2882,11 +3090,17 @@ void Game::updateChat(f32 dtime, const v2u32 &screensize) chat_backend->step(dtime); // Display all messages in a static text element - m_game_ui->setChatText(chat_backend->getRecentChat(), - chat_backend->getRecentBuffer().getLineCount()); + auto &buf = chat_backend->getRecentBuffer(); + if (buf.getLinesModified()) { + buf.resetLinesModified(); + m_game_ui->setChatText(chat_backend->getRecentChat(), buf.getLineCount()); + } + + // Make sure that the size is still correct + m_game_ui->updateChatSize(); } -void Game::updateCamera(u32 busy_time, f32 dtime) +void Game::updateCamera(f32 dtime) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -2916,6 +3130,11 @@ void Game::updateCamera(u32 busy_time, f32 dtime) camera->toggleCameraMode(); +#ifdef HAVE_TOUCHSCREENGUI + if (g_touchscreengui) + g_touchscreengui->setUseCrosshair(!isNoCrosshairAllowed()); +#endif + // Make the player visible depending on camera mode. playercao->updateMeshCulling(); playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); @@ -2925,17 +3144,18 @@ void Game::updateCamera(u32 busy_time, f32 dtime) float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval; tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); - camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio); + camera->update(player, dtime, tool_reload_ratio); camera->step(dtime); - v3f camera_position = camera->getPosition(); - v3f camera_direction = camera->getDirection(); f32 camera_fov = camera->getFovMax(); v3s16 camera_offset = camera->getOffset(); m_camera_offset_changed = (camera_offset != old_camera_offset); if (!m_flags.disable_camera_update) { + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + client->getEnv().getClientMap().updateCamera(camera_position, camera_direction, camera_fov, camera_offset); @@ -2988,7 +3208,7 @@ void Game::updateSound(f32 dtime) } -void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) +void Game::processPlayerInteraction(f32 dtime, bool show_hud) { LocalPlayer *player = client->getEnv().getLocalPlayer(); @@ -3025,16 +3245,14 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) shootline.end = shootline.start + camera_direction * BS * d; #ifdef HAVE_TOUCHSCREENGUI - - if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { + if (g_touchscreengui && isNoCrosshairAllowed()) { shootline = g_touchscreengui->getShootline(); // Scale shootline to the acual distance the player can reach - shootline.end = shootline.start - + shootline.getVector().normalize() * BS * d; + shootline.end = shootline.start + + shootline.getVector().normalize() * BS * d; shootline.start += intToFloat(camera_offset, BS); shootline.end += intToFloat(camera_offset, BS); } - #endif PointedThing pointed = updatePointedThing(shootline, @@ -3042,10 +3260,12 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) !runData.btn_down_for_dig, camera_offset); - if (pointed != runData.pointed_old) { + if (pointed != runData.pointed_old) infostream << "Pointing at " << pointed.dump() << std::endl; - hud->updateSelectionMesh(camera_offset); - } + + // Note that updating the selection mesh every frame is not particularly efficient, + // but the halo rendering code is already inefficient so there's no point in optimizing it here + hud->updateSelectionMesh(camera_offset); // Allow digging again if button is not pressed if (runData.digging_blocked && !isKeyDown(KeyType::DIG)) @@ -3090,7 +3310,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) runData.punching = false; - soundmaker->m_player_leftpunch_sound.name = ""; + soundmaker->m_player_leftpunch_sound = SimpleSoundSpec(); + soundmaker->m_player_leftpunch_sound2 = pointed.type != POINTEDTHING_NOTHING ? + selected_def.sound_use : selected_def.sound_use_air; // Prepare for repeating, unless we're not supposed to if (isKeyDown(KeyType::PLACE) && !g_settings->getBool("safe_dig_and_place")) @@ -3106,7 +3328,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud, bool show_debug) handlePointingAtNode(pointed, selected_item, hand_item, dtime); } else if (pointed.type == POINTEDTHING_OBJECT) { v3f player_position = player->getPosition(); - handlePointingAtObject(pointed, tool_item, player_position, show_debug); + bool basic_debug_allowed = client->checkPrivilege("debug") || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); + handlePointingAtObject(pointed, tool_item, player_position, + m_game_ui->m_flags.show_basic_debug && basic_debug_allowed); } else if (isKeyDown(KeyType::DIG)) { // When button is held down in air, show continuous animation runData.punching = true; @@ -3144,7 +3368,7 @@ PointedThing Game::updatePointedThing( { std::vector *selectionboxes = hud->getSelectionBoxes(); selectionboxes->clear(); - hud->setSelectedFaceNormal(v3f(0.0, 0.0, 0.0)); + hud->setSelectedFaceNormal(v3f()); static thread_local const bool show_entity_selectionbox = g_settings->getBool( "show_entity_selectionbox"); @@ -3168,7 +3392,13 @@ PointedThing Game::updatePointedThing( v3f pos = runData.selected_object->getPosition(); selectionboxes->push_back(aabb3f(selection_box)); hud->setSelectionPos(pos, camera_offset); + GenericCAO* gcao = dynamic_cast(runData.selected_object); + if (gcao != nullptr && gcao->getProperties().rotate_selectionbox) + hud->setSelectionRotation(gcao->getSceneNode()->getAbsoluteTransformation().getRotationDegrees()); + else + hud->setSelectionRotation(v3f()); } + hud->setSelectedFaceNormal(result.raw_intersection_normal); } else if (result.type == POINTEDTHING_NODE) { // Update selection boxes MapNode n = map.getNode(result.node_undersurface); @@ -3186,10 +3416,8 @@ PointedThing Game::updatePointedThing( } hud->setSelectionPos(intToFloat(result.node_undersurface, BS), camera_offset); - hud->setSelectedFaceNormal(v3f( - result.intersection_normal.X, - result.intersection_normal.Y, - result.intersection_normal.Z)); + hud->setSelectionRotation(v3f()); + hud->setSelectedFaceNormal(result.intersection_normal); } // Update selection mesh light level and vertex colors @@ -3214,7 +3442,7 @@ PointedThing Game::updatePointedThing( final_color_blend(&c, light_level, daynight_ratio); // Modify final color a bit with time - u32 timer = porting::getTimeMs() % 5000; + u32 timer = client->getEnv().getFrameTime() % 5000; float timerf = (float) (irr::core::PI * ((timer / 2500.0) - 0.5)); float sin_r = 0.08f * std::sin(timerf); float sin_g = 0.08f * std::sin(timerf + irr::core::PI * 0.5f); @@ -3243,7 +3471,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, const ItemStack &selected_item, const ItemStack &hand_item, f32 dtime) { v3s16 nodepos = pointed.node_undersurface; - v3s16 neighbourpos = pointed.node_abovesurface; + v3s16 neighborpos = pointed.node_abovesurface; /* Check information text of node @@ -3266,9 +3494,8 @@ void Game::handlePointingAtNode(const PointedThing &pointed, } else { MapNode n = map.getNode(nodepos); - if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { - m_game_ui->setInfoText(L"Unknown node: " + - utf8_to_wide(nodedef_manager->get(n).name)); + if (nodedef_manager->get(n).name == "unknown") { + m_game_ui->setInfoText(L"Unknown node"); } } @@ -3288,7 +3515,7 @@ void Game::handlePointingAtNode(const PointedThing &pointed, // And also set the sound and send the interact // But first check for meta formspec and rightclickable auto &def = selected_item.getDefinition(itemdef_manager); - bool placed = nodePlacement(def, selected_item, nodepos, neighbourpos, + bool placed = nodePlacement(def, selected_item, nodepos, neighborpos, pointed, meta); if (placed && client->modsLoaded()) @@ -3297,7 +3524,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 ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighborpos, const PointedThing &pointed, const NodeMetadata *meta) { const auto &prediction = selected_def.node_placement_prediction; @@ -3330,8 +3557,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); auto *&formspec = m_game_ui->updateFormspec(""); - GUIFormSpecMenu::create(formspec, client, &input->joystick, fs_src, - txt_dst, client->getFormspecPrepend(), sound); + GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), + &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); formspec->setFormSpec(meta->getString("formspec"), inventoryloc); return false; @@ -3347,7 +3574,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, verbosestream << "Node placement prediction for " << selected_def.name << " is " << prediction << std::endl; - v3s16 p = neighbourpos; + v3s16 p = neighborpos; // Place inside node itself if buildable_to MapNode n_under = map.getNode(nodepos, &is_valid_position); @@ -3381,53 +3608,61 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, const ContentFeatures &predicted_f = nodedef->get(id); - // Predict param2 for facedir and wallmounted nodes - // Compare core.item_place_node() for what the server does - u8 param2 = 0; + // Compare core.item_place_node() for what the server does with param2 + MapNode predicted_node(id, 0, 0); const u8 place_param2 = selected_def.place_param2; if (place_param2) { - param2 = place_param2; + predicted_node.setParam2(place_param2); } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { - v3s16 dir = nodepos - neighbourpos; + v3s16 dir = nodepos - neighborpos; if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { - param2 = dir.Y < 0 ? 1 : 0; + predicted_node.setParam2(dir.Y < 0 ? 1 : 0); } else if (abs(dir.X) > abs(dir.Z)) { - param2 = dir.X < 0 ? 3 : 2; + predicted_node.setParam2(dir.X < 0 ? 3 : 2); } else { - param2 = dir.Z < 0 ? 5 : 4; + predicted_node.setParam2(dir.Z < 0 ? 5 : 4); } } else if (predicted_f.param_type_2 == CPT2_FACEDIR || - predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) { + predicted_f.param_type_2 == CPT2_COLORED_FACEDIR || + predicted_f.param_type_2 == CPT2_4DIR || + predicted_f.param_type_2 == CPT2_COLORED_4DIR) { v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS); if (abs(dir.X) > abs(dir.Z)) { - param2 = dir.X < 0 ? 3 : 1; + predicted_node.setParam2(dir.X < 0 ? 3 : 1); } else { - param2 = dir.Z < 0 ? 2 : 0; + predicted_node.setParam2(dir.Z < 0 ? 2 : 0); } } // Check attachment if node is in group attached_node - if (itemgroup_get(predicted_f.groups, "attached_node") != 0) { - const static v3s16 wallmounted_dirs[8] = { - v3s16(0, 1, 0), - v3s16(0, -1, 0), - v3s16(1, 0, 0), - v3s16(-1, 0, 0), - v3s16(0, 0, 1), - v3s16(0, 0, -1), - }; + int an = itemgroup_get(predicted_f.groups, "attached_node"); + if (an != 0) { v3s16 pp; - if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || - predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) - pp = p + wallmounted_dirs[param2]; - else + if (an == 3) { + pp = p + v3s16(0, -1, 0); + } else if (an == 4) { + pp = p + v3s16(0, 1, 0); + } else if (an == 2) { + if (predicted_f.param_type_2 == CPT2_FACEDIR || + predicted_f.param_type_2 == CPT2_COLORED_FACEDIR || + predicted_f.param_type_2 == CPT2_4DIR || + predicted_f.param_type_2 == CPT2_COLORED_4DIR) { + pp = p + facedir_dirs[predicted_node.getFaceDir(nodedef)]; + } else { + pp = p; + } + } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED || + predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { + pp = p + predicted_node.getWallMountedDir(nodedef); + } else { pp = p + v3s16(0, -1, 0); + } if (!nodedef->get(map.getNode(pp)).walkable) { soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed; @@ -3440,39 +3675,41 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, // Apply color if (!place_param2 && (predicted_f.param_type_2 == CPT2_COLOR || predicted_f.param_type_2 == CPT2_COLORED_FACEDIR + || predicted_f.param_type_2 == CPT2_COLORED_4DIR || predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED)) { const auto &indexstr = selected_item.metadata. getString("palette_index", 0); if (!indexstr.empty()) { s32 index = mystoi(indexstr); if (predicted_f.param_type_2 == CPT2_COLOR) { - param2 = index; + predicted_node.setParam2(index); } else if (predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) { // param2 = pure palette index + other - param2 = (index & 0xf8) | (param2 & 0x07); + predicted_node.setParam2((index & 0xf8) | (predicted_node.getParam2() & 0x07)); } else if (predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) { // param2 = pure palette index + other - param2 = (index & 0xe0) | (param2 & 0x1f); + predicted_node.setParam2((index & 0xe0) | (predicted_node.getParam2() & 0x1f)); + } else if (predicted_f.param_type_2 == CPT2_COLORED_4DIR) { + // param2 = pure palette index + other + predicted_node.setParam2((index & 0xfc) | (predicted_node.getParam2() & 0x03)); } } } // Add node to client map - MapNode n(id, 0, param2); - try { LocalPlayer *player = client->getEnv().getLocalPlayer(); - // Dont place node when player would be inside new node + // Don't place node when player would be inside new node // NOTE: This is to be eventually implemented by a mod as client-side Lua - if (!nodedef->get(n).walkable || + if (!predicted_f.walkable || g_settings->getBool("enable_build_where_you_stand") || (client->checkPrivilege("noclip") && g_settings->getBool("noclip")) || - (nodedef->get(n).walkable && - neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) && - neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) { + (predicted_f.walkable && + neighborpos != player->getStandingNodePos() + v3s16(0, 1, 0) && + neighborpos != player->getStandingNodePos() + v3s16(0, 2, 0))) { // This triggers the required mesh update too - client->addNode(p, n); + client->addNode(p, predicted_node); // Report to server client->interact(INTERACT_PLACE, pointed); // A node is predicted, also play a sound @@ -3549,17 +3786,19 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, // See also: serverpackethandle.cpp, action == 2 LocalPlayer *player = client->getEnv().getLocalPlayer(); ClientMap &map = client->getEnv().getClientMap(); - MapNode n = client->getEnv().getClientMap().getNode(nodepos); + MapNode n = map.getNode(nodepos); + const auto &features = nodedef_manager->get(n); // NOTE: Similar piece of code exists on the server side for // cheat detection. // Get digging parameters - DigParams params = getDigParams(nodedef_manager->get(n).groups, - &selected_item.getToolCapabilities(itemdef_manager)); + DigParams params = getDigParams(features.groups, + &selected_item.getToolCapabilities(itemdef_manager), + selected_item.wear); // If can't dig, try hand if (!params.diggable) { - params = getDigParams(nodedef_manager->get(n).groups, + params = getDigParams(features.groups, &hand_item.getToolCapabilities(itemdef_manager)); } @@ -3570,7 +3809,6 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, runData.dig_time_complete = params.time; if (m_cache_enable_particles) { - const ContentFeatures &features = client->getNodeDefManager()->get(n); client->getParticleManager()->addNodeParticle(client, player, nodepos, n, features); } @@ -3581,6 +3819,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, runData.dig_instantly = runData.dig_time_complete == 0; if (client->modsLoaded() && client->getScript()->on_punchnode(nodepos, n)) return; + client->interact(INTERACT_START_DIGGING, pointed); runData.digging = true; runData.btn_down_for_dig = true; @@ -3595,7 +3834,7 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, runData.dig_index = crack_animation_length; } - SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig; + const auto &sound_dig = features.sound_dig; if (sound_dig.exists() && params.diggable) { if (sound_dig.name == "__group") { @@ -3613,8 +3852,6 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, // Don't show cracks if not diggable if (runData.dig_time_complete >= 100000.0) { } else if (runData.dig_index < crack_animation_length) { - //TimeTaker timer("client.setTempMod"); - //infostream<<"dig_index="<setCrack(runData.dig_index, nodepos); } else { infostream << "Digging completed" << std::endl; @@ -3636,38 +3873,31 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos, else if (runData.dig_instantly) runData.nodig_delay_timer = 0.15; - bool is_valid_position; - MapNode wasnode = map.getNode(nodepos, &is_valid_position); - if (is_valid_position) { - if (client->modsLoaded() && - client->getScript()->on_dignode(nodepos, wasnode)) { - return; - } + if (client->modsLoaded() && + client->getScript()->on_dignode(nodepos, n)) { + return; + } - const ContentFeatures &f = client->ndef()->get(wasnode); - if (f.node_dig_prediction == "air") { - client->removeNode(nodepos); - } else if (!f.node_dig_prediction.empty()) { - content_t id; - bool found = client->ndef()->getId(f.node_dig_prediction, id); - if (found) - client->addNode(nodepos, id, true); - } - // implicit else: no prediction + if (features.node_dig_prediction == "air") { + client->removeNode(nodepos); + } else if (!features.node_dig_prediction.empty()) { + content_t id; + bool found = nodedef_manager->getId(features.node_dig_prediction, id); + if (found) + client->addNode(nodepos, id, true); } + // implicit else: no prediction client->interact(INTERACT_DIGGING_COMPLETED, pointed); if (m_cache_enable_particles) { - const ContentFeatures &features = - client->getNodeDefManager()->get(wasnode); client->getParticleManager()->addDiggingParticles(client, - player, nodepos, wasnode, features); + player, nodepos, n, features); } // Send event to trigger sound - client->getEventManager()->put(new NodeDugEvent(nodepos, wasnode)); + client->getEventManager()->put(new NodeDugEvent(nodepos, n)); } if (runData.dig_time_complete < 100000.0) { @@ -3686,6 +3916,12 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, TimeTaker tt_update("Game::updateFrame()"); LocalPlayer *player = client->getEnv().getLocalPlayer(); + /* + Frame time + */ + + client->getEnv().updateFrameTime(m_is_paused); + /* Fog range */ @@ -3704,7 +3940,10 @@ 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) { + // When in noclip mode force same sky brightness as above ground so you + // can see properly + if (draw_control->allow_noclip && m_cache_enable_free_move && + client->checkPrivilege("fly")) { direct_brightness = time_brightness; sunlight_seen = true; } else { @@ -3802,12 +4041,28 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } /* - Get chat messages from client + Damage camera tilt */ + if (player->hurt_tilt_timer > 0.0f) { + player->hurt_tilt_timer -= dtime * 6.0f; - v2u32 screensize = driver->getScreenSize(); + if (player->hurt_tilt_timer < 0.0f) + player->hurt_tilt_strength = 0.0f; + } - updateChat(dtime, screensize); + /* + Update minimap pos and rotation + */ + if (mapper && m_game_ui->m_flags.show_hud) { + mapper->setPos(floatToInt(player->getPosition(), BS)); + mapper->setAngle(player->getYaw()); + } + + /* + Get chat messages from client + */ + + updateChat(dtime); /* Inventory @@ -3828,14 +4083,30 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, changed much */ runData.update_draw_list_timer += dtime; + runData.touch_blocks_timer += dtime; + + bool draw_list_updated = false; + + float update_draw_list_delta = 0.2f; v3f camera_direction = camera->getDirection(); - if (runData.update_draw_list_timer >= 0.2 + if (runData.update_draw_list_timer >= update_draw_list_delta || runData.update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 - || m_camera_offset_changed) { + || m_camera_offset_changed + || client->getEnv().getClientMap().needsUpdateDrawList()) { runData.update_draw_list_timer = 0; client->getEnv().getClientMap().updateDrawList(); runData.update_draw_list_last_cam_dir = camera_direction; + draw_list_updated = true; + } + + if (runData.touch_blocks_timer > update_draw_list_delta && !draw_list_updated) { + client->getEnv().getClientMap().touchMapBlocks(); + runData.touch_blocks_timer = 0; + } + + if (RenderingEngine::get_shadow_renderer()) { + updateShadows(); } m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime); @@ -3869,11 +4140,11 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } while (false); /* - Drawing begins + ==================== Drawing begins ==================== */ - const video::SColor &skycolor = sky->getSkyColor(); + const video::SColor skycolor = sky->getSkyColor(); - TimeTaker tt_draw("Draw scene"); + TimeTaker tt_draw("Draw scene", nullptr, PRECISION_MICRO); driver->beginScene(true, true, skycolor); bool draw_wield_tool = (m_game_ui->m_flags.show_hud && @@ -3883,17 +4154,17 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, (player->hud_flags & HUD_FLAG_CROSSHAIR_VISIBLE) && (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT)); #ifdef HAVE_TOUCHSCREENGUI - try { - draw_crosshair = !g_settings->getBool("touchtarget"); - } catch (SettingNotFoundException) { - } + if (isNoCrosshairAllowed()) + draw_crosshair = false; #endif - RenderingEngine::draw_scene(skycolor, m_game_ui->m_flags.show_hud, + m_rendering_engine->draw_scene(skycolor, m_game_ui->m_flags.show_hud, m_game_ui->m_flags.show_minimap, draw_wield_tool, draw_crosshair); /* Profiler graph */ + v2u32 screensize = driver->getScreenSize(); + if (m_game_ui->m_flags.show_profiler_graph) graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont()); @@ -3910,52 +4181,14 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime, } /* - Damage camera tilt - */ - if (player->hurt_tilt_timer > 0.0f) { - player->hurt_tilt_timer -= dtime * 6.0f; - - if (player->hurt_tilt_timer < 0.0f) - player->hurt_tilt_strength = 0.0f; - } - - /* - Update minimap pos and rotation - */ - if (mapper && m_game_ui->m_flags.show_hud) { - mapper->setPos(floatToInt(player->getPosition(), BS)); - mapper->setAngle(player->getYaw()); - } - - /* - End scene + ==================== End scene ==================== */ - if (++m_reset_HW_buffer_counter > 500) { - /* - Periodically remove all mesh HW buffers. - - Work around for a quirk in Irrlicht where a HW buffer is only - released after 20000 iterations (triggered from endScene()). - - Without this, all loaded but unused meshes will retain their HW - buffers for at least 5 minutes, at which point looking up the HW buffers - becomes a bottleneck and the framerate drops (as much as 30%). - - Tests showed that numbers between 50 and 1000 are good, so picked 500. - There are no other public Irrlicht APIs that allow interacting with the - HW buffers without tracking the status of every individual mesh. - The HW buffers for _visible_ meshes will be reinitialized in the next frame. - */ - infostream << "Game::updateFrame(): Removing all HW buffers." << std::endl; - driver->removeAllHardwareBuffers(); - m_reset_HW_buffer_counter = 0; - } driver->endScene(); stats->drawtime = tt_draw.stop(true); - g_profiler->avg("Game::updateFrame(): draw scene [ms]", stats->drawtime); - g_profiler->graphAdd("Update frame [ms]", tt_update.stop(true)); + g_profiler->graphAdd("Draw scene [us]", stats->drawtime); + g_profiler->avg("Game::updateFrame(): update frame [ms]", tt_update.stop(true)); } /* Log times and stuff for visualization */ @@ -3966,59 +4199,87 @@ inline void Game::updateProfilerGraphs(ProfilerGraph *graph) graph->put(values); } +/**************************************************************************** + * Shadows + *****************************************************************************/ +void Game::updateShadows() +{ + ShadowRenderer *shadow = RenderingEngine::get_shadow_renderer(); + if (!shadow) + return; + + float in_timeofday = fmod(runData.time_of_day_smooth, 1.0f); + + float timeoftheday = getWickedTimeOfDay(in_timeofday); + bool is_day = timeoftheday > 0.25 && timeoftheday < 0.75; + bool is_shadow_visible = is_day ? sky->getSunVisible() : sky->getMoonVisible(); + shadow->setShadowIntensity(is_shadow_visible ? client->getEnv().getLocalPlayer()->getLighting().shadow_intensity : 0.0f); + + timeoftheday = fmod(timeoftheday + 0.75f, 0.5f) + 0.25f; + const float offset_constant = 10000.0f; + + v3f light = is_day ? sky->getSunDirection() : sky->getMoonDirection(); + v3f sun_pos = light * offset_constant; + + if (shadow->getDirectionalLightCount() == 0) + shadow->addDirectionalLight(); + shadow->getDirectionalLight().setDirection(sun_pos); + shadow->setTimeOfDay(in_timeofday); + + shadow->getDirectionalLight().update_frustum(camera, client, m_camera_offset_changed); +} /**************************************************************************** Misc ****************************************************************************/ -/* On some computers framerate doesn't seem to be automatically limited +void FpsControl::reset() +{ + last_time = porting::getTimeUs(); +} + +/* + * On some computers framerate doesn't seem to be automatically limited */ -inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime) +void FpsControl::limit(IrrlichtDevice *device, f32 *dtime) { - // not using getRealTime is necessary for wine - device->getTimer()->tick(); // Maker sure device time is up-to-date - u32 time = device->getTimer()->getTime(); - u32 last_time = fps_timings->last_time; + const float fps_limit = (device->isWindowFocused() && !g_menumgr.pausesGame()) + ? g_settings->getFloat("fps_max") + : g_settings->getFloat("fps_max_unfocused"); + const u64 frametime_min = 1000000.0f / std::max(fps_limit, 1.0f); - if (time > last_time) // Make sure time hasn't overflowed - fps_timings->busy_time = time - last_time; - else - fps_timings->busy_time = 0; + u64 time = porting::getTimeUs(); - u32 frametime_min = 1000 / ( - device->isWindowFocused() && !g_menumgr.pausesGame() - ? g_settings->getFloat("fps_max") - : g_settings->getFloat("fps_max_unfocused")); + if (time > last_time) // Make sure time hasn't overflowed + busy_time = time - last_time; + else + busy_time = 0; - if (fps_timings->busy_time < frametime_min) { - fps_timings->sleep_time = frametime_min - fps_timings->busy_time; - device->sleep(fps_timings->sleep_time); + if (busy_time < frametime_min) { + sleep_time = frametime_min - busy_time; + if (sleep_time > 1000) + sleep_ms(sleep_time / 1000); } else { - fps_timings->sleep_time = 0; + sleep_time = 0; } - /* Get the new value of the device timer. Note that device->sleep() may - * not sleep for the entire requested time as sleep may be interrupted and - * therefore it is arguably more accurate to get the new time from the - * device rather than calculating it by adding sleep_time to time. - */ - - device->getTimer()->tick(); // Update device timer - time = device->getTimer()->getTime(); + // Read the timer again to accurately determine how long we actually slept, + // rather than calculating it by adding sleep_time to time. + time = porting::getTimeUs(); - if (time > last_time) // Make sure last_time hasn't overflowed - *dtime = (time - last_time) / 1000.0; + if (time > last_time) // Make sure last_time hasn't overflowed + *dtime = (time - last_time) / 1000000.0f; else *dtime = 0; - fps_timings->last_time = time; + last_time = time; } void Game::showOverlayMessage(const char *msg, float dtime, int percent, bool draw_clouds) { const wchar_t *wmsg = wgettext(msg); - RenderingEngine::draw_load_screen(wmsg, guienv, texture_src, dtime, percent, + m_rendering_engine->draw_load_screen(wmsg, guienv, texture_src, dtime, percent, draw_clouds); delete[] wmsg; } @@ -4035,9 +4296,9 @@ void Game::readSettings() m_cache_enable_joysticks = g_settings->getBool("enable_joysticks"); m_cache_enable_particles = g_settings->getBool("enable_particles"); m_cache_enable_fog = g_settings->getBool("enable_fog"); - m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity"); - m_cache_joystick_frustum_sensitivity = g_settings->getFloat("joystick_frustum_sensitivity"); - m_repeat_place_time = g_settings->getFloat("repeat_place_time"); + m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity", 0.001f, 10.0f); + m_cache_joystick_frustum_sensitivity = std::max(g_settings->getFloat("joystick_frustum_sensitivity"), 0.001f); + m_repeat_place_time = g_settings->getFloat("repeat_place_time", 0.16f, 2.0); m_cache_enable_noclip = g_settings->getBool("noclip"); m_cache_enable_free_move = g_settings->getBool("free_move"); @@ -4063,27 +4324,6 @@ void Game::readSettings() ****************************************************************************/ /****************************************************************************/ -void Game::extendedResourceCleanup() -{ - // Extended resource accounting - infostream << "Irrlicht resources after cleanup:" << std::endl; - infostream << "\tRemaining meshes : " - << RenderingEngine::get_mesh_cache()->getMeshCount() << std::endl; - infostream << "\tRemaining textures : " - << driver->getTextureCount() << std::endl; - - for (unsigned int i = 0; i < driver->getTextureCount(); i++) { - irr::video::ITexture *texture = driver->getTextureByIndex(i); - infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str() - << std::endl; - } - - clearTextureNameCache(); - infostream << "\tRemaining materials: " - << driver-> getMaterialRendererCount() - << " (note: irrlicht doesn't support removing renderers)" << std::endl; -} - void Game::showDeathFormspec() { static std::string formspec_str = @@ -4101,15 +4341,15 @@ void Game::showDeathFormspec() LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend(), sound); + GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), + &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); formspec->setFocus("btn_respawn"); } #define GET_KEY_NAME(KEY) gettext(getKeySetting(#KEY).name()) void Game::showPauseMenu() { -#ifdef __ANDROID__ +#ifdef HAVE_TOUCHSCREENGUI static const std::string control_text = strgettext("Default Controls:\n" "No menu visible:\n" "- single tap: button activate\n" @@ -4209,16 +4449,18 @@ void Game::showPauseMenu() if (simple_singleplayer_mode || address.empty()) { static const std::string on = strgettext("On"); static const std::string off = strgettext("Off"); - const std::string &damage = g_settings->getBool("enable_damage") ? on : off; - const std::string &creative = g_settings->getBool("creative_mode") ? on : off; + // Note: Status of enable_damage and creative_mode settings is intentionally + // NOT shown here because the game might roll its own damage system and/or do + // a per-player Creative Mode, in which case writing it here would mislead. + bool damage = g_settings->getBool("enable_damage"); const std::string &announced = g_settings->getBool("server_announce") ? on : off; - os << strgettext("- Damage: ") << damage << "\n" - << strgettext("- Creative Mode: ") << creative << "\n"; if (!simple_singleplayer_mode) { - const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; - //~ PvP = Player versus Player - os << strgettext("- PvP: ") << pvp << "\n" - << strgettext("- Public: ") << announced << "\n"; + if (damage) { + const std::string &pvp = g_settings->getBool("enable_pvp") ? on : off; + //~ PvP = Player versus Player + os << strgettext("- PvP: ") << pvp << "\n"; + } + os << strgettext("- Public: ") << announced << "\n"; std::string server_name = g_settings->get("server_name"); str_formspec_escape(server_name); if (announced == on && !server_name.empty()) @@ -4235,13 +4477,11 @@ void Game::showPauseMenu() LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); auto *&formspec = m_game_ui->getFormspecGUI(); - GUIFormSpecMenu::create(formspec, client, &input->joystick, - fs_src, txt_dst, client->getFormspecPrepend(), sound); + GUIFormSpecMenu::create(formspec, client, m_rendering_engine->get_gui_env(), + &input->joystick, fs_src, txt_dst, client->getFormspecPrepend(), sound); formspec->setFocus("btn_continue"); + // game will be paused in next step, if in singleplayer (see m_is_paused) formspec->doPause = true; - - if (simple_singleplayer_mode) - pauseAnimation(); } /****************************************************************************/ @@ -4252,6 +4492,7 @@ void Game::showPauseMenu() void the_game(bool *kill, InputHandler *input, + RenderingEngine *rendering_engine, const GameStartData &start_data, std::string &error_message, ChatBackend &chat_backend, @@ -4266,20 +4507,21 @@ void the_game(bool *kill, try { - if (game.startup(kill, input, start_data, error_message, - reconnect_requested, &chat_backend)) { + if (game.startup(kill, input, rendering_engine, start_data, + error_message, reconnect_requested, &chat_backend)) { game.run(); } } catch (SerializationError &e) { - error_message = std::string("A serialization error occurred:\n") - + e.what() + "\n\nThe server is probably " - " running a different version of " PROJECT_NAME_C "."; + const std::string ver_err = fmtgettext("The server is probably running a different version of %s.", PROJECT_NAME_C); + error_message = strgettext("A serialization error occurred:") +"\n" + + e.what() + "\n\n" + ver_err; errorstream << error_message << std::endl; } catch (ServerError &e) { error_message = e.what(); errorstream << "ServerError: " << error_message << std::endl; } catch (ModError &e) { + // DO NOT TRANSLATE the `ModError`, it's used by ui.lua error_message = std::string("ModError: ") + e.what() + strgettext("\nCheck debug.txt for details."); errorstream << error_message << std::endl;