aboutsummaryrefslogtreecommitdiff
path: root/src/client/game.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/game.cpp')
-rw-r--r--src/client/game.cpp891
1 files changed, 484 insertions, 407 deletions
diff --git a/src/client/game.cpp b/src/client/game.cpp
index a88972d53..4337d308e 100644
--- a/src/client/game.cpp
+++ b/src/client/game.cpp
@@ -68,6 +68,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#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"
@@ -399,12 +400,7 @@ public:
};
-// before 1.8 there isn't a "integer interface", only float
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
-typedef f32 SamplerLayer_t;
-#else
typedef s32 SamplerLayer_t;
-#endif
class GameGlobalShaderConstantSetter : public IShaderConstantSetter
@@ -512,38 +508,20 @@ public:
float eye_position_array[3];
v3f epos = m_client->getEnv().getLocalPlayer()->getEyePosition();
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- eye_position_array[0] = epos.X;
- eye_position_array[1] = epos.Y;
- eye_position_array[2] = epos.Z;
-#else
epos.getAs3Values(eye_position_array);
-#endif
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();
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- minimap_yaw_array[0] = minimap_yaw.X;
- minimap_yaw_array[1] = minimap_yaw.Y;
- minimap_yaw_array[2] = minimap_yaw.Z;
-#else
minimap_yaw.getAs3Values(minimap_yaw_array);
-#endif
m_minimap_yaw.set(minimap_yaw_array, services);
}
float camera_offset_array[3];
v3f offset = intToFloat(m_client->getCamera()->getOffset(), BS);
-#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
- camera_offset_array[0] = offset.X;
- camera_offset_array[1] = offset.Y;
- camera_offset_array[2] = offset.Z;
-#else
offset.getAs3Values(camera_offset_array);
-#endif
m_camera_offset_pixel.set(camera_offset_array, services);
m_camera_offset_vertex.set(camera_offset_array, services);
@@ -588,7 +566,7 @@ public:
}
};
-#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
@@ -597,10 +575,19 @@ public:
/****************************************************************************
****************************************************************************/
-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;
};
@@ -650,6 +637,8 @@ struct ClientEventHandler
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
@@ -662,6 +651,7 @@ public:
bool startup(bool *kill,
InputHandler *input,
+ RenderingEngine *rendering_engine,
const GameStartData &game_params,
std::string &error_message,
bool *reconnect,
@@ -672,8 +662,6 @@ public:
protected:
- void extendedResourceCleanup();
-
// Basic initialisation
bool init(const std::string &map_dir, const std::string &address,
u16 port, const SubgameSpec &gamespec);
@@ -697,6 +685,7 @@ protected:
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);
@@ -714,6 +703,7 @@ protected:
void toggleFast();
void toggleNoClip();
void toggleCinematic();
+ void toggleBlockBounds();
void toggleAutoforward();
void toggleMinimap(bool shift_pressed);
@@ -731,7 +721,7 @@ protected:
void updatePlayerControl(const CameraOrientation &cam);
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);
/*!
@@ -759,10 +749,9 @@ protected:
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);
@@ -799,6 +788,9 @@ private:
void showDeathFormspec();
void showPauseMenu();
+ void pauseAnimation();
+ void resumeAnimation();
+
// ClientEvent handlers
void handleClientEvent_None(ClientEvent *event, CameraOrientation *cam);
void handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation *cam);
@@ -819,13 +811,15 @@ private:
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 NodeMetadata *meta);
static const ClientEventHandler clientEventHandler[CLIENTEVENT_MAX];
+ f32 getSensitivityScaleFactor() const;
+
InputHandler *input = nullptr;
Client *client = nullptr;
@@ -870,12 +864,14 @@ private:
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' */
@@ -912,8 +908,10 @@ private:
bool m_does_lost_focus_pause_game = false;
int m_reset_HW_buffer_counter = 0;
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
bool m_cache_hold_aux1;
+#endif
+#ifdef __ANDROID__
bool m_android_chat_open;
#endif
};
@@ -951,7 +949,7 @@ Game::Game() :
readSettings();
-#ifdef __ANDROID__
+#ifdef HAVE_TOUCHSCREENGUI
m_cache_hold_aux1 = false; // This is initialised properly later
#endif
@@ -982,7 +980,7 @@ Game::~Game()
delete itemdef_manager;
delete draw_control;
- extendedResourceCleanup();
+ clearTextureNameCache();
g_settings->deregisterChangedCallback("doubletap_jump",
&settingChangedCallback, this);
@@ -1010,6 +1008,7 @@ Game::~Game()
bool Game::startup(bool *kill,
InputHandler *input,
+ RenderingEngine *rendering_engine,
const GameStartData &start_data,
std::string &error_message,
bool *reconnect,
@@ -1017,21 +1016,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();
@@ -1052,7 +1051,7 @@ bool Game::startup(bool *kill,
if (!createClient(start_data))
return false;
- RenderingEngine::initialize(client, hud);
+ m_rendering_engine->initialize(client, hud);
return true;
}
@@ -1064,18 +1063,18 @@ void Game::run()
RunStats stats = { 0 };
CameraOrientation cam_view_target = { 0 };
CameraOrientation cam_view = { 0 };
- FpsControl draw_times = { 0 };
+ 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
@@ -1083,12 +1082,12 @@ void Game::run()
irr::core::dimension2d<u32> 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<u32> &current_screen_size =
- RenderingEngine::get_video_driver()->getScreenSize();
+ m_rendering_engine->get_video_driver()->getScreenSize();
// 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
@@ -1101,9 +1100,9 @@ void Game::run()
}
// Calculate dtime =
- // RenderingEngine::run() from this iteration
+ // m_rendering_engine->run() from this iteration
// + Sleep time until the wanted FPS are reached
- limitFps(&draw_times, &dtime);
+ draw_times.limit(device, &dtime);
// Prepare render data for next iteration
@@ -1120,6 +1119,7 @@ void Game::run()
m_game_ui->clearInfoText();
hud->resizeHotbar();
+
updateProfilers(stats, draw_times, dtime);
processUserInput(dtime);
// Update camera before player movement to avoid camera lag of one frame
@@ -1131,10 +1131,11 @@ void Game::run()
updatePlayerControl(cam_view);
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);
+ m_game_ui->m_flags.show_basic_debug);
updateFrame(&graph, &stats, dtime, cam_view);
updateProfilerGraphs(&graph);
@@ -1150,7 +1151,7 @@ void Game::run()
void Game::shutdown()
{
- RenderingEngine::finalize();
+ m_rendering_engine->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);
@@ -1288,9 +1289,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;
}
@@ -1306,7 +1306,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;
@@ -1323,7 +1323,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;
@@ -1332,7 +1332,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;
@@ -1347,9 +1347,9 @@ 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);
/* Clouds
@@ -1359,7 +1359,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
@@ -1381,16 +1381,28 @@ bool Game::createClient(const GameStartData &start_data)
std::wstring str = utf8_to_wide(PROJECT_NAME_C);
str += L" ";
str += utf8_to_wide(g_version_hash);
+ {
+ const wchar_t *text = nullptr;
+ if (simple_singleplayer_mode)
+ text = wgettext("Singleplayer");
+ else
+ text = wgettext("Multiplayer");
+ str += L" [";
+ str += text;
+ str += L"]";
+ delete[] text;
+ }
str += L" [";
str += driver->getName();
str += L"]";
+
device->setWindowCaption(str.c_str());
LocalPlayer *player = client->getEnv().getLocalPlayer();
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();
@@ -1439,7 +1451,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;
@@ -1450,24 +1461,30 @@ 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());
+ 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;
@@ -1485,15 +1502,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);
@@ -1512,8 +1529,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;
@@ -1528,7 +1544,7 @@ bool Game::connectToServer(const GameStartData &start_data,
if (client->m_is_registration_confirmation_state) {
if (registration_confirmation_shown) {
// Keep drawing the GUI
- RenderingEngine::draw_menu_scene(guienv, dtime, true);
+ m_rendering_engine->draw_menu_scene(guienv, dtime, true);
} else {
registration_confirmation_shown = true;
(new GUIConfirmRegistration(guienv, guienv->getRootGUIElement(), -1,
@@ -1539,7 +1555,7 @@ bool Game::connectToServer(const GameStartData &start_data,
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.";
+ *error_message = gettext("Connection timed out.");
errorstream << *error_message << std::endl;
break;
}
@@ -1561,14 +1577,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);
@@ -1587,7 +1603,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;
}
@@ -1604,17 +1620,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;
@@ -1637,7 +1653,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);
}
}
@@ -1669,8 +1685,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;
@@ -1723,6 +1738,25 @@ void Game::processQueues()
shader_src->processQueue();
}
+void Game::updateDebugState()
+{
+ const bool has_basic_debug = true;
+ bool has_debug = client->checkPrivilege("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;
+}
void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times,
f32 dtime)
@@ -1747,10 +1781,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);
}
@@ -1783,9 +1817,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;
@@ -1822,6 +1856,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
@@ -1904,24 +1939,18 @@ 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];
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];
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");
}
@@ -1934,6 +1963,8 @@ void Game::processKeyInput()
toggleCinematic();
} else if (wasKeyDown(KeyType::SCREENSHOT)) {
client->makeScreenshot();
+ } else if (wasKeyDown(KeyType::TOGGLE_BLOCK_BOUNDS)) {
+ toggleBlockBounds();
} else if (wasKeyDown(KeyType::TOGGLE_HUD)) {
m_game_ui->toggleHud();
} else if (wasKeyDown(KeyType::MINIMAP)) {
@@ -2042,15 +2073,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);
}
@@ -2138,7 +2176,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 +2209,32 @@ void Game::toggleCinematic()
m_game_ui->showTranslatedStatusText("Cinematic mode disabled");
}
+void Game::toggleBlockBounds()
+{
+ if (true /* basic_debug */) {
+ 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;
+ }
+
+ } else {
+ m_game_ui->showTranslatedStatusText("Can't show block bounds (need 'basic_debug' privilege)");
+ }
+}
+
// Autoforward by toggling continuous forward.
void Game::toggleAutoforward()
{
@@ -2234,24 +2298,39 @@ void Game::toggleFog()
void Game::toggleDebug()
{
- // Initial / 4x toggle: Chat only
- // 1x toggle: Debug text with chat
+ // 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 always used.
+
+ const bool has_basic_debug = true;
+ 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")) {
@@ -2278,20 +2357,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 +2374,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));
}
@@ -2337,7 +2403,6 @@ void Game::checkZoomEnabled()
m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
}
-
void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
{
if ((device->isWindowActive() && device->isWindowFocused()
@@ -2373,6 +2438,18 @@ void Game::updateCameraDirection(CameraOrientation *cam, float dtime)
}
}
+// Get the factor to multiply with sensitivity to get the same mouse/joystick
+// responsiveness independently of FOV.
+f32 Game::getSensitivityScaleFactor() const
+{
+ f32 fov_y = client->getCamera()->getFovY();
+
+ // Multiply by a constant such that it becomes 1.0 at 72 degree FOV and
+ // 16:9 aspect ratio to minimize disruption of existing sensitivity
+ // settings.
+ return tan(fov_y / 2.0f) * 1.3763818698f;
+}
+
void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
{
#ifdef HAVE_TOUCHSCREENGUI
@@ -2388,8 +2465,9 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
dist.Y = -dist.Y;
}
- cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity;
- cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity;
+ f32 sens_scale = getSensitivityScaleFactor();
+ cam->camera_yaw -= dist.X * m_cache_mouse_sensitivity * sens_scale;
+ cam->camera_pitch += dist.Y * m_cache_mouse_sensitivity * sens_scale;
if (dist.X != 0 || dist.Y != 0)
input->setMousePos(center.X, center.Y);
@@ -2398,7 +2476,8 @@ void Game::updateCameraOrientation(CameraOrientation *cam, float dtime)
#endif
if (m_cache_enable_joysticks) {
- f32 c = m_cache_joystick_frustum_sensitivity * (1.f / 32767.f) * dtime;
+ f32 sens_scale = getSensitivityScaleFactor();
+ 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;
}
@@ -2409,71 +2488,46 @@ 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::SPECIAL1),
+ 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),
isKeyDown(KeyType::DIG),
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::SPECIAL1) & 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 towards pointed position at maximum speed
+ if (player->getPlayerSettings().continuous_forward &&
+ client->activeObjectsReceived() && !player->isDead()) {
+ control.movement_speed = 1.0f;
+ control.movement_direction = 0.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();
}
@@ -2487,6 +2541,9 @@ inline void Game::step(f32 *dtime)
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);
@@ -2494,6 +2551,33 @@ inline void Game::step(f32 *dtime)
}
}
+static void pauseNodeAnimation(PausedNodesList &paused, scene::ISceneNode *node) {
+ if (!node)
+ return;
+ for (auto &&child: node->getChildren())
+ pauseNodeAnimation(paused, child);
+ if (node->getType() != scene::ESNT_ANIMATED_MESH)
+ return;
+ auto animated_node = static_cast<scene::IAnimatedMeshSceneNode *>(node);
+ float speed = animated_node->getAnimationSpeed();
+ if (!speed)
+ return;
+ paused.push_back({grab(animated_node), speed});
+ animated_node->setAnimationSpeed(0.0f);
+}
+
+void Game::pauseAnimation()
+{
+ pauseNodeAnimation(paused_animated_nodes, smgr->getRootSceneNode());
+}
+
+void Game::resumeAnimation()
+{
+ for (auto &&pair: paused_animated_nodes)
+ pair.first->setAnimationSpeed(pair.second);
+ paused_animated_nodes.clear();
+}
+
const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = {
{&Game::handleClientEvent_None},
{&Game::handleClientEvent_PlayerDamage},
@@ -2527,14 +2611,18 @@ void Game::handleClientEvent_PlayerDamage(ClientEvent *event, CameraOrientation
// Damage flash and hurt tilt are not used at death
if (client->getHP() > 0) {
- runData.damage_flash += 95.0f + 3.2f * event->player_damage.amount;
- runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
-
LocalPlayer *player = client->getEnv().getLocalPlayer();
+ f32 hp_max = player->getCAO() ?
+ 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;
+ runData.damage_flash = MYMIN(runData.damage_flash, 127.0f);
+
player->hurt_tilt_timer = 1.5f;
player->hurt_tilt_strength =
- rangelim(event->player_damage.amount / 4.0f, 1.0f, 4.0f);
+ rangelim(damage_ratio * 5.0f, 1.0f, 4.0f);
}
// Play damage sound
@@ -2578,8 +2666,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;
@@ -2591,8 +2679,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;
@@ -2609,48 +2697,33 @@ void Game::handleClientEvent_HudAdd(ClientEvent *event, CameraOrientation *cam)
{
LocalPlayer *player = client->getEnv().getLocalPlayer();
- u32 server_id = event->hudadd.server_id;
+ u32 server_id = event->hudadd->server_id;
// ignore if we already have a HUD with that ID
auto i = m_hud_server_to_client.find(server_id);
if (i != m_hud_server_to_client.end()) {
- delete event->hudadd.pos;
- delete event->hudadd.name;
- delete event->hudadd.scale;
- delete event->hudadd.text;
- delete event->hudadd.align;
- delete event->hudadd.offset;
- delete event->hudadd.world_pos;
- delete event->hudadd.size;
- delete event->hudadd.text2;
+ delete event->hudadd;
return;
}
HudElement *e = new HudElement;
- e->type = (HudElementType)event->hudadd.type;
- e->pos = *event->hudadd.pos;
- e->name = *event->hudadd.name;
- e->scale = *event->hudadd.scale;
- e->text = *event->hudadd.text;
- e->number = event->hudadd.number;
- e->item = event->hudadd.item;
- e->dir = event->hudadd.dir;
- e->align = *event->hudadd.align;
- e->offset = *event->hudadd.offset;
- e->world_pos = *event->hudadd.world_pos;
- e->size = *event->hudadd.size;
- e->z_index = event->hudadd.z_index;
- e->text2 = *event->hudadd.text2;
+ e->type = static_cast<HudElementType>(event->hudadd->type);
+ e->pos = event->hudadd->pos;
+ e->name = event->hudadd->name;
+ e->scale = event->hudadd->scale;
+ e->text = event->hudadd->text;
+ e->number = event->hudadd->number;
+ e->item = event->hudadd->item;
+ e->dir = event->hudadd->dir;
+ e->align = event->hudadd->align;
+ e->offset = event->hudadd->offset;
+ e->world_pos = event->hudadd->world_pos;
+ 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.pos;
- delete event->hudadd.name;
- delete event->hudadd.scale;
- delete event->hudadd.text;
- delete event->hudadd.align;
- delete event->hudadd.offset;
- delete event->hudadd.world_pos;
- delete event->hudadd.size;
- delete event->hudadd.text2;
+ delete event->hudadd;
}
void Game::handleClientEvent_HudRemove(ClientEvent *event, CameraOrientation *cam)
@@ -2672,77 +2745,54 @@ void Game::handleClientEvent_HudChange(ClientEvent *event, CameraOrientation *ca
HudElement *e = nullptr;
- auto i = m_hud_server_to_client.find(event->hudchange.id);
+ auto i = m_hud_server_to_client.find(event->hudchange->id);
if (i != m_hud_server_to_client.end()) {
e = player->getHud(i->second);
}
if (e == nullptr) {
- delete event->hudchange.v3fdata;
- delete event->hudchange.v2fdata;
- delete event->hudchange.sdata;
- delete event->hudchange.v2s32data;
+ delete event->hudchange;
return;
}
- switch (event->hudchange.stat) {
- case HUD_STAT_POS:
- e->pos = *event->hudchange.v2fdata;
- break;
+#define CASE_SET(statval, prop, dataprop) \
+ case statval: \
+ e->prop = event->hudchange->dataprop; \
+ break
- case HUD_STAT_NAME:
- e->name = *event->hudchange.sdata;
- break;
+ switch (event->hudchange->stat) {
+ CASE_SET(HUD_STAT_POS, pos, v2fdata);
- case HUD_STAT_SCALE:
- e->scale = *event->hudchange.v2fdata;
- break;
+ CASE_SET(HUD_STAT_NAME, name, sdata);
- case HUD_STAT_TEXT:
- e->text = *event->hudchange.sdata;
- break;
+ CASE_SET(HUD_STAT_SCALE, scale, v2fdata);
- case HUD_STAT_NUMBER:
- e->number = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_TEXT, text, sdata);
- case HUD_STAT_ITEM:
- e->item = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_NUMBER, number, data);
- case HUD_STAT_DIR:
- e->dir = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_ITEM, item, data);
- case HUD_STAT_ALIGN:
- e->align = *event->hudchange.v2fdata;
- break;
+ CASE_SET(HUD_STAT_DIR, dir, data);
- case HUD_STAT_OFFSET:
- e->offset = *event->hudchange.v2fdata;
- break;
+ CASE_SET(HUD_STAT_ALIGN, align, v2fdata);
- case HUD_STAT_WORLD_POS:
- e->world_pos = *event->hudchange.v3fdata;
- break;
+ CASE_SET(HUD_STAT_OFFSET, offset, v2fdata);
- case HUD_STAT_SIZE:
- e->size = *event->hudchange.v2s32data;
- break;
+ CASE_SET(HUD_STAT_WORLD_POS, world_pos, v3fdata);
- case HUD_STAT_Z_INDEX:
- e->z_index = event->hudchange.data;
- break;
+ CASE_SET(HUD_STAT_SIZE, size, v2s32data);
- case HUD_STAT_TEXT2:
- e->text2 = *event->hudchange.sdata;
- break;
+ CASE_SET(HUD_STAT_Z_INDEX, z_index, data);
+
+ CASE_SET(HUD_STAT_TEXT2, text2, sdata);
+
+ CASE_SET(HUD_STAT_STYLE, style, data);
}
- delete event->hudchange.v3fdata;
- delete event->hudchange.v2fdata;
- delete event->hudchange.sdata;
- delete event->hudchange.v2s32data;
+#undef CASE_SET
+
+ delete event->hudchange;
}
void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
@@ -2797,6 +2847,7 @@ void Game::handleClientEvent_SetSky(ClientEvent *event, CameraOrientation *cam)
"custom"
);
}
+
delete event->set_sky;
}
@@ -2823,7 +2874,7 @@ 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);
delete event->star_params;
@@ -2860,7 +2911,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())
@@ -2876,11 +2927,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();
@@ -2919,7 +2976,7 @@ 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();
@@ -3260,9 +3317,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");
}
}
@@ -3294,7 +3350,8 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
const ItemStack &selected_item, const v3s16 &nodepos, const v3s16 &neighbourpos,
const PointedThing &pointed, const NodeMetadata *meta)
{
- std::string prediction = selected_def.node_placement_prediction;
+ const auto &prediction = selected_def.node_placement_prediction;
+
const NodeDefManager *nodedef = client->ndef();
ClientMap &map = client->getEnv().getClientMap();
MapNode node;
@@ -3323,8 +3380,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;
@@ -3364,8 +3421,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
if (!found) {
errorstream << "Node placement prediction failed for "
- << selected_def.name << " (places "
- << prediction
+ << selected_def.name << " (places " << prediction
<< ") - Name not known" << std::endl;
// Handle this as if prediction was empty
// Report to server
@@ -3376,9 +3432,14 @@ 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;
- if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
+ const u8 place_param2 = selected_def.place_param2;
+
+ if (place_param2) {
+ param2 = place_param2;
+ } else if (predicted_f.param_type_2 == CPT2_WALLMOUNTED ||
predicted_f.param_type_2 == CPT2_COLORED_WALLMOUNTED) {
v3s16 dir = nodepos - neighbourpos;
@@ -3389,9 +3450,7 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
} else {
param2 = dir.Z < 0 ? 5 : 4;
}
- }
-
- if (predicted_f.param_type_2 == CPT2_FACEDIR ||
+ } else if (predicted_f.param_type_2 == CPT2_FACEDIR ||
predicted_f.param_type_2 == CPT2_COLORED_FACEDIR) {
v3s16 dir = nodepos - floatToInt(client->getEnv().getLocalPlayer()->getPosition(), BS);
@@ -3402,11 +3461,9 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
}
}
- assert(param2 <= 5);
-
- //Check attachment if node is in group attached_node
- if (((ItemGroupList) predicted_f.groups)["attached_node"] != 0) {
- static v3s16 wallmounted_dirs[8] = {
+ // 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),
@@ -3431,11 +3488,11 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
}
// Apply color
- if ((predicted_f.param_type_2 == CPT2_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_WALLMOUNTED)) {
- const std::string &indexstr = selected_item.metadata.getString(
- "palette_index", 0);
+ 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) {
@@ -3475,11 +3532,10 @@ bool Game::nodePlacement(const ItemDefinition &selected_def,
soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
return false;
}
- } catch (InvalidPositionException &e) {
+ } catch (const InvalidPositionException &e) {
errorstream << "Node placement prediction failed for "
<< selected_def.name << " (places "
- << prediction
- << ") - Position not loaded" << std::endl;
+ << prediction << ") - Position not loaded" << std::endl;
soundmaker->m_player_rightpunch_sound = selected_def.sound_place_failed;
return false;
}
@@ -3549,7 +3605,8 @@ void Game::handleDigging(const PointedThing &pointed, const v3s16 &nodepos,
// cheat detection.
// Get digging parameters
DigParams params = getDigParams(nodedef_manager->get(n).groups,
- &selected_item.getToolCapabilities(itemdef_manager));
+ &selected_item.getToolCapabilities(itemdef_manager),
+ selected_item.wear);
// If can't dig, try hand
if (!params.diggable) {
@@ -3796,12 +3853,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;
+ }
+
+ /*
+ Update minimap pos and rotation
+ */
+ if (mapper && m_game_ui->m_flags.show_hud) {
+ mapper->setPos(floatToInt(player->getPosition(), BS));
+ mapper->setAngle(player->getYaw());
+ }
- updateChat(dtime, screensize);
+ /*
+ Get chat messages from client
+ */
+
+ updateChat(dtime);
/*
Inventory
@@ -3823,15 +3896,22 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
*/
runData.update_draw_list_timer += dtime;
+ 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;
}
+ if (RenderingEngine::get_shadow_renderer()) {
+ updateShadows();
+ }
+
m_game_ui->update(*stats, client, draw_control, cam, runData.pointed_old, gui_chat_console, dtime);
/*
@@ -3863,11 +3943,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 &&
@@ -3882,12 +3962,14 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
} catch (SettingNotFoundException) {
}
#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());
@@ -3904,25 +3986,7 @@ 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) {
/*
@@ -3945,11 +4009,12 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
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 */
@@ -3960,59 +4025,85 @@ 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 = fmod(getWickedTimeOfDay(in_timeofday) + 0.75f, 0.5f) + 0.25f;
+ const float offset_constant = 10000.0f;
+
+ v3f light(0.0f, 0.0f, -1.0f);
+ light.rotateXZBy(90);
+ light.rotateXYBy(timeoftheday * 360 - 90);
+ light.rotateYZBy(sky->getSkyBodyOrbitTilt());
+ 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
- */
-inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
+void FpsControl::reset()
{
- // 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;
-
- if (time > last_time) // Make sure time hasn't overflowed
- fps_timings->busy_time = time - last_time;
- else
- fps_timings->busy_time = 0;
+ last_time = porting::getTimeUs();
+}
- u32 frametime_min = 1000 / (
+/*
+ * On some computers framerate doesn't seem to be automatically limited
+ */
+void FpsControl::limit(IrrlichtDevice *device, f32 *dtime)
+{
+ const u64 frametime_min = 1000000.0f / (
device->isWindowFocused() && !g_menumgr.pausesGame()
? g_settings->getFloat("fps_max")
: g_settings->getFloat("fps_max_unfocused"));
- if (fps_timings->busy_time < frametime_min) {
- fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
- device->sleep(fps_timings->sleep_time);
+ u64 time = porting::getTimeUs();
+
+ if (time > last_time) // Make sure time hasn't overflowed
+ busy_time = time - last_time;
+ else
+ busy_time = 0;
+
+ 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;
}
@@ -4057,27 +4148,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 =
@@ -4095,15 +4165,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"
@@ -4203,16 +4273,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())
@@ -4229,10 +4301,13 @@ 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");
formspec->doPause = true;
+
+ if (simple_singleplayer_mode)
+ pauseAnimation();
}
/****************************************************************************/
@@ -4243,6 +4318,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,
@@ -4257,20 +4333,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;