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: Better dungeons
\r
119 TODO: Startup and configuration menu
\r
121 TODO: There are some lighting-related todos and fixmes in
\r
122 ServerMap::emergeBlock
\r
124 TODO: Proper handling of spawning place (try to find something that
\r
125 is not in the middle of an ocean (some land to stand on at
\r
126 least) and save it in map config.
\r
128 TODO: Players to only be hidden when the client quits.
\r
129 TODO: - Players to be saved on disk, with inventory
\r
130 TODO: Players to be saved as text in map/players/<name>
\r
131 TODO: Player inventory to be saved on disk
\r
133 TODO: Make fetching sector's blocks more efficient when rendering
\r
134 sectors that have very large amounts of blocks (on client)
\r
136 TODO: Make the video backend selectable
\r
138 TODO: Copy the text of the last picked sign to inventory in creative
\r
141 TODO: Get rid of GotSplitPacketException
\r
143 TODO: Check what goes wrong with caching map to disk (Kray)
\r
145 Block object server side:
\r
146 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
147 - For all blocks in the buffer, objects are stepped(). This
\r
148 means they are active.
\r
149 - TODO: A global active buffer is needed for the server
\r
150 - TODO: A timestamp to blocks
\r
151 - TODO: All blocks going in and out of the buffer are recorded.
\r
152 - TODO: For outgoing blocks, timestamp is written.
\r
153 - TODO: For incoming blocks, time difference is calculated and
\r
154 objects are stepped according to it.
\r
156 TODO: Better handling of objects and mobs
\r
158 - There has to be some way to do it with less spaghetti code
\r
159 - Make separate classes for client and server
\r
160 - Client should not discriminate between blocks, server should
\r
161 - Make other players utilize the same framework
\r
162 - This is also needed for objects that don't get sent to client
\r
163 but are used for triggers etc
\r
165 TODO: Draw big amounts of torches better (that is, throw them in the
\r
166 same meshbuffer (can the meshcollector class be used?))
\r
168 TODO: Check if the usage of Client::isFetchingBlocks() in
\r
169 updateViewingRange() actually does something
\r
170 NOTE: It isn't used anymore after the rewrite.
\r
172 TODO: Make an option to the server to disable building and digging near
\r
173 the starting position
\r
175 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
176 need an additional metadata field for the texts
\r
177 - This is also needed for item container chests
\r
178 TODO: There has to be some better way to handle static objects than to
\r
179 send them all the time. This affects signs and item objects.
\r
181 TODO: When server sees that client is removing an inexistent block or
\r
182 adding a block to an existent position, resend the MapBlock.
\r
184 TODO: When player dies, throw items on map
\r
186 TODO: Optimize day/night mesh updating somehow
\r
187 - create copies of all textures for all lighting values and only
\r
188 change texture for material?
\r
189 - Umm... the collecting of the faces is the slow part
\r
190 -> what about just changing the color values of the existing
\r
191 meshbuffers? It should go quite fast.
\r
193 TODO: Map generator version 2
\r
196 ======================================================================
\r
198 ======================================================================
\r
203 Setting this to 1 enables a special camera mode that forces
\r
204 the renderers to think that the camera statically points from
\r
205 the starting place to a static direction.
\r
207 This allows one to move around with the player and see what
\r
208 is actually drawn behind solid things and behind the player.
\r
210 #define FIELD_OF_VIEW_TEST 0
\r
212 #ifdef UNITTEST_DISABLE
\r
214 #pragma message ("Disabling unit tests")
\r
216 #warning "Disabling unit tests"
\r
218 // Disable unit tests
\r
219 #define ENABLE_TESTS 0
\r
221 // Enable unit tests
\r
222 #define ENABLE_TESTS 1
\r
226 #pragma comment(lib, "Irrlicht.lib")
\r
227 #pragma comment(lib, "jthread.lib")
\r
228 #pragma comment(lib, "zlibwapi.lib")
\r
229 // This would get rid of the console window
\r
230 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
233 #include <iostream>
\r
235 #include <jmutexautolock.h>
\r
236 #include <locale.h>
\r
237 #include "common_irrlicht.h"
\r
240 #include "player.h"
\r
243 #include "environment.h"
\r
244 #include "server.h"
\r
245 #include "client.h"
\r
246 #include "serialization.h"
\r
247 #include "constants.h"
\r
248 #include "strfnd.h"
\r
249 #include "porting.h"
\r
250 #include "irrlichtwrapper.h"
\r
251 #include "gettime.h"
\r
252 #include "porting.h"
\r
253 #include "guiPauseMenu.h"
\r
254 #include "guiInventoryMenu.h"
\r
255 #include "guiTextInputMenu.h"
\r
256 #include "materials.h"
\r
257 #include "guiMessageMenu.h"
\r
259 IrrlichtWrapper *g_irrlicht;
\r
261 MapDrawControl draw_control;
\r
265 These are loaded from the config file.
\r
268 Settings g_settings;
\r
270 extern void set_default_settings();
\r
276 IrrlichtDevice *g_device = NULL;
\r
277 Client *g_client = NULL;
\r
282 gui::IGUIEnvironment* guienv = NULL;
\r
283 gui::IGUIStaticText *guiroot = NULL;
\r
284 int g_active_menu_count = 0;
\r
286 bool noMenuActive()
\r
288 return (g_active_menu_count == 0);
\r
291 // Inventory actions from the menu are buffered here before sending
\r
292 Queue<InventoryAction*> inventory_action_queue;
\r
293 // This is a copy of the inventory that the client's environment has
\r
294 Inventory local_inventory;
\r
296 u16 g_selected_item = 0;
\r
303 std::ostream *dout_con_ptr = &dummyout;
\r
304 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
305 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
306 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
307 //std::ostream *dout_con_ptr = &dstream;
\r
308 //std::ostream *derr_con_ptr = &dstream;
\r
311 std::ostream *dout_server_ptr = &dstream;
\r
312 std::ostream *derr_server_ptr = &dstream;
\r
315 std::ostream *dout_client_ptr = &dstream;
\r
316 std::ostream *derr_client_ptr = &dstream;
\r
319 gettime.h implementation
\r
325 Use irrlicht because it is more precise than porting.h's
\r
328 if(g_irrlicht == NULL)
\r
330 return g_irrlicht->getTime();
\r
337 struct TextDestSign : public TextDest
\r
339 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
341 m_blockpos = blockpos;
\r
345 void gotText(std::wstring text)
\r
347 std::string ntext = wide_to_narrow(text);
\r
348 dstream<<"Changing text of a sign object: "
\r
349 <<ntext<<std::endl;
\r
350 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
358 struct TextDestChat : public TextDest
\r
360 TextDestChat(Client *client)
\r
364 void gotText(std::wstring text)
\r
366 m_client->sendChatMessage(text);
\r
367 m_client->addChatMessage(text);
\r
373 class MyEventReceiver : public IEventReceiver
\r
376 // This is the one method that we have to implement
\r
377 virtual bool OnEvent(const SEvent& event)
\r
380 React to nothing here if a menu is active
\r
382 if(noMenuActive() == false)
\r
388 // Remember whether each key is down or up
\r
389 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
391 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
393 if(event.KeyInput.PressedDown)
\r
395 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
401 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
403 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
405 dstream<<DTIME<<"MyEventReceiver: "
\r
406 <<"Launching pause menu"<<std::endl;
\r
407 // It will delete itself by itself
\r
408 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
409 &g_active_menu_count))->drop();
\r
412 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
414 dstream<<DTIME<<"MyEventReceiver: "
\r
415 <<"Launching inventory"<<std::endl;
\r
416 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
417 &local_inventory, &inventory_action_queue,
\r
418 &g_active_menu_count))->drop();
\r
421 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
423 TextDest *dest = new TextDestChat(g_client);
\r
425 (new GUITextInputMenu(guienv, guiroot, -1,
\r
426 &g_active_menu_count, dest,
\r
431 // Material selection
\r
432 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
434 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
437 g_selected_item = 0;
\r
438 dstream<<DTIME<<"Selected item: "
\r
439 <<g_selected_item<<std::endl;
\r
442 // Viewing range selection
\r
443 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
445 if(draw_control.range_all)
\r
447 draw_control.range_all = false;
\r
448 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
452 draw_control.range_all = true;
\r
453 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
457 // Print debug stacks
\r
458 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
460 dstream<<"-----------------------------------------"
\r
462 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
463 dstream<<"-----------------------------------------"
\r
465 debug_stacks_print();
\r
470 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
472 if(noMenuActive() == false)
\r
474 left_active = false;
\r
475 middle_active = false;
\r
476 right_active = false;
\r
480 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
481 left_active = event.MouseInput.isLeftPressed();
\r
482 middle_active = event.MouseInput.isMiddlePressed();
\r
483 right_active = event.MouseInput.isRightPressed();
\r
485 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
487 leftclicked = true;
\r
489 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
491 rightclicked = true;
\r
493 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
495 leftreleased = true;
\r
497 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
499 rightreleased = true;
\r
501 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
503 /*dstream<<"event.MouseInput.Wheel="
\r
504 <<event.MouseInput.Wheel<<std::endl;*/
\r
505 if(event.MouseInput.Wheel < 0)
\r
507 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
510 g_selected_item = 0;
\r
512 else if(event.MouseInput.Wheel > 0)
\r
514 if(g_selected_item > 0)
\r
517 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
526 // This is used to check whether a key is being held down
\r
527 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
529 return keyIsDown[keyCode];
\r
534 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
535 keyIsDown[i] = false;
\r
537 leftclicked = false;
\r
538 rightclicked = false;
\r
539 leftreleased = false;
\r
540 rightreleased = false;
\r
542 left_active = false;
\r
543 middle_active = false;
\r
544 right_active = false;
\r
555 bool rightreleased;
\r
558 bool middle_active;
\r
562 // We use this array to store the current state of each key
\r
563 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
566 IrrlichtDevice *m_device;
\r
575 virtual ~InputHandler()
\r
579 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
581 virtual v2s32 getMousePos() = 0;
\r
582 virtual void setMousePos(s32 x, s32 y) = 0;
\r
584 virtual bool getLeftState() = 0;
\r
585 virtual bool getRightState() = 0;
\r
587 virtual bool getLeftClicked() = 0;
\r
588 virtual bool getRightClicked() = 0;
\r
589 virtual void resetLeftClicked() = 0;
\r
590 virtual void resetRightClicked() = 0;
\r
592 virtual bool getLeftReleased() = 0;
\r
593 virtual bool getRightReleased() = 0;
\r
594 virtual void resetLeftReleased() = 0;
\r
595 virtual void resetRightReleased() = 0;
\r
597 virtual void step(float dtime) {};
\r
599 virtual void clear() {};
\r
602 InputHandler *g_input = NULL;
\r
604 class RealInputHandler : public InputHandler
\r
607 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
609 m_receiver(receiver)
\r
612 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
614 return m_receiver->IsKeyDown(keyCode);
\r
616 virtual v2s32 getMousePos()
\r
618 return m_device->getCursorControl()->getPosition();
\r
620 virtual void setMousePos(s32 x, s32 y)
\r
622 m_device->getCursorControl()->setPosition(x, y);
\r
625 virtual bool getLeftState()
\r
627 return m_receiver->left_active;
\r
629 virtual bool getRightState()
\r
631 return m_receiver->right_active;
\r
634 virtual bool getLeftClicked()
\r
636 return m_receiver->leftclicked;
\r
638 virtual bool getRightClicked()
\r
640 return m_receiver->rightclicked;
\r
642 virtual void resetLeftClicked()
\r
644 m_receiver->leftclicked = false;
\r
646 virtual void resetRightClicked()
\r
648 m_receiver->rightclicked = false;
\r
651 virtual bool getLeftReleased()
\r
653 return m_receiver->leftreleased;
\r
655 virtual bool getRightReleased()
\r
657 return m_receiver->rightreleased;
\r
659 virtual void resetLeftReleased()
\r
661 m_receiver->leftreleased = false;
\r
663 virtual void resetRightReleased()
\r
665 m_receiver->rightreleased = false;
\r
670 resetRightClicked();
\r
671 resetLeftClicked();
\r
674 IrrlichtDevice *m_device;
\r
675 MyEventReceiver *m_receiver;
\r
678 class RandomInputHandler : public InputHandler
\r
681 RandomInputHandler()
\r
683 leftclicked = false;
\r
684 rightclicked = false;
\r
685 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
686 keydown[i] = false;
\r
688 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
690 return keydown[keyCode];
\r
692 virtual v2s32 getMousePos()
\r
696 virtual void setMousePos(s32 x, s32 y)
\r
698 mousepos = v2s32(x,y);
\r
701 virtual bool getLeftState()
\r
705 virtual bool getRightState()
\r
710 virtual bool getLeftClicked()
\r
712 return leftclicked;
\r
714 virtual bool getRightClicked()
\r
716 return rightclicked;
\r
718 virtual void resetLeftClicked()
\r
720 leftclicked = false;
\r
722 virtual void resetRightClicked()
\r
724 rightclicked = false;
\r
727 virtual bool getLeftReleased()
\r
731 virtual bool getRightReleased()
\r
735 virtual void resetLeftReleased()
\r
738 virtual void resetRightReleased()
\r
742 virtual void step(float dtime)
\r
745 static float counter1 = 0;
\r
749 counter1 = 0.1*Rand(1,10);
\r
750 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
751 g_selected_material++;
\r
753 g_selected_material = 0;*/
\r
754 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
757 g_selected_item = 0;
\r
761 static float counter1 = 0;
\r
765 counter1 = 0.1*Rand(1, 40);
\r
766 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
770 static float counter1 = 0;
\r
774 counter1 = 0.1*Rand(1, 40);
\r
775 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
779 static float counter1 = 0;
\r
783 counter1 = 0.1*Rand(1, 40);
\r
784 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
788 static float counter1 = 0;
\r
792 counter1 = 0.1*Rand(1, 40);
\r
793 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
797 static float counter1 = 0;
\r
801 counter1 = 0.1*Rand(1, 20);
\r
802 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
806 static float counter1 = 0;
\r
810 counter1 = 0.1*Rand(1, 30);
\r
811 leftclicked = true;
\r
815 static float counter1 = 0;
\r
819 counter1 = 0.1*Rand(1, 20);
\r
820 rightclicked = true;
\r
823 mousepos += mousespeed;
\r
826 s32 Rand(s32 min, s32 max)
\r
828 return (myrand()%(max-min+1))+min;
\r
831 bool keydown[KEY_KEY_CODES_COUNT];
\r
838 void updateViewingRange(f32 frametime_in, Client *client)
\r
840 if(draw_control.range_all == true)
\r
843 static f32 added_frametime = 0;
\r
844 static s16 added_frames = 0;
\r
846 added_frametime += frametime_in;
\r
849 // Actually this counter kind of sucks because frametime is busytime
\r
850 static f32 counter = 0;
\r
851 counter -= frametime_in;
\r
857 /*dstream<<__FUNCTION_NAME
\r
858 <<": Collected "<<added_frames<<" frames, total of "
\r
859 <<added_frametime<<"s."<<std::endl;*/
\r
861 /*dstream<<"draw_control.blocks_drawn="
\r
862 <<draw_control.blocks_drawn
\r
863 <<", draw_control.blocks_would_have_drawn="
\r
864 <<draw_control.blocks_would_have_drawn
\r
867 float range_min = g_settings.getS16("viewing_range_nodes_min");
\r
868 float range_max = g_settings.getS16("viewing_range_nodes_max");
\r
870 draw_control.wanted_min_range = range_min;
\r
871 draw_control.wanted_max_blocks = (1.2*draw_control.blocks_drawn)+1;
\r
873 float block_draw_ratio = 1.0;
\r
874 if(draw_control.blocks_would_have_drawn != 0)
\r
876 block_draw_ratio = (float)draw_control.blocks_drawn
\r
877 / (float)draw_control.blocks_would_have_drawn;
\r
880 // Calculate the average frametime in the case that all wanted
\r
881 // blocks had been drawn
\r
882 f32 frametime = added_frametime / added_frames / block_draw_ratio;
\r
884 added_frametime = 0.0;
\r
887 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
888 float wanted_frametime = 1.0 / wanted_fps;
\r
890 f32 wanted_frametime_change = wanted_frametime - frametime;
\r
891 //dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
\r
893 // If needed frametime change is very small, just return
\r
894 if(fabs(wanted_frametime_change) < wanted_frametime*0.2)
\r
896 //dstream<<"ignoring small wanted_frametime_change"<<std::endl;
\r
900 float range = draw_control.wanted_range;
\r
901 float new_range = range;
\r
903 static s16 range_old = 0;
\r
904 static f32 frametime_old = 0;
\r
906 float d_range = range - range_old;
\r
907 f32 d_frametime = frametime - frametime_old;
\r
908 // A sane default of 30ms per 50 nodes of range
\r
909 static f32 time_per_range = 30. / 50;
\r
912 time_per_range = d_frametime / d_range;
\r
915 // The minimum allowed calculated frametime-range derivative:
\r
916 // Practically this sets the maximum speed of changing the range.
\r
917 // The lower this value, the higher the maximum changing speed.
\r
918 // A low value here results in wobbly range (0.001)
\r
919 // A high value here results in slow changing range (0.0025)
\r
920 // SUGG: This could be dynamically adjusted so that when
\r
921 // the camera is turning, this is lower
\r
922 //float min_time_per_range = 0.0015;
\r
923 float min_time_per_range = 0.0010;
\r
924 //float min_time_per_range = 0.05 / range;
\r
925 if(time_per_range < min_time_per_range)
\r
927 time_per_range = min_time_per_range;
\r
928 //dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
\r
932 //dstream<<"time_per_range="<<time_per_range<<std::endl;
\r
935 f32 wanted_range_change = wanted_frametime_change / time_per_range;
\r
936 // Dampen the change a bit to kill oscillations
\r
937 //wanted_range_change *= 0.9;
\r
938 //wanted_range_change *= 0.75;
\r
939 wanted_range_change *= 0.5;
\r
940 //dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
\r
942 // If needed range change is very small, just return
\r
943 if(fabs(wanted_range_change) < 0.001)
\r
945 //dstream<<"ignoring small wanted_range_change"<<std::endl;
\r
949 new_range += wanted_range_change;
\r
950 //dstream<<"new_range="<<new_range/*<<std::endl*/;
\r
952 //float new_range_unclamped = new_range;
\r
953 if(new_range < range_min)
\r
954 new_range = range_min;
\r
955 if(new_range > range_max)
\r
956 new_range = range_max;
\r
958 /*if(new_range != new_range_unclamped)
\r
959 dstream<<", clamped to "<<new_range<<std::endl;
\r
961 dstream<<std::endl;*/
\r
963 draw_control.wanted_range = new_range;
\r
965 range_old = new_range;
\r
966 frametime_old = frametime;
\r
969 class GUIQuickInventory : public IEventReceiver
\r
973 gui::IGUIEnvironment* env,
\r
974 gui::IGUIElement* parent,
\r
977 Inventory *inventory):
\r
978 m_itemcount(itemcount),
\r
979 m_inventory(inventory)
\r
981 core::rect<s32> imgsize(0,0,48,48);
\r
982 core::rect<s32> textsize(0,0,48,16);
\r
983 v2s32 spacing(0, 64);
\r
984 for(s32 i=0; i<m_itemcount; i++)
\r
986 m_images.push_back(env->addImage(
\r
987 imgsize + pos + spacing*i
\r
989 m_images[i]->setScaleImage(true);
\r
990 m_texts.push_back(env->addStaticText(
\r
992 textsize + pos + spacing*i,
\r
995 m_texts[i]->setBackgroundColor(
\r
996 video::SColor(128,0,0,0));
\r
997 m_texts[i]->setTextAlignment(
\r
999 gui::EGUIA_UPPERLEFT);
\r
1003 virtual bool OnEvent(const SEvent& event)
\r
1008 void setSelection(s32 i)
\r
1017 start = m_selection - m_itemcount / 2;
\r
1019 InventoryList *mainlist = m_inventory->getList("main");
\r
1021 for(s32 i=0; i<m_itemcount; i++)
\r
1023 s32 j = i + start;
\r
1025 if(j > (s32)mainlist->getSize() - 1)
\r
1026 j -= mainlist->getSize();
\r
1028 j += mainlist->getSize();
\r
1030 InventoryItem *item = mainlist->getItem(j);
\r
1034 m_images[i]->setImage(NULL);
\r
1037 if(m_selection == j)
\r
1038 swprintf(t, 10, L"<-");
\r
1040 swprintf(t, 10, L"");
\r
1041 m_texts[i]->setText(t);
\r
1043 // The next ifs will segfault with a NULL pointer
\r
1048 m_images[i]->setImage(item->getImage());
\r
1051 if(m_selection == j)
\r
1052 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1054 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1055 m_texts[i]->setText(t);
\r
1061 core::array<gui::IGUIStaticText*> m_texts;
\r
1062 core::array<gui::IGUIImage*> m_images;
\r
1063 Inventory *m_inventory;
\r
1074 ChatLine(const std::wstring &a_text):
\r
1080 std::wstring text;
\r
1083 int main(int argc, char *argv[])
\r
1086 Low-level initialization
\r
1089 bool disable_stderr = false;
\r
1091 disable_stderr = true;
\r
1094 // Initialize debug streams
\r
1095 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1096 // Initialize debug stacks
\r
1097 debug_stacks_init();
\r
1099 DSTACK(__FUNCTION_NAME);
\r
1101 initializeMaterialProperties();
\r
1103 BEGIN_DEBUG_EXCEPTION_HANDLER
\r
1109 Parse command line
\r
1112 // List all allowed options
\r
1113 core::map<std::string, ValueSpec> allowed_options;
\r
1114 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1115 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1116 "Run server directly"));
\r
1117 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1118 "Load configuration from specified file"));
\r
1119 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1120 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1121 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1122 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1123 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1125 Settings cmd_args;
\r
1127 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1129 if(ret == false || cmd_args.getFlag("help"))
\r
1131 dstream<<"Allowed options:"<<std::endl;
\r
1132 for(core::map<std::string, ValueSpec>::Iterator
\r
1133 i = allowed_options.getIterator();
\r
1134 i.atEnd() == false; i++)
\r
1136 dstream<<" --"<<i.getNode()->getKey();
\r
1137 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1142 dstream<<" <value>";
\r
1144 dstream<<std::endl;
\r
1146 if(i.getNode()->getValue().help != NULL)
\r
1148 dstream<<" "<<i.getNode()->getValue().help
\r
1153 return cmd_args.getFlag("help") ? 0 : 1;
\r
1158 Basic initialization
\r
1161 // Initialize default settings
\r
1162 set_default_settings();
\r
1164 // Print startup message
\r
1165 dstream<<DTIME<<"minetest-c55"
\r
1166 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1167 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1170 // Set locale. This is for forcing '.' as the decimal point.
\r
1171 std::locale::global(std::locale("C"));
\r
1172 // This enables printing all characters in bitmap font
\r
1173 setlocale(LC_CTYPE, "en_US");
\r
1175 // Initialize sockets
\r
1177 atexit(sockets_cleanup);
\r
1187 // Path of configuration file in use
\r
1188 std::string configpath = "";
\r
1190 if(cmd_args.exists("config"))
\r
1192 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1195 dstream<<"Could not read configuration from \""
\r
1196 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1199 configpath = cmd_args.get("config");
\r
1203 const char *filenames[2] =
\r
1205 "../minetest.conf",
\r
1206 "../../minetest.conf"
\r
1209 for(u32 i=0; i<2; i++)
\r
1211 bool r = g_settings.readConfigFile(filenames[i]);
\r
1214 configpath = filenames[i];
\r
1220 // Initialize random seed
\r
1227 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1228 || cmd_args.getFlag("enable-unittests") == true)
\r
1233 // Read map parameters from settings
\r
1235 HMParams hm_params;
\r
1236 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1237 hm_params.randmax = g_settings.get("height_randmax");
\r
1238 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1239 hm_params.base = g_settings.get("height_base");
\r
1241 MapParams map_params;
\r
1242 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1243 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1249 std::cout<<std::endl<<std::endl;
\r
1252 <<" .__ __ __ "<<std::endl
\r
1253 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1254 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1255 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1256 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1257 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1260 std::cout<<std::endl;
\r
1261 //char templine[100];
\r
1265 if(cmd_args.exists("port"))
\r
1267 port = cmd_args.getU16("port");
\r
1271 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1272 std::cout<<"-> "<<port<<std::endl;
\r
1275 if(cmd_args.getFlag("server"))
\r
1277 DSTACK("Dedicated server branch");
\r
1279 std::cout<<std::endl;
\r
1280 std::cout<<"========================"<<std::endl;
\r
1281 std::cout<<"Running dedicated server"<<std::endl;
\r
1282 std::cout<<"========================"<<std::endl;
\r
1283 std::cout<<std::endl;
\r
1285 Server server("../map", hm_params, map_params);
\r
1286 server.start(port);
\r
1290 // This is kind of a hack but can be done like this
\r
1291 // because server.step() is very light
\r
1293 server.step(0.030);
\r
1295 static int counter = 0;
\r
1301 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1302 core::list<PlayerInfo>::Iterator i;
\r
1303 static u32 sum_old = 0;
\r
1304 u32 sum = PIChecksum(list);
\r
1305 if(sum != sum_old)
\r
1307 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1308 for(i=list.begin(); i!=list.end(); i++)
\r
1310 i->PrintLine(&std::cout);
\r
1320 bool hosting = false;
\r
1321 char connect_name[100] = "";
\r
1323 if(cmd_args.exists("address"))
\r
1325 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1327 else if(is_yes(g_settings.get("host_game")) == false)
\r
1329 if(g_settings.get("address") != "")
\r
1331 std::cout<<g_settings.get("address")<<std::endl;
\r
1332 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1336 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1337 std::cin.getline(connect_name, 100);
\r
1341 if(connect_name[0] == 0){
\r
1342 snprintf(connect_name, 100, "127.0.0.1");
\r
1347 std::cout<<"> Hosting game"<<std::endl;
\r
1349 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1351 char playername[PLAYERNAME_SIZE] = "";
\r
1352 if(g_settings.get("name") != "")
\r
1354 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1358 std::cout<<"Name of player: ";
\r
1359 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1361 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1364 Resolution selection
\r
1367 bool fullscreen = false;
\r
1368 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1369 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1373 MyEventReceiver receiver;
\r
1375 video::E_DRIVER_TYPE driverType;
\r
1378 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1379 driverType = video::EDT_OPENGL;
\r
1381 driverType = video::EDT_OPENGL;
\r
1384 // create device and exit if creation failed
\r
1386 IrrlichtDevice *device;
\r
1387 device = createDevice(driverType,
\r
1388 core::dimension2d<u32>(screenW, screenH),
\r
1389 16, fullscreen, false, false, &receiver);
\r
1392 return 1; // could not create selected driver.
\r
1394 g_device = device;
\r
1395 g_irrlicht = new IrrlichtWrapper(device);
\r
1397 //g_device = device;
\r
1399 device->setResizable(true);
\r
1401 bool random_input = g_settings.getBool("random_input")
\r
1402 || cmd_args.getFlag("random-input");
\r
1404 g_input = new RandomInputHandler();
\r
1406 g_input = new RealInputHandler(device, &receiver);
\r
1409 Continue initialization
\r
1412 video::IVideoDriver* driver = device->getVideoDriver();
\r
1415 This changes the minimum allowed number of vertices in a VBO
\r
1417 //driver->setMinHardwareBufferVertexCount(50);
\r
1419 scene::ISceneManager* smgr = device->getSceneManager();
\r
1421 guienv = device->getGUIEnvironment();
\r
1422 gui::IGUISkin* skin = guienv->getSkin();
\r
1423 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1425 skin->setFont(font);
\r
1427 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1428 dstream<<"text_height="<<text_height<<std::endl;
\r
1430 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1431 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1432 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1433 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1434 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1435 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1437 const wchar_t *text = L"Loading and connecting...";
\r
1438 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1439 core::vector2d<s32> textsize(300, text_height);
\r
1440 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1442 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1443 text, textrect, false, false);
\r
1444 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1446 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1447 guienv->drawAll();
\r
1448 driver->endScene();
\r
1451 Preload some textures
\r
1454 tile_materials_preload(g_irrlicht);
\r
1457 Make a scope here for the client so that it gets removed
\r
1458 before the irrlicht device
\r
1462 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1467 SharedPtr<Server> server;
\r
1469 server = new Server("../map", hm_params, map_params);
\r
1470 server->start(port);
\r
1477 Client client(device, playername, draw_control);
\r
1479 g_client = &client;
\r
1481 Address connect_address(0,0,0,0, port);
\r
1483 connect_address.Resolve(connect_name);
\r
1485 catch(ResolveError &e)
\r
1487 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1491 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1492 client.connect(connect_address);
\r
1495 while(client.connectedAndInitialized() == false)
\r
1498 if(server != NULL){
\r
1499 server->step(0.1);
\r
1504 catch(con::PeerNotFoundException &e)
\r
1506 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1513 /*scene::ISceneNode* skybox;
\r
1514 skybox = smgr->addSkyBoxSceneNode(
\r
1515 driver->getTexture("../data/skybox2.png"),
\r
1516 driver->getTexture("../data/skybox3.png"),
\r
1517 driver->getTexture("../data/skybox1.png"),
\r
1518 driver->getTexture("../data/skybox1.png"),
\r
1519 driver->getTexture("../data/skybox1.png"),
\r
1520 driver->getTexture("../data/skybox1.png"));*/
\r
1523 Create the camera node
\r
1526 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1527 0, // Camera parent
\r
1528 v3f(BS*100, BS*2, BS*100), // Look from
\r
1529 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1533 if(camera == NULL)
\r
1536 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1538 camera->setFOV(FOV_ANGLE);
\r
1540 // Just so big a value that everything rendered is visible
\r
1541 camera->setFarValue(100000*BS);
\r
1543 f32 camera_yaw = 0; // "right/left"
\r
1544 f32 camera_pitch = 0; // "up/down"
\r
1550 gui_loadingtext->remove();
\r
1553 Add some gui stuff
\r
1556 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1557 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1560 We need some kind of a root node to be able to add
\r
1561 custom elements directly on the screen.
\r
1562 Otherwise they won't be automatically drawn.
\r
1564 guiroot = guienv->addStaticText(L"",
\r
1565 core::rect<s32>(0, 0, 10000, 10000));
\r
1567 // Test the text input system
\r
1568 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1570 /*GUIMessageMenu *menu =
\r
1571 new GUIMessageMenu(guienv, guiroot, -1,
\r
1572 &g_active_menu_count,
\r
1576 // Launch pause menu
\r
1577 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1578 &g_active_menu_count))->drop();
\r
1580 // First line of debug text
\r
1581 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1583 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1585 // Second line of debug text
\r
1586 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1588 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1591 // At the middle of the screen
\r
1592 // Object infos are shown in this
\r
1593 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1595 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1599 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1600 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1601 core::rect<s32>(70, 60, 795, 150),
\r
1603 chat_guitext->setBackgroundColor(video::SColor(96,0,0,0));
\r
1604 core::list<ChatLine> chat_lines;
\r
1607 Some statistics are collected in these
\r
1610 u32 beginscenetime = 0;
\r
1611 u32 scenetime = 0;
\r
1612 u32 endscenetime = 0;
\r
1615 //throw con::PeerNotFoundException("lol");
\r
1621 bool first_loop_after_window_activation = true;
\r
1623 // Time is in milliseconds
\r
1624 // NOTE: getRealTime() causes strange problems in wine (imprecision?)
\r
1625 // NOTE: So we have to use getTime() and call run()s between them
\r
1626 u32 lasttime = device->getTimer()->getTime();
\r
1628 while(device->run())
\r
1631 Run global IrrlichtWrapper's main thread processing stuff
\r
1633 g_irrlicht->Run();
\r
1636 Random calculations
\r
1638 v2u32 screensize = driver->getScreenSize();
\r
1639 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1641 // Hilight boxes collected during the loop and displayed
\r
1642 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1645 std::wstring infotext;
\r
1647 //TimeTaker //timer1("//timer1");
\r
1649 // Time of frame without fps limit
\r
1653 // not using getRealTime is necessary for wine
\r
1654 u32 time = device->getTimer()->getTime();
\r
1655 if(time > lasttime)
\r
1656 busytime_u32 = time - lasttime;
\r
1659 busytime = busytime_u32 / 1000.0;
\r
1662 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1664 // Absolutelu necessary for wine!
\r
1671 updateViewingRange(busytime, &client);
\r
1678 float fps_max = g_settings.getFloat("fps_max");
\r
1679 u32 frametime_min = 1000./fps_max;
\r
1681 if(busytime_u32 < frametime_min)
\r
1683 u32 sleeptime = frametime_min - busytime_u32;
\r
1684 device->sleep(sleeptime);
\r
1688 // Absolutelu necessary for wine!
\r
1692 Time difference calculation
\r
1694 f32 dtime; // in seconds
\r
1696 u32 time = device->getTimer()->getTime();
\r
1697 if(time > lasttime)
\r
1698 dtime = (time - lasttime) / 1000.0;
\r
1704 Time average and jitter calculation
\r
1707 static f32 dtime_avg1 = 0.0;
\r
1708 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1709 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1711 static f32 dtime_jitter1_max_sample = 0.0;
\r
1712 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1714 static f32 jitter1_max = 0.0;
\r
1715 static f32 counter = 0.0;
\r
1716 if(dtime_jitter1 > jitter1_max)
\r
1717 jitter1_max = dtime_jitter1;
\r
1722 dtime_jitter1_max_sample = jitter1_max;
\r
1723 dtime_jitter1_max_fraction
\r
1724 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1725 jitter1_max = 0.0;
\r
1728 Control freetime ratio
\r
1730 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1732 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1733 g_freetime_ratio += 0.01;
\r
1737 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1738 g_freetime_ratio -= 0.01;
\r
1744 Busytime average and jitter calculation
\r
1747 static f32 busytime_avg1 = 0.0;
\r
1748 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1749 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1751 static f32 busytime_jitter1_max_sample = 0.0;
\r
1752 static f32 busytime_jitter1_min_sample = 0.0;
\r
1754 static f32 jitter1_max = 0.0;
\r
1755 static f32 jitter1_min = 0.0;
\r
1756 static f32 counter = 0.0;
\r
1757 if(busytime_jitter1 > jitter1_max)
\r
1758 jitter1_max = busytime_jitter1;
\r
1759 if(busytime_jitter1 < jitter1_min)
\r
1760 jitter1_min = busytime_jitter1;
\r
1762 if(counter > 0.0){
\r
1764 busytime_jitter1_max_sample = jitter1_max;
\r
1765 busytime_jitter1_min_sample = jitter1_min;
\r
1766 jitter1_max = 0.0;
\r
1767 jitter1_min = 0.0;
\r
1772 Debug info for client
\r
1775 static float counter = 0.0;
\r
1780 client.printDebugInfo(std::cout);
\r
1785 Input handler step()
\r
1787 g_input->step(dtime);
\r
1790 Player speed control
\r
1799 bool a_superspeed,
\r
1802 PlayerControl control(
\r
1803 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1804 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1805 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1806 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1807 g_input->isKeyDown(irr::KEY_SPACE),
\r
1808 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1812 client.setPlayerControl(control);
\r
1816 Process environment
\r
1820 //TimeTaker timer("client.step(dtime)");
\r
1821 client.step(dtime);
\r
1822 //client.step(dtime_avg1);
\r
1825 if(server != NULL)
\r
1827 //TimeTaker timer("server->step(dtime)");
\r
1828 server->step(dtime);
\r
1831 v3f player_position = client.getPlayerPosition();
\r
1833 //TimeTaker //timer2("//timer2");
\r
1836 Mouse and camera control
\r
1839 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1842 device->getCursorControl()->setVisible(false);
\r
1844 if(first_loop_after_window_activation){
\r
1845 //std::cout<<"window active, first loop"<<std::endl;
\r
1846 first_loop_after_window_activation = false;
\r
1849 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1850 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1851 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1852 camera_yaw -= dx*0.2;
\r
1853 camera_pitch += dy*0.2;
\r
1854 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1855 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1857 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1860 device->getCursorControl()->setVisible(true);
\r
1862 //std::cout<<"window inactive"<<std::endl;
\r
1863 first_loop_after_window_activation = true;
\r
1866 camera_yaw = wrapDegrees(camera_yaw);
\r
1867 camera_pitch = wrapDegrees(camera_pitch);
\r
1869 v3f camera_direction = v3f(0,0,1);
\r
1870 camera_direction.rotateYZBy(camera_pitch);
\r
1871 camera_direction.rotateXZBy(camera_yaw);
\r
1873 // This is at the height of the eyes of the current figure
\r
1874 v3f camera_position =
\r
1875 player_position + v3f(0, BS+BS/2, 0);
\r
1876 // This is more like in minecraft
\r
1877 /*v3f camera_position =
\r
1878 player_position + v3f(0, BS+BS*0.65, 0);*/
\r
1880 camera->setPosition(camera_position);
\r
1881 // *100.0 helps in large map coordinates
\r
1882 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1884 if(FIELD_OF_VIEW_TEST){
\r
1885 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1886 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1889 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1890 //TimeTaker timer("client.updateCamera");
\r
1891 client.updateCamera(camera_position, camera_direction);
\r
1895 //TimeTaker //timer3("//timer3");
\r
1898 Calculate what block is the crosshair pointing to
\r
1901 //u32 t1 = device->getTimer()->getRealTime();
\r
1903 //f32 d = 4; // max. distance
\r
1904 f32 d = 4; // max. distance
\r
1905 core::line3d<f32> shootline(camera_position,
\r
1906 camera_position + camera_direction * BS * (d+1));
\r
1908 MapBlockObject *selected_object = client.getSelectedObject
\r
1909 (d*BS, camera_position, shootline);
\r
1912 If it's pointing to a MapBlockObject
\r
1915 if(selected_object != NULL)
\r
1917 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1919 core::aabbox3d<f32> box_on_map
\r
1920 = selected_object->getSelectionBoxOnMap();
\r
1922 hilightboxes.push_back(box_on_map);
\r
1924 infotext = narrow_to_wide(selected_object->infoText());
\r
1926 if(g_input->getLeftClicked())
\r
1928 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1929 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1930 selected_object->getId(), g_selected_item);
\r
1932 else if(g_input->getRightClicked())
\r
1934 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1936 Check if we want to modify the object ourselves
\r
1938 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1940 dstream<<"Sign object right-clicked"<<std::endl;
\r
1942 if(random_input == false)
\r
1944 // Get a new text for it
\r
1946 TextDest *dest = new TextDestSign(
\r
1947 selected_object->getBlock()->getPos(),
\r
1948 selected_object->getId(),
\r
1951 SignObject *sign_object = (SignObject*)selected_object;
\r
1953 std::wstring wtext =
\r
1954 narrow_to_wide(sign_object->getText());
\r
1956 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1957 &g_active_menu_count, dest,
\r
1962 Otherwise pass the event to the server as-is
\r
1966 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1967 selected_object->getId(), g_selected_item);
\r
1971 else // selected_object == NULL
\r
1975 Find out which node we are pointing at
\r
1978 bool nodefound = false;
\r
1980 v3s16 neighbourpos;
\r
1981 core::aabbox3d<f32> nodefacebox;
\r
1982 f32 mindistance = BS * 1001;
\r
1984 v3s16 pos_i = floatToInt(player_position);
\r
1986 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1990 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1991 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1992 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1993 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1994 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1995 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1997 for(s16 y = ystart; y <= yend; y++)
\r
1998 for(s16 z = zstart; z <= zend; z++)
\r
1999 for(s16 x = xstart; x <= xend; x++)
\r
2004 n = client.getNode(v3s16(x,y,z));
\r
2005 if(content_pointable(n.d) == false)
\r
2008 catch(InvalidPositionException &e)
\r
2014 v3f npf = intToFloat(np);
\r
2019 v3s16(0,0,1), // back
\r
2020 v3s16(0,1,0), // top
\r
2021 v3s16(1,0,0), // right
\r
2022 v3s16(0,0,-1), // front
\r
2023 v3s16(0,-1,0), // bottom
\r
2024 v3s16(-1,0,0), // left
\r
2030 if(n.d == CONTENT_TORCH)
\r
2032 v3s16 dir = unpackDir(n.dir);
\r
2033 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2034 dir_f *= BS/2 - BS/6 - BS/20;
\r
2035 v3f cpf = npf + dir_f;
\r
2036 f32 distance = (cpf - camera_position).getLength();
\r
2038 core::aabbox3d<f32> box;
\r
2041 if(dir == v3s16(0,-1,0))
\r
2043 box = core::aabbox3d<f32>(
\r
2044 npf - v3f(BS/6, BS/2, BS/6),
\r
2045 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2049 else if(dir == v3s16(0,1,0))
\r
2051 box = core::aabbox3d<f32>(
\r
2052 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2053 npf + v3f(BS/6, BS/2, BS/6)
\r
2059 box = core::aabbox3d<f32>(
\r
2060 cpf - v3f(BS/6, BS/3, BS/6),
\r
2061 cpf + v3f(BS/6, BS/3, BS/6)
\r
2065 if(distance < mindistance)
\r
2067 if(box.intersectsWithLine(shootline))
\r
2071 neighbourpos = np;
\r
2072 mindistance = distance;
\r
2073 nodefacebox = box;
\r
2082 for(u16 i=0; i<6; i++)
\r
2084 v3f dir_f = v3f(dirs[i].X,
\r
2085 dirs[i].Y, dirs[i].Z);
\r
2086 v3f centerpoint = npf + dir_f * BS/2;
\r
2088 (centerpoint - camera_position).getLength();
\r
2090 if(distance < mindistance)
\r
2092 core::CMatrix4<f32> m;
\r
2093 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2095 // This is the back face
\r
2096 v3f corners[2] = {
\r
2097 v3f(BS/2, BS/2, BS/2),
\r
2098 v3f(-BS/2, -BS/2, BS/2+d)
\r
2101 for(u16 j=0; j<2; j++)
\r
2103 m.rotateVect(corners[j]);
\r
2104 corners[j] += npf;
\r
2107 core::aabbox3d<f32> facebox(corners[0]);
\r
2108 facebox.addInternalPoint(corners[1]);
\r
2110 if(facebox.intersectsWithLine(shootline))
\r
2114 neighbourpos = np + dirs[i];
\r
2115 mindistance = distance;
\r
2116 nodefacebox = facebox;
\r
2118 } // if distance < mindistance
\r
2120 } // regular block
\r
2123 static float nodig_delay_counter = 0.0;
\r
2127 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2129 static float dig_time = 0.0;
\r
2130 static u16 dig_index = 0;
\r
2132 hilightboxes.push_back(nodefacebox);
\r
2134 if(g_input->getLeftReleased())
\r
2136 client.clearTempMod(nodepos);
\r
2140 if(nodig_delay_counter > 0.0)
\r
2142 nodig_delay_counter -= dtime;
\r
2146 if(nodepos != nodepos_old)
\r
2148 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2149 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2151 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2153 client.clearTempMod(nodepos_old);
\r
2158 if(g_input->getLeftClicked() ||
\r
2159 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2161 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2162 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2164 if(g_input->getLeftClicked())
\r
2166 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2168 if(g_input->getLeftState())
\r
2170 MapNode n = client.getNode(nodepos);
\r
2172 // Get tool name. Default is "" = bare hands
\r
2173 std::string toolname = "";
\r
2174 InventoryList *mlist = local_inventory.getList("main");
\r
2177 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2178 if(item && (std::string)item->getName() == "ToolItem")
\r
2180 ToolItem *titem = (ToolItem*)item;
\r
2181 toolname = titem->getToolName();
\r
2185 // Get digging properties for material and tool
\r
2186 u8 material = n.d;
\r
2187 DiggingProperties prop =
\r
2188 getDiggingProperties(material, toolname);
\r
2190 float dig_time_complete = 0.0;
\r
2192 if(prop.diggable == false)
\r
2194 /*dstream<<"Material "<<(int)material
\r
2195 <<" not diggable with \""
\r
2196 <<toolname<<"\""<<std::endl;*/
\r
2197 // I guess nobody will wait for this long
\r
2198 dig_time_complete = 10000000.0;
\r
2202 dig_time_complete = prop.time;
\r
2205 if(dig_time_complete >= 0.001)
\r
2207 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2208 * dig_time/dig_time_complete);
\r
2210 // This is for torches
\r
2213 dig_index = CRACK_ANIMATION_LENGTH;
\r
2216 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2218 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2219 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2223 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2224 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2225 client.clearTempMod(nodepos);
\r
2226 client.removeNode(nodepos);
\r
2230 nodig_delay_counter = dig_time_complete
\r
2231 / (float)CRACK_ANIMATION_LENGTH;
\r
2233 // We don't want a corresponding delay to
\r
2234 // very time consuming nodes
\r
2235 if(nodig_delay_counter > 0.5)
\r
2237 nodig_delay_counter = 0.5;
\r
2239 // We want a slight delay to very little
\r
2240 // time consuming nodes
\r
2241 //float mindelay = 0.15;
\r
2242 float mindelay = 0.20;
\r
2243 if(nodig_delay_counter < mindelay)
\r
2245 nodig_delay_counter = mindelay;
\r
2249 dig_time += dtime;
\r
2253 if(g_input->getRightClicked())
\r
2255 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2256 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2259 nodepos_old = nodepos;
\r
2264 } // selected_object == NULL
\r
2266 g_input->resetLeftClicked();
\r
2267 g_input->resetRightClicked();
\r
2269 if(g_input->getLeftReleased())
\r
2271 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2273 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2275 if(g_input->getRightReleased())
\r
2277 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2281 g_input->resetLeftReleased();
\r
2282 g_input->resetRightReleased();
\r
2285 Calculate stuff for drawing
\r
2288 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2290 u32 daynight_ratio = client.getDayNightRatio();
\r
2291 /*video::SColor bgcolor = video::SColor(
\r
2293 skycolor.getRed() * daynight_ratio / 1000,
\r
2294 skycolor.getGreen() * daynight_ratio / 1000,
\r
2295 skycolor.getBlue() * daynight_ratio / 1000);*/
\r
2297 u8 l = decode_light((daynight_ratio * LIGHT_SUN) / 1000);
\r
2298 video::SColor bgcolor = video::SColor(
\r
2300 skycolor.getRed() * l / 255,
\r
2301 skycolor.getGreen() * l / 255,
\r
2302 skycolor.getBlue() * l / 255);
\r
2308 if(g_settings.getBool("enable_fog") == true)
\r
2310 f32 range = draw_control.wanted_range * BS;
\r
2311 if(draw_control.range_all)
\r
2312 range = 100000*BS;
\r
2316 video::EFT_FOG_LINEAR,
\r
2320 false, // pixel fog
\r
2321 false // range fog
\r
2327 Update gui stuff (0ms)
\r
2330 //TimeTaker guiupdatetimer("Gui updating");
\r
2333 wchar_t temptext[150];
\r
2335 static float drawtime_avg = 0;
\r
2336 drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
\r
2337 static float beginscenetime_avg = 0;
\r
2338 beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
\r
2339 static float scenetime_avg = 0;
\r
2340 scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
\r
2341 static float endscenetime_avg = 0;
\r
2342 endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;
\r
2344 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2346 L", R: range_all=%i"
\r
2348 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2350 draw_control.range_all,
\r
2352 beginscenetime_avg,
\r
2357 guitext->setText(temptext);
\r
2361 wchar_t temptext[150];
\r
2362 swprintf(temptext, 150,
\r
2363 L"(% .1f, % .1f, % .1f)"
\r
2364 L" (% .3f < btime_jitter < % .3f"
\r
2365 L", dtime_jitter = % .1f %%"
\r
2366 L", v_range = %.1f)",
\r
2367 player_position.X/BS,
\r
2368 player_position.Y/BS,
\r
2369 player_position.Z/BS,
\r
2370 busytime_jitter1_min_sample,
\r
2371 busytime_jitter1_max_sample,
\r
2372 dtime_jitter1_max_fraction * 100.0,
\r
2373 draw_control.wanted_range
\r
2376 guitext2->setText(temptext);
\r
2380 guitext_info->setText(infotext.c_str());
\r
2384 Get chat messages from client
\r
2387 // Get new messages
\r
2388 std::wstring message;
\r
2389 while(client.getChatMessage(message))
\r
2391 chat_lines.push_back(ChatLine(message));
\r
2392 /*if(chat_lines.size() > 6)
\r
2394 core::list<ChatLine>::Iterator
\r
2395 i = chat_lines.begin();
\r
2396 chat_lines.erase(i);
\r
2399 // Append them to form the whole static text and throw
\r
2400 // it to the gui element
\r
2401 std::wstring whole;
\r
2402 // This will correspond to the line number counted from
\r
2403 // top to bottom, from size-1 to 0
\r
2404 s16 line_number = chat_lines.size();
\r
2405 // Count of messages to be removed from the top
\r
2406 u16 to_be_removed_count = 0;
\r
2407 for(core::list<ChatLine>::Iterator
\r
2408 i = chat_lines.begin();
\r
2409 i != chat_lines.end(); i++)
\r
2411 // After this, line number is valid for this loop
\r
2414 (*i).age += dtime;
\r
2416 This results in a maximum age of 60*6 to the
\r
2417 lowermost line and a maximum of 6 lines
\r
2419 float allowed_age = (6-line_number) * 60.0;
\r
2421 if((*i).age > allowed_age)
\r
2423 to_be_removed_count++;
\r
2426 whole += (*i).text + L'\n';
\r
2428 for(u16 i=0; i<to_be_removed_count; i++)
\r
2430 core::list<ChatLine>::Iterator
\r
2431 it = chat_lines.begin();
\r
2432 chat_lines.erase(it);
\r
2434 chat_guitext->setText(whole.c_str());
\r
2435 // Update gui element size and position
\r
2436 core::rect<s32> rect(
\r
2438 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2439 screensize.X - 10,
\r
2442 chat_guitext->setRelativePosition(rect);
\r
2444 if(chat_lines.size() == 0)
\r
2445 chat_guitext->setVisible(false);
\r
2447 chat_guitext->setVisible(true);
\r
2454 static u16 old_selected_item = 65535;
\r
2455 if(client.getLocalInventoryUpdated()
\r
2456 || g_selected_item != old_selected_item)
\r
2458 old_selected_item = g_selected_item;
\r
2459 //std::cout<<"Updating local inventory"<<std::endl;
\r
2460 client.getLocalInventory(local_inventory);
\r
2461 quick_inventory->setSelection(g_selected_item);
\r
2462 quick_inventory->update();
\r
2466 Send actions returned by the inventory menu
\r
2468 while(inventory_action_queue.size() != 0)
\r
2470 InventoryAction *a = inventory_action_queue.pop_front();
\r
2472 client.sendInventoryAction(a);
\r
2481 TimeTaker drawtimer("Drawing");
\r
2485 TimeTaker timer("beginScene");
\r
2486 driver->beginScene(true, true, bgcolor);
\r
2487 //driver->beginScene(false, true, bgcolor);
\r
2488 beginscenetime = timer.stop(true);
\r
2493 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2496 TimeTaker timer("smgr");
\r
2498 scenetime = timer.stop(true);
\r
2502 //TimeTaker timer9("auxiliary drawings");
\r
2506 //TimeTaker //timer10("//timer10");
\r
2508 video::SMaterial m;
\r
2510 m.Lighting = false;
\r
2511 driver->setMaterial(m);
\r
2513 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2515 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2516 i != hilightboxes.end(); i++)
\r
2518 /*std::cout<<"hilightbox min="
\r
2519 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2521 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2523 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2529 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2530 displaycenter + core::vector2d<s32>(10,0),
\r
2531 video::SColor(255,255,255,255));
\r
2532 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2533 displaycenter + core::vector2d<s32>(0,10),
\r
2534 video::SColor(255,255,255,255));
\r
2539 //TimeTaker //timer11("//timer11");
\r
2545 guienv->drawAll();
\r
2549 TimeTaker timer("endScene");
\r
2550 driver->endScene();
\r
2551 endscenetime = timer.stop(true);
\r
2554 drawtime = drawtimer.stop(true);
\r
2560 static s16 lastFPS = 0;
\r
2561 //u16 fps = driver->getFPS();
\r
2562 u16 fps = (1.0/dtime_avg1);
\r
2564 if (lastFPS != fps)
\r
2566 core::stringw str = L"Minetest [";
\r
2567 str += driver->getName();
\r
2571 device->setWindowCaption(str.c_str());
\r
2577 device->yield();*/
\r
2580 delete quick_inventory;
\r
2582 } // client is deleted at this point
\r
2587 In the end, delete the Irrlicht device.
\r
2592 Update configuration file
\r
2594 /*if(configpath != "")
\r
2596 g_settings.updateConfigFile(configpath.c_str());
\r
2600 catch(con::PeerNotFoundException &e)
\r
2602 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2606 GUIMessageMenu *menu =
\r
2607 new GUIMessageMenu(guienv, guiroot, -1,
\r
2608 &g_active_menu_count,
\r
2609 L"Connection timed out");
\r
2611 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
2613 dstream<<"Created menu"<<std::endl;
\r
2615 while(g_device->run() && menu->getStatus() == false)
\r
2617 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2618 guienv->drawAll();
\r
2619 driver->endScene();
\r
2622 dstream<<"Dropping menu"<<std::endl;
\r
2628 END_DEBUG_EXCEPTION_HANDLER
\r
2630 debugstreams_deinit();
\r