From 429ecb2b94a66a11cf06be7303d05aa2038d19d2 Mon Sep 17 00:00:00 2001 From: Craig Robbins Date: Fri, 31 Oct 2014 22:13:04 +1000 Subject: 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 | 5331 +++++++++++++++++++++++++++++++--------------------------- src/game.h | 55 +- src/main.cpp | 2 +- 3 files changed, 2904 insertions(+), 2484 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; - - 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; - - std::vector boxes = n.getSelectionBoxes(nodedef); - - v3s16 np(x,y,z); - v3f npf = intToFloat(np, BS); - - 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) + if (yend == 32767) + yend = 32766; + + if (zend == 32767) + zend = 32766; + + if (xend == 32767) + xend = 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(!facebox.intersectsWithLine(shootline)) + } + + if (!isPointableNode(n, client, liquids_pointable)) 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); + std::vector boxes = n.getSelectionBoxes(nodedef); + + v3s16 np(x, y, z); + v3f npf = intToFloat(np, BS); + + 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))) { - - // This triggers the required mesh update too - client.addNode(p, n); - return true; - } - }catch(InvalidPositionException &e){ - errorstream<<"Node placement prediction failed for " - <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 " + << playeritem_def.name << " (places " + << prediction + << ") - Position not loaded" << std::endl; } } + return false; } -static inline void create_formspec_menu(GUIFormSpecMenu** cur_formspec, +static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec, InventoryManager *invmgr, IGameDef *gamedef, - IWritableTextureSource* tsrc, IrrlichtDevice * device, - IFormSource* fs_src, TextDest* txt_dest, Client* client - ) { + IWritableTextureSource *tsrc, IrrlichtDevice *device, + IFormSource *fs_src, TextDest *txt_dest, Client *client) +{ if (*cur_formspec == 0) { *cur_formspec = new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr, - invmgr, gamedef, tsrc, fs_src, txt_dest, client); + invmgr, gamedef, tsrc, fs_src, txt_dest, client); (*cur_formspec)->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,2486 +1159,2853 @@ 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) -{ - GUIFormSpecMenu* current_formspec = 0; - video::IVideoDriver* driver = device->getVideoDriver(); - scene::ISceneManager* smgr = device->getSceneManager(); - // Calculate text height using the font - u32 text_height = font->getDimension(L"Random test string").Height; - /* - Draw "Loading" screen - */ +/**************************************************************************** + THE GAME + ****************************************************************************/ - { - wchar_t* text = wgettext("Loading..."); - draw_load_screen(text, device, guienv, font, 0, 0); - delete[] text; - } +const float object_hit_delay = 0.2; - // Create texture source - IWritableTextureSource *tsrc = createTextureSource(device); +struct FpsControl { + u32 last_time, busy_time, sleep_time; +}; - // Create shader source - IWritableShaderSource *shsrc = createShaderSource(device); - // These will be filled by data received from the server - // Create item definition manager - IWritableItemDefManager *itemdef = createItemDefManager(); - // Create node definition manager - IWritableNodeDefManager *nodedef = createNodeDefManager(); +/* 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) + */ - // Sound fetcher (useful when testing) - GameOnDemandSoundFetcher soundfetcher; +struct CameraOrientation { + f32 camera_yaw; // "right/left" + f32 camera_pitch; // "up/down" +}; - // Sound manager - ISoundManager *sound = NULL; - bool sound_is_dummy = false; -#if USE_SOUND - if(g_settings->getBool("enable_sound")){ - infostream<<"Attempting to use OpenAL audio"<get("bind_address"); - Address bind_addr(0,0,0,0, port); - 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; - } +/* 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 +{ +public: + MinetestApp(); + ~MinetestApp(); + + 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); + + 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); - 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); - } + gui::IGUIFont *font; - do{ // Client scope (breakable do-while(0)) + IWritableTextureSource *texture_src; + IWritableShaderSource *shader_src; - /* - Create client - */ + // When created, these will be filled with data received from the server + IWritableItemDefManager *itemdef_manager; + IWritableNodeDefManager *nodedef_manager; - { - wchar_t* text = wgettext("Creating client..."); - draw_load_screen(text, device, guienv, font, 0, 50); - delete[] text; - } - infostream<<"Creating client"<getBool("enable_ipv6")) { - error_message = L"Unable to connect to " + - narrow_to_wide(connect_address.serializeString()) + - L" because IPv6 is disabled"; - errorstream<Drop() + MapDrawControl *draw_control; + Camera *camera; + Clouds *clouds; // Free using ->Drop() + Sky *sky; // Free using ->Drop() + Inventory *local_inventory; + Hud *hud; - /* - Attempt to connect to the server + /* '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; + + 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 - infostream<<"Connecting to server at "; - connect_address.print(&infostream); - infostream<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); +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) +{ - // 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; - } - // Update client and server - client.step(dtime); - if (server != NULL) - server->step(dtime); +MinetestApp::~MinetestApp() +{ + delete client; + delete soundmaker; + if (!sound_is_dummy) + delete sound; - // End condition - if (client.mediaReceived() && - client.itemdefReceived() && - client.nodedefReceived()) { - got_content = true; - break; - } - // Break conditions - if (client.accessDenied()) { - error_message = L"Access denied. Reason: " - +client.accessDeniedReason(); - errorstream<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::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; +} - if (cur > 900) { - cur /= 1024.0; - cur_unit = gettext(" MB/s"); - } - message << " ( " << cur << cur_unit << " )"; - } - progress = 50+client.mediaReceiveProgress()*50+0.5; - draw_load_screen(narrow_to_wide(message.str().c_str()), device, - guienv, font, dtime, progress); - } - // 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; - } - } +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 - if(!got_content){ - if(error_message == L"" && !content_aborted){ - error_message = L"Something failed"; - errorstream<getBool("invert_mouse"); - f32 camera_yaw = 0; // "right/left" - f32 camera_pitch = 0; // "up/down" + /* Clear the profiler */ + Profiler::GraphValues dummyvalues; + g_profiler->graphGet(dummyvalues); - /* - Clouds - */ + draw_times.last_time = device->getTimer()->getTime(); - Clouds *clouds = NULL; - if(g_settings->getBool("enable_clouds")) - { - clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0)); - } + shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter( + sky, + &flags.force_fog_off, + &interactArgs.fog_range, + client)); - /* - Skybox thingy - */ + while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) { - Sky *sky = NULL; - sky = new Sky(smgr->getRootSceneNode(), smgr, -1); + /* Must be called immediately after a device->run() call because it + * uses device->getTimer()->getTime() + */ + limitFps(&draw_times, &dtime); - scene::ISceneNode* skybox = NULL; + updateStats(&stats, draw_times, dtime); + updateInteractTimers(&interactArgs, dtime); - /* - A copy of the local inventory - */ - Inventory local_inventory(itemdef); + if (!checkConnection()) + break; + if (!handleCallbacks()) + break; - /* - 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; + 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); } +} - /* - Add some gui stuff - */ - // 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( +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()); + } + + if (current_formspec) { + current_formspec->drop(); + current_formspec = NULL; + } + + chat_backend->addMessage(L"", L"# Disconnected."); + chat_backend->addMessage(L"", L""); + + if (client) { + client->Stop(); + while (!client->isShutdown()) { + assert(texture_src != NULL); + assert(shader_src != NULL); + texture_src->processQueue(); + shader_src->processQueue(); + sleep_ms(100); + } + } +} + + + +/**************************************************************************** + 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" << std::endl; + sound = createOpenALSoundManager(&soundfetcher); + if (!sound) + infostream << "Failed to initialize OpenAL audio" << std::endl; + } else + infostream << "Sound disabled." << std::endl; +#endif + + if (!sound) { + infostream << "Using dummy audio." << std::endl; + sound = &dummySoundManager; + sound_is_dummy = true; + } + + soundmaker = new SoundMaker(sound, nodedef_manager); + if (!soundmaker) + return false; + + soundmaker->registerReceiver(eventmgr); + + return true; +} + +bool MinetestApp::createSingleplayerServer(const std::string map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address) +{ + showOverlayMessage("Creating server...", 0, 25); + + std::string bind_str = g_settings->get("bind_address"); + Address bind_addr(0, 0, 0, 0, port); + + 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 " + + narrow_to_wide(bind_addr.serializeString()) + + L" because IPv6 is disabled"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } + + server = new Server(map_dir, gamespec, simple_singleplayer_mode, + bind_addr.isIPv6()); + + server->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; + } + return false; + } + + 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; + } + + // Update cached textures, meshes and materials + client->afterContentReceived(device, font); + + /* 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; + } + } + + /* 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 - gui::IGUIStaticText *guitext_info = guienv->addStaticText( + guitext_info = guienv->addStaticText( L"", - core::rect(0,0,400,text_height*5+5) + v2s32(100,200), + 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.) - gui::IGUIStaticText *guitext_status = guienv->addStaticText( + guitext_status = guienv->addStaticText( L"", - core::rect(0,0,0,0), + core::rect(0, 0, 0, 0), false, false, guiroot); guitext_status->setVisible(false); - std::wstring statustext; - float statustext_time = 0; - // Chat text - gui::IGUIStaticText *guitext_chat = guienv->addStaticText( + guitext_chat = guienv->addStaticText( L"", - core::rect(0,0,0,0), + 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->clearRecentChat(); + // Chat backend and console - GUIChatConsole *gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(), -1, &chat_backend, &client); + 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) - gui::IGUIStaticText *guitext_profiler = guienv->addStaticText( + guitext_profiler = guienv->addStaticText( L"", - core::rect(0,0,0,0), + core::rect(0, 0, 0, 0), false, false, guiroot); - guitext_profiler->setBackgroundColor(video::SColor(120,0,0,0)); + 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()); + g_touchscreengui->init(tsrc, porting::getDisplayDensity()); + #endif - /* - Some statistics are collected in these - */ - u32 drawtime = 0; - u32 beginscenetime = 0; - u32 endscenetime = 0; + return true; +} - float recent_turn_speed = 0.0; +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); - ProfilerGraph graph; - // Initially clear the profiler - Profiler::GraphValues dummyvalues; - g_profiler->graphGet(dummyvalues); + Address connect_address(0, 0, 0, 0, port); - 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; + try { + connect_address.Resolve(address->c_str()); - float damage_flash = 0; + if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY + //connect_address.Resolve("localhost"); + if (connect_address.isIPv6()) { + IPv6AddressBytes addr_bytes; + addr_bytes.bytes[15] = 1; + connect_address.setAddress(&addr_bytes); + } else { + connect_address.setAddress(127, 0, 0, 1); + } + } + } catch (ResolveError &e) { + *error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what()); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } - float jump_timer = 0; - bool reset_jump_timer = false; + if (connect_address.isIPv6() && !g_settings->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; + } - const float object_hit_delay = 0.2; - float object_hit_delay_timer = 0.0; - float time_from_last_punch = 10; + client = new Client(device, playername.c_str(), password, *draw_control, + texture_src, shader_src, itemdef_manager, nodedef_manager, sound, + eventmgr, connect_address.isIPv6()); - float update_draw_list_timer = 0.0; - v3f update_draw_list_last_cam_dir; + if (!client) + return false; - bool invert_mouse = g_settings->getBool("invert_mouse"); + gamedef = client; // Client acts as our GameDef - bool update_wielded_item_trigger = true; - 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 + infostream << "Connecting to server at "; + connect_address.print(&infostream); + infostream << std::endl; - float time_of_day = 0; - float time_of_day_smooth = 0; + client->connect(connect_address); - float repeat_rightclick_timer = 0; /* - Shader constants + Wait for server to accept connection */ - shsrc->addGlobalConstantSetter(new GameGlobalShaderConstantSetter( - sky, &force_fog_off, &fog_range, &client)); - /* - Main loop - */ + try { + input->clear(); - bool first_loop_after_window_activation = true; + FpsControl fps_control = { 0 }; + f32 dtime; // in seconds - // TODO: Convert the static interval timers to these - // Interval limiter for profiler - IntervalLimiter m_profiler_interval; + while (device->run()) { - // 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(); + limitFps(&fps_control, &dtime); - LocalPlayer* player = client.getEnv().getLocalPlayer(); - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; + // Update client and server + client->step(dtime); - /* - HUD object - */ - Hud hud(driver, smgr, guienv, font, text_height, - gamedef, player, &local_inventory); + if (server != NULL) + server->step(dtime); - core::stringw str = L"Minetest ["; - str += driver->getName(); - str += "]"; - device->setWindowCaption(str.c_str()); + // End condition + if (client->getState() == LC_Init) { + *connect_ok = true; + break; + } - // Info text - std::wstring infotext; + // Break conditions + if (client->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)) { + *aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + break; + } + + // Update status + showOverlayMessage("Connecting to server...", dtime, 100); + } + } catch (con::PeerNotFoundException &e) { + // TODO: Should something be done here? At least an info/error + // message? + return false; + } + + return true; +} + +bool MinetestApp::getServerContent(bool *aborted) +{ + input->clear(); + + 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"); + } + + message << " ( " << cur << cur_unit << " )"; + } + + progress = 50 + client->mediaReceiveProgress() * 50 + 0.5; + draw_load_screen(narrow_to_wide(message.str().c_str()), device, + guienv, font, dtime, progress); + } + } + + return true; +} + + + +/**************************************************************************** + Run + ****************************************************************************/ + +inline void MinetestApp::updateInteractTimers(InteractParams *args, f32 dtime) +{ + if (args->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; + } + + return true; +} + + +/* returns false if app should exit, otherwise true + */ +inline bool MinetestApp::handleCallbacks() +{ + if (g_gamecallback->disconnect_requested) { + g_gamecallback->disconnect_requested = false; + return false; + } + + 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; + } + + return true; +} + + +void MinetestApp::processQueues() +{ + texture_src->processQueue(); + itemdef_manager->processQueue(gamedef); + shader_src->processQueue(); +} + + +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->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; - for(;;) - { - if(device->run() == false || kill == true || - g_gamecallback->shutdown_requested) - break; + case HUD_STAT_NUMBER: + e->number = event.hudchange.data; + break; - v2u32 screensize = driver->getScreenSize(); + case HUD_STAT_ITEM: + e->item = event.hudchange.data; + break; - // 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; - } + case HUD_STAT_DIR: + e->dir = event.hudchange.data; + break; - g_profiler->graphAdd("mainloop_other", busytime - (float)drawtime/1000.0f); + case HUD_STAT_ALIGN: + e->align = *event.hudchange.v2fdata; + break; - // Necessary for device->getTimer()->getTime() - device->run(); + case HUD_STAT_OFFSET: + e->offset = *event.hudchange.v2fdata; + break; - /* - FPS limiter - */ + case HUD_STAT_WORLD_POS: + e->world_pos = *event.hudchange.v3fdata; + break; - { - 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); + case HUD_STAT_SIZE: + e->size = *event.hudchange.v2s32data; + break; } - } - // Necessary for device->getTimer()->getTime() - device->run(); + 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; - /* - Time difference calculation - */ - f32 dtime; // in seconds + sky->setFallbackBgColor(*event.set_sky.bgcolor); + } - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; + 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); + } + } +} - g_profiler->graphAdd("mainloop_dtime", dtime); - /* Run timers */ +void MinetestApp::updateCamera(VolatileRunFlags *flags, u32 busy_time, + f32 dtime, float time_from_last_punch) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); - 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; + /* + 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"); - g_profiler->add("Elapsed time", dtime); - g_profiler->avg("FPS", 1./dtime); + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } - /* - Time average and jitter calculation - */ + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); - 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; - } - } + v3s16 old_camera_offset = camera->getOffset(); - /* - Busytime average and jitter calculation - */ + if (input->wasKeyDown(getKeySetting("keymap_camera_mode"))) { + camera->toggleCameraMode(); + GenericCAO *playercao = player->getCAO(); - 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; - } - } + assert(playercao != NULL); - /* - Handle miscellaneous stuff - */ + playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + } - if(client.accessDenied()) - { - error_message = L"Access denied. Reason: " - +client.accessDeniedReason(); - errorstream<disconnect_requested) - { - g_gamecallback->disconnect_requested = false; - break; - } + 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); - if(g_gamecallback->changepassword_requested) - { - (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, &client))->drop(); - g_gamecallback->changepassword_requested = false; - } + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + f32 camera_fov = camera->getFovMax(); + v3s16 camera_offset = camera->getOffset(); - if(g_gamecallback->changevolume_requested) - { - (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr, &client))->drop(); - g_gamecallback->changevolume_requested = false; - } + flags->camera_offset_changed = (camera_offset != old_camera_offset); - if(g_gamecallback->keyconfig_requested) - { - (new GUIKeyChangeMenu(guienv, guiroot, -1, - &g_menumgr))->drop(); - g_gamecallback->keyconfig_requested = false; - } + 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); - /* Process TextureSource's queue */ - tsrc->processQueue(); + if (clouds) + clouds->updateCameraOffset(camera_offset); + } + } +} - /* Process ItemDefManager's queue */ - itemdef->processQueue(gamedef); - /* - Process ShaderSource's queue - */ - shsrc->processQueue(); +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")); - /* - Random calculations - */ - hud.resizeHotbar(); - // Hilight boxes collected during the loop and displayed - std::vector hilightboxes; + // Update sound maker + soundmaker->step(dtime); - /* 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); - } + LocalPlayer *player = client->getEnv().getLocalPlayer(); - update_profiler_gui(guitext_profiler, font, text_height, - show_profiler, show_profiler_max); + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = map.getNodeNoEx(player->getStandingNodePos()); + soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep; +} - g_profiler->clear(); - } - /* - Direct handling of user input - */ +void MinetestApp::processPlayerInteraction(std::vector &highlight_boxes, + InteractParams *interactArgs, f32 dtime, bool show_hud, bool show_debug) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); - // 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(); - } + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); - // 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 + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } - // Increase timer for doubleclick of "jump" - if(g_settings->getBool("doubletap_jump") && jump_timer <= 0.2) - jump_timer += dtime; + const ItemDefinition &playeritem_def = + playeritem.getDefinition(itemdef_manager); - /* - 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"<getPosition(); + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + v3s16 camera_offset = camera->getOffset(); - PlayerInventoryFormSource* fs_src = new PlayerInventoryFormSource(&client); - TextDest* txt_dst = new TextDestPlayerInventory(&client); - create_formspec_menu(¤t_formspec, &client, gamedef, tsrc, device, fs_src, txt_dst, &client); + /* + Calculate what block is the crosshair pointing to + */ - InventoryLocation inventoryloc; - inventoryloc.setCurrentPlayer(); - current_formspec->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; - } + f32 d = playeritem_def.range; // max. distance + f32 d_hand = itemdef_manager->get("").range; - // Reset jump_timer - if(!input->isKeyDown(getKeySetting("keymap_jump")) && reset_jump_timer) - { - reset_jump_timer = false; - jump_timer = 0.0; - } + if (d < 0 && d_hand >= 0) + d = d_hand; + else if (d < 0) + d = 4.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; - } - } + core::line3d shootline; - // 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; - } - } + if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) { - // 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: " - <(camera_position, + camera_position + camera_direction * BS * (d + 1)); - // Viewing range selection - if(input->wasKeyDown(getKeySetting("keymap_rangeselect"))) - { - draw_control.range_all = !draw_control.range_all; - if(draw_control.range_all) - { - infostream<<"Enabled full viewing range"<getCameraMode() == CAMERA_MODE_THIRD_FRONT) + shootline = core::line3d(0, 0, 0, 0, 0, 0); + } - // Print debug stacks - if(input->wasKeyDown(getKeySetting("keymap_print_debug_stacks"))) - { - dstream<<"-----------------------------------------" - <getBool("touchtarget")) && (g_touchscreengui)) { + shootline = g_touchscreengui->getShootline(); + shootline.start += intToFloat(camera_offset, BS); + shootline.end += intToFloat(camera_offset, BS); + } - float turn_amount = 0; - if((device->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"<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 { -#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 ((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; + client->setHighlighted(pointed.node_undersurface, false); } - 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); + /* + 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; + } } - /* - 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); - } + if (!interactArgs->digging) { + client->interact(1, interactArgs->pointed_old); + client->setCrack(-1, v3s16(0, 0, 0)); + interactArgs->dig_time = 0.0; } + } - { - // 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: "<digging && interactArgs->ldown_for_dig && !input->getLeftState()) { + interactArgs->ldown_for_dig = false; + } - damage_flash += 100.0; - damage_flash += 8.0 * event.player_damage.amount; + interactArgs->left_punch = false; - player->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); + soundmaker->m_player_leftpunch_sound.name = ""; - 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); + if (input->getRightState()) + interactArgs->repeat_rightclick_timer += dtime; + else + interactArgs->repeat_rightclick_timer = 0; - chat_backend.addMessage(L"", L"You died."); + 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; + } - /* Handle visualization */ - damage_flash = 0; + interactArgs->pointed_old = pointed; - LocalPlayer* player = client.getEnv().getLocalPlayer(); - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; + if (interactArgs->left_punch || input->getLeftClicked()) + camera->setDigging(0); // left click animation - } - 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)); + input->resetLeftClicked(); + input->resetRightClicked(); - create_formspec_menu(¤t_formspec, &client, gamedef, - tsrc, device, fs_src, txt_dst, &client); + input->resetLeftReleased(); + input->resetRightReleased(); +} - 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); +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; - 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; - } + /* + Check information text of node + */ - 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; - } + ClientMap &map = client->getEnv().getClientMap(); + NodeMetadata *meta = map.getNodeMetadata(nodepos); - 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); - } + if (meta) { + infotext = narrow_to_wide(meta->getString("infotext")); + } else { + MapNode n = map.getNode(nodepos); - 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); - } - } + if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { + infotext = L"Unknown node: "; + infotext += narrow_to_wide(nodedef_manager->get(n).name); } + } - //TimeTaker //timer2("//timer2"); + if (interactArgs->nodig_delay_timer <= 0.0 && input->getLeftState() + && client->checkPrivilege("interact")) { + handleDigging(interactArgs, pointed, nodepos, playeritem_toolcap, dtime); + } - /* - 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); + 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; - /* - Update camera - */ + if (meta && meta->getString("formspec") != "" && !random_input + && !input->isKeyDown(getKeySetting("keymap_sneak"))) { + infostream << "Launching custom inventory view" << std::endl; - v3s16 old_camera_offset = camera.getOffset(); + InventoryLocation inventoryloc; + inventoryloc.setNodeMeta(nodepos); - 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; + NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( + &client->getEnv().getClientMap(), nodepos); + TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); - if(input->wasKeyDown(getKeySetting("keymap_camera_mode"))) { - camera.toggleCameraMode(); - GenericCAO* playercao = player->getCAO(); + create_formspec_menu(¤t_formspec, client, gamedef, + texture_src, device, fs_src, txt_dst, client); - assert( playercao != NULL ); - if (camera.getCameraMode() > CAMERA_MODE_FIRST) { - playercao->setVisible(true); - } - else { - playercao->setVisible(false); + 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 } - tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); - camera.update(player, dtime, busytime, tool_reload_ratio, - client.getEnv()); - camera.step(dtime); + } +} - 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); +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(!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); - } - } + if (infotext == L"" && show_debug) { + infotext = narrow_to_wide(interactArgs->selected_object->debugInfoText()); + } - // 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")); + if (input->getLeftState()) { + bool do_punch = false; + bool do_punch_damage = false; - /* - Update sound maker - */ - { - soundmaker.step(dtime); + 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; - ClientMap &map = client.getEnv().getClientMap(); - MapNode n = map.getNodeNoEx(player->getStandingNodePos()); - soundmaker.m_player_step_sound = nodedef->get(n).sound_footstep; + if (do_punch) { + infostream << "Left-clicked object" << std::endl; + interactArgs->left_punch = true; } - /* - Calculate what block is the crosshair pointing to - */ + if (do_punch_damage) { + // Report direct punch + v3f objpos = interactArgs->selected_object->getPosition(); + v3f dir = (objpos - player_position).normalize(); - //u32 t1 = device->getTimer()->getRealTime(); + bool disable_send = interactArgs->selected_object->directReportPunch( + dir, &playeritem, interactArgs->time_from_last_punch); + interactArgs->time_from_last_punch = 0; - 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)); + if (!disable_send) + client->interact(0, pointed); + } + } else if (input->getRightClicked()) { + infostream << "Right-clicked object" << std::endl; + client->interact(3, pointed); // place + } +} - // prevent player pointing anything in front-view - if (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT) - shootline = core::line3d(0,0,0,0,0,0); +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; + } -#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 + LocalPlayer *player = client->getEnv().getLocalPlayer(); + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = client->getEnv().getClientMap().getNode(nodepos); - 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); - } - } - } + // 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); - /* - 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; - } + // If can't dig, try hand + if (!params.diggable) { + const ItemDefinition &hand = itemdef_manager->get(""); + const ToolCapabilities *tp = hand.tool_capabilities; - bool left_punch = false; - soundmaker.m_player_leftpunch_sound.name = ""; + if (tp) + params = getDigParams(nodedef_manager->get(n).groups, tp); + } - if(input->getRightState()) - repeat_rightclick_timer += dtime; - else - repeat_rightclick_timer = 0; + 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(playeritem_def.usable && input->getLeftState()) - { - if(input->getLeftClicked()) - client.interact(4, pointed); + if (g_settings->getBool("enable_particles")) { + const ContentFeatures &features = + client->getNodeDefManager()->get(n); + addPunchingParticles(gamedef, smgr, player, + client->getEnv(), nodepos, features.tiles); } - 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); - } + 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; + } - float dig_time_complete = 0.0; + SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig; - 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 (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; + } + } - 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; - } + // 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); - 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; - } - } + if (g_settings->getBool("enable_particles")) { + const ContentFeatures &features = + client->getNodeDefManager()->get(wasnode); + addDiggingParticles + (gamedef, smgr, player, client->getEnv(), + nodepos, features.tiles); + } - // 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); - } + interactArgs->dig_time = 0; + interactArgs->digging = false; - 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); - } + interactArgs->nodig_delay_timer = + interactArgs->dig_time_complete / (float)crack_animation_length; - if(dig_time_complete < 100000.0) - dig_time += dtime; - else { - dig_time = 0; - client.setCrack(-1, nodepos); - } + // 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; - camera.setDigging(0); // left click animation - } + // We want a slight delay to very little + // time consuming nodes + const float mindelay = 0.15; - if((input->getRightClicked() || - repeat_rightclick_timer >= - g_settings->getFloat("repeat_rightclick_time")) && - client.checkPrivilege("interact")) - { - repeat_rightclick_timer = 0; - infostream<<"Ground right-clicked"<nodig_delay_timer < mindelay) + interactArgs->nodig_delay_timer = mindelay; - if(meta && meta->getString("formspec") != "" && !random_input - && !input->isKeyDown(getKeySetting("keymap_sneak"))) - { - infostream<<"Launching custom inventory view"<event()->put(e); + } - InventoryLocation inventoryloc; - inventoryloc.setNodeMeta(nodepos); + if (interactArgs->dig_time_complete < 100000.0) { + interactArgs->dig_time += dtime; + } else { + interactArgs->dig_time = 0; + client->setCrack(-1, nodepos); + } - NodeMetadataFormSource* fs_src = new NodeMetadataFormSource( - &client.getEnv().getClientMap(), nodepos); - TextDest* txt_dst = new TextDestNodeMetadata(nodepos, &client); + camera->setDigging(0); // left click animation +} - create_formspec_menu(¤t_formspec, &client, gamedef, - tsrc, device, fs_src, txt_dst, &client); - current_formspec->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(); - } +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(); - 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()); + /* + Fog range + */ - if(infotext == L"" && show_debug){ - infotext = narrow_to_wide(selected_object->debugInfoText()); - } + 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; + } - //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; - } + /* + 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; + } - pointed_old = pointed; + float time_of_day = 0; + float time_of_day_smooth = 0; - if(left_punch || input->getLeftClicked()) - { - camera.setDigging(0); // left click animation - } + time_of_day = client->getEnv().getTimeOfDayF(); - input->resetLeftClicked(); - input->resetRightClicked(); + const float maxsm = 0.05; - input->resetLeftReleased(); - input->resetRightReleased(); + 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; - /* - Calculate stuff for drawing - */ + const float todsm = 0.05; - /* - Fog range - */ + 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; - 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; - } + sky->update(time_of_day_smooth, time_brightness, direct_brightness, + sunlight_seen, camera->getCameraMode(), player->getYaw(), + player->getPitch()); - /* - 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; + /* + Update clouds + */ + if (clouds) { + v3f player_position = player->getPosition(); + 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; - } - - 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; - - sky->update(time_of_day_smooth, time_brightness, direct_brightness, - sunlight_seen,camera.getCameraMode(), player->getYaw(), - player->getPitch()); - - video::SColor bgcolor = sky->getBgColor(); - video::SColor skycolor = sky->getSkyColor(); - - /* - 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); - } + clouds->setVisible(false); } + } - /* - Update particles - */ + /* + Update particles + */ - allparticles_step(dtime); - allparticlespawners_step(dtime, client.getEnv()); + allparticles_step(dtime); + allparticlespawners_step(dtime, client->getEnv()); - /* - Fog - */ + /* + 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 - ); - } - - /* - Update gui stuff (0ms) - */ - - //TimeTaker guiupdatetimer("Gui updating"); - - 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;*/ - - u16 fps = (1.0/dtime_avg1); - - std::ostringstream os(std::ios_base::binary); - os<setText(narrow_to_wide(os.str()).c_str()); - guitext->setVisible(true); - } - else - { - guitext->setVisible(false); - } + ); + } - if (guitext->isVisible()) - { - core::rect rect( - 5, - 5, - screensize.X, - 5 + text_height - ); - guitext->setRelativePosition(rect); - } + /* + Get chat messages from client + */ - 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); - } + v2u32 screensize = driver->getScreenSize(); - { - guitext_info->setText(infotext.c_str()); - guitext_info->setVisible(show_hud && g_menumgr.menuCount() == 0); - } + updateChat(*client, dtime, flags.show_debug, screensize, + flags.show_chat, interactArgs->profiler_current_page, + *chat_backend, guitext_chat, font); - { - 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); - } - } + /* + Inventory + */ - /* - Get chat messages from client - */ - updateChat(client, dtime, show_debug, screensize, show_chat, - show_profiler, chat_backend, guitext_chat, font); + bool update_wielded_item_trigger = true; - /* - Inventory - */ + if (client->getPlayerItem() != interactArgs->new_playeritem) { + client->selectPlayerItem(interactArgs->new_playeritem); + } - if(client.getPlayerItem() != new_playeritem) - { - client.selectPlayerItem(new_playeritem); - } - if(client.getLocalInventoryUpdated()) - { - //infostream<<"Updating local inventory"<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 != NULL) && (client.getPlayerItem() < mlist->getSize())) - item = mlist->getItem(client.getPlayerItem()); - camera.wield(item, client.getPlayerItem()); - } + update_wielded_item_trigger = true; + } - /* - 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; - } + if (update_wielded_item_trigger) { + update_wielded_item_trigger = false; + // Update wielded tool + InventoryList *mlist = local_inventory->getList("main"); + ItemStack item; - /* - 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); - } - } + if (mlist && (client->getPlayerItem() < mlist->getSize())) + item = mlist->getItem(client->getPlayerItem()); - /* - Drawing begins - */ - TimeTaker tt_draw("mainloop: draw"); - { - TimeTaker timer("beginScene"); - driver->beginScene(true, true, skycolor); - beginscenetime = timer.stop(true); - } + 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; + } - draw_scene(driver, smgr, camera, client, player, hud, guienv, - hilightboxes, screensize, skycolor, show_hud); + updateGui(&interactArgs->statustext_time, *stats, dtime, flags, cam); - /* - Profiler graph - */ - if(show_profiler_graph) - { - graph.draw(10, screensize.Y - 10, driver, font); + /* + 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); } + } - /* - 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), - NULL); + /* + Drawing begins + */ - damage_flash -= 100.0*dtime; - } + video::SColor skycolor = sky->getSkyColor(); - /* - 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; - } + TimeTaker tt_draw("mainloop: draw"); + { + TimeTaker timer("beginScene"); + driver->beginScene(true, true, skycolor); + stats->beginscenetime = timer.stop(true); + } - /* - End scene - */ - { - TimeTaker timer("endScene"); - driver->endScene(); - endscenetime = timer.stop(true); - } + draw_scene(driver, smgr, *camera, *client, player, *hud, guienv, + highlight_boxes, screensize, skycolor, flags.show_hud); - drawtime = tt_draw.stop(true); - g_profiler->graphAdd("mainloop_draw", (float)drawtime/1000.0f); + /* + Profiler graph + */ + if (flags.show_profiler_graph) + graph->draw(10, screensize.Y - 10, driver, font); - /* - End of drawing - */ + /* + 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); - /* - 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); +} + + +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(); - client.Stop(); + if (flags.show_debug) { + static float drawtime_avg = 0; + drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05; - //force answer all texture and shader jobs (TODO return empty values) + u16 fps = 1.0 / stats.dtime_jitter.avg; - while(!client.isShutdown()) { - tsrc->processQueue(); - shsrc->processQueue(); - sleep_ms(100); + 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) - { - 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<isVisible()) { + core::rect rect( + 5, 5, + screensize.X, 5 + text_height + ); + guitext->setRelativePosition(rect); } - catch(ServerError &e) { - error_message = narrow_to_wide(e.what()); - errorstream << "ServerError: " << e.what() << std::endl; + + 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); } - catch(ModError &e) { - errorstream << "ModError: " << e.what() << std::endl; - error_message = narrow_to_wide(e.what()) + wgettext("\nCheck debug.txt for details."); + + 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); +} - if(!sound_is_dummy) - delete sound; +/**************************************************************************** + 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]; +} + - //has to be deleted first to stop all server threads - delete server; - delete tsrc; - delete shsrc; - delete nodedef; - delete itemdef; +/**************************************************************************** + Shutdown / cleanup + ****************************************************************************/ - //extended resource accounting +void MinetestApp::extendedResourceCleanup() +{ + // Extended resource accounting infostream << "Irrlicht resources after cleanup:" << std::endl; infostream << "\tRemaining meshes : " - << device->getSceneManager()->getMeshCache()->getMeshCount() << std::endl; + << 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); + << 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; + << std::endl; } + clearTextureNameCache(); infostream << "\tRemaining materials: " - << driver-> getMaterialRendererCount () - << " (note: irrlicht doesn't support removing renderers)"<< std::endl; + << 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 << wide_to_narrow(error_message) << std::endl; + } catch (ServerError &e) { + error_message = narrow_to_wide(e.what()); + errorstream << "ServerError: " << e.what() << std::endl; + } catch (ModError &e) { + errorstream << "ModError: " << e.what() << std::endl; + error_message = narrow_to_wide(e.what()) + wgettext("\nCheck debug.txt for details."); + } } 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, -- cgit v1.2.3