3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
\r
5 This program is free software; you can redistribute it and/or modify
\r
6 it under the terms of the GNU General Public License as published by
\r
7 the Free Software Foundation; either version 2 of the License, or
\r
8 (at your option) any later version.
\r
10 This program is distributed in the hope that it will be useful,
\r
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 GNU General Public License for more details.
\r
15 You should have received a copy of the GNU General Public License along
\r
16 with this program; if not, write to the Free Software Foundation, Inc.,
\r
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
21 =============================== NOTES ==============================
\r
22 NOTE: Things starting with TODO are sometimes only suggestions.
\r
24 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
25 is an apparanet memory leak in irrlicht when using it (not sure)
\r
27 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
28 NOTE: Global locale is now set at initialization
\r
30 SUGG: Fix address to be ipv6 compatible
\r
32 FIXME: When a new sector is generated, it may change the ground level
\r
33 of it's and it's neighbors border that two blocks that are
\r
34 above and below each other and that are generated before and
\r
35 after the sector heightmap generation (order doesn't matter),
\r
36 can have a small gap between each other at the border.
\r
37 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
38 using for UnlimitedHeightmap? (getting all neighbors
\r
41 SUGG: Transfer more blocks in a single packet
\r
42 SUGG: A blockdata combiner class, to which blocks are added and at
\r
43 destruction it sends all the stuff in as few packets as possible.
\r
45 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
46 SUGG: Fetch stuff mainly from the viewing direction
\r
48 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
49 - This enables saving many packets and making a faster connection
\r
50 - This also enables server to check if client has received the
\r
51 most recent block sent, for example.
\r
52 SUGG: Add a sane bandwidth throttling system to Connection
\r
54 SUGG: More fine-grained control of client's dumping of blocks from
\r
56 - ...What does this mean in the first place?
\r
58 SUGG: A map editing mode (similar to dedicated server mode)
\r
60 SUGG: Add a time value to the param of footstepped grass and check it
\r
61 against a global timer when a block is accessed, to make old
\r
64 SUGG: Make a copy of close-range environment on client for showing
\r
65 on screen, with minimal mutexes to slow down the main loop
\r
67 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
68 it by sending more stuff in a single packet.
\r
69 - Add a packet queue to RemoteClient, from which packets will be
\r
70 combined with object data packets
\r
71 - This is not exactly trivial: the object data packets are
\r
72 sometimes very big by themselves
\r
74 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
75 - This will allow saving ages of rats on disk but not sending
\r
78 SUGG: Implement lighting using VoxelManipulator
\r
79 - Would it be significantly faster?
\r
81 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
82 - Does their position get saved to a border value or something?
\r
83 - Does this happen anymore?
\r
85 SUGG: MovingObject::move and Player::move are basically the same.
\r
88 SUGG: Implement a "Fast check queue" (a queue with a map for checking
\r
89 if something is already in it)
\r
90 - Use it in active block queue in water flowing
\r
92 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
94 SUGG: A version number to blocks, which increments when the block is
\r
95 modified (node add/remove, water update, lighting update)
\r
96 - This can then be used to make sure the most recent version of
\r
97 a block has been sent to client
\r
99 SUGG: Make the amount of blocks sending to client and the total
\r
100 amount of blocks dynamically limited. Transferring blocks is the
\r
101 main network eater of this system, so it is the one that has
\r
102 to be throttled so that RTTs stay low.
\r
104 SUGG: Meshes of blocks could be split into 6 meshes facing into
\r
105 different directions and then only those drawn that need to be
\r
106 - Also an 1-dimensional tile map would be nice probably
\r
108 TODO: Untie client network operations from framerate
\r
109 - Needs some input queues or something
\r
110 - Not really necessary?
\r
112 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
114 - That is >500 vertices
\r
116 TODO: Startup and configuration menu
\r
118 TODO: There are some lighting-related todos and fixmes in
\r
119 ServerMap::emergeBlock
\r
121 TODO: Proper handling of spawning place (try to find something that
\r
122 is not in the middle of an ocean (some land to stand on at
\r
123 least) and save it in map config.
\r
125 TODO: Players to only be hidden when the client quits.
\r
126 TODO: - Players to be saved on disk, with inventory
\r
127 TODO: Players to be saved as text in map/players/<name>
\r
128 TODO: Player inventory to be saved on disk
\r
130 TODO: Make fetching sector's blocks more efficient when rendering
\r
131 sectors that have very large amounts of blocks (on client)
\r
133 TODO: Make the video backend selectable
\r
135 TODO: Copy the text of the last picked sign to inventory in creative
\r
138 TODO: Get rid of GotSplitPacketException
\r
140 TODO: Check what goes wrong with caching map to disk (Kray)
\r
143 Block object server side:
\r
144 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
145 - For all blocks in the buffer, objects are stepped(). This
\r
146 means they are active.
\r
147 - TODO: A global active buffer is needed for the server
\r
148 - TODO: A timestamp to blocks
\r
149 - TODO: All blocks going in and out of the buffer are recorded.
\r
150 - TODO: For outgoing blocks, timestamp is written.
\r
151 - TODO: For incoming blocks, time difference is calculated and
\r
152 objects are stepped according to it.
\r
154 TODO: Better handling of objects and mobs
\r
156 - There has to be some way to do it with less spaghetti code
\r
157 - Make separate classes for client and server
\r
158 - Client should not discriminate between blocks, server should
\r
159 - Make other players utilize the same framework
\r
160 - This is also needed for objects that don't get sent to client
\r
161 but are used for triggers etc
\r
163 TODO: Draw big amounts of torches better (that is, throw them in the
\r
164 same meshbuffer (can the meshcollector class be used?))
\r
166 TODO: Make an option to the server to disable building and digging near
\r
167 the starting position
\r
169 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
170 need an additional metadata field for the texts
\r
171 - This is also needed for item container chests
\r
172 TODO: There has to be some better way to handle static objects than to
\r
173 send them all the time. This affects signs and item objects.
\r
175 TODO: When server sees that client is removing an inexistent block or
\r
176 adding a block to an existent position, resend the MapBlock.
\r
178 TODO: When player dies, throw items on map
\r
180 TODO: Optimize day/night mesh updating somehow
\r
181 - create copies of all textures for all lighting values and only
\r
182 change texture for material?
\r
183 - Umm... the collecting of the faces is the slow part
\r
184 -> what about just changing the color values of the existing
\r
185 meshbuffers? It should go quite fast.
\r
187 TODO: Map generator version 2
\r
188 - Create surface areas based on central points; a given point's
\r
189 area type is given by the nearest central point
\r
192 TODO: A Constant for the "../data/" path (differs on Mac and on proper
\r
193 linux installations)
\r
195 TODO: Add defined(__APPLE__) to filesys.cpp
\r
198 ======================================================================
\r
200 ======================================================================
\r
205 Setting this to 1 enables a special camera mode that forces
\r
206 the renderers to think that the camera statically points from
\r
207 the starting place to a static direction.
\r
209 This allows one to move around with the player and see what
\r
210 is actually drawn behind solid things and behind the player.
\r
212 #define FIELD_OF_VIEW_TEST 0
\r
214 #ifdef UNITTEST_DISABLE
\r
216 #pragma message ("Disabling unit tests")
\r
218 #warning "Disabling unit tests"
\r
220 // Disable unit tests
\r
221 #define ENABLE_TESTS 0
\r
223 // Enable unit tests
\r
224 #define ENABLE_TESTS 1
\r
228 #pragma comment(lib, "Irrlicht.lib")
\r
229 #pragma comment(lib, "jthread.lib")
\r
230 #pragma comment(lib, "zlibwapi.lib")
\r
231 // This would get rid of the console window
\r
232 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
235 #include <iostream>
\r
237 #include <jmutexautolock.h>
\r
238 #include <locale.h>
\r
239 #include "common_irrlicht.h"
\r
242 #include "player.h"
\r
245 #include "environment.h"
\r
246 #include "server.h"
\r
247 #include "client.h"
\r
248 #include "serialization.h"
\r
249 #include "constants.h"
\r
250 #include "strfnd.h"
\r
251 #include "porting.h"
\r
252 #include "irrlichtwrapper.h"
\r
253 #include "gettime.h"
\r
254 #include "porting.h"
\r
255 #include "guiPauseMenu.h"
\r
256 #include "guiInventoryMenu.h"
\r
257 #include "guiTextInputMenu.h"
\r
258 #include "materials.h"
\r
259 #include "guiMessageMenu.h"
\r
261 IrrlichtWrapper *g_irrlicht;
\r
263 MapDrawControl draw_control;
\r
267 These are loaded from the config file.
\r
270 Settings g_settings;
\r
272 extern void set_default_settings();
\r
278 IrrlichtDevice *g_device = NULL;
\r
279 Client *g_client = NULL;
\r
284 gui::IGUIEnvironment* guienv = NULL;
\r
285 gui::IGUIStaticText *guiroot = NULL;
\r
286 int g_active_menu_count = 0;
\r
288 bool noMenuActive()
\r
290 return (g_active_menu_count == 0);
\r
293 // Inventory actions from the menu are buffered here before sending
\r
294 Queue<InventoryAction*> inventory_action_queue;
\r
295 // This is a copy of the inventory that the client's environment has
\r
296 Inventory local_inventory;
\r
298 u16 g_selected_item = 0;
\r
305 std::ostream *dout_con_ptr = &dummyout;
\r
306 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
307 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
308 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
309 //std::ostream *dout_con_ptr = &dstream;
\r
310 //std::ostream *derr_con_ptr = &dstream;
\r
313 std::ostream *dout_server_ptr = &dstream;
\r
314 std::ostream *derr_server_ptr = &dstream;
\r
317 std::ostream *dout_client_ptr = &dstream;
\r
318 std::ostream *derr_client_ptr = &dstream;
\r
321 gettime.h implementation
\r
327 Use irrlicht because it is more precise than porting.h's
\r
330 if(g_irrlicht == NULL)
\r
332 return g_irrlicht->getTime();
\r
339 struct TextDestSign : public TextDest
\r
341 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
343 m_blockpos = blockpos;
\r
347 void gotText(std::wstring text)
\r
349 std::string ntext = wide_to_narrow(text);
\r
350 dstream<<"Changing text of a sign object: "
\r
351 <<ntext<<std::endl;
\r
352 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
360 struct TextDestChat : public TextDest
\r
362 TextDestChat(Client *client)
\r
366 void gotText(std::wstring text)
\r
368 m_client->sendChatMessage(text);
\r
369 m_client->addChatMessage(text);
\r
375 class MyEventReceiver : public IEventReceiver
\r
378 // This is the one method that we have to implement
\r
379 virtual bool OnEvent(const SEvent& event)
\r
382 React to nothing here if a menu is active
\r
384 if(noMenuActive() == false)
\r
390 // Remember whether each key is down or up
\r
391 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
393 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
395 if(event.KeyInput.PressedDown)
\r
397 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
403 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
405 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
407 dstream<<DTIME<<"MyEventReceiver: "
\r
408 <<"Launching pause menu"<<std::endl;
\r
409 // It will delete itself by itself
\r
410 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
411 &g_active_menu_count))->drop();
\r
414 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
416 dstream<<DTIME<<"MyEventReceiver: "
\r
417 <<"Launching inventory"<<std::endl;
\r
418 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
419 &local_inventory, &inventory_action_queue,
\r
420 &g_active_menu_count))->drop();
\r
423 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
425 TextDest *dest = new TextDestChat(g_client);
\r
427 (new GUITextInputMenu(guienv, guiroot, -1,
\r
428 &g_active_menu_count, dest,
\r
433 // Material selection
\r
434 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
436 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
439 g_selected_item = 0;
\r
440 dstream<<DTIME<<"Selected item: "
\r
441 <<g_selected_item<<std::endl;
\r
444 // Viewing range selection
\r
445 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
447 if(draw_control.range_all)
\r
449 draw_control.range_all = false;
\r
450 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
454 draw_control.range_all = true;
\r
455 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
459 // Print debug stacks
\r
460 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
462 dstream<<"-----------------------------------------"
\r
464 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
465 dstream<<"-----------------------------------------"
\r
467 debug_stacks_print();
\r
472 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
474 if(noMenuActive() == false)
\r
476 left_active = false;
\r
477 middle_active = false;
\r
478 right_active = false;
\r
482 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
483 left_active = event.MouseInput.isLeftPressed();
\r
484 middle_active = event.MouseInput.isMiddlePressed();
\r
485 right_active = event.MouseInput.isRightPressed();
\r
487 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
489 leftclicked = true;
\r
491 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
493 rightclicked = true;
\r
495 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
497 leftreleased = true;
\r
499 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
501 rightreleased = true;
\r
503 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
505 /*dstream<<"event.MouseInput.Wheel="
\r
506 <<event.MouseInput.Wheel<<std::endl;*/
\r
507 if(event.MouseInput.Wheel < 0)
\r
509 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
512 g_selected_item = 0;
\r
514 else if(event.MouseInput.Wheel > 0)
\r
516 if(g_selected_item > 0)
\r
519 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
528 // This is used to check whether a key is being held down
\r
529 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
531 return keyIsDown[keyCode];
\r
536 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
537 keyIsDown[i] = false;
\r
539 leftclicked = false;
\r
540 rightclicked = false;
\r
541 leftreleased = false;
\r
542 rightreleased = false;
\r
544 left_active = false;
\r
545 middle_active = false;
\r
546 right_active = false;
\r
557 bool rightreleased;
\r
560 bool middle_active;
\r
564 // We use this array to store the current state of each key
\r
565 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
568 IrrlichtDevice *m_device;
\r
577 virtual ~InputHandler()
\r
581 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
583 virtual v2s32 getMousePos() = 0;
\r
584 virtual void setMousePos(s32 x, s32 y) = 0;
\r
586 virtual bool getLeftState() = 0;
\r
587 virtual bool getRightState() = 0;
\r
589 virtual bool getLeftClicked() = 0;
\r
590 virtual bool getRightClicked() = 0;
\r
591 virtual void resetLeftClicked() = 0;
\r
592 virtual void resetRightClicked() = 0;
\r
594 virtual bool getLeftReleased() = 0;
\r
595 virtual bool getRightReleased() = 0;
\r
596 virtual void resetLeftReleased() = 0;
\r
597 virtual void resetRightReleased() = 0;
\r
599 virtual void step(float dtime) {};
\r
601 virtual void clear() {};
\r
604 InputHandler *g_input = NULL;
\r
606 class RealInputHandler : public InputHandler
\r
609 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
611 m_receiver(receiver)
\r
614 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
616 return m_receiver->IsKeyDown(keyCode);
\r
618 virtual v2s32 getMousePos()
\r
620 return m_device->getCursorControl()->getPosition();
\r
622 virtual void setMousePos(s32 x, s32 y)
\r
624 m_device->getCursorControl()->setPosition(x, y);
\r
627 virtual bool getLeftState()
\r
629 return m_receiver->left_active;
\r
631 virtual bool getRightState()
\r
633 return m_receiver->right_active;
\r
636 virtual bool getLeftClicked()
\r
638 return m_receiver->leftclicked;
\r
640 virtual bool getRightClicked()
\r
642 return m_receiver->rightclicked;
\r
644 virtual void resetLeftClicked()
\r
646 m_receiver->leftclicked = false;
\r
648 virtual void resetRightClicked()
\r
650 m_receiver->rightclicked = false;
\r
653 virtual bool getLeftReleased()
\r
655 return m_receiver->leftreleased;
\r
657 virtual bool getRightReleased()
\r
659 return m_receiver->rightreleased;
\r
661 virtual void resetLeftReleased()
\r
663 m_receiver->leftreleased = false;
\r
665 virtual void resetRightReleased()
\r
667 m_receiver->rightreleased = false;
\r
672 resetRightClicked();
\r
673 resetLeftClicked();
\r
676 IrrlichtDevice *m_device;
\r
677 MyEventReceiver *m_receiver;
\r
680 class RandomInputHandler : public InputHandler
\r
683 RandomInputHandler()
\r
685 leftclicked = false;
\r
686 rightclicked = false;
\r
687 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
688 keydown[i] = false;
\r
690 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
692 return keydown[keyCode];
\r
694 virtual v2s32 getMousePos()
\r
698 virtual void setMousePos(s32 x, s32 y)
\r
700 mousepos = v2s32(x,y);
\r
703 virtual bool getLeftState()
\r
707 virtual bool getRightState()
\r
712 virtual bool getLeftClicked()
\r
714 return leftclicked;
\r
716 virtual bool getRightClicked()
\r
718 return rightclicked;
\r
720 virtual void resetLeftClicked()
\r
722 leftclicked = false;
\r
724 virtual void resetRightClicked()
\r
726 rightclicked = false;
\r
729 virtual bool getLeftReleased()
\r
733 virtual bool getRightReleased()
\r
737 virtual void resetLeftReleased()
\r
740 virtual void resetRightReleased()
\r
744 virtual void step(float dtime)
\r
747 static float counter1 = 0;
\r
751 counter1 = 0.1*Rand(1,10);
\r
752 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
753 g_selected_material++;
\r
755 g_selected_material = 0;*/
\r
756 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
759 g_selected_item = 0;
\r
763 static float counter1 = 0;
\r
767 counter1 = 0.1*Rand(1, 40);
\r
768 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
772 static float counter1 = 0;
\r
776 counter1 = 0.1*Rand(1, 40);
\r
777 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
781 static float counter1 = 0;
\r
785 counter1 = 0.1*Rand(1, 40);
\r
786 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
790 static float counter1 = 0;
\r
794 counter1 = 0.1*Rand(1, 40);
\r
795 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
799 static float counter1 = 0;
\r
803 counter1 = 0.1*Rand(1, 20);
\r
804 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
808 static float counter1 = 0;
\r
812 counter1 = 0.1*Rand(1, 30);
\r
813 leftclicked = true;
\r
817 static float counter1 = 0;
\r
821 counter1 = 0.1*Rand(1, 20);
\r
822 rightclicked = true;
\r
825 mousepos += mousespeed;
\r
828 s32 Rand(s32 min, s32 max)
\r
830 return (myrand()%(max-min+1))+min;
\r
833 bool keydown[KEY_KEY_CODES_COUNT];
\r
840 void updateViewingRange(f32 frametime_in, Client *client)
\r
842 if(draw_control.range_all == true)
\r
845 static f32 added_frametime = 0;
\r
846 static s16 added_frames = 0;
\r
848 added_frametime += frametime_in;
\r
851 // Actually this counter kind of sucks because frametime is busytime
\r
852 static f32 counter = 0;
\r
853 counter -= frametime_in;
\r
859 /*dstream<<__FUNCTION_NAME
\r
860 <<": Collected "<<added_frames<<" frames, total of "
\r
861 <<added_frametime<<"s."<<std::endl;*/
\r
863 /*dstream<<"draw_control.blocks_drawn="
\r
864 <<draw_control.blocks_drawn
\r
865 <<", draw_control.blocks_would_have_drawn="
\r
866 <<draw_control.blocks_would_have_drawn
\r
869 float range_min = g_settings.getS16("viewing_range_nodes_min");
\r
870 float range_max = g_settings.getS16("viewing_range_nodes_max");
\r
872 draw_control.wanted_min_range = range_min;
\r
873 draw_control.wanted_max_blocks = (1.2*draw_control.blocks_drawn)+1;
\r
875 float block_draw_ratio = 1.0;
\r
876 if(draw_control.blocks_would_have_drawn != 0)
\r
878 block_draw_ratio = (float)draw_control.blocks_drawn
\r
879 / (float)draw_control.blocks_would_have_drawn;
\r
882 // Calculate the average frametime in the case that all wanted
\r
883 // blocks had been drawn
\r
884 f32 frametime = added_frametime / added_frames / block_draw_ratio;
\r
886 added_frametime = 0.0;
\r
889 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
890 float wanted_frametime = 1.0 / wanted_fps;
\r
892 f32 wanted_frametime_change = wanted_frametime - frametime;
\r
893 //dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
\r
895 // If needed frametime change is very small, just return
\r
896 if(fabs(wanted_frametime_change) < wanted_frametime*0.2)
\r
898 //dstream<<"ignoring small wanted_frametime_change"<<std::endl;
\r
902 float range = draw_control.wanted_range;
\r
903 float new_range = range;
\r
905 static s16 range_old = 0;
\r
906 static f32 frametime_old = 0;
\r
908 float d_range = range - range_old;
\r
909 f32 d_frametime = frametime - frametime_old;
\r
910 // A sane default of 30ms per 50 nodes of range
\r
911 static f32 time_per_range = 30. / 50;
\r
914 time_per_range = d_frametime / d_range;
\r
917 // The minimum allowed calculated frametime-range derivative:
\r
918 // Practically this sets the maximum speed of changing the range.
\r
919 // The lower this value, the higher the maximum changing speed.
\r
920 // A low value here results in wobbly range (0.001)
\r
921 // A high value here results in slow changing range (0.0025)
\r
922 // SUGG: This could be dynamically adjusted so that when
\r
923 // the camera is turning, this is lower
\r
924 //float min_time_per_range = 0.0015;
\r
925 float min_time_per_range = 0.0010;
\r
926 //float min_time_per_range = 0.05 / range;
\r
927 if(time_per_range < min_time_per_range)
\r
929 time_per_range = min_time_per_range;
\r
930 //dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
\r
934 //dstream<<"time_per_range="<<time_per_range<<std::endl;
\r
937 f32 wanted_range_change = wanted_frametime_change / time_per_range;
\r
938 // Dampen the change a bit to kill oscillations
\r
939 //wanted_range_change *= 0.9;
\r
940 //wanted_range_change *= 0.75;
\r
941 wanted_range_change *= 0.5;
\r
942 //dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
\r
944 // If needed range change is very small, just return
\r
945 if(fabs(wanted_range_change) < 0.001)
\r
947 //dstream<<"ignoring small wanted_range_change"<<std::endl;
\r
951 new_range += wanted_range_change;
\r
952 //dstream<<"new_range="<<new_range/*<<std::endl*/;
\r
954 //float new_range_unclamped = new_range;
\r
955 if(new_range < range_min)
\r
956 new_range = range_min;
\r
957 if(new_range > range_max)
\r
958 new_range = range_max;
\r
960 /*if(new_range != new_range_unclamped)
\r
961 dstream<<", clamped to "<<new_range<<std::endl;
\r
963 dstream<<std::endl;*/
\r
965 draw_control.wanted_range = new_range;
\r
967 range_old = new_range;
\r
968 frametime_old = frametime;
\r
971 class GUIQuickInventory : public IEventReceiver
\r
975 gui::IGUIEnvironment* env,
\r
976 gui::IGUIElement* parent,
\r
979 Inventory *inventory):
\r
980 m_itemcount(itemcount),
\r
981 m_inventory(inventory)
\r
983 core::rect<s32> imgsize(0,0,48,48);
\r
984 core::rect<s32> textsize(0,0,48,16);
\r
985 v2s32 spacing(0, 64);
\r
986 for(s32 i=0; i<m_itemcount; i++)
\r
988 m_images.push_back(env->addImage(
\r
989 imgsize + pos + spacing*i
\r
991 m_images[i]->setScaleImage(true);
\r
992 m_texts.push_back(env->addStaticText(
\r
994 textsize + pos + spacing*i,
\r
997 m_texts[i]->setBackgroundColor(
\r
998 video::SColor(128,0,0,0));
\r
999 m_texts[i]->setTextAlignment(
\r
1000 gui::EGUIA_CENTER,
\r
1001 gui::EGUIA_UPPERLEFT);
\r
1005 virtual bool OnEvent(const SEvent& event)
\r
1010 void setSelection(s32 i)
\r
1019 start = m_selection - m_itemcount / 2;
\r
1021 InventoryList *mainlist = m_inventory->getList("main");
\r
1023 for(s32 i=0; i<m_itemcount; i++)
\r
1025 s32 j = i + start;
\r
1027 if(j > (s32)mainlist->getSize() - 1)
\r
1028 j -= mainlist->getSize();
\r
1030 j += mainlist->getSize();
\r
1032 InventoryItem *item = mainlist->getItem(j);
\r
1036 m_images[i]->setImage(NULL);
\r
1039 if(m_selection == j)
\r
1040 swprintf(t, 10, L"<-");
\r
1042 swprintf(t, 10, L"");
\r
1043 m_texts[i]->setText(t);
\r
1045 // The next ifs will segfault with a NULL pointer
\r
1050 m_images[i]->setImage(item->getImage());
\r
1053 if(m_selection == j)
\r
1054 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1056 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1057 m_texts[i]->setText(t);
\r
1063 core::array<gui::IGUIStaticText*> m_texts;
\r
1064 core::array<gui::IGUIImage*> m_images;
\r
1065 Inventory *m_inventory;
\r
1076 ChatLine(const std::wstring &a_text):
\r
1082 std::wstring text;
\r
1085 int main(int argc, char *argv[])
\r
1088 Low-level initialization
\r
1091 bool disable_stderr = false;
\r
1093 disable_stderr = true;
\r
1096 // Initialize debug streams
\r
1097 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1098 // Initialize debug stacks
\r
1099 debug_stacks_init();
\r
1101 DSTACK(__FUNCTION_NAME);
\r
1103 initializeMaterialProperties();
\r
1105 BEGIN_DEBUG_EXCEPTION_HANDLER
\r
1111 Parse command line
\r
1114 // List all allowed options
\r
1115 core::map<std::string, ValueSpec> allowed_options;
\r
1116 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1117 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1118 "Run server directly"));
\r
1119 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1120 "Load configuration from specified file"));
\r
1121 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1122 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1123 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1124 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1125 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1127 Settings cmd_args;
\r
1129 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1131 if(ret == false || cmd_args.getFlag("help"))
\r
1133 dstream<<"Allowed options:"<<std::endl;
\r
1134 for(core::map<std::string, ValueSpec>::Iterator
\r
1135 i = allowed_options.getIterator();
\r
1136 i.atEnd() == false; i++)
\r
1138 dstream<<" --"<<i.getNode()->getKey();
\r
1139 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1144 dstream<<" <value>";
\r
1146 dstream<<std::endl;
\r
1148 if(i.getNode()->getValue().help != NULL)
\r
1150 dstream<<" "<<i.getNode()->getValue().help
\r
1155 return cmd_args.getFlag("help") ? 0 : 1;
\r
1160 Basic initialization
\r
1163 // Initialize default settings
\r
1164 set_default_settings();
\r
1166 // Print startup message
\r
1167 dstream<<DTIME<<"minetest-c55"
\r
1168 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1169 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1172 // Set locale. This is for forcing '.' as the decimal point.
\r
1173 std::locale::global(std::locale("C"));
\r
1174 // This enables printing all characters in bitmap font
\r
1175 setlocale(LC_CTYPE, "en_US");
\r
1177 // Initialize sockets
\r
1179 atexit(sockets_cleanup);
\r
1189 // Path of configuration file in use
\r
1190 std::string configpath = "";
\r
1192 if(cmd_args.exists("config"))
\r
1194 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1197 dstream<<"Could not read configuration from \""
\r
1198 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1201 configpath = cmd_args.get("config");
\r
1205 const char *filenames[2] =
\r
1207 "../minetest.conf",
\r
1208 "../../minetest.conf"
\r
1211 for(u32 i=0; i<2; i++)
\r
1213 bool r = g_settings.readConfigFile(filenames[i]);
\r
1216 configpath = filenames[i];
\r
1222 // Initialize random seed
\r
1229 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1230 || cmd_args.getFlag("enable-unittests") == true)
\r
1235 // Read map parameters from settings
\r
1237 HMParams hm_params;
\r
1238 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1239 hm_params.randmax = g_settings.get("height_randmax");
\r
1240 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1241 hm_params.base = g_settings.get("height_base");
\r
1243 MapParams map_params;
\r
1244 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1245 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1251 std::cout<<std::endl<<std::endl;
\r
1254 <<" .__ __ __ "<<std::endl
\r
1255 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1256 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1257 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1258 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1259 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1262 std::cout<<std::endl;
\r
1263 //char templine[100];
\r
1267 if(cmd_args.exists("port"))
\r
1269 port = cmd_args.getU16("port");
\r
1273 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1274 std::cout<<"-> "<<port<<std::endl;
\r
1277 if(cmd_args.getFlag("server"))
\r
1279 DSTACK("Dedicated server branch");
\r
1281 std::cout<<std::endl;
\r
1282 std::cout<<"========================"<<std::endl;
\r
1283 std::cout<<"Running dedicated server"<<std::endl;
\r
1284 std::cout<<"========================"<<std::endl;
\r
1285 std::cout<<std::endl;
\r
1287 Server server("../map", hm_params, map_params);
\r
1288 server.start(port);
\r
1292 // This is kind of a hack but can be done like this
\r
1293 // because server.step() is very light
\r
1295 server.step(0.030);
\r
1297 static int counter = 0;
\r
1303 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1304 core::list<PlayerInfo>::Iterator i;
\r
1305 static u32 sum_old = 0;
\r
1306 u32 sum = PIChecksum(list);
\r
1307 if(sum != sum_old)
\r
1309 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1310 for(i=list.begin(); i!=list.end(); i++)
\r
1312 i->PrintLine(&std::cout);
\r
1322 bool hosting = false;
\r
1323 char connect_name[100] = "";
\r
1325 if(cmd_args.exists("address"))
\r
1327 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1329 else if(is_yes(g_settings.get("host_game")) == false)
\r
1331 if(g_settings.get("address") != "")
\r
1333 std::cout<<g_settings.get("address")<<std::endl;
\r
1334 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1338 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1339 std::cin.getline(connect_name, 100);
\r
1343 if(connect_name[0] == 0){
\r
1344 snprintf(connect_name, 100, "127.0.0.1");
\r
1349 std::cout<<"> Hosting game"<<std::endl;
\r
1351 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1353 char playername[PLAYERNAME_SIZE] = "";
\r
1354 if(g_settings.get("name") != "")
\r
1356 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1360 std::cout<<"Name of player: ";
\r
1361 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1363 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1366 Resolution selection
\r
1369 bool fullscreen = false;
\r
1370 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1371 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1375 MyEventReceiver receiver;
\r
1377 video::E_DRIVER_TYPE driverType;
\r
1380 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1381 driverType = video::EDT_OPENGL;
\r
1383 driverType = video::EDT_OPENGL;
\r
1386 // create device and exit if creation failed
\r
1388 IrrlichtDevice *device;
\r
1389 device = createDevice(driverType,
\r
1390 core::dimension2d<u32>(screenW, screenH),
\r
1391 16, fullscreen, false, false, &receiver);
\r
1394 return 1; // could not create selected driver.
\r
1396 g_device = device;
\r
1397 g_irrlicht = new IrrlichtWrapper(device);
\r
1399 //g_device = device;
\r
1401 device->setResizable(true);
\r
1403 bool random_input = g_settings.getBool("random_input")
\r
1404 || cmd_args.getFlag("random-input");
\r
1406 g_input = new RandomInputHandler();
\r
1408 g_input = new RealInputHandler(device, &receiver);
\r
1411 Continue initialization
\r
1414 video::IVideoDriver* driver = device->getVideoDriver();
\r
1417 This changes the minimum allowed number of vertices in a VBO
\r
1419 //driver->setMinHardwareBufferVertexCount(50);
\r
1421 scene::ISceneManager* smgr = device->getSceneManager();
\r
1423 guienv = device->getGUIEnvironment();
\r
1424 gui::IGUISkin* skin = guienv->getSkin();
\r
1425 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1427 skin->setFont(font);
\r
1429 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1430 dstream<<"text_height="<<text_height<<std::endl;
\r
1432 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1433 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1434 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1435 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1436 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1437 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1439 const wchar_t *text = L"Loading and connecting...";
\r
1440 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1441 core::vector2d<s32> textsize(300, text_height);
\r
1442 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1444 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1445 text, textrect, false, false);
\r
1446 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1448 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1449 guienv->drawAll();
\r
1450 driver->endScene();
\r
1453 Preload some textures
\r
1456 tile_materials_preload(g_irrlicht);
\r
1459 Make a scope here for the client so that it gets removed
\r
1460 before the irrlicht device
\r
1464 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1469 SharedPtr<Server> server;
\r
1471 server = new Server("../map", hm_params, map_params);
\r
1472 server->start(port);
\r
1479 Client client(device, playername, draw_control);
\r
1481 g_client = &client;
\r
1483 Address connect_address(0,0,0,0, port);
\r
1485 connect_address.Resolve(connect_name);
\r
1487 catch(ResolveError &e)
\r
1489 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1493 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1494 client.connect(connect_address);
\r
1497 while(client.connectedAndInitialized() == false)
\r
1500 if(server != NULL){
\r
1501 server->step(0.1);
\r
1506 catch(con::PeerNotFoundException &e)
\r
1508 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1515 /*scene::ISceneNode* skybox;
\r
1516 skybox = smgr->addSkyBoxSceneNode(
\r
1517 driver->getTexture("../data/skybox2.png"),
\r
1518 driver->getTexture("../data/skybox3.png"),
\r
1519 driver->getTexture("../data/skybox1.png"),
\r
1520 driver->getTexture("../data/skybox1.png"),
\r
1521 driver->getTexture("../data/skybox1.png"),
\r
1522 driver->getTexture("../data/skybox1.png"));*/
\r
1525 Create the camera node
\r
1528 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1529 0, // Camera parent
\r
1530 v3f(BS*100, BS*2, BS*100), // Look from
\r
1531 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1535 if(camera == NULL)
\r
1538 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1540 camera->setFOV(FOV_ANGLE);
\r
1542 // Just so big a value that everything rendered is visible
\r
1543 camera->setFarValue(100000*BS);
\r
1545 f32 camera_yaw = 0; // "right/left"
\r
1546 f32 camera_pitch = 0; // "up/down"
\r
1552 gui_loadingtext->remove();
\r
1555 Add some gui stuff
\r
1558 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1559 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1562 We need some kind of a root node to be able to add
\r
1563 custom elements directly on the screen.
\r
1564 Otherwise they won't be automatically drawn.
\r
1566 guiroot = guienv->addStaticText(L"",
\r
1567 core::rect<s32>(0, 0, 10000, 10000));
\r
1569 // Test the text input system
\r
1570 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1572 /*GUIMessageMenu *menu =
\r
1573 new GUIMessageMenu(guienv, guiroot, -1,
\r
1574 &g_active_menu_count,
\r
1578 // Launch pause menu
\r
1579 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1580 &g_active_menu_count))->drop();
\r
1582 // First line of debug text
\r
1583 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1585 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1587 // Second line of debug text
\r
1588 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1590 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1593 // At the middle of the screen
\r
1594 // Object infos are shown in this
\r
1595 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1597 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1601 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1602 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1603 core::rect<s32>(70, 60, 795, 150),
\r
1605 chat_guitext->setBackgroundColor(video::SColor(96,0,0,0));
\r
1606 core::list<ChatLine> chat_lines;
\r
1609 Some statistics are collected in these
\r
1612 u32 beginscenetime = 0;
\r
1613 u32 scenetime = 0;
\r
1614 u32 endscenetime = 0;
\r
1617 //throw con::PeerNotFoundException("lol");
\r
1623 bool first_loop_after_window_activation = true;
\r
1625 // Time is in milliseconds
\r
1626 // NOTE: getRealTime() causes strange problems in wine (imprecision?)
\r
1627 // NOTE: So we have to use getTime() and call run()s between them
\r
1628 u32 lasttime = device->getTimer()->getTime();
\r
1630 while(device->run())
\r
1633 Run global IrrlichtWrapper's main thread processing stuff
\r
1635 g_irrlicht->Run();
\r
1638 Random calculations
\r
1640 v2u32 screensize = driver->getScreenSize();
\r
1641 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1643 // Hilight boxes collected during the loop and displayed
\r
1644 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1647 std::wstring infotext;
\r
1649 //TimeTaker //timer1("//timer1");
\r
1651 // Time of frame without fps limit
\r
1655 // not using getRealTime is necessary for wine
\r
1656 u32 time = device->getTimer()->getTime();
\r
1657 if(time > lasttime)
\r
1658 busytime_u32 = time - lasttime;
\r
1661 busytime = busytime_u32 / 1000.0;
\r
1664 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1666 // Absolutelu necessary for wine!
\r
1673 updateViewingRange(busytime, &client);
\r
1680 float fps_max = g_settings.getFloat("fps_max");
\r
1681 u32 frametime_min = 1000./fps_max;
\r
1683 if(busytime_u32 < frametime_min)
\r
1685 u32 sleeptime = frametime_min - busytime_u32;
\r
1686 device->sleep(sleeptime);
\r
1690 // Absolutelu necessary for wine!
\r
1694 Time difference calculation
\r
1696 f32 dtime; // in seconds
\r
1698 u32 time = device->getTimer()->getTime();
\r
1699 if(time > lasttime)
\r
1700 dtime = (time - lasttime) / 1000.0;
\r
1706 Time average and jitter calculation
\r
1709 static f32 dtime_avg1 = 0.0;
\r
1710 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1711 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1713 static f32 dtime_jitter1_max_sample = 0.0;
\r
1714 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1716 static f32 jitter1_max = 0.0;
\r
1717 static f32 counter = 0.0;
\r
1718 if(dtime_jitter1 > jitter1_max)
\r
1719 jitter1_max = dtime_jitter1;
\r
1724 dtime_jitter1_max_sample = jitter1_max;
\r
1725 dtime_jitter1_max_fraction
\r
1726 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1727 jitter1_max = 0.0;
\r
1730 Control freetime ratio
\r
1732 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1734 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1735 g_freetime_ratio += 0.01;
\r
1739 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1740 g_freetime_ratio -= 0.01;
\r
1746 Busytime average and jitter calculation
\r
1749 static f32 busytime_avg1 = 0.0;
\r
1750 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1751 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1753 static f32 busytime_jitter1_max_sample = 0.0;
\r
1754 static f32 busytime_jitter1_min_sample = 0.0;
\r
1756 static f32 jitter1_max = 0.0;
\r
1757 static f32 jitter1_min = 0.0;
\r
1758 static f32 counter = 0.0;
\r
1759 if(busytime_jitter1 > jitter1_max)
\r
1760 jitter1_max = busytime_jitter1;
\r
1761 if(busytime_jitter1 < jitter1_min)
\r
1762 jitter1_min = busytime_jitter1;
\r
1764 if(counter > 0.0){
\r
1766 busytime_jitter1_max_sample = jitter1_max;
\r
1767 busytime_jitter1_min_sample = jitter1_min;
\r
1768 jitter1_max = 0.0;
\r
1769 jitter1_min = 0.0;
\r
1774 Debug info for client
\r
1777 static float counter = 0.0;
\r
1782 client.printDebugInfo(std::cout);
\r
1787 Input handler step()
\r
1789 g_input->step(dtime);
\r
1792 Player speed control
\r
1801 bool a_superspeed,
\r
1804 PlayerControl control(
\r
1805 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1806 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1807 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1808 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1809 g_input->isKeyDown(irr::KEY_SPACE),
\r
1810 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1814 client.setPlayerControl(control);
\r
1818 Process environment
\r
1822 //TimeTaker timer("client.step(dtime)");
\r
1823 client.step(dtime);
\r
1824 //client.step(dtime_avg1);
\r
1827 if(server != NULL)
\r
1829 //TimeTaker timer("server->step(dtime)");
\r
1830 server->step(dtime);
\r
1833 v3f player_position = client.getPlayerPosition();
\r
1835 //TimeTaker //timer2("//timer2");
\r
1838 Mouse and camera control
\r
1841 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1844 device->getCursorControl()->setVisible(false);
\r
1846 if(first_loop_after_window_activation){
\r
1847 //std::cout<<"window active, first loop"<<std::endl;
\r
1848 first_loop_after_window_activation = false;
\r
1851 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1852 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1853 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1854 camera_yaw -= dx*0.2;
\r
1855 camera_pitch += dy*0.2;
\r
1856 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1857 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1859 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1862 device->getCursorControl()->setVisible(true);
\r
1864 //std::cout<<"window inactive"<<std::endl;
\r
1865 first_loop_after_window_activation = true;
\r
1868 camera_yaw = wrapDegrees(camera_yaw);
\r
1869 camera_pitch = wrapDegrees(camera_pitch);
\r
1871 v3f camera_direction = v3f(0,0,1);
\r
1872 camera_direction.rotateYZBy(camera_pitch);
\r
1873 camera_direction.rotateXZBy(camera_yaw);
\r
1875 // This is at the height of the eyes of the current figure
\r
1876 v3f camera_position =
\r
1877 player_position + v3f(0, BS+BS/2, 0);
\r
1878 // This is more like in minecraft
\r
1879 /*v3f camera_position =
\r
1880 player_position + v3f(0, BS+BS*0.65, 0);*/
\r
1882 camera->setPosition(camera_position);
\r
1883 // *100.0 helps in large map coordinates
\r
1884 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1886 if(FIELD_OF_VIEW_TEST){
\r
1887 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1888 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1891 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1892 //TimeTaker timer("client.updateCamera");
\r
1893 client.updateCamera(camera_position, camera_direction);
\r
1897 //TimeTaker //timer3("//timer3");
\r
1900 Calculate what block is the crosshair pointing to
\r
1903 //u32 t1 = device->getTimer()->getRealTime();
\r
1905 //f32 d = 4; // max. distance
\r
1906 f32 d = 4; // max. distance
\r
1907 core::line3d<f32> shootline(camera_position,
\r
1908 camera_position + camera_direction * BS * (d+1));
\r
1910 MapBlockObject *selected_object = client.getSelectedObject
\r
1911 (d*BS, camera_position, shootline);
\r
1914 If it's pointing to a MapBlockObject
\r
1917 if(selected_object != NULL)
\r
1919 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1921 core::aabbox3d<f32> box_on_map
\r
1922 = selected_object->getSelectionBoxOnMap();
\r
1924 hilightboxes.push_back(box_on_map);
\r
1926 infotext = narrow_to_wide(selected_object->infoText());
\r
1928 if(g_input->getLeftClicked())
\r
1930 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1931 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1932 selected_object->getId(), g_selected_item);
\r
1934 else if(g_input->getRightClicked())
\r
1936 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1938 Check if we want to modify the object ourselves
\r
1940 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1942 dstream<<"Sign object right-clicked"<<std::endl;
\r
1944 if(random_input == false)
\r
1946 // Get a new text for it
\r
1948 TextDest *dest = new TextDestSign(
\r
1949 selected_object->getBlock()->getPos(),
\r
1950 selected_object->getId(),
\r
1953 SignObject *sign_object = (SignObject*)selected_object;
\r
1955 std::wstring wtext =
\r
1956 narrow_to_wide(sign_object->getText());
\r
1958 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1959 &g_active_menu_count, dest,
\r
1964 Otherwise pass the event to the server as-is
\r
1968 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1969 selected_object->getId(), g_selected_item);
\r
1973 else // selected_object == NULL
\r
1977 Find out which node we are pointing at
\r
1980 bool nodefound = false;
\r
1982 v3s16 neighbourpos;
\r
1983 core::aabbox3d<f32> nodefacebox;
\r
1984 f32 mindistance = BS * 1001;
\r
1986 v3s16 pos_i = floatToInt(player_position);
\r
1988 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1992 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1993 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1994 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1995 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1996 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1997 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1999 for(s16 y = ystart; y <= yend; y++)
\r
2000 for(s16 z = zstart; z <= zend; z++)
\r
2001 for(s16 x = xstart; x <= xend; x++)
\r
2006 n = client.getNode(v3s16(x,y,z));
\r
2007 if(content_pointable(n.d) == false)
\r
2010 catch(InvalidPositionException &e)
\r
2016 v3f npf = intToFloat(np);
\r
2021 v3s16(0,0,1), // back
\r
2022 v3s16(0,1,0), // top
\r
2023 v3s16(1,0,0), // right
\r
2024 v3s16(0,0,-1), // front
\r
2025 v3s16(0,-1,0), // bottom
\r
2026 v3s16(-1,0,0), // left
\r
2032 if(n.d == CONTENT_TORCH)
\r
2034 v3s16 dir = unpackDir(n.dir);
\r
2035 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2036 dir_f *= BS/2 - BS/6 - BS/20;
\r
2037 v3f cpf = npf + dir_f;
\r
2038 f32 distance = (cpf - camera_position).getLength();
\r
2040 core::aabbox3d<f32> box;
\r
2043 if(dir == v3s16(0,-1,0))
\r
2045 box = core::aabbox3d<f32>(
\r
2046 npf - v3f(BS/6, BS/2, BS/6),
\r
2047 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2051 else if(dir == v3s16(0,1,0))
\r
2053 box = core::aabbox3d<f32>(
\r
2054 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2055 npf + v3f(BS/6, BS/2, BS/6)
\r
2061 box = core::aabbox3d<f32>(
\r
2062 cpf - v3f(BS/6, BS/3, BS/6),
\r
2063 cpf + v3f(BS/6, BS/3, BS/6)
\r
2067 if(distance < mindistance)
\r
2069 if(box.intersectsWithLine(shootline))
\r
2073 neighbourpos = np;
\r
2074 mindistance = distance;
\r
2075 nodefacebox = box;
\r
2084 for(u16 i=0; i<6; i++)
\r
2086 v3f dir_f = v3f(dirs[i].X,
\r
2087 dirs[i].Y, dirs[i].Z);
\r
2088 v3f centerpoint = npf + dir_f * BS/2;
\r
2090 (centerpoint - camera_position).getLength();
\r
2092 if(distance < mindistance)
\r
2094 core::CMatrix4<f32> m;
\r
2095 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2097 // This is the back face
\r
2098 v3f corners[2] = {
\r
2099 v3f(BS/2, BS/2, BS/2),
\r
2100 v3f(-BS/2, -BS/2, BS/2+d)
\r
2103 for(u16 j=0; j<2; j++)
\r
2105 m.rotateVect(corners[j]);
\r
2106 corners[j] += npf;
\r
2109 core::aabbox3d<f32> facebox(corners[0]);
\r
2110 facebox.addInternalPoint(corners[1]);
\r
2112 if(facebox.intersectsWithLine(shootline))
\r
2116 neighbourpos = np + dirs[i];
\r
2117 mindistance = distance;
\r
2118 nodefacebox = facebox;
\r
2120 } // if distance < mindistance
\r
2122 } // regular block
\r
2125 static float nodig_delay_counter = 0.0;
\r
2129 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2131 static float dig_time = 0.0;
\r
2132 static u16 dig_index = 0;
\r
2134 hilightboxes.push_back(nodefacebox);
\r
2136 if(g_input->getLeftReleased())
\r
2138 client.clearTempMod(nodepos);
\r
2142 if(nodig_delay_counter > 0.0)
\r
2144 nodig_delay_counter -= dtime;
\r
2148 if(nodepos != nodepos_old)
\r
2150 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2151 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2153 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2155 client.clearTempMod(nodepos_old);
\r
2160 if(g_input->getLeftClicked() ||
\r
2161 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2163 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2164 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2166 if(g_input->getLeftClicked())
\r
2168 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2170 if(g_input->getLeftState())
\r
2172 MapNode n = client.getNode(nodepos);
\r
2174 // Get tool name. Default is "" = bare hands
\r
2175 std::string toolname = "";
\r
2176 InventoryList *mlist = local_inventory.getList("main");
\r
2179 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2180 if(item && (std::string)item->getName() == "ToolItem")
\r
2182 ToolItem *titem = (ToolItem*)item;
\r
2183 toolname = titem->getToolName();
\r
2187 // Get digging properties for material and tool
\r
2188 u8 material = n.d;
\r
2189 DiggingProperties prop =
\r
2190 getDiggingProperties(material, toolname);
\r
2192 float dig_time_complete = 0.0;
\r
2194 if(prop.diggable == false)
\r
2196 /*dstream<<"Material "<<(int)material
\r
2197 <<" not diggable with \""
\r
2198 <<toolname<<"\""<<std::endl;*/
\r
2199 // I guess nobody will wait for this long
\r
2200 dig_time_complete = 10000000.0;
\r
2204 dig_time_complete = prop.time;
\r
2207 if(dig_time_complete >= 0.001)
\r
2209 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2210 * dig_time/dig_time_complete);
\r
2212 // This is for torches
\r
2215 dig_index = CRACK_ANIMATION_LENGTH;
\r
2218 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2220 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2221 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2225 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2226 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2227 client.clearTempMod(nodepos);
\r
2228 client.removeNode(nodepos);
\r
2232 nodig_delay_counter = dig_time_complete
\r
2233 / (float)CRACK_ANIMATION_LENGTH;
\r
2235 // We don't want a corresponding delay to
\r
2236 // very time consuming nodes
\r
2237 if(nodig_delay_counter > 0.5)
\r
2239 nodig_delay_counter = 0.5;
\r
2241 // We want a slight delay to very little
\r
2242 // time consuming nodes
\r
2243 //float mindelay = 0.15;
\r
2244 float mindelay = 0.20;
\r
2245 if(nodig_delay_counter < mindelay)
\r
2247 nodig_delay_counter = mindelay;
\r
2251 dig_time += dtime;
\r
2255 if(g_input->getRightClicked())
\r
2257 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2258 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2261 nodepos_old = nodepos;
\r
2266 } // selected_object == NULL
\r
2268 g_input->resetLeftClicked();
\r
2269 g_input->resetRightClicked();
\r
2271 if(g_input->getLeftReleased())
\r
2273 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2275 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2277 if(g_input->getRightReleased())
\r
2279 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2283 g_input->resetLeftReleased();
\r
2284 g_input->resetRightReleased();
\r
2287 Calculate stuff for drawing
\r
2290 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2292 u32 daynight_ratio = client.getDayNightRatio();
\r
2293 /*video::SColor bgcolor = video::SColor(
\r
2295 skycolor.getRed() * daynight_ratio / 1000,
\r
2296 skycolor.getGreen() * daynight_ratio / 1000,
\r
2297 skycolor.getBlue() * daynight_ratio / 1000);*/
\r
2299 u8 l = decode_light((daynight_ratio * LIGHT_SUN) / 1000);
\r
2300 video::SColor bgcolor = video::SColor(
\r
2302 skycolor.getRed() * l / 255,
\r
2303 skycolor.getGreen() * l / 255,
\r
2304 skycolor.getBlue() * l / 255);
\r
2310 if(g_settings.getBool("enable_fog") == true)
\r
2312 f32 range = draw_control.wanted_range * BS;
\r
2313 if(draw_control.range_all)
\r
2314 range = 100000*BS;
\r
2318 video::EFT_FOG_LINEAR,
\r
2322 false, // pixel fog
\r
2323 false // range fog
\r
2329 Update gui stuff (0ms)
\r
2332 //TimeTaker guiupdatetimer("Gui updating");
\r
2335 wchar_t temptext[150];
\r
2337 static float drawtime_avg = 0;
\r
2338 drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
\r
2339 static float beginscenetime_avg = 0;
\r
2340 beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
\r
2341 static float scenetime_avg = 0;
\r
2342 scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
\r
2343 static float endscenetime_avg = 0;
\r
2344 endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;
\r
2346 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2348 L", R: range_all=%i"
\r
2350 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2352 draw_control.range_all,
\r
2354 beginscenetime_avg,
\r
2359 guitext->setText(temptext);
\r
2363 wchar_t temptext[150];
\r
2364 swprintf(temptext, 150,
\r
2365 L"(% .1f, % .1f, % .1f)"
\r
2366 L" (% .3f < btime_jitter < % .3f"
\r
2367 L", dtime_jitter = % .1f %%"
\r
2368 L", v_range = %.1f)",
\r
2369 player_position.X/BS,
\r
2370 player_position.Y/BS,
\r
2371 player_position.Z/BS,
\r
2372 busytime_jitter1_min_sample,
\r
2373 busytime_jitter1_max_sample,
\r
2374 dtime_jitter1_max_fraction * 100.0,
\r
2375 draw_control.wanted_range
\r
2378 guitext2->setText(temptext);
\r
2382 guitext_info->setText(infotext.c_str());
\r
2386 Get chat messages from client
\r
2389 // Get new messages
\r
2390 std::wstring message;
\r
2391 while(client.getChatMessage(message))
\r
2393 chat_lines.push_back(ChatLine(message));
\r
2394 /*if(chat_lines.size() > 6)
\r
2396 core::list<ChatLine>::Iterator
\r
2397 i = chat_lines.begin();
\r
2398 chat_lines.erase(i);
\r
2401 // Append them to form the whole static text and throw
\r
2402 // it to the gui element
\r
2403 std::wstring whole;
\r
2404 // This will correspond to the line number counted from
\r
2405 // top to bottom, from size-1 to 0
\r
2406 s16 line_number = chat_lines.size();
\r
2407 // Count of messages to be removed from the top
\r
2408 u16 to_be_removed_count = 0;
\r
2409 for(core::list<ChatLine>::Iterator
\r
2410 i = chat_lines.begin();
\r
2411 i != chat_lines.end(); i++)
\r
2413 // After this, line number is valid for this loop
\r
2416 (*i).age += dtime;
\r
2418 This results in a maximum age of 60*6 to the
\r
2419 lowermost line and a maximum of 6 lines
\r
2421 float allowed_age = (6-line_number) * 60.0;
\r
2423 if((*i).age > allowed_age)
\r
2425 to_be_removed_count++;
\r
2428 whole += (*i).text + L'\n';
\r
2430 for(u16 i=0; i<to_be_removed_count; i++)
\r
2432 core::list<ChatLine>::Iterator
\r
2433 it = chat_lines.begin();
\r
2434 chat_lines.erase(it);
\r
2436 chat_guitext->setText(whole.c_str());
\r
2437 // Update gui element size and position
\r
2438 core::rect<s32> rect(
\r
2440 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2441 screensize.X - 10,
\r
2444 chat_guitext->setRelativePosition(rect);
\r
2446 if(chat_lines.size() == 0)
\r
2447 chat_guitext->setVisible(false);
\r
2449 chat_guitext->setVisible(true);
\r
2456 static u16 old_selected_item = 65535;
\r
2457 if(client.getLocalInventoryUpdated()
\r
2458 || g_selected_item != old_selected_item)
\r
2460 old_selected_item = g_selected_item;
\r
2461 //std::cout<<"Updating local inventory"<<std::endl;
\r
2462 client.getLocalInventory(local_inventory);
\r
2463 quick_inventory->setSelection(g_selected_item);
\r
2464 quick_inventory->update();
\r
2468 Send actions returned by the inventory menu
\r
2470 while(inventory_action_queue.size() != 0)
\r
2472 InventoryAction *a = inventory_action_queue.pop_front();
\r
2474 client.sendInventoryAction(a);
\r
2483 TimeTaker drawtimer("Drawing");
\r
2487 TimeTaker timer("beginScene");
\r
2488 driver->beginScene(true, true, bgcolor);
\r
2489 //driver->beginScene(false, true, bgcolor);
\r
2490 beginscenetime = timer.stop(true);
\r
2495 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2498 TimeTaker timer("smgr");
\r
2500 scenetime = timer.stop(true);
\r
2504 //TimeTaker timer9("auxiliary drawings");
\r
2508 //TimeTaker //timer10("//timer10");
\r
2510 video::SMaterial m;
\r
2512 m.Lighting = false;
\r
2513 driver->setMaterial(m);
\r
2515 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2517 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2518 i != hilightboxes.end(); i++)
\r
2520 /*std::cout<<"hilightbox min="
\r
2521 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2523 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2525 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2531 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2532 displaycenter + core::vector2d<s32>(10,0),
\r
2533 video::SColor(255,255,255,255));
\r
2534 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2535 displaycenter + core::vector2d<s32>(0,10),
\r
2536 video::SColor(255,255,255,255));
\r
2541 //TimeTaker //timer11("//timer11");
\r
2547 guienv->drawAll();
\r
2551 TimeTaker timer("endScene");
\r
2552 driver->endScene();
\r
2553 endscenetime = timer.stop(true);
\r
2556 drawtime = drawtimer.stop(true);
\r
2562 static s16 lastFPS = 0;
\r
2563 //u16 fps = driver->getFPS();
\r
2564 u16 fps = (1.0/dtime_avg1);
\r
2566 if (lastFPS != fps)
\r
2568 core::stringw str = L"Minetest [";
\r
2569 str += driver->getName();
\r
2573 device->setWindowCaption(str.c_str());
\r
2579 device->yield();*/
\r
2582 delete quick_inventory;
\r
2584 } // client is deleted at this point
\r
2589 In the end, delete the Irrlicht device.
\r
2594 Update configuration file
\r
2596 /*if(configpath != "")
\r
2598 g_settings.updateConfigFile(configpath.c_str());
\r
2602 catch(con::PeerNotFoundException &e)
\r
2604 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2608 GUIMessageMenu *menu =
\r
2609 new GUIMessageMenu(guienv, guiroot, -1,
\r
2610 &g_active_menu_count,
\r
2611 L"Connection timed out");
\r
2613 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
2615 dstream<<"Created menu"<<std::endl;
\r
2617 while(g_device->run() && menu->getStatus() == false)
\r
2619 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2620 guienv->drawAll();
\r
2621 driver->endScene();
\r
2624 dstream<<"Dropping menu"<<std::endl;
\r
2630 END_DEBUG_EXCEPTION_HANDLER
\r
2632 debugstreams_deinit();
\r