diff options
-rw-r--r-- | src/game.cpp | 5237 | ||||
-rw-r--r-- | src/game.h | 55 | ||||
-rw-r--r-- | src/main.cpp | 2 |
3 files changed, 2857 insertions, 2437 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 <iomanip> @@ -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 ("<<m_p.X<<"," - <<m_p.Y<<","<<m_p.Z<<"): "<<ntext<<std::endl; + infostream << "Submitting 'text' field of node at (" << m_p.X << "," + << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl; std::map<std::string, std::string> 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<std::string,std::string>::iterator iter = fields.begin(); + + for (std::map<std::string, std::string>::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, <<std::endl;*/ s16 a = d; - 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); + 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<aabb3f> boxes = n.getSelectionBoxes(nodedef); - - v3s16 np(x,y,z); - v3f npf = intToFloat(np, BS); - - for(std::vector<aabb3f>::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<aabb3f>::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<aabb3f> boxes = n.getSelectionBoxes(nodedef); + + v3s16 np(x, y, z); + v3f npf = intToFloat(np, BS); + + for (std::vector<aabb3f>::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<aabb3f>::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<s32> rect(6, 4+(text_height+5)*2, 12+w, - 8+(text_height+5)*2 + - font->getDimension(text.c_str()).Height); + + core::rect<s32> 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<std::string, Meta> m_meta; - for(std::list<Piece>::const_iterator k = m_log.begin(); - k != m_log.end(); k++) - { + + for (std::list<Piece>::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<std::string, Meta>::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<std::string, Meta>::iterator i = m_meta.begin(); - i != m_meta.end(); i++){ + + for (std::map<std::string, Meta>::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<std::string, Meta>::const_iterator i = m_meta.begin(); - i != m_meta.end(); i++){ + + for (std::map<std::string, Meta>::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<s32>(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<s32>(textx, y - texth, - textx2, y), + textx2, y), meta.color); font->draw(narrow_to_wide(id).c_str(), - core::rect<s32>(textx, y - graphh/2 - texth/2, - textx2, y - graphh/2 + texth/2), + core::rect<s32>(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<Piece>::const_iterator j = m_log.begin(); - j != m_log.end(); j++) - { + + for (std::list<Piece>::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<std::string> m_fetched; public: - void fetchSounds(const std::string &name, std::set<std::string> &dst_paths, std::set<std::string> &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 " - <<playeritem_def.name<<" is " - <<prediction<<std::endl; + if (prediction != "" && !nodedef->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 " - <<playeritem_def.name<<" (places " - <<prediction - <<") - Name not known"<<std::endl; + + if (!found) { + errorstream << "Node placement prediction failed for " + << playeritem_def.name << " (places " + << prediction + << ") - Name not known" << std::endl; return false; } + // Predict param2 for facedir and wallmounted nodes u8 param2 = 0; - if(nodedef->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 " - <<playeritem_def.name<<" (places " - <<prediction - <<") - Position not loaded"<<std::endl; + (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 " + << 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<s32> 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<s32>(10, chat_y, width, - chat_y + guitext_chat->getTextHeight()); + chat_y + guitext_chat->getTextHeight()); guitext_chat->setRelativePosition(rect); // Don't show chat if disabled or empty or profiler is enabled guitext_chat->setVisible( - show_chat && recent_chat_count != 0 && !show_profiler); + show_chat && recent_chat_count != 0 && !show_profiler); } -/******************************************************************************/ -void the_game(bool &kill, bool random_input, InputHandler *input, - IrrlichtDevice *device, gui::IGUIFont* font, std::string map_dir, - std::string playername, std::string password, - std::string address /* If "", local server is used */, - u16 port, std::wstring &error_message, ChatBackend &chat_backend, - const SubgameSpec &gamespec /* Used for local game */, - bool simple_singleplayer_mode) + + +/**************************************************************************** + THE GAME + ****************************************************************************/ + +const float object_hit_delay = 0.2; + +struct FpsControl { + u32 last_time, busy_time, sleep_time; +}; + + +/* The reason the following structs are not anonymous structs within the + * class is that they are not used by the majority of member functions and + * many functions that do require objects of thse types do not modify them + * (so they can be passed as a const qualified parameter) + */ + +struct CameraOrientation { + f32 camera_yaw; // "right/left" + f32 camera_pitch; // "up/down" +}; + +//TODO: Needs a better name because fog_range etc are included +struct InteractParams { + u16 dig_index; + u16 new_playeritem; + PointedThing pointed_old; + bool digging; + bool ldown_for_dig; + bool left_punch; + float nodig_delay_timer; + float dig_time; + float dig_time_complete; + float repeat_rightclick_timer; + float object_hit_delay_timer; + float time_from_last_punch; + ClientActiveObject *selected_object; + + float jump_timer; + float damage_flash; + float update_draw_list_timer; + float statustext_time; + + f32 fog_range; + + v3f update_draw_list_last_cam_dir; + + u32 profiler_current_page; + u32 profiler_max_page; // Number of pages +}; + +struct Jitter { + f32 max, min, avg, counter, max_sample, min_sample, max_fraction; +}; + +struct RunStats { + u32 drawtime; + u32 beginscenetime; + u32 endscenetime; + + Jitter dtime_jitter, busy_time_jitter; +}; + +/* Flags that can, or may, change during main game loop + */ +struct VolatileRunFlags { + bool invert_mouse; + bool show_chat; + bool show_hud; + bool force_fog_off; + bool show_debug; + bool show_profiler_graph; + bool disable_camera_update; + bool first_loop_after_window_activation; + bool camera_offset_changed; +}; + + +/* This is not intended to be a public class. If a public class becomes + * desirable then it may be better to create another 'wrapper' class that + * hides most of the stuff in this class (nothing in this class is required + * by any other file) but exposes the public methods/data only. + */ +class MinetestApp { - GUIFormSpecMenu* current_formspec = 0; - video::IVideoDriver* driver = device->getVideoDriver(); - scene::ISceneManager* smgr = device->getSceneManager(); +public: + MinetestApp(); + ~MinetestApp(); + + 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<aabb3f> &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<aabb3f> &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); - // Calculate text height using the font - u32 text_height = font->getDimension(L"Random test string").Height; +private: + InputHandler *input; - /* - Draw "Loading" screen + Client *client; + Server *server; + + gui::IGUIFont *font; + + IWritableTextureSource *texture_src; + IWritableShaderSource *shader_src; + + // When created, these will be filled with data received from the server + IWritableItemDefManager *itemdef_manager; + IWritableNodeDefManager *nodedef_manager; + + GameOnDemandSoundFetcher soundfetcher; // useful when testing + ISoundManager *sound; + bool sound_is_dummy; + SoundMaker *soundmaker; + + ChatBackend *chat_backend; + + GUIFormSpecMenu *current_formspec; + + EventManager *eventmgr; + QuicktuneShortcutter *quicktune; + + GUIChatConsole *gui_chat_console; // Free using ->Drop() + MapDrawControl *draw_control; + Camera *camera; + Clouds *clouds; // Free using ->Drop() + Sky *sky; // Free using ->Drop() + Inventory *local_inventory; + Hud *hud; + + /* 'cache' + This class does take ownership/responsibily for cleaning up etc of any of + these items (e.g. device) */ + IrrlichtDevice *device; + video::IVideoDriver *driver; + scene::ISceneManager *smgr; + u32 text_height; + bool *kill; + std::wstring *error_message; + IGameDef *gamedef; // Convenience (same as *client) + scene::ISceneNode *skybox; + + 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 - { - wchar_t* text = wgettext("Loading..."); - draw_load_screen(text, device, guienv, font, 0, 0); - delete[] text; - } + std::wstring infotext; + std::wstring statustext; +}; - // Create texture source - IWritableTextureSource *tsrc = createTextureSource(device); +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) +{ - // 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(); - // Sound fetcher (useful when testing) - GameOnDemandSoundFetcher soundfetcher; - // 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"<<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; - } +/**************************************************************************** + MinetestApp Public + ****************************************************************************/ - Server *server = NULL; +MinetestApp::~MinetestApp() +{ + delete client; + delete soundmaker; + if (!sound_is_dummy) + delete sound; - try{ - // Event manager - EventManager eventmgr; + delete server; // deleted first to stop all server threads - // Sound maker - SoundMaker soundmaker(sound, nodedef); - soundmaker.registerReceiver(&eventmgr); + delete hud; + delete local_inventory; + delete camera; + delete quicktune; + delete eventmgr; + delete texture_src; + delete shader_src; + delete nodedef_manager; + delete itemdef_manager; + delete draw_control; - // Create UI for modifying quicktune values - QuicktuneShortcutter quicktune; + extendedResourceCleanup(); +} - /* - Create server. - */ +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(address == ""){ - wchar_t* text = wgettext("Creating server...."); - draw_load_screen(text, device, guienv, font, 0, 25); - delete[] text; - infostream<<"Creating server"<<std::endl; - std::string bind_str = g_settings->get("bind_address"); - Address bind_addr(0,0,0,0, port); +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 (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; - } + interactArgs.time_from_last_punch = 10.0; + interactArgs.profiler_max_page = 3; - 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; - // Break out of client scope - return; - } + flags.show_chat = true; + flags.show_hud = true; - server = new Server(map_dir, gamespec, - simple_singleplayer_mode, - bind_addr.isIPv6()); + /* FIXME: This should be updated every iteration, or not stored locally + now that key settings can be changed in-game */ + flags.invert_mouse = g_settings->getBool("invert_mouse"); - server->start(bind_addr); - } + /* Clear the profiler */ + Profiler::GraphValues dummyvalues; + g_profiler->graphGet(dummyvalues); - do{ // Client scope (breakable do-while(0)) + draw_times.last_time = device->getTimer()->getTime(); - /* - Create client - */ + shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter( + sky, + &flags.force_fog_off, + &interactArgs.fog_range, + client)); - { - wchar_t* text = wgettext("Creating client..."); - draw_load_screen(text, device, guienv, font, 0, 50); - delete[] text; + while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) { + + /* Must be called immediately after a device->run() call because it + * uses device->getTimer()->getTime() + */ + limitFps(&draw_times, &dtime); + + updateStats(&stats, draw_times, dtime); + updateInteractTimers(&interactArgs, dtime); + + if (!checkConnection()) + break; + if (!handleCallbacks()) + break; + + processQueues(); + + std::vector<aabb3f> 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); } - infostream<<"Creating client"<<std::endl; +} - MapDrawControl draw_control; - { - wchar_t* text = wgettext("Resolving address..."); - draw_load_screen(text, device, guienv, font, 0, 75); - delete[] text; +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()); } - Address connect_address(0,0,0,0, port); - try { - connect_address.Resolve(address.c_str()); - if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY - //connect_address.Resolve("localhost"); - if (connect_address.isIPv6()) { - IPv6AddressBytes addr_bytes; - addr_bytes.bytes[15] = 1; - connect_address.setAddress(&addr_bytes); - } else { - connect_address.setAddress(127,0,0,1); - } - } + + if (current_formspec) { + current_formspec->drop(); + current_formspec = NULL; } - catch(ResolveError &e) { - error_message = L"Couldn't resolve address: " + narrow_to_wide(e.what()); - errorstream<<wide_to_narrow(error_message)<<std::endl; - // Break out of client scope - break; + + 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); + } } - 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; - // Break out of client scope - break; +} + + + +/**************************************************************************** + 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; } - /* - Create client - */ - Client client(device, playername.c_str(), password, draw_control, - tsrc, shsrc, itemdef, nodedef, sound, &eventmgr, - connect_address.isIPv6()); + return true; +} - // Client acts as our GameDef - IGameDef *gamedef = &client; +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 - /* - Attempt to connect to the server - */ + if (!sound) { + infostream << "Using dummy audio." << std::endl; + sound = &dummySoundManager; + sound_is_dummy = true; + } - infostream<<"Connecting to server at "; - connect_address.print(&infostream); - infostream<<std::endl; - client.connect(connect_address); + soundmaker = new SoundMaker(sound, nodedef_manager); + if (!soundmaker) + return false; - /* - Wait for server to accept connection - */ - bool could_connect = false; - bool connect_aborted = false; - try{ - float time_counter = 0.0; - input->clear(); - float fps_max = g_settings->getFloat("fps_max"); - bool cloud_menu_background = g_settings->getBool("menu_clouds"); - u32 lasttime = device->getTimer()->getTime(); - while(device->run()) - { - f32 dtime = 0.033; // in seconds - if (cloud_menu_background) { - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; - } - // Update client and server - client.step(dtime); - if(server != NULL) - server->step(dtime); + soundmaker->registerReceiver(eventmgr); - // 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<<wide_to_narrow(error_message)<<std::endl; - break; - } - if(input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { - connect_aborted = true; - infostream<<"Connect aborted [Escape]"<<std::endl; - break; - } + return true; +} - // Display status - { - wchar_t* text = wgettext("Connecting to server..."); - draw_load_screen(text, device, guienv, font, dtime, 100); - delete[] text; - } +bool MinetestApp::createSingleplayerServer(const std::string map_dir, + const SubgameSpec &gamespec, u16 port, std::string *address) +{ + showOverlayMessage("Creating server...", 0, 25); - // 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; - } + 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); } - catch(con::PeerNotFoundException &e) - {} - /* - Handle failure to connect - */ - if(!could_connect) { - if(error_message == L"" && !connect_aborted) { - error_message = L"Connection failed"; - errorstream<<wide_to_narrow(error_message)<<std::endl; - } - // Break out of client scope - break; + 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; } - /* - Wait until content has been received - */ - bool got_content = false; - bool content_aborted = false; - { - float time_counter = 0.0; - input->clear(); - float fps_max = g_settings->getFloat("fps_max"); - bool cloud_menu_background = g_settings->getBool("menu_clouds"); - u32 lasttime = device->getTimer()->getTime(); - while (device->run()) { - f32 dtime = 0.033; // in seconds - if (cloud_menu_background) { - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; - } - // Update client and server - client.step(dtime); - if (server != NULL) - server->step(dtime); + 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; + } - // 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<<wide_to_narrow(error_message)<<std::endl; - break; - } - if (client.getState() < LC_Init) { - error_message = L"Client disconnected"; - errorstream<<wide_to_narrow(error_message)<<std::endl; - break; - } - if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { - content_aborted = true; - infostream<<"Connect aborted [Escape]"<<std::endl; - break; - } + server = new Server(map_dir, gamespec, simple_singleplayer_mode, + bind_addr.isIPv6()); - // 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 - { + server->start(bind_addr); - std::stringstream message; - message.precision(3); - message << gettext("Media..."); + return true; +} - if ( ( USE_CURL == 0) || - (!g_settings->getBool("enable_remote_media_server"))) { - float cur = client.getCurRate(); - std::string cur_unit = gettext(" KB/s"); +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); - 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); - } + draw_control = new MapDrawControl; + if (!draw_control) + return false; - // 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; + 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(!got_content){ - if(error_message == L"" && !content_aborted){ - error_message = L"Something failed"; - errorstream<<wide_to_narrow(error_message)<<std::endl; + 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; } - // Break out of client scope - break; + return false; } - /* - After all content has been received: - Update cached textures, meshes and materials - */ - client.afterContentReceived(device,font); - - /* - Create the camera node - */ - Camera camera(smgr, draw_control, gamedef); - if (!camera.successfullyCreated(error_message)) - return; + // Update cached textures, meshes and materials + client->afterContentReceived(device, font); - f32 camera_yaw = 0; // "right/left" - f32 camera_pitch = 0; // "up/down" + /* Camera + */ + camera = new Camera(smgr, *draw_control, gamedef); + if (!camera || !camera->successfullyCreated(*error_message)) + return false; - /* - Clouds - */ - - Clouds *clouds = NULL; - if(g_settings->getBool("enable_clouds")) - { + /* 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 thingy - */ - - Sky *sky = NULL; + /* Skybox + */ sky = new Sky(smgr->getRootSceneNode(), smgr, -1); + skybox = NULL; // This is used/set later on in the main run loop - scene::ISceneNode* skybox = NULL; + local_inventory = new Inventory(itemdef_manager); - /* - A copy of the local inventory - */ - Inventory local_inventory(itemdef); + 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; + } - /* - Find out size of crack animation - */ - int crack_animation_length = 5; - { - video::ITexture *t = tsrc->getTexture("crack_anylength.png"); + /* 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; } - /* - Add some gui stuff - */ + 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 - gui::IGUIStaticText *guitext = guienv->addStaticText( + guitext = guienv->addStaticText( L"Minetest", core::rect<s32>(0, 0, 0, 0), false, false, guiroot); + // Second line of debug text - gui::IGUIStaticText *guitext2 = guienv->addStaticText( + guitext2 = guienv->addStaticText( L"", core::rect<s32>(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<s32>(0,0,400,text_height*5+5) + v2s32(100,200), + core::rect<s32>(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"<Status>", - core::rect<s32>(0,0,0,0), + core::rect<s32>(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<s32>(0,0,0,0), + core::rect<s32>(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"<Profiler>", - core::rect<s32>(0,0,0,0), + core::rect<s32>(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; + } - for(;;) - { - if(device->run() == false || kill == true || - g_gamecallback->shutdown_requested) + 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; + } - v2u32 screensize = driver->getScreenSize(); + // Error conditions + if (client->accessDenied()) { + *error_message = L"Access denied. Reason: " + + client->accessDeniedReason(); + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; + } - // 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; + if (client->getState() < LC_Init) { + *error_message = L"Client disconnected"; + errorstream << wide_to_narrow(*error_message) << std::endl; + return false; } - g_profiler->graphAdd("mainloop_other", busytime - (float)drawtime/1000.0f); + if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) { + *aborted = true; + infostream << "Connect aborted [Escape]" << std::endl; + return false; + } - // Necessary for device->getTimer()->getTime() - device->run(); + // Display status + int progress = 0; - /* - FPS limiter - */ + 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"); + } - { - 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); + 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); } + } - // Necessary for device->getTimer()->getTime() - device->run(); + return true; +} - /* - Time difference calculation - */ - f32 dtime; // in seconds - u32 time = device->getTimer()->getTime(); - if(time > lasttime) - dtime = (time - lasttime) / 1000.0; - else - dtime = 0; - lasttime = time; - g_profiler->graphAdd("mainloop_dtime", dtime); +/**************************************************************************** + Run + ****************************************************************************/ - /* Run timers */ +inline void MinetestApp::updateInteractTimers(InteractParams *args, f32 dtime) +{ + if (args->nodig_delay_timer >= 0) + args->nodig_delay_timer -= dtime; - 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; + if (args->object_hit_delay_timer >= 0) + args->object_hit_delay_timer -= dtime; - g_profiler->add("Elapsed time", dtime); - g_profiler->avg("FPS", 1./dtime); + args->time_from_last_punch += dtime; +} - /* - Time average and jitter calculation - */ - static f32 dtime_avg1 = 0.0; - dtime_avg1 = dtime_avg1 * 0.96 + dtime * 0.04; - f32 dtime_jitter1 = dtime - dtime_avg1; - - static f32 dtime_jitter1_max_sample = 0.0; - static f32 dtime_jitter1_max_fraction = 0.0; - { - static f32 jitter1_max = 0.0; - static f32 counter = 0.0; - if(dtime_jitter1 > jitter1_max) - jitter1_max = dtime_jitter1; - counter += dtime; - if(counter > 0.0) - { - counter -= 3.0; - dtime_jitter1_max_sample = jitter1_max; - dtime_jitter1_max_fraction - = dtime_jitter1_max_sample / (dtime_avg1+0.001); - jitter1_max = 0.0; - } - } +/* 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; + } - /* - Busytime average and jitter calculation - */ + return true; +} - static f32 busytime_avg1 = 0.0; - busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02; - f32 busytime_jitter1 = busytime - busytime_avg1; - - static f32 busytime_jitter1_max_sample = 0.0; - static f32 busytime_jitter1_min_sample = 0.0; - { - static f32 jitter1_max = 0.0; - static f32 jitter1_min = 0.0; - static f32 counter = 0.0; - if(busytime_jitter1 > jitter1_max) - jitter1_max = busytime_jitter1; - if(busytime_jitter1 < jitter1_min) - jitter1_min = busytime_jitter1; - counter += dtime; - if(counter > 0.0){ - counter -= 3.0; - busytime_jitter1_max_sample = jitter1_max; - busytime_jitter1_min_sample = jitter1_min; - jitter1_max = 0.0; - jitter1_min = 0.0; - } - } - /* - Handle miscellaneous stuff - */ +/* 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(client.accessDenied()) - { - error_message = L"Access denied. Reason: " - +client.accessDeniedReason(); - errorstream<<wide_to_narrow(error_message)<<std::endl; - break; - } + if (g_gamecallback->changepassword_requested) { + (new GUIPasswordChange(guienv, guiroot, -1, + &g_menumgr, client))->drop(); + g_gamecallback->changepassword_requested = false; + } - if(g_gamecallback->disconnect_requested) - { - g_gamecallback->disconnect_requested = false; - break; - } + if (g_gamecallback->changevolume_requested) { + (new GUIVolumeChange(guienv, guiroot, -1, + &g_menumgr, client))->drop(); + g_gamecallback->changevolume_requested = false; + } - if(g_gamecallback->changepassword_requested) - { - (new GUIPasswordChange(guienv, guiroot, -1, - &g_menumgr, &client))->drop(); - g_gamecallback->changepassword_requested = false; - } + if (g_gamecallback->keyconfig_requested) { + (new GUIKeyChangeMenu(guienv, guiroot, -1, + &g_menumgr))->drop(); + g_gamecallback->keyconfig_requested = false; + } - if(g_gamecallback->changevolume_requested) - { - (new GUIVolumeChange(guienv, guiroot, -1, - &g_menumgr, &client))->drop(); - g_gamecallback->changevolume_requested = false; - } + return true; +} - if(g_gamecallback->keyconfig_requested) - { - (new GUIKeyChangeMenu(guienv, guiroot, -1, - &g_menumgr))->drop(); - g_gamecallback->keyconfig_requested = false; - } +void MinetestApp::processQueues() +{ + texture_src->processQueue(); + itemdef_manager->processQueue(gamedef); + shader_src->processQueue(); +} - /* Process TextureSource's queue */ - tsrc->processQueue(); - /* Process ItemDefManager's queue */ - itemdef->processQueue(gamedef); +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); - /* - Process ShaderSource's queue - */ - shsrc->processQueue(); + if (draw_times.sleep_time != 0) + g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f); + g_profiler->graphAdd("mainloop_dtime", dtime); - /* - Random calculations - */ - hud.resizeHotbar(); + g_profiler->add("Elapsed time", dtime); + g_profiler->avg("FPS", 1. / dtime); +} - // Hilight boxes collected during the loop and displayed - std::vector<aabb3f> hilightboxes; - /* reset infotext */ - infotext = L""; - /* - Profiler - */ - float profiler_print_interval = - g_settings->getFloat("profiler_print_interval"); - bool print_to_log = true; - if(profiler_print_interval == 0){ - print_to_log = false; - profiler_print_interval = 5; - } - if(m_profiler_interval.step(dtime, profiler_print_interval)) - { - if(print_to_log){ - infostream<<"Profiler:"<<std::endl; - g_profiler->print(infostream); - } +void MinetestApp::updateStats(RunStats *stats, const FpsControl &draw_times, + f32 dtime) +{ - update_profiler_gui(guitext_profiler, font, text_height, - show_profiler, show_profiler_max); + f32 jitter; + Jitter *jp; - g_profiler->clear(); - } + /* Time average and jitter calculation + */ + jp = &stats->dtime_jitter; + jp->avg = jp->avg * 0.96 + dtime * 0.04; - /* - Direct handling of user input - */ + jitter = dtime - jp->avg; - // 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(); - } + 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); - // Input handler step() (used by the random input generator) - input->step(dtime); #ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui) { - g_touchscreengui->step(dtime); - } + + if (g_touchscreengui) { + g_touchscreengui->step(dtime); + } + #endif #ifdef __ANDROID__ - if (current_formspec != 0) - current_formspec->getAndroidUIInput(); + + if (current_formspec != 0) + current_formspec->getAndroidUIInput(); + #endif - // Increase timer for doubleclick of "jump" - if(g_settings->getBool("doubletap_jump") && jump_timer <= 0.2) - jump_timer += dtime; + // 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; - /* - 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"<<std::endl; + processKeyboardInput( + flags, + &interact_args->statustext_time, + &interact_args->jump_timer, + &interact_args->profiler_current_page, + interact_args->profiler_max_page); - PlayerInventoryFormSource* fs_src = new PlayerInventoryFormSource(&client); - TextDest* txt_dst = new TextDestPlayerInventory(&client); + processItemSelection(&interact_args->new_playeritem); +} - create_formspec_menu(¤t_formspec, &client, gamedef, tsrc, device, fs_src, txt_dst, &client); - 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 "<<show_profiler - <<" of "<<show_profiler_max<<")"; - statustext = sstr.str(); - statustext_time = 0; - } - else - { - statustext = L"Profiler hidden"; - statustext_time = 0; - } - } - else if(input->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; - } +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); + } - // Reset jump_timer - if(!input->isKeyDown(getKeySetting("keymap_jump")) && reset_jump_timer) - { - reset_jump_timer = false; - jump_timer = 0.0; - } + // Handle QuicktuneShortcutter + if (input->wasKeyDown(getKeySetting("keymap_quicktune_next"))) + quicktune->next(); + 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; + } - // 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; - } - } + // 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(); + } +} - // Item selection with mouse wheel - u16 new_playeritem = client.getPlayerItem(); - { - s32 wheel = input->getMouseWheel(); - u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE-1, - player->hud_hotbar_itemcount-1); - - if(wheel < 0) - { - if(new_playeritem < max_item) - new_playeritem++; - else - new_playeritem = 0; - } - else if(wheel > 0) - { - if(new_playeritem > 0) - new_playeritem--; - else - new_playeritem = max_item; - } - } - // Item selection - for(u16 i=0; i<10; i++) - { - const KeyPress *kp = NumberKey + (i + 1) % 10; - if(input->wasKeyDown(*kp)) - { - if(i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) - { - new_playeritem = i; - - infostream<<"Selected item: " - <<new_playeritem<<std::endl; - } - } - } +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, + }; - // 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"<<std::endl; - statustext = L"Enabled full viewing range"; - statustext_time = 0; - } - else - { - infostream<<"Disabled full viewing range"<<std::endl; - statustext = L"Disabled full viewing range"; - statustext_time = 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; } + } +} - // 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(); - } - /* - Mouse and camera control - NOTE: Do this before client.setPlayerControl() to not cause a camera lag of one frame - */ +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); +} - 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); - } + +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 - if(first_loop_after_window_activation){ - //infostream<<"window active, first loop"<<std::endl; - first_loop_after_window_activation = false; - } else { -#ifdef HAVE_TOUCHSCREENGUI - if (g_touchscreengui) { - camera_yaw = g_touchscreengui->getYaw(); - camera_pitch = g_touchscreengui->getPitch(); - } else { + //infostream<<"window inactive"<<std::endl; + flags->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 - 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 "<<dx<<","<<dy<<std::endl; - - /*const float keyspeed = 500; - if(input->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; + + if (flags->first_loop_after_window_activation) { + //infostream<<"window active, first loop"<<std::endl; + flags->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 - if(camera_pitch < -89.5) camera_pitch = -89.5; - if(camera_pitch > 89.5) camera_pitch = 89.5; + 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; } - 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"<<std::endl; - first_loop_after_window_activation = true; - } - recent_turn_speed = recent_turn_speed * 0.9 + turn_amount * 0.1; - //std::cerr<<"recent_turn_speed = "<<recent_turn_speed<<std::endl; + //infostream<<"window active, pos difference "<<dx<<","<<dy<<std::endl; - /* - Player speed control - */ - { - /*bool a_up, - bool a_down, - bool a_left, - bool a_right, - bool a_jump, - bool a_superspeed, - bool a_sneak, - bool a_LMB, - bool a_RMB, - float a_pitch, - float a_yaw*/ - PlayerControl control( - input->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(), - 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); - } + float d = g_settings->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? - /* - 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); +#ifdef HAVE_TOUCHSCREENGUI } - } +#endif - { - // 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: "<<damage<<std::endl; + if (cam->camera_pitch < -89.5) + cam->camera_pitch = -89.5; + else if (cam->camera_pitch > 89.5) + cam->camera_pitch = 89.5; + } - damage_flash += 100.0; - damage_flash += 8.0 * event.player_damage.amount; + input->setMousePos(driver->getScreenSize().Width / 2, + driver->getScreenSize().Height / 2); - 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); + // Deprecated? Not used anywhere else + // recent_turn_speed = recent_turn_speed * 0.9 + turn_amount * 0.1; + // std::cerr<<"recent_turn_speed = "<<recent_turn_speed<<std::endl; +} - MtEvent *e = new SimpleTriggerEvent("PlayerDamage"); - gamedef->event()->put(e); - } - else if(event.type == CE_PLAYER_FORCE_MOVE) { - camera_yaw = event.player_force_move.yaw; - camera_pitch = event.player_force_move.pitch; - } - else if(event.type == CE_DEATHSCREEN) { - show_deathscreen(¤t_formspec, &client, gamedef, tsrc, - device, &client); - chat_backend.addMessage(L"", L"You died."); +void MinetestApp::updatePlayerControl(const CameraOrientation &cam) +{ + PlayerControl control( + input->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 + ); - /* Handle visualization */ - damage_flash = 0; +} - LocalPlayer* player = client.getEnv().getLocalPlayer(); - player->hurt_tilt_timer = 0; - player->hurt_tilt_strength = 0; - } - else if (event.type == CE_SHOW_FORMSPEC) { - FormspecFormSource* fs_src = - new FormspecFormSource(*(event.show_formspec.formspec)); - TextDestPlayerInventory* txt_dst = - new TextDestPlayerInventory(&client,*(event.show_formspec.formname)); +inline void MinetestApp::step(f32 *dtime) +{ + bool can_be_and_is_paused = + (simple_singleplayer_mode && g_menumgr.pausesGame()); - create_formspec_menu(¤t_formspec, &client, gamedef, - tsrc, device, fs_src, txt_dst, &client); + 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); + } - 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; - } + //TimeTaker timer("client.step(dtime)"); + client->step(*dtime); + } +} - 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; - } +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: "<<damage<<std::endl; + + *damage_flash += 100.0; + *damage_flash += 8.0 * event.player_damage.amount; + + 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); + + 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; - 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; - } + case HUD_STAT_NAME: + e->name = *event.hudchange.sdata; + break; - delete event.hudchange.v3fdata; - delete event.hudchange.v2fdata; - delete event.hudchange.sdata; - delete event.hudchange.v2s32data; - } - else if (event.type == CE_SET_SKY) { - sky->setVisible(false); - if(skybox){ - skybox->remove(); - skybox = NULL; - } - // Handle according to type - if(*event.set_sky.type == "regular") { - sky->setVisible(true); - } - else if(*event.set_sky.type == "skybox" && - event.set_sky.params->size() == 6) { - sky->setFallbackBgColor(*event.set_sky.bgcolor); - skybox = smgr->addSkyBoxSceneNode( - tsrc->getTexture((*event.set_sky.params)[0]), - tsrc->getTexture((*event.set_sky.params)[1]), - tsrc->getTexture((*event.set_sky.params)[2]), - tsrc->getTexture((*event.set_sky.params)[3]), - tsrc->getTexture((*event.set_sky.params)[4]), - tsrc->getTexture((*event.set_sky.params)[5])); - } - // Handle everything else as plain color - else { - if(*event.set_sky.type != "plain") - infostream<<"Unknown sky type: " - <<(*event.set_sky.type)<<std::endl; - sky->setFallbackBgColor(*event.set_sky.bgcolor); - } + case HUD_STAT_SCALE: + e->scale = *event.hudchange.v2fdata; + break; - 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); - } - } - } + case HUD_STAT_TEXT: + e->text = *event.hudchange.sdata; + break; - //TimeTaker //timer2("//timer2"); + case HUD_STAT_NUMBER: + e->number = event.hudchange.data; + break; - /* - 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); + case HUD_STAT_ITEM: + e->item = event.hudchange.data; + break; - /* - Update camera - */ + case HUD_STAT_DIR: + e->dir = event.hudchange.data; + break; - v3s16 old_camera_offset = camera.getOffset(); + case HUD_STAT_ALIGN: + e->align = *event.hudchange.v2fdata; + break; - 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; + case HUD_STAT_OFFSET: + e->offset = *event.hudchange.v2fdata; + break; - if(input->wasKeyDown(getKeySetting("keymap_camera_mode"))) { - camera.toggleCameraMode(); - GenericCAO* playercao = player->getCAO(); + case HUD_STAT_WORLD_POS: + e->world_pos = *event.hudchange.v3fdata; + break; - assert( playercao != NULL ); - if (camera.getCameraMode() > CAMERA_MODE_FIRST) { - playercao->setVisible(true); + case HUD_STAT_SIZE: + e->size = *event.hudchange.v2s32data; + break; } + + delete event.hudchange.v3fdata; + delete event.hudchange.v2fdata; + delete event.hudchange.sdata; + delete event.hudchange.v2s32data; + } else if (event.type == CE_SET_SKY) { + sky->setVisible(false); + + if (skybox) { + skybox->remove(); + skybox = NULL; + } + + // Handle according to type + if (*event.set_sky.type == "regular") { + sky->setVisible(true); + } else if (*event.set_sky.type == "skybox" && + event.set_sky.params->size() == 6) { + sky->setFallbackBgColor(*event.set_sky.bgcolor); + skybox = smgr->addSkyBoxSceneNode( + texture_src->getTexture((*event.set_sky.params)[0]), + texture_src->getTexture((*event.set_sky.params)[1]), + texture_src->getTexture((*event.set_sky.params)[2]), + texture_src->getTexture((*event.set_sky.params)[3]), + texture_src->getTexture((*event.set_sky.params)[4]), + texture_src->getTexture((*event.set_sky.params)[5])); + } + // Handle everything else as plain color else { - playercao->setVisible(false); + if (*event.set_sky.type != "plain") + infostream << "Unknown sky type: " + << (*event.set_sky.type) << std::endl; + + sky->setFallbackBgColor(*event.set_sky.bgcolor); } + + delete event.set_sky.bgcolor; + delete event.set_sky.type; + delete event.set_sky.params; + } else if (event.type == CE_OVERRIDE_DAY_NIGHT_RATIO) { + bool enable = event.override_day_night_ratio.do_override; + u32 value = event.override_day_night_ratio.ratio_f * 1000; + client->getEnv().setDayNightRatioOverride(enable, value); } - 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::updateCamera(VolatileRunFlags *flags, u32 busy_time, + f32 dtime, float time_from_last_punch) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* + 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(!disable_camera_update){ - client.getEnv().getClientMap().updateCamera(camera_position, + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } + + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); + + v3s16 old_camera_offset = camera->getOffset(); + + if (input->wasKeyDown(getKeySetting("keymap_camera_mode"))) { + camera->toggleCameraMode(); + GenericCAO *playercao = player->getCAO(); + + assert(playercao != NULL); + + playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + } + + float full_punch_interval = playeritem_toolcap.full_punch_interval; + float tool_reload_ratio = time_from_last_punch / full_punch_interval; + + tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0); + camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio, + client->getEnv()); + camera->step(dtime); + + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + f32 camera_fov = camera->getFovMax(); + v3s16 camera_offset = camera->getOffset(); + + flags->camera_offset_changed = (camera_offset != old_camera_offset); + + if (!flags->disable_camera_update) { + client->getEnv().getClientMap().updateCamera(camera_position, camera_direction, camera_fov, camera_offset); - if (camera_offset_changed){ - client.updateCameraOffset(camera_offset); - client.getEnv().updateCameraOffset(camera_offset); - if (clouds) - clouds->updateCameraOffset(camera_offset); - } + + if (flags->camera_offset_changed) { + client->updateCameraOffset(camera_offset); + client->getEnv().updateCameraOffset(camera_offset); + + if (clouds) + clouds->updateCameraOffset(camera_offset); } + } +} - // Update sound listener - sound->updateListener(camera.getCameraNode()->getPosition()+intToFloat(camera_offset, BS), - v3f(0,0,0), // velocity - camera.getDirection(), - camera.getCameraNode()->getUpVector()); - sound->setListenerGain(g_settings->getFloat("sound_volume")); - /* - Update sound maker - */ - { - soundmaker.step(dtime); +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")); - ClientMap &map = client.getEnv().getClientMap(); - MapNode n = map.getNodeNoEx(player->getStandingNodePos()); - soundmaker.m_player_step_sound = nodedef->get(n).sound_footstep; - } - /* - Calculate what block is the crosshair pointing to - */ + // Update sound maker + soundmaker->step(dtime); + + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = map.getNodeNoEx(player->getStandingNodePos()); + soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep; +} + + +void MinetestApp::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes, + InteractParams *interactArgs, f32 dtime, bool show_hud, bool show_debug) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + ItemStack playeritem; + { + InventoryList *mlist = local_inventory->getList("main"); + + if (mlist && client->getPlayerItem() < mlist->getSize()) + playeritem = mlist->getItem(client->getPlayerItem()); + } + + const ItemDefinition &playeritem_def = + playeritem.getDefinition(itemdef_manager); - //u32 t1 = device->getTimer()->getRealTime(); + v3f player_position = player->getPosition(); + v3f camera_position = camera->getPosition(); + v3f camera_direction = camera->getDirection(); + v3s16 camera_offset = camera->getOffset(); - 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<f32> shootline(camera_position, - camera_position + camera_direction * BS * (d+1)); + /* + Calculate what block is the crosshair pointing to + */ + + f32 d = playeritem_def.range; // max. distance + f32 d_hand = itemdef_manager->get("").range; + + if (d < 0 && d_hand >= 0) + d = d_hand; + else if (d < 0) + d = 4.0; + + core::line3d<f32> shootline; - // prevent player pointing anything in front-view - if (camera.getCameraMode() == CAMERA_MODE_THIRD_FRONT) - shootline = core::line3d<f32>(0,0,0,0,0,0); + if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) { + + shootline = core::line3d<f32>(camera_position, + camera_position + camera_direction * BS * (d + 1)); + + } else { + // prevent player pointing anything in front-view + if (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) + shootline = core::line3d<f32>(0, 0, 0, 0, 0, 0); + } #ifdef HAVE_TOUCHSCREENGUI - if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { - shootline = g_touchscreengui->getShootline(); - shootline.start += intToFloat(camera_offset,BS); - shootline.end += intToFloat(camera_offset,BS); - } + + if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) { + shootline = g_touchscreengui->getShootline(); + shootline.start += intToFloat(camera_offset, BS); + shootline.end += intToFloat(camera_offset, BS); + } + #endif - ClientActiveObject *selected_object = NULL; - - PointedThing pointed = getPointedThing( - // input - &client, player_position, camera_direction, - camera_position, shootline, d, - playeritem_def.liquids_pointable, !ldown_for_dig, - camera_offset, - // output - hilightboxes, - selected_object); - - if(pointed != pointed_old) - { - infostream<<"Pointing at "<<pointed.dump()<<std::endl; - if (g_settings->getBool("enable_node_highlighting")) { - if (pointed.type == POINTEDTHING_NODE) { - client.setHighlighted(pointed.node_undersurface, show_hud); - } else { - client.setHighlighted(pointed.node_undersurface, false); - } + PointedThing pointed = getPointedThing( + // input + client, player_position, camera_direction, + camera_position, shootline, d, + playeritem_def.liquids_pointable, + !interactArgs->ldown_for_dig, + camera_offset, + // output + highlight_boxes, + interactArgs->selected_object); + + if (pointed != interactArgs->pointed_old) { + infostream << "Pointing at " << pointed.dump() << std::endl; + + if (g_settings->getBool("enable_node_highlighting")) { + if (pointed.type == POINTEDTHING_NODE) { + client->setHighlighted(pointed.node_undersurface, show_hud); + } else { + client->setHighlighted(pointed.node_undersurface, false); } } + } - /* - Stop digging when - - releasing left mouse button - - pointing away from node - */ - if(digging) - { - if(input->getLeftReleased()) - { - infostream<<"Left button released" - <<" (stopped digging)"<<std::endl; - digging = false; - } - else if(pointed != pointed_old) - { - if (pointed.type == POINTEDTHING_NODE - && pointed_old.type == POINTEDTHING_NODE - && pointed.node_undersurface == 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; - digging = false; - } - } - if(!digging) - { - client.interact(1, pointed_old); - client.setCrack(-1, v3s16(0,0,0)); - dig_time = 0.0; + /* + Stop digging when + - releasing left mouse button + - pointing away from node + */ + if (interactArgs->digging) { + if (input->getLeftReleased()) { + infostream << "Left button released" + << " (stopped digging)" << std::endl; + interactArgs->digging = false; + } else if (pointed != interactArgs->pointed_old) { + if (pointed.type == POINTEDTHING_NODE + && interactArgs->pointed_old.type == POINTEDTHING_NODE + && pointed.node_undersurface + == interactArgs->pointed_old.node_undersurface) { + // Still pointing to the same node, but a different face. + // Don't reset. + } else { + infostream << "Pointing away from node" + << " (stopped digging)" << std::endl; + interactArgs->digging = false; } } - if(!digging && ldown_for_dig && !input->getLeftState()) - { - ldown_for_dig = false; + + if (!interactArgs->digging) { + client->interact(1, interactArgs->pointed_old); + client->setCrack(-1, v3s16(0, 0, 0)); + interactArgs->dig_time = 0.0; } + } - bool left_punch = false; - soundmaker.m_player_leftpunch_sound.name = ""; + if (!interactArgs->digging && interactArgs->ldown_for_dig && !input->getLeftState()) { + interactArgs->ldown_for_dig = false; + } - if(input->getRightState()) - repeat_rightclick_timer += dtime; - else - repeat_rightclick_timer = 0; + interactArgs->left_punch = false; - if(playeritem_def.usable && input->getLeftState()) - { - if(input->getLeftClicked()) - client.interact(4, pointed); - } - else if(pointed.type == POINTEDTHING_NODE) - { - v3s16 nodepos = pointed.node_undersurface; - v3s16 neighbourpos = pointed.node_abovesurface; - - /* - Check information text of node - */ - - ClientMap &map = client.getEnv().getClientMap(); - NodeMetadata *meta = map.getNodeMetadata(nodepos); - if(meta){ - infotext = narrow_to_wide(meta->getString("infotext")); - } else { - MapNode n = map.getNode(nodepos); - if(nodedef->get(n).tiledef[0].name == "unknown_node.png"){ - infotext = L"Unknown node: "; - infotext += narrow_to_wide(nodedef->get(n).name); - } - } + soundmaker->m_player_leftpunch_sound.name = ""; - /* - Handle digging - */ - - if(nodig_delay_timer <= 0.0 && input->getLeftState() - && client.checkPrivilege("interact")) - { - if(!digging) - { - infostream<<"Started digging"<<std::endl; - client.interact(0, pointed); - digging = true; - ldown_for_dig = true; - } - MapNode n = client.getEnv().getClientMap().getNode(nodepos); - - // NOTE: Similar piece of code exists on the server side for - // cheat detection. - // Get digging parameters - DigParams params = getDigParams(nodedef->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 (input->getRightState()) + interactArgs->repeat_rightclick_timer += dtime; + else + interactArgs->repeat_rightclick_timer = 0; - float dig_time_complete = 0.0; + if (playeritem_def.usable && input->getLeftState()) { + if (input->getLeftClicked()) + client->interact(4, pointed); + } else if (pointed.type == POINTEDTHING_NODE) { + ToolCapabilities playeritem_toolcap = + playeritem.getToolCapabilities(itemdef_manager); + handlePointingAtNode(interactArgs, pointed, playeritem_def, + playeritem_toolcap, dtime); + } else if (pointed.type == POINTEDTHING_OBJECT) { + handlePointingAtObject(interactArgs, pointed, playeritem, + player_position, show_debug); + } else if (input->getLeftState()) { + // When button is held down in air, show continuous animation + interactArgs->left_punch = true; + } - 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); - } - } + interactArgs->pointed_old = pointed; - 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; - } + if (interactArgs->left_punch || input->getLeftClicked()) + camera->setDigging(0); // left click animation - 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; - } - } + input->resetLeftClicked(); + input->resetRightClicked(); - // 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="<<dig_index<<std::endl; - client.setCrack(dig_index, nodepos); - } - else - { - infostream<<"Digging completed"<<std::endl; - client.interact(2, pointed); - client.setCrack(-1, v3s16(0,0,0)); - MapNode wasnode = map.getNode(nodepos); - client.removeNode(nodepos); - - if (g_settings->getBool("enable_particles")) - { - const ContentFeatures &features = - client.getNodeDefManager()->get(wasnode); - addDiggingParticles - (gamedef, smgr, player, client.getEnv(), - nodepos, features.tiles); - } + input->resetLeftReleased(); + input->resetRightReleased(); +} - dig_time = 0; - digging = false; - - nodig_delay_timer = dig_time_complete - / (float)crack_animation_length; - - // We don't want a corresponding delay to - // very time consuming nodes - if(nodig_delay_timer > 0.3) - nodig_delay_timer = 0.3; - // We want a slight delay to very little - // time consuming nodes - float mindelay = 0.15; - if(nodig_delay_timer < mindelay) - nodig_delay_timer = mindelay; - - // Send event to trigger sound - MtEvent *e = new NodeDugEvent(nodepos, wasnode); - gamedef->event()->put(e); - } - if(dig_time_complete < 100000.0) - dig_time += dtime; - else { - dig_time = 0; - client.setCrack(-1, nodepos); - } +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; - camera.setDigging(0); // left click animation - } + /* + Check information text of node + */ - if((input->getRightClicked() || - repeat_rightclick_timer >= - g_settings->getFloat("repeat_rightclick_time")) && - client.checkPrivilege("interact")) - { - repeat_rightclick_timer = 0; - infostream<<"Ground right-clicked"<<std::endl; + ClientMap &map = client->getEnv().getClientMap(); + NodeMetadata *meta = map.getNodeMetadata(nodepos); - if(meta && meta->getString("formspec") != "" && !random_input - && !input->isKeyDown(getKeySetting("keymap_sneak"))) - { - infostream<<"Launching custom inventory view"<<std::endl; + if (meta) { + infotext = narrow_to_wide(meta->getString("infotext")); + } else { + MapNode n = map.getNode(nodepos); - InventoryLocation inventoryloc; - inventoryloc.setNodeMeta(nodepos); + if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") { + infotext = L"Unknown node: "; + infotext += narrow_to_wide(nodedef_manager->get(n).name); + } + } - NodeMetadataFormSource* fs_src = new NodeMetadataFormSource( - &client.getEnv().getClientMap(), nodepos); - TextDest* txt_dst = new TextDestNodeMetadata(nodepos, &client); + if (interactArgs->nodig_delay_timer <= 0.0 && input->getLeftState() + && client->checkPrivilege("interact")) { + handleDigging(interactArgs, pointed, nodepos, playeritem_toolcap, dtime); + } - create_formspec_menu(¤t_formspec, &client, gamedef, - tsrc, device, fs_src, txt_dst, &client); + 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; - 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(); - } + if (meta && meta->getString("formspec") != "" && !random_input + && !input->isKeyDown(getKeySetting("keymap_sneak"))) { + infostream << "Launching custom inventory view" << std::endl; - if (playeritem_def.node_placement_prediction == "" || - nodedef->get(map.getNode(nodepos)).rightclickable) - client.interact(3, pointed); // Report to server - } + InventoryLocation inventoryloc; + inventoryloc.setNodeMeta(nodepos); + + NodeMetadataFormSource *fs_src = new NodeMetadataFormSource( + &client->getEnv().getClientMap(), nodepos); + TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client); + + create_formspec_menu(¤t_formspec, client, gamedef, + texture_src, device, fs_src, txt_dst, client); + + current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc); + } else { + // Report right click to server + + camera->setDigging(1); // right click animation (always shown for feedback) + + // If the wielded item has node placement prediction, + // make that happen + bool placed = nodePlacementPrediction(*client, + playeritem_def, + nodepos, neighbourpos); + + if (placed) { + // Report to server + client->interact(3, pointed); + // Read the sound + soundmaker->m_player_rightpunch_sound = + playeritem_def.sound_place; + } else { + soundmaker->m_player_rightpunch_sound = + SimpleSoundSpec(); } + + if (playeritem_def.node_placement_prediction == "" || + nodedef_manager->get(map.getNode(nodepos)).rightclickable) + client->interact(3, pointed); // Report to server } - else if(pointed.type == POINTEDTHING_OBJECT) - { - infotext = narrow_to_wide(selected_object->infoText()); + } +} - if(infotext == L"" && show_debug){ - infotext = narrow_to_wide(selected_object->debugInfoText()); - } - //if(input->getLeftClicked()) - if(input->getLeftState()) - { - bool do_punch = false; - bool do_punch_damage = false; - if(object_hit_delay_timer <= 0.0){ - do_punch = true; - do_punch_damage = true; - object_hit_delay_timer = object_hit_delay; - } - if(input->getLeftClicked()){ - do_punch = true; - } - if(do_punch){ - infostream<<"Left-clicked object"<<std::endl; - left_punch = true; - } - if(do_punch_damage){ - // Report direct punch - v3f objpos = selected_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"<<std::endl; - client.interact(3, pointed); // place - } +void MinetestApp::handlePointingAtObject(InteractParams *interactArgs, + const PointedThing &pointed, + const ItemStack &playeritem, + const v3f &player_position, + bool show_debug) +{ + infotext = narrow_to_wide(interactArgs->selected_object->infoText()); + + if (infotext == L"" && show_debug) { + infotext = narrow_to_wide(interactArgs->selected_object->debugInfoText()); + } + + if (input->getLeftState()) { + bool do_punch = false; + bool do_punch_damage = false; + + if (interactArgs->object_hit_delay_timer <= 0.0) { + do_punch = true; + do_punch_damage = true; + interactArgs->object_hit_delay_timer = object_hit_delay; } - else if(input->getLeftState()) - { - // When button is held down in air, show continuous animation - left_punch = true; + + if (input->getLeftClicked()) + do_punch = true; + + if (do_punch) { + infostream << "Left-clicked object" << std::endl; + interactArgs->left_punch = true; } - pointed_old = pointed; + if (do_punch_damage) { + // Report direct punch + v3f objpos = interactArgs->selected_object->getPosition(); + v3f dir = (objpos - player_position).normalize(); - if(left_punch || input->getLeftClicked()) - { - camera.setDigging(0); // left click animation + bool disable_send = interactArgs->selected_object->directReportPunch( + dir, &playeritem, interactArgs->time_from_last_punch); + interactArgs->time_from_last_punch = 0; + + if (!disable_send) + client->interact(0, pointed); } + } else if (input->getRightClicked()) { + infostream << "Right-clicked object" << std::endl; + client->interact(3, pointed); // place + } +} - input->resetLeftClicked(); - input->resetRightClicked(); - input->resetLeftReleased(); - input->resetRightReleased(); +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; + } - /* - Calculate stuff for drawing - */ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + ClientMap &map = client->getEnv().getClientMap(); + MapNode n = client->getEnv().getClientMap().getNode(nodepos); - /* - Fog range - */ + // NOTE: Similar piece of code exists on the server side for + // cheat detection. + // Get digging parameters + DigParams params = getDigParams(nodedef_manager->get(n).groups, + &playeritem_toolcap); + + // If can't dig, try hand + if (!params.diggable) { + const ItemDefinition &hand = itemdef_manager->get(""); + const ToolCapabilities *tp = hand.tool_capabilities; - if(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; + if (tp) + params = getDigParams(nodedef_manager->get(n).groups, tp); + } + + if (params.diggable == false) { + // I guess nobody will wait for this long + interactArgs->dig_time_complete = 10000000.0; + } else { + interactArgs->dig_time_complete = params.time; + + if (g_settings->getBool("enable_particles")) { + const ContentFeatures &features = + client->getNodeDefManager()->get(n); + addPunchingParticles(gamedef, smgr, player, + client->getEnv(), nodepos, features.tiles); } + } - /* - Calculate general brightness - */ - u32 daynight_ratio = client.getEnv().getDayNightRatio(); - float time_brightness = decode_light_f((float)daynight_ratio/1000.0); - float direct_brightness = 0; - bool sunlight_seen = false; - if(g_settings->getBool("free_move")){ - direct_brightness = time_brightness; - sunlight_seen = true; + if (interactArgs->dig_time_complete >= 0.001) { + interactArgs->dig_index = (float)crack_animation_length + * interactArgs->dig_time + / interactArgs->dig_time_complete; + } else { + // This is for torches + interactArgs->dig_index = crack_animation_length; + } + + SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig; + + if (sound_dig.exists() && params.diggable) { + if (sound_dig.name == "__group") { + if (params.main_group != "") { + soundmaker->m_player_leftpunch_sound.gain = 0.5; + soundmaker->m_player_leftpunch_sound.name = + std::string("default_dig_") + + params.main_group; + } } else { - 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; + soundmaker->m_player_leftpunch_sound = sound_dig; } + } - 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(); + // 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="<<dig_index<<std::endl; + client->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); - /* - 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); - } + if (g_settings->getBool("enable_particles")) { + const ContentFeatures &features = + client->getNodeDefManager()->get(wasnode); + addDiggingParticles + (gamedef, smgr, player, client->getEnv(), + nodepos, features.tiles); } - /* - Update particles - */ + interactArgs->dig_time = 0; + interactArgs->digging = false; - allparticles_step(dtime); - allparticlespawners_step(dtime, client.getEnv()); + interactArgs->nodig_delay_timer = + interactArgs->dig_time_complete / (float)crack_animation_length; - /* - Fog - */ + // We don't want a corresponding delay to + // very time consuming nodes + if (interactArgs->nodig_delay_timer > 0.3) + interactArgs->nodig_delay_timer = 0.3; + + // We want a slight delay to very little + // time consuming nodes + const float mindelay = 0.15; + + if (interactArgs->nodig_delay_timer < mindelay) + interactArgs->nodig_delay_timer = mindelay; - if(g_settings->getBool("enable_fog") && !force_fog_off) - { - driver->setFog( - bgcolor, + // Send event to trigger sound + MtEvent *e = new NodeDugEvent(nodepos, wasnode); + gamedef->event()->put(e); + } + + if (interactArgs->dig_time_complete < 100000.0) { + interactArgs->dig_time += dtime; + } else { + interactArgs->dig_time = 0; + client->setCrack(-1, nodepos); + } + + camera->setDigging(0); // left click animation +} + + +void MinetestApp::updateFrame(std::vector<aabb3f> &highlight_boxes, + ProfilerGraph *graph, RunStats *stats, InteractParams *interactArgs, + f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam) +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + /* + Fog range + */ + + if (draw_control->range_all) { + interactArgs->fog_range = 100000 * BS; + } else { + interactArgs->fog_range = draw_control->wanted_range * BS + + 0.0 * MAP_BLOCKSIZE * BS; + interactArgs->fog_range = MYMIN( + interactArgs->fog_range, + (draw_control->farthest_drawn + 20) * BS); + interactArgs->fog_range *= 0.9; + } + + /* + Calculate general brightness + */ + u32 daynight_ratio = client->getEnv().getDayNightRatio(); + float time_brightness = decode_light_f((float)daynight_ratio / 1000.0); + float direct_brightness = 0; + bool sunlight_seen = false; + + if (g_settings->getBool("free_move")) { + direct_brightness = time_brightness; + sunlight_seen = true; + } else { + ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG); + float old_brightness = sky->getBrightness(); + direct_brightness = client->getEnv().getClientMap() + .getBackgroundBrightness(MYMIN(interactArgs->fog_range * 1.2, 60 * BS), + daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen) + / 255.0; + } + + float time_of_day = 0; + float time_of_day_smooth = 0; + + time_of_day = client->getEnv().getTimeOfDayF(); + + const float maxsm = 0.05; + + if (fabs(time_of_day - time_of_day_smooth) > maxsm && + fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm && + fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm) + time_of_day_smooth = time_of_day; + + const float todsm = 0.05; + + if (time_of_day_smooth > 0.8 && time_of_day < 0.2) + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + (time_of_day + 1.0) * todsm; + else + time_of_day_smooth = time_of_day_smooth * (1.0 - todsm) + + time_of_day * todsm; + + sky->update(time_of_day_smooth, time_brightness, direct_brightness, + sunlight_seen, camera->getCameraMode(), player->getYaw(), + player->getPitch()); + + /* + 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 { + clouds->setVisible(false); + } + } + + /* + Update particles + */ + + allparticles_step(dtime); + allparticlespawners_step(dtime, client->getEnv()); + + /* + Fog + */ + + 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<<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 = " - <<(dtime_jitter1_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(show_hud || 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); - } + ); + } - if (guitext->isVisible()) - { - core::rect<s32> 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<<std::setprecision(1)<<std::fixed - <<"(" <<(player_position.X/BS) - <<", "<<(player_position.Y/BS) - <<", "<<(player_position.Z/BS) - <<") (yaw="<<(wrapDegrees_0_360(camera_yaw)) - <<") (seed = "<<((u64)client.getMapSeed()) - <<")"; - guitext2->setText(narrow_to_wide(os.str()).c_str()); - guitext2->setVisible(true); - - core::rect<s32> 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<s32> 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"<<std::endl; - client.getLocalInventory(local_inventory); + if (client->getLocalInventoryUpdated()) { + //infostream<<"Updating local inventory"<<std::endl; + client->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<s32>(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<s32>(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); +} - client.Stop(); - //force answer all texture and shader jobs (TODO return empty values) +void MinetestApp::updateGui(float *statustext_time, const RunStats& stats, + f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam) +{ + v2u32 screensize = driver->getScreenSize(); + LocalPlayer *player = client->getEnv().getLocalPlayer(); + v3f player_position = player->getPosition(); + + if (flags.show_debug) { + static float drawtime_avg = 0; + drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05; - while(!client.isShutdown()) { - tsrc->processQueue(); - shsrc->processQueue(); - sleep_ms(100); + u16 fps = 1.0 / stats.dtime_jitter.avg; + + std::ostringstream os(std::ios_base::binary); + os << std::fixed + << "Minetest " << minetest_version_hash + << " FPS = " << fps + << " (R: range_all=" << draw_control->range_all << ")" + << std::setprecision(0) + << " drawtime = " << drawtime_avg + << std::setprecision(1) + << ", dtime_jitter = " + << (stats.dtime_jitter.max_fraction * 100.0) << " %" + << std::setprecision(1) + << ", v_range = " << draw_control->wanted_range + << std::setprecision(3) + << ", RTT = " << client->getRTT(); + guitext->setText(narrow_to_wide(os.str()).c_str()); + guitext->setVisible(true); + } else if (flags.show_hud || flags.show_chat) { + std::ostringstream os(std::ios_base::binary); + os << "Minetest " << minetest_version_hash; + guitext->setText(narrow_to_wide(os.str()).c_str()); + guitext->setVisible(true); + } else { + guitext->setVisible(false); } - // Client scope (client is destructed before destructing *def and tsrc) - }while(0); - } // try-catch - catch(SerializationError &e) - { - 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; + if (guitext->isVisible()) { + core::rect<s32> 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<s32> 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<s32> 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); + } +} - if(!sound_is_dummy) - delete sound; +/* Log times and stuff for visualization */ +inline void MinetestApp::updateProfilerGraphs(ProfilerGraph *graph) +{ + Profiler::GraphValues values; + g_profiler->graphGet(values); + graph->put(values); +} + + + +/**************************************************************************** + Misc + ****************************************************************************/ + +/* On some computers framerate doesn't seem to be automatically limited + * + * *Must* be called after device->run() so that device->getTimer()->getTime(); + * is correct + */ +inline void MinetestApp::limitFps(FpsControl *params, f32 *dtime) +{ + // not using getRealTime is necessary for wine + u32 time = device->getTimer()->getTime(); + + u32 last_time = params->last_time; + + if (time > last_time) // Make sure last_time hasn't overflowed + params->busy_time = time - last_time; + else + params->busy_time = 0; - //has to be deleted first to stop all server threads - delete server; + u32 frametime_min = 1000 / (g_menumgr.pausesGame() + ? g_settings->getFloat("pause_fps_max") + : g_settings->getFloat("fps_max")); - delete tsrc; - delete shsrc; - delete nodedef; - delete itemdef; + 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; - //extended resource accounting + params->last_time = time; +} + + +void MinetestApp::showOverlayMessage(const char *msg, float dtime, + int percent, bool draw_clouds) +{ + wchar_t *text = wgettext(msg); + draw_load_screen(text, device, guienv, font, dtime, percent, draw_clouds); + delete[] text; +} + + +inline const char *MinetestApp::boolToCStr(bool v) +{ + static const char *str[] = { "false", "true" }; + return str[v]; +} + + + +/**************************************************************************** + Shutdown / cleanup + ****************************************************************************/ + +void MinetestApp::extendedResourceCleanup() +{ + // Extended resource accounting infostream << "Irrlicht resources after cleanup:" << std::endl; infostream << "\tRemaining meshes : " - << device->getSceneManager()->getMeshCache()->getMeshCount() << std::endl; + << 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<KeyPress> { 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<KeyPress> { 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, |