Line data Source code
1 : /*
2 : Minetest
3 : Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 :
5 : This program is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Lesser General Public License as published by
7 : the Free Software Foundation; either version 2.1 of the License, or
8 : (at your option) any later version.
9 :
10 : This program is distributed in the hope that it will be useful,
11 : but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU Lesser General Public License for more details.
14 :
15 : You should have received a copy of the GNU Lesser General Public License along
16 : with this program; if not, write to the Free Software Foundation, Inc.,
17 : 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 : */
19 :
20 : #include "game.h"
21 :
22 : #include <iomanip>
23 : #include "camera.h"
24 : #include "client.h"
25 : #include "client/tile.h" // For TextureSource
26 : #include "clientmap.h"
27 : #include "clouds.h"
28 : #include "config.h"
29 : #include "content_cao.h"
30 : #include "drawscene.h"
31 : #include "event_manager.h"
32 : #include "fontengine.h"
33 : #include "itemdef.h"
34 : #include "log.h"
35 : #include "filesys.h"
36 : #include "gettext.h"
37 : #include "guiChatConsole.h"
38 : #include "guiFormSpecMenu.h"
39 : #include "guiKeyChangeMenu.h"
40 : #include "guiPasswordChange.h"
41 : #include "guiVolumeChange.h"
42 : #include "hud.h"
43 : #include "logoutputbuffer.h"
44 : #include "mainmenumanager.h"
45 : #include "mapblock.h"
46 : #include "nodedef.h" // Needed for determining pointing to nodes
47 : #include "nodemetadata.h"
48 : #include "particles.h"
49 : #include "profiler.h"
50 : #include "quicktune_shortcutter.h"
51 : #include "server.h"
52 : #include "settings.h"
53 : #include "shader.h" // For ShaderSource
54 : #include "sky.h"
55 : #include "subgame.h"
56 : #include "tool.h"
57 : #include "util/directiontables.h"
58 : #include "util/pointedthing.h"
59 : #include "version.h"
60 : #include "minimap.h"
61 :
62 : #include "sound.h"
63 :
64 : #if USE_SOUND
65 : #include "sound_openal.h"
66 : #endif
67 :
68 : #ifdef HAVE_TOUCHSCREENGUI
69 : #include "touchscreengui.h"
70 : #endif
71 :
72 : extern Settings *g_settings;
73 : extern Profiler *g_profiler;
74 :
75 : /*
76 : Text input system
77 : */
78 :
79 0 : struct TextDestNodeMetadata : public TextDest {
80 0 : TextDestNodeMetadata(v3s16 p, Client *client)
81 0 : {
82 0 : m_p = p;
83 0 : m_client = client;
84 0 : }
85 : // This is deprecated I guess? -celeron55
86 0 : void gotText(std::wstring text)
87 : {
88 0 : std::string ntext = wide_to_narrow(text);
89 0 : infostream << "Submitting 'text' field of node at (" << m_p.X << ","
90 0 : << m_p.Y << "," << m_p.Z << "): " << ntext << std::endl;
91 0 : StringMap fields;
92 0 : fields["text"] = ntext;
93 0 : m_client->sendNodemetaFields(m_p, "", fields);
94 0 : }
95 0 : void gotText(const StringMap &fields)
96 : {
97 0 : m_client->sendNodemetaFields(m_p, "", fields);
98 0 : }
99 :
100 : v3s16 m_p;
101 : Client *m_client;
102 : };
103 :
104 0 : struct TextDestPlayerInventory : public TextDest {
105 0 : TextDestPlayerInventory(Client *client)
106 0 : {
107 0 : m_client = client;
108 0 : m_formname = "";
109 0 : }
110 0 : TextDestPlayerInventory(Client *client, std::string formname)
111 0 : {
112 0 : m_client = client;
113 0 : m_formname = formname;
114 0 : }
115 0 : void gotText(const StringMap &fields)
116 : {
117 0 : m_client->sendInventoryFields(m_formname, fields);
118 0 : }
119 :
120 : Client *m_client;
121 : };
122 :
123 2 : struct LocalFormspecHandler : public TextDest {
124 : LocalFormspecHandler();
125 1 : LocalFormspecHandler(std::string formname) :
126 1 : m_client(0)
127 : {
128 1 : m_formname = formname;
129 1 : }
130 :
131 0 : LocalFormspecHandler(std::string formname, Client *client) :
132 0 : m_client(client)
133 : {
134 0 : m_formname = formname;
135 0 : }
136 :
137 0 : void gotText(std::wstring message)
138 : {
139 0 : errorstream << "LocalFormspecHandler::gotText old style message received" << std::endl;
140 0 : }
141 :
142 1 : void gotText(const StringMap &fields)
143 : {
144 1 : if (m_formname == "MT_PAUSE_MENU") {
145 1 : if (fields.find("btn_sound") != fields.end()) {
146 0 : g_gamecallback->changeVolume();
147 1 : return;
148 : }
149 :
150 1 : if (fields.find("btn_key_config") != fields.end()) {
151 0 : g_gamecallback->keyConfig();
152 0 : return;
153 : }
154 :
155 1 : if (fields.find("btn_exit_menu") != fields.end()) {
156 0 : g_gamecallback->disconnect();
157 0 : return;
158 : }
159 :
160 1 : if (fields.find("btn_exit_os") != fields.end()) {
161 1 : g_gamecallback->exitToOS();
162 1 : return;
163 : }
164 :
165 0 : if (fields.find("btn_change_password") != fields.end()) {
166 0 : g_gamecallback->changePassword();
167 0 : return;
168 : }
169 :
170 0 : if (fields.find("quit") != fields.end()) {
171 0 : return;
172 : }
173 :
174 0 : if (fields.find("btn_continue") != fields.end()) {
175 0 : return;
176 : }
177 : }
178 :
179 0 : if (m_formname == "MT_CHAT_MENU") {
180 : assert(m_client != 0);
181 :
182 0 : if ((fields.find("btn_send") != fields.end()) ||
183 0 : (fields.find("quit") != fields.end())) {
184 0 : StringMap::const_iterator it = fields.find("f_text");
185 0 : if (it != fields.end())
186 0 : m_client->typeChatMessage(narrow_to_wide(it->second));
187 :
188 0 : return;
189 : }
190 : }
191 :
192 0 : if (m_formname == "MT_DEATH_SCREEN") {
193 : assert(m_client != 0);
194 :
195 0 : if ((fields.find("btn_respawn") != fields.end())) {
196 0 : m_client->sendRespawn();
197 0 : return;
198 : }
199 :
200 0 : if (fields.find("quit") != fields.end()) {
201 0 : m_client->sendRespawn();
202 0 : return;
203 : }
204 : }
205 :
206 : // don't show error message for unhandled cursor keys
207 0 : if ((fields.find("key_up") != fields.end()) ||
208 0 : (fields.find("key_down") != fields.end()) ||
209 0 : (fields.find("key_left") != fields.end()) ||
210 0 : (fields.find("key_right") != fields.end())) {
211 0 : return;
212 : }
213 :
214 0 : errorstream << "LocalFormspecHandler::gotText unhandled >"
215 0 : << m_formname << "< event" << std::endl;
216 :
217 0 : int i = 0;
218 0 : StringMap::const_iterator it;
219 0 : for (it = fields.begin(); it != fields.end(); ++it) {
220 0 : errorstream << "\t" << i << ": " << it->first
221 0 : << "=" << it->second << std::endl;
222 0 : i++;
223 : }
224 : }
225 :
226 : Client *m_client;
227 : };
228 :
229 : /* Form update callback */
230 :
231 0 : class NodeMetadataFormSource: public IFormSource
232 : {
233 : public:
234 0 : NodeMetadataFormSource(ClientMap *map, v3s16 p):
235 : m_map(map),
236 0 : m_p(p)
237 : {
238 0 : }
239 0 : std::string getForm()
240 : {
241 0 : NodeMetadata *meta = m_map->getNodeMetadata(m_p);
242 :
243 0 : if (!meta)
244 0 : return "";
245 :
246 0 : return meta->getString("formspec");
247 : }
248 0 : std::string resolveText(std::string str)
249 : {
250 0 : NodeMetadata *meta = m_map->getNodeMetadata(m_p);
251 :
252 0 : if (!meta)
253 0 : return str;
254 :
255 0 : return meta->resolveString(str);
256 : }
257 :
258 : ClientMap *m_map;
259 : v3s16 m_p;
260 : };
261 :
262 0 : class PlayerInventoryFormSource: public IFormSource
263 : {
264 : public:
265 0 : PlayerInventoryFormSource(Client *client):
266 0 : m_client(client)
267 : {
268 0 : }
269 0 : std::string getForm()
270 : {
271 0 : LocalPlayer *player = m_client->getEnv().getLocalPlayer();
272 0 : return player->inventory_formspec;
273 : }
274 :
275 : Client *m_client;
276 : };
277 :
278 : /*
279 : Check if a node is pointable
280 : */
281 293652 : inline bool isPointableNode(const MapNode &n,
282 : Client *client, bool liquids_pointable)
283 : {
284 293652 : const ContentFeatures &features = client->getNodeDefManager()->get(n);
285 483849 : return features.pointable ||
286 293652 : (liquids_pointable && features.isLiquid());
287 : }
288 :
289 : /*
290 : Find what the player is pointing at
291 : */
292 1166 : PointedThing getPointedThing(Client *client, v3f player_position,
293 : v3f camera_direction, v3f camera_position, core::line3d<f32> shootline,
294 : f32 d, bool liquids_pointable, bool look_for_object, v3s16 camera_offset,
295 : std::vector<aabb3f> &hilightboxes, ClientActiveObject *&selected_object)
296 : {
297 1166 : PointedThing result;
298 :
299 1166 : hilightboxes.clear();
300 1166 : selected_object = NULL;
301 :
302 1166 : INodeDefManager *nodedef = client->getNodeDefManager();
303 1166 : ClientMap &map = client->getEnv().getClientMap();
304 :
305 1166 : f32 mindistance = BS * 1001;
306 :
307 : // First try to find a pointed at active object
308 1166 : if (look_for_object) {
309 2332 : selected_object = client->getSelectedActiveObject(d * BS,
310 1166 : camera_position, shootline);
311 :
312 1166 : if (selected_object != NULL) {
313 0 : if (selected_object->doShowSelectionBox()) {
314 0 : aabb3f *selection_box = selected_object->getSelectionBox();
315 : // Box should exist because object was
316 : // returned in the first place
317 : assert(selection_box);
318 :
319 0 : v3f pos = selected_object->getPosition();
320 0 : hilightboxes.push_back(aabb3f(
321 0 : selection_box->MinEdge + pos - intToFloat(camera_offset, BS),
322 0 : selection_box->MaxEdge + pos - intToFloat(camera_offset, BS)));
323 : }
324 :
325 0 : mindistance = (selected_object->getPosition() - camera_position).getLength();
326 :
327 0 : result.type = POINTEDTHING_OBJECT;
328 0 : result.object_id = selected_object->getId();
329 : }
330 : }
331 :
332 : // That didn't work, try to find a pointed at node
333 :
334 :
335 1166 : v3s16 pos_i = floatToInt(player_position, BS);
336 :
337 : /*infostream<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
338 : <<std::endl;*/
339 :
340 1166 : s16 a = d;
341 1166 : s16 ystart = pos_i.Y + 0 - (camera_direction.Y < 0 ? a : 1);
342 1166 : s16 zstart = pos_i.Z - (camera_direction.Z < 0 ? a : 1);
343 1166 : s16 xstart = pos_i.X - (camera_direction.X < 0 ? a : 1);
344 1166 : s16 yend = pos_i.Y + 1 + (camera_direction.Y > 0 ? a : 1);
345 1166 : s16 zend = pos_i.Z + (camera_direction.Z > 0 ? a : 1);
346 1166 : s16 xend = pos_i.X + (camera_direction.X > 0 ? a : 1);
347 :
348 : // Prevent signed number overflow
349 1166 : if (yend == 32767)
350 0 : yend = 32766;
351 :
352 1166 : if (zend == 32767)
353 0 : zend = 32766;
354 :
355 1166 : if (xend == 32767)
356 0 : xend = 32766;
357 :
358 9325 : for (s16 y = ystart; y <= yend; y++)
359 57113 : for (s16 z = zstart; z <= zend; z++)
360 342606 : for (s16 x = xstart; x <= xend; x++) {
361 293652 : MapNode n;
362 : bool is_valid_position;
363 :
364 293652 : n = map.getNodeNoEx(v3s16(x, y, z), &is_valid_position);
365 293652 : if (!is_valid_position)
366 103455 : continue;
367 :
368 293652 : if (!isPointableNode(n, client, liquids_pointable))
369 103455 : continue;
370 :
371 380394 : std::vector<aabb3f> boxes = n.getSelectionBoxes(nodedef);
372 :
373 190197 : v3s16 np(x, y, z);
374 190197 : v3f npf = intToFloat(np, BS);
375 :
376 950985 : for (std::vector<aabb3f>::const_iterator
377 190197 : i = boxes.begin();
378 760788 : i != boxes.end(); i++) {
379 190197 : aabb3f box = *i;
380 190197 : box.MinEdge += npf;
381 190197 : box.MaxEdge += npf;
382 :
383 1331379 : for (u16 j = 0; j < 6; j++) {
384 1141182 : v3s16 facedir = g_6dirs[j];
385 1141182 : aabb3f facebox = box;
386 :
387 1141182 : f32 d = 0.001 * BS;
388 :
389 1141182 : if (facedir.X > 0)
390 190197 : facebox.MinEdge.X = facebox.MaxEdge.X - d;
391 950985 : else if (facedir.X < 0)
392 190197 : facebox.MaxEdge.X = facebox.MinEdge.X + d;
393 760788 : else if (facedir.Y > 0)
394 190197 : facebox.MinEdge.Y = facebox.MaxEdge.Y - d;
395 570591 : else if (facedir.Y < 0)
396 190197 : facebox.MaxEdge.Y = facebox.MinEdge.Y + d;
397 380394 : else if (facedir.Z > 0)
398 190197 : facebox.MinEdge.Z = facebox.MaxEdge.Z - d;
399 190197 : else if (facedir.Z < 0)
400 190197 : facebox.MaxEdge.Z = facebox.MinEdge.Z + d;
401 :
402 1141182 : v3f centerpoint = facebox.getCenter();
403 1141182 : f32 distance = (centerpoint - camera_position).getLength();
404 :
405 1141182 : if (distance >= mindistance)
406 1157318 : continue;
407 :
408 1124571 : if (!facebox.intersectsWithLine(shootline))
409 1124096 : continue;
410 :
411 475 : v3s16 np_above = np + facedir;
412 :
413 475 : result.type = POINTEDTHING_NODE;
414 475 : result.node_undersurface = np;
415 475 : result.node_abovesurface = np_above;
416 475 : mindistance = distance;
417 :
418 475 : hilightboxes.clear();
419 :
420 475 : if (!g_settings->getBool("enable_node_highlighting")) {
421 0 : for (std::vector<aabb3f>::const_iterator
422 0 : i2 = boxes.begin();
423 0 : i2 != boxes.end(); i2++) {
424 0 : aabb3f box = *i2;
425 0 : box.MinEdge += npf + v3f(-d, -d, -d) - intToFloat(camera_offset, BS);
426 0 : box.MaxEdge += npf + v3f(d, d, d) - intToFloat(camera_offset, BS);
427 0 : hilightboxes.push_back(box);
428 : }
429 : }
430 : }
431 : }
432 : } // for coords
433 :
434 1166 : return result;
435 : }
436 :
437 : /* Profiler display */
438 :
439 8 : void update_profiler_gui(gui::IGUIStaticText *guitext_profiler, FontEngine *fe,
440 : u32 show_profiler, u32 show_profiler_max, s32 screen_height)
441 : {
442 8 : if (show_profiler == 0) {
443 8 : guitext_profiler->setVisible(false);
444 : } else {
445 :
446 0 : std::ostringstream os(std::ios_base::binary);
447 0 : g_profiler->printPage(os, show_profiler, show_profiler_max);
448 0 : std::wstring text = narrow_to_wide(os.str());
449 0 : guitext_profiler->setText(text.c_str());
450 0 : guitext_profiler->setVisible(true);
451 :
452 0 : s32 w = fe->getTextWidth(text.c_str());
453 :
454 0 : if (w < 400)
455 0 : w = 400;
456 :
457 0 : unsigned text_height = fe->getTextHeight();
458 :
459 0 : core::position2di upper_left, lower_right;
460 :
461 0 : upper_left.X = 6;
462 0 : upper_left.Y = (text_height + 5) * 2;
463 0 : lower_right.X = 12 + w;
464 0 : lower_right.Y = upper_left.Y + (text_height + 1) * MAX_PROFILER_TEXT_ROWS;
465 :
466 0 : if (lower_right.Y > screen_height * 2 / 3)
467 0 : lower_right.Y = screen_height * 2 / 3;
468 :
469 0 : core::rect<s32> rect(upper_left, lower_right);
470 :
471 0 : guitext_profiler->setRelativePosition(rect);
472 0 : guitext_profiler->setVisible(true);
473 : }
474 8 : }
475 :
476 1 : class ProfilerGraph
477 : {
478 : private:
479 198374 : struct Piece {
480 : Profiler::GraphValues values;
481 : };
482 : struct Meta {
483 : float min;
484 : float max;
485 : video::SColor color;
486 0 : Meta(float initial = 0,
487 : video::SColor color = video::SColor(255, 255, 255, 255)):
488 : min(initial),
489 : max(initial),
490 0 : color(color)
491 0 : {}
492 : };
493 : std::vector<Piece> m_log;
494 : public:
495 : u32 m_log_max_size;
496 :
497 1 : ProfilerGraph():
498 1 : m_log_max_size(200)
499 1 : {}
500 :
501 1166 : void put(const Profiler::GraphValues &values)
502 : {
503 2332 : Piece piece;
504 1166 : piece.values = values;
505 1166 : m_log.push_back(piece);
506 :
507 3098 : while (m_log.size() > m_log_max_size)
508 966 : m_log.erase(m_log.begin());
509 1166 : }
510 :
511 0 : void draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver,
512 : gui::IGUIFont *font) const
513 : {
514 0 : std::map<std::string, Meta> m_meta;
515 :
516 0 : for (std::vector<Piece>::const_iterator k = m_log.begin();
517 0 : k != m_log.end(); k++) {
518 0 : const Piece &piece = *k;
519 :
520 0 : for (Profiler::GraphValues::const_iterator i = piece.values.begin();
521 0 : i != piece.values.end(); i++) {
522 0 : const std::string &id = i->first;
523 0 : const float &value = i->second;
524 : std::map<std::string, Meta>::iterator j =
525 0 : m_meta.find(id);
526 :
527 0 : if (j == m_meta.end()) {
528 0 : m_meta[id] = Meta(value);
529 0 : continue;
530 : }
531 :
532 0 : if (value < j->second.min)
533 0 : j->second.min = value;
534 :
535 0 : if (value > j->second.max)
536 0 : j->second.max = value;
537 : }
538 : }
539 :
540 : // Assign colors
541 : static const video::SColor usable_colors[] = {
542 : video::SColor(255, 255, 100, 100),
543 : video::SColor(255, 90, 225, 90),
544 : video::SColor(255, 100, 100, 255),
545 : video::SColor(255, 255, 150, 50),
546 : video::SColor(255, 220, 220, 100)
547 0 : };
548 : static const u32 usable_colors_count =
549 : sizeof(usable_colors) / sizeof(*usable_colors);
550 0 : u32 next_color_i = 0;
551 :
552 0 : for (std::map<std::string, Meta>::iterator i = m_meta.begin();
553 0 : i != m_meta.end(); i++) {
554 0 : Meta &meta = i->second;
555 0 : video::SColor color(255, 200, 200, 200);
556 :
557 0 : if (next_color_i < usable_colors_count)
558 0 : color = usable_colors[next_color_i++];
559 :
560 0 : meta.color = color;
561 : }
562 :
563 0 : s32 graphh = 50;
564 0 : s32 textx = x_left + m_log_max_size + 15;
565 0 : s32 textx2 = textx + 200 - 15;
566 0 : s32 meta_i = 0;
567 :
568 0 : for (std::map<std::string, Meta>::const_iterator i = m_meta.begin();
569 0 : i != m_meta.end(); i++) {
570 0 : const std::string &id = i->first;
571 0 : const Meta &meta = i->second;
572 0 : s32 x = x_left;
573 0 : s32 y = y_bottom - meta_i * 50;
574 0 : float show_min = meta.min;
575 0 : float show_max = meta.max;
576 :
577 0 : if (show_min >= -0.0001 && show_max >= -0.0001) {
578 0 : if (show_min <= show_max * 0.5)
579 0 : show_min = 0;
580 : }
581 :
582 0 : s32 texth = 15;
583 : char buf[10];
584 0 : snprintf(buf, 10, "%.3g", show_max);
585 0 : font->draw(narrow_to_wide(buf).c_str(),
586 : core::rect<s32>(textx, y - graphh,
587 0 : textx2, y - graphh + texth),
588 0 : meta.color);
589 0 : snprintf(buf, 10, "%.3g", show_min);
590 0 : font->draw(narrow_to_wide(buf).c_str(),
591 : core::rect<s32>(textx, y - texth,
592 : textx2, y),
593 0 : meta.color);
594 0 : font->draw(narrow_to_wide(id).c_str(),
595 0 : core::rect<s32>(textx, y - graphh / 2 - texth / 2,
596 0 : textx2, y - graphh / 2 + texth / 2),
597 0 : meta.color);
598 0 : s32 graph1y = y;
599 0 : s32 graph1h = graphh;
600 0 : bool relativegraph = (show_min != 0 && show_min != show_max);
601 0 : float lastscaledvalue = 0.0;
602 0 : bool lastscaledvalue_exists = false;
603 :
604 0 : for (std::vector<Piece>::const_iterator j = m_log.begin();
605 0 : j != m_log.end(); j++) {
606 0 : const Piece &piece = *j;
607 0 : float value = 0;
608 0 : bool value_exists = false;
609 : Profiler::GraphValues::const_iterator k =
610 0 : piece.values.find(id);
611 :
612 0 : if (k != piece.values.end()) {
613 0 : value = k->second;
614 0 : value_exists = true;
615 : }
616 :
617 0 : if (!value_exists) {
618 0 : x++;
619 0 : lastscaledvalue_exists = false;
620 0 : continue;
621 : }
622 :
623 0 : float scaledvalue = 1.0;
624 :
625 0 : if (show_max != show_min)
626 0 : scaledvalue = (value - show_min) / (show_max - show_min);
627 :
628 0 : if (scaledvalue == 1.0 && value == 0) {
629 0 : x++;
630 0 : lastscaledvalue_exists = false;
631 0 : continue;
632 : }
633 :
634 0 : if (relativegraph) {
635 0 : if (lastscaledvalue_exists) {
636 0 : s32 ivalue1 = lastscaledvalue * graph1h;
637 0 : s32 ivalue2 = scaledvalue * graph1h;
638 0 : driver->draw2DLine(v2s32(x - 1, graph1y - ivalue1),
639 0 : v2s32(x, graph1y - ivalue2), meta.color);
640 : }
641 :
642 0 : lastscaledvalue = scaledvalue;
643 0 : lastscaledvalue_exists = true;
644 : } else {
645 0 : s32 ivalue = scaledvalue * graph1h;
646 0 : driver->draw2DLine(v2s32(x, graph1y),
647 0 : v2s32(x, graph1y - ivalue), meta.color);
648 : }
649 :
650 0 : x++;
651 : }
652 :
653 0 : meta_i++;
654 : }
655 0 : }
656 : };
657 :
658 0 : class NodeDugEvent: public MtEvent
659 : {
660 : public:
661 : v3s16 p;
662 : MapNode n;
663 :
664 0 : NodeDugEvent(v3s16 p, MapNode n):
665 : p(p),
666 0 : n(n)
667 0 : {}
668 0 : const char *getType() const
669 : {
670 0 : return "NodeDug";
671 : }
672 : };
673 :
674 1 : class SoundMaker
675 : {
676 : ISoundManager *m_sound;
677 : INodeDefManager *m_ndef;
678 : public:
679 : float m_player_step_timer;
680 :
681 : SimpleSoundSpec m_player_step_sound;
682 : SimpleSoundSpec m_player_leftpunch_sound;
683 : SimpleSoundSpec m_player_rightpunch_sound;
684 :
685 1 : SoundMaker(ISoundManager *sound, INodeDefManager *ndef):
686 : m_sound(sound),
687 : m_ndef(ndef),
688 1 : m_player_step_timer(0)
689 : {
690 1 : }
691 :
692 38 : void playPlayerStep()
693 : {
694 38 : if (m_player_step_timer <= 0 && m_player_step_sound.exists()) {
695 37 : m_player_step_timer = 0.03;
696 37 : m_sound->playSound(m_player_step_sound, false);
697 : }
698 38 : }
699 :
700 37 : static void viewBobbingStep(MtEvent *e, void *data)
701 : {
702 37 : SoundMaker *sm = (SoundMaker *)data;
703 37 : sm->playPlayerStep();
704 37 : }
705 :
706 1 : static void playerRegainGround(MtEvent *e, void *data)
707 : {
708 1 : SoundMaker *sm = (SoundMaker *)data;
709 1 : sm->playPlayerStep();
710 1 : }
711 :
712 0 : static void playerJump(MtEvent *e, void *data)
713 : {
714 : //SoundMaker *sm = (SoundMaker*)data;
715 0 : }
716 :
717 0 : static void cameraPunchLeft(MtEvent *e, void *data)
718 : {
719 0 : SoundMaker *sm = (SoundMaker *)data;
720 0 : sm->m_sound->playSound(sm->m_player_leftpunch_sound, false);
721 0 : }
722 :
723 0 : static void cameraPunchRight(MtEvent *e, void *data)
724 : {
725 0 : SoundMaker *sm = (SoundMaker *)data;
726 0 : sm->m_sound->playSound(sm->m_player_rightpunch_sound, false);
727 0 : }
728 :
729 0 : static void nodeDug(MtEvent *e, void *data)
730 : {
731 0 : SoundMaker *sm = (SoundMaker *)data;
732 0 : NodeDugEvent *nde = (NodeDugEvent *)e;
733 0 : sm->m_sound->playSound(sm->m_ndef->get(nde->n).sound_dug, false);
734 0 : }
735 :
736 0 : static void playerDamage(MtEvent *e, void *data)
737 : {
738 0 : SoundMaker *sm = (SoundMaker *)data;
739 0 : sm->m_sound->playSound(SimpleSoundSpec("player_damage", 0.5), false);
740 0 : }
741 :
742 0 : static void playerFallingDamage(MtEvent *e, void *data)
743 : {
744 0 : SoundMaker *sm = (SoundMaker *)data;
745 0 : sm->m_sound->playSound(SimpleSoundSpec("player_falling_damage", 0.5), false);
746 0 : }
747 :
748 1 : void registerReceiver(MtEventManager *mgr)
749 : {
750 1 : mgr->reg("ViewBobbingStep", SoundMaker::viewBobbingStep, this);
751 1 : mgr->reg("PlayerRegainGround", SoundMaker::playerRegainGround, this);
752 1 : mgr->reg("PlayerJump", SoundMaker::playerJump, this);
753 1 : mgr->reg("CameraPunchLeft", SoundMaker::cameraPunchLeft, this);
754 1 : mgr->reg("CameraPunchRight", SoundMaker::cameraPunchRight, this);
755 1 : mgr->reg("NodeDug", SoundMaker::nodeDug, this);
756 1 : mgr->reg("PlayerDamage", SoundMaker::playerDamage, this);
757 1 : mgr->reg("PlayerFallingDamage", SoundMaker::playerFallingDamage, this);
758 1 : }
759 :
760 1166 : void step(float dtime)
761 : {
762 1166 : m_player_step_timer -= dtime;
763 1166 : }
764 : };
765 :
766 : // Locally stored sounds don't need to be preloaded because of this
767 2 : class GameOnDemandSoundFetcher: public OnDemandSoundFetcher
768 : {
769 : std::set<std::string> m_fetched;
770 : public:
771 0 : void fetchSounds(const std::string &name,
772 : std::set<std::string> &dst_paths,
773 : std::set<std::string> &dst_datas)
774 : {
775 0 : if (m_fetched.count(name))
776 0 : return;
777 :
778 0 : m_fetched.insert(name);
779 0 : std::string base = porting::path_share + DIR_DELIM + "testsounds";
780 0 : dst_paths.insert(base + DIR_DELIM + name + ".ogg");
781 0 : dst_paths.insert(base + DIR_DELIM + name + ".0.ogg");
782 0 : dst_paths.insert(base + DIR_DELIM + name + ".1.ogg");
783 0 : dst_paths.insert(base + DIR_DELIM + name + ".2.ogg");
784 0 : dst_paths.insert(base + DIR_DELIM + name + ".3.ogg");
785 0 : dst_paths.insert(base + DIR_DELIM + name + ".4.ogg");
786 0 : dst_paths.insert(base + DIR_DELIM + name + ".5.ogg");
787 0 : dst_paths.insert(base + DIR_DELIM + name + ".6.ogg");
788 0 : dst_paths.insert(base + DIR_DELIM + name + ".7.ogg");
789 0 : dst_paths.insert(base + DIR_DELIM + name + ".8.ogg");
790 0 : dst_paths.insert(base + DIR_DELIM + name + ".9.ogg");
791 : }
792 : };
793 :
794 : class GameGlobalShaderConstantSetter : public IShaderConstantSetter
795 : {
796 : Sky *m_sky;
797 : bool *m_force_fog_off;
798 : f32 *m_fog_range;
799 : Client *m_client;
800 : bool m_fogEnabled;
801 :
802 : public:
803 0 : void onSettingsChange(const std::string &name)
804 : {
805 0 : if (name == "enable_fog")
806 0 : m_fogEnabled = g_settings->getBool("enable_fog");
807 0 : }
808 :
809 0 : static void SettingsCallback(const std::string name, void *userdata)
810 : {
811 0 : reinterpret_cast<GameGlobalShaderConstantSetter*>(userdata)->onSettingsChange(name);
812 0 : }
813 :
814 1 : GameGlobalShaderConstantSetter(Sky *sky, bool *force_fog_off,
815 : f32 *fog_range, Client *client) :
816 : m_sky(sky),
817 : m_force_fog_off(force_fog_off),
818 : m_fog_range(fog_range),
819 1 : m_client(client)
820 : {
821 1 : g_settings->registerChangedCallback("enable_fog", SettingsCallback, this);
822 1 : m_fogEnabled = g_settings->getBool("enable_fog");
823 1 : }
824 :
825 2 : ~GameGlobalShaderConstantSetter()
826 2 : {
827 1 : g_settings->deregisterChangedCallback("enable_fog", SettingsCallback, this);
828 2 : }
829 :
830 1426078 : virtual void onSetConstants(video::IMaterialRendererServices *services,
831 : bool is_highlevel)
832 : {
833 1426078 : if (!is_highlevel)
834 0 : return;
835 :
836 : // Background color
837 1426078 : video::SColor bgcolor = m_sky->getBgColor();
838 1426078 : video::SColorf bgcolorf(bgcolor);
839 : float bgcolorfa[4] = {
840 1426078 : bgcolorf.r,
841 1426078 : bgcolorf.g,
842 1426078 : bgcolorf.b,
843 1426078 : bgcolorf.a,
844 5704312 : };
845 1426078 : services->setPixelShaderConstant("skyBgColor", bgcolorfa, 4);
846 :
847 : // Fog distance
848 1426078 : float fog_distance = 10000 * BS;
849 :
850 1426078 : if (m_fogEnabled && !*m_force_fog_off)
851 1426078 : fog_distance = *m_fog_range;
852 :
853 1426078 : services->setPixelShaderConstant("fogDistance", &fog_distance, 1);
854 :
855 : // Day-night ratio
856 1426078 : u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
857 1426078 : float daynight_ratio_f = (float)daynight_ratio / 1000.0;
858 1426078 : services->setPixelShaderConstant("dayNightRatio", &daynight_ratio_f, 1);
859 :
860 1426078 : u32 animation_timer = porting::getTimeMs() % 100000;
861 1426078 : float animation_timer_f = (float)animation_timer / 100000.0;
862 1426078 : services->setPixelShaderConstant("animationTimer", &animation_timer_f, 1);
863 1426078 : services->setVertexShaderConstant("animationTimer", &animation_timer_f, 1);
864 :
865 1426078 : LocalPlayer *player = m_client->getEnv().getLocalPlayer();
866 1426078 : v3f eye_position = player->getEyePosition();
867 1426078 : services->setPixelShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
868 1426078 : services->setVertexShaderConstant("eyePosition", (irr::f32 *)&eye_position, 3);
869 :
870 1426078 : v3f minimap_yaw_vec = m_client->getMapper()->getYawVec();
871 1426078 : services->setPixelShaderConstant("yawVec", (irr::f32 *)&minimap_yaw_vec, 3);
872 :
873 : // Uniform sampler layers
874 1426078 : int layer0 = 0;
875 1426078 : int layer1 = 1;
876 1426078 : int layer2 = 2;
877 : // before 1.8 there isn't a "integer interface", only float
878 : #if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
879 : services->setPixelShaderConstant("baseTexture" , (irr::f32 *)&layer0, 1);
880 : services->setPixelShaderConstant("normalTexture" , (irr::f32 *)&layer1, 1);
881 : services->setPixelShaderConstant("useNormalmap" , (irr::f32 *)&layer2, 1);
882 : #else
883 1426078 : services->setPixelShaderConstant("baseTexture" , (irr::s32 *)&layer0, 1);
884 1426078 : services->setPixelShaderConstant("normalTexture" , (irr::s32 *)&layer1, 1);
885 1426078 : services->setPixelShaderConstant("useNormalmap" , (irr::s32 *)&layer2, 1);
886 : #endif
887 : }
888 : };
889 :
890 0 : bool nodePlacementPrediction(Client &client,
891 : const ItemDefinition &playeritem_def, v3s16 nodepos, v3s16 neighbourpos)
892 : {
893 0 : std::string prediction = playeritem_def.node_placement_prediction;
894 0 : INodeDefManager *nodedef = client.ndef();
895 0 : ClientMap &map = client.getEnv().getClientMap();
896 0 : MapNode node;
897 : bool is_valid_position;
898 :
899 0 : node = map.getNodeNoEx(nodepos, &is_valid_position);
900 0 : if (!is_valid_position)
901 0 : return false;
902 :
903 0 : if (prediction != "" && !nodedef->get(node).rightclickable) {
904 0 : verbosestream << "Node placement prediction for "
905 0 : << playeritem_def.name << " is "
906 0 : << prediction << std::endl;
907 0 : v3s16 p = neighbourpos;
908 :
909 : // Place inside node itself if buildable_to
910 0 : MapNode n_under = map.getNodeNoEx(nodepos, &is_valid_position);
911 0 : if (is_valid_position)
912 : {
913 0 : if (nodedef->get(n_under).buildable_to)
914 0 : p = nodepos;
915 : else {
916 0 : node = map.getNodeNoEx(p, &is_valid_position);
917 0 : if (is_valid_position &&!nodedef->get(node).buildable_to)
918 0 : return false;
919 : }
920 : }
921 :
922 : // Find id of predicted node
923 : content_t id;
924 0 : bool found = nodedef->getId(prediction, id);
925 :
926 0 : if (!found) {
927 0 : errorstream << "Node placement prediction failed for "
928 0 : << playeritem_def.name << " (places "
929 0 : << prediction
930 0 : << ") - Name not known" << std::endl;
931 0 : return false;
932 : }
933 :
934 : // Predict param2 for facedir and wallmounted nodes
935 0 : u8 param2 = 0;
936 :
937 0 : if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED) {
938 0 : v3s16 dir = nodepos - neighbourpos;
939 :
940 0 : if (abs(dir.Y) > MYMAX(abs(dir.X), abs(dir.Z))) {
941 0 : param2 = dir.Y < 0 ? 1 : 0;
942 0 : } else if (abs(dir.X) > abs(dir.Z)) {
943 0 : param2 = dir.X < 0 ? 3 : 2;
944 : } else {
945 0 : param2 = dir.Z < 0 ? 5 : 4;
946 : }
947 : }
948 :
949 0 : if (nodedef->get(id).param_type_2 == CPT2_FACEDIR) {
950 0 : v3s16 dir = nodepos - floatToInt(client.getEnv().getLocalPlayer()->getPosition(), BS);
951 :
952 0 : if (abs(dir.X) > abs(dir.Z)) {
953 0 : param2 = dir.X < 0 ? 3 : 1;
954 : } else {
955 0 : param2 = dir.Z < 0 ? 2 : 0;
956 : }
957 : }
958 :
959 : assert(param2 <= 5);
960 :
961 : //Check attachment if node is in group attached_node
962 0 : if (((ItemGroupList) nodedef->get(id).groups)["attached_node"] != 0) {
963 : static v3s16 wallmounted_dirs[8] = {
964 : v3s16(0, 1, 0),
965 : v3s16(0, -1, 0),
966 : v3s16(1, 0, 0),
967 : v3s16(-1, 0, 0),
968 : v3s16(0, 0, 1),
969 : v3s16(0, 0, -1),
970 0 : };
971 0 : v3s16 pp;
972 :
973 0 : if (nodedef->get(id).param_type_2 == CPT2_WALLMOUNTED)
974 0 : pp = p + wallmounted_dirs[param2];
975 : else
976 0 : pp = p + v3s16(0, -1, 0);
977 :
978 0 : if (!nodedef->get(map.getNodeNoEx(pp)).walkable)
979 0 : return false;
980 : }
981 :
982 : // Add node to client map
983 0 : MapNode n(id, 0, param2);
984 :
985 : try {
986 0 : LocalPlayer *player = client.getEnv().getLocalPlayer();
987 :
988 : // Dont place node when player would be inside new node
989 : // NOTE: This is to be eventually implemented by a mod as client-side Lua
990 0 : if (!nodedef->get(n).walkable ||
991 0 : g_settings->getBool("enable_build_where_you_stand") ||
992 0 : (client.checkPrivilege("noclip") && g_settings->getBool("noclip")) ||
993 0 : (nodedef->get(n).walkable &&
994 0 : neighbourpos != player->getStandingNodePos() + v3s16(0, 1, 0) &&
995 0 : neighbourpos != player->getStandingNodePos() + v3s16(0, 2, 0))) {
996 :
997 : // This triggers the required mesh update too
998 0 : client.addNode(p, n);
999 0 : return true;
1000 : }
1001 0 : } catch (InvalidPositionException &e) {
1002 0 : errorstream << "Node placement prediction failed for "
1003 0 : << playeritem_def.name << " (places "
1004 0 : << prediction
1005 0 : << ") - Position not loaded" << std::endl;
1006 : }
1007 : }
1008 :
1009 0 : return false;
1010 : }
1011 :
1012 1 : static inline void create_formspec_menu(GUIFormSpecMenu **cur_formspec,
1013 : InventoryManager *invmgr, IGameDef *gamedef,
1014 : IWritableTextureSource *tsrc, IrrlichtDevice *device,
1015 : IFormSource *fs_src, TextDest *txt_dest, Client *client)
1016 : {
1017 :
1018 1 : if (*cur_formspec == 0) {
1019 : *cur_formspec = new GUIFormSpecMenu(device, guiroot, -1, &g_menumgr,
1020 1 : invmgr, gamedef, tsrc, fs_src, txt_dest, client);
1021 1 : (*cur_formspec)->doPause = false;
1022 :
1023 : /*
1024 : Caution: do not call (*cur_formspec)->drop() here --
1025 : the reference might outlive the menu, so we will
1026 : periodically check if *cur_formspec is the only
1027 : remaining reference (i.e. the menu was removed)
1028 : and delete it in that case.
1029 : */
1030 :
1031 : } else {
1032 0 : (*cur_formspec)->setFormSource(fs_src);
1033 0 : (*cur_formspec)->setTextDest(txt_dest);
1034 : }
1035 1 : }
1036 :
1037 : #ifdef __ANDROID__
1038 : #define SIZE_TAG "size[11,5.5]"
1039 : #else
1040 : #define SIZE_TAG "size[11,5.5,true]" // Fixed size on desktop
1041 : #endif
1042 :
1043 0 : static void show_chat_menu(GUIFormSpecMenu **cur_formspec,
1044 : InventoryManager *invmgr, IGameDef *gamedef,
1045 : IWritableTextureSource *tsrc, IrrlichtDevice *device,
1046 : Client *client, std::string text)
1047 : {
1048 : std::string formspec =
1049 : FORMSPEC_VERSION_STRING
1050 : SIZE_TAG
1051 0 : "field[3,2.35;6,0.5;f_text;;" + text + "]"
1052 0 : "button_exit[4,3;3,0.5;btn_send;" + wide_to_narrow(wstrgettext("Proceed")) + "]"
1053 : ;
1054 :
1055 : /* Create menu */
1056 : /* Note: FormspecFormSource and LocalFormspecHandler
1057 : * are deleted by guiFormSpecMenu */
1058 0 : FormspecFormSource *fs_src = new FormspecFormSource(formspec);
1059 0 : LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_CHAT_MENU", client);
1060 :
1061 0 : create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL);
1062 0 : }
1063 :
1064 0 : static void show_deathscreen(GUIFormSpecMenu **cur_formspec,
1065 : InventoryManager *invmgr, IGameDef *gamedef,
1066 : IWritableTextureSource *tsrc, IrrlichtDevice *device, Client *client)
1067 : {
1068 : std::string formspec =
1069 0 : std::string(FORMSPEC_VERSION_STRING) +
1070 : SIZE_TAG
1071 : "bgcolor[#320000b4;true]"
1072 0 : "label[4.85,1.35;" + gettext("You died.") + "]"
1073 0 : "button_exit[4,3;3,0.5;btn_respawn;" + gettext("Respawn") + "]"
1074 : ;
1075 :
1076 : /* Create menu */
1077 : /* Note: FormspecFormSource and LocalFormspecHandler
1078 : * are deleted by guiFormSpecMenu */
1079 0 : FormspecFormSource *fs_src = new FormspecFormSource(formspec);
1080 0 : LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_DEATH_SCREEN", client);
1081 :
1082 0 : create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL);
1083 0 : }
1084 :
1085 : /******************************************************************************/
1086 1 : static void show_pause_menu(GUIFormSpecMenu **cur_formspec,
1087 : InventoryManager *invmgr, IGameDef *gamedef,
1088 : IWritableTextureSource *tsrc, IrrlichtDevice *device,
1089 : bool singleplayermode)
1090 : {
1091 : #ifdef __ANDROID__
1092 : std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n"
1093 : "No menu visible:\n"
1094 : "- single tap: button activate\n"
1095 : "- double tap: place/use\n"
1096 : "- slide finger: look around\n"
1097 : "Menu/Inventory visible:\n"
1098 : "- double tap (outside):\n"
1099 : " -->close\n"
1100 : "- touch stack, touch slot:\n"
1101 : " --> move stack\n"
1102 : "- touch&drag, tap 2nd finger\n"
1103 : " --> place single item to slot\n"
1104 : ));
1105 : #else
1106 3 : std::string control_text = wide_to_narrow(wstrgettext("Default Controls:\n"
1107 : "- WASD: move\n"
1108 : "- Space: jump/climb\n"
1109 : "- Shift: sneak/go down\n"
1110 : "- Q: drop item\n"
1111 : "- I: inventory\n"
1112 : "- Mouse: turn/look\n"
1113 : "- Mouse left: dig/punch\n"
1114 : "- Mouse right: place/use\n"
1115 : "- Mouse wheel: select item\n"
1116 : "- T: chat\n"
1117 3 : ));
1118 : #endif
1119 :
1120 1 : float ypos = singleplayermode ? 0.5 : 0.1;
1121 2 : std::ostringstream os;
1122 :
1123 1 : os << FORMSPEC_VERSION_STRING << SIZE_TAG
1124 2 : << "button_exit[4," << (ypos++) << ";3,0.5;btn_continue;"
1125 2 : << wide_to_narrow(wstrgettext("Continue")) << "]";
1126 :
1127 1 : if (!singleplayermode) {
1128 1 : os << "button_exit[4," << (ypos++) << ";3,0.5;btn_change_password;"
1129 2 : << wide_to_narrow(wstrgettext("Change Password")) << "]";
1130 : }
1131 :
1132 : #ifndef __ANDROID__
1133 1 : os << "button_exit[4," << (ypos++) << ";3,0.5;btn_sound;"
1134 2 : << wide_to_narrow(wstrgettext("Sound Volume")) << "]";
1135 1 : os << "button_exit[4," << (ypos++) << ";3,0.5;btn_key_config;"
1136 2 : << wide_to_narrow(wstrgettext("Change Keys")) << "]";
1137 : #endif
1138 1 : os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_menu;"
1139 2 : << wide_to_narrow(wstrgettext("Exit to Menu")) << "]";
1140 1 : os << "button_exit[4," << (ypos++) << ";3,0.5;btn_exit_os;"
1141 3 : << wide_to_narrow(wstrgettext("Exit to OS")) << "]"
1142 1 : << "textarea[7.5,0.25;3.9,6.25;;" << control_text << ";]"
1143 1 : << "textarea[0.4,0.25;3.5,6;;" << PROJECT_NAME_C "\n"
1144 2 : << g_build_info << "\n"
1145 3 : << "path_user = " << wrap_rows(porting::path_user, 20)
1146 1 : << "\n;]";
1147 :
1148 : /* Create menu */
1149 : /* Note: FormspecFormSource and LocalFormspecHandler *
1150 : * are deleted by guiFormSpecMenu */
1151 1 : FormspecFormSource *fs_src = new FormspecFormSource(os.str());
1152 1 : LocalFormspecHandler *txt_dst = new LocalFormspecHandler("MT_PAUSE_MENU");
1153 :
1154 1 : create_formspec_menu(cur_formspec, invmgr, gamedef, tsrc, device, fs_src, txt_dst, NULL);
1155 2 : std::string con("btn_continue");
1156 1 : (*cur_formspec)->setFocus(con);
1157 1 : (*cur_formspec)->doPause = true;
1158 1 : }
1159 :
1160 : /******************************************************************************/
1161 1166 : static void updateChat(Client &client, f32 dtime, bool show_debug,
1162 : const v2u32 &screensize, bool show_chat, u32 show_profiler,
1163 : ChatBackend &chat_backend, gui::IGUIStaticText *guitext_chat)
1164 : {
1165 : // Add chat log output for errors to be shown in chat
1166 1166 : static LogOutputBuffer chat_log_error_buf(LMT_ERROR);
1167 :
1168 : // Get new messages from error log buffer
1169 1166 : while (!chat_log_error_buf.empty()) {
1170 0 : chat_backend.addMessage(L"", narrow_to_wide(chat_log_error_buf.get()));
1171 : }
1172 :
1173 : // Get new messages from client
1174 2332 : std::wstring message;
1175 :
1176 1170 : while (client.getChatMessage(message)) {
1177 2 : chat_backend.addUnparsedMessage(message);
1178 : }
1179 :
1180 : // Remove old messages
1181 1166 : chat_backend.step(dtime);
1182 :
1183 : // Display all messages in a static text element
1184 1166 : unsigned int recent_chat_count = chat_backend.getRecentBuffer().getLineCount();
1185 2332 : std::wstring recent_chat = chat_backend.getRecentChat();
1186 1166 : unsigned int line_height = g_fontengine->getLineHeight();
1187 :
1188 1166 : guitext_chat->setText(recent_chat.c_str());
1189 :
1190 : // Update gui element size and position
1191 1166 : s32 chat_y = 5 + line_height;
1192 :
1193 1166 : if (show_debug)
1194 0 : chat_y += line_height;
1195 :
1196 : // first pass to calculate height of text to be set
1197 2332 : s32 width = std::min(g_fontengine->getTextWidth(recent_chat) + 10,
1198 3498 : porting::getWindowSize().X - 20);
1199 1166 : core::rect<s32> rect(10, chat_y, width, chat_y + porting::getWindowSize().Y);
1200 1166 : guitext_chat->setRelativePosition(rect);
1201 :
1202 : //now use real height of text and adjust rect according to this size
1203 2332 : rect = core::rect<s32>(10, chat_y, width,
1204 2332 : chat_y + guitext_chat->getTextHeight());
1205 :
1206 :
1207 1166 : guitext_chat->setRelativePosition(rect);
1208 : // Don't show chat if disabled or empty or profiler is enabled
1209 1166 : guitext_chat->setVisible(
1210 2332 : show_chat && recent_chat_count != 0 && !show_profiler);
1211 1166 : }
1212 :
1213 :
1214 : /****************************************************************************
1215 : Fast key cache for main game loop
1216 : ****************************************************************************/
1217 :
1218 : /* This is faster than using getKeySetting with the tradeoff that functions
1219 : * using it must make sure that it's initialised before using it and there is
1220 : * no error handling (for example bounds checking). This is really intended for
1221 : * use only in the main running loop of the client (the_game()) where the faster
1222 : * (up to 10x faster) key lookup is an asset. Other parts of the codebase
1223 : * (e.g. formspecs) should continue using getKeySetting().
1224 : */
1225 1 : struct KeyCache {
1226 :
1227 1 : KeyCache() { populate(); }
1228 :
1229 : enum {
1230 : // Player movement
1231 : KEYMAP_ID_FORWARD,
1232 : KEYMAP_ID_BACKWARD,
1233 : KEYMAP_ID_LEFT,
1234 : KEYMAP_ID_RIGHT,
1235 : KEYMAP_ID_JUMP,
1236 : KEYMAP_ID_SPECIAL1,
1237 : KEYMAP_ID_SNEAK,
1238 :
1239 : // Other
1240 : KEYMAP_ID_DROP,
1241 : KEYMAP_ID_INVENTORY,
1242 : KEYMAP_ID_CHAT,
1243 : KEYMAP_ID_CMD,
1244 : KEYMAP_ID_CONSOLE,
1245 : KEYMAP_ID_MINIMAP,
1246 : KEYMAP_ID_FREEMOVE,
1247 : KEYMAP_ID_FASTMOVE,
1248 : KEYMAP_ID_NOCLIP,
1249 : KEYMAP_ID_CINEMATIC,
1250 : KEYMAP_ID_SCREENSHOT,
1251 : KEYMAP_ID_TOGGLE_HUD,
1252 : KEYMAP_ID_TOGGLE_CHAT,
1253 : KEYMAP_ID_TOGGLE_FORCE_FOG_OFF,
1254 : KEYMAP_ID_TOGGLE_UPDATE_CAMERA,
1255 : KEYMAP_ID_TOGGLE_DEBUG,
1256 : KEYMAP_ID_TOGGLE_PROFILER,
1257 : KEYMAP_ID_CAMERA_MODE,
1258 : KEYMAP_ID_INCREASE_VIEWING_RANGE,
1259 : KEYMAP_ID_DECREASE_VIEWING_RANGE,
1260 : KEYMAP_ID_RANGESELECT,
1261 :
1262 : KEYMAP_ID_QUICKTUNE_NEXT,
1263 : KEYMAP_ID_QUICKTUNE_PREV,
1264 : KEYMAP_ID_QUICKTUNE_INC,
1265 : KEYMAP_ID_QUICKTUNE_DEC,
1266 :
1267 : KEYMAP_ID_DEBUG_STACKS,
1268 :
1269 : // Fake keycode for array size and internal checks
1270 : KEYMAP_INTERNAL_ENUM_COUNT
1271 :
1272 :
1273 : };
1274 :
1275 : void populate();
1276 :
1277 : KeyPress key[KEYMAP_INTERNAL_ENUM_COUNT];
1278 : };
1279 :
1280 1 : void KeyCache::populate()
1281 : {
1282 1 : key[KEYMAP_ID_FORWARD] = getKeySetting("keymap_forward");
1283 1 : key[KEYMAP_ID_BACKWARD] = getKeySetting("keymap_backward");
1284 1 : key[KEYMAP_ID_LEFT] = getKeySetting("keymap_left");
1285 1 : key[KEYMAP_ID_RIGHT] = getKeySetting("keymap_right");
1286 1 : key[KEYMAP_ID_JUMP] = getKeySetting("keymap_jump");
1287 1 : key[KEYMAP_ID_SPECIAL1] = getKeySetting("keymap_special1");
1288 1 : key[KEYMAP_ID_SNEAK] = getKeySetting("keymap_sneak");
1289 :
1290 1 : key[KEYMAP_ID_DROP] = getKeySetting("keymap_drop");
1291 1 : key[KEYMAP_ID_INVENTORY] = getKeySetting("keymap_inventory");
1292 1 : key[KEYMAP_ID_CHAT] = getKeySetting("keymap_chat");
1293 1 : key[KEYMAP_ID_CMD] = getKeySetting("keymap_cmd");
1294 1 : key[KEYMAP_ID_CONSOLE] = getKeySetting("keymap_console");
1295 1 : key[KEYMAP_ID_MINIMAP] = getKeySetting("keymap_minimap");
1296 1 : key[KEYMAP_ID_FREEMOVE] = getKeySetting("keymap_freemove");
1297 1 : key[KEYMAP_ID_FASTMOVE] = getKeySetting("keymap_fastmove");
1298 1 : key[KEYMAP_ID_NOCLIP] = getKeySetting("keymap_noclip");
1299 1 : key[KEYMAP_ID_CINEMATIC] = getKeySetting("keymap_cinematic");
1300 1 : key[KEYMAP_ID_SCREENSHOT] = getKeySetting("keymap_screenshot");
1301 1 : key[KEYMAP_ID_TOGGLE_HUD] = getKeySetting("keymap_toggle_hud");
1302 1 : key[KEYMAP_ID_TOGGLE_CHAT] = getKeySetting("keymap_toggle_chat");
1303 : key[KEYMAP_ID_TOGGLE_FORCE_FOG_OFF]
1304 1 : = getKeySetting("keymap_toggle_force_fog_off");
1305 : key[KEYMAP_ID_TOGGLE_UPDATE_CAMERA]
1306 1 : = getKeySetting("keymap_toggle_update_camera");
1307 : key[KEYMAP_ID_TOGGLE_DEBUG]
1308 1 : = getKeySetting("keymap_toggle_debug");
1309 : key[KEYMAP_ID_TOGGLE_PROFILER]
1310 1 : = getKeySetting("keymap_toggle_profiler");
1311 : key[KEYMAP_ID_CAMERA_MODE]
1312 1 : = getKeySetting("keymap_camera_mode");
1313 : key[KEYMAP_ID_INCREASE_VIEWING_RANGE]
1314 1 : = getKeySetting("keymap_increase_viewing_range_min");
1315 : key[KEYMAP_ID_DECREASE_VIEWING_RANGE]
1316 1 : = getKeySetting("keymap_decrease_viewing_range_min");
1317 : key[KEYMAP_ID_RANGESELECT]
1318 1 : = getKeySetting("keymap_rangeselect");
1319 :
1320 1 : key[KEYMAP_ID_QUICKTUNE_NEXT] = getKeySetting("keymap_quicktune_next");
1321 1 : key[KEYMAP_ID_QUICKTUNE_PREV] = getKeySetting("keymap_quicktune_prev");
1322 1 : key[KEYMAP_ID_QUICKTUNE_INC] = getKeySetting("keymap_quicktune_inc");
1323 1 : key[KEYMAP_ID_QUICKTUNE_DEC] = getKeySetting("keymap_quicktune_dec");
1324 :
1325 1 : key[KEYMAP_ID_DEBUG_STACKS] = getKeySetting("keymap_print_debug_stacks");
1326 1 : }
1327 :
1328 :
1329 : /****************************************************************************
1330 :
1331 : ****************************************************************************/
1332 :
1333 : const float object_hit_delay = 0.2;
1334 :
1335 : struct FpsControl {
1336 : u32 last_time, busy_time, sleep_time;
1337 : };
1338 :
1339 :
1340 : /* The reason the following structs are not anonymous structs within the
1341 : * class is that they are not used by the majority of member functions and
1342 : * many functions that do require objects of thse types do not modify them
1343 : * (so they can be passed as a const qualified parameter)
1344 : */
1345 : struct CameraOrientation {
1346 : f32 camera_yaw; // "right/left"
1347 : f32 camera_pitch; // "up/down"
1348 : };
1349 :
1350 : struct GameRunData {
1351 : u16 dig_index;
1352 : u16 new_playeritem;
1353 : PointedThing pointed_old;
1354 : bool digging;
1355 : bool ldown_for_dig;
1356 : bool left_punch;
1357 : bool update_wielded_item_trigger;
1358 : bool reset_jump_timer;
1359 : float nodig_delay_timer;
1360 : float dig_time;
1361 : float dig_time_complete;
1362 : float repeat_rightclick_timer;
1363 : float object_hit_delay_timer;
1364 : float time_from_last_punch;
1365 : ClientActiveObject *selected_object;
1366 :
1367 : float jump_timer;
1368 : float damage_flash;
1369 : float update_draw_list_timer;
1370 : float statustext_time;
1371 :
1372 : f32 fog_range;
1373 :
1374 : v3f update_draw_list_last_cam_dir;
1375 :
1376 : u32 profiler_current_page;
1377 : u32 profiler_max_page; // Number of pages
1378 :
1379 : float time_of_day;
1380 : float time_of_day_smooth;
1381 : };
1382 :
1383 : struct Jitter {
1384 : f32 max, min, avg, counter, max_sample, min_sample, max_fraction;
1385 : };
1386 :
1387 : struct RunStats {
1388 : u32 drawtime;
1389 : u32 beginscenetime;
1390 : u32 endscenetime;
1391 :
1392 : Jitter dtime_jitter, busy_time_jitter;
1393 : };
1394 :
1395 : /* Flags that can, or may, change during main game loop
1396 : */
1397 : struct VolatileRunFlags {
1398 : bool invert_mouse;
1399 : bool show_chat;
1400 : bool show_hud;
1401 : bool show_minimap;
1402 : bool force_fog_off;
1403 : bool show_debug;
1404 : bool show_profiler_graph;
1405 : bool disable_camera_update;
1406 : bool first_loop_after_window_activation;
1407 : bool camera_offset_changed;
1408 : };
1409 :
1410 :
1411 : /****************************************************************************
1412 : THE GAME
1413 : ****************************************************************************/
1414 :
1415 : /* This is not intended to be a public class. If a public class becomes
1416 : * desirable then it may be better to create another 'wrapper' class that
1417 : * hides most of the stuff in this class (nothing in this class is required
1418 : * by any other file) but exposes the public methods/data only.
1419 : */
1420 : class Game
1421 : {
1422 : public:
1423 : Game();
1424 : ~Game();
1425 :
1426 : bool startup(bool *kill,
1427 : bool random_input,
1428 : InputHandler *input,
1429 : IrrlichtDevice *device,
1430 : const std::string &map_dir,
1431 : const std::string &playername,
1432 : const std::string &password,
1433 : // If address is "", local server is used and address is updated
1434 : std::string *address,
1435 : u16 port,
1436 : std::string &error_message,
1437 : ChatBackend *chat_backend,
1438 : const SubgameSpec &gamespec, // Used for local game
1439 : bool simple_singleplayer_mode);
1440 :
1441 : void run();
1442 : void shutdown();
1443 :
1444 : protected:
1445 :
1446 : void extendedResourceCleanup();
1447 :
1448 : // Basic initialisation
1449 : bool init(const std::string &map_dir, std::string *address,
1450 : u16 port,
1451 : const SubgameSpec &gamespec);
1452 : bool initSound();
1453 : bool createSingleplayerServer(const std::string map_dir,
1454 : const SubgameSpec &gamespec, u16 port, std::string *address);
1455 :
1456 : // Client creation
1457 : bool createClient(const std::string &playername,
1458 : const std::string &password, std::string *address, u16 port);
1459 : bool initGui();
1460 :
1461 : // Client connection
1462 : bool connectToServer(const std::string &playername,
1463 : const std::string &password, std::string *address, u16 port,
1464 : bool *connect_ok, bool *aborted);
1465 : bool getServerContent(bool *aborted);
1466 :
1467 : // Main loop
1468 :
1469 : void updateInteractTimers(GameRunData *runData, f32 dtime);
1470 : bool checkConnection();
1471 : bool handleCallbacks();
1472 : void processQueues();
1473 : void updateProfilers(const GameRunData &runData, const RunStats &stats,
1474 : const FpsControl &draw_times, f32 dtime);
1475 : void addProfilerGraphs(const RunStats &stats, const FpsControl &draw_times,
1476 : f32 dtime);
1477 : void updateStats(RunStats *stats, const FpsControl &draw_times, f32 dtime);
1478 :
1479 : void processUserInput(VolatileRunFlags *flags, GameRunData *runData,
1480 : f32 dtime);
1481 : void processKeyboardInput(VolatileRunFlags *flags,
1482 : float *statustext_time,
1483 : float *jump_timer,
1484 : bool *reset_jump_timer,
1485 : u32 *profiler_current_page,
1486 : u32 profiler_max_page);
1487 : void processItemSelection(u16 *new_playeritem);
1488 :
1489 : void dropSelectedItem();
1490 : void openInventory();
1491 : void openConsole();
1492 : void toggleFreeMove(float *statustext_time);
1493 : void toggleFreeMoveAlt(float *statustext_time, float *jump_timer);
1494 : void toggleFast(float *statustext_time);
1495 : void toggleNoClip(float *statustext_time);
1496 : void toggleCinematic(float *statustext_time);
1497 :
1498 : void toggleChat(float *statustext_time, bool *flag);
1499 : void toggleHud(float *statustext_time, bool *flag);
1500 : void toggleMinimap(float *statustext_time, bool *flag1, bool *flag2,
1501 : bool shift_pressed);
1502 : void toggleFog(float *statustext_time, bool *flag);
1503 : void toggleDebug(float *statustext_time, bool *show_debug,
1504 : bool *show_profiler_graph);
1505 : void toggleUpdateCamera(float *statustext_time, bool *flag);
1506 : void toggleProfiler(float *statustext_time, u32 *profiler_current_page,
1507 : u32 profiler_max_page);
1508 :
1509 : void increaseViewRange(float *statustext_time);
1510 : void decreaseViewRange(float *statustext_time);
1511 : void toggleFullViewRange(float *statustext_time);
1512 :
1513 : void updateCameraDirection(CameraOrientation *cam, VolatileRunFlags *flags);
1514 : void updateCameraOrientation(CameraOrientation *cam,
1515 : const VolatileRunFlags &flags);
1516 : void updatePlayerControl(const CameraOrientation &cam);
1517 : void step(f32 *dtime);
1518 : void processClientEvents(CameraOrientation *cam, float *damage_flash);
1519 : void updateCamera(VolatileRunFlags *flags, u32 busy_time, f32 dtime,
1520 : float time_from_last_punch);
1521 : void updateSound(f32 dtime);
1522 : void processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
1523 : GameRunData *runData, f32 dtime, bool show_hud,
1524 : bool show_debug);
1525 : void handlePointingAtNode(GameRunData *runData,
1526 : const PointedThing &pointed, const ItemDefinition &playeritem_def,
1527 : const ToolCapabilities &playeritem_toolcap, f32 dtime);
1528 : void handlePointingAtObject(GameRunData *runData,
1529 : const PointedThing &pointed, const ItemStack &playeritem,
1530 : const v3f &player_position, bool show_debug);
1531 : void handleDigging(GameRunData *runData, const PointedThing &pointed,
1532 : const v3s16 &nodepos, const ToolCapabilities &playeritem_toolcap,
1533 : f32 dtime);
1534 : void updateFrame(std::vector<aabb3f> &highlight_boxes, ProfilerGraph *graph,
1535 : RunStats *stats, GameRunData *runData,
1536 : f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam);
1537 : void updateGui(float *statustext_time, const RunStats &stats,
1538 : const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags,
1539 : const CameraOrientation &cam);
1540 : void updateProfilerGraphs(ProfilerGraph *graph);
1541 :
1542 : // Misc
1543 : void limitFps(FpsControl *fps_timings, f32 *dtime);
1544 :
1545 : void showOverlayMessage(const wchar_t *msg, float dtime, int percent,
1546 : bool draw_clouds = true);
1547 :
1548 : private:
1549 : InputHandler *input;
1550 :
1551 : Client *client;
1552 : Server *server;
1553 :
1554 : IWritableTextureSource *texture_src;
1555 : IWritableShaderSource *shader_src;
1556 :
1557 : // When created, these will be filled with data received from the server
1558 : IWritableItemDefManager *itemdef_manager;
1559 : IWritableNodeDefManager *nodedef_manager;
1560 :
1561 : GameOnDemandSoundFetcher soundfetcher; // useful when testing
1562 : ISoundManager *sound;
1563 : bool sound_is_dummy;
1564 : SoundMaker *soundmaker;
1565 :
1566 : ChatBackend *chat_backend;
1567 :
1568 : GUIFormSpecMenu *current_formspec;
1569 :
1570 : EventManager *eventmgr;
1571 : QuicktuneShortcutter *quicktune;
1572 :
1573 : GUIChatConsole *gui_chat_console; // Free using ->Drop()
1574 : MapDrawControl *draw_control;
1575 : Camera *camera;
1576 : Clouds *clouds; // Free using ->Drop()
1577 : Sky *sky; // Free using ->Drop()
1578 : Inventory *local_inventory;
1579 : Hud *hud;
1580 : Mapper *mapper;
1581 :
1582 : /* 'cache'
1583 : This class does take ownership/responsibily for cleaning up etc of any of
1584 : these items (e.g. device)
1585 : */
1586 : IrrlichtDevice *device;
1587 : video::IVideoDriver *driver;
1588 : scene::ISceneManager *smgr;
1589 : bool *kill;
1590 : std::string *error_message;
1591 : IGameDef *gamedef; // Convenience (same as *client)
1592 : scene::ISceneNode *skybox;
1593 :
1594 : bool random_input;
1595 : bool simple_singleplayer_mode;
1596 : /* End 'cache' */
1597 :
1598 : /* Pre-calculated values
1599 : */
1600 : int crack_animation_length;
1601 :
1602 : /* GUI stuff
1603 : */
1604 : gui::IGUIStaticText *guitext; // First line of debug text
1605 : gui::IGUIStaticText *guitext2; // Second line of debug text
1606 : gui::IGUIStaticText *guitext_info; // At the middle of the screen
1607 : gui::IGUIStaticText *guitext_status;
1608 : gui::IGUIStaticText *guitext_chat; // Chat text
1609 : gui::IGUIStaticText *guitext_profiler; // Profiler text
1610 :
1611 : std::wstring infotext;
1612 : std::wstring statustext;
1613 :
1614 : KeyCache keycache;
1615 :
1616 : IntervalLimiter profiler_interval;
1617 :
1618 : /* TODO: Add a callback function so these can be updated when a setting
1619 : * changes. At this point in time it doesn't matter (e.g. /set
1620 : * is documented to change server settings only)
1621 : *
1622 : * TODO: Local caching of settings is not optimal and should at some stage
1623 : * be updated to use a global settings object for getting thse values
1624 : * (as opposed to the this local caching). This can be addressed in
1625 : * a later release.
1626 : */
1627 : bool m_cache_doubletap_jump;
1628 : bool m_cache_enable_node_highlighting;
1629 : bool m_cache_enable_clouds;
1630 : bool m_cache_enable_particles;
1631 : bool m_cache_enable_fog;
1632 : f32 m_cache_mouse_sensitivity;
1633 : f32 m_repeat_right_click_time;
1634 :
1635 : #ifdef __ANDROID__
1636 : bool m_cache_hold_aux1;
1637 : #endif
1638 :
1639 : };
1640 :
1641 1 : Game::Game() :
1642 : client(NULL),
1643 : server(NULL),
1644 : texture_src(NULL),
1645 : shader_src(NULL),
1646 : itemdef_manager(NULL),
1647 : nodedef_manager(NULL),
1648 : sound(NULL),
1649 : sound_is_dummy(false),
1650 : soundmaker(NULL),
1651 : chat_backend(NULL),
1652 : current_formspec(NULL),
1653 : eventmgr(NULL),
1654 : quicktune(NULL),
1655 : gui_chat_console(NULL),
1656 : draw_control(NULL),
1657 : camera(NULL),
1658 : clouds(NULL),
1659 : sky(NULL),
1660 : local_inventory(NULL),
1661 : hud(NULL),
1662 1 : mapper(NULL)
1663 : {
1664 1 : m_cache_doubletap_jump = g_settings->getBool("doubletap_jump");
1665 1 : m_cache_enable_node_highlighting = g_settings->getBool("enable_node_highlighting");
1666 1 : m_cache_enable_clouds = g_settings->getBool("enable_clouds");
1667 1 : m_cache_enable_particles = g_settings->getBool("enable_particles");
1668 1 : m_cache_enable_fog = g_settings->getBool("enable_fog");
1669 1 : m_cache_mouse_sensitivity = g_settings->getFloat("mouse_sensitivity");
1670 1 : m_repeat_right_click_time = g_settings->getFloat("repeat_rightclick_time");
1671 :
1672 1 : m_cache_mouse_sensitivity = rangelim(m_cache_mouse_sensitivity, 0.001, 100.0);
1673 :
1674 : #ifdef __ANDROID__
1675 : m_cache_hold_aux1 = false; // This is initialised properly later
1676 : #endif
1677 :
1678 1 : }
1679 :
1680 :
1681 :
1682 : /****************************************************************************
1683 : MinetestApp Public
1684 : ****************************************************************************/
1685 :
1686 2 : Game::~Game()
1687 : {
1688 1 : delete client;
1689 1 : delete soundmaker;
1690 1 : if (!sound_is_dummy)
1691 1 : delete sound;
1692 :
1693 1 : delete server; // deleted first to stop all server threads
1694 :
1695 1 : delete hud;
1696 1 : delete local_inventory;
1697 1 : delete camera;
1698 1 : delete quicktune;
1699 1 : delete eventmgr;
1700 1 : delete texture_src;
1701 1 : delete shader_src;
1702 1 : delete nodedef_manager;
1703 1 : delete itemdef_manager;
1704 1 : delete draw_control;
1705 :
1706 1 : extendedResourceCleanup();
1707 1 : }
1708 :
1709 1 : bool Game::startup(bool *kill,
1710 : bool random_input,
1711 : InputHandler *input,
1712 : IrrlichtDevice *device,
1713 : const std::string &map_dir,
1714 : const std::string &playername,
1715 : const std::string &password,
1716 : std::string *address, // can change if simple_singleplayer_mode
1717 : u16 port,
1718 : std::string &error_message,
1719 : ChatBackend *chat_backend,
1720 : const SubgameSpec &gamespec,
1721 : bool simple_singleplayer_mode)
1722 : {
1723 : // "cache"
1724 1 : this->device = device;
1725 1 : this->kill = kill;
1726 1 : this->error_message = &error_message;
1727 1 : this->random_input = random_input;
1728 1 : this->input = input;
1729 1 : this->chat_backend = chat_backend;
1730 1 : this->simple_singleplayer_mode = simple_singleplayer_mode;
1731 :
1732 1 : driver = device->getVideoDriver();
1733 1 : smgr = device->getSceneManager();
1734 :
1735 1 : smgr->getParameters()->setAttribute(scene::OBJ_LOADER_IGNORE_MATERIAL_FILES, true);
1736 :
1737 1 : if (!init(map_dir, address, port, gamespec))
1738 0 : return false;
1739 :
1740 1 : if (!createClient(playername, password, address, port))
1741 0 : return false;
1742 :
1743 1 : return true;
1744 : }
1745 :
1746 :
1747 1 : void Game::run()
1748 : {
1749 2 : ProfilerGraph graph;
1750 1 : RunStats stats = { 0 };
1751 1 : CameraOrientation cam_view_target = { 0 };
1752 1 : CameraOrientation cam_view = { 0 };
1753 1 : GameRunData runData = { 0 };
1754 1 : FpsControl draw_times = { 0 };
1755 1 : VolatileRunFlags flags = { 0 };
1756 : f32 dtime; // in seconds
1757 :
1758 1 : runData.time_from_last_punch = 10.0;
1759 1 : runData.profiler_max_page = 3;
1760 1 : runData.update_wielded_item_trigger = true;
1761 :
1762 1 : flags.show_chat = true;
1763 1 : flags.show_hud = true;
1764 1 : flags.show_minimap = g_settings->getBool("enable_minimap");
1765 1 : flags.show_debug = g_settings->getBool("show_debug");
1766 1 : flags.invert_mouse = g_settings->getBool("invert_mouse");
1767 1 : flags.first_loop_after_window_activation = true;
1768 :
1769 : /* Clear the profiler */
1770 2 : Profiler::GraphValues dummyvalues;
1771 1 : g_profiler->graphGet(dummyvalues);
1772 :
1773 1 : draw_times.last_time = device->getTimer()->getTime();
1774 :
1775 3 : shader_src->addGlobalConstantSetter(new GameGlobalShaderConstantSetter(
1776 : sky,
1777 : &flags.force_fog_off,
1778 : &runData.fog_range,
1779 3 : client));
1780 :
1781 2 : std::vector<aabb3f> highlight_boxes;
1782 :
1783 1 : set_light_table(g_settings->getFloat("display_gamma"));
1784 :
1785 : #ifdef __ANDROID__
1786 : m_cache_hold_aux1 = g_settings->getBool("fast_move")
1787 : && client->checkPrivilege("fast");
1788 : #endif
1789 :
1790 2333 : while (device->run() && !(*kill || g_gamecallback->shutdown_requested)) {
1791 :
1792 : /* Must be called immediately after a device->run() call because it
1793 : * uses device->getTimer()->getTime()
1794 : */
1795 1166 : limitFps(&draw_times, &dtime);
1796 :
1797 1166 : updateStats(&stats, draw_times, dtime);
1798 1166 : updateInteractTimers(&runData, dtime);
1799 :
1800 1166 : if (!checkConnection())
1801 0 : break;
1802 1166 : if (!handleCallbacks())
1803 0 : break;
1804 :
1805 1166 : processQueues();
1806 :
1807 1166 : infotext = L"";
1808 1166 : hud->resizeHotbar();
1809 :
1810 1166 : updateProfilers(runData, stats, draw_times, dtime);
1811 1166 : processUserInput(&flags, &runData, dtime);
1812 : // Update camera before player movement to avoid camera lag of one frame
1813 1166 : updateCameraDirection(&cam_view_target, &flags);
1814 1166 : float cam_smoothing = 0;
1815 1166 : if (g_settings->getBool("cinematic"))
1816 0 : cam_smoothing = 1 - g_settings->getFloat("cinematic_camera_smoothing");
1817 : else
1818 1166 : cam_smoothing = 1 - g_settings->getFloat("camera_smoothing");
1819 1166 : cam_smoothing = rangelim(cam_smoothing, 0.01f, 1.0f);
1820 2332 : cam_view.camera_yaw += (cam_view_target.camera_yaw -
1821 3498 : cam_view.camera_yaw) * cam_smoothing;
1822 2332 : cam_view.camera_pitch += (cam_view_target.camera_pitch -
1823 3498 : cam_view.camera_pitch) * cam_smoothing;
1824 1166 : updatePlayerControl(cam_view);
1825 1166 : step(&dtime);
1826 1166 : processClientEvents(&cam_view_target, &runData.damage_flash);
1827 2332 : updateCamera(&flags, draw_times.busy_time, dtime,
1828 2332 : runData.time_from_last_punch);
1829 1166 : updateSound(dtime);
1830 2332 : processPlayerInteraction(highlight_boxes, &runData, dtime,
1831 3498 : flags.show_hud, flags.show_debug);
1832 : updateFrame(highlight_boxes, &graph, &stats, &runData, dtime,
1833 1166 : flags, cam_view);
1834 1166 : updateProfilerGraphs(&graph);
1835 : }
1836 1 : }
1837 :
1838 :
1839 1 : void Game::shutdown()
1840 : {
1841 1 : showOverlayMessage(wgettext("Shutting down..."), 0, 0, false);
1842 :
1843 1 : if (clouds)
1844 1 : clouds->drop();
1845 :
1846 1 : if (gui_chat_console)
1847 1 : gui_chat_console->drop();
1848 :
1849 1 : if (sky)
1850 1 : sky->drop();
1851 :
1852 : /* cleanup menus */
1853 1 : while (g_menumgr.menuCount() > 0) {
1854 0 : g_menumgr.m_stack.front()->setVisible(false);
1855 0 : g_menumgr.deletingMenu(g_menumgr.m_stack.front());
1856 : }
1857 :
1858 1 : if (current_formspec) {
1859 1 : current_formspec->drop();
1860 1 : current_formspec = NULL;
1861 : }
1862 :
1863 1 : chat_backend->addMessage(L"", L"# Disconnected.");
1864 1 : chat_backend->addMessage(L"", L"");
1865 :
1866 1 : if (client) {
1867 1 : client->Stop();
1868 5 : while (!client->isShutdown()) {
1869 : assert(texture_src != NULL);
1870 : assert(shader_src != NULL);
1871 2 : texture_src->processQueue();
1872 2 : shader_src->processQueue();
1873 2 : sleep_ms(100);
1874 : }
1875 : }
1876 1 : }
1877 :
1878 :
1879 : /****************************************************************************/
1880 : /****************************************************************************
1881 : Startup
1882 : ****************************************************************************/
1883 : /****************************************************************************/
1884 :
1885 1 : bool Game::init(
1886 : const std::string &map_dir,
1887 : std::string *address,
1888 : u16 port,
1889 : const SubgameSpec &gamespec)
1890 : {
1891 1 : showOverlayMessage(wgettext("Loading..."), 0, 0);
1892 :
1893 1 : texture_src = createTextureSource(device);
1894 1 : shader_src = createShaderSource(device);
1895 :
1896 1 : itemdef_manager = createItemDefManager();
1897 1 : nodedef_manager = createNodeDefManager();
1898 :
1899 1 : eventmgr = new EventManager();
1900 1 : quicktune = new QuicktuneShortcutter();
1901 :
1902 1 : if (!(texture_src && shader_src && itemdef_manager && nodedef_manager
1903 1 : && eventmgr && quicktune))
1904 0 : return false;
1905 :
1906 1 : if (!initSound())
1907 0 : return false;
1908 :
1909 : // Create a server if not connecting to an existing one
1910 1 : if (*address == "") {
1911 0 : if (!createSingleplayerServer(map_dir, gamespec, port, address))
1912 0 : return false;
1913 : }
1914 :
1915 1 : return true;
1916 : }
1917 :
1918 1 : bool Game::initSound()
1919 : {
1920 : #if USE_SOUND
1921 1 : if (g_settings->getBool("enable_sound")) {
1922 1 : infostream << "Attempting to use OpenAL audio" << std::endl;
1923 1 : sound = createOpenALSoundManager(&soundfetcher);
1924 1 : if (!sound)
1925 0 : infostream << "Failed to initialize OpenAL audio" << std::endl;
1926 : } else
1927 0 : infostream << "Sound disabled." << std::endl;
1928 : #endif
1929 :
1930 1 : if (!sound) {
1931 0 : infostream << "Using dummy audio." << std::endl;
1932 0 : sound = &dummySoundManager;
1933 0 : sound_is_dummy = true;
1934 : }
1935 :
1936 1 : soundmaker = new SoundMaker(sound, nodedef_manager);
1937 1 : if (!soundmaker)
1938 0 : return false;
1939 :
1940 1 : soundmaker->registerReceiver(eventmgr);
1941 :
1942 1 : return true;
1943 : }
1944 :
1945 0 : bool Game::createSingleplayerServer(const std::string map_dir,
1946 : const SubgameSpec &gamespec, u16 port, std::string *address)
1947 : {
1948 0 : showOverlayMessage(wgettext("Creating server..."), 0, 5);
1949 :
1950 0 : std::string bind_str = g_settings->get("bind_address");
1951 0 : Address bind_addr(0, 0, 0, 0, port);
1952 :
1953 0 : if (g_settings->getBool("ipv6_server")) {
1954 0 : bind_addr.setAddress((IPv6AddressBytes *) NULL);
1955 : }
1956 :
1957 : try {
1958 0 : bind_addr.Resolve(bind_str.c_str());
1959 0 : } catch (ResolveError &e) {
1960 0 : infostream << "Resolving bind address \"" << bind_str
1961 0 : << "\" failed: " << e.what()
1962 0 : << " -- Listening on all addresses." << std::endl;
1963 : }
1964 :
1965 0 : if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) {
1966 0 : *error_message = "Unable to listen on " +
1967 0 : bind_addr.serializeString() +
1968 0 : " because IPv6 is disabled";
1969 0 : errorstream << *error_message << std::endl;
1970 0 : return false;
1971 : }
1972 :
1973 0 : server = new Server(map_dir, gamespec, simple_singleplayer_mode,
1974 0 : bind_addr.isIPv6());
1975 :
1976 0 : server->start(bind_addr);
1977 :
1978 0 : return true;
1979 : }
1980 :
1981 1 : bool Game::createClient(const std::string &playername,
1982 : const std::string &password, std::string *address, u16 port)
1983 : {
1984 1 : showOverlayMessage(wgettext("Creating client..."), 0, 10);
1985 :
1986 1 : draw_control = new MapDrawControl;
1987 1 : if (!draw_control)
1988 0 : return false;
1989 :
1990 : bool could_connect, connect_aborted;
1991 :
1992 1 : if (!connectToServer(playername, password, address, port,
1993 : &could_connect, &connect_aborted))
1994 0 : return false;
1995 :
1996 1 : if (!could_connect) {
1997 0 : if (error_message->empty() && !connect_aborted) {
1998 : // Should not happen if error messages are set properly
1999 0 : *error_message = "Connection failed for unknown reason";
2000 0 : errorstream << *error_message << std::endl;
2001 : }
2002 0 : return false;
2003 : }
2004 :
2005 1 : if (!getServerContent(&connect_aborted)) {
2006 0 : if (error_message->empty() && !connect_aborted) {
2007 : // Should not happen if error messages are set properly
2008 0 : *error_message = "Connection failed for unknown reason";
2009 0 : errorstream << *error_message << std::endl;
2010 : }
2011 0 : return false;
2012 : }
2013 :
2014 : // Update cached textures, meshes and materials
2015 1 : client->afterContentReceived(device);
2016 :
2017 : /* Camera
2018 : */
2019 1 : camera = new Camera(smgr, *draw_control, gamedef);
2020 1 : if (!camera || !camera->successfullyCreated(*error_message))
2021 0 : return false;
2022 :
2023 : /* Clouds
2024 : */
2025 1 : if (m_cache_enable_clouds) {
2026 1 : clouds = new Clouds(smgr->getRootSceneNode(), smgr, -1, time(0));
2027 1 : if (!clouds) {
2028 0 : *error_message = "Memory allocation error (clouds)";
2029 0 : errorstream << *error_message << std::endl;
2030 0 : return false;
2031 : }
2032 : }
2033 :
2034 : /* Skybox
2035 : */
2036 1 : sky = new Sky(smgr->getRootSceneNode(), smgr, -1, texture_src);
2037 1 : skybox = NULL; // This is used/set later on in the main run loop
2038 :
2039 1 : local_inventory = new Inventory(itemdef_manager);
2040 :
2041 1 : if (!(sky && local_inventory)) {
2042 0 : *error_message = "Memory allocation error (sky or local inventory)";
2043 0 : errorstream << *error_message << std::endl;
2044 0 : return false;
2045 : }
2046 :
2047 : /* Pre-calculated values
2048 : */
2049 1 : video::ITexture *t = texture_src->getTexture("crack_anylength.png");
2050 1 : if (t) {
2051 1 : v2u32 size = t->getOriginalSize();
2052 1 : crack_animation_length = size.Y / size.X;
2053 : } else {
2054 0 : crack_animation_length = 5;
2055 : }
2056 :
2057 1 : if (!initGui())
2058 0 : return false;
2059 :
2060 : /* Set window caption
2061 : */
2062 2 : std::wstring str = narrow_to_wide(PROJECT_NAME_C);
2063 1 : str += L" [";
2064 1 : str += driver->getName();
2065 1 : str += L"]";
2066 1 : device->setWindowCaption(str.c_str());
2067 :
2068 1 : LocalPlayer *player = client->getEnv().getLocalPlayer();
2069 1 : player->hurt_tilt_timer = 0;
2070 1 : player->hurt_tilt_strength = 0;
2071 :
2072 1 : hud = new Hud(driver, smgr, guienv, gamedef, player, local_inventory);
2073 :
2074 1 : if (!hud) {
2075 0 : *error_message = "Memory error: could not create HUD";
2076 0 : errorstream << *error_message << std::endl;
2077 0 : return false;
2078 : }
2079 :
2080 1 : mapper = client->getMapper();
2081 1 : mapper->setMinimapMode(MINIMAP_MODE_OFF);
2082 :
2083 1 : return true;
2084 : }
2085 :
2086 1 : bool Game::initGui()
2087 : {
2088 : // First line of debug text
2089 4 : guitext = guienv->addStaticText(
2090 2 : narrow_to_wide(PROJECT_NAME_C).c_str(),
2091 : core::rect<s32>(0, 0, 0, 0),
2092 2 : false, false, guiroot);
2093 :
2094 : // Second line of debug text
2095 3 : guitext2 = guienv->addStaticText(
2096 : L"",
2097 : core::rect<s32>(0, 0, 0, 0),
2098 2 : false, false, guiroot);
2099 :
2100 : // At the middle of the screen
2101 : // Object infos are shown in this
2102 3 : guitext_info = guienv->addStaticText(
2103 : L"",
2104 2 : core::rect<s32>(0, 0, 400, g_fontengine->getTextHeight() * 5 + 5) + v2s32(100, 200),
2105 2 : false, true, guiroot);
2106 :
2107 : // Status text (displays info when showing and hiding GUI stuff, etc.)
2108 3 : guitext_status = guienv->addStaticText(
2109 : L"<Status>",
2110 : core::rect<s32>(0, 0, 0, 0),
2111 2 : false, false, guiroot);
2112 1 : guitext_status->setVisible(false);
2113 :
2114 : // Chat text
2115 3 : guitext_chat = guienv->addStaticText(
2116 : L"",
2117 : core::rect<s32>(0, 0, 0, 0),
2118 : //false, false); // Disable word wrap as of now
2119 2 : false, true, guiroot);
2120 : // Remove stale "recent" chat messages from previous connections
2121 1 : chat_backend->clearRecentChat();
2122 :
2123 : // Chat backend and console
2124 1 : gui_chat_console = new GUIChatConsole(guienv, guienv->getRootGUIElement(),
2125 1 : -1, chat_backend, client);
2126 1 : if (!gui_chat_console) {
2127 0 : *error_message = "Could not allocate memory for chat console";
2128 0 : errorstream << *error_message << std::endl;
2129 0 : return false;
2130 : }
2131 :
2132 : // Profiler text (size is updated when text is updated)
2133 3 : guitext_profiler = guienv->addStaticText(
2134 : L"<Profiler>",
2135 : core::rect<s32>(0, 0, 0, 0),
2136 2 : false, false, guiroot);
2137 1 : guitext_profiler->setBackgroundColor(video::SColor(120, 0, 0, 0));
2138 1 : guitext_profiler->setVisible(false);
2139 1 : guitext_profiler->setWordWrap(true);
2140 :
2141 : #ifdef HAVE_TOUCHSCREENGUI
2142 :
2143 : if (g_touchscreengui)
2144 : g_touchscreengui->init(texture_src, porting::getDisplayDensity());
2145 :
2146 : #endif
2147 :
2148 1 : return true;
2149 : }
2150 :
2151 1 : bool Game::connectToServer(const std::string &playername,
2152 : const std::string &password, std::string *address, u16 port,
2153 : bool *connect_ok, bool *aborted)
2154 : {
2155 1 : *connect_ok = false; // Let's not be overly optimistic
2156 1 : *aborted = false;
2157 1 : bool local_server_mode = false;
2158 :
2159 1 : showOverlayMessage(wgettext("Resolving address..."), 0, 15);
2160 :
2161 1 : Address connect_address(0, 0, 0, 0, port);
2162 :
2163 : try {
2164 1 : connect_address.Resolve(address->c_str());
2165 :
2166 1 : if (connect_address.isZero()) { // i.e. INADDR_ANY, IN6ADDR_ANY
2167 : //connect_address.Resolve("localhost");
2168 0 : if (connect_address.isIPv6()) {
2169 0 : IPv6AddressBytes addr_bytes;
2170 0 : addr_bytes.bytes[15] = 1;
2171 0 : connect_address.setAddress(&addr_bytes);
2172 : } else {
2173 0 : connect_address.setAddress(127, 0, 0, 1);
2174 : }
2175 0 : local_server_mode = true;
2176 : }
2177 0 : } catch (ResolveError &e) {
2178 0 : *error_message = std::string("Couldn't resolve address: ") + e.what();
2179 0 : errorstream << *error_message << std::endl;
2180 0 : return false;
2181 : }
2182 :
2183 1 : if (connect_address.isIPv6() && !g_settings->getBool("enable_ipv6")) {
2184 0 : *error_message = "Unable to connect to " +
2185 0 : connect_address.serializeString() +
2186 0 : " because IPv6 is disabled";
2187 0 : errorstream << *error_message << std::endl;
2188 0 : return false;
2189 : }
2190 :
2191 : client = new Client(device,
2192 : playername.c_str(), password,
2193 1 : *draw_control, texture_src, shader_src,
2194 1 : itemdef_manager, nodedef_manager, sound, eventmgr,
2195 3 : connect_address.isIPv6());
2196 :
2197 1 : if (!client)
2198 0 : return false;
2199 :
2200 1 : gamedef = client; // Client acts as our GameDef
2201 :
2202 1 : infostream << "Connecting to server at ";
2203 1 : connect_address.print(&infostream);
2204 1 : infostream << std::endl;
2205 :
2206 1 : client->connect(connect_address, *address,
2207 2 : simple_singleplayer_mode || local_server_mode);
2208 :
2209 : /*
2210 : Wait for server to accept connection
2211 : */
2212 :
2213 : try {
2214 1 : input->clear();
2215 :
2216 1 : FpsControl fps_control = { 0 };
2217 : f32 dtime;
2218 1 : f32 wait_time = 0; // in seconds
2219 :
2220 1 : fps_control.last_time = device->getTimer()->getTime();
2221 :
2222 17 : while (device->run()) {
2223 :
2224 9 : limitFps(&fps_control, &dtime);
2225 :
2226 : // Update client and server
2227 9 : client->step(dtime);
2228 :
2229 9 : if (server != NULL)
2230 0 : server->step(dtime);
2231 :
2232 : // End condition
2233 9 : if (client->getState() == LC_Init) {
2234 1 : *connect_ok = true;
2235 1 : break;
2236 : }
2237 :
2238 : // Break conditions
2239 8 : if (client->accessDenied()) {
2240 0 : *error_message = "Access denied. Reason: "
2241 0 : + client->accessDeniedReason();
2242 0 : errorstream << *error_message << std::endl;
2243 0 : break;
2244 : }
2245 :
2246 8 : if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
2247 0 : *aborted = true;
2248 0 : infostream << "Connect aborted [Escape]" << std::endl;
2249 0 : break;
2250 : }
2251 :
2252 8 : wait_time += dtime;
2253 : // Only time out if we aren't waiting for the server we started
2254 8 : if ((*address != "") && (wait_time > 10)) {
2255 0 : *error_message = "Connection timed out.";
2256 0 : errorstream << *error_message << std::endl;
2257 0 : break;
2258 : }
2259 :
2260 : // Update status
2261 8 : showOverlayMessage(wgettext("Connecting to server..."), dtime, 20);
2262 : }
2263 0 : } catch (con::PeerNotFoundException &e) {
2264 : // TODO: Should something be done here? At least an info/error
2265 : // message?
2266 0 : return false;
2267 : }
2268 :
2269 1 : return true;
2270 : }
2271 :
2272 1 : bool Game::getServerContent(bool *aborted)
2273 : {
2274 1 : input->clear();
2275 :
2276 1 : FpsControl fps_control = { 0 };
2277 : f32 dtime; // in seconds
2278 :
2279 1 : fps_control.last_time = device->getTimer()->getTime();
2280 :
2281 35 : while (device->run()) {
2282 :
2283 18 : limitFps(&fps_control, &dtime);
2284 :
2285 : // Update client and server
2286 18 : client->step(dtime);
2287 :
2288 18 : if (server != NULL)
2289 0 : server->step(dtime);
2290 :
2291 : // End condition
2292 19 : if (client->mediaReceived() && client->itemdefReceived() &&
2293 1 : client->nodedefReceived()) {
2294 1 : break;
2295 : }
2296 :
2297 : // Error conditions
2298 17 : if (!checkConnection())
2299 0 : return false;
2300 :
2301 17 : if (client->getState() < LC_Init) {
2302 0 : *error_message = "Client disconnected";
2303 0 : errorstream << *error_message << std::endl;
2304 0 : return false;
2305 : }
2306 :
2307 17 : if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
2308 0 : *aborted = true;
2309 0 : infostream << "Connect aborted [Escape]" << std::endl;
2310 0 : return false;
2311 : }
2312 :
2313 : // Display status
2314 17 : int progress = 25;
2315 :
2316 17 : if (!client->itemdefReceived()) {
2317 12 : const wchar_t *text = wgettext("Item definitions...");
2318 12 : progress = 25;
2319 12 : draw_load_screen(text, device, guienv, dtime, progress);
2320 12 : delete[] text;
2321 5 : } else if (!client->nodedefReceived()) {
2322 4 : const wchar_t *text = wgettext("Node definitions...");
2323 4 : progress = 30;
2324 4 : draw_load_screen(text, device, guienv, dtime, progress);
2325 4 : delete[] text;
2326 : } else {
2327 2 : std::stringstream message;
2328 1 : message.precision(3);
2329 1 : message << gettext("Media...");
2330 :
2331 2 : if ((USE_CURL == 0) ||
2332 2 : (!g_settings->getBool("enable_remote_media_server"))) {
2333 0 : float cur = client->getCurRate();
2334 0 : std::string cur_unit = gettext("KiB/s");
2335 :
2336 0 : if (cur > 900) {
2337 0 : cur /= 1024.0;
2338 0 : cur_unit = gettext("MiB/s");
2339 : }
2340 :
2341 0 : message << " (" << cur << ' ' << cur_unit << ")";
2342 : }
2343 :
2344 1 : progress = 30 + client->mediaReceiveProgress() * 35 + 0.5;
2345 2 : draw_load_screen(narrow_to_wide(message.str()), device,
2346 2 : guienv, dtime, progress);
2347 : }
2348 : }
2349 :
2350 1 : return true;
2351 : }
2352 :
2353 :
2354 : /****************************************************************************/
2355 : /****************************************************************************
2356 : Run
2357 : ****************************************************************************/
2358 : /****************************************************************************/
2359 :
2360 1166 : inline void Game::updateInteractTimers(GameRunData *runData, f32 dtime)
2361 : {
2362 1166 : if (runData->nodig_delay_timer >= 0)
2363 1 : runData->nodig_delay_timer -= dtime;
2364 :
2365 1166 : if (runData->object_hit_delay_timer >= 0)
2366 1 : runData->object_hit_delay_timer -= dtime;
2367 :
2368 1166 : runData->time_from_last_punch += dtime;
2369 1166 : }
2370 :
2371 :
2372 : /* returns false if game should exit, otherwise true
2373 : */
2374 1183 : inline bool Game::checkConnection()
2375 : {
2376 1183 : if (client->accessDenied()) {
2377 0 : *error_message = "Access denied. Reason: "
2378 0 : + client->accessDeniedReason();
2379 0 : errorstream << *error_message << std::endl;
2380 0 : return false;
2381 : }
2382 :
2383 1183 : return true;
2384 : }
2385 :
2386 :
2387 : /* returns false if game should exit, otherwise true
2388 : */
2389 1166 : inline bool Game::handleCallbacks()
2390 : {
2391 1166 : if (g_gamecallback->disconnect_requested) {
2392 0 : g_gamecallback->disconnect_requested = false;
2393 0 : return false;
2394 : }
2395 :
2396 1166 : if (g_gamecallback->changepassword_requested) {
2397 : (new GUIPasswordChange(guienv, guiroot, -1,
2398 0 : &g_menumgr, client))->drop();
2399 0 : g_gamecallback->changepassword_requested = false;
2400 : }
2401 :
2402 1166 : if (g_gamecallback->changevolume_requested) {
2403 : (new GUIVolumeChange(guienv, guiroot, -1,
2404 0 : &g_menumgr, client))->drop();
2405 0 : g_gamecallback->changevolume_requested = false;
2406 : }
2407 :
2408 1166 : if (g_gamecallback->keyconfig_requested) {
2409 : (new GUIKeyChangeMenu(guienv, guiroot, -1,
2410 0 : &g_menumgr))->drop();
2411 0 : g_gamecallback->keyconfig_requested = false;
2412 : }
2413 :
2414 1166 : if (g_gamecallback->keyconfig_changed) {
2415 0 : keycache.populate(); // update the cache with new settings
2416 0 : g_gamecallback->keyconfig_changed = false;
2417 : }
2418 :
2419 1166 : return true;
2420 : }
2421 :
2422 :
2423 1166 : void Game::processQueues()
2424 : {
2425 1166 : texture_src->processQueue();
2426 1166 : itemdef_manager->processQueue(gamedef);
2427 1166 : shader_src->processQueue();
2428 1166 : }
2429 :
2430 :
2431 1166 : void Game::updateProfilers(const GameRunData &runData, const RunStats &stats,
2432 : const FpsControl &draw_times, f32 dtime)
2433 : {
2434 : float profiler_print_interval =
2435 1166 : g_settings->getFloat("profiler_print_interval");
2436 1166 : bool print_to_log = true;
2437 :
2438 1166 : if (profiler_print_interval == 0) {
2439 1166 : print_to_log = false;
2440 1166 : profiler_print_interval = 5;
2441 : }
2442 :
2443 1166 : if (profiler_interval.step(dtime, profiler_print_interval)) {
2444 8 : if (print_to_log) {
2445 0 : infostream << "Profiler:" << std::endl;
2446 0 : g_profiler->print(infostream);
2447 : }
2448 :
2449 16 : update_profiler_gui(guitext_profiler, g_fontengine,
2450 8 : runData.profiler_current_page, runData.profiler_max_page,
2451 24 : driver->getScreenSize().Height);
2452 :
2453 8 : g_profiler->clear();
2454 : }
2455 :
2456 1166 : addProfilerGraphs(stats, draw_times, dtime);
2457 1166 : }
2458 :
2459 :
2460 1166 : void Game::addProfilerGraphs(const RunStats &stats,
2461 : const FpsControl &draw_times, f32 dtime)
2462 : {
2463 2332 : g_profiler->graphAdd("mainloop_other",
2464 2332 : draw_times.busy_time / 1000.0f - stats.drawtime / 1000.0f);
2465 :
2466 1166 : if (draw_times.sleep_time != 0)
2467 82 : g_profiler->graphAdd("mainloop_sleep", draw_times.sleep_time / 1000.0f);
2468 1166 : g_profiler->graphAdd("mainloop_dtime", dtime);
2469 :
2470 1166 : g_profiler->add("Elapsed time", dtime);
2471 1166 : g_profiler->avg("FPS", 1. / dtime);
2472 1166 : }
2473 :
2474 :
2475 1166 : void Game::updateStats(RunStats *stats, const FpsControl &draw_times,
2476 : f32 dtime)
2477 : {
2478 :
2479 : f32 jitter;
2480 : Jitter *jp;
2481 :
2482 : /* Time average and jitter calculation
2483 : */
2484 1166 : jp = &stats->dtime_jitter;
2485 1166 : jp->avg = jp->avg * 0.96 + dtime * 0.04;
2486 :
2487 1166 : jitter = dtime - jp->avg;
2488 :
2489 1166 : if (jitter > jp->max)
2490 56 : jp->max = jitter;
2491 :
2492 1166 : jp->counter += dtime;
2493 :
2494 1166 : if (jp->counter > 0.0) {
2495 15 : jp->counter -= 3.0;
2496 15 : jp->max_sample = jp->max;
2497 15 : jp->max_fraction = jp->max_sample / (jp->avg + 0.001);
2498 15 : jp->max = 0.0;
2499 : }
2500 :
2501 : /* Busytime average and jitter calculation
2502 : */
2503 1166 : jp = &stats->busy_time_jitter;
2504 1166 : jp->avg = jp->avg + draw_times.busy_time * 0.02;
2505 :
2506 1166 : jitter = draw_times.busy_time - jp->avg;
2507 :
2508 1166 : if (jitter > jp->max)
2509 1 : jp->max = jitter;
2510 1166 : if (jitter < jp->min)
2511 491 : jp->min = jitter;
2512 :
2513 1166 : jp->counter += dtime;
2514 :
2515 1166 : if (jp->counter > 0.0) {
2516 15 : jp->counter -= 3.0;
2517 15 : jp->max_sample = jp->max;
2518 15 : jp->min_sample = jp->min;
2519 15 : jp->max = 0.0;
2520 15 : jp->min = 0.0;
2521 : }
2522 1166 : }
2523 :
2524 :
2525 :
2526 : /****************************************************************************
2527 : Input handling
2528 : ****************************************************************************/
2529 :
2530 1166 : void Game::processUserInput(VolatileRunFlags *flags,
2531 : GameRunData *runData, f32 dtime)
2532 : {
2533 : // Reset input if window not active or some menu is active
2534 2332 : if (device->isWindowActive() == false
2535 1166 : || noMenuActive() == false
2536 2313 : || guienv->hasFocus(gui_chat_console)) {
2537 19 : input->clear();
2538 : }
2539 :
2540 1166 : if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
2541 0 : gui_chat_console->closeConsoleAtOnce();
2542 : }
2543 :
2544 : // Input handler step() (used by the random input generator)
2545 1166 : input->step(dtime);
2546 :
2547 : #ifdef HAVE_TOUCHSCREENGUI
2548 :
2549 : if (g_touchscreengui) {
2550 : g_touchscreengui->step(dtime);
2551 : }
2552 :
2553 : #endif
2554 : #ifdef __ANDROID__
2555 :
2556 : if (current_formspec != 0)
2557 : current_formspec->getAndroidUIInput();
2558 :
2559 : #endif
2560 :
2561 : // Increase timer for double tap of "keymap_jump"
2562 1166 : if (m_cache_doubletap_jump && runData->jump_timer <= 0.2)
2563 0 : runData->jump_timer += dtime;
2564 :
2565 1166 : processKeyboardInput(
2566 : flags,
2567 : &runData->statustext_time,
2568 : &runData->jump_timer,
2569 : &runData->reset_jump_timer,
2570 : &runData->profiler_current_page,
2571 1166 : runData->profiler_max_page);
2572 :
2573 1166 : processItemSelection(&runData->new_playeritem);
2574 1166 : }
2575 :
2576 :
2577 1166 : void Game::processKeyboardInput(VolatileRunFlags *flags,
2578 : float *statustext_time,
2579 : float *jump_timer,
2580 : bool *reset_jump_timer,
2581 : u32 *profiler_current_page,
2582 : u32 profiler_max_page)
2583 : {
2584 :
2585 : //TimeTaker tt("process kybd input", NULL, PRECISION_NANO);
2586 :
2587 1166 : if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DROP])) {
2588 0 : dropSelectedItem();
2589 1166 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INVENTORY])) {
2590 0 : openInventory();
2591 1166 : } else if (input->wasKeyDown(EscapeKey) || input->wasKeyDown(CancelKey)) {
2592 3 : show_pause_menu(¤t_formspec, client, gamedef, texture_src, device,
2593 4 : simple_singleplayer_mode);
2594 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CHAT])) {
2595 0 : show_chat_menu(¤t_formspec, client, gamedef, texture_src, device,
2596 0 : client, "");
2597 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CMD])) {
2598 0 : show_chat_menu(¤t_formspec, client, gamedef, texture_src, device,
2599 0 : client, "/");
2600 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CONSOLE])) {
2601 0 : openConsole();
2602 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FREEMOVE])) {
2603 0 : toggleFreeMove(statustext_time);
2604 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP])) {
2605 0 : toggleFreeMoveAlt(statustext_time, jump_timer);
2606 0 : *reset_jump_timer = true;
2607 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_FASTMOVE])) {
2608 0 : toggleFast(statustext_time);
2609 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_NOCLIP])) {
2610 0 : toggleNoClip(statustext_time);
2611 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CINEMATIC])) {
2612 0 : toggleCinematic(statustext_time);
2613 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_SCREENSHOT])) {
2614 0 : client->makeScreenshot(device);
2615 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_HUD])) {
2616 0 : toggleHud(statustext_time, &flags->show_hud);
2617 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_MINIMAP])) {
2618 0 : toggleMinimap(statustext_time, &flags->show_minimap, &flags->show_hud,
2619 0 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]));
2620 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_CHAT])) {
2621 0 : toggleChat(statustext_time, &flags->show_chat);
2622 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_FORCE_FOG_OFF])) {
2623 0 : toggleFog(statustext_time, &flags->force_fog_off);
2624 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_UPDATE_CAMERA])) {
2625 0 : toggleUpdateCamera(statustext_time, &flags->disable_camera_update);
2626 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_DEBUG])) {
2627 0 : toggleDebug(statustext_time, &flags->show_debug, &flags->show_profiler_graph);
2628 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_TOGGLE_PROFILER])) {
2629 0 : toggleProfiler(statustext_time, profiler_current_page, profiler_max_page);
2630 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_INCREASE_VIEWING_RANGE])) {
2631 0 : increaseViewRange(statustext_time);
2632 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DECREASE_VIEWING_RANGE])) {
2633 0 : decreaseViewRange(statustext_time);
2634 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_RANGESELECT])) {
2635 0 : toggleFullViewRange(statustext_time);
2636 1165 : } else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_NEXT]))
2637 0 : quicktune->next();
2638 1165 : else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_PREV]))
2639 0 : quicktune->prev();
2640 1165 : else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_INC]))
2641 0 : quicktune->inc();
2642 1165 : else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_QUICKTUNE_DEC]))
2643 0 : quicktune->dec();
2644 1165 : else if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_DEBUG_STACKS])) {
2645 : // Print debug stacks
2646 0 : dstream << "-----------------------------------------"
2647 0 : << std::endl;
2648 0 : dstream << DTIME << "Printing debug stacks:" << std::endl;
2649 0 : dstream << "-----------------------------------------"
2650 0 : << std::endl;
2651 0 : debug_stacks_print();
2652 : }
2653 :
2654 1166 : if (!input->isKeyDown(getKeySetting("keymap_jump")) && *reset_jump_timer) {
2655 0 : *reset_jump_timer = false;
2656 0 : *jump_timer = 0.0;
2657 : }
2658 :
2659 : //tt.stop();
2660 :
2661 1166 : if (quicktune->hasMessage()) {
2662 0 : std::string msg = quicktune->getMessage();
2663 0 : statustext = narrow_to_wide(msg);
2664 0 : *statustext_time = 0;
2665 : }
2666 1166 : }
2667 :
2668 :
2669 1166 : void Game::processItemSelection(u16 *new_playeritem)
2670 : {
2671 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
2672 :
2673 : /* Item selection using mouse wheel
2674 : */
2675 1166 : *new_playeritem = client->getPlayerItem();
2676 :
2677 1166 : s32 wheel = input->getMouseWheel();
2678 1166 : u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE - 1,
2679 : player->hud_hotbar_itemcount - 1);
2680 :
2681 1166 : if (wheel < 0)
2682 17 : *new_playeritem = *new_playeritem < max_item ? *new_playeritem + 1 : 0;
2683 1149 : else if (wheel > 0)
2684 3 : *new_playeritem = *new_playeritem > 0 ? *new_playeritem - 1 : max_item;
2685 : // else wheel == 0
2686 :
2687 :
2688 : /* Item selection using keyboard
2689 : */
2690 12826 : for (u16 i = 0; i < 10; i++) {
2691 : static const KeyPress *item_keys[10] = {
2692 : NumberKey + 1, NumberKey + 2, NumberKey + 3, NumberKey + 4,
2693 : NumberKey + 5, NumberKey + 6, NumberKey + 7, NumberKey + 8,
2694 : NumberKey + 9, NumberKey + 0,
2695 : };
2696 :
2697 11660 : if (input->wasKeyDown(*item_keys[i])) {
2698 0 : if (i < PLAYER_INVENTORY_SIZE && i < player->hud_hotbar_itemcount) {
2699 0 : *new_playeritem = i;
2700 0 : infostream << "Selected item: " << new_playeritem << std::endl;
2701 : }
2702 0 : break;
2703 : }
2704 : }
2705 1166 : }
2706 :
2707 :
2708 0 : void Game::dropSelectedItem()
2709 : {
2710 0 : IDropAction *a = new IDropAction();
2711 0 : a->count = 0;
2712 0 : a->from_inv.setCurrentPlayer();
2713 0 : a->from_list = "main";
2714 0 : a->from_i = client->getPlayerItem();
2715 0 : client->inventoryAction(a);
2716 0 : }
2717 :
2718 :
2719 0 : void Game::openInventory()
2720 : {
2721 : /*
2722 : * Don't permit to open inventory is CAO or player doesn't exists.
2723 : * This prevent showing an empty inventory at player load
2724 : */
2725 :
2726 0 : LocalPlayer *player = client->getEnv().getLocalPlayer();
2727 0 : if (player == NULL || player->getCAO() == NULL)
2728 0 : return;
2729 :
2730 0 : infostream << "the_game: " << "Launching inventory" << std::endl;
2731 :
2732 0 : PlayerInventoryFormSource *fs_src = new PlayerInventoryFormSource(client);
2733 0 : TextDest *txt_dst = new TextDestPlayerInventory(client);
2734 :
2735 0 : create_formspec_menu(¤t_formspec, client, gamedef, texture_src,
2736 0 : device, fs_src, txt_dst, client);
2737 :
2738 0 : InventoryLocation inventoryloc;
2739 0 : inventoryloc.setCurrentPlayer();
2740 0 : current_formspec->setFormSpec(fs_src->getForm(), inventoryloc);
2741 : }
2742 :
2743 :
2744 0 : void Game::openConsole()
2745 : {
2746 0 : if (!gui_chat_console->isOpenInhibited()) {
2747 : // Open up to over half of the screen
2748 0 : gui_chat_console->openConsole(0.6);
2749 0 : guienv->setFocus(gui_chat_console);
2750 : }
2751 0 : }
2752 :
2753 :
2754 0 : void Game::toggleFreeMove(float *statustext_time)
2755 : {
2756 : static const wchar_t *msg[] = { L"free_move disabled", L"free_move enabled" };
2757 :
2758 0 : bool free_move = !g_settings->getBool("free_move");
2759 0 : g_settings->set("free_move", bool_to_cstr(free_move));
2760 :
2761 0 : *statustext_time = 0;
2762 0 : statustext = msg[free_move];
2763 0 : if (free_move && !client->checkPrivilege("fly"))
2764 0 : statustext += L" (note: no 'fly' privilege)";
2765 0 : }
2766 :
2767 :
2768 0 : void Game::toggleFreeMoveAlt(float *statustext_time, float *jump_timer)
2769 : {
2770 0 : if (m_cache_doubletap_jump && *jump_timer < 0.2f)
2771 0 : toggleFreeMove(statustext_time);
2772 0 : }
2773 :
2774 :
2775 0 : void Game::toggleFast(float *statustext_time)
2776 : {
2777 : static const wchar_t *msg[] = { L"fast_move disabled", L"fast_move enabled" };
2778 0 : bool fast_move = !g_settings->getBool("fast_move");
2779 0 : g_settings->set("fast_move", bool_to_cstr(fast_move));
2780 :
2781 0 : *statustext_time = 0;
2782 0 : statustext = msg[fast_move];
2783 :
2784 0 : bool has_fast_privs = client->checkPrivilege("fast");
2785 :
2786 0 : if (fast_move && !has_fast_privs)
2787 0 : statustext += L" (note: no 'fast' privilege)";
2788 :
2789 : #ifdef __ANDROID__
2790 : m_cache_hold_aux1 = fast_move && has_fast_privs;
2791 : #endif
2792 0 : }
2793 :
2794 :
2795 0 : void Game::toggleNoClip(float *statustext_time)
2796 : {
2797 : static const wchar_t *msg[] = { L"noclip disabled", L"noclip enabled" };
2798 0 : bool noclip = !g_settings->getBool("noclip");
2799 0 : g_settings->set("noclip", bool_to_cstr(noclip));
2800 :
2801 0 : *statustext_time = 0;
2802 0 : statustext = msg[noclip];
2803 :
2804 0 : if (noclip && !client->checkPrivilege("noclip"))
2805 0 : statustext += L" (note: no 'noclip' privilege)";
2806 0 : }
2807 :
2808 0 : void Game::toggleCinematic(float *statustext_time)
2809 : {
2810 : static const wchar_t *msg[] = { L"cinematic disabled", L"cinematic enabled" };
2811 0 : bool cinematic = !g_settings->getBool("cinematic");
2812 0 : g_settings->set("cinematic", bool_to_cstr(cinematic));
2813 :
2814 0 : *statustext_time = 0;
2815 0 : statustext = msg[cinematic];
2816 0 : }
2817 :
2818 :
2819 0 : void Game::toggleChat(float *statustext_time, bool *flag)
2820 : {
2821 : static const wchar_t *msg[] = { L"Chat hidden", L"Chat shown" };
2822 :
2823 0 : *flag = !*flag;
2824 0 : *statustext_time = 0;
2825 0 : statustext = msg[*flag];
2826 0 : }
2827 :
2828 :
2829 0 : void Game::toggleHud(float *statustext_time, bool *flag)
2830 : {
2831 : static const wchar_t *msg[] = { L"HUD hidden", L"HUD shown" };
2832 :
2833 0 : *flag = !*flag;
2834 0 : *statustext_time = 0;
2835 0 : statustext = msg[*flag];
2836 0 : if (g_settings->getBool("enable_node_highlighting"))
2837 0 : client->setHighlighted(client->getHighlighted(), *flag);
2838 0 : }
2839 :
2840 0 : void Game::toggleMinimap(float *statustext_time, bool *flag, bool *show_hud, bool shift_pressed)
2841 : {
2842 0 : if (*show_hud && g_settings->getBool("enable_minimap")) {
2843 0 : if (shift_pressed) {
2844 0 : mapper->toggleMinimapShape();
2845 0 : return;
2846 : }
2847 0 : MinimapMode mode = mapper->getMinimapMode();
2848 0 : mode = (MinimapMode)((int)(mode) + 1);
2849 0 : *flag = true;
2850 0 : switch (mode) {
2851 : case MINIMAP_MODE_SURFACEx1:
2852 0 : statustext = L"Minimap in surface mode, Zoom x1";
2853 0 : break;
2854 : case MINIMAP_MODE_SURFACEx2:
2855 0 : statustext = L"Minimap in surface mode, Zoom x2";
2856 0 : break;
2857 : case MINIMAP_MODE_SURFACEx4:
2858 0 : statustext = L"Minimap in surface mode, Zoom x4";
2859 0 : break;
2860 : case MINIMAP_MODE_RADARx1:
2861 0 : statustext = L"Minimap in radar mode, Zoom x1";
2862 0 : break;
2863 : case MINIMAP_MODE_RADARx2:
2864 0 : statustext = L"Minimap in radar mode, Zoom x2";
2865 0 : break;
2866 : case MINIMAP_MODE_RADARx4:
2867 0 : statustext = L"Minimap in radar mode, Zoom x4";
2868 0 : break;
2869 : default:
2870 0 : mode = MINIMAP_MODE_OFF;
2871 0 : *flag = false;
2872 0 : statustext = L"Minimap hidden";
2873 : }
2874 0 : *statustext_time = 0;
2875 0 : mapper->setMinimapMode(mode);
2876 : }
2877 : }
2878 :
2879 0 : void Game::toggleFog(float *statustext_time, bool *flag)
2880 : {
2881 : static const wchar_t *msg[] = { L"Fog enabled", L"Fog disabled" };
2882 :
2883 0 : *flag = !*flag;
2884 0 : *statustext_time = 0;
2885 0 : statustext = msg[*flag];
2886 0 : }
2887 :
2888 :
2889 0 : void Game::toggleDebug(float *statustext_time, bool *show_debug,
2890 : bool *show_profiler_graph)
2891 : {
2892 : // Initial / 3x toggle: Chat only
2893 : // 1x toggle: Debug text with chat
2894 : // 2x toggle: Debug text with profiler graph
2895 0 : if (!*show_debug) {
2896 0 : *show_debug = true;
2897 0 : *show_profiler_graph = false;
2898 0 : statustext = L"Debug info shown";
2899 0 : } else if (*show_profiler_graph) {
2900 0 : *show_debug = false;
2901 0 : *show_profiler_graph = false;
2902 0 : statustext = L"Debug info and profiler graph hidden";
2903 : } else {
2904 0 : *show_profiler_graph = true;
2905 0 : statustext = L"Profiler graph shown";
2906 : }
2907 0 : *statustext_time = 0;
2908 0 : }
2909 :
2910 :
2911 0 : void Game::toggleUpdateCamera(float *statustext_time, bool *flag)
2912 : {
2913 : static const wchar_t *msg[] = {
2914 : L"Camera update enabled",
2915 : L"Camera update disabled"
2916 : };
2917 :
2918 0 : *flag = !*flag;
2919 0 : *statustext_time = 0;
2920 0 : statustext = msg[*flag];
2921 0 : }
2922 :
2923 :
2924 0 : void Game::toggleProfiler(float *statustext_time, u32 *profiler_current_page,
2925 : u32 profiler_max_page)
2926 : {
2927 0 : *profiler_current_page = (*profiler_current_page + 1) % (profiler_max_page + 1);
2928 :
2929 : // FIXME: This updates the profiler with incomplete values
2930 0 : update_profiler_gui(guitext_profiler, g_fontengine, *profiler_current_page,
2931 0 : profiler_max_page, driver->getScreenSize().Height);
2932 :
2933 0 : if (*profiler_current_page != 0) {
2934 0 : std::wstringstream sstr;
2935 0 : sstr << "Profiler shown (page " << *profiler_current_page
2936 0 : << " of " << profiler_max_page << ")";
2937 0 : statustext = sstr.str();
2938 : } else {
2939 0 : statustext = L"Profiler hidden";
2940 : }
2941 0 : *statustext_time = 0;
2942 0 : }
2943 :
2944 :
2945 0 : void Game::increaseViewRange(float *statustext_time)
2946 : {
2947 0 : s16 range = g_settings->getS16("viewing_range_nodes_min");
2948 0 : s16 range_new = range + 10;
2949 0 : g_settings->set("viewing_range_nodes_min", itos(range_new));
2950 0 : statustext = narrow_to_wide("Minimum viewing range changed to "
2951 0 : + itos(range_new));
2952 0 : *statustext_time = 0;
2953 0 : }
2954 :
2955 :
2956 0 : void Game::decreaseViewRange(float *statustext_time)
2957 : {
2958 0 : s16 range = g_settings->getS16("viewing_range_nodes_min");
2959 0 : s16 range_new = range - 10;
2960 :
2961 0 : if (range_new < 0)
2962 0 : range_new = range;
2963 :
2964 0 : g_settings->set("viewing_range_nodes_min", itos(range_new));
2965 0 : statustext = narrow_to_wide("Minimum viewing range changed to "
2966 0 : + itos(range_new));
2967 0 : *statustext_time = 0;
2968 0 : }
2969 :
2970 :
2971 0 : void Game::toggleFullViewRange(float *statustext_time)
2972 : {
2973 : static const wchar_t *msg[] = {
2974 : L"Disabled full viewing range",
2975 : L"Enabled full viewing range"
2976 : };
2977 :
2978 0 : draw_control->range_all = !draw_control->range_all;
2979 0 : infostream << msg[draw_control->range_all] << std::endl;
2980 0 : statustext = msg[draw_control->range_all];
2981 0 : *statustext_time = 0;
2982 0 : }
2983 :
2984 :
2985 1166 : void Game::updateCameraDirection(CameraOrientation *cam,
2986 : VolatileRunFlags *flags)
2987 : {
2988 1166 : if ((device->isWindowActive() && noMenuActive()) || random_input) {
2989 :
2990 : #ifndef __ANDROID__
2991 1146 : if (!random_input) {
2992 : // Mac OSX gets upset if this is set every frame
2993 1146 : if (device->getCursorControl()->isVisible())
2994 1 : device->getCursorControl()->setVisible(false);
2995 : }
2996 : #endif
2997 :
2998 1146 : if (flags->first_loop_after_window_activation)
2999 1 : flags->first_loop_after_window_activation = false;
3000 : else
3001 1145 : updateCameraOrientation(cam, *flags);
3002 :
3003 2292 : input->setMousePos((driver->getScreenSize().Width / 2),
3004 2292 : (driver->getScreenSize().Height / 2));
3005 : } else {
3006 :
3007 : #ifndef ANDROID
3008 : // Mac OSX gets upset if this is set every frame
3009 20 : if (device->getCursorControl()->isVisible() == false)
3010 1 : device->getCursorControl()->setVisible(true);
3011 : #endif
3012 :
3013 20 : if (!flags->first_loop_after_window_activation)
3014 1 : flags->first_loop_after_window_activation = true;
3015 :
3016 : }
3017 1166 : }
3018 :
3019 :
3020 1145 : void Game::updateCameraOrientation(CameraOrientation *cam,
3021 : const VolatileRunFlags &flags)
3022 : {
3023 : #ifdef HAVE_TOUCHSCREENGUI
3024 : if (g_touchscreengui) {
3025 : cam->camera_yaw = g_touchscreengui->getYaw();
3026 : cam->camera_pitch = g_touchscreengui->getPitch();
3027 : } else {
3028 : #endif
3029 1145 : s32 dx = input->getMousePos().X - (driver->getScreenSize().Width / 2);
3030 1145 : s32 dy = input->getMousePos().Y - (driver->getScreenSize().Height / 2);
3031 :
3032 2290 : if (flags.invert_mouse
3033 1145 : || camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT) {
3034 0 : dy = -dy;
3035 : }
3036 :
3037 1145 : cam->camera_yaw -= dx * m_cache_mouse_sensitivity;
3038 1145 : cam->camera_pitch += dy * m_cache_mouse_sensitivity;
3039 :
3040 : #ifdef HAVE_TOUCHSCREENGUI
3041 : }
3042 : #endif
3043 :
3044 1145 : cam->camera_pitch = rangelim(cam->camera_pitch, -89.5, 89.5);
3045 1145 : }
3046 :
3047 :
3048 1166 : void Game::updatePlayerControl(const CameraOrientation &cam)
3049 : {
3050 : //TimeTaker tt("update player control", NULL, PRECISION_NANO);
3051 :
3052 : PlayerControl control(
3053 1166 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]),
3054 1166 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]),
3055 1166 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]),
3056 1166 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]),
3057 1166 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]),
3058 1166 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]),
3059 1166 : input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]),
3060 1166 : input->getLeftState(),
3061 1166 : input->getRightState(),
3062 1166 : cam.camera_pitch,
3063 1166 : cam.camera_yaw
3064 10494 : );
3065 :
3066 : u32 keypress_bits =
3067 2332 : ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_FORWARD]) & 0x1) << 0) |
3068 2332 : ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_BACKWARD]) & 0x1) << 1) |
3069 2332 : ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_LEFT]) & 0x1) << 2) |
3070 2332 : ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_RIGHT]) & 0x1) << 3) |
3071 2332 : ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_JUMP]) & 0x1) << 4) |
3072 2332 : ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SPECIAL1]) & 0x1) << 5) |
3073 2332 : ( (u32)(input->isKeyDown(keycache.key[KeyCache::KEYMAP_ID_SNEAK]) & 0x1) << 6) |
3074 1166 : ( (u32)(input->getLeftState() & 0x1) << 7) |
3075 1166 : ( (u32)(input->getRightState() & 0x1) << 8
3076 1166 : );
3077 :
3078 : #ifdef ANDROID
3079 : /* For Android, simulate holding down AUX1 (fast move) if the user has
3080 : * the fast_move setting toggled on. If there is an aux1 key defined for
3081 : * Android then its meaning is inverted (i.e. holding aux1 means walk and
3082 : * not fast)
3083 : */
3084 : if (m_cache_hold_aux1) {
3085 : control.aux1 = control.aux1 ^ true;
3086 : keypress_bits ^= ((u32)(1U << 5));
3087 : }
3088 : #endif
3089 :
3090 1166 : client->setPlayerControl(control);
3091 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3092 1166 : player->keyPressed = keypress_bits;
3093 :
3094 : //tt.stop();
3095 1166 : }
3096 :
3097 :
3098 1166 : inline void Game::step(f32 *dtime)
3099 : {
3100 : bool can_be_and_is_paused =
3101 1166 : (simple_singleplayer_mode && g_menumgr.pausesGame());
3102 :
3103 1166 : if (can_be_and_is_paused) { // This is for a singleplayer server
3104 0 : *dtime = 0; // No time passes
3105 : } else {
3106 1166 : if (server != NULL) {
3107 : //TimeTaker timer("server->step(dtime)");
3108 0 : server->step(*dtime);
3109 : }
3110 :
3111 : //TimeTaker timer("client.step(dtime)");
3112 1166 : client->step(*dtime);
3113 : }
3114 1166 : }
3115 :
3116 :
3117 1166 : void Game::processClientEvents(CameraOrientation *cam, float *damage_flash)
3118 : {
3119 1166 : ClientEvent event = client->getClientEvent();
3120 :
3121 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3122 :
3123 1220 : for ( ; event.type != CE_NONE; event = client->getClientEvent()) {
3124 :
3125 27 : if (event.type == CE_PLAYER_DAMAGE &&
3126 0 : client->getHP() != 0) {
3127 : //u16 damage = event.player_damage.amount;
3128 : //infostream<<"Player damage: "<<damage<<std::endl;
3129 :
3130 0 : *damage_flash += 100.0;
3131 0 : *damage_flash += 8.0 * event.player_damage.amount;
3132 :
3133 0 : player->hurt_tilt_timer = 1.5;
3134 0 : player->hurt_tilt_strength = event.player_damage.amount / 4;
3135 0 : player->hurt_tilt_strength = rangelim(player->hurt_tilt_strength, 1.0, 4.0);
3136 :
3137 0 : MtEvent *e = new SimpleTriggerEvent("PlayerDamage");
3138 0 : gamedef->event()->put(e);
3139 27 : } else if (event.type == CE_PLAYER_FORCE_MOVE) {
3140 1 : cam->camera_yaw = event.player_force_move.yaw;
3141 1 : cam->camera_pitch = event.player_force_move.pitch;
3142 26 : } else if (event.type == CE_DEATHSCREEN) {
3143 0 : show_deathscreen(¤t_formspec, client, gamedef, texture_src,
3144 0 : device, client);
3145 :
3146 0 : chat_backend->addMessage(L"", L"You died.");
3147 :
3148 : /* Handle visualization */
3149 0 : *damage_flash = 0;
3150 0 : player->hurt_tilt_timer = 0;
3151 0 : player->hurt_tilt_strength = 0;
3152 :
3153 26 : } else if (event.type == CE_SHOW_FORMSPEC) {
3154 : FormspecFormSource *fs_src =
3155 0 : new FormspecFormSource(*(event.show_formspec.formspec));
3156 : TextDestPlayerInventory *txt_dst =
3157 0 : new TextDestPlayerInventory(client, *(event.show_formspec.formname));
3158 :
3159 0 : create_formspec_menu(¤t_formspec, client, gamedef,
3160 0 : texture_src, device, fs_src, txt_dst, client);
3161 :
3162 0 : delete(event.show_formspec.formspec);
3163 0 : delete(event.show_formspec.formname);
3164 52 : } else if ((event.type == CE_SPAWN_PARTICLE) ||
3165 43 : (event.type == CE_ADD_PARTICLESPAWNER) ||
3166 17 : (event.type == CE_DELETE_PARTICLESPAWNER)) {
3167 9 : client->getParticleManager()->handleParticleEvent(&event, gamedef,
3168 9 : smgr, player);
3169 17 : } else if (event.type == CE_HUDADD) {
3170 2 : u32 id = event.hudadd.id;
3171 :
3172 2 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3173 2 : HudElement *e = player->getHud(id);
3174 :
3175 2 : if (e != NULL) {
3176 0 : delete event.hudadd.pos;
3177 0 : delete event.hudadd.name;
3178 0 : delete event.hudadd.scale;
3179 0 : delete event.hudadd.text;
3180 0 : delete event.hudadd.align;
3181 0 : delete event.hudadd.offset;
3182 0 : delete event.hudadd.world_pos;
3183 0 : delete event.hudadd.size;
3184 0 : continue;
3185 : }
3186 :
3187 2 : e = new HudElement;
3188 2 : e->type = (HudElementType)event.hudadd.type;
3189 2 : e->pos = *event.hudadd.pos;
3190 2 : e->name = *event.hudadd.name;
3191 2 : e->scale = *event.hudadd.scale;
3192 2 : e->text = *event.hudadd.text;
3193 2 : e->number = event.hudadd.number;
3194 2 : e->item = event.hudadd.item;
3195 2 : e->dir = event.hudadd.dir;
3196 2 : e->align = *event.hudadd.align;
3197 2 : e->offset = *event.hudadd.offset;
3198 2 : e->world_pos = *event.hudadd.world_pos;
3199 2 : e->size = *event.hudadd.size;
3200 :
3201 2 : u32 new_id = player->addHud(e);
3202 : //if this isn't true our huds aren't consistent
3203 2 : sanity_check(new_id == id);
3204 :
3205 2 : delete event.hudadd.pos;
3206 2 : delete event.hudadd.name;
3207 2 : delete event.hudadd.scale;
3208 2 : delete event.hudadd.text;
3209 2 : delete event.hudadd.align;
3210 2 : delete event.hudadd.offset;
3211 2 : delete event.hudadd.world_pos;
3212 2 : delete event.hudadd.size;
3213 15 : } else if (event.type == CE_HUDRM) {
3214 0 : HudElement *e = player->removeHud(event.hudrm.id);
3215 :
3216 0 : if (e != NULL)
3217 0 : delete(e);
3218 15 : } else if (event.type == CE_HUDCHANGE) {
3219 15 : u32 id = event.hudchange.id;
3220 15 : HudElement *e = player->getHud(id);
3221 :
3222 15 : if (e == NULL) {
3223 2 : delete event.hudchange.v3fdata;
3224 2 : delete event.hudchange.v2fdata;
3225 2 : delete event.hudchange.sdata;
3226 2 : delete event.hudchange.v2s32data;
3227 2 : continue;
3228 : }
3229 :
3230 13 : switch (event.hudchange.stat) {
3231 : case HUD_STAT_POS:
3232 0 : e->pos = *event.hudchange.v2fdata;
3233 0 : break;
3234 :
3235 : case HUD_STAT_NAME:
3236 0 : e->name = *event.hudchange.sdata;
3237 0 : break;
3238 :
3239 : case HUD_STAT_SCALE:
3240 0 : e->scale = *event.hudchange.v2fdata;
3241 0 : break;
3242 :
3243 : case HUD_STAT_TEXT:
3244 13 : e->text = *event.hudchange.sdata;
3245 13 : break;
3246 :
3247 : case HUD_STAT_NUMBER:
3248 0 : e->number = event.hudchange.data;
3249 0 : break;
3250 :
3251 : case HUD_STAT_ITEM:
3252 0 : e->item = event.hudchange.data;
3253 0 : break;
3254 :
3255 : case HUD_STAT_DIR:
3256 0 : e->dir = event.hudchange.data;
3257 0 : break;
3258 :
3259 : case HUD_STAT_ALIGN:
3260 0 : e->align = *event.hudchange.v2fdata;
3261 0 : break;
3262 :
3263 : case HUD_STAT_OFFSET:
3264 0 : e->offset = *event.hudchange.v2fdata;
3265 0 : break;
3266 :
3267 : case HUD_STAT_WORLD_POS:
3268 0 : e->world_pos = *event.hudchange.v3fdata;
3269 0 : break;
3270 :
3271 : case HUD_STAT_SIZE:
3272 0 : e->size = *event.hudchange.v2s32data;
3273 0 : break;
3274 : }
3275 :
3276 13 : delete event.hudchange.v3fdata;
3277 13 : delete event.hudchange.v2fdata;
3278 13 : delete event.hudchange.sdata;
3279 13 : delete event.hudchange.v2s32data;
3280 0 : } else if (event.type == CE_SET_SKY) {
3281 0 : sky->setVisible(false);
3282 :
3283 0 : if (skybox) {
3284 0 : skybox->remove();
3285 0 : skybox = NULL;
3286 : }
3287 :
3288 : // Handle according to type
3289 0 : if (*event.set_sky.type == "regular") {
3290 0 : sky->setVisible(true);
3291 0 : } else if (*event.set_sky.type == "skybox" &&
3292 0 : event.set_sky.params->size() == 6) {
3293 0 : sky->setFallbackBgColor(*event.set_sky.bgcolor);
3294 0 : skybox = smgr->addSkyBoxSceneNode(
3295 0 : texture_src->getTextureForMesh((*event.set_sky.params)[0]),
3296 0 : texture_src->getTextureForMesh((*event.set_sky.params)[1]),
3297 0 : texture_src->getTextureForMesh((*event.set_sky.params)[2]),
3298 0 : texture_src->getTextureForMesh((*event.set_sky.params)[3]),
3299 0 : texture_src->getTextureForMesh((*event.set_sky.params)[4]),
3300 0 : texture_src->getTextureForMesh((*event.set_sky.params)[5]));
3301 : }
3302 : // Handle everything else as plain color
3303 : else {
3304 0 : if (*event.set_sky.type != "plain")
3305 0 : infostream << "Unknown sky type: "
3306 0 : << (*event.set_sky.type) << std::endl;
3307 :
3308 0 : sky->setFallbackBgColor(*event.set_sky.bgcolor);
3309 : }
3310 :
3311 0 : delete event.set_sky.bgcolor;
3312 0 : delete event.set_sky.type;
3313 0 : delete event.set_sky.params;
3314 0 : } else if (event.type == CE_OVERRIDE_DAY_NIGHT_RATIO) {
3315 0 : bool enable = event.override_day_night_ratio.do_override;
3316 0 : u32 value = event.override_day_night_ratio.ratio_f * 1000;
3317 0 : client->getEnv().setDayNightRatioOverride(enable, value);
3318 : }
3319 : }
3320 1166 : }
3321 :
3322 :
3323 1166 : void Game::updateCamera(VolatileRunFlags *flags, u32 busy_time,
3324 : f32 dtime, float time_from_last_punch)
3325 : {
3326 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3327 :
3328 : /*
3329 : For interaction purposes, get info about the held item
3330 : - What item is it?
3331 : - Is it a usable item?
3332 : - Can it point to liquids?
3333 : */
3334 2332 : ItemStack playeritem;
3335 : {
3336 1166 : InventoryList *mlist = local_inventory->getList("main");
3337 :
3338 1166 : if (mlist && client->getPlayerItem() < mlist->getSize())
3339 1165 : playeritem = mlist->getItem(client->getPlayerItem());
3340 : }
3341 :
3342 : ToolCapabilities playeritem_toolcap =
3343 2332 : playeritem.getToolCapabilities(itemdef_manager);
3344 :
3345 1166 : v3s16 old_camera_offset = camera->getOffset();
3346 :
3347 1166 : if (input->wasKeyDown(keycache.key[KeyCache::KEYMAP_ID_CAMERA_MODE])) {
3348 0 : GenericCAO *playercao = player->getCAO();
3349 :
3350 : // If playercao not loaded, don't change camera
3351 0 : if (playercao == NULL)
3352 0 : return;
3353 :
3354 0 : camera->toggleCameraMode();
3355 :
3356 0 : playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3357 0 : playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
3358 : }
3359 :
3360 1166 : float full_punch_interval = playeritem_toolcap.full_punch_interval;
3361 1166 : float tool_reload_ratio = time_from_last_punch / full_punch_interval;
3362 :
3363 1166 : tool_reload_ratio = MYMIN(tool_reload_ratio, 1.0);
3364 1166 : camera->update(player, dtime, busy_time / 1000.0f, tool_reload_ratio,
3365 2332 : client->getEnv());
3366 1166 : camera->step(dtime);
3367 :
3368 1166 : v3f camera_position = camera->getPosition();
3369 1166 : v3f camera_direction = camera->getDirection();
3370 1166 : f32 camera_fov = camera->getFovMax();
3371 1166 : v3s16 camera_offset = camera->getOffset();
3372 :
3373 1166 : flags->camera_offset_changed = (camera_offset != old_camera_offset);
3374 :
3375 1166 : if (!flags->disable_camera_update) {
3376 2332 : client->getEnv().getClientMap().updateCamera(camera_position,
3377 1166 : camera_direction, camera_fov, camera_offset);
3378 :
3379 1166 : if (flags->camera_offset_changed) {
3380 0 : client->updateCameraOffset(camera_offset);
3381 0 : client->getEnv().updateCameraOffset(camera_offset);
3382 :
3383 0 : if (clouds)
3384 0 : clouds->updateCameraOffset(camera_offset);
3385 : }
3386 : }
3387 : }
3388 :
3389 :
3390 1166 : void Game::updateSound(f32 dtime)
3391 : {
3392 : // Update sound listener
3393 1166 : v3s16 camera_offset = camera->getOffset();
3394 5830 : sound->updateListener(camera->getCameraNode()->getPosition() + intToFloat(camera_offset, BS),
3395 : v3f(0, 0, 0), // velocity
3396 2332 : camera->getDirection(),
3397 2332 : camera->getCameraNode()->getUpVector());
3398 1166 : sound->setListenerGain(g_settings->getFloat("sound_volume"));
3399 :
3400 :
3401 : // Update sound maker
3402 1166 : soundmaker->step(dtime);
3403 :
3404 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3405 :
3406 1166 : ClientMap &map = client->getEnv().getClientMap();
3407 1166 : MapNode n = map.getNodeNoEx(player->getStandingNodePos());
3408 1166 : soundmaker->m_player_step_sound = nodedef_manager->get(n).sound_footstep;
3409 1166 : }
3410 :
3411 :
3412 1166 : void Game::processPlayerInteraction(std::vector<aabb3f> &highlight_boxes,
3413 : GameRunData *runData, f32 dtime, bool show_hud, bool show_debug)
3414 : {
3415 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3416 :
3417 2332 : ItemStack playeritem;
3418 : {
3419 1166 : InventoryList *mlist = local_inventory->getList("main");
3420 :
3421 1166 : if (mlist && client->getPlayerItem() < mlist->getSize())
3422 1165 : playeritem = mlist->getItem(client->getPlayerItem());
3423 : }
3424 :
3425 : const ItemDefinition &playeritem_def =
3426 1166 : playeritem.getDefinition(itemdef_manager);
3427 :
3428 1166 : v3f player_position = player->getPosition();
3429 1166 : v3f camera_position = camera->getPosition();
3430 1166 : v3f camera_direction = camera->getDirection();
3431 1166 : v3s16 camera_offset = camera->getOffset();
3432 :
3433 :
3434 : /*
3435 : Calculate what block is the crosshair pointing to
3436 : */
3437 :
3438 1166 : f32 d = playeritem_def.range; // max. distance
3439 1166 : f32 d_hand = itemdef_manager->get("").range;
3440 :
3441 1166 : if (d < 0 && d_hand >= 0)
3442 0 : d = d_hand;
3443 1166 : else if (d < 0)
3444 1166 : d = 4.0;
3445 :
3446 1166 : core::line3d<f32> shootline;
3447 :
3448 1166 : if (camera->getCameraMode() != CAMERA_MODE_THIRD_FRONT) {
3449 :
3450 2332 : shootline = core::line3d<f32>(camera_position,
3451 3498 : camera_position + camera_direction * BS * (d + 1));
3452 :
3453 : } else {
3454 : // prevent player pointing anything in front-view
3455 0 : if (camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT)
3456 0 : shootline = core::line3d<f32>(0, 0, 0, 0, 0, 0);
3457 : }
3458 :
3459 : #ifdef HAVE_TOUCHSCREENGUI
3460 :
3461 : if ((g_settings->getBool("touchtarget")) && (g_touchscreengui)) {
3462 : shootline = g_touchscreengui->getShootline();
3463 : shootline.start += intToFloat(camera_offset, BS);
3464 : shootline.end += intToFloat(camera_offset, BS);
3465 : }
3466 :
3467 : #endif
3468 :
3469 : PointedThing pointed = getPointedThing(
3470 : // input
3471 : client, player_position, camera_direction,
3472 : camera_position, shootline, d,
3473 1166 : playeritem_def.liquids_pointable,
3474 1166 : !runData->ldown_for_dig,
3475 : camera_offset,
3476 : // output
3477 : highlight_boxes,
3478 3498 : runData->selected_object);
3479 :
3480 1166 : if (pointed != runData->pointed_old) {
3481 57 : infostream << "Pointing at " << pointed.dump() << std::endl;
3482 :
3483 57 : if (m_cache_enable_node_highlighting) {
3484 57 : if (pointed.type == POINTEDTHING_NODE) {
3485 42 : client->setHighlighted(pointed.node_undersurface, show_hud);
3486 : } else {
3487 15 : client->setHighlighted(pointed.node_undersurface, false);
3488 : }
3489 : }
3490 : }
3491 :
3492 : /*
3493 : Stop digging when
3494 : - releasing left mouse button
3495 : - pointing away from node
3496 : */
3497 1166 : if (runData->digging) {
3498 0 : if (input->getLeftReleased()) {
3499 0 : infostream << "Left button released"
3500 0 : << " (stopped digging)" << std::endl;
3501 0 : runData->digging = false;
3502 0 : } else if (pointed != runData->pointed_old) {
3503 0 : if (pointed.type == POINTEDTHING_NODE
3504 0 : && runData->pointed_old.type == POINTEDTHING_NODE
3505 0 : && pointed.node_undersurface
3506 0 : == runData->pointed_old.node_undersurface) {
3507 : // Still pointing to the same node, but a different face.
3508 : // Don't reset.
3509 : } else {
3510 0 : infostream << "Pointing away from node"
3511 0 : << " (stopped digging)" << std::endl;
3512 0 : runData->digging = false;
3513 : }
3514 : }
3515 :
3516 0 : if (!runData->digging) {
3517 0 : client->interact(1, runData->pointed_old);
3518 0 : client->setCrack(-1, v3s16(0, 0, 0));
3519 0 : runData->dig_time = 0.0;
3520 : }
3521 : }
3522 :
3523 1166 : if (!runData->digging && runData->ldown_for_dig && !input->getLeftState()) {
3524 0 : runData->ldown_for_dig = false;
3525 : }
3526 :
3527 1166 : runData->left_punch = false;
3528 :
3529 1166 : soundmaker->m_player_leftpunch_sound.name = "";
3530 :
3531 1166 : if (input->getRightState())
3532 0 : runData->repeat_rightclick_timer += dtime;
3533 : else
3534 1166 : runData->repeat_rightclick_timer = 0;
3535 :
3536 1166 : if (playeritem_def.usable && input->getLeftState()) {
3537 0 : if (input->getLeftClicked())
3538 0 : client->interact(4, pointed);
3539 1166 : } else if (pointed.type == POINTEDTHING_NODE) {
3540 : ToolCapabilities playeritem_toolcap =
3541 570 : playeritem.getToolCapabilities(itemdef_manager);
3542 : handlePointingAtNode(runData, pointed, playeritem_def,
3543 285 : playeritem_toolcap, dtime);
3544 881 : } else if (pointed.type == POINTEDTHING_OBJECT) {
3545 0 : handlePointingAtObject(runData, pointed, playeritem,
3546 0 : player_position, show_debug);
3547 881 : } else if (input->getLeftState()) {
3548 : // When button is held down in air, show continuous animation
3549 0 : runData->left_punch = true;
3550 : }
3551 :
3552 1166 : runData->pointed_old = pointed;
3553 :
3554 1166 : if (runData->left_punch || input->getLeftClicked())
3555 0 : camera->setDigging(0); // left click animation
3556 :
3557 1166 : input->resetLeftClicked();
3558 1166 : input->resetRightClicked();
3559 :
3560 1166 : input->resetLeftReleased();
3561 1166 : input->resetRightReleased();
3562 1166 : }
3563 :
3564 :
3565 285 : void Game::handlePointingAtNode(GameRunData *runData,
3566 : const PointedThing &pointed, const ItemDefinition &playeritem_def,
3567 : const ToolCapabilities &playeritem_toolcap, f32 dtime)
3568 : {
3569 285 : v3s16 nodepos = pointed.node_undersurface;
3570 285 : v3s16 neighbourpos = pointed.node_abovesurface;
3571 :
3572 : /*
3573 : Check information text of node
3574 : */
3575 :
3576 285 : ClientMap &map = client->getEnv().getClientMap();
3577 285 : NodeMetadata *meta = map.getNodeMetadata(nodepos);
3578 :
3579 285 : if (meta) {
3580 76 : infotext = narrow_to_wide(meta->getString("infotext"));
3581 : } else {
3582 209 : MapNode n = map.getNodeNoEx(nodepos);
3583 :
3584 209 : if (nodedef_manager->get(n).tiledef[0].name == "unknown_node.png") {
3585 0 : infotext = L"Unknown node: ";
3586 0 : infotext += narrow_to_wide(nodedef_manager->get(n).name);
3587 : }
3588 : }
3589 :
3590 1425 : if (runData->nodig_delay_timer <= 0.0 && input->getLeftState()
3591 855 : && client->checkPrivilege("interact")) {
3592 0 : handleDigging(runData, pointed, nodepos, playeritem_toolcap, dtime);
3593 : }
3594 :
3595 1425 : if ((input->getRightClicked() ||
3596 570 : runData->repeat_rightclick_timer >= m_repeat_right_click_time) &&
3597 285 : client->checkPrivilege("interact")) {
3598 0 : runData->repeat_rightclick_timer = 0;
3599 0 : infostream << "Ground right-clicked" << std::endl;
3600 :
3601 0 : if (meta && meta->getString("formspec") != "" && !random_input
3602 0 : && !input->isKeyDown(getKeySetting("keymap_sneak"))) {
3603 0 : infostream << "Launching custom inventory view" << std::endl;
3604 :
3605 0 : InventoryLocation inventoryloc;
3606 0 : inventoryloc.setNodeMeta(nodepos);
3607 :
3608 : NodeMetadataFormSource *fs_src = new NodeMetadataFormSource(
3609 0 : &client->getEnv().getClientMap(), nodepos);
3610 0 : TextDest *txt_dst = new TextDestNodeMetadata(nodepos, client);
3611 :
3612 0 : create_formspec_menu(¤t_formspec, client, gamedef,
3613 0 : texture_src, device, fs_src, txt_dst, client);
3614 :
3615 0 : current_formspec->setFormSpec(meta->getString("formspec"), inventoryloc);
3616 : } else {
3617 : // Report right click to server
3618 :
3619 0 : camera->setDigging(1); // right click animation (always shown for feedback)
3620 :
3621 : // If the wielded item has node placement prediction,
3622 : // make that happen
3623 0 : bool placed = nodePlacementPrediction(*client,
3624 : playeritem_def,
3625 0 : nodepos, neighbourpos);
3626 :
3627 0 : if (placed) {
3628 : // Report to server
3629 0 : client->interact(3, pointed);
3630 : // Read the sound
3631 0 : soundmaker->m_player_rightpunch_sound =
3632 0 : playeritem_def.sound_place;
3633 : } else {
3634 0 : soundmaker->m_player_rightpunch_sound =
3635 0 : SimpleSoundSpec();
3636 : }
3637 :
3638 0 : if (playeritem_def.node_placement_prediction == "" ||
3639 0 : nodedef_manager->get(map.getNodeNoEx(nodepos)).rightclickable)
3640 0 : client->interact(3, pointed); // Report to server
3641 : }
3642 : }
3643 285 : }
3644 :
3645 :
3646 0 : void Game::handlePointingAtObject(GameRunData *runData,
3647 : const PointedThing &pointed,
3648 : const ItemStack &playeritem,
3649 : const v3f &player_position,
3650 : bool show_debug)
3651 : {
3652 0 : infotext = narrow_to_wide(runData->selected_object->infoText());
3653 :
3654 0 : if (infotext == L"" && show_debug) {
3655 0 : infotext = narrow_to_wide(runData->selected_object->debugInfoText());
3656 : }
3657 :
3658 0 : if (input->getLeftState()) {
3659 0 : bool do_punch = false;
3660 0 : bool do_punch_damage = false;
3661 :
3662 0 : if (runData->object_hit_delay_timer <= 0.0) {
3663 0 : do_punch = true;
3664 0 : do_punch_damage = true;
3665 0 : runData->object_hit_delay_timer = object_hit_delay;
3666 : }
3667 :
3668 0 : if (input->getLeftClicked())
3669 0 : do_punch = true;
3670 :
3671 0 : if (do_punch) {
3672 0 : infostream << "Left-clicked object" << std::endl;
3673 0 : runData->left_punch = true;
3674 : }
3675 :
3676 0 : if (do_punch_damage) {
3677 : // Report direct punch
3678 0 : v3f objpos = runData->selected_object->getPosition();
3679 0 : v3f dir = (objpos - player_position).normalize();
3680 :
3681 0 : bool disable_send = runData->selected_object->directReportPunch(
3682 0 : dir, &playeritem, runData->time_from_last_punch);
3683 0 : runData->time_from_last_punch = 0;
3684 :
3685 0 : if (!disable_send)
3686 0 : client->interact(0, pointed);
3687 : }
3688 0 : } else if (input->getRightClicked()) {
3689 0 : infostream << "Right-clicked object" << std::endl;
3690 0 : client->interact(3, pointed); // place
3691 : }
3692 0 : }
3693 :
3694 :
3695 0 : void Game::handleDigging(GameRunData *runData,
3696 : const PointedThing &pointed, const v3s16 &nodepos,
3697 : const ToolCapabilities &playeritem_toolcap, f32 dtime)
3698 : {
3699 0 : if (!runData->digging) {
3700 0 : infostream << "Started digging" << std::endl;
3701 0 : client->interact(0, pointed);
3702 0 : runData->digging = true;
3703 0 : runData->ldown_for_dig = true;
3704 : }
3705 :
3706 0 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3707 0 : ClientMap &map = client->getEnv().getClientMap();
3708 0 : MapNode n = client->getEnv().getClientMap().getNodeNoEx(nodepos);
3709 :
3710 : // NOTE: Similar piece of code exists on the server side for
3711 : // cheat detection.
3712 : // Get digging parameters
3713 0 : DigParams params = getDigParams(nodedef_manager->get(n).groups,
3714 0 : &playeritem_toolcap);
3715 :
3716 : // If can't dig, try hand
3717 0 : if (!params.diggable) {
3718 0 : const ItemDefinition &hand = itemdef_manager->get("");
3719 0 : const ToolCapabilities *tp = hand.tool_capabilities;
3720 :
3721 0 : if (tp)
3722 0 : params = getDigParams(nodedef_manager->get(n).groups, tp);
3723 : }
3724 :
3725 0 : if (params.diggable == false) {
3726 : // I guess nobody will wait for this long
3727 0 : runData->dig_time_complete = 10000000.0;
3728 : } else {
3729 0 : runData->dig_time_complete = params.time;
3730 :
3731 0 : if (m_cache_enable_particles) {
3732 : const ContentFeatures &features =
3733 0 : client->getNodeDefManager()->get(n);
3734 0 : client->getParticleManager()->addPunchingParticles(gamedef, smgr,
3735 0 : player, nodepos, features.tiles);
3736 : }
3737 : }
3738 :
3739 0 : if (runData->dig_time_complete >= 0.001) {
3740 0 : runData->dig_index = (float)crack_animation_length
3741 0 : * runData->dig_time
3742 0 : / runData->dig_time_complete;
3743 : } else {
3744 : // This is for torches
3745 0 : runData->dig_index = crack_animation_length;
3746 : }
3747 :
3748 0 : SimpleSoundSpec sound_dig = nodedef_manager->get(n).sound_dig;
3749 :
3750 0 : if (sound_dig.exists() && params.diggable) {
3751 0 : if (sound_dig.name == "__group") {
3752 0 : if (params.main_group != "") {
3753 0 : soundmaker->m_player_leftpunch_sound.gain = 0.5;
3754 0 : soundmaker->m_player_leftpunch_sound.name =
3755 0 : std::string("default_dig_") +
3756 0 : params.main_group;
3757 : }
3758 : } else {
3759 0 : soundmaker->m_player_leftpunch_sound = sound_dig;
3760 : }
3761 : }
3762 :
3763 : // Don't show cracks if not diggable
3764 0 : if (runData->dig_time_complete >= 100000.0) {
3765 0 : } else if (runData->dig_index < crack_animation_length) {
3766 : //TimeTaker timer("client.setTempMod");
3767 : //infostream<<"dig_index="<<dig_index<<std::endl;
3768 0 : client->setCrack(runData->dig_index, nodepos);
3769 : } else {
3770 0 : infostream << "Digging completed" << std::endl;
3771 0 : client->interact(2, pointed);
3772 0 : client->setCrack(-1, v3s16(0, 0, 0));
3773 : bool is_valid_position;
3774 0 : MapNode wasnode = map.getNodeNoEx(nodepos, &is_valid_position);
3775 0 : if (is_valid_position)
3776 0 : client->removeNode(nodepos);
3777 :
3778 0 : if (m_cache_enable_particles) {
3779 : const ContentFeatures &features =
3780 0 : client->getNodeDefManager()->get(wasnode);
3781 0 : client->getParticleManager()->addDiggingParticles(gamedef, smgr,
3782 0 : player, nodepos, features.tiles);
3783 : }
3784 :
3785 0 : runData->dig_time = 0;
3786 0 : runData->digging = false;
3787 :
3788 : runData->nodig_delay_timer =
3789 0 : runData->dig_time_complete / (float)crack_animation_length;
3790 :
3791 : // We don't want a corresponding delay to
3792 : // very time consuming nodes
3793 0 : if (runData->nodig_delay_timer > 0.3)
3794 0 : runData->nodig_delay_timer = 0.3;
3795 :
3796 : // We want a slight delay to very little
3797 : // time consuming nodes
3798 0 : const float mindelay = 0.15;
3799 :
3800 0 : if (runData->nodig_delay_timer < mindelay)
3801 0 : runData->nodig_delay_timer = mindelay;
3802 :
3803 : // Send event to trigger sound
3804 0 : MtEvent *e = new NodeDugEvent(nodepos, wasnode);
3805 0 : gamedef->event()->put(e);
3806 : }
3807 :
3808 0 : if (runData->dig_time_complete < 100000.0) {
3809 0 : runData->dig_time += dtime;
3810 : } else {
3811 0 : runData->dig_time = 0;
3812 0 : client->setCrack(-1, nodepos);
3813 : }
3814 :
3815 0 : camera->setDigging(0); // left click animation
3816 0 : }
3817 :
3818 :
3819 1166 : void Game::updateFrame(std::vector<aabb3f> &highlight_boxes,
3820 : ProfilerGraph *graph, RunStats *stats, GameRunData *runData,
3821 : f32 dtime, const VolatileRunFlags &flags, const CameraOrientation &cam)
3822 : {
3823 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
3824 :
3825 : /*
3826 : Fog range
3827 : */
3828 :
3829 1166 : if (draw_control->range_all) {
3830 0 : runData->fog_range = 100000 * BS;
3831 : } else {
3832 1166 : runData->fog_range = draw_control->wanted_range * BS
3833 1166 : + 0.0 * MAP_BLOCKSIZE * BS;
3834 2170 : runData->fog_range = MYMIN(
3835 : runData->fog_range,
3836 2170 : (draw_control->farthest_drawn + 20) * BS);
3837 1166 : runData->fog_range *= 0.9;
3838 : }
3839 :
3840 : /*
3841 : Calculate general brightness
3842 : */
3843 1166 : u32 daynight_ratio = client->getEnv().getDayNightRatio();
3844 1166 : float time_brightness = decode_light_f((float)daynight_ratio / 1000.0);
3845 : float direct_brightness;
3846 : bool sunlight_seen;
3847 :
3848 1166 : if (g_settings->getBool("free_move")) {
3849 0 : direct_brightness = time_brightness;
3850 0 : sunlight_seen = true;
3851 : } else {
3852 2332 : ScopeProfiler sp(g_profiler, "Detecting background light", SPT_AVG);
3853 1166 : float old_brightness = sky->getBrightness();
3854 1166 : direct_brightness = client->getEnv().getClientMap()
3855 2332 : .getBackgroundBrightness(MYMIN(runData->fog_range * 1.2, 60 * BS),
3856 1166 : daynight_ratio, (int)(old_brightness * 255.5), &sunlight_seen)
3857 1166 : / 255.0;
3858 : }
3859 :
3860 1166 : float time_of_day = runData->time_of_day;
3861 1166 : float time_of_day_smooth = runData->time_of_day_smooth;
3862 :
3863 1166 : time_of_day = client->getEnv().getTimeOfDayF();
3864 :
3865 1166 : const float maxsm = 0.05;
3866 1166 : const float todsm = 0.05;
3867 :
3868 1167 : if (fabs(time_of_day - time_of_day_smooth) > maxsm &&
3869 2 : fabs(time_of_day - time_of_day_smooth + 1.0) > maxsm &&
3870 1 : fabs(time_of_day - time_of_day_smooth - 1.0) > maxsm)
3871 1 : time_of_day_smooth = time_of_day;
3872 :
3873 1166 : if (time_of_day_smooth > 0.8 && time_of_day < 0.2)
3874 0 : time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3875 0 : + (time_of_day + 1.0) * todsm;
3876 : else
3877 1166 : time_of_day_smooth = time_of_day_smooth * (1.0 - todsm)
3878 1166 : + time_of_day * todsm;
3879 :
3880 1166 : runData->time_of_day = time_of_day;
3881 1166 : runData->time_of_day_smooth = time_of_day_smooth;
3882 :
3883 2332 : sky->update(time_of_day_smooth, time_brightness, direct_brightness,
3884 1166 : sunlight_seen, camera->getCameraMode(), player->getYaw(),
3885 1166 : player->getPitch());
3886 :
3887 : /*
3888 : Update clouds
3889 : */
3890 1166 : if (clouds) {
3891 1166 : v3f player_position = player->getPosition();
3892 1166 : if (sky->getCloudsVisible()) {
3893 1166 : clouds->setVisible(true);
3894 1166 : clouds->step(dtime);
3895 2332 : clouds->update(v2f(player_position.X, player_position.Z),
3896 2332 : sky->getCloudColor());
3897 : } else {
3898 0 : clouds->setVisible(false);
3899 : }
3900 : }
3901 :
3902 : /*
3903 : Update particles
3904 : */
3905 1166 : client->getParticleManager()->step(dtime);
3906 :
3907 : /*
3908 : Fog
3909 : */
3910 :
3911 1166 : if (m_cache_enable_fog && !flags.force_fog_off) {
3912 4664 : driver->setFog(
3913 1166 : sky->getBgColor(),
3914 : video::EFT_FOG_LINEAR,
3915 1166 : runData->fog_range * 0.4,
3916 : runData->fog_range * 1.0,
3917 : 0.01,
3918 : false, // pixel fog
3919 : false // range fog
3920 2332 : );
3921 : } else {
3922 0 : driver->setFog(
3923 0 : sky->getBgColor(),
3924 : video::EFT_FOG_LINEAR,
3925 : 100000 * BS,
3926 : 110000 * BS,
3927 : 0.01,
3928 : false, // pixel fog
3929 : false // range fog
3930 0 : );
3931 : }
3932 :
3933 : /*
3934 : Get chat messages from client
3935 : */
3936 :
3937 1166 : v2u32 screensize = driver->getScreenSize();
3938 :
3939 3498 : updateChat(*client, dtime, flags.show_debug, screensize,
3940 1166 : flags.show_chat, runData->profiler_current_page,
3941 2332 : *chat_backend, guitext_chat);
3942 :
3943 : /*
3944 : Inventory
3945 : */
3946 :
3947 1166 : if (client->getPlayerItem() != runData->new_playeritem)
3948 20 : client->selectPlayerItem(runData->new_playeritem);
3949 :
3950 : // Update local inventory if it has changed
3951 1166 : if (client->getLocalInventoryUpdated()) {
3952 : //infostream<<"Updating local inventory"<<std::endl;
3953 24 : client->getLocalInventory(*local_inventory);
3954 24 : runData->update_wielded_item_trigger = true;
3955 : }
3956 :
3957 1166 : if (runData->update_wielded_item_trigger) {
3958 : // Update wielded tool
3959 24 : InventoryList *mlist = local_inventory->getList("main");
3960 :
3961 24 : if (mlist && (client->getPlayerItem() < mlist->getSize())) {
3962 48 : ItemStack item = mlist->getItem(client->getPlayerItem());
3963 24 : camera->wield(item);
3964 : }
3965 24 : runData->update_wielded_item_trigger = false;
3966 : }
3967 :
3968 : /*
3969 : Update block draw list every 200ms or when camera direction has
3970 : changed much
3971 : */
3972 1166 : runData->update_draw_list_timer += dtime;
3973 :
3974 1166 : v3f camera_direction = camera->getDirection();
3975 2332 : if (runData->update_draw_list_timer >= 0.2
3976 1041 : || runData->update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2
3977 2153 : || flags.camera_offset_changed) {
3978 179 : runData->update_draw_list_timer = 0;
3979 179 : client->getEnv().getClientMap().updateDrawList(driver);
3980 179 : runData->update_draw_list_last_cam_dir = camera_direction;
3981 : }
3982 :
3983 1166 : updateGui(&runData->statustext_time, *stats, *runData, dtime, flags, cam);
3984 :
3985 : /*
3986 : make sure menu is on top
3987 : 1. Delete formspec menu reference if menu was removed
3988 : 2. Else, make sure formspec menu is on top
3989 : */
3990 1166 : if (current_formspec) {
3991 20 : if (current_formspec->getReferenceCount() == 1) {
3992 0 : current_formspec->drop();
3993 0 : current_formspec = NULL;
3994 20 : } else if (!noMenuActive()) {
3995 20 : guiroot->bringToFront(current_formspec);
3996 : }
3997 : }
3998 :
3999 : /*
4000 : Drawing begins
4001 : */
4002 :
4003 1166 : video::SColor skycolor = sky->getSkyColor();
4004 :
4005 2332 : TimeTaker tt_draw("mainloop: draw");
4006 : {
4007 2332 : TimeTaker timer("beginScene");
4008 1166 : driver->beginScene(true, true, skycolor);
4009 1166 : stats->beginscenetime = timer.stop(true);
4010 : }
4011 :
4012 4664 : draw_scene(driver, smgr, *camera, *client, player, *hud, *mapper,
4013 1166 : guienv, highlight_boxes, screensize, skycolor, flags.show_hud,
4014 3498 : flags.show_minimap);
4015 :
4016 : /*
4017 : Profiler graph
4018 : */
4019 1166 : if (flags.show_profiler_graph)
4020 0 : graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont());
4021 :
4022 : /*
4023 : Damage flash
4024 : */
4025 1166 : if (runData->damage_flash > 0.0) {
4026 0 : video::SColor color(std::min(runData->damage_flash, 180.0f),
4027 : 180,
4028 : 0,
4029 0 : 0);
4030 0 : driver->draw2DRectangle(color,
4031 0 : core::rect<s32>(0, 0, screensize.X, screensize.Y),
4032 0 : NULL);
4033 :
4034 0 : runData->damage_flash -= 100.0 * dtime;
4035 : }
4036 :
4037 : /*
4038 : Damage camera tilt
4039 : */
4040 1166 : if (player->hurt_tilt_timer > 0.0) {
4041 0 : player->hurt_tilt_timer -= dtime * 5;
4042 :
4043 0 : if (player->hurt_tilt_timer < 0)
4044 0 : player->hurt_tilt_strength = 0;
4045 : }
4046 :
4047 : /*
4048 : Update minimap pos
4049 : */
4050 1166 : if (flags.show_minimap && flags.show_hud) {
4051 1166 : mapper->setPos(floatToInt(player->getPosition(), BS));
4052 : }
4053 :
4054 : /*
4055 : End scene
4056 : */
4057 : {
4058 2332 : TimeTaker timer("endScene");
4059 1166 : driver->endScene();
4060 1166 : stats->endscenetime = timer.stop(true);
4061 : }
4062 :
4063 1166 : stats->drawtime = tt_draw.stop(true);
4064 1166 : g_profiler->graphAdd("mainloop_draw", stats->drawtime / 1000.0f);
4065 1166 : }
4066 :
4067 :
4068 0 : inline static const char *yawToDirectionString(int yaw)
4069 : {
4070 : // NOTE: TODO: This can be done mathematically without the else/else-if
4071 : // cascade.
4072 :
4073 : const char *player_direction;
4074 :
4075 0 : yaw = wrapDegrees_0_360(yaw);
4076 :
4077 0 : if (yaw >= 45 && yaw < 135)
4078 0 : player_direction = "West [-X]";
4079 0 : else if (yaw >= 135 && yaw < 225)
4080 0 : player_direction = "South [-Z]";
4081 0 : else if (yaw >= 225 && yaw < 315)
4082 0 : player_direction = "East [+X]";
4083 : else
4084 0 : player_direction = "North [+Z]";
4085 :
4086 0 : return player_direction;
4087 : }
4088 :
4089 :
4090 1166 : void Game::updateGui(float *statustext_time, const RunStats &stats,
4091 : const GameRunData& runData, f32 dtime, const VolatileRunFlags &flags,
4092 : const CameraOrientation &cam)
4093 : {
4094 1166 : v2u32 screensize = driver->getScreenSize();
4095 1166 : LocalPlayer *player = client->getEnv().getLocalPlayer();
4096 1166 : v3f player_position = player->getPosition();
4097 :
4098 1166 : if (flags.show_debug) {
4099 : static float drawtime_avg = 0;
4100 0 : drawtime_avg = drawtime_avg * 0.95 + stats.drawtime * 0.05;
4101 :
4102 0 : u16 fps = 1.0 / stats.dtime_jitter.avg;
4103 : //s32 fps = driver->getFPS();
4104 :
4105 0 : std::ostringstream os(std::ios_base::binary);
4106 0 : os << std::fixed
4107 0 : << PROJECT_NAME_C " " << g_version_hash
4108 0 : << " FPS = " << fps
4109 0 : << " (R: range_all=" << draw_control->range_all << ")"
4110 0 : << std::setprecision(0)
4111 0 : << " drawtime = " << drawtime_avg
4112 0 : << std::setprecision(1)
4113 0 : << ", dtime_jitter = "
4114 0 : << (stats.dtime_jitter.max_fraction * 100.0) << " %"
4115 0 : << std::setprecision(1)
4116 0 : << ", v_range = " << draw_control->wanted_range
4117 0 : << std::setprecision(3)
4118 0 : << ", RTT = " << client->getRTT();
4119 0 : guitext->setText(narrow_to_wide(os.str()).c_str());
4120 0 : guitext->setVisible(true);
4121 1166 : } else if (flags.show_hud || flags.show_chat) {
4122 2332 : std::ostringstream os(std::ios_base::binary);
4123 1166 : os << PROJECT_NAME_C " " << g_version_hash;
4124 1166 : guitext->setText(narrow_to_wide(os.str()).c_str());
4125 2332 : guitext->setVisible(true);
4126 : } else {
4127 0 : guitext->setVisible(false);
4128 : }
4129 :
4130 1166 : if (guitext->isVisible()) {
4131 : core::rect<s32> rect(
4132 : 5, 5,
4133 2332 : screensize.X, 5 + g_fontengine->getTextHeight()
4134 2332 : );
4135 1166 : guitext->setRelativePosition(rect);
4136 : }
4137 :
4138 1166 : if (flags.show_debug) {
4139 0 : std::ostringstream os(std::ios_base::binary);
4140 0 : os << std::setprecision(1) << std::fixed
4141 0 : << "(" << (player_position.X / BS)
4142 0 : << ", " << (player_position.Y / BS)
4143 0 : << ", " << (player_position.Z / BS)
4144 0 : << ") (yaw=" << (wrapDegrees_0_360(cam.camera_yaw))
4145 0 : << " " << yawToDirectionString(cam.camera_yaw)
4146 0 : << ") (seed = " << ((u64)client->getMapSeed())
4147 0 : << ")";
4148 :
4149 0 : if (runData.pointed_old.type == POINTEDTHING_NODE) {
4150 0 : ClientMap &map = client->getEnv().getClientMap();
4151 0 : const INodeDefManager *nodedef = client->getNodeDefManager();
4152 0 : MapNode n = map.getNodeNoEx(runData.pointed_old.node_undersurface);
4153 0 : if (n.getContent() != CONTENT_IGNORE && nodedef->get(n).name != "unknown") {
4154 0 : const ContentFeatures &features = nodedef->get(n);
4155 0 : os << " (pointing_at = " << nodedef->get(n).name
4156 0 : << " - " << features.tiledef[0].name.c_str()
4157 0 : << ")";
4158 : }
4159 : }
4160 :
4161 0 : guitext2->setText(narrow_to_wide(os.str()).c_str());
4162 0 : guitext2->setVisible(true);
4163 :
4164 : core::rect<s32> rect(
4165 0 : 5, 5 + g_fontengine->getTextHeight(),
4166 0 : screensize.X, 5 + g_fontengine->getTextHeight() * 2
4167 0 : );
4168 0 : guitext2->setRelativePosition(rect);
4169 : } else {
4170 1166 : guitext2->setVisible(false);
4171 : }
4172 :
4173 1166 : guitext_info->setText(infotext.c_str());
4174 1166 : guitext_info->setVisible(flags.show_hud && g_menumgr.menuCount() == 0);
4175 :
4176 1166 : float statustext_time_max = 1.5;
4177 :
4178 1166 : if (!statustext.empty()) {
4179 0 : *statustext_time += dtime;
4180 :
4181 0 : if (*statustext_time >= statustext_time_max) {
4182 0 : statustext = L"";
4183 0 : *statustext_time = 0;
4184 : }
4185 : }
4186 :
4187 1166 : guitext_status->setText(statustext.c_str());
4188 1166 : guitext_status->setVisible(!statustext.empty());
4189 :
4190 1166 : if (!statustext.empty()) {
4191 0 : s32 status_width = guitext_status->getTextWidth();
4192 0 : s32 status_height = guitext_status->getTextHeight();
4193 0 : s32 status_y = screensize.Y - 150;
4194 0 : s32 status_x = (screensize.X - status_width) / 2;
4195 : core::rect<s32> rect(
4196 : status_x , status_y - status_height,
4197 : status_x + status_width, status_y
4198 0 : );
4199 0 : guitext_status->setRelativePosition(rect);
4200 :
4201 : // Fade out
4202 0 : video::SColor initial_color(255, 0, 0, 0);
4203 :
4204 0 : if (guienv->getSkin())
4205 0 : initial_color = guienv->getSkin()->getColor(gui::EGDC_BUTTON_TEXT);
4206 :
4207 0 : video::SColor final_color = initial_color;
4208 0 : final_color.setAlpha(0);
4209 : video::SColor fade_color = initial_color.getInterpolated_quadratic(
4210 : initial_color, final_color,
4211 0 : pow(*statustext_time / statustext_time_max, 2.0f));
4212 0 : guitext_status->setOverrideColor(fade_color);
4213 0 : guitext_status->enableOverrideColor(true);
4214 : }
4215 1166 : }
4216 :
4217 :
4218 : /* Log times and stuff for visualization */
4219 1166 : inline void Game::updateProfilerGraphs(ProfilerGraph *graph)
4220 : {
4221 2332 : Profiler::GraphValues values;
4222 1166 : g_profiler->graphGet(values);
4223 1166 : graph->put(values);
4224 1166 : }
4225 :
4226 :
4227 :
4228 : /****************************************************************************
4229 : Misc
4230 : ****************************************************************************/
4231 :
4232 : /* On some computers framerate doesn't seem to be automatically limited
4233 : */
4234 1193 : inline void Game::limitFps(FpsControl *fps_timings, f32 *dtime)
4235 : {
4236 : // not using getRealTime is necessary for wine
4237 1193 : device->getTimer()->tick(); // Maker sure device time is up-to-date
4238 1193 : u32 time = device->getTimer()->getTime();
4239 1193 : u32 last_time = fps_timings->last_time;
4240 :
4241 1193 : if (time > last_time) // Make sure time hasn't overflowed
4242 1190 : fps_timings->busy_time = time - last_time;
4243 : else
4244 3 : fps_timings->busy_time = 0;
4245 :
4246 1193 : u32 frametime_min = 1000 / (g_menumgr.pausesGame()
4247 1250 : ? g_settings->getFloat("pause_fps_max")
4248 2405 : : g_settings->getFloat("fps_max"));
4249 :
4250 1193 : if (fps_timings->busy_time < frametime_min) {
4251 106 : fps_timings->sleep_time = frametime_min - fps_timings->busy_time;
4252 106 : device->sleep(fps_timings->sleep_time);
4253 : } else {
4254 1087 : fps_timings->sleep_time = 0;
4255 : }
4256 :
4257 : /* Get the new value of the device timer. Note that device->sleep() may
4258 : * not sleep for the entire requested time as sleep may be interrupted and
4259 : * therefore it is arguably more accurate to get the new time from the
4260 : * device rather than calculating it by adding sleep_time to time.
4261 : */
4262 :
4263 1193 : device->getTimer()->tick(); // Update device timer
4264 1193 : time = device->getTimer()->getTime();
4265 :
4266 1193 : if (time > last_time) // Make sure last_time hasn't overflowed
4267 1193 : *dtime = (time - last_time) / 1000.0;
4268 : else
4269 0 : *dtime = 0;
4270 :
4271 1193 : fps_timings->last_time = time;
4272 1193 : }
4273 :
4274 : // Note: This will free (using delete[])! \p msg. If you want to use it later,
4275 : // pass a copy of it to this function
4276 : // Note: \p msg must be allocated using new (not malloc())
4277 12 : void Game::showOverlayMessage(const wchar_t *msg, float dtime,
4278 : int percent, bool draw_clouds)
4279 : {
4280 12 : draw_load_screen(msg, device, guienv, dtime, percent, draw_clouds);
4281 12 : delete[] msg;
4282 12 : }
4283 :
4284 :
4285 : /****************************************************************************/
4286 : /****************************************************************************
4287 : Shutdown / cleanup
4288 : ****************************************************************************/
4289 : /****************************************************************************/
4290 :
4291 1 : void Game::extendedResourceCleanup()
4292 : {
4293 : // Extended resource accounting
4294 1 : infostream << "Irrlicht resources after cleanup:" << std::endl;
4295 1 : infostream << "\tRemaining meshes : "
4296 2 : << device->getSceneManager()->getMeshCache()->getMeshCount() << std::endl;
4297 1 : infostream << "\tRemaining textures : "
4298 2 : << driver->getTextureCount() << std::endl;
4299 :
4300 4 : for (unsigned int i = 0; i < driver->getTextureCount(); i++) {
4301 3 : irr::video::ITexture *texture = driver->getTextureByIndex(i);
4302 3 : infostream << "\t\t" << i << ":" << texture->getName().getPath().c_str()
4303 3 : << std::endl;
4304 : }
4305 :
4306 1 : clearTextureNameCache();
4307 1 : infostream << "\tRemaining materials: "
4308 2 : << driver-> getMaterialRendererCount()
4309 1 : << " (note: irrlicht doesn't support removing renderers)" << std::endl;
4310 1 : }
4311 :
4312 :
4313 : /****************************************************************************/
4314 : /****************************************************************************
4315 : extern function for launching the game
4316 : ****************************************************************************/
4317 : /****************************************************************************/
4318 :
4319 1 : void the_game(bool *kill,
4320 : bool random_input,
4321 : InputHandler *input,
4322 : IrrlichtDevice *device,
4323 :
4324 : const std::string &map_dir,
4325 : const std::string &playername,
4326 : const std::string &password,
4327 : const std::string &address, // If empty local server is created
4328 : u16 port,
4329 :
4330 : std::string &error_message,
4331 : ChatBackend &chat_backend,
4332 : const SubgameSpec &gamespec, // Used for local game
4333 : bool simple_singleplayer_mode)
4334 : {
4335 2 : Game game;
4336 :
4337 : /* Make a copy of the server address because if a local singleplayer server
4338 : * is created then this is updated and we don't want to change the value
4339 : * passed to us by the calling function
4340 : */
4341 2 : std::string server_address = address;
4342 :
4343 : try {
4344 :
4345 1 : if (game.startup(kill, random_input, input, device, map_dir,
4346 : playername, password, &server_address, port,
4347 : error_message, &chat_backend, gamespec,
4348 : simple_singleplayer_mode)) {
4349 1 : game.run();
4350 1 : game.shutdown();
4351 : }
4352 :
4353 0 : } catch (SerializationError &e) {
4354 : error_message = std::string("A serialization error occurred:\n")
4355 0 : + e.what() + "\n\nThe server is probably "
4356 0 : " running a different version of " PROJECT_NAME_C ".";
4357 0 : errorstream << error_message << std::endl;
4358 0 : } catch (ServerError &e) {
4359 0 : error_message = e.what();
4360 0 : errorstream << "ServerError: " << error_message << std::endl;
4361 0 : } catch (ModError &e) {
4362 0 : error_message = e.what() + strgettext("\nCheck debug.txt for details.");
4363 0 : errorstream << "ModError: " << error_message << std::endl;
4364 : }
4365 4 : }
4366 :
|