diff options
author | Craig Robbins <kde.psych@gmail.com> | 2014-10-31 22:13:04 +1000 |
---|---|---|
committer | RealBadAngel <maciej.kasatkin@o2.pl> | 2014-11-02 02:18:25 +0100 |
commit | 429ecb2b94a66a11cf06be7303d05aa2038d19d2 (patch) | |
tree | 79257430f3598282311a270b218f28461036ff72 /src | |
parent | 9e811a92e7846b958e4bc84aeb30bad8b51e8e1d (diff) | |
download | minetest-429ecb2b94a66a11cf06be7303d05aa2038d19d2.tar.gz minetest-429ecb2b94a66a11cf06be7303d05aa2038d19d2.tar.bz2 minetest-429ecb2b94a66a11cf06be7303d05aa2038d19d2.zip |
Refactor the_game() to make it more understandable and maintainable.
The following is a record of 31 commits before squashing:
Revert "Remove m_ext_ptr in GUIFormSpecMenu, replaced by refcount mechanism"
This reverts commit b49e5cfc7013cef7e9af79d17e04f7e7e4c377d4.
Basic reformatting with astyle
-- additional formatting will be modified, manually, as the need for it is encountered
Start "outlining" what a MinetestApp class might look like
Add MinetestApp::shutdown()
Converted class member functions to camelCase and created protos for new functions
First stage of connect to server done
Add get itemdefs/nodedefs/media code
Init clouds, camera, sky, init GUI, HUD
Input handling
Client events, camera, sound, draw
Fix wield hand getting stuck digging and add debug text back
Fix FPS
Added profiler graph back
Fix FPS issue
Need to work out what went wrong and clean up the copy/paste stuff
Annotate
Various:
Rewrote limitFps()
Limited scope of some variables
Jitter calcs
Reduce scope of objects
Move some stuff out of ::run and minor formatting cleanup
Scope reduction
Function splits
Removed old (broken) limitFps()
Added exception handling back
Fixed some formatting
Reverted commented out unit tests (uncommented them)
Slow clouds down on loading and media screens so the behaviour is like the original the_game()
Formatting/style (no functional changes)
Manually reapply upstream b49e5cf: Remove m_ext_ptr in GUIFormSpecMenu, replaced by refcount mechanism
Fixed silly errors on my part
Minor formatting cleanups
Removed strange differentiation in FPS limiting when loading
FPS limiting was done differently if cloud_menu_background was true, which does not make sense
Cleaning up
Add some comments
Diffstat (limited to 'src')
-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, |