From 429ecb2b94a66a11cf06be7303d05aa2038d19d2 Mon Sep 17 00:00:00 2001 From: Craig Robbins Date: Fri, 31 Oct 2014 22:13:04 +1000 Subject: [PATCH] Refactor the_game() to make it more understandable and maintainable. The following is a record of 31 commits before squashing: Revert "Remove m_ext_ptr in GUIFormSpecMenu, replaced by refcount mechanism" This reverts commit b49e5cfc7013cef7e9af79d17e04f7e7e4c377d4. Basic reformatting with astyle -- additional formatting will be modified, manually, as the need for it is encountered Start "outlining" what a MinetestApp class might look like Add MinetestApp::shutdown() Converted class member functions to camelCase and created protos for new functions First stage of connect to server done Add get itemdefs/nodedefs/media code Init clouds, camera, sky, init GUI, HUD Input handling Client events, camera, sound, draw Fix wield hand getting stuck digging and add debug text back Fix FPS Added profiler graph back Fix FPS issue Need to work out what went wrong and clean up the copy/paste stuff Annotate Various: Rewrote limitFps() Limited scope of some variables Jitter calcs Reduce scope of objects Move some stuff out of ::run and minor formatting cleanup Scope reduction Function splits Removed old (broken) limitFps() Added exception handling back Fixed some formatting Reverted commented out unit tests (uncommented them) Slow clouds down on loading and media screens so the behaviour is like the original the_game() Formatting/style (no functional changes) Manually reapply upstream b49e5cf: Remove m_ext_ptr in GUIFormSpecMenu, replaced by refcount mechanism Fixed silly errors on my part Minor formatting cleanups Removed strange differentiation in FPS limiting when loading FPS limiting was done differently if cloud_menu_background was true, which does not make sense Cleaning up Add some comments --- src/game.cpp | 5597 +++++++++++++++++++++++++++----------------------- src/game.h | 55 +- src/main.cpp | 2 +- 3 files changed, 3037 insertions(+), 2617 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index 2e4485e36..03f526166 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -61,7 +61,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "sky.h" #include "sound.h" #if USE_SOUND - #include "sound_openal.h" +#include "sound_openal.h" #endif #include "event_manager.h" #include @@ -79,8 +79,7 @@ with this program; if not, write to the Free Software Foundation, Inc., Text input system */ -struct TextDestNodeMetadata : public TextDest -{ +struct TextDestNodeMetadata : public TextDest { TextDestNodeMetadata(v3s16 p, Client *client) { m_p = p; @@ -90,8 +89,8 @@ struct TextDestNodeMetadata : public TextDest void gotText(std::wstring text) { std::string ntext = wide_to_narrow(text); - infostream<<"Submitting 'text' field of node at ("< fields; fields["text"] = ntext; m_client->sendNodemetaFields(m_p, "", fields); @@ -105,8 +104,7 @@ struct TextDestNodeMetadata : public TextDest Client *m_client; }; -struct TextDestPlayerInventory : public TextDest -{ +struct TextDestPlayerInventory : public TextDest { TextDestPlayerInventory(Client *client) { m_client = client; @@ -125,8 +123,7 @@ struct TextDestPlayerInventory : public TextDest Client *m_client; }; -struct LocalFormspecHandler : public TextDest -{ +struct LocalFormspecHandler : public TextDest { LocalFormspecHandler(); LocalFormspecHandler(std::string formname) : m_client(0) @@ -140,7 +137,8 @@ struct LocalFormspecHandler : public TextDest m_formname = formname; } - void gotText(std::wstring message) { + void gotText(std::wstring message) + { errorstream << "LocalFormspecHandler::gotText old style message received" << std::endl; } @@ -180,19 +178,23 @@ struct LocalFormspecHandler : public TextDest return; } } + if (m_formname == "MT_CHAT_MENU") { assert(m_client != 0); + if ((fields.find("btn_send") != fields.end()) || (fields.find("quit") != fields.end())) { if (fields.find("f_text") != fields.end()) { m_client->typeChatMessage(narrow_to_wide(fields["f_text"])); } + return; } } if (m_formname == "MT_DEATH_SCREEN") { assert(m_client != 0); + if ((fields.find("btn_respawn") != fields.end())) { m_client->sendRespawn(); return; @@ -205,18 +207,19 @@ struct LocalFormspecHandler : public TextDest } // don't show error message for unhandled cursor keys - if ( (fields.find("key_up") != fields.end()) || - (fields.find("key_down") != fields.end()) || - (fields.find("key_left") != fields.end()) || - (fields.find("key_right") != fields.end())) { + if ((fields.find("key_up") != fields.end()) || + (fields.find("key_down") != fields.end()) || + (fields.find("key_left") != fields.end()) || + (fields.find("key_right") != fields.end())) { return; } errorstream << "LocalFormspecHandler::gotText unhandled >" << m_formname << "< event" << std::endl; int i = 0; - for (std::map::iterator iter = fields.begin(); + + for (std::map::iterator iter = fields.begin(); iter != fields.end(); iter++) { - errorstream << "\t"<< i << ": " << iter->first << "=" << iter->second << std::endl; + errorstream << "\t" << i << ": " << iter->first << "=" << iter->second << std::endl; i++; } } @@ -237,15 +240,19 @@ public: std::string getForm() { NodeMetadata *meta = m_map->getNodeMetadata(m_p); - if(!meta) + + if (!meta) return ""; + return meta->getString("formspec"); } std::string resolveText(std::string str) { NodeMetadata *meta = m_map->getNodeMetadata(m_p); - if(!meta) + + if (!meta) return str; + return meta->resolveString(str); } @@ -262,7 +269,7 @@ public: } std::string getForm() { - LocalPlayer* player = m_client->getEnv().getLocalPlayer(); + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); return player->inventory_formspec; } @@ -272,12 +279,12 @@ public: /* Check if a node is pointable */ -inline bool isPointableNode(const MapNode& n, - Client *client, bool liquids_pointable) +inline bool isPointableNode(const MapNode &n, + Client *client, bool liquids_pointable) { const ContentFeatures &features = client->getNodeDefManager()->get(n); return features.pointable || - (liquids_pointable && features.isLiquid()); + (liquids_pointable && features.isLiquid()); } /* @@ -299,15 +306,12 @@ PointedThing getPointedThing(Client *client, v3f player_position, f32 mindistance = BS * 1001; // First try to find a pointed at active object - if(look_for_object) - { - selected_object = client->getSelectedActiveObject(d*BS, - camera_position, shootline); + if (look_for_object) { + selected_object = client->getSelectedActiveObject(d * BS, + camera_position, shootline); - if(selected_object != NULL) - { - if(selected_object->doShowSelectionBox()) - { + if (selected_object != NULL) { + if (selected_object->doShowSelectionBox()) { aabb3f *selection_box = selected_object->getSelectionBox(); // Box should exist because object was // returned in the first place @@ -315,8 +319,8 @@ PointedThing getPointedThing(Client *client, v3f player_position, v3f pos = selected_object->getPosition(); hilightboxes.push_back(aabb3f( - selection_box->MinEdge + pos - intToFloat(camera_offset, BS), - selection_box->MaxEdge + pos - intToFloat(camera_offset, BS))); + selection_box->MinEdge + pos - intToFloat(camera_offset, BS), + selection_box->MaxEdge + pos - intToFloat(camera_offset, BS))); } mindistance = (selected_object->getPosition() - camera_position).getLength(); @@ -335,98 +339,99 @@ PointedThing getPointedThing(Client *client, v3f player_position, <0 ? a : 1); - s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1); - s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1); + s16 ystart = pos_i.Y + 0 - (camera_direction.Y < 0 ? a : 1); + s16 zstart = pos_i.Z - (camera_direction.Z < 0 ? a : 1); + s16 xstart = pos_i.X - (camera_direction.X < 0 ? a : 1); + s16 yend = pos_i.Y + 1 + (camera_direction.Y > 0 ? a : 1); + s16 zend = pos_i.Z + (camera_direction.Z > 0 ? a : 1); + s16 xend = pos_i.X + (camera_direction.X > 0 ? a : 1); // Prevent signed number overflow - if(yend==32767) - yend=32766; - if(zend==32767) - zend=32766; - if(xend==32767) - xend=32766; + if (yend == 32767) + yend = 32766; - for(s16 y = ystart; y <= yend; y++) - for(s16 z = zstart; z <= zend; z++) - for(s16 x = xstart; x <= xend; x++) - { - MapNode n; - try - { - n = map.getNode(v3s16(x,y,z)); - } - catch(InvalidPositionException &e) - { - continue; - } - if(!isPointableNode(n, client, liquids_pointable)) - continue; + if (zend == 32767) + zend = 32766; - std::vector boxes = n.getSelectionBoxes(nodedef); + if (xend == 32767) + xend = 32766; - v3s16 np(x,y,z); - v3f npf = intToFloat(np, BS); + for (s16 y = ystart; y <= yend; y++) + for (s16 z = zstart; z <= zend; z++) + for (s16 x = xstart; x <= xend; x++) { + MapNode n; - for(std::vector::const_iterator - i = boxes.begin(); - i != boxes.end(); i++) - { - aabb3f box = *i; - box.MinEdge += npf; - box.MaxEdge += npf; - - for(u16 j=0; j<6; j++) - { - v3s16 facedir = g_6dirs[j]; - aabb3f facebox = box; - - f32 d = 0.001*BS; - if(facedir.X > 0) - facebox.MinEdge.X = facebox.MaxEdge.X-d; - else if(facedir.X < 0) - facebox.MaxEdge.X = facebox.MinEdge.X+d; - else if(facedir.Y > 0) - facebox.MinEdge.Y = facebox.MaxEdge.Y-d; - else if(facedir.Y < 0) - facebox.MaxEdge.Y = facebox.MinEdge.Y+d; - else if(facedir.Z > 0) - facebox.MinEdge.Z = facebox.MaxEdge.Z-d; - else if(facedir.Z < 0) - facebox.MaxEdge.Z = facebox.MinEdge.Z+d; - - v3f centerpoint = facebox.getCenter(); - f32 distance = (centerpoint - camera_position).getLength(); - if(distance >= mindistance) + try { + n = map.getNode(v3s16(x, y, z)); + } catch (InvalidPositionException &e) { continue; - if(!facebox.intersectsWithLine(shootline)) + } + + if (!isPointableNode(n, client, liquids_pointable)) continue; - v3s16 np_above = np + facedir; + std::vector boxes = n.getSelectionBoxes(nodedef); - result.type = POINTEDTHING_NODE; - result.node_undersurface = np; - result.node_abovesurface = np_above; - mindistance = distance; + v3s16 np(x, y, z); + v3f npf = intToFloat(np, BS); - hilightboxes.clear(); - if (!g_settings->getBool("enable_node_highlighting")) { - for(std::vector::const_iterator - i2 = boxes.begin(); - i2 != boxes.end(); i2++) - { - aabb3f box = *i2; - box.MinEdge += npf + v3f(-d,-d,-d) - intToFloat(camera_offset, BS); - box.MaxEdge += npf + v3f(d,d,d) - intToFloat(camera_offset, BS); - hilightboxes.push_back(box); + for (std::vector::const_iterator + i = boxes.begin(); + i != boxes.end(); i++) { + aabb3f box = *i; + box.MinEdge += npf; + box.MaxEdge += npf; + + for (u16 j = 0; j < 6; j++) { + v3s16 facedir = g_6dirs[j]; + aabb3f facebox = box; + + f32 d = 0.001 * BS; + + if (facedir.X > 0) + facebox.MinEdge.X = facebox.MaxEdge.X - d; + else if (facedir.X < 0) + facebox.MaxEdge.X = facebox.MinEdge.X + d; + else if (facedir.Y > 0) + facebox.MinEdge.Y = facebox.MaxEdge.Y - d; + else if (facedir.Y < 0) + facebox.MaxEdge.Y = facebox.MinEdge.Y + d; + else if (facedir.Z > 0) + facebox.MinEdge.Z = facebox.MaxEdge.Z - d; + else if (facedir.Z < 0) + facebox.MaxEdge.Z = facebox.MinEdge.Z + d; + + v3f centerpoint = facebox.getCenter(); + f32 distance = (centerpoint - camera_position).getLength(); + + if (distance >= mindistance) + continue; + + if (!facebox.intersectsWithLine(shootline)) + continue; + + v3s16 np_above = np + facedir; + + result.type = POINTEDTHING_NODE; + result.node_undersurface = np; + result.node_abovesurface = np_above; + mindistance = distance; + + hilightboxes.clear(); + + if (!g_settings->getBool("enable_node_highlighting")) { + for (std::vector::const_iterator + i2 = boxes.begin(); + i2 != boxes.end(); i2++) { + aabb3f box = *i2; + box.MinEdge += npf + v3f(-d, -d, -d) - intToFloat(camera_offset, BS); + box.MaxEdge += npf + v3f(d, d, d) - intToFloat(camera_offset, BS); + hilightboxes.push_back(box); + } + } } } - } - } - } // for coords + } // for coords return result; } @@ -437,12 +442,9 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, gui::IGUIFont *font, u32 text_height, u32 show_profiler, u32 show_profiler_max) { - if(show_profiler == 0) - { + if (show_profiler == 0) { guitext_profiler->setVisible(false); - } - else - { + } else { std::ostringstream os(std::ios_base::binary); g_profiler->printPage(os, show_profiler, show_profiler_max); @@ -451,11 +453,13 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, guitext_profiler->setVisible(true); s32 w = font->getDimension(text.c_str()).Width; - if(w < 400) + + if (w < 400) w = 400; - core::rect rect(6, 4+(text_height+5)*2, 12+w, - 8+(text_height+5)*2 + - font->getDimension(text.c_str()).Height); + + core::rect rect(6, 4 + (text_height + 5) * 2, 12 + w, + 8 + (text_height + 5) * 2 + + font->getDimension(text.c_str()).Height); guitext_profiler->setRelativePosition(rect); guitext_profiler->setVisible(true); } @@ -464,15 +468,15 @@ void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, class ProfilerGraph { private: - struct Piece{ + struct Piece { Profiler::GraphValues values; }; - struct Meta{ + struct Meta { float min; float max; video::SColor color; - Meta(float initial=0, video::SColor color= - video::SColor(255,255,255,255)): + Meta(float initial = 0, + video::SColor color = video::SColor(255, 255, 255, 255)): min(initial), max(initial), color(color) @@ -491,52 +495,60 @@ public: Piece piece; piece.values = values; m_log.push_back(piece); - while(m_log.size() > m_log_max_size) + + while (m_log.size() > m_log_max_size) m_log.erase(m_log.begin()); } void draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, - gui::IGUIFont* font) const + gui::IGUIFont *font) const { std::map m_meta; - for(std::list::const_iterator k = m_log.begin(); - k != m_log.end(); k++) - { + + for (std::list::const_iterator k = m_log.begin(); + k != m_log.end(); k++) { const Piece &piece = *k; - for(Profiler::GraphValues::const_iterator i = piece.values.begin(); - i != piece.values.end(); i++){ + + for (Profiler::GraphValues::const_iterator i = piece.values.begin(); + i != piece.values.end(); i++) { const std::string &id = i->first; const float &value = i->second; std::map::iterator j = - m_meta.find(id); - if(j == m_meta.end()){ + m_meta.find(id); + + if (j == m_meta.end()) { m_meta[id] = Meta(value); continue; } - if(value < j->second.min) + + if (value < j->second.min) j->second.min = value; - if(value > j->second.max) + + if (value > j->second.max) j->second.max = value; } } // Assign colors static const video::SColor usable_colors[] = { - video::SColor(255,255,100,100), - video::SColor(255,90,225,90), - video::SColor(255,100,100,255), - video::SColor(255,255,150,50), - video::SColor(255,220,220,100) + video::SColor(255, 255, 100, 100), + video::SColor(255, 90, 225, 90), + video::SColor(255, 100, 100, 255), + video::SColor(255, 255, 150, 50), + video::SColor(255, 220, 220, 100) }; static const u32 usable_colors_count = - sizeof(usable_colors) / sizeof(*usable_colors); + sizeof(usable_colors) / sizeof(*usable_colors); u32 next_color_i = 0; - for(std::map::iterator i = m_meta.begin(); - i != m_meta.end(); i++){ + + for (std::map::iterator i = m_meta.begin(); + i != m_meta.end(); i++) { Meta &meta = i->second; - video::SColor color(255,200,200,200); - if(next_color_i < usable_colors_count) + video::SColor color(255, 200, 200, 200); + + if (next_color_i < usable_colors_count) color = usable_colors[next_color_i++]; + meta.color = color; } @@ -554,80 +566,92 @@ public: }*/ s32 meta_i = 0; - for(std::map::const_iterator i = m_meta.begin(); - i != m_meta.end(); i++){ + + for (std::map::const_iterator i = m_meta.begin(); + i != m_meta.end(); i++) { const std::string &id = i->first; const Meta &meta = i->second; s32 x = x_left; s32 y = y_bottom - meta_i * 50; float show_min = meta.min; float show_max = meta.max; - if(show_min >= -0.0001 && show_max >= -0.0001){ - if(show_min <= show_max * 0.5) + + if (show_min >= -0.0001 && show_max >= -0.0001) { + if (show_min <= show_max * 0.5) show_min = 0; } + s32 texth = 15; char buf[10]; snprintf(buf, 10, "%.3g", show_max); font->draw(narrow_to_wide(buf).c_str(), core::rect(textx, y - graphh, - textx2, y - graphh + texth), + textx2, y - graphh + texth), meta.color); snprintf(buf, 10, "%.3g", show_min); font->draw(narrow_to_wide(buf).c_str(), core::rect(textx, y - texth, - textx2, y), + textx2, y), meta.color); font->draw(narrow_to_wide(id).c_str(), - core::rect(textx, y - graphh/2 - texth/2, - textx2, y - graphh/2 + texth/2), + core::rect(textx, y - graphh / 2 - texth / 2, + textx2, y - graphh / 2 + texth / 2), meta.color); s32 graph1y = y; s32 graph1h = graphh; bool relativegraph = (show_min != 0 && show_min != show_max); float lastscaledvalue = 0.0; bool lastscaledvalue_exists = false; - for(std::list::const_iterator j = m_log.begin(); - j != m_log.end(); j++) - { + + for (std::list::const_iterator j = m_log.begin(); + j != m_log.end(); j++) { const Piece &piece = *j; float value = 0; bool value_exists = false; Profiler::GraphValues::const_iterator k = - piece.values.find(id); - if(k != piece.values.end()){ + piece.values.find(id); + + if (k != piece.values.end()) { value = k->second; value_exists = true; } - if(!value_exists){ + + if (!value_exists) { x++; lastscaledvalue_exists = false; continue; } + float scaledvalue = 1.0; - if(show_max != show_min) + + if (show_max != show_min) scaledvalue = (value - show_min) / (show_max - show_min); - if(scaledvalue == 1.0 && value == 0){ + + if (scaledvalue == 1.0 && value == 0) { x++; lastscaledvalue_exists = false; continue; } - if(relativegraph){ - if(lastscaledvalue_exists){ + + if (relativegraph) { + if (lastscaledvalue_exists) { s32 ivalue1 = lastscaledvalue * graph1h; s32 ivalue2 = scaledvalue * graph1h; - driver->draw2DLine(v2s32(x-1, graph1y - ivalue1), - v2s32(x, graph1y - ivalue2), meta.color); + driver->draw2DLine(v2s32(x - 1, graph1y - ivalue1), + v2s32(x, graph1y - ivalue2), meta.color); } + lastscaledvalue = scaledvalue; lastscaledvalue_exists = true; - } else{ + } else { s32 ivalue = scaledvalue * graph1h; driver->draw2DLine(v2s32(x, graph1y), - v2s32(x, graph1y - ivalue), meta.color); + v2s32(x, graph1y - ivalue), meta.color); } + x++; } + meta_i++; } } @@ -643,8 +667,10 @@ public: p(p), n(n) {} - const char* getType() const - {return "NodeDug";} + const char *getType() const + { + return "NodeDug"; + } }; class SoundMaker @@ -667,7 +693,7 @@ public: void playPlayerStep() { - if(m_player_step_timer <= 0 && m_player_step_sound.exists()){ + if (m_player_step_timer <= 0 && m_player_step_sound.exists()) { m_player_step_timer = 0.03; m_sound->playSound(m_player_step_sound, false); } @@ -675,13 +701,13 @@ public: static void viewBobbingStep(MtEvent *e, void *data) { - SoundMaker *sm = (SoundMaker*)data; + SoundMaker *sm = (SoundMaker *)data; sm->playPlayerStep(); } static void playerRegainGround(MtEvent *e, void *data) { - SoundMaker *sm = (SoundMaker*)data; + SoundMaker *sm = (SoundMaker *)data; sm->playPlayerStep(); } @@ -692,32 +718,32 @@ public: static void cameraPunchLeft(MtEvent *e, void *data) { - SoundMaker *sm = (SoundMaker*)data; + SoundMaker *sm = (SoundMaker *)data; sm->m_sound->playSound(sm->m_player_leftpunch_sound, false); } static void cameraPunchRight(MtEvent *e, void *data) { - SoundMaker *sm = (SoundMaker*)data; + SoundMaker *sm = (SoundMaker *)data; sm->m_sound->playSound(sm->m_player_rightpunch_sound, false); } static void nodeDug(MtEvent *e, void *data) { - SoundMaker *sm = (SoundMaker*)data; - NodeDugEvent *nde = (NodeDugEvent*)e; + SoundMaker *sm = (SoundMaker *)data; + NodeDugEvent *nde = (NodeDugEvent *)e; sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false); } static void playerDamage(MtEvent *e, void *data) { - SoundMaker *sm = (SoundMaker*)data; + SoundMaker *sm = (SoundMaker *)data; sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false); } static void playerFallingDamage(MtEvent *e, void *data) { - SoundMaker *sm = (SoundMaker*)data; + SoundMaker *sm = (SoundMaker *)data; sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false); } @@ -744,13 +770,13 @@ class GameOnDemandSoundFetcher: public OnDemandSoundFetcher { std::set m_fetched; public: - void fetchSounds(const std::string &name, std::set &dst_paths, std::set &dst_datas) { - if(m_fetched.count(name)) + if (m_fetched.count(name)) return; + m_fetched.insert(name); std::string base = porting::path_share + DIR_DELIM + "testsounds"; dst_paths.insert(base + DIR_DELIM + name + ".ogg"); @@ -776,7 +802,7 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter public: GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off, - f32 *fog_range, Client *client): + f32 *fog_range, Client *client) : m_sky(sky), m_force_fog_off(force_fog_off), m_fog_range(fog_range), @@ -787,7 +813,7 @@ public: virtual void onSetConstants(video::IMaterialRendererServices *services, bool is_highlevel) { - if(!is_highlevel) + if (!is_highlevel) return; // Background color @@ -802,9 +828,11 @@ public: services->setPixelShaderConstant("skyBgColor", bgcolorfa, 4); // Fog distance - float fog_distance = 10000*BS; - if(g_settings->getBool("enable_fog") && !*m_force_fog_off) + float fog_distance = 10000 * BS; + + if (g_settings->getBool("enable_fog") && !*m_force_fog_off) fog_distance = *m_fog_range; + services->setPixelShaderConstant("fogDistance", &fog_distance, 1); // Day-night ratio @@ -817,10 +845,10 @@ public: services->setPixelShaderConstant("animationTimer", &animation_timer_f, 1); services->setVertexShaderConstant("animationTimer", &animation_timer_f, 1); - LocalPlayer* player = m_client->getEnv().getLocalPlayer(); + LocalPlayer *player = m_client->getEnv().getLocalPlayer(); v3f eye_position = player->getEyePosition(); - services->setPixelShaderConstant("eyePosition", (irr::f32*)&eye_position, 3); - services->setVertexShaderConstant("eyePosition", (irr::f32*)&eye_position, 3); + services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); + services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3); // Uniform sampler layers int layer0 = 0; @@ -828,13 +856,13 @@ public: int layer2 = 2; // before 1.8 there isn't a "integer interface", only float #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8) - services->setPixelShaderConstant("baseTexture" , (irr::f32*)&layer0, 1); - services->setPixelShaderConstant("normalTexture" , (irr::f32*)&layer1, 1); - services->setPixelShaderConstant("useNormalmap" , (irr::f32*)&layer2, 1); + services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1); + services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1); + services->setPixelShaderConstant("useNormalmap" , (irr::f32 *)&layer2, 1); #else - services->setPixelShaderConstant("baseTexture" , (irr::s32*)&layer0, 1); - services->setPixelShaderConstant("normalTexture" , (irr::s32*)&layer1, 1); - services->setPixelShaderConstant("useNormalmap" , (irr::s32*)&layer2, 1); + services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1); + services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1); + services->setPixelShaderConstant("useNormalmap" , (irr::s32 *)&layer2, 1); #endif } }; @@ -846,105 +874,120 @@ bool nodePlacementPrediction(Client &client, INodeDefManager *nodedef = client.ndef(); ClientMap &map = client.getEnv().getClientMap(); - if(prediction != "" && !nodedef->get(map.getNode(nodepos)).rightclickable) - { - verbosestream<<"Node placement prediction for " - <get(map.getNode(nodepos)).rightclickable) { + verbosestream << "Node placement prediction for " + << playeritem_def.name << " is " + << prediction << std::endl; v3s16 p = neighbourpos; + // Place inside node itself if buildable_to - try{ + try { MapNode n_under = map.getNode(nodepos); - if(nodedef->get(n_under).buildable_to) + + if (nodedef->get(n_under).buildable_to) p = nodepos; else if (!nodedef->get(map.getNode(p)).buildable_to) return false; - }catch(InvalidPositionException &e){} + } catch (InvalidPositionException &e) {} + // Find id of predicted node content_t id; bool found = nodedef->getId(prediction, id); - if(!found){ - errorstream<<"Node placement prediction failed for " - <get(id).param_type_2 == CPT2_WALLMOUNTED){ + + if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) { v3s16 dir = nodepos - neighbourpos; - if(abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))){ + + if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) { param2 = dir.Y < 0 ? 1 : 0; - } else if(abs(dir.X) > abs(dir.Z)){ + } else if (abs(dir.X) > abs(dir.Z)) { param2 = dir.X < 0 ? 3 : 2; } else { param2 = dir.Z < 0 ? 5 : 4; } } - if(nodedef->get(id).param_type_2 == CPT2_FACEDIR){ + + if (nodedef->get(id).param_type_2 == CPT2_FACEDIR) { v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS); - if(abs(dir.X) > abs(dir.Z)){ + + if (abs(dir.X) > abs(dir.Z)) { param2 = dir.X < 0 ? 3 : 1; } else { param2 = dir.Z < 0 ? 2 : 0; } } - assert(param2 >= 0 && param2 <= 5); + + assert(param2 <= 5); + //Check attachment if node is in group attached_node - if(((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0){ + if (((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0) { 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), + v3s16(0, 1, 0), + v3s16(0, -1, 0), + v3s16(1, 0, 0), + v3s16(-1, 0, 0), + v3s16(0, 0, 1), + v3s16(0, 0, -1), }; v3s16 pp; - if(nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) + + if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) pp = p + wallmounted_dirs[param2]; else - pp = p + v3s16(0,-1,0); - if(!nodedef->get(map.getNode(pp)).walkable) + pp = p + v3s16(0, -1, 0); + + if (!nodedef->get(map.getNode(pp)).walkable) return false; } + // Add node to client map MapNode n(id, 0, param2); - try{ - LocalPlayer* player = client.getEnv().getLocalPlayer(); + + try { + LocalPlayer *player = client.getEnv().getLocalPlayer(); // Dont 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 || - (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))) { + (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))) { - // This triggers the required mesh update too - client.addNode(p, n); - return true; - } - }catch(InvalidPositionException &e){ - errorstream<<"Node placement prediction failed for " - <doPause = false; /* @@ -954,8 +997,8 @@ static inline void create_formspec_menu(GUIFormSpecMenu** cur_formspec, remaining reference (i.e. the menu was removed) and delete it in that case. */ - } - else { + + } else { (*cur_formspec)->setFormSource(fs_src); (*cur_formspec)->setTextDest(txt_dest); } @@ -967,10 +1010,10 @@ static inline void create_formspec_menu(GUIFormSpecMenu** cur_formspec, #define SIZE_TAG "size[11,5.5,true]" #endif -static void show_chat_menu(GUIFormSpecMenu** cur_formspec, +static void show_chat_menu(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, - IWritableTextureSource* tsrc, IrrlichtDevice * device, - Client* client, std::string text) + IWritableTextureSource *tsrc, IrrlichtDevice *device, + Client *client, std::string text) { std::string formspec = FORMSPEC_VERSION_STRING @@ -982,15 +1025,15 @@ static void show_chat_menu(GUIFormSpecMenu** cur_formspec, /* Create menu */ /* Note: FormspecFormSource and LocalFormspecHandler * are deleted by guiFormSpecMenu */ - FormspecFormSource* fs_src = new FormspecFormSource(formspec); - LocalFormspecHandler* txt_dst = new LocalFormspecHandler("MT_CHAT_MENU", client); + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_CHAT_MENU", client); create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); } -static void show_deathscreen(GUIFormSpecMenu** cur_formspec, +static void show_deathscreen(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, - IWritableTextureSource* tsrc, IrrlichtDevice * device, Client* client) + IWritableTextureSource *tsrc, IrrlichtDevice *device, Client *client) { std::string formspec = std::string(FORMSPEC_VERSION_STRING) + @@ -1003,67 +1046,67 @@ static void show_deathscreen(GUIFormSpecMenu** cur_formspec, /* Create menu */ /* Note: FormspecFormSource and LocalFormspecHandler * are deleted by guiFormSpecMenu */ - FormspecFormSource* fs_src = new FormspecFormSource(formspec); - LocalFormspecHandler* txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); + FormspecFormSource *fs_src = new FormspecFormSource(formspec); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client); create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); } /******************************************************************************/ -static void show_pause_menu(GUIFormSpecMenu** cur_formspec, +static void show_pause_menu(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, - IWritableTextureSource* tsrc, IrrlichtDevice * device, + IWritableTextureSource *tsrc, IrrlichtDevice *device, bool singleplayermode) { #ifdef __ANDROID__ std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" - "No menu visible:\n" - "- single tap: button activate\n" - "- double tap: place/use\n" - "- slide finger: look around\n" - "Menu/Inventory visible:\n" - "- double tap (outside):\n" - " -->close\n" - "- touch stack, touch slot:\n" - " --> move stack\n" - "- touch&drag, tap 2nd finger\n" - " --> place single item to slot\n" - )); + "No menu visible:\n" + "- single tap: button activate\n" + "- double tap: place/use\n" + "- slide finger: look around\n" + "Menu/Inventory visible:\n" + "- double tap (outside):\n" + " -->close\n" + "- touch stack, touch slot:\n" + " --> move stack\n" + "- touch&drag, tap 2nd finger\n" + " --> place single item to slot\n" + )); #else std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n" - "- WASD: move\n" - "- Space: jump/climb\n" - "- Shift: sneak/go down\n" - "- Q: drop item\n" - "- I: inventory\n" - "- Mouse: turn/look\n" - "- Mouse left: dig/punch\n" - "- Mouse right: place/use\n" - "- Mouse wheel: select item\n" - "- T: chat\n" - )); + "- WASD: move\n" + "- Space: jump/climb\n" + "- Shift: sneak/go down\n" + "- Q: drop item\n" + "- I: inventory\n" + "- Mouse: turn/look\n" + "- Mouse left: dig/punch\n" + "- Mouse right: place/use\n" + "- Mouse wheel: select item\n" + "- T: chat\n" + )); #endif float ypos = singleplayermode ? 0.5 : 0.1; std::ostringstream os; os << FORMSPEC_VERSION_STRING << SIZE_TAG - << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" - << wide_to_narrow(wstrgettext("Continue")) << "]"; + << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;" + << wide_to_narrow(wstrgettext("Continue")) << "]"; if (!singleplayermode) { os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;" - << wide_to_narrow(wstrgettext("Change Password")) << "]"; - } + << wide_to_narrow(wstrgettext("Change Password")) << "]"; + } os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;" - << wide_to_narrow(wstrgettext("Sound Volume")) << "]"; + << wide_to_narrow(wstrgettext("Sound Volume")) << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;" - << wide_to_narrow(wstrgettext("Change Keys")) << "]"; + << wide_to_narrow(wstrgettext("Change Keys")) << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;" - << wide_to_narrow(wstrgettext("Exit to Menu")) << "]"; + << wide_to_narrow(wstrgettext("Exit to Menu")) << "]"; os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;" - << wide_to_narrow(wstrgettext("Exit to OS")) << "]" + << wide_to_narrow(wstrgettext("Exit to OS")) << "]" << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]" << "textarea[0.4,0.25;3.5,6;;" << "Minetest\n" << minetest_build_info << "\n" @@ -1073,8 +1116,8 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec, /* Create menu */ /* Note: FormspecFormSource and LocalFormspecHandler * * are deleted by guiFormSpecMenu */ - FormspecFormSource* fs_src = new FormspecFormSource(os.str()); - LocalFormspecHandler* txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); + FormspecFormSource *fs_src = new FormspecFormSource(os.str()); + LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU"); create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL); @@ -1082,21 +1125,22 @@ static void show_pause_menu(GUIFormSpecMenu** cur_formspec, } /******************************************************************************/ -static void updateChat(Client& client, f32 dtime, bool show_debug, - const v2u32& screensize, bool show_chat, u32 show_profiler, - ChatBackend& chat_backend, gui::IGUIStaticText* guitext_chat, - gui::IGUIFont* font) +static void updateChat(Client &client, f32 dtime, bool show_debug, + const v2u32 &screensize, bool show_chat, u32 show_profiler, + ChatBackend &chat_backend, gui::IGUIStaticText *guitext_chat, + gui::IGUIFont *font) { // Add chat log output for errors to be shown in chat static LogOutputBuffer chat_log_error_buf(LMT_ERROR); // Get new messages from error log buffer - while(!chat_log_error_buf.empty()) { + while (!chat_log_error_buf.empty()) { chat_backend.addMessage(L"", narrow_to_wide(chat_log_error_buf.get())); } // Get new messages from client std::wstring message; + while (client.getChatMessage(message)) { chat_backend.addUnparsedMessage(message); } @@ -1115,163 +1159,795 @@ static void updateChat(Client& client, f32 dtime, bool show_debug, // Update gui element size and position s32 chat_y = 5 + line_height; + if (show_debug) chat_y += line_height; // first pass to calculate height of text to be set s32 width = std::min(font->getDimension(recent_chat.c_str()).Width + 10, - porting::getWindowSize().X - 20); + porting::getWindowSize().X - 20); core::rect rect(10, chat_y, width, chat_y + porting::getWindowSize().Y); guitext_chat->setRelativePosition(rect); - //now use real height of text and adjust rect according to this size + //now use real height of text and adjust rect according to this size rect = core::rect(10, chat_y, width, - chat_y + guitext_chat->getTextHeight()); + chat_y + guitext_chat->getTextHeight()); guitext_chat->setRelativePosition(rect); // Don't show chat if disabled or empty or profiler is enabled guitext_chat->setVisible( - show_chat && recent_chat_count != 0 && !show_profiler); + show_chat && recent_chat_count != 0 && !show_profiler); } -/******************************************************************************/ -void the_game(bool &kill, bool random_input, InputHandler *input, - IrrlichtDevice *device, gui::IGUIFont* font, std::string map_dir, - std::string playername, std::string password, - std::string address /* If "", local server is used */, - u16 port, std::wstring &error_message, ChatBackend &chat_backend, - const SubgameSpec &gamespec /* Used for local game */, - bool simple_singleplayer_mode) + + +/**************************************************************************** + THE GAME + ****************************************************************************/ + +const float object_hit_delay = 0.2; + +struct FpsControl { + u32 last_time, busy_time, sleep_time; +}; + + +/* The reason the following structs are not anonymous structs within the + * class is that they are not used by the majority of member functions and + * many functions that do require objects of thse types do not modify them + * (so they can be passed as a const qualified parameter) + */ + +struct CameraOrientation { + f32 camera_yaw; // "right/left" + f32 camera_pitch; // "up/down" +}; + +//TODO: Needs a better name because fog_range etc are included +struct InteractParams { + u16 dig_index; + u16 new_playeritem; + PointedThing pointed_old; + bool digging; + bool ldown_for_dig; + bool left_punch; + float nodig_delay_timer; + float dig_time; + float dig_time_complete; + float repeat_rightclick_timer; + float object_hit_delay_timer; + float time_from_last_punch; + ClientActiveObject *selected_object; + + float jump_timer; + float damage_flash; + float update_draw_list_timer; + float statustext_time; + + f32 fog_range; + + v3f update_draw_list_last_cam_dir; + + u32 profiler_current_page; + u32 profiler_max_page; // Number of pages +}; + +struct Jitter { + f32 max, min, avg, counter, max_sample, min_sample, max_fraction; +}; + +struct RunStats { + u32 drawtime; + u32 beginscenetime; + u32 endscenetime; + + Jitter dtime_jitter, busy_time_jitter; +}; + +/* Flags that can, or may, change during main game loop + */ +struct VolatileRunFlags { + bool invert_mouse; + bool show_chat; + bool show_hud; + bool force_fog_off; + bool show_debug; + bool show_profiler_graph; + bool disable_camera_update; + bool first_loop_after_window_activation; + bool camera_offset_changed; +}; + + +/* This is not intended to be a public class. If a public class becomes + * desirable then it may be better to create another 'wrapper' class that + * hides most of the stuff in this class (nothing in this class is required + * by any other file) but exposes the public methods/data only. + */ +class MinetestApp { - GUIFormSpecMenu* current_formspec = 0; - video::IVideoDriver* driver = device->getVideoDriver(); - scene::ISceneManager* smgr = device->getSceneManager(); +public: + MinetestApp(); + ~MinetestApp(); - // Calculate text height using the font - u32 text_height = font->getDimension(L"Random test string").Height; + bool startup(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + gui::IGUIFont *font, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + // If address is "", local server is used and address is updated + std::string *address, + u16 port, + std::wstring *error_message, + ChatBackend *chat_backend, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode); - /* - Draw "Loading" screen + void run(); + void shutdown(); + +protected: + + void extendedResourceCleanup(); + + // Basic initialisation + bool init(const std::string &map_dir, std::string *address, + u16 port, + const SubgameSpec &gamespec); + bool initSound(); + bool createSingleplayerServer(const std::string map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address); + + // Client creation + bool createClient(const std::string &playername, + const std::string &password, std::string *address, u16 port, + std::wstring *error_message); + bool initGui(std::wstring *error_message); + + // Client connection + bool connectToServer(const std::string &playername, + const std::string &password, std::string *address, u16 port, + bool *connect_ok, bool *aborted); + bool getServerContent(bool *aborted); + + // Main loop + + void updateInteractTimers(InteractParams *args, f32 dtime); + bool checkConnection(); + bool handleCallbacks(); + void processQueues(); + void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times, + f32 dtime); + void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime); + + void processUserInput(VolatileRunFlags *flags, InteractParams *interact_args, + f32 dtime); + void processKeyboardInput(VolatileRunFlags *flags, + float *statustext_time, + float *jump_timer, + u32 *profiler_current_page, + u32 profiler_max_page); + void processItemSelection(u16 *new_playeritem); + + void dropSelectedItem(); + void openInventory(); + void openConsole(); + void toggleFreeMove(float *statustext_time); + void toggleFreeMoveAlt(float *statustext_time, float *jump_timer); + void toggleFast(float *statustext_time); + void toggleNoClip(float *statustext_time); + + void toggleChat(float *statustext_time, bool *flag); + void toggleHud(float *statustext_time, bool *flag); + void toggleFog(float *statustext_time, bool *flag); + void toggleDebug(float *statustext_time, bool *show_debug, + bool *show_profiler_graph); + void toggleUpdateCamera(float *statustext_time, bool *flag); + void toggleProfiler(float *statustext_time, u32 *profiler_current_page, + u32 profiler_max_page); + + void increaseViewRange(float *statustext_time); + void decreaseViewRange(float *statustext_time); + void toggleFullViewRange(float *statustext_time); + + void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags); + void updatePlayerControl(const CameraOrientation &cam); + void step(f32 *dtime); + void processClientEvents(CameraOrientation *cam, float *damage_flash); + void updateCamera(VolatileRunFlags *flags, u32 busy_time, f32 dtime, + float time_from_last_punch); + void updateSound(f32 dtime); + void processPlayerInteraction(std::vector &highlight_boxes, + InteractParams *interactArgs, f32 dtime, bool show_hud, + bool show_debug); + void handlePointingAtNode(InteractParams *interactArgs, + const PointedThing &pointed, const ItemDefinition &playeritem_def, + const ToolCapabilities &playeritem_toolcap, f32 dtime); + void handlePointingAtObject(InteractParams *interactArgs, + const PointedThing &pointed, const ItemStack &playeritem, + const v3f &player_position, bool show_debug); + void handleDigging(InteractParams *interactArgs, const PointedThing &pointed, + const v3s16 &nodepos, const ToolCapabilities &playeritem_toolcap, + f32 dtime); + void updateFrame(std::vector &highlight_boxes, ProfilerGraph *graph, + RunStats *stats, InteractParams *interactArgs, + f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam); + void updateGui(float *statustext_time, const RunStats &stats, f32 dtime, + const VolatileRunFlags &flags, const CameraOrientation &cam); + void updateProfilerGraphs(ProfilerGraph *graph); + + // Misc + void limitFps(FpsControl *params, f32 *dtime); + + void showOverlayMessage(const char *msg, float dtime, int percent, + bool draw_clouds = true); + + inline const char *boolToCStr(bool v); + +private: + InputHandler *input; + + Client *client; + Server *server; + + gui::IGUIFont *font; + + IWritableTextureSource *texture_src; + IWritableShaderSource *shader_src; + + // When created, these will be filled with data received from the server + IWritableItemDefManager *itemdef_manager; + IWritableNodeDefManager *nodedef_manager; + + GameOnDemandSoundFetcher soundfetcher; // useful when testing + ISoundManager *sound; + bool sound_is_dummy; + SoundMaker *soundmaker; + + ChatBackend *chat_backend; + + GUIFormSpecMenu *current_formspec; + + EventManager *eventmgr; + QuicktuneShortcutter *quicktune; + + GUIChatConsole *gui_chat_console; // Free using ->Drop() + MapDrawControl *draw_control; + Camera *camera; + Clouds *clouds; // Free using ->Drop() + Sky *sky; // Free using ->Drop() + Inventory *local_inventory; + Hud *hud; + + /* 'cache' + This class does take ownership/responsibily for cleaning up etc of any of + these items (e.g. device) */ + IrrlichtDevice *device; + video::IVideoDriver *driver; + scene::ISceneManager *smgr; + u32 text_height; + bool *kill; + std::wstring *error_message; + IGameDef *gamedef; // Convenience (same as *client) + scene::ISceneNode *skybox; - { - wchar_t* text = wgettext("Loading..."); - draw_load_screen(text, device, guienv, font, 0, 0); - delete[] text; + bool random_input; + bool simple_singleplayer_mode; + /* End 'cache' */ + + /* Pre-calculated values + */ + int crack_animation_length; + + /* GUI stuff + */ + gui::IGUIStaticText *guitext; // First line of debug text + gui::IGUIStaticText *guitext2; // Second line of debug text + gui::IGUIStaticText *guitext_info; // At the middle of the screen + gui::IGUIStaticText *guitext_status; + gui::IGUIStaticText *guitext_chat; // Chat text + gui::IGUIStaticText *guitext_profiler; // Profiler text + + std::wstring infotext; + std::wstring statustext; +}; + +MinetestApp::MinetestApp() : + client(NULL), + server(NULL), + font(NULL), + texture_src(NULL), + shader_src(NULL), + itemdef_manager(NULL), + nodedef_manager(NULL), + sound(NULL), + sound_is_dummy(false), + soundmaker(NULL), + chat_backend(NULL), + current_formspec(NULL), + eventmgr(NULL), + quicktune(NULL), + gui_chat_console(NULL), + draw_control(NULL), + camera(NULL), + clouds(NULL), + sky(NULL), + local_inventory(NULL), + hud(NULL) +{ + +} + + + +/**************************************************************************** + MinetestApp Public + ****************************************************************************/ + +MinetestApp::~MinetestApp() +{ + delete client; + delete soundmaker; + if (!sound_is_dummy) + delete sound; + + delete server; // deleted first to stop all server threads + + delete hud; + delete local_inventory; + delete camera; + delete quicktune; + delete eventmgr; + delete texture_src; + delete shader_src; + delete nodedef_manager; + delete itemdef_manager; + delete draw_control; + + extendedResourceCleanup(); +} + +bool MinetestApp::startup(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + gui::IGUIFont *font, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + std::string *address, // can change if simple_singleplayer_mode + u16 port, + std::wstring *error_message, + ChatBackend *chat_backend, + const SubgameSpec &gamespec, + bool simple_singleplayer_mode) +{ + // "cache" + this->device = device; + this->font = font; + this->kill = kill; + this->error_message = error_message; + this->random_input = random_input; + this->input = input; + this->chat_backend = chat_backend; + this->simple_singleplayer_mode = simple_singleplayer_mode; + + driver = device->getVideoDriver(); + smgr = device->getSceneManager(); + text_height = font->getDimension(L"Random test string").Height; + + if (!init(map_dir, address, port, gamespec)) + return false; + + if (!createClient(playername, password, address, port, error_message)) + return false; + + return true; +} + + +void MinetestApp::run() +{ + ProfilerGraph graph; + RunStats stats = { 0 }; + CameraOrientation cam_view = { 0 }; + InteractParams interactArgs = { 0 }; + FpsControl draw_times = { 0 }; + VolatileRunFlags flags = { 0 }; + f32 dtime; // in seconds + + interactArgs.time_from_last_punch = 10.0; + interactArgs.profiler_max_page = 3; + + flags.show_chat = true; + flags.show_hud = true; + + /* FIXME: This should be updated every iteration, or not stored locally + now that key settings can be changed in-game */ + flags.invert_mouse = g_settings->getBool("invert_mouse"); + + /* Clear the profiler */ + Profiler::GraphValues dummyvalues; + g_profiler->graphGet(dummyvalues); + + draw_times.last_time = device->getTimer()->getTime(); + + shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter( + sky, + &flags.force_fog_off, + &interactArgs.fog_range, + client)); + + while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) { + + /* Must be called immediately after a device->run() call because it + * uses device->getTimer()->getTime() + */ + limitFps(&draw_times, &dtime); + + updateStats(&stats, draw_times, dtime); + updateInteractTimers(&interactArgs, dtime); + + if (!checkConnection()) + break; + if (!handleCallbacks()) + break; + + processQueues(); + + std::vector highlight_boxes; + + infotext = L""; + hud->resizeHotbar(); + addProfilerGraphs(stats, draw_times, dtime); + processUserInput(&flags, &interactArgs, dtime); + // Update camera before player movement to avoid camera lag of one frame + updateCameraDirection(&cam_view, &flags); + updatePlayerControl(cam_view); + step(&dtime); + processClientEvents(&cam_view, &interactArgs.damage_flash); + updateCamera(&flags, draw_times.busy_time, dtime, + interactArgs.time_from_last_punch); + updateSound(dtime); + processPlayerInteraction(highlight_boxes, &interactArgs, dtime, + flags.show_hud, flags.show_debug); + updateFrame(highlight_boxes, &graph, &stats, &interactArgs, dtime, + flags, cam_view); + updateProfilerGraphs(&graph); + } +} + + +void MinetestApp::shutdown() +{ + showOverlayMessage("Shutting down...", 0, 0, false); + + if (clouds) + clouds->drop(); + + if (gui_chat_console) + gui_chat_console->drop(); + + if (sky) + sky->drop(); + + clear_particles(); + + /* cleanup menus */ + while (g_menumgr.menuCount() > 0) { + g_menumgr.m_stack.front()->setVisible(false); + g_menumgr.deletingMenu(g_menumgr.m_stack.front()); } - // Create texture source - IWritableTextureSource *tsrc = createTextureSource(device); + if (current_formspec) { + current_formspec->drop(); + current_formspec = NULL; + } - // Create shader source - IWritableShaderSource *shsrc = createShaderSource(device); + chat_backend->addMessage(L"", L"# Disconnected."); + chat_backend->addMessage(L"", L""); - // These will be filled by data received from the server - // Create item definition manager - IWritableItemDefManager *itemdef = createItemDefManager(); - // Create node definition manager - IWritableNodeDefManager *nodedef = createNodeDefManager(); + if (client) { + client->Stop(); + while (!client->isShutdown()) { + assert(texture_src != NULL); + assert(shader_src != NULL); + texture_src->processQueue(); + shader_src->processQueue(); + sleep_ms(100); + } + } +} - // Sound fetcher (useful when testing) - GameOnDemandSoundFetcher soundfetcher; - // Sound manager - ISoundManager *sound = NULL; - bool sound_is_dummy = false; + +/**************************************************************************** + Startup + ****************************************************************************/ + +bool MinetestApp::init( + const std::string &map_dir, + std::string *address, + u16 port, + const SubgameSpec &gamespec) +{ + showOverlayMessage("Loading...", 0, 0); + + texture_src = createTextureSource(device); + shader_src = createShaderSource(device); + + itemdef_manager = createItemDefManager(); + nodedef_manager = createNodeDefManager(); + + eventmgr = new EventManager(); + quicktune = new QuicktuneShortcutter(); + + if (!(texture_src && shader_src && itemdef_manager && nodedef_manager + && eventmgr && quicktune)) + return false; + + if (!initSound()) + return false; + + // Create a server if not connecting to an existing one + if (*address == "") { + if (!createSingleplayerServer(map_dir, gamespec, port, address)) + return false; + } + + return true; +} + +bool MinetestApp::initSound() +{ #if USE_SOUND - if(g_settings->getBool("enable_sound")){ - infostream<<"Attempting to use OpenAL audio"<getBool("enable_sound")) { + infostream << "Attempting to use OpenAL audio" << std::endl; sound = createOpenALSoundManager(&soundfetcher); - if(!sound) - infostream<<"Failed to initialize OpenAL audio"<registerReceiver(eventmgr); - // Sound maker - SoundMaker soundmaker(sound, nodedef); - soundmaker.registerReceiver(&eventmgr); + return true; +} - // Create UI for modifying quicktune values - QuicktuneShortcutter quicktune; +bool MinetestApp::createSingleplayerServer(const std::string map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address) +{ + showOverlayMessage("Creating server...", 0, 25); - /* - Create server. - */ + std::string bind_str = g_settings->get("bind_address"); + Address bind_addr(0, 0, 0, 0, port); - if(address == ""){ - wchar_t* text = wgettext("Creating server...."); - draw_load_screen(text, device, guienv, font, 0, 25); - delete[] text; - infostream<<"Creating server"<getBool("ipv6_server")) { + bind_addr.setAddress((IPv6AddressBytes *) NULL); + } - std::string bind_str = g_settings->get("bind_address"); - Address bind_addr(0,0,0,0, port); + try { + bind_addr.Resolve(bind_str.c_str()); + *address = bind_str; + } catch (ResolveError &e) { + infostream << "Resolving bind address \"" << bind_str + << "\" failed: " << e.what() + << " -- Listening on all addresses." << std::endl; + } - if (g_settings->getBool("ipv6_server")) { - bind_addr.setAddress((IPv6AddressBytes*) NULL); - } - try { - bind_addr.Resolve(bind_str.c_str()); - address = bind_str; - } catch (ResolveError &e) { - infostream << "Resolving bind address \"" << bind_str - << "\" failed: " << e.what() - << " -- Listening on all addresses." << std::endl; - } - - if(bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { - error_message = L"Unable to listen on " + + if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { + *error_message = L"Unable to listen on " + narrow_to_wide(bind_addr.serializeString()) + L" because IPv6 is disabled"; - errorstream<start(bind_addr); + + return true; +} + +bool MinetestApp::createClient(const std::string &playername, + const std::string &password, std::string *address, u16 port, + std::wstring *error_message) +{ + showOverlayMessage("Creating client...", 0, 50); + + draw_control = new MapDrawControl; + if (!draw_control) + return false; + + bool could_connect, connect_aborted; + + if (!connectToServer(playername, password, address, port, + &could_connect, &connect_aborted)) + return false; + + if (!could_connect) { + if (*error_message == L"" && !connect_aborted) { + // Should not happen if error messages are set properly + *error_message = L"Connection failed for unknown reason"; + errorstream << wide_to_narrow(*error_message) << std::endl; } - - server = new Server(map_dir, gamespec, - simple_singleplayer_mode, - bind_addr.isIPv6()); - - server->start(bind_addr); + return false; } - do{ // Client scope (breakable do-while(0)) - - /* - Create client - */ - - { - wchar_t* text = wgettext("Creating client..."); - draw_load_screen(text, device, guienv, font, 0, 50); - delete[] text; + if (!getServerContent(&connect_aborted)) { + if (*error_message == L"" && !connect_aborted) { + // Should not happen if error messages are set properly + *error_message = L"Connection failed for unknown reason"; + errorstream << wide_to_narrow(*error_message) << std::endl; + } + return false; } - infostream<<"Creating client"<afterContentReceived(device, font); - { - wchar_t* text = wgettext("Resolving address..."); - draw_load_screen(text, device, guienv, font, 0, 75); - delete[] text; + /* Camera + */ + camera = new Camera(smgr, *draw_control, gamedef); + if (!camera || !camera->successfullyCreated(*error_message)) + return false; + + /* Clouds + */ + if (g_settings->getBool("enable_clouds")) { + clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0)); + if (!clouds) { + *error_message = L"Memory allocation error"; + *error_message += narrow_to_wide(" (clouds)"); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } } - Address connect_address(0,0,0,0, port); + + /* Skybox + */ + sky = new Sky(smgr->getRootSceneNode(), smgr, -1); + skybox = NULL; // This is used/set later on in the main run loop + + local_inventory = new Inventory(itemdef_manager); + + if (!(sky && local_inventory)) { + *error_message = L"Memory allocation error"; + *error_message += narrow_to_wide(" (sky or local inventory)"); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + /* Pre-calculated values + */ + video::ITexture *t = texture_src->getTexture("crack_anylength.png"); + if (t) { + v2u32 size = t->getOriginalSize(); + crack_animation_length = size.Y / size.X; + } else { + crack_animation_length = 5; + } + + if (!initGui(error_message)) + return false; + + /* Set window caption + */ + core::stringw str = L"Minetest ["; + str += driver->getName(); + str += "]"; + device->setWindowCaption(str.c_str()); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + player->hurt_tilt_timer = 0; + player->hurt_tilt_strength = 0; + + hud = new Hud(driver, smgr, guienv, font, text_height, gamedef, + player, local_inventory); + + if (!hud) { + *error_message = L"Memory error: could not create HUD"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + return true; +} + +bool MinetestApp::initGui(std::wstring *error_message) +{ + // First line of debug text + guitext = guienv->addStaticText( + L"Minetest", + core::rect(0, 0, 0, 0), + false, false, guiroot); + + // Second line of debug text + guitext2 = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + false, false, guiroot); + + // At the middle of the screen + // Object infos are shown in this + guitext_info = guienv->addStaticText( + L"", + core::rect(0, 0, 400, text_height * 5 + 5) + v2s32(100, 200), + false, true, guiroot); + + // Status text (displays info when showing and hiding GUI stuff, etc.) + guitext_status = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + false, false, guiroot); + guitext_status->setVisible(false); + + // Chat text + guitext_chat = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + //false, false); // Disable word wrap as of now + false, true, guiroot); + // Remove stale "recent" chat messages from previous connections + chat_backend->clearRecentChat(); + + // Chat backend and console + gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), + -1, chat_backend, client); + if (!gui_chat_console) { + *error_message = L"Could not allocate memory for chat console"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + // Profiler text (size is updated when text is updated) + guitext_profiler = guienv->addStaticText( + L"", + core::rect(0, 0, 0, 0), + false, false, guiroot); + guitext_profiler->setBackgroundColor(video::SColor(120, 0, 0, 0)); + guitext_profiler->setVisible(false); + guitext_profiler->setWordWrap(true); + +#ifdef HAVE_TOUCHSCREENGUI + + if (g_touchscreengui) + g_touchscreengui->init(tsrc, porting::getDisplayDensity()); + +#endif + + return true; +} + +bool MinetestApp::connectToServer(const std::string &playername, + const std::string &password, std::string *address, u16 port, + bool *connect_ok, bool *aborted) +{ + showOverlayMessage("Resolving address...", 0, 75); + + Address connect_address(0, 0, 0, 0, port); + try { - connect_address.Resolve(address.c_str()); + connect_address.Resolve(address->c_str()); + if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY //connect_address.Resolve("localhost"); if (connect_address.isIPv6()) { @@ -1279,2322 +1955,2057 @@ void the_game(bool &kill, bool random_input, InputHandler *input, addr_bytes.bytes[15] = 1; connect_address.setAddress(&addr_bytes); } else { - connect_address.setAddress(127,0,0,1); + connect_address.setAddress(127, 0, 0, 1); } } - } - catch(ResolveError &e) { - error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what()); - errorstream<getBool("enable_ipv6")) { - error_message = L"Unable to connect to " + - narrow_to_wide(connect_address.serializeString()) + - L" because IPv6 is disabled"; - errorstream<getBool("enable_ipv6")) { + *error_message = L"Unable to connect to " + + narrow_to_wide(connect_address.serializeString()) + + L" because IPv6 is disabled"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } - // Client acts as our GameDef - IGameDef *gamedef = &client; + client = new Client(device, playername.c_str(), password, *draw_control, + texture_src, shader_src, itemdef_manager, nodedef_manager, sound, + eventmgr, connect_address.isIPv6()); - /* - Attempt to connect to the server - */ + if (!client) + return false; - infostream<<"Connecting to server at "; + gamedef = client; // Client acts as our GameDef + + + infostream << "Connecting to server at "; connect_address.print(&infostream); - infostream<connect(connect_address); + /* Wait for server to accept connection */ - bool could_connect = false; - bool connect_aborted = false; - try{ - float time_counter = 0.0; + + try { input->clear(); - float fps_max = g_settings->getFloat("fps_max"); - bool cloud_menu_background = g_settings->getBool("menu_clouds"); - u32 lasttime = device->getTimer()->getTime(); - while(device->run()) - { - f32 dtime = 0.033; // in seconds - if (cloud_menu_background) { - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; - } - // Update client and server - client.step(dtime); - if(server != NULL) - server->step(dtime); - // End condition - if(client.getState() == LC_Init) { - could_connect = true; - break; - } - // Break conditions - if(client.accessDenied()) { - error_message = L"Access denied. Reason: " - +client.accessDeniedReason(); - errorstream<wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { - connect_aborted = true; - infostream<<"Connect aborted [Escape]"<getTimer()->getTime(); - if(time > lasttime) - busytime_u32 = time - lasttime; - else - busytime_u32 = 0; - busytime = busytime_u32 / 1000.0; - - // FPS limiter - u32 frametime_min = 1000./fps_max; - - if(busytime_u32 < frametime_min) { - u32 sleeptime = frametime_min - busytime_u32; - device->sleep(sleeptime); - } - } else { - sleep_ms(25); - } - time_counter += dtime; - } - } - catch(con::PeerNotFoundException &e) - {} - - /* - Handle failure to connect - */ - if(!could_connect) { - if(error_message == L"" && !connect_aborted) { - error_message = L"Connection failed"; - errorstream<clear(); - float fps_max = g_settings->getFloat("fps_max"); - bool cloud_menu_background = g_settings->getBool("menu_clouds"); - u32 lasttime = device->getTimer()->getTime(); while (device->run()) { - f32 dtime = 0.033; // in seconds - if (cloud_menu_background) { - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; - } + + limitFps(&fps_control, &dtime); + // Update client and server - client.step(dtime); + client->step(dtime); + if (server != NULL) server->step(dtime); // End condition - if (client.mediaReceived() && - client.itemdefReceived() && - client.nodedefReceived()) { - got_content = true; + if (client->getState() == LC_Init) { + *connect_ok = true; break; } + // Break conditions - if (client.accessDenied()) { - error_message = L"Access denied. Reason: " - +client.accessDeniedReason(); - errorstream<accessDenied()) { + *error_message = L"Access denied. Reason: " + + client->accessDeniedReason(); + errorstream << wide_to_narrow(*error_message) << std::endl; break; } + if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { - content_aborted = true; - infostream<<"Connect aborted [Escape]"<getBool("enable_remote_media_server"))) { - float cur = client.getCurRate(); - std::string cur_unit = gettext(" KB/s"); +bool MinetestApp::getServerContent(bool *aborted) +{ + input->clear(); - if (cur > 900) { - cur /= 1024.0; - cur_unit = gettext(" MB/s"); - } - message << " ( " << cur << cur_unit << " )"; + FpsControl fps_control = { 0 }; + f32 dtime; // in seconds + + while (device->run()) { + + limitFps(&fps_control, &dtime); + + // Update client and server + client->step(dtime); + + if (server != NULL) + server->step(dtime); + + // End condition + if (client->mediaReceived() && client->itemdefReceived() && + client->nodedefReceived()) { + break; + } + + // Error conditions + if (client->accessDenied()) { + *error_message = L"Access denied. Reason: " + + client->accessDeniedReason(); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + if (client->getState() < LC_Init) { + *error_message = L"Client disconnected"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + *aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + return false; + } + + // Display status + int progress = 0; + + if (!client->itemdefReceived()) { + wchar_t *text = wgettext("Item definitions..."); + progress = 0; + draw_load_screen(text, device, guienv, font, dtime, progress); + delete[] text; + } else if (!client->nodedefReceived()) { + wchar_t *text = wgettext("Node definitions..."); + progress = 25; + draw_load_screen(text, device, guienv, font, dtime, progress); + delete[] text; + } else { + std::stringstream message; + message.precision(3); + message << gettext("Media..."); + + if ((USE_CURL == 0) || + (!g_settings->getBool("enable_remote_media_server"))) { + float cur = client->getCurRate(); + std::string cur_unit = gettext(" KB/s"); + + if (cur > 900) { + cur /= 1024.0; + cur_unit = gettext(" MB/s"); } - progress = 50+client.mediaReceiveProgress()*50+0.5; - draw_load_screen(narrow_to_wide(message.str().c_str()), device, - guienv, font, dtime, progress); + + message << " ( " << cur << cur_unit << " )"; } - // On some computers framerate doesn't seem to be - // automatically limited - if (cloud_menu_background) { - // Time of frame without fps limit - float busytime; - u32 busytime_u32; - // not using getRealTime is necessary for wine - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - busytime_u32 = time - lasttime; - else - busytime_u32 = 0; - busytime = busytime_u32 / 1000.0; - - // FPS limiter - u32 frametime_min = 1000./fps_max; - - if(busytime_u32 < frametime_min) { - u32 sleeptime = frametime_min - busytime_u32; - device->sleep(sleeptime); - } - } else { - sleep_ms(25); - } - time_counter += dtime; + progress = 50 + client->mediaReceiveProgress() * 50 + 0.5; + draw_load_screen(narrow_to_wide(message.str().c_str()), device, + guienv, font, dtime, progress); } } - if(!got_content){ - if(error_message == L"" && !content_aborted){ - error_message = L"Something failed"; - errorstream<nodig_delay_timer >= 0) + args->nodig_delay_timer -= dtime; + + if (args->object_hit_delay_timer >= 0) + args->object_hit_delay_timer -= dtime; + + args->time_from_last_punch += dtime; +} + + +/* returns false if app should exit, otherwise true + */ +inline bool MinetestApp::checkConnection() +{ + if (client->accessDenied()) { + *error_message = L"Access denied. Reason: " + + client->accessDeniedReason(); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; } - /* - After all content has been received: - Update cached textures, meshes and materials - */ - client.afterContentReceived(device,font); + return true; +} - /* - Create the camera node - */ - Camera camera(smgr, draw_control, gamedef); - if (!camera.successfullyCreated(error_message)) - return; - f32 camera_yaw = 0; // "right/left" - f32 camera_pitch = 0; // "up/down" - - /* - Clouds - */ - - Clouds *clouds = NULL; - if(g_settings->getBool("enable_clouds")) - { - clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0)); +/* returns false if app should exit, otherwise true + */ +inline bool MinetestApp::handleCallbacks() +{ + if (g_gamecallback->disconnect_requested) { + g_gamecallback->disconnect_requested = false; + return false; } - /* - Skybox thingy - */ - - Sky *sky = NULL; - sky = new Sky(smgr->getRootSceneNode(), smgr, -1); - - scene::ISceneNode* skybox = NULL; - - /* - A copy of the local inventory - */ - Inventory local_inventory(itemdef); - - /* - Find out size of crack animation - */ - int crack_animation_length = 5; - { - video::ITexture *t = tsrc->getTexture("crack_anylength.png"); - v2u32 size = t->getOriginalSize(); - crack_animation_length = size.Y / size.X; + if (g_gamecallback->changepassword_requested) { + (new GUIPasswordChange(guienv, guiroot, -1, + &g_menumgr, client))->drop(); + g_gamecallback->changepassword_requested = false; } - /* - Add some gui stuff - */ + if (g_gamecallback->changevolume_requested) { + (new GUIVolumeChange(guienv, guiroot, -1, + &g_menumgr, client))->drop(); + g_gamecallback->changevolume_requested = false; + } - // First line of debug text - gui::IGUIStaticText *guitext = guienv->addStaticText( - L"Minetest", - core::rect(0, 0, 0, 0), - false, false, guiroot); - // Second line of debug text - gui::IGUIStaticText *guitext2 = guienv->addStaticText( - L"", - core::rect(0, 0, 0, 0), - false, false, guiroot); - // At the middle of the screen - // Object infos are shown in this - gui::IGUIStaticText *guitext_info = guienv->addStaticText( - L"", - core::rect(0,0,400,text_height*5+5) + v2s32(100,200), - false, true, guiroot); + if (g_gamecallback->keyconfig_requested) { + (new GUIKeyChangeMenu(guienv, guiroot, -1, + &g_menumgr))->drop(); + g_gamecallback->keyconfig_requested = false; + } - // Status text (displays info when showing and hiding GUI stuff, etc.) - gui::IGUIStaticText *guitext_status = guienv->addStaticText( - L"", - core::rect(0,0,0,0), - false, false, guiroot); - guitext_status->setVisible(false); + return true; +} - std::wstring statustext; - float statustext_time = 0; - // Chat text - gui::IGUIStaticText *guitext_chat = guienv->addStaticText( - L"", - core::rect(0,0,0,0), - //false, false); // Disable word wrap as of now - false, true, guiroot); - // Remove stale "recent" chat messages from previous connections - chat_backend.clearRecentChat(); - // Chat backend and console - GUIChatConsole *gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, &chat_backend, &client); +void MinetestApp::processQueues() +{ + texture_src->processQueue(); + itemdef_manager->processQueue(gamedef); + shader_src->processQueue(); +} - // Profiler text (size is updated when text is updated) - gui::IGUIStaticText *guitext_profiler = guienv->addStaticText( - L"", - core::rect(0,0,0,0), - false, false, guiroot); - guitext_profiler->setBackgroundColor(video::SColor(120,0,0,0)); - guitext_profiler->setVisible(false); - guitext_profiler->setWordWrap(true); + +void MinetestApp::addProfilerGraphs(const RunStats &stats, + const FpsControl &draw_times, f32 dtime) +{ + g_profiler->graphAdd("mainloop_other", + draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f); + + if (draw_times.sleep_time != 0) + g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f); + g_profiler->graphAdd("mainloop_dtime", dtime); + + g_profiler->add("Elapsed time", dtime); + g_profiler->avg("FPS", 1. / dtime); +} + + +void MinetestApp::updateStats(RunStats *stats, const FpsControl &draw_times, + f32 dtime) +{ + + f32 jitter; + Jitter *jp; + + /* Time average and jitter calculation + */ + jp = &stats->dtime_jitter; + jp->avg = jp->avg * 0.96 + dtime * 0.04; + + jitter = dtime - jp->avg; + + if (jitter > jp->max) + jp->max = jitter; + + jp->counter += dtime; + + if (jp->counter > 0.0) { + jp->counter -= 3.0; + jp->max_sample = jp->max; + jp->max_fraction = jp->max_sample / (jp->avg + 0.001); + jp->max = 0.0; + } + + /* Busytime average and jitter calculation + */ + jp = &stats->busy_time_jitter; + jp->avg = jp->avg + draw_times.busy_time * 0.02; + + jitter = draw_times.busy_time - jp->avg; + + if (jitter > jp->max) + jp->max = jitter; + if (jitter < jp->min) + jp->min = jitter; + + jp->counter += dtime; + + if (jp->counter > 0.0) { + jp->counter -= 3.0; + jp->max_sample = jp->max; + jp->min_sample = jp->min; + jp->max = 0.0; + jp->min = 0.0; + } +} + + + +/**************************************************************************** + Input handling + ****************************************************************************/ + +void MinetestApp::processUserInput(VolatileRunFlags *flags, + InteractParams *interact_args, f32 dtime) +{ + // Reset input if window not active or some menu is active + if (device->isWindowActive() == false + || noMenuActive() == false + || guienv->hasFocus(gui_chat_console)) { + input->clear(); + } + + if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) { + gui_chat_console->closeConsoleAtOnce(); + } + + // Input handler step() (used by the random input generator) + input->step(dtime); #ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui) - g_touchscreengui->init(tsrc,porting::getDisplayDensity()); + + if (g_touchscreengui) { + g_touchscreengui->step(dtime); + } + +#endif +#ifdef __ANDROID__ + + if (current_formspec != 0) + current_formspec->getAndroidUIInput(); + #endif + // Increase timer for double tap of "keymap_jump" + if (g_settings->getBool("doubletap_jump") && interact_args->jump_timer <= 0.2) + interact_args->jump_timer += dtime; + + processKeyboardInput( + flags, + &interact_args->statustext_time, + &interact_args->jump_timer, + &interact_args->profiler_current_page, + interact_args->profiler_max_page); + + processItemSelection(&interact_args->new_playeritem); +} + + +void MinetestApp::processKeyboardInput(VolatileRunFlags *flags, + float *statustext_time, + float *jump_timer, + u32 *profiler_current_page, + u32 profiler_max_page) +{ + if (input->wasKeyDown(getKeySetting("keymap_drop"))) { + dropSelectedItem(); + } else if (input->wasKeyDown(getKeySetting("keymap_inventory"))) { + openInventory(); + } else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + show_pause_menu(¤t_formspec, client, gamedef, texture_src, device, + simple_singleplayer_mode); + } else if (input->wasKeyDown(getKeySetting("keymap_chat"))) { + show_chat_menu(¤t_formspec, client, gamedef, texture_src, device, + client, ""); + } else if (input->wasKeyDown(getKeySetting("keymap_cmd"))) { + show_chat_menu(¤t_formspec, client, gamedef, texture_src, device, + client, "/"); + } else if (input->wasKeyDown(getKeySetting("keymap_console"))) { + openConsole(); + } else if (input->wasKeyDown(getKeySetting("keymap_freemove"))) { + toggleFreeMove(statustext_time); + } else if (input->wasKeyDown(getKeySetting("keymap_jump"))) { + toggleFreeMoveAlt(statustext_time, jump_timer); + } else if (input->wasKeyDown(getKeySetting("keymap_fastmove"))) { + toggleFast(statustext_time); + } else if (input->wasKeyDown(getKeySetting("keymap_noclip"))) { + toggleNoClip(statustext_time); + } else if (input->wasKeyDown(getKeySetting("keymap_screenshot"))) { + client->makeScreenshot(device); + } else if (input->wasKeyDown(getKeySetting("keymap_toggle_hud"))) { + toggleHud(statustext_time, &flags->show_hud); + } else if (input->wasKeyDown(getKeySetting("keymap_toggle_chat"))) { + toggleChat(statustext_time, &flags->show_chat); + } else if (input->wasKeyDown(getKeySetting("keymap_toggle_force_fog_off"))) { + toggleFog(statustext_time, &flags->force_fog_off); + } else if (input->wasKeyDown(getKeySetting("keymap_toggle_update_camera"))) { + toggleUpdateCamera(statustext_time, &flags->disable_camera_update); + } else if (input->wasKeyDown(getKeySetting("keymap_toggle_debug"))) { + toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph); + } else if (input->wasKeyDown(getKeySetting("keymap_toggle_profiler"))) { + toggleProfiler(statustext_time, profiler_current_page, profiler_max_page); + } else if (input->wasKeyDown(getKeySetting("keymap_increase_viewing_range_min"))) { + increaseViewRange(statustext_time); + } else if (input->wasKeyDown(getKeySetting("keymap_decrease_viewing_range_min"))) { + decreaseViewRange(statustext_time); + } else if (input->wasKeyDown(getKeySetting("keymap_rangeselect"))) { + toggleFullViewRange(statustext_time); + } + + // Handle QuicktuneShortcutter + if (input->wasKeyDown(getKeySetting("keymap_quicktune_next"))) + quicktune->next(); + else if (input->wasKeyDown(getKeySetting("keymap_quicktune_prev"))) + quicktune->prev(); + else if (input->wasKeyDown(getKeySetting("keymap_quicktune_inc"))) + quicktune->inc(); + else if (input->wasKeyDown(getKeySetting("keymap_quicktune_dec"))) + quicktune->dec(); + + std::string msg = quicktune->getMessage(); + if (msg != "") { + statustext = narrow_to_wide(msg); + *statustext_time = 0; + } + + // Print debug stacks + if (input->wasKeyDown(getKeySetting("keymap_print_debug_stacks"))) { + dstream << "-----------------------------------------" + << std::endl; + dstream << DTIME << "Printing debug stacks:" << std::endl; + dstream << "-----------------------------------------" + << std::endl; + debug_stacks_print(); + } +} + + +void MinetestApp::processItemSelection(u16 *new_playeritem) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* Item selection using mouse wheel + */ + *new_playeritem = client->getPlayerItem(); + + s32 wheel = input->getMouseWheel(); + u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1, + player->hud_hotbar_itemcount - 1); + + if (wheel < 0) + *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : max_item; + else if (wheel > 0) + *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item; + // else wheel == 0 + + + /* Item selection using keyboard + */ + for (u16 i = 0; i < 10; i++) { + static const KeyPress *item_keys[10] = { + NumberKey + 1, NumberKey + 2, NumberKey + 3, NumberKey + 4, + NumberKey + 5, NumberKey + 6, NumberKey + 7, NumberKey + 8, + NumberKey + 9, NumberKey + 0, + }; + + if (input->wasKeyDown(*item_keys[i])) { + if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) { + *new_playeritem = i; + infostream << "Selected item: " << new_playeritem << std::endl; + } + break; + } + } +} + + +void MinetestApp::dropSelectedItem() +{ + IDropAction *a = new IDropAction(); + a->count = 0; + a->from_inv.setCurrentPlayer(); + a->from_list = "main"; + a->from_i = client->getPlayerItem(); + client->inventoryAction(a); +} + + +void MinetestApp::openInventory() +{ + infostream << "the_game: " << "Launching inventory" << std::endl; + + PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client); + TextDest *txt_dst = new TextDestPlayerInventory(client); + + create_formspec_menu(¤t_formspec, client, gamedef, texture_src, + device, fs_src, txt_dst, client); + + InventoryLocation inventoryloc; + inventoryloc.setCurrentPlayer(); + current_formspec->setFormSpec(fs_src->getForm(), inventoryloc); +} + + +void MinetestApp::openConsole() +{ + if (!gui_chat_console->isOpenInhibited()) { + // Open up to over half of the screen + gui_chat_console->openConsole(0.6); + guienv->setFocus(gui_chat_console); + } +} + + +void MinetestApp::toggleFreeMove(float *statustext_time) +{ + static const wchar_t *msg[] = { L"free_move disabled", L"free_move enabled" }; + + bool free_move = !g_settings->getBool("free_move"); + g_settings->set("free_move", boolToCStr(free_move)); + + *statustext_time = 0; + statustext = msg[free_move]; + if (free_move && !client->checkPrivilege("fly")) + statustext += L" (note: no 'fly' privilege)"; +} + + +void MinetestApp::toggleFreeMoveAlt(float *statustext_time, float *jump_timer) +{ + if (g_settings->getBool("doubletap_jump") && *jump_timer < 0.2f) { + toggleFreeMove(statustext_time); + *jump_timer = 0; + } +} + + +void MinetestApp::toggleFast(float *statustext_time) +{ + static const wchar_t *msg[] = { L"fast_move disabled", L"fast_move enabled" }; + bool fast_move = !g_settings->getBool("fast_move"); + g_settings->set("fast_move", boolToCStr(fast_move)); + + *statustext_time = 0; + statustext = msg[fast_move]; + + if (fast_move && !client->checkPrivilege("fast")) + statustext += L" (note: no 'fast' privilege)"; +} + + +void MinetestApp::toggleNoClip(float *statustext_time) +{ + static const wchar_t *msg[] = { L"noclip disabled", L"noclip enabled" }; + bool noclip = !g_settings->getBool("noclip"); + g_settings->set("noclip", boolToCStr(noclip)); + + *statustext_time = 0; + statustext = msg[noclip]; + + if (noclip && !client->checkPrivilege("noclip")) + statustext += L" (note: no 'noclip' privilege)"; +} + + +void MinetestApp::toggleChat(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { L"Chat hidden", L"Chat shown" }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; +} + + +void MinetestApp::toggleHud(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { L"HUD hidden", L"HUD shown" }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; + client->setHighlighted(client->getHighlighted(), *flag); +} + + +void MinetestApp::toggleFog(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { L"Fog enabled", L"Fog disabled" }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; +} + + +void MinetestApp::toggleDebug(float *statustext_time, bool *show_debug, + bool *show_profiler_graph) +{ + // Initial / 3x toggle: Chat only + // 1x toggle: Debug text with chat + // 2x toggle: Debug text with profiler graph + if (!*show_debug) { + *show_debug = true; + *show_profiler_graph = false; + statustext = L"Debug info shown"; + } else if (*show_profiler_graph) { + *show_debug = false; + *show_profiler_graph = false; + statustext = L"Debug info and profiler graph hidden"; + } else { + *show_profiler_graph = true; + statustext = L"Profiler graph shown"; + } + *statustext_time = 0; +} + + +void MinetestApp::toggleUpdateCamera(float *statustext_time, bool *flag) +{ + static const wchar_t *msg[] = { + L"Camera update enabled", + L"Camera update disabled" + }; + + *flag = !*flag; + *statustext_time = 0; + statustext = msg[*flag]; +} + + +void MinetestApp::toggleProfiler(float *statustext_time, u32 *profiler_current_page, + u32 profiler_max_page) +{ + *profiler_current_page = (*profiler_current_page + 1) % (profiler_max_page + 1); + + // FIXME: This updates the profiler with incomplete values + update_profiler_gui(guitext_profiler, font, text_height, + *profiler_current_page, profiler_max_page); + + if (*profiler_current_page != 0) { + std::wstringstream sstr; + sstr << "Profiler shown (page " << *profiler_current_page + << " of " << profiler_max_page << ")"; + statustext = sstr.str(); + } else { + statustext = L"Profiler hidden"; + } + *statustext_time = 0; +} + + +void MinetestApp::increaseViewRange(float *statustext_time) +{ + s16 range = g_settings->getS16("viewing_range_nodes_min"); + s16 range_new = range + 10; + g_settings->set("viewing_range_nodes_min", itos(range_new)); + statustext = narrow_to_wide("Minimum viewing range changed to " + + itos(range_new)); + *statustext_time = 0; +} + + +void MinetestApp::decreaseViewRange(float *statustext_time) +{ + s16 range = g_settings->getS16("viewing_range_nodes_min"); + s16 range_new = range - 10; + + if (range_new < 0) + range_new = range; + + g_settings->set("viewing_range_nodes_min", itos(range_new)); + statustext = narrow_to_wide("Minimum viewing range changed to " + + itos(range_new)); + *statustext_time = 0; +} + + +void MinetestApp::toggleFullViewRange(float *statustext_time) +{ + static const wchar_t *msg[] = { + L"Disabled full viewing range", + L"Enabled full viewing range" + }; + + draw_control->range_all = !draw_control->range_all; + infostream << msg[draw_control->range_all] << std::endl; + statustext = msg[draw_control->range_all]; + *statustext_time = 0; +} + + +void MinetestApp::updateCameraDirection(CameraOrientation *cam, + VolatileRunFlags *flags) +{ + // float turn_amount = 0; // Deprecated? + + if (!(device->isWindowActive() && noMenuActive()) || random_input) { + + // FIXME: Clean this up + +#ifndef ANDROID + // Mac OSX gets upset if this is set every frame + if (device->getCursorControl()->isVisible() == false) + device->getCursorControl()->setVisible(true); +#endif + + //infostream<<"window inactive"<first_loop_after_window_activation = true; + return; + } + +#ifndef __ANDROID__ + if (!random_input) { + // Mac OSX gets upset if this is set every frame + if (device->getCursorControl()->isVisible()) + device->getCursorControl()->setVisible(false); + } +#endif + + if (flags->first_loop_after_window_activation) { + //infostream<<"window active, first loop"<first_loop_after_window_activation = false; + } else { + +#ifdef HAVE_TOUCHSCREENGUI + + if (g_touchscreengui) { + camera_yaw = g_touchscreengui->getYaw(); + camera_pitch = g_touchscreengui->getPitch(); + } else { +#endif + s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2); + s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2); + + if (flags->invert_mouse + || (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT)) { + dy = -dy; + } + + //infostream<<"window active, pos difference "<getFloat("mouse_sensitivity"); + d = rangelim(d, 0.01, 100.0); + cam->camera_yaw -= dx * d; + cam->camera_pitch += dy * d; + // turn_amount = v2f(dx, dy).getLength() * d; // deprecated? + +#ifdef HAVE_TOUCHSCREENGUI + } +#endif + + if (cam->camera_pitch < -89.5) + cam->camera_pitch = -89.5; + else if (cam->camera_pitch > 89.5) + cam->camera_pitch = 89.5; + } + + input->setMousePos(driver->getScreenSize().Width / 2, + driver->getScreenSize().Height / 2); + + // Deprecated? Not used anywhere else + // recent_turn_speed = recent_turn_speed * 0.9 + turn_amount * 0.1; + // std::cerr<<"recent_turn_speed = "<isKeyDown(getKeySetting("keymap_forward")), + input->isKeyDown(getKeySetting("keymap_backward")), + input->isKeyDown(getKeySetting("keymap_left")), + input->isKeyDown(getKeySetting("keymap_right")), + input->isKeyDown(getKeySetting("keymap_jump")), + input->isKeyDown(getKeySetting("keymap_special1")), + input->isKeyDown(getKeySetting("keymap_sneak")), + input->getLeftState(), + input->getRightState(), + cam.camera_pitch, + cam.camera_yaw + ); + client->setPlayerControl(control); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + player->keyPressed = + ( (u32)(input->isKeyDown(getKeySetting("keymap_forward")) & 0x1) << 0) | + ( (u32)(input->isKeyDown(getKeySetting("keymap_backward")) & 0x1) << 1) | + ( (u32)(input->isKeyDown(getKeySetting("keymap_left")) & 0x1) << 2) | + ( (u32)(input->isKeyDown(getKeySetting("keymap_right")) & 0x1) << 3) | + ( (u32)(input->isKeyDown(getKeySetting("keymap_jump")) & 0x1) << 4) | + ( (u32)(input->isKeyDown(getKeySetting("keymap_special1")) & 0x1) << 5) | + ( (u32)(input->isKeyDown(getKeySetting("keymap_sneak")) & 0x1) << 6) | + ( (u32)(input->getLeftState() & 0x1) << 7) | + ( (u32)(input->getRightState() & 0x1) << 8 + ); + +} + + +inline void MinetestApp::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 (server != NULL) { + //TimeTaker timer("server->step(dtime)"); + server->step(*dtime); + } + + //TimeTaker timer("client.step(dtime)"); + client->step(*dtime); + } +} + + +void MinetestApp::processClientEvents(CameraOrientation *cam, float *damage_flash) +{ + ClientEvent event = client->getClientEvent(); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + for ( ; event.type != CE_NONE; event = client->getClientEvent()) { + + if (event.type == CE_PLAYER_DAMAGE && + client->getHP() != 0) { + //u16 damage = event.player_damage.amount; + //infostream<<"Player damage: "<hurt_tilt_timer = 1.5; + player->hurt_tilt_strength = event.player_damage.amount / 4; + player->hurt_tilt_strength = rangelim(player->hurt_tilt_strength, 1.0, 4.0); + + MtEvent *e = new SimpleTriggerEvent("PlayerDamage"); + gamedef->event()->put(e); + } else if (event.type == CE_PLAYER_FORCE_MOVE) { + cam->camera_yaw = event.player_force_move.yaw; + cam->camera_pitch = event.player_force_move.pitch; + } else if (event.type == CE_DEATHSCREEN) { + show_deathscreen(¤t_formspec, client, gamedef, texture_src, + device, client); + + chat_backend->addMessage(L"", L"You died."); + + /* Handle visualization */ + *damage_flash = 0; + player->hurt_tilt_timer = 0; + player->hurt_tilt_strength = 0; + + } else if (event.type == CE_SHOW_FORMSPEC) { + FormspecFormSource *fs_src = + new FormspecFormSource(*(event.show_formspec.formspec)); + TextDestPlayerInventory *txt_dst = + new TextDestPlayerInventory(client, *(event.show_formspec.formname)); + + create_formspec_menu(¤t_formspec, client, gamedef, + texture_src, device, fs_src, txt_dst, client); + + delete(event.show_formspec.formspec); + delete(event.show_formspec.formname); + } else if (event.type == CE_SPAWN_PARTICLE) { + video::ITexture *texture = + gamedef->tsrc()->getTexture(*(event.spawn_particle.texture)); + + new Particle(gamedef, smgr, player, client->getEnv(), + *event.spawn_particle.pos, + *event.spawn_particle.vel, + *event.spawn_particle.acc, + event.spawn_particle.expirationtime, + event.spawn_particle.size, + event.spawn_particle.collisiondetection, + event.spawn_particle.vertical, + texture, + v2f(0.0, 0.0), + v2f(1.0, 1.0)); + } else if (event.type == CE_ADD_PARTICLESPAWNER) { + video::ITexture *texture = + gamedef->tsrc()->getTexture(*(event.add_particlespawner.texture)); + + new ParticleSpawner(gamedef, smgr, player, + event.add_particlespawner.amount, + event.add_particlespawner.spawntime, + *event.add_particlespawner.minpos, + *event.add_particlespawner.maxpos, + *event.add_particlespawner.minvel, + *event.add_particlespawner.maxvel, + *event.add_particlespawner.minacc, + *event.add_particlespawner.maxacc, + event.add_particlespawner.minexptime, + event.add_particlespawner.maxexptime, + event.add_particlespawner.minsize, + event.add_particlespawner.maxsize, + event.add_particlespawner.collisiondetection, + event.add_particlespawner.vertical, + texture, + event.add_particlespawner.id); + } else if (event.type == CE_DELETE_PARTICLESPAWNER) { + delete_particlespawner(event.delete_particlespawner.id); + } else if (event.type == CE_HUDADD) { + u32 id = event.hudadd.id; + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + HudElement *e = player->getHud(id); + + if (e != NULL) { + 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; + continue; + } + + 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; + + u32 new_id = player->addHud(e); + //if this isn't true our huds aren't consistent + assert(new_id == id); + + 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; + } else if (event.type == CE_HUDRM) { + HudElement *e = player->removeHud(event.hudrm.id); + + if (e != NULL) + delete(e); + } else if (event.type == CE_HUDCHANGE) { + u32 id = event.hudchange.id; + HudElement *e = player->getHud(id); + + if (e == NULL) { + delete event.hudchange.v3fdata; + delete event.hudchange.v2fdata; + delete event.hudchange.sdata; + delete event.hudchange.v2s32data; + continue; + } + + switch (event.hudchange.stat) { + case HUD_STAT_POS: + e->pos = *event.hudchange.v2fdata; + break; + + case HUD_STAT_NAME: + e->name = *event.hudchange.sdata; + break; + + case HUD_STAT_SCALE: + e->scale = *event.hudchange.v2fdata; + break; + + case HUD_STAT_TEXT: + e->text = *event.hudchange.sdata; + break; + + case HUD_STAT_NUMBER: + e->number = event.hudchange.data; + break; + + case HUD_STAT_ITEM: + e->item = event.hudchange.data; + break; + + case HUD_STAT_DIR: + e->dir = event.hudchange.data; + break; + + case HUD_STAT_ALIGN: + e->align = *event.hudchange.v2fdata; + break; + + case HUD_STAT_OFFSET: + e->offset = *event.hudchange.v2fdata; + break; + + case HUD_STAT_WORLD_POS: + e->world_pos = *event.hudchange.v3fdata; + break; + + case HUD_STAT_SIZE: + e->size = *event.hudchange.v2s32data; + break; + } + + delete event.hudchange.v3fdata; + delete event.hudchange.v2fdata; + delete event.hudchange.sdata; + delete event.hudchange.v2s32data; + } else if (event.type == CE_SET_SKY) { + sky->setVisible(false); + + if (skybox) { + skybox->remove(); + skybox = NULL; + } + + // Handle according to type + if (*event.set_sky.type == "regular") { + sky->setVisible(true); + } else if (*event.set_sky.type == "skybox" && + event.set_sky.params->size() == 6) { + sky->setFallbackBgColor(*event.set_sky.bgcolor); + skybox = smgr->addSkyBoxSceneNode( + texture_src->getTexture((*event.set_sky.params)[0]), + texture_src->getTexture((*event.set_sky.params)[1]), + texture_src->getTexture((*event.set_sky.params)[2]), + texture_src->getTexture((*event.set_sky.params)[3]), + texture_src->getTexture((*event.set_sky.params)[4]), + texture_src->getTexture((*event.set_sky.params)[5])); + } + // Handle everything else as plain color + else { + if (*event.set_sky.type != "plain") + infostream << "Unknown sky type: " + << (*event.set_sky.type) << std::endl; + + sky->setFallbackBgColor(*event.set_sky.bgcolor); + } + + delete event.set_sky.bgcolor; + delete event.set_sky.type; + delete event.set_sky.params; + } else if (event.type == CE_OVERRIDE_DAY_NIGHT_RATIO) { + bool enable = event.override_day_night_ratio.do_override; + u32 value = event.override_day_night_ratio.ratio_f * 1000; + client->getEnv().setDayNightRatioOverride(enable, value); + } + } +} + + +void MinetestApp::updateCamera(VolatileRunFlags *flags, u32 busy_time, + f32 dtime, float time_from_last_punch) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + /* - Some statistics are collected in these + For interaction purposes, get info about the held item + - What item is it? + - Is it a usable item? + - Can it point to liquids? */ - u32 drawtime = 0; - u32 beginscenetime = 0; - u32 endscenetime = 0; + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); - float recent_turn_speed = 0.0; + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } - ProfilerGraph graph; - // Initially clear the profiler - Profiler::GraphValues dummyvalues; - g_profiler->graphGet(dummyvalues); + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); - float nodig_delay_timer = 0.0; - float dig_time = 0.0; - u16 dig_index = 0; - PointedThing pointed_old; - bool digging = false; - bool ldown_for_dig = false; + v3s16 old_camera_offset = camera->getOffset(); - float damage_flash = 0; + if (input->wasKeyDown(getKeySetting("keymap_camera_mode"))) { + camera->toggleCameraMode(); + GenericCAO *playercao = player->getCAO(); - float jump_timer = 0; - bool reset_jump_timer = false; + assert(playercao != NULL); - const float object_hit_delay = 0.2; - float object_hit_delay_timer = 0.0; - float time_from_last_punch = 10; + playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + } - float update_draw_list_timer = 0.0; - v3f update_draw_list_last_cam_dir; + float full_punch_interval = playeritem_toolcap.full_punch_interval; + float tool_reload_ratio = time_from_last_punch / full_punch_interval; - bool invert_mouse = g_settings->getBool("invert_mouse"); + tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); + camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio, + client->getEnv()); + camera->step(dtime); - bool update_wielded_item_trigger = true; + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + f32 camera_fov = camera->getFovMax(); + v3s16 camera_offset = camera->getOffset(); - bool show_hud = true; - bool show_chat = true; - bool force_fog_off = false; - f32 fog_range = 100*BS; - bool disable_camera_update = false; - bool show_debug = g_settings->getBool("show_debug"); - bool show_profiler_graph = false; - u32 show_profiler = 0; - u32 show_profiler_max = 3; // Number of pages + flags->camera_offset_changed = (camera_offset != old_camera_offset); + + if (!flags->disable_camera_update) { + client->getEnv().getClientMap().updateCamera(camera_position, + camera_direction, camera_fov, camera_offset); + + if (flags->camera_offset_changed) { + client->updateCameraOffset(camera_offset); + client->getEnv().updateCameraOffset(camera_offset); + + if (clouds) + clouds->updateCameraOffset(camera_offset); + } + } +} + + +void MinetestApp::updateSound(f32 dtime) +{ + // Update sound listener + v3s16 camera_offset = camera->getOffset(); + sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS), + v3f(0, 0, 0), // velocity + camera->getDirection(), + camera->getCameraNode()->getUpVector()); + sound->setListenerGain(g_settings->getFloat("sound_volume")); + + + // Update sound maker + soundmaker->step(dtime); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = map.getNodeNoEx(player->getStandingNodePos()); + soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep; +} + + +void MinetestApp::processPlayerInteraction(std::vector &highlight_boxes, + InteractParams *interactArgs, f32 dtime, bool show_hud, bool show_debug) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } + + const ItemDefinition &playeritem_def = + playeritem.getDefinition(itemdef_manager); + + v3f player_position = player->getPosition(); + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + v3s16 camera_offset = camera->getOffset(); + + + /* + Calculate what block is the crosshair pointing to + */ + + f32 d = playeritem_def.range; // max. distance + f32 d_hand = itemdef_manager->get("").range; + + if (d < 0 && d_hand >= 0) + d = d_hand; + else if (d < 0) + d = 4.0; + + core::line3d shootline; + + if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) { + + shootline = core::line3d(camera_position, + camera_position + camera_direction * BS * (d + 1)); + + } else { + // prevent player pointing anything in front-view + if (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) + shootline = core::line3d(0, 0, 0, 0, 0, 0); + } + +#ifdef HAVE_TOUCHSCREENGUI + + if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { + shootline = g_touchscreengui->getShootline(); + shootline.start += intToFloat(camera_offset, BS); + shootline.end += intToFloat(camera_offset, BS); + } + +#endif + + PointedThing pointed = getPointedThing( + // input + client, player_position, camera_direction, + camera_position, shootline, d, + playeritem_def.liquids_pointable, + !interactArgs->ldown_for_dig, + camera_offset, + // output + highlight_boxes, + interactArgs->selected_object); + + if (pointed != interactArgs->pointed_old) { + infostream << "Pointing at " << pointed.dump() << std::endl; + + if (g_settings->getBool("enable_node_highlighting")) { + if (pointed.type == POINTEDTHING_NODE) { + client->setHighlighted(pointed.node_undersurface, show_hud); + } else { + client->setHighlighted(pointed.node_undersurface, false); + } + } + } + + /* + Stop digging when + - releasing left mouse button + - pointing away from node + */ + if (interactArgs->digging) { + if (input->getLeftReleased()) { + infostream << "Left button released" + << " (stopped digging)" << std::endl; + interactArgs->digging = false; + } else if (pointed != interactArgs->pointed_old) { + if (pointed.type == POINTEDTHING_NODE + && interactArgs->pointed_old.type == POINTEDTHING_NODE + && pointed.node_undersurface + == interactArgs->pointed_old.node_undersurface) { + // Still pointing to the same node, but a different face. + // Don't reset. + } else { + infostream << "Pointing away from node" + << " (stopped digging)" << std::endl; + interactArgs->digging = false; + } + } + + if (!interactArgs->digging) { + client->interact(1, interactArgs->pointed_old); + client->setCrack(-1, v3s16(0, 0, 0)); + interactArgs->dig_time = 0.0; + } + } + + if (!interactArgs->digging && interactArgs->ldown_for_dig && !input->getLeftState()) { + interactArgs->ldown_for_dig = false; + } + + interactArgs->left_punch = false; + + soundmaker->m_player_leftpunch_sound.name = ""; + + if (input->getRightState()) + interactArgs->repeat_rightclick_timer += dtime; + else + interactArgs->repeat_rightclick_timer = 0; + + if (playeritem_def.usable && input->getLeftState()) { + if (input->getLeftClicked()) + client->interact(4, pointed); + } else if (pointed.type == POINTEDTHING_NODE) { + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); + handlePointingAtNode(interactArgs, pointed, playeritem_def, + playeritem_toolcap, dtime); + } else if (pointed.type == POINTEDTHING_OBJECT) { + handlePointingAtObject(interactArgs, pointed, playeritem, + player_position, show_debug); + } else if (input->getLeftState()) { + // When button is held down in air, show continuous animation + interactArgs->left_punch = true; + } + + interactArgs->pointed_old = pointed; + + if (interactArgs->left_punch || input->getLeftClicked()) + camera->setDigging(0); // left click animation + + input->resetLeftClicked(); + input->resetRightClicked(); + + input->resetLeftReleased(); + input->resetRightReleased(); +} + + +void MinetestApp::handlePointingAtNode(InteractParams *interactArgs, + const PointedThing &pointed, const ItemDefinition &playeritem_def, + const ToolCapabilities &playeritem_toolcap, f32 dtime) +{ + v3s16 nodepos = pointed.node_undersurface; + v3s16 neighbourpos = pointed.node_abovesurface; + + /* + Check information text of node + */ + + ClientMap &map = client->getEnv().getClientMap(); + NodeMetadata *meta = map.getNodeMetadata(nodepos); + + if (meta) { + infotext = narrow_to_wide(meta->getString("infotext")); + } else { + MapNode n = map.getNode(nodepos); + + if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { + infotext = L"Unknown node: "; + infotext += narrow_to_wide(nodedef_manager->get(n).name); + } + } + + if (interactArgs->nodig_delay_timer <= 0.0 && input->getLeftState() + && client->checkPrivilege("interact")) { + handleDigging(interactArgs, pointed, nodepos, playeritem_toolcap, dtime); + } + + if ((input->getRightClicked() || + interactArgs->repeat_rightclick_timer >= + g_settings->getFloat("repeat_rightclick_time")) && + client->checkPrivilege("interact")) { + interactArgs->repeat_rightclick_timer = 0; + infostream << "Ground right-clicked" << std::endl; + + if (meta && meta->getString("formspec") != "" && !random_input + && !input->isKeyDown(getKeySetting("keymap_sneak"))) { + infostream << "Launching custom inventory view" << std::endl; + + InventoryLocation inventoryloc; + inventoryloc.setNodeMeta(nodepos); + + NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( + &client->getEnv().getClientMap(), nodepos); + TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); + + create_formspec_menu(¤t_formspec, client, gamedef, + texture_src, device, fs_src, txt_dst, client); + + current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); + } else { + // Report right click to server + + camera->setDigging(1); // right click animation (always shown for feedback) + + // If the wielded item has node placement prediction, + // make that happen + bool placed = nodePlacementPrediction(*client, + playeritem_def, + nodepos, neighbourpos); + + if (placed) { + // Report to server + client->interact(3, pointed); + // Read the sound + soundmaker->m_player_rightpunch_sound = + playeritem_def.sound_place; + } else { + soundmaker->m_player_rightpunch_sound = + SimpleSoundSpec(); + } + + if (playeritem_def.node_placement_prediction == "" || + nodedef_manager->get(map.getNode(nodepos)).rightclickable) + client->interact(3, pointed); // Report to server + } + } +} + + +void MinetestApp::handlePointingAtObject(InteractParams *interactArgs, + const PointedThing &pointed, + const ItemStack &playeritem, + const v3f &player_position, + bool show_debug) +{ + infotext = narrow_to_wide(interactArgs->selected_object->infoText()); + + if (infotext == L"" && show_debug) { + infotext = narrow_to_wide(interactArgs->selected_object->debugInfoText()); + } + + if (input->getLeftState()) { + bool do_punch = false; + bool do_punch_damage = false; + + if (interactArgs->object_hit_delay_timer <= 0.0) { + do_punch = true; + do_punch_damage = true; + interactArgs->object_hit_delay_timer = object_hit_delay; + } + + if (input->getLeftClicked()) + do_punch = true; + + if (do_punch) { + infostream << "Left-clicked object" << std::endl; + interactArgs->left_punch = true; + } + + if (do_punch_damage) { + // Report direct punch + v3f objpos = interactArgs->selected_object->getPosition(); + v3f dir = (objpos - player_position).normalize(); + + bool disable_send = interactArgs->selected_object->directReportPunch( + dir, &playeritem, interactArgs->time_from_last_punch); + interactArgs->time_from_last_punch = 0; + + if (!disable_send) + client->interact(0, pointed); + } + } else if (input->getRightClicked()) { + infostream << "Right-clicked object" << std::endl; + client->interact(3, pointed); // place + } +} + + +void MinetestApp::handleDigging(InteractParams *interactArgs, + const PointedThing &pointed, const v3s16 &nodepos, + const ToolCapabilities &playeritem_toolcap, f32 dtime) +{ + if (!interactArgs->digging) { + infostream << "Started digging" << std::endl; + client->interact(0, pointed); + interactArgs->digging = true; + interactArgs->ldown_for_dig = true; + } + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = client->getEnv().getClientMap().getNode(nodepos); + + // NOTE: Similar piece of code exists on the server side for + // cheat detection. + // Get digging parameters + DigParams params = getDigParams(nodedef_manager->get(n).groups, + &playeritem_toolcap); + + // If can't dig, try hand + if (!params.diggable) { + const ItemDefinition &hand = itemdef_manager->get(""); + const ToolCapabilities *tp = hand.tool_capabilities; + + if (tp) + params = getDigParams(nodedef_manager->get(n).groups, tp); + } + + if (params.diggable == false) { + // I guess nobody will wait for this long + interactArgs->dig_time_complete = 10000000.0; + } else { + interactArgs->dig_time_complete = params.time; + + if (g_settings->getBool("enable_particles")) { + const ContentFeatures &features = + client->getNodeDefManager()->get(n); + addPunchingParticles(gamedef, smgr, player, + client->getEnv(), nodepos, features.tiles); + } + } + + if (interactArgs->dig_time_complete >= 0.001) { + interactArgs->dig_index = (float)crack_animation_length + * interactArgs->dig_time + / interactArgs->dig_time_complete; + } else { + // This is for torches + interactArgs->dig_index = crack_animation_length; + } + + SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig; + + if (sound_dig.exists() && params.diggable) { + if (sound_dig.name == "__group") { + if (params.main_group != "") { + soundmaker->m_player_leftpunch_sound.gain = 0.5; + soundmaker->m_player_leftpunch_sound.name = + std::string("default_dig_") + + params.main_group; + } + } else { + soundmaker->m_player_leftpunch_sound = sound_dig; + } + } + + // Don't show cracks if not diggable + if (interactArgs->dig_time_complete >= 100000.0) { + } else if (interactArgs->dig_index < crack_animation_length) { + //TimeTaker timer("client.setTempMod"); + //infostream<<"dig_index="<setCrack(interactArgs->dig_index, nodepos); + } else { + infostream << "Digging completed" << std::endl; + client->interact(2, pointed); + client->setCrack(-1, v3s16(0, 0, 0)); + MapNode wasnode = map.getNode(nodepos); + client->removeNode(nodepos); + + if (g_settings->getBool("enable_particles")) { + const ContentFeatures &features = + client->getNodeDefManager()->get(wasnode); + addDiggingParticles + (gamedef, smgr, player, client->getEnv(), + nodepos, features.tiles); + } + + interactArgs->dig_time = 0; + interactArgs->digging = false; + + interactArgs->nodig_delay_timer = + interactArgs->dig_time_complete / (float)crack_animation_length; + + // We don't want a corresponding delay to + // very time consuming nodes + if (interactArgs->nodig_delay_timer > 0.3) + interactArgs->nodig_delay_timer = 0.3; + + // We want a slight delay to very little + // time consuming nodes + const float mindelay = 0.15; + + if (interactArgs->nodig_delay_timer < mindelay) + interactArgs->nodig_delay_timer = mindelay; + + // Send event to trigger sound + MtEvent *e = new NodeDugEvent(nodepos, wasnode); + gamedef->event()->put(e); + } + + if (interactArgs->dig_time_complete < 100000.0) { + interactArgs->dig_time += dtime; + } else { + interactArgs->dig_time = 0; + client->setCrack(-1, nodepos); + } + + camera->setDigging(0); // left click animation +} + + +void MinetestApp::updateFrame(std::vector &highlight_boxes, + ProfilerGraph *graph, RunStats *stats, InteractParams *interactArgs, + f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* + Fog range + */ + + if (draw_control->range_all) { + interactArgs->fog_range = 100000 * BS; + } else { + interactArgs->fog_range = draw_control->wanted_range * BS + + 0.0 * MAP_BLOCKSIZE * BS; + interactArgs->fog_range = MYMIN( + interactArgs->fog_range, + (draw_control->farthest_drawn + 20) * BS); + interactArgs->fog_range *= 0.9; + } + + /* + Calculate general brightness + */ + u32 daynight_ratio = client->getEnv().getDayNightRatio(); + float time_brightness = decode_light_f((float)daynight_ratio / 1000.0); + float direct_brightness = 0; + bool sunlight_seen = false; + + if (g_settings->getBool("free_move")) { + direct_brightness = time_brightness; + sunlight_seen = true; + } else { + ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG); + float old_brightness = sky->getBrightness(); + direct_brightness = client->getEnv().getClientMap() + .getBackgroundBrightness(MYMIN(interactArgs->fog_range * 1.2, 60 * BS), + daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen) + / 255.0; + } float time_of_day = 0; float time_of_day_smooth = 0; - float repeat_rightclick_timer = 0; + time_of_day = client->getEnv().getTimeOfDayF(); + + const float maxsm = 0.05; + + if (fabs(time_of_day - time_of_day_smooth) > maxsm && + fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm && + fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm) + time_of_day_smooth = time_of_day; + + const float todsm = 0.05; + + if (time_of_day_smooth > 0.8 && time_of_day < 0.2) + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + (time_of_day + 1.0) * todsm; + else + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + time_of_day * todsm; + + sky->update(time_of_day_smooth, time_brightness, direct_brightness, + sunlight_seen, camera->getCameraMode(), player->getYaw(), + player->getPitch()); /* - Shader constants + Update clouds */ - shsrc->addGlobalConstantSetter(new GameGlobalShaderConstantSetter( - sky, &force_fog_off, &fog_range, &client)); - - /* - Main loop - */ - - bool first_loop_after_window_activation = true; - - // TODO: Convert the static interval timers to these - // Interval limiter for profiler - IntervalLimiter m_profiler_interval; - - // Time is in milliseconds - // NOTE: getRealTime() causes strange problems in wine (imprecision?) - // NOTE: So we have to use getTime() and call run()s between them - u32 lasttime = device->getTimer()->getTime(); - - LocalPlayer* player = client.getEnv().getLocalPlayer(); - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; - - /* - HUD object - */ - Hud hud(driver, smgr, guienv, font, text_height, - gamedef, player, &local_inventory); - - core::stringw str = L"Minetest ["; - str += driver->getName(); - str += "]"; - device->setWindowCaption(str.c_str()); - - // Info text - std::wstring infotext; - - for(;;) - { - if(device->run() == false || kill == true || - g_gamecallback->shutdown_requested) - break; - - v2u32 screensize = driver->getScreenSize(); - - // Time of frame without fps limit - float busytime; - u32 busytime_u32; - { - // not using getRealTime is necessary for wine - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - busytime_u32 = time - lasttime; - else - busytime_u32 = 0; - busytime = busytime_u32 / 1000.0; - } - - g_profiler->graphAdd("mainloop_other", busytime - (float)drawtime/1000.0f); - - // Necessary for device->getTimer()->getTime() - device->run(); - - /* - FPS limiter - */ - - { - float fps_max = g_menumgr.pausesGame() ? - g_settings->getFloat("pause_fps_max") : - g_settings->getFloat("fps_max"); - u32 frametime_min = 1000./fps_max; - - if(busytime_u32 < frametime_min) - { - u32 sleeptime = frametime_min - busytime_u32; - device->sleep(sleeptime); - g_profiler->graphAdd("mainloop_sleep", (float)sleeptime/1000.0f); - } - } - - // Necessary for device->getTimer()->getTime() - device->run(); - - /* - Time difference calculation - */ - f32 dtime; // in seconds - - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; - - g_profiler->graphAdd("mainloop_dtime", dtime); - - /* Run timers */ - - if(nodig_delay_timer >= 0) - nodig_delay_timer -= dtime; - if(object_hit_delay_timer >= 0) - object_hit_delay_timer -= dtime; - time_from_last_punch += dtime; - - g_profiler->add("Elapsed time", dtime); - g_profiler->avg("FPS", 1./dtime); - - /* - Time average and jitter calculation - */ - - static f32 dtime_avg1 = 0.0; - dtime_avg1 = dtime_avg1 * 0.96 + dtime * 0.04; - f32 dtime_jitter1 = dtime - dtime_avg1; - - static f32 dtime_jitter1_max_sample = 0.0; - static f32 dtime_jitter1_max_fraction = 0.0; - { - static f32 jitter1_max = 0.0; - static f32 counter = 0.0; - if(dtime_jitter1 > jitter1_max) - jitter1_max = dtime_jitter1; - counter += dtime; - if(counter > 0.0) - { - counter -= 3.0; - dtime_jitter1_max_sample = jitter1_max; - dtime_jitter1_max_fraction - = dtime_jitter1_max_sample / (dtime_avg1+0.001); - jitter1_max = 0.0; - } - } - - /* - Busytime average and jitter calculation - */ - - static f32 busytime_avg1 = 0.0; - busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02; - f32 busytime_jitter1 = busytime - busytime_avg1; - - static f32 busytime_jitter1_max_sample = 0.0; - static f32 busytime_jitter1_min_sample = 0.0; - { - static f32 jitter1_max = 0.0; - static f32 jitter1_min = 0.0; - static f32 counter = 0.0; - if(busytime_jitter1 > jitter1_max) - jitter1_max = busytime_jitter1; - if(busytime_jitter1 < jitter1_min) - jitter1_min = busytime_jitter1; - counter += dtime; - if(counter > 0.0){ - counter -= 3.0; - busytime_jitter1_max_sample = jitter1_max; - busytime_jitter1_min_sample = jitter1_min; - jitter1_max = 0.0; - jitter1_min = 0.0; - } - } - - /* - Handle miscellaneous stuff - */ - - if(client.accessDenied()) - { - error_message = L"Access denied. Reason: " - +client.accessDeniedReason(); - errorstream<disconnect_requested) - { - g_gamecallback->disconnect_requested = false; - break; - } - - if(g_gamecallback->changepassword_requested) - { - (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, &client))->drop(); - g_gamecallback->changepassword_requested = false; - } - - if(g_gamecallback->changevolume_requested) - { - (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr, &client))->drop(); - g_gamecallback->changevolume_requested = false; - } - - if(g_gamecallback->keyconfig_requested) - { - (new GUIKeyChangeMenu(guienv, guiroot, -1, - &g_menumgr))->drop(); - g_gamecallback->keyconfig_requested = false; - } - - - /* Process TextureSource's queue */ - tsrc->processQueue(); - - /* Process ItemDefManager's queue */ - itemdef->processQueue(gamedef); - - /* - Process ShaderSource's queue - */ - shsrc->processQueue(); - - /* - Random calculations - */ - hud.resizeHotbar(); - - // Hilight boxes collected during the loop and displayed - std::vector hilightboxes; - - /* reset infotext */ - infotext = L""; - /* - Profiler - */ - float profiler_print_interval = - g_settings->getFloat("profiler_print_interval"); - bool print_to_log = true; - if(profiler_print_interval == 0){ - print_to_log = false; - profiler_print_interval = 5; - } - if(m_profiler_interval.step(dtime, profiler_print_interval)) - { - if(print_to_log){ - infostream<<"Profiler:"<print(infostream); - } - - update_profiler_gui(guitext_profiler, font, text_height, - show_profiler, show_profiler_max); - - g_profiler->clear(); - } - - /* - Direct handling of user input - */ - - // Reset input if window not active or some menu is active - if(device->isWindowActive() == false - || noMenuActive() == false - || guienv->hasFocus(gui_chat_console)) - { - input->clear(); - } - if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) - { - gui_chat_console->closeConsoleAtOnce(); - } - - // Input handler step() (used by the random input generator) - input->step(dtime); -#ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui) { - g_touchscreengui->step(dtime); - } -#endif -#ifdef __ANDROID__ - if (current_formspec != 0) - current_formspec->getAndroidUIInput(); -#endif - - // Increase timer for doubleclick of "jump" - if(g_settings->getBool("doubletap_jump") && jump_timer <= 0.2) - jump_timer += dtime; - - /* - Launch menus and trigger stuff according to keys - */ - if(input->wasKeyDown(getKeySetting("keymap_drop"))) - { - // drop selected item - IDropAction *a = new IDropAction(); - a->count = 0; - a->from_inv.setCurrentPlayer(); - a->from_list = "main"; - a->from_i = client.getPlayerItem(); - client.inventoryAction(a); - } - else if(input->wasKeyDown(getKeySetting("keymap_inventory"))) - { - infostream<<"the_game: " - <<"Launching inventory"<setFormSpec(fs_src->getForm(), inventoryloc); - } - else if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) - { - show_pause_menu(¤t_formspec, &client, gamedef, tsrc, device, - simple_singleplayer_mode); - } - else if(input->wasKeyDown(getKeySetting("keymap_chat"))) - { - show_chat_menu(¤t_formspec, &client, gamedef, tsrc, device, - &client,""); - } - else if(input->wasKeyDown(getKeySetting("keymap_cmd"))) - { - show_chat_menu(¤t_formspec, &client, gamedef, tsrc, device, - &client,"/"); - } - else if(input->wasKeyDown(getKeySetting("keymap_console"))) - { - if (!gui_chat_console->isOpenInhibited()) - { - // Open up to over half of the screen - gui_chat_console->openConsole(0.6); - guienv->setFocus(gui_chat_console); - } - } - else if(input->wasKeyDown(getKeySetting("keymap_freemove"))) - { - if(g_settings->getBool("free_move")) - { - g_settings->set("free_move","false"); - statustext = L"free_move disabled"; - statustext_time = 0; - } - else - { - g_settings->set("free_move","true"); - statustext = L"free_move enabled"; - statustext_time = 0; - if(!client.checkPrivilege("fly")) - statustext += L" (note: no 'fly' privilege)"; - } - } - else if(input->wasKeyDown(getKeySetting("keymap_jump"))) - { - if(g_settings->getBool("doubletap_jump") && jump_timer < 0.2) - { - if(g_settings->getBool("free_move")) - { - g_settings->set("free_move","false"); - statustext = L"free_move disabled"; - statustext_time = 0; - } - else - { - g_settings->set("free_move","true"); - statustext = L"free_move enabled"; - statustext_time = 0; - if(!client.checkPrivilege("fly")) - statustext += L" (note: no 'fly' privilege)"; - } - } - reset_jump_timer = true; - } - else if(input->wasKeyDown(getKeySetting("keymap_fastmove"))) - { - if(g_settings->getBool("fast_move")) - { - g_settings->set("fast_move","false"); - statustext = L"fast_move disabled"; - statustext_time = 0; - } - else - { - g_settings->set("fast_move","true"); - statustext = L"fast_move enabled"; - statustext_time = 0; - if(!client.checkPrivilege("fast")) - statustext += L" (note: no 'fast' privilege)"; - } - } - else if(input->wasKeyDown(getKeySetting("keymap_noclip"))) - { - if(g_settings->getBool("noclip")) - { - g_settings->set("noclip","false"); - statustext = L"noclip disabled"; - statustext_time = 0; - } - else - { - g_settings->set("noclip","true"); - statustext = L"noclip enabled"; - statustext_time = 0; - if(!client.checkPrivilege("noclip")) - statustext += L" (note: no 'noclip' privilege)"; - } - } - else if(input->wasKeyDown(getKeySetting("keymap_screenshot"))) - { - client.makeScreenshot(device); - } - else if(input->wasKeyDown(getKeySetting("keymap_toggle_hud"))) - { - show_hud = !show_hud; - if(show_hud) { - statustext = L"HUD shown"; - client.setHighlighted(client.getHighlighted(), true); - } else { - statustext = L"HUD hidden"; - client.setHighlighted(client.getHighlighted(), false); - } - statustext_time = 0; - } - else if(input->wasKeyDown(getKeySetting("keymap_toggle_chat"))) - { - show_chat = !show_chat; - if(show_chat) - statustext = L"Chat shown"; - else - statustext = L"Chat hidden"; - statustext_time = 0; - } - else if(input->wasKeyDown(getKeySetting("keymap_toggle_force_fog_off"))) - { - force_fog_off = !force_fog_off; - if(force_fog_off) - statustext = L"Fog disabled"; - else - statustext = L"Fog enabled"; - statustext_time = 0; - } - else if(input->wasKeyDown(getKeySetting("keymap_toggle_update_camera"))) - { - disable_camera_update = !disable_camera_update; - if(disable_camera_update) - statustext = L"Camera update disabled"; - else - statustext = L"Camera update enabled"; - statustext_time = 0; - } - else if(input->wasKeyDown(getKeySetting("keymap_toggle_debug"))) - { - // Initial / 3x toggle: Chat only - // 1x toggle: Debug text with chat - // 2x toggle: Debug text with profiler graph - if(!show_debug) - { - show_debug = true; - show_profiler_graph = false; - statustext = L"Debug info shown"; - statustext_time = 0; - } - else if(show_profiler_graph) - { - show_debug = false; - show_profiler_graph = false; - statustext = L"Debug info and profiler graph hidden"; - statustext_time = 0; - } - else - { - show_profiler_graph = true; - statustext = L"Profiler graph shown"; - statustext_time = 0; - } - } - else if(input->wasKeyDown(getKeySetting("keymap_toggle_profiler"))) - { - show_profiler = (show_profiler + 1) % (show_profiler_max + 1); - - // FIXME: This updates the profiler with incomplete values - update_profiler_gui(guitext_profiler, font, text_height, - show_profiler, show_profiler_max); - - if(show_profiler != 0) - { - std::wstringstream sstr; - sstr<<"Profiler shown (page "<wasKeyDown(getKeySetting("keymap_increase_viewing_range_min"))) - { - s16 range = g_settings->getS16("viewing_range_nodes_min"); - s16 range_new = range + 10; - g_settings->set("viewing_range_nodes_min", itos(range_new)); - statustext = narrow_to_wide( - "Minimum viewing range changed to " - + itos(range_new)); - statustext_time = 0; - } - else if(input->wasKeyDown(getKeySetting("keymap_decrease_viewing_range_min"))) - { - s16 range = g_settings->getS16("viewing_range_nodes_min"); - s16 range_new = range - 10; - if(range_new < 0) - range_new = range; - g_settings->set("viewing_range_nodes_min", - itos(range_new)); - statustext = narrow_to_wide( - "Minimum viewing range changed to " - + itos(range_new)); - statustext_time = 0; - } - - // Reset jump_timer - if(!input->isKeyDown(getKeySetting("keymap_jump")) && reset_jump_timer) - { - reset_jump_timer = false; - jump_timer = 0.0; - } - - // Handle QuicktuneShortcutter - if(input->wasKeyDown(getKeySetting("keymap_quicktune_next"))) - quicktune.next(); - if(input->wasKeyDown(getKeySetting("keymap_quicktune_prev"))) - quicktune.prev(); - if(input->wasKeyDown(getKeySetting("keymap_quicktune_inc"))) - quicktune.inc(); - if(input->wasKeyDown(getKeySetting("keymap_quicktune_dec"))) - quicktune.dec(); - { - std::string msg = quicktune.getMessage(); - if(msg != ""){ - statustext = narrow_to_wide(msg); - statustext_time = 0; - } - } - - // Item selection with mouse wheel - u16 new_playeritem = client.getPlayerItem(); - { - s32 wheel = input->getMouseWheel(); - u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE-1, - player->hud_hotbar_itemcount-1); - - if(wheel < 0) - { - if(new_playeritem < max_item) - new_playeritem++; - else - new_playeritem = 0; - } - else if(wheel > 0) - { - if(new_playeritem > 0) - new_playeritem--; - else - new_playeritem = max_item; - } - } - - // Item selection - for(u16 i=0; i<10; i++) - { - const KeyPress *kp = NumberKey + (i + 1) % 10; - if(input->wasKeyDown(*kp)) - { - if(i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) - { - new_playeritem = i; - - infostream<<"Selected item: " - <wasKeyDown(getKeySetting("keymap_rangeselect"))) - { - draw_control.range_all = !draw_control.range_all; - if(draw_control.range_all) - { - infostream<<"Enabled full viewing range"<wasKeyDown(getKeySetting("keymap_print_debug_stacks"))) - { - dstream<<"-----------------------------------------" - <isWindowActive() && noMenuActive()) || random_input) - { -#ifndef __ANDROID__ - if(!random_input) - { - // Mac OSX gets upset if this is set every frame - if(device->getCursorControl()->isVisible()) - device->getCursorControl()->setVisible(false); - } -#endif - - if(first_loop_after_window_activation){ - //infostream<<"window active, first loop"<getYaw(); - camera_pitch = g_touchscreengui->getPitch(); - } else { -#endif - s32 dx = input->getMousePos().X - (driver->getScreenSize().Width/2); - s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height/2); - if ((invert_mouse) - || (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT)) { - dy = -dy; - } - //infostream<<"window active, pos difference "<isKeyDown(irr::KEY_UP)) - dy -= dtime * keyspeed; - if(input->isKeyDown(irr::KEY_DOWN)) - dy += dtime * keyspeed; - if(input->isKeyDown(irr::KEY_LEFT)) - dx -= dtime * keyspeed; - if(input->isKeyDown(irr::KEY_RIGHT)) - dx += dtime * keyspeed;*/ - - float d = g_settings->getFloat("mouse_sensitivity"); - d = rangelim(d, 0.01, 100.0); - camera_yaw -= dx*d; - camera_pitch += dy*d; - turn_amount = v2f(dx, dy).getLength() * d; - -#ifdef HAVE_TOUCHSCREENGUI - } -#endif - if(camera_pitch < -89.5) camera_pitch = -89.5; - if(camera_pitch > 89.5) camera_pitch = 89.5; - } - input->setMousePos((driver->getScreenSize().Width/2), - (driver->getScreenSize().Height/2)); - } - else{ -#ifndef ANDROID - // Mac OSX gets upset if this is set every frame - if(device->getCursorControl()->isVisible() == false) - device->getCursorControl()->setVisible(true); -#endif - - //infostream<<"window inactive"<isKeyDown(getKeySetting("keymap_backward")), - input->isKeyDown(getKeySetting("keymap_left")), - input->isKeyDown(getKeySetting("keymap_right")), - input->isKeyDown(getKeySetting("keymap_jump")), - input->isKeyDown(getKeySetting("keymap_special1")), - input->isKeyDown(getKeySetting("keymap_sneak")), - input->getLeftState(), - input->getRightState(), - camera_pitch, - camera_yaw - ); - client.setPlayerControl(control); - LocalPlayer* player = client.getEnv().getLocalPlayer(); - player->keyPressed= - (((int)input->isKeyDown(getKeySetting("keymap_forward")) & 0x1) << 0) | - (((int)input->isKeyDown(getKeySetting("keymap_backward")) & 0x1) << 1) | - (((int)input->isKeyDown(getKeySetting("keymap_left")) & 0x1) << 2) | - (((int)input->isKeyDown(getKeySetting("keymap_right")) & 0x1) << 3) | - (((int)input->isKeyDown(getKeySetting("keymap_jump")) & 0x1) << 4) | - (((int)input->isKeyDown(getKeySetting("keymap_special1")) & 0x1) << 5) | - (((int)input->isKeyDown(getKeySetting("keymap_sneak")) & 0x1) << 6) | - (((int)input->getLeftState() & 0x1) << 7) | - (((int)input->getRightState() & 0x1) << 8); - } - - /* - Run server, client (and process environments) - */ - bool can_be_and_is_paused = - (simple_singleplayer_mode && g_menumgr.pausesGame()); - if(can_be_and_is_paused) - { - // No time passes - dtime = 0; - } - else - { - if(server != NULL) - { - //TimeTaker timer("server->step(dtime)"); - server->step(dtime); - } - { - //TimeTaker timer("client.step(dtime)"); - client.step(dtime); - } - } - - { - // Read client events - for(;;) { - ClientEvent event = client.getClientEvent(); - if(event.type == CE_NONE) { - break; - } - else if(event.type == CE_PLAYER_DAMAGE && - client.getHP() != 0) { - //u16 damage = event.player_damage.amount; - //infostream<<"Player damage: "<hurt_tilt_timer = 1.5; - player->hurt_tilt_strength = event.player_damage.amount/4; - player->hurt_tilt_strength = rangelim(player->hurt_tilt_strength, 1.0, 4.0); - - MtEvent *e = new SimpleTriggerEvent("PlayerDamage"); - gamedef->event()->put(e); - } - else if(event.type == CE_PLAYER_FORCE_MOVE) { - camera_yaw = event.player_force_move.yaw; - camera_pitch = event.player_force_move.pitch; - } - else if(event.type == CE_DEATHSCREEN) { - show_deathscreen(¤t_formspec, &client, gamedef, tsrc, - device, &client); - - chat_backend.addMessage(L"", L"You died."); - - /* Handle visualization */ - damage_flash = 0; - - LocalPlayer* player = client.getEnv().getLocalPlayer(); - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; - - } - else if (event.type == CE_SHOW_FORMSPEC) { - FormspecFormSource* fs_src = - new FormspecFormSource(*(event.show_formspec.formspec)); - TextDestPlayerInventory* txt_dst = - new TextDestPlayerInventory(&client,*(event.show_formspec.formname)); - - create_formspec_menu(¤t_formspec, &client, gamedef, - tsrc, device, fs_src, txt_dst, &client); - - delete(event.show_formspec.formspec); - delete(event.show_formspec.formname); - } - else if(event.type == CE_SPAWN_PARTICLE) { - LocalPlayer* player = client.getEnv().getLocalPlayer(); - video::ITexture *texture = - gamedef->tsrc()->getTexture(*(event.spawn_particle.texture)); - - new Particle(gamedef, smgr, player, client.getEnv(), - *event.spawn_particle.pos, - *event.spawn_particle.vel, - *event.spawn_particle.acc, - event.spawn_particle.expirationtime, - event.spawn_particle.size, - event.spawn_particle.collisiondetection, - event.spawn_particle.vertical, - texture, - v2f(0.0, 0.0), - v2f(1.0, 1.0)); - } - else if(event.type == CE_ADD_PARTICLESPAWNER) { - LocalPlayer* player = client.getEnv().getLocalPlayer(); - video::ITexture *texture = - gamedef->tsrc()->getTexture(*(event.add_particlespawner.texture)); - - new ParticleSpawner(gamedef, smgr, player, - event.add_particlespawner.amount, - event.add_particlespawner.spawntime, - *event.add_particlespawner.minpos, - *event.add_particlespawner.maxpos, - *event.add_particlespawner.minvel, - *event.add_particlespawner.maxvel, - *event.add_particlespawner.minacc, - *event.add_particlespawner.maxacc, - event.add_particlespawner.minexptime, - event.add_particlespawner.maxexptime, - event.add_particlespawner.minsize, - event.add_particlespawner.maxsize, - event.add_particlespawner.collisiondetection, - event.add_particlespawner.vertical, - texture, - event.add_particlespawner.id); - } - else if(event.type == CE_DELETE_PARTICLESPAWNER) { - delete_particlespawner (event.delete_particlespawner.id); - } - else if (event.type == CE_HUDADD) { - u32 id = event.hudadd.id; - - HudElement *e = player->getHud(id); - - if (e != NULL) { - 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; - continue; - } - - 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; - - u32 new_id = player->addHud(e); - //if this isn't true our huds aren't consistent - assert(new_id == id); - - 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; - } - else if (event.type == CE_HUDRM) { - HudElement* e = player->removeHud(event.hudrm.id); - - if (e != NULL) - delete (e); - } - else if (event.type == CE_HUDCHANGE) { - u32 id = event.hudchange.id; - HudElement* e = player->getHud(id); - if (e == NULL) - { - delete event.hudchange.v3fdata; - delete event.hudchange.v2fdata; - delete event.hudchange.sdata; - delete event.hudchange.v2s32data; - continue; - } - - switch (event.hudchange.stat) { - case HUD_STAT_POS: - e->pos = *event.hudchange.v2fdata; - break; - case HUD_STAT_NAME: - e->name = *event.hudchange.sdata; - break; - case HUD_STAT_SCALE: - e->scale = *event.hudchange.v2fdata; - break; - case HUD_STAT_TEXT: - e->text = *event.hudchange.sdata; - break; - case HUD_STAT_NUMBER: - e->number = event.hudchange.data; - break; - case HUD_STAT_ITEM: - e->item = event.hudchange.data; - break; - case HUD_STAT_DIR: - e->dir = event.hudchange.data; - break; - case HUD_STAT_ALIGN: - e->align = *event.hudchange.v2fdata; - break; - case HUD_STAT_OFFSET: - e->offset = *event.hudchange.v2fdata; - break; - case HUD_STAT_WORLD_POS: - e->world_pos = *event.hudchange.v3fdata; - break; - case HUD_STAT_SIZE: - e->size = *event.hudchange.v2s32data; - break; - } - - delete event.hudchange.v3fdata; - delete event.hudchange.v2fdata; - delete event.hudchange.sdata; - delete event.hudchange.v2s32data; - } - else if (event.type == CE_SET_SKY) { - sky->setVisible(false); - if(skybox){ - skybox->remove(); - skybox = NULL; - } - // Handle according to type - if(*event.set_sky.type == "regular") { - sky->setVisible(true); - } - else if(*event.set_sky.type == "skybox" && - event.set_sky.params->size() == 6) { - sky->setFallbackBgColor(*event.set_sky.bgcolor); - skybox = smgr->addSkyBoxSceneNode( - tsrc->getTexture((*event.set_sky.params)[0]), - tsrc->getTexture((*event.set_sky.params)[1]), - tsrc->getTexture((*event.set_sky.params)[2]), - tsrc->getTexture((*event.set_sky.params)[3]), - tsrc->getTexture((*event.set_sky.params)[4]), - tsrc->getTexture((*event.set_sky.params)[5])); - } - // Handle everything else as plain color - else { - if(*event.set_sky.type != "plain") - infostream<<"Unknown sky type: " - <<(*event.set_sky.type)<setFallbackBgColor(*event.set_sky.bgcolor); - } - - delete event.set_sky.bgcolor; - delete event.set_sky.type; - delete event.set_sky.params; - } - else if (event.type == CE_OVERRIDE_DAY_NIGHT_RATIO) { - bool enable = event.override_day_night_ratio.do_override; - u32 value = event.override_day_night_ratio.ratio_f * 1000; - client.getEnv().setDayNightRatioOverride(enable, value); - } - } - } - - //TimeTaker //timer2("//timer2"); - - /* - For interaction purposes, get info about the held item - - What item is it? - - Is it a usable item? - - Can it point to liquids? - */ - ItemStack playeritem; - { - InventoryList *mlist = local_inventory.getList("main"); - if((mlist != NULL) && (client.getPlayerItem() < mlist->getSize())) - playeritem = mlist->getItem(client.getPlayerItem()); - } - const ItemDefinition &playeritem_def = - playeritem.getDefinition(itemdef); - ToolCapabilities playeritem_toolcap = - playeritem.getToolCapabilities(itemdef); - - /* - Update camera - */ - - v3s16 old_camera_offset = camera.getOffset(); - - LocalPlayer* player = client.getEnv().getLocalPlayer(); - float full_punch_interval = playeritem_toolcap.full_punch_interval; - float tool_reload_ratio = time_from_last_punch / full_punch_interval; - - if(input->wasKeyDown(getKeySetting("keymap_camera_mode"))) { - camera.toggleCameraMode(); - GenericCAO* playercao = player->getCAO(); - - assert( playercao != NULL ); - if (camera.getCameraMode() > CAMERA_MODE_FIRST) { - playercao->setVisible(true); - } - else { - playercao->setVisible(false); - } - } - tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); - camera.update(player, dtime, busytime, tool_reload_ratio, - client.getEnv()); - camera.step(dtime); - + if (clouds) { v3f player_position = player->getPosition(); - v3f camera_position = camera.getPosition(); - v3f camera_direction = camera.getDirection(); - f32 camera_fov = camera.getFovMax(); - v3s16 camera_offset = camera.getOffset(); - - bool camera_offset_changed = (camera_offset != old_camera_offset); - - if(!disable_camera_update){ - client.getEnv().getClientMap().updateCamera(camera_position, - camera_direction, camera_fov, camera_offset); - if (camera_offset_changed){ - client.updateCameraOffset(camera_offset); - client.getEnv().updateCameraOffset(camera_offset); - if (clouds) - clouds->updateCameraOffset(camera_offset); - } - } - - // Update sound listener - sound->updateListener(camera.getCameraNode()->getPosition()+intToFloat(camera_offset, BS), - v3f(0,0,0), // velocity - camera.getDirection(), - camera.getCameraNode()->getUpVector()); - sound->setListenerGain(g_settings->getFloat("sound_volume")); - - /* - Update sound maker - */ - { - soundmaker.step(dtime); - - ClientMap &map = client.getEnv().getClientMap(); - MapNode n = map.getNodeNoEx(player->getStandingNodePos()); - soundmaker.m_player_step_sound = nodedef->get(n).sound_footstep; - } - - /* - Calculate what block is the crosshair pointing to - */ - - //u32 t1 = device->getTimer()->getRealTime(); - - f32 d = playeritem_def.range; // max. distance - f32 d_hand = itemdef->get("").range; - if(d < 0 && d_hand >= 0) - d = d_hand; - else if(d < 0) - d = 4.0; - core::line3d shootline(camera_position, - camera_position + camera_direction * BS * (d+1)); - - - // prevent player pointing anything in front-view - if (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT) - shootline = core::line3d(0,0,0,0,0,0); - -#ifdef HAVE_TOUCHSCREENGUI - if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { - shootline = g_touchscreengui->getShootline(); - shootline.start += intToFloat(camera_offset,BS); - shootline.end += intToFloat(camera_offset,BS); - } -#endif - - ClientActiveObject *selected_object = NULL; - - PointedThing pointed = getPointedThing( - // input - &client, player_position, camera_direction, - camera_position, shootline, d, - playeritem_def.liquids_pointable, !ldown_for_dig, - camera_offset, - // output - hilightboxes, - selected_object); - - if(pointed != pointed_old) - { - infostream<<"Pointing at "<getBool("enable_node_highlighting")) { - if (pointed.type == POINTEDTHING_NODE) { - client.setHighlighted(pointed.node_undersurface, show_hud); - } else { - client.setHighlighted(pointed.node_undersurface, false); - } - } - } - - /* - Stop digging when - - releasing left mouse button - - pointing away from node - */ - if(digging) - { - if(input->getLeftReleased()) - { - infostream<<"Left button released" - <<" (stopped digging)"<getLeftState()) - { - ldown_for_dig = false; - } - - bool left_punch = false; - soundmaker.m_player_leftpunch_sound.name = ""; - - if(input->getRightState()) - repeat_rightclick_timer += dtime; - else - repeat_rightclick_timer = 0; - - if(playeritem_def.usable && input->getLeftState()) - { - if(input->getLeftClicked()) - client.interact(4, pointed); - } - else if(pointed.type == POINTEDTHING_NODE) - { - v3s16 nodepos = pointed.node_undersurface; - v3s16 neighbourpos = pointed.node_abovesurface; - - /* - Check information text of node - */ - - ClientMap &map = client.getEnv().getClientMap(); - NodeMetadata *meta = map.getNodeMetadata(nodepos); - if(meta){ - infotext = narrow_to_wide(meta->getString("infotext")); - } else { - MapNode n = map.getNode(nodepos); - if(nodedef->get(n).tiledef[0].name == "unknown_node.png"){ - infotext = L"Unknown node: "; - infotext += narrow_to_wide(nodedef->get(n).name); - } - } - - /* - Handle digging - */ - - if(nodig_delay_timer <= 0.0 && input->getLeftState() - && client.checkPrivilege("interact")) - { - if(!digging) - { - infostream<<"Started digging"<get(n).groups, - &playeritem_toolcap); - // If can't dig, try hand - if(!params.diggable){ - const ItemDefinition &hand = itemdef->get(""); - const ToolCapabilities *tp = hand.tool_capabilities; - if(tp) - params = getDigParams(nodedef->get(n).groups, tp); - } - - float dig_time_complete = 0.0; - - if(params.diggable == false) - { - // I guess nobody will wait for this long - dig_time_complete = 10000000.0; - } - else - { - dig_time_complete = params.time; - if (g_settings->getBool("enable_particles")) - { - const ContentFeatures &features = - client.getNodeDefManager()->get(n); - addPunchingParticles - (gamedef, smgr, player, client.getEnv(), - nodepos, features.tiles); - } - } - - if(dig_time_complete >= 0.001) - { - dig_index = (u16)((float)crack_animation_length - * dig_time/dig_time_complete); - } - // This is for torches - else - { - dig_index = crack_animation_length; - } - - SimpleSoundSpec sound_dig = nodedef->get(n).sound_dig; - if(sound_dig.exists() && params.diggable){ - if(sound_dig.name == "__group"){ - if(params.main_group != ""){ - soundmaker.m_player_leftpunch_sound.gain = 0.5; - soundmaker.m_player_leftpunch_sound.name = - std::string("default_dig_") + - params.main_group; - } - } else{ - soundmaker.m_player_leftpunch_sound = sound_dig; - } - } - - // Don't show cracks if not diggable - if(dig_time_complete >= 100000.0) - { - } - else if(dig_index < crack_animation_length) - { - //TimeTaker timer("client.setTempMod"); - //infostream<<"dig_index="<getBool("enable_particles")) - { - const ContentFeatures &features = - client.getNodeDefManager()->get(wasnode); - addDiggingParticles - (gamedef, smgr, player, client.getEnv(), - nodepos, features.tiles); - } - - dig_time = 0; - digging = false; - - nodig_delay_timer = dig_time_complete - / (float)crack_animation_length; - - // We don't want a corresponding delay to - // very time consuming nodes - if(nodig_delay_timer > 0.3) - nodig_delay_timer = 0.3; - // We want a slight delay to very little - // time consuming nodes - float mindelay = 0.15; - if(nodig_delay_timer < mindelay) - nodig_delay_timer = mindelay; - - // Send event to trigger sound - MtEvent *e = new NodeDugEvent(nodepos, wasnode); - gamedef->event()->put(e); - } - - if(dig_time_complete < 100000.0) - dig_time += dtime; - else { - dig_time = 0; - client.setCrack(-1, nodepos); - } - - camera.setDigging(0); // left click animation - } - - if((input->getRightClicked() || - repeat_rightclick_timer >= - g_settings->getFloat("repeat_rightclick_time")) && - client.checkPrivilege("interact")) - { - repeat_rightclick_timer = 0; - infostream<<"Ground right-clicked"<getString("formspec") != "" && !random_input - && !input->isKeyDown(getKeySetting("keymap_sneak"))) - { - infostream<<"Launching custom inventory view"<setFormSpec(meta->getString("formspec"), inventoryloc); - } - // Otherwise report right click to server - else - { - camera.setDigging(1); // right click animation (always shown for feedback) - - // If the wielded item has node placement prediction, - // make that happen - bool placed = nodePlacementPrediction(client, - playeritem_def, - nodepos, neighbourpos); - - if(placed) { - // Report to server - client.interact(3, pointed); - // Read the sound - soundmaker.m_player_rightpunch_sound = - playeritem_def.sound_place; - } else { - soundmaker.m_player_rightpunch_sound = - SimpleSoundSpec(); - } - - if (playeritem_def.node_placement_prediction == "" || - nodedef->get(map.getNode(nodepos)).rightclickable) - client.interact(3, pointed); // Report to server - } - } - } - else if(pointed.type == POINTEDTHING_OBJECT) - { - infotext = narrow_to_wide(selected_object->infoText()); - - if(infotext == L"" && show_debug){ - infotext = narrow_to_wide(selected_object->debugInfoText()); - } - - //if(input->getLeftClicked()) - if(input->getLeftState()) - { - bool do_punch = false; - bool do_punch_damage = false; - if(object_hit_delay_timer <= 0.0){ - do_punch = true; - do_punch_damage = true; - object_hit_delay_timer = object_hit_delay; - } - if(input->getLeftClicked()){ - do_punch = true; - } - if(do_punch){ - infostream<<"Left-clicked object"<getPosition(); - v3f dir = (objpos - player_position).normalize(); - - bool disable_send = selected_object->directReportPunch( - dir, &playeritem, time_from_last_punch); - time_from_last_punch = 0; - if(!disable_send) - client.interact(0, pointed); - } - } - else if(input->getRightClicked()) - { - infostream<<"Right-clicked object"<getLeftState()) - { - // When button is held down in air, show continuous animation - left_punch = true; - } - - pointed_old = pointed; - - if(left_punch || input->getLeftClicked()) - { - camera.setDigging(0); // left click animation - } - - input->resetLeftClicked(); - input->resetRightClicked(); - - input->resetLeftReleased(); - input->resetRightReleased(); - - /* - Calculate stuff for drawing - */ - - /* - Fog range - */ - - if(draw_control.range_all) - fog_range = 100000*BS; - else { - fog_range = draw_control.wanted_range*BS + 0.0*MAP_BLOCKSIZE*BS; - fog_range = MYMIN(fog_range, (draw_control.farthest_drawn+20)*BS); - fog_range *= 0.9; - } - - /* - Calculate general brightness - */ - u32 daynight_ratio = client.getEnv().getDayNightRatio(); - float time_brightness = decode_light_f((float)daynight_ratio/1000.0); - float direct_brightness = 0; - bool sunlight_seen = false; - if(g_settings->getBool("free_move")){ - direct_brightness = time_brightness; - sunlight_seen = true; + if (sky->getCloudsVisible()) { + clouds->setVisible(true); + clouds->step(dtime); + clouds->update(v2f(player_position.X, player_position.Z), + sky->getCloudColor()); } else { - ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG); - float old_brightness = sky->getBrightness(); - direct_brightness = (float)client.getEnv().getClientMap() - .getBackgroundBrightness(MYMIN(fog_range*1.2, 60*BS), - daynight_ratio, (int)(old_brightness*255.5), &sunlight_seen) - / 255.0; + clouds->setVisible(false); } + } - time_of_day = client.getEnv().getTimeOfDayF(); - float maxsm = 0.05; - if(fabs(time_of_day - time_of_day_smooth) > maxsm && - fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm && - fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm) - time_of_day_smooth = time_of_day; - float todsm = 0.05; - if(time_of_day_smooth > 0.8 && time_of_day < 0.2) - time_of_day_smooth = time_of_day_smooth * (1.0-todsm) - + (time_of_day+1.0) * todsm; - else - time_of_day_smooth = time_of_day_smooth * (1.0-todsm) - + time_of_day * todsm; + /* + Update particles + */ - sky->update(time_of_day_smooth, time_brightness, direct_brightness, - sunlight_seen,camera.getCameraMode(), player->getYaw(), - player->getPitch()); + allparticles_step(dtime); + allparticlespawners_step(dtime, client->getEnv()); - video::SColor bgcolor = sky->getBgColor(); - video::SColor skycolor = sky->getSkyColor(); + /* + Fog + */ - /* - Update clouds - */ - if(clouds){ - if(sky->getCloudsVisible()){ - clouds->setVisible(true); - clouds->step(dtime); - clouds->update(v2f(player_position.X, player_position.Z), - sky->getCloudColor()); - } else{ - clouds->setVisible(false); - } - } - - /* - Update particles - */ - - allparticles_step(dtime); - allparticlespawners_step(dtime, client.getEnv()); - - /* - Fog - */ - - if(g_settings->getBool("enable_fog") && !force_fog_off) - { - driver->setFog( - bgcolor, + if (g_settings->getBool("enable_fog") && !flags.force_fog_off) { + driver->setFog( + sky->getBgColor(), video::EFT_FOG_LINEAR, - fog_range*0.4, - fog_range*1.0, + interactArgs->fog_range * 0.4, + interactArgs->fog_range * 1.0, 0.01, false, // pixel fog false // range fog - ); - } - else - { - driver->setFog( - bgcolor, + ); + } else { + driver->setFog( + sky->getBgColor(), video::EFT_FOG_LINEAR, - 100000*BS, - 110000*BS, + 100000 * BS, + 110000 * BS, 0.01, false, // pixel fog false // range fog - ); + ); + } + + /* + Get chat messages from client + */ + + v2u32 screensize = driver->getScreenSize(); + + updateChat(*client, dtime, flags.show_debug, screensize, + flags.show_chat, interactArgs->profiler_current_page, + *chat_backend, guitext_chat, font); + + /* + Inventory + */ + + bool update_wielded_item_trigger = true; + + if (client->getPlayerItem() != interactArgs->new_playeritem) { + client->selectPlayerItem(interactArgs->new_playeritem); + } + + if (client->getLocalInventoryUpdated()) { + //infostream<<"Updating local inventory"<getLocalInventory(*local_inventory); + + update_wielded_item_trigger = true; + } + + if (update_wielded_item_trigger) { + update_wielded_item_trigger = false; + // Update wielded tool + InventoryList *mlist = local_inventory->getList("main"); + ItemStack item; + + if (mlist && (client->getPlayerItem() < mlist->getSize())) + item = mlist->getItem(client->getPlayerItem()); + + camera->wield(item, client->getPlayerItem()); + } + + /* + Update block draw list every 200ms or when camera direction has + changed much + */ + interactArgs->update_draw_list_timer += dtime; + + v3f camera_direction = camera->getDirection(); + if (interactArgs->update_draw_list_timer >= 0.2 + || interactArgs->update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 + || flags.camera_offset_changed) { + interactArgs->update_draw_list_timer = 0; + client->getEnv().getClientMap().updateDrawList(driver); + interactArgs->update_draw_list_last_cam_dir = camera_direction; + } + + updateGui(&interactArgs->statustext_time, *stats, dtime, flags, cam); + + /* + make sure menu is on top + 1. Delete formspec menu reference if menu was removed + 2. Else, make sure formspec menu is on top + */ + if (current_formspec) { + if (current_formspec->getReferenceCount() == 1) { + current_formspec->drop(); + current_formspec = NULL; + } else if (!noMenuActive()) { + guiroot->bringToFront(current_formspec); } + } - /* - Update gui stuff (0ms) - */ + /* + Drawing begins + */ - //TimeTaker guiupdatetimer("Gui updating"); + video::SColor skycolor = sky->getSkyColor(); - if(show_debug) - { - static float drawtime_avg = 0; - drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05; - /*static float beginscenetime_avg = 0; - beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05; - static float scenetime_avg = 0; - scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05; - static float endscenetime_avg = 0; - endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;*/ + TimeTaker tt_draw("mainloop: draw"); + { + TimeTaker timer("beginScene"); + driver->beginScene(true, true, skycolor); + stats->beginscenetime = timer.stop(true); + } - u16 fps = (1.0/dtime_avg1); + draw_scene(driver, smgr, *camera, *client, player, *hud, guienv, + highlight_boxes, screensize, skycolor, flags.show_hud); - std::ostringstream os(std::ios_base::binary); - os<setText(narrow_to_wide(os.str()).c_str()); - guitext->setVisible(true); - } - else - { - guitext->setVisible(false); - } + /* + Profiler graph + */ + if (flags.show_profiler_graph) + graph->draw(10, screensize.Y - 10, driver, font); - if (guitext->isVisible()) - { - core::rect rect( - 5, - 5, - screensize.X, - 5 + text_height - ); - guitext->setRelativePosition(rect); - } - - if(show_debug) - { - std::ostringstream os(std::ios_base::binary); - os<setText(narrow_to_wide(os.str()).c_str()); - guitext2->setVisible(true); - - core::rect rect( - 5, - 5 + text_height, - screensize.X, - 5 + (text_height * 2) - ); - guitext2->setRelativePosition(rect); - } - else - { - guitext2->setVisible(false); - } - - { - guitext_info->setText(infotext.c_str()); - guitext_info->setVisible(show_hud && g_menumgr.menuCount() == 0); - } - - { - float statustext_time_max = 1.5; - if(!statustext.empty()) - { - statustext_time += dtime; - if(statustext_time >= statustext_time_max) - { - statustext = L""; - statustext_time = 0; - } - } - guitext_status->setText(statustext.c_str()); - guitext_status->setVisible(!statustext.empty()); - - if(!statustext.empty()) - { - s32 status_y = screensize.Y - 130; - core::rect rect( - 10, - status_y - guitext_status->getTextHeight(), - 10 + guitext_status->getTextWidth(), - status_y - ); - guitext_status->setRelativePosition(rect); - - // Fade out - video::SColor initial_color(255,0,0,0); - if(guienv->getSkin()) - initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT); - video::SColor final_color = initial_color; - final_color.setAlpha(0); - video::SColor fade_color = - initial_color.getInterpolated_quadratic( - initial_color, - final_color, - pow(statustext_time / (float)statustext_time_max, 2.0f)); - guitext_status->setOverrideColor(fade_color); - guitext_status->enableOverrideColor(true); - } - } - - /* - Get chat messages from client - */ - updateChat(client, dtime, show_debug, screensize, show_chat, - show_profiler, chat_backend, guitext_chat, font); - - /* - Inventory - */ - - if(client.getPlayerItem() != new_playeritem) - { - client.selectPlayerItem(new_playeritem); - } - if(client.getLocalInventoryUpdated()) - { - //infostream<<"Updating local inventory"<getSize())) - item = mlist->getItem(client.getPlayerItem()); - camera.wield(item, client.getPlayerItem()); - } - - /* - Update block draw list every 200ms or when camera direction has - changed much - */ - update_draw_list_timer += dtime; - if(update_draw_list_timer >= 0.2 || - update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2 || - camera_offset_changed){ - update_draw_list_timer = 0; - client.getEnv().getClientMap().updateDrawList(driver); - update_draw_list_last_cam_dir = camera_direction; - } - - /* - 1. Delete formspec menu reference if menu was removed - 2. Else, make sure formspec menu is on top - */ - if (current_formspec) { - if (current_formspec->getReferenceCount() == 1) { - current_formspec->drop(); - current_formspec = NULL; - } else if (!noMenuActive()) { - guiroot->bringToFront(current_formspec); - } - } - - /* - Drawing begins - */ - TimeTaker tt_draw("mainloop: draw"); - { - TimeTaker timer("beginScene"); - driver->beginScene(true, true, skycolor); - beginscenetime = timer.stop(true); - } - - - draw_scene(driver, smgr, camera, client, player, hud, guienv, - hilightboxes, screensize, skycolor, show_hud); - - /* - Profiler graph - */ - if(show_profiler_graph) - { - graph.draw(10, screensize.Y - 10, driver, font); - } - - /* - Damage flash - */ - if(damage_flash > 0.0) - { - video::SColor color(std::min(damage_flash, 180.0f),180,0,0); - driver->draw2DRectangle(color, - core::rect(0,0,screensize.X,screensize.Y), + /* + Damage flash + */ + if (interactArgs->damage_flash > 0.0) { + video::SColor color(std::min(interactArgs->damage_flash, 180.0f), + 180, + 0, + 0); + driver->draw2DRectangle(color, + core::rect(0, 0, screensize.X, screensize.Y), NULL); - damage_flash -= 100.0*dtime; - } - - /* - Damage camera tilt - */ - if(player->hurt_tilt_timer > 0.0) - { - player->hurt_tilt_timer -= dtime*5; - if(player->hurt_tilt_timer < 0) - player->hurt_tilt_strength = 0; - } - - /* - End scene - */ - { - TimeTaker timer("endScene"); - driver->endScene(); - endscenetime = timer.stop(true); - } - - drawtime = tt_draw.stop(true); - g_profiler->graphAdd("mainloop_draw", (float)drawtime/1000.0f); - - /* - End of drawing - */ - - /* - Log times and stuff for visualization - */ - Profiler::GraphValues values; - g_profiler->graphGet(values); - graph.put(values); + interactArgs->damage_flash -= 100.0 * dtime; } /* - Drop stuff + Damage camera tilt */ - if (clouds) - clouds->drop(); - if (gui_chat_console) - gui_chat_console->drop(); - if (sky) - sky->drop(); - clear_particles(); + if (player->hurt_tilt_timer > 0.0) { + player->hurt_tilt_timer -= dtime * 5; - /* cleanup menus */ - while (g_menumgr.menuCount() > 0) - { - g_menumgr.m_stack.front()->setVisible(false); - g_menumgr.deletingMenu(g_menumgr.m_stack.front()); - } - if (current_formspec) { - current_formspec->drop(); - current_formspec = NULL; + if (player->hurt_tilt_timer < 0) + player->hurt_tilt_strength = 0; } /* - Draw a "shutting down" screen, which will be shown while the map - generator and other stuff quits + End scene */ { - wchar_t* text = wgettext("Shutting down stuff..."); - draw_load_screen(text, device, guienv, font, 0, -1, false); - delete[] text; + TimeTaker timer("endScene"); + driver->endScene(); + stats->endscenetime = timer.stop(true); } - chat_backend.addMessage(L"", L"# Disconnected."); - chat_backend.addMessage(L"", L""); + stats->drawtime = tt_draw.stop(true); + g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f); +} - client.Stop(); - //force answer all texture and shader jobs (TODO return empty values) +void MinetestApp::updateGui(float *statustext_time, const RunStats& stats, + f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam) +{ + v2u32 screensize = driver->getScreenSize(); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + v3f player_position = player->getPosition(); - while(!client.isShutdown()) { - tsrc->processQueue(); - shsrc->processQueue(); - sleep_ms(100); + if (flags.show_debug) { + static float drawtime_avg = 0; + drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05; + + u16 fps = 1.0 / stats.dtime_jitter.avg; + + std::ostringstream os(std::ios_base::binary); + os << std::fixed + << "Minetest " << minetest_version_hash + << " FPS = " << fps + << " (R: range_all=" << draw_control->range_all << ")" + << std::setprecision(0) + << " drawtime = " << drawtime_avg + << std::setprecision(1) + << ", dtime_jitter = " + << (stats.dtime_jitter.max_fraction * 100.0) << " %" + << std::setprecision(1) + << ", v_range = " << draw_control->wanted_range + << std::setprecision(3) + << ", RTT = " << client->getRTT(); + guitext->setText(narrow_to_wide(os.str()).c_str()); + guitext->setVisible(true); + } else if (flags.show_hud || flags.show_chat) { + std::ostringstream os(std::ios_base::binary); + os << "Minetest " << minetest_version_hash; + guitext->setText(narrow_to_wide(os.str()).c_str()); + guitext->setVisible(true); + } else { + guitext->setVisible(false); } - // Client scope (client is destructed before destructing *def and tsrc) - }while(0); - } // try-catch - catch(SerializationError &e) - { + if (guitext->isVisible()) { + core::rect rect( + 5, 5, + screensize.X, 5 + text_height + ); + guitext->setRelativePosition(rect); + } + + if (flags.show_debug) { + std::ostringstream os(std::ios_base::binary); + os << std::setprecision(1) << std::fixed + << "(" << (player_position.X / BS) + << ", " << (player_position.Y / BS) + << ", " << (player_position.Z / BS) + << ") (yaw=" << (wrapDegrees_0_360(cam.camera_yaw)) + << ") (seed = " << ((u64)client->getMapSeed()) + << ")"; + guitext2->setText(narrow_to_wide(os.str()).c_str()); + guitext2->setVisible(true); + + core::rect rect( + 5, 5 + text_height, + screensize.X, 5 + text_height * 2 + ); + guitext2->setRelativePosition(rect); + } else { + guitext2->setVisible(false); + } + + guitext_info->setText(infotext.c_str()); + guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0); + + float statustext_time_max = 1.5; + + if (!statustext.empty()) { + *statustext_time += dtime; + + if (*statustext_time >= statustext_time_max) { + statustext = L""; + *statustext_time = 0; + } + } + + guitext_status->setText(statustext.c_str()); + guitext_status->setVisible(!statustext.empty()); + + if (!statustext.empty()) { + s32 status_y = screensize.Y - 130; + core::rect rect( + 10, status_y - guitext_status->getTextHeight(), + 10 + guitext_status->getTextWidth(), status_y + ); + guitext_status->setRelativePosition(rect); + + // Fade out + video::SColor initial_color(255, 0, 0, 0); + + if (guienv->getSkin()) + initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT); + + video::SColor final_color = initial_color; + final_color.setAlpha(0); + video::SColor fade_color = initial_color.getInterpolated_quadratic( + initial_color, final_color, + pow(*statustext_time / statustext_time_max, 2.0f)); + guitext_status->setOverrideColor(fade_color); + guitext_status->enableOverrideColor(true); + } +} + + +/* Log times and stuff for visualization */ +inline void MinetestApp::updateProfilerGraphs(ProfilerGraph *graph) +{ + Profiler::GraphValues values; + g_profiler->graphGet(values); + graph->put(values); +} + + + +/**************************************************************************** + Misc + ****************************************************************************/ + +/* On some computers framerate doesn't seem to be automatically limited + * + * *Must* be called after device->run() so that device->getTimer()->getTime(); + * is correct + */ +inline void MinetestApp::limitFps(FpsControl *params, f32 *dtime) +{ + // not using getRealTime is necessary for wine + u32 time = device->getTimer()->getTime(); + + u32 last_time = params->last_time; + + if (time > last_time) // Make sure last_time hasn't overflowed + params->busy_time = time - last_time; + else + params->busy_time = 0; + + u32 frametime_min = 1000 / (g_menumgr.pausesGame() + ? g_settings->getFloat("pause_fps_max") + : g_settings->getFloat("fps_max")); + + if (params->busy_time < frametime_min) { + params->sleep_time = frametime_min - params->busy_time; + device->sleep(params->sleep_time); + } else { + params->sleep_time = 0; + } + + // Necessary for device->getTimer()->getTime() + device->run(); + time = device->getTimer()->getTime(); + + if (time > last_time) // Make sure last_time hasn't overflowed + *dtime = (time - last_time) / 1000.0; + else + *dtime = 0; + + params->last_time = time; +} + + +void MinetestApp::showOverlayMessage(const char *msg, float dtime, + int percent, bool draw_clouds) +{ + wchar_t *text = wgettext(msg); + draw_load_screen(text, device, guienv, font, dtime, percent, draw_clouds); + delete[] text; +} + + +inline const char *MinetestApp::boolToCStr(bool v) +{ + static const char *str[] = { "false", "true" }; + return str[v]; +} + + + +/**************************************************************************** + Shutdown / cleanup + ****************************************************************************/ + +void MinetestApp::extendedResourceCleanup() +{ + // Extended resource accounting + infostream << "Irrlicht resources after cleanup:" << std::endl; + infostream << "\tRemaining meshes : " + << device->getSceneManager()->getMeshCache()->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; +} + + + +/**************************************************************************** + extern function for launching the game + ****************************************************************************/ + +void the_game(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + gui::IGUIFont *font, + + const std::string &map_dir, + const std::string &playername, + const std::string &password, + const std::string &address, // If empty local server is created + u16 port, + + std::wstring &error_message, + ChatBackend &chat_backend, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode) +{ + MinetestApp app; + + /* Make a copy of the server address because if a local singleplayer server + * is created then this is updated and we don't want to change the value + * passed to us by the calling function + */ + std::string server_address = address; + + try { + + if (app.startup(kill, random_input, input, device, font, map_dir, + playername, password, &server_address, port, + &error_message, &chat_backend, gamespec, + simple_singleplayer_mode)) { + + //std::cout << "App started" << std::endl; + app.run(); + app.shutdown(); + } + + } catch (SerializationError &e) { error_message = L"A serialization error occurred:\n" + narrow_to_wide(e.what()) + L"\n\nThe server is probably " L" running a different version of Minetest."; - errorstream<getSceneManager()->getMeshCache()->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; } diff --git a/src/game.h b/src/game.h index 1c831c530..c3a7691d8 100644 --- a/src/game.h +++ b/src/game.h @@ -35,11 +35,14 @@ class KeyList : protected std::list { const_iterator f(begin()); const_iterator e(end()); - while (f!=e) { + + while (f != e) { if (*f == key) return f; + ++f; } + return e; } @@ -47,16 +50,22 @@ class KeyList : protected std::list { iterator f(begin()); iterator e(end()); - while (f!=e) { + + while (f != e) { if (*f == key) return f; + ++f; } + return e; } public: - void clear() { super::clear(); } + void clear() + { + super::clear(); + } void set(const KeyPress &key) { @@ -67,6 +76,7 @@ public: void unset(const KeyPress &key) { iterator p(find(key)); + if (p != end()) erase(p); } @@ -74,6 +84,7 @@ public: void toggle(const KeyPress &key) { iterator p(this->find(key)); + if (p != end()) erase(p); else @@ -98,7 +109,7 @@ public: virtual bool isKeyDown(const KeyPress &keyCode) = 0; virtual bool wasKeyDown(const KeyPress &keyCode) = 0; - + virtual v2s32 getMousePos() = 0; virtual void setMousePos(s32 x, s32 y) = 0; @@ -114,33 +125,31 @@ public: virtual bool getRightReleased() = 0; virtual void resetLeftReleased() = 0; virtual void resetRightReleased() = 0; - + virtual s32 getMouseWheel() = 0; - virtual void step(float dtime) {}; + virtual void step(float dtime) {} - virtual void clear() {}; + virtual void clear() {} }; class ChatBackend; /* to avoid having to include chat.h */ struct SubgameSpec; -void the_game( - bool &kill, - bool random_input, - InputHandler *input, - IrrlichtDevice *device, - gui::IGUIFont* font, - std::string map_dir, - std::string playername, - std::string password, - std::string address, // If "", local server is used - u16 port, - std::wstring &error_message, - ChatBackend &chat_backend, - const SubgameSpec &gamespec, // Used for local game - bool simple_singleplayer_mode -); +void the_game(bool *kill, + bool random_input, + InputHandler *input, + IrrlichtDevice *device, + gui::IGUIFont *font, + const std::string &map_dir, + const std::string &playername, + const std::string &password, + const std::string &address, // If "", local server is used + u16 port, + std::wstring &error_message, + ChatBackend &chat_backend, + const SubgameSpec &gamespec, // Used for local game + bool simple_singleplayer_mode); #endif diff --git a/src/main.cpp b/src/main.cpp index 9d336825e..7a6b3e47c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1861,7 +1861,7 @@ int main(int argc, char *argv[]) g_touchscreengui = receiver->m_touchscreengui; #endif the_game( - kill, + &kill, random_input, input, device,