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
26 - It is not a memory leak but some kind of a buffer.
\r
28 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
29 NOTE: Global locale is now set at initialization
\r
31 Random suggeestions:
\r
32 --------------------
\r
34 SUGG: Fix address to be ipv6 compatible
\r
36 NOTE: When a new sector is generated, it may change the ground level
\r
37 of it's and it's neighbors border that two blocks that are
\r
38 above and below each other and that are generated before and
\r
39 after the sector heightmap generation (order doesn't matter),
\r
40 can have a small gap between each other at the border.
\r
41 SUGG: Use same technique for sector heightmaps as what we're
\r
42 using for UnlimitedHeightmap? (getting all neighbors
\r
45 SUGG: Transfer more blocks in a single packet
\r
46 SUGG: A blockdata combiner class, to which blocks are added and at
\r
47 destruction it sends all the stuff in as few packets as possible.
\r
49 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
50 SUGG: Fetch stuff mainly from the viewing direction
\r
52 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
53 - This enables saving many packets and making a faster connection
\r
54 - This also enables server to check if client has received the
\r
55 most recent block sent, for example.
\r
56 SUGG: Add a sane bandwidth throttling system to Connection
\r
58 SUGG: More fine-grained control of client's dumping of blocks from
\r
60 - ...What does this mean in the first place?
\r
62 SUGG: A map editing mode (similar to dedicated server mode)
\r
64 SUGG: Add a time value to the param of footstepped grass and check it
\r
65 against a global timer when a block is accessed, to make old
\r
68 SUGG: Make a copy of close-range environment on client for showing
\r
69 on screen, with minimal mutexes to slow down the main loop
\r
71 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
72 it by sending more stuff in a single packet.
\r
73 - Add a packet queue to RemoteClient, from which packets will be
\r
74 combined with object data packets
\r
75 - This is not exactly trivial: the object data packets are
\r
76 sometimes very big by themselves
\r
78 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
79 - This will allow saving ages of rats on disk but not sending
\r
82 SUGG: MovingObject::move and Player::move are basically the same.
\r
84 - NOTE: Player::move is more up-to-date.
\r
86 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
87 - This is not doable because it is currently hand-made and not
\r
88 based on some mathematical function.
\r
89 - Note: This has been changing lately
\r
91 SUGG: A version number to blocks, which increments when the block is
\r
92 modified (node add/remove, water update, lighting update)
\r
93 - This can then be used to make sure the most recent version of
\r
94 a block has been sent to client
\r
96 SUGG: Make the amount of blocks sending to client and the total
\r
97 amount of blocks dynamically limited. Transferring blocks is the
\r
98 main network eater of this system, so it is the one that has
\r
99 to be throttled so that RTTs stay low.
\r
101 SUGG: Meshes of blocks could be split into 6 meshes facing into
\r
102 different directions and then only those drawn that need to be
\r
104 SUGG: Calculate lighting per vertex to get a lighting effect like in
\r
110 - Aim for something like controlling a single dwarf in Dwarf Fortress
\r
112 - The player could go faster by a crafting a boat, or riding an animal
\r
114 - Random NPC traders. what else?
\r
119 Build system / running:
\r
120 -----------------------
\r
122 Networking and serialization:
\r
123 -----------------------------
\r
125 TODO: Get rid of GotSplitPacketException
\r
130 TODO: Add gui option to remove map
\r
132 TODO: Configuration menu, at least for keys
\r
137 TODO: Optimize day/night mesh updating somehow
\r
138 - create copies of all textures for all lighting values and only
\r
139 change texture for material?
\r
140 - Umm... the collecting of the faces is the slow part
\r
141 -> what about just changing the color values of the existing
\r
142 meshbuffers? It should go quite fast.
\r
143 - This is not easy; There'd need to be a buffer somewhere
\r
144 that would contain the night and day lighting values.
\r
145 - Actually if FastFaces would be stored, they could
\r
148 FEATURE: Combine MapBlock's face caches to so big pieces that VBO
\r
150 - That is >500 vertices
\r
151 - This is not easy; all the MapBlocks close to the player would
\r
152 still need to be drawn separately and combining the blocks
\r
153 would have to happen in a background thread
\r
155 TODO: Make fetching sector's blocks more efficient when rendering
\r
156 sectors that have very large amounts of blocks (on client)
\r
157 - Is this necessary at all?
\r
159 TODO: Flowing water animation
\r
161 * Combine meshes to bigger ones in ClientMap and set them EHM_STATIC
\r
163 SUGG: Draw cubes in inventory directly with 3D drawing commands, so that
\r
164 animating them is easier.
\r
172 TODO: Untie client network operations from framerate
\r
173 - Needs some input queues or something
\r
175 SUGG: Make morning and evening transition more smooth and maybe shorter
\r
177 SUGG: Don't update all meshes always on single node changes, but
\r
178 check which ones should be updated
\r
179 - implement Map::updateNodeMeshes()
\r
181 TODO: Remove IrrlichtWrapper
\r
186 SUGG: Make an option to the server to disable building and digging near
\r
187 the starting position
\r
189 TODO: Copy the text of the last picked sign to inventory in creative
\r
192 TODO: Check what goes wrong with caching map to disk (Kray)
\r
195 FIXME: Server sometimes goes into some infinite PeerNotFoundException loop
\r
197 * Fix the problem with the server constantly saving one or a few
\r
198 blocks? List the first saved block, maybe it explains.
\r
199 - It is probably caused by oscillating water
\r
200 * Make a small history check to transformLiquids to detect and log
\r
201 continuous oscillations, in such detail that they can be fixed.
\r
203 TODO: When player dies, throw items on map
\r
208 TODO: There has to be some better way to handle static objects than to
\r
209 send them all the time. This affects signs and item objects.
\r
210 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
211 need an additional metadata field for the texts
\r
212 - This is also needed for item container chests
\r
214 Block object server side:
\r
215 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
216 - For all blocks in the buffer, objects are stepped(). This
\r
217 means they are active.
\r
218 - A global active buffer is needed for the server
\r
219 - A timestamp to blocks
\r
220 - All blocks going in and out of the buffer are recorded.
\r
221 - For outgoing blocks, timestamp is written.
\r
222 - For incoming blocks, time difference is calculated and
\r
223 objects are stepped according to it.
\r
225 - When an active object goes far from a player, either delete
\r
226 it or store it statically.
\r
227 - When a statically stored active object comes near a player,
\r
228 recreate the active object
\r
230 * Continue making the scripting system:
\r
231 * Make updateNodeMesh for a less verbose mesh update on add/removenode
\r
232 * Switch to using a safe way for the self and env pointers
\r
233 * Make some global environment hooks, like node placed and general
\r
235 * Add a global Lua spawn handler and such
\r
236 * Get rid of MapBlockObjects
\r
237 * Other players could be sent to clients as LuaCAOs
\r
242 TODO: Mineral and ground material properties
\r
243 - This way mineral ground toughness can be calculated with just
\r
244 some formula, as well as tool strengths
\r
246 TODO: Flowing water to actually contain flow direction information
\r
248 FEATURE: Create a system that allows a huge amount of different "map
\r
249 generator modules/filters"
\r
251 FEATURE: Erosion simulation at map generation time
\r
252 - Simulate water flows, which would carve out dirt fast and
\r
253 then turn stone into gravel and sand and relocate it.
\r
254 - How about relocating minerals, too? Coal and gold in
\r
255 downstream sand and gravel would be kind of cool
\r
256 - This would need a better way of handling minerals, mainly
\r
257 to have mineral content as a separate field. the first
\r
258 parameter field is free for this.
\r
259 - Simulate rock falling from cliffs when water has removed
\r
260 enough solid rock from the bottom
\r
263 * only_from_disk might not work anymore - check and fix it.
\r
264 * Make the generator to run in background and not blocking block
\r
265 placement and transfer
\r
266 * Possibly add some kind of erosion and other stuff
\r
267 * Make client to fetch stuff asynchronously
\r
268 - Needs method SyncProcessData
\r
269 * Better water generation (spread it to underwater caverns but don't
\r
270 fill dungeons that don't touch big water masses)
\r
271 * When generating a chunk and the neighboring chunk doesn't have mud
\r
272 and stuff yet and the ground is fairly flat, the mud will flow to
\r
273 the other chunk making nasty straight walls when the other chunk
\r
274 is generated. Fix it.
\r
278 * Make an "environment metafile" to store at least time of day
\r
279 * Move digging property stuff from material.{h,cpp} to mapnode.cpp...
\r
280 - Or maybe move content_features to material.{h,cpp}?
\r
282 Make a system for pregenerating quick information for mapblocks, so
\r
283 that the client can show them as cubes before they are actually sent
\r
285 * Optimize VoxelManipulator lighting implementation by using indices
\r
286 in place of coordinates?
\r
288 Making it more portable:
\r
289 ------------------------
\r
290 * Some MSVC: std::sto* are defined without a namespace and collide
\r
291 with the ones in utility.h
\r
293 ======================================================================
\r
298 Setting this to 1 enables a special camera mode that forces
\r
299 the renderers to think that the camera statically points from
\r
300 the starting place to a static direction.
\r
302 This allows one to move around with the player and see what
\r
303 is actually drawn behind solid things and behind the player.
\r
305 #define FIELD_OF_VIEW_TEST 0
\r
309 #pragma message ("Disabling unit tests")
\r
311 #warning "Disabling unit tests"
\r
313 // Disable unit tests
\r
314 #define ENABLE_TESTS 0
\r
316 // Enable unit tests
\r
317 #define ENABLE_TESTS 1
\r
321 #pragma comment(lib, "Irrlicht.lib")
\r
322 //#pragma comment(lib, "jthread.lib")
\r
323 #pragma comment(lib, "zlibwapi.lib")
\r
324 #pragma comment(lib, "Shell32.lib")
\r
325 // This would get rid of the console window
\r
326 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
329 #include <iostream>
\r
331 #include <jmutexautolock.h>
\r
332 #include <locale.h>
\r
334 #include "common_irrlicht.h"
\r
337 #include "player.h"
\r
339 //#include "environment.h"
\r
340 #include "server.h"
\r
341 #include "client.h"
\r
342 //#include "serialization.h"
\r
343 #include "constants.h"
\r
344 //#include "strfnd.h"
\r
345 #include "porting.h"
\r
346 #include "irrlichtwrapper.h"
\r
347 #include "gettime.h"
\r
348 #include "porting.h"
\r
349 #include "guiPauseMenu.h"
\r
350 #include "guiInventoryMenu.h"
\r
351 #include "guiTextInputMenu.h"
\r
352 #include "materials.h"
\r
353 #include "guiMessageMenu.h"
\r
354 #include "filesys.h"
\r
355 #include "config.h"
\r
356 #include "guiMainMenu.h"
\r
357 #include "mineral.h"
\r
361 // TODO: Remove this
\r
362 IrrlichtWrapper *g_irrlicht = NULL;
\r
364 // This makes textures
\r
365 ITextureSource *g_texturesource = NULL;
\r
367 MapDrawControl draw_control;
\r
371 These are loaded from the config file.
\r
374 Settings g_settings;
\r
376 extern void set_default_settings();
\r
382 IrrlichtDevice *g_device = NULL;
\r
383 Client *g_client = NULL;
\r
385 /*const s16 quickinv_size = 40;
\r
386 const s16 quickinv_padding = 8;
\r
387 const s16 quickinv_spacing = quickinv_size + quickinv_padding;
\r
388 const s16 quickinv_outer_padding = 4;
\r
389 const s16 quickinv_itemcount = 8;*/
\r
391 const s32 hotbar_itemcount = 8;
\r
392 const s32 hotbar_imagesize = 36;
\r
398 gui::IGUIEnvironment* guienv = NULL;
\r
399 gui::IGUIStaticText *guiroot = NULL;
\r
401 class MainMenuManager : public IMenuManager
\r
404 virtual void createdMenu(GUIModalMenu *menu)
\r
406 for(core::list<GUIModalMenu*>::Iterator
\r
407 i = m_stack.begin();
\r
408 i != m_stack.end(); i++)
\r
410 assert(*i != menu);
\r
413 if(m_stack.size() != 0)
\r
414 (*m_stack.getLast())->setVisible(false);
\r
415 m_stack.push_back(menu);
\r
418 virtual void deletingMenu(GUIModalMenu *menu)
\r
420 // Remove all entries if there are duplicates
\r
421 bool removed_entry;
\r
423 removed_entry = false;
\r
424 for(core::list<GUIModalMenu*>::Iterator
\r
425 i = m_stack.begin();
\r
426 i != m_stack.end(); i++)
\r
431 removed_entry = true;
\r
435 }while(removed_entry);
\r
437 /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
\r
438 assert(*i == menu);
\r
439 m_stack.erase(i);*/
\r
441 if(m_stack.size() != 0)
\r
442 (*m_stack.getLast())->setVisible(true);
\r
447 return m_stack.size();
\r
450 core::list<GUIModalMenu*> m_stack;
\r
453 MainMenuManager g_menumgr;
\r
455 bool noMenuActive()
\r
457 return (g_menumgr.menuCount() == 0);
\r
460 bool g_disconnect_requested = false;
\r
462 class MainGameCallback : public IGameCallback
\r
465 virtual void exitToOS()
\r
467 g_device->closeDevice();
\r
470 virtual void disconnect()
\r
472 g_disconnect_requested = true;
\r
476 MainGameCallback g_gamecallback;
\r
478 // Inventory actions from the menu are buffered here before sending
\r
479 Queue<InventoryAction*> inventory_action_queue;
\r
480 // This is a copy of the inventory that the client's environment has
\r
481 Inventory local_inventory;
\r
483 u16 g_selected_item = 0;
\r
485 /*bool g_show_map_plot = false;
\r
486 bool g_refresh_map_plot = false;*/
\r
493 std::ostream *dout_con_ptr = &dummyout;
\r
494 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
495 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
496 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
497 //std::ostream *dout_con_ptr = &dstream;
\r
498 //std::ostream *derr_con_ptr = &dstream;
\r
501 std::ostream *dout_server_ptr = &dstream;
\r
502 std::ostream *derr_server_ptr = &dstream;
\r
505 std::ostream *dout_client_ptr = &dstream;
\r
506 std::ostream *derr_client_ptr = &dstream;
\r
509 gettime.h implementation
\r
515 Use irrlicht because it is more precise than porting.h's
\r
518 if(g_irrlicht == NULL)
\r
520 return g_irrlicht->getTime();
\r
527 struct TextDestSign : public TextDest
\r
529 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
531 m_blockpos = blockpos;
\r
535 void gotText(std::wstring text)
\r
537 std::string ntext = wide_to_narrow(text);
\r
538 dstream<<"Changing text of a sign object: "
\r
539 <<ntext<<std::endl;
\r
540 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
548 struct TextDestChat : public TextDest
\r
550 TextDestChat(Client *client)
\r
554 void gotText(std::wstring text)
\r
556 // Discard empty line
\r
560 // Parse command (server command starts with "/#")
\r
561 if(text[0] == L'/' && text[1] != L'#')
\r
563 std::wstring reply = L"Local: ";
\r
565 reply += L"Local commands not yet supported. "
\r
566 L"Server prefix is \"/#\".";
\r
568 m_client->addChatMessage(reply);
\r
573 m_client->sendChatMessage(text);
\r
575 m_client->addChatMessage(text);
\r
581 struct TextDestSignNode : public TextDest
\r
583 TextDestSignNode(v3s16 p, Client *client)
\r
588 void gotText(std::wstring text)
\r
590 std::string ntext = wide_to_narrow(text);
\r
591 dstream<<"Changing text of a sign node: "
\r
592 <<ntext<<std::endl;
\r
593 m_client->sendSignNodeText(m_p, ntext);
\r
600 class MyEventReceiver : public IEventReceiver
\r
603 // This is the one method that we have to implement
\r
604 virtual bool OnEvent(const SEvent& event)
\r
607 React to nothing here if a menu is active
\r
609 if(noMenuActive() == false)
\r
615 // Remember whether each key is down or up
\r
616 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
618 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
620 if(event.KeyInput.PressedDown)
\r
622 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
623 /*if(g_show_map_plot)
\r
625 if(event.KeyInput.Key == irr::KEY_ESCAPE
\r
626 || event.KeyInput.Key == irr::KEY_KEY_M)
\r
628 g_show_map_plot = false;
\r
637 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
639 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
641 dstream<<DTIME<<"MyEventReceiver: "
\r
642 <<"Launching pause menu"<<std::endl;
\r
643 // It will delete itself by itself
\r
644 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
645 &g_menumgr))->drop();
\r
648 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
650 dstream<<DTIME<<"MyEventReceiver: "
\r
651 <<"Launching inventory"<<std::endl;
\r
652 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
653 &local_inventory, &inventory_action_queue,
\r
654 &g_menumgr))->drop();
\r
657 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
659 TextDest *dest = new TextDestChat(g_client);
\r
661 (new GUITextInputMenu(guienv, guiroot, -1,
\r
668 if(event.KeyInput.Key >= irr::KEY_KEY_0
\r
669 && event.KeyInput.Key <= irr::KEY_KEY_9)
\r
671 u16 s1 = event.KeyInput.Key - irr::KEY_KEY_0;
\r
672 if(event.KeyInput.Key == irr::KEY_KEY_0)
\r
674 if(s1 < PLAYER_INVENTORY_SIZE && s1 < hotbar_itemcount)
\r
675 g_selected_item = s1-1;
\r
676 dstream<<DTIME<<"Selected item: "
\r
677 <<g_selected_item<<std::endl;
\r
680 // Viewing range selection
\r
681 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
683 if(draw_control.range_all)
\r
685 draw_control.range_all = false;
\r
686 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
690 draw_control.range_all = true;
\r
691 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
695 // Print debug stacks
\r
696 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
698 dstream<<"-----------------------------------------"
\r
700 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
701 dstream<<"-----------------------------------------"
\r
703 debug_stacks_print();
\r
707 /*if(event.KeyInput.Key == irr::KEY_KEY_M)
\r
709 dstream<<"Map plot requested"<<std::endl;
\r
710 g_show_map_plot = !g_show_map_plot;
\r
711 if(g_show_map_plot)
\r
712 g_refresh_map_plot = true;
\r
718 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
720 if(noMenuActive() == false)
\r
722 left_active = false;
\r
723 middle_active = false;
\r
724 right_active = false;
\r
728 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
729 left_active = event.MouseInput.isLeftPressed();
\r
730 middle_active = event.MouseInput.isMiddlePressed();
\r
731 right_active = event.MouseInput.isRightPressed();
\r
733 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
735 leftclicked = true;
\r
737 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
739 rightclicked = true;
\r
741 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
743 leftreleased = true;
\r
745 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
747 rightreleased = true;
\r
749 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
751 /*dstream<<"event.MouseInput.Wheel="
\r
752 <<event.MouseInput.Wheel<<std::endl;*/
\r
754 u16 max_item = MYMIN(PLAYER_INVENTORY_SIZE-1,
\r
755 hotbar_itemcount-1);
\r
756 if(event.MouseInput.Wheel < 0)
\r
758 if(g_selected_item < max_item)
\r
761 g_selected_item = 0;
\r
763 else if(event.MouseInput.Wheel > 0)
\r
765 if(g_selected_item > 0)
\r
768 g_selected_item = max_item;
\r
777 // This is used to check whether a key is being held down
\r
778 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
780 return keyIsDown[keyCode];
\r
785 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
786 keyIsDown[i] = false;
\r
788 leftclicked = false;
\r
789 rightclicked = false;
\r
790 leftreleased = false;
\r
791 rightreleased = false;
\r
793 left_active = false;
\r
794 middle_active = false;
\r
795 right_active = false;
\r
806 bool rightreleased;
\r
809 bool middle_active;
\r
813 // We use this array to store the current state of each key
\r
814 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
817 IrrlichtDevice *m_device;
\r
826 virtual ~InputHandler()
\r
830 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
832 virtual v2s32 getMousePos() = 0;
\r
833 virtual void setMousePos(s32 x, s32 y) = 0;
\r
835 virtual bool getLeftState() = 0;
\r
836 virtual bool getRightState() = 0;
\r
838 virtual bool getLeftClicked() = 0;
\r
839 virtual bool getRightClicked() = 0;
\r
840 virtual void resetLeftClicked() = 0;
\r
841 virtual void resetRightClicked() = 0;
\r
843 virtual bool getLeftReleased() = 0;
\r
844 virtual bool getRightReleased() = 0;
\r
845 virtual void resetLeftReleased() = 0;
\r
846 virtual void resetRightReleased() = 0;
\r
848 virtual void step(float dtime) {};
\r
850 virtual void clear() {};
\r
853 InputHandler *g_input = NULL;
\r
855 class RealInputHandler : public InputHandler
\r
858 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
860 m_receiver(receiver)
\r
863 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
865 return m_receiver->IsKeyDown(keyCode);
\r
867 virtual v2s32 getMousePos()
\r
869 return m_device->getCursorControl()->getPosition();
\r
871 virtual void setMousePos(s32 x, s32 y)
\r
873 m_device->getCursorControl()->setPosition(x, y);
\r
876 virtual bool getLeftState()
\r
878 return m_receiver->left_active;
\r
880 virtual bool getRightState()
\r
882 return m_receiver->right_active;
\r
885 virtual bool getLeftClicked()
\r
887 return m_receiver->leftclicked;
\r
889 virtual bool getRightClicked()
\r
891 return m_receiver->rightclicked;
\r
893 virtual void resetLeftClicked()
\r
895 m_receiver->leftclicked = false;
\r
897 virtual void resetRightClicked()
\r
899 m_receiver->rightclicked = false;
\r
902 virtual bool getLeftReleased()
\r
904 return m_receiver->leftreleased;
\r
906 virtual bool getRightReleased()
\r
908 return m_receiver->rightreleased;
\r
910 virtual void resetLeftReleased()
\r
912 m_receiver->leftreleased = false;
\r
914 virtual void resetRightReleased()
\r
916 m_receiver->rightreleased = false;
\r
921 resetRightClicked();
\r
922 resetLeftClicked();
\r
925 IrrlichtDevice *m_device;
\r
926 MyEventReceiver *m_receiver;
\r
929 class RandomInputHandler : public InputHandler
\r
932 RandomInputHandler()
\r
936 leftclicked = false;
\r
937 rightclicked = false;
\r
938 leftreleased = false;
\r
939 rightreleased = false;
\r
940 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
941 keydown[i] = false;
\r
943 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
945 return keydown[keyCode];
\r
947 virtual v2s32 getMousePos()
\r
951 virtual void setMousePos(s32 x, s32 y)
\r
953 mousepos = v2s32(x,y);
\r
956 virtual bool getLeftState()
\r
960 virtual bool getRightState()
\r
965 virtual bool getLeftClicked()
\r
967 return leftclicked;
\r
969 virtual bool getRightClicked()
\r
971 return rightclicked;
\r
973 virtual void resetLeftClicked()
\r
975 leftclicked = false;
\r
977 virtual void resetRightClicked()
\r
979 rightclicked = false;
\r
982 virtual bool getLeftReleased()
\r
984 return leftreleased;
\r
986 virtual bool getRightReleased()
\r
988 return rightreleased;
\r
990 virtual void resetLeftReleased()
\r
992 leftreleased = false;
\r
994 virtual void resetRightReleased()
\r
996 rightreleased = false;
\r
999 virtual void step(float dtime)
\r
1002 static float counter1 = 0;
\r
1003 counter1 -= dtime;
\r
1004 if(counter1 < 0.0)
\r
1006 counter1 = 0.1*Rand(1, 40);
\r
1007 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
1011 static float counter1 = 0;
\r
1012 counter1 -= dtime;
\r
1013 if(counter1 < 0.0)
\r
1015 counter1 = 0.1*Rand(1, 40);
\r
1016 keydown[irr::KEY_KEY_E] = !keydown[irr::KEY_KEY_E];
\r
1020 static float counter1 = 0;
\r
1021 counter1 -= dtime;
\r
1022 if(counter1 < 0.0)
\r
1024 counter1 = 0.1*Rand(1, 40);
\r
1025 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
1029 static float counter1 = 0;
\r
1030 counter1 -= dtime;
\r
1031 if(counter1 < 0.0)
\r
1033 counter1 = 0.1*Rand(1, 40);
\r
1034 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
1038 static float counter1 = 0;
\r
1039 counter1 -= dtime;
\r
1040 if(counter1 < 0.0)
\r
1042 counter1 = 0.1*Rand(1, 20);
\r
1043 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
1047 static float counter1 = 0;
\r
1048 counter1 -= dtime;
\r
1049 if(counter1 < 0.0)
\r
1051 counter1 = 0.1*Rand(1, 30);
\r
1052 leftdown = !leftdown;
\r
1054 leftclicked = true;
\r
1056 leftreleased = true;
\r
1060 static float counter1 = 0;
\r
1061 counter1 -= dtime;
\r
1062 if(counter1 < 0.0)
\r
1064 counter1 = 0.1*Rand(1, 15);
\r
1065 rightdown = !rightdown;
\r
1067 rightclicked = true;
\r
1069 rightreleased = true;
\r
1072 mousepos += mousespeed;
\r
1075 s32 Rand(s32 min, s32 max)
\r
1077 return (myrand()%(max-min+1))+min;
\r
1080 bool keydown[KEY_KEY_CODES_COUNT];
\r
1086 bool rightclicked;
\r
1087 bool leftreleased;
\r
1088 bool rightreleased;
\r
1091 void updateViewingRange(f32 frametime_in, Client *client)
\r
1093 if(draw_control.range_all == true)
\r
1096 static f32 added_frametime = 0;
\r
1097 static s16 added_frames = 0;
\r
1099 added_frametime += frametime_in;
\r
1100 added_frames += 1;
\r
1102 // Actually this counter kind of sucks because frametime is busytime
\r
1103 static f32 counter = 0;
\r
1104 counter -= frametime_in;
\r
1110 /*dstream<<__FUNCTION_NAME
\r
1111 <<": Collected "<<added_frames<<" frames, total of "
\r
1112 <<added_frametime<<"s."<<std::endl;*/
\r
1114 /*dstream<<"draw_control.blocks_drawn="
\r
1115 <<draw_control.blocks_drawn
\r
1116 <<", draw_control.blocks_would_have_drawn="
\r
1117 <<draw_control.blocks_would_have_drawn
\r
1120 float range_min = g_settings.getS16("viewing_range_nodes_min");
\r
1121 float range_max = g_settings.getS16("viewing_range_nodes_max");
\r
1123 draw_control.wanted_min_range = range_min;
\r
1124 draw_control.wanted_max_blocks = (1.2*draw_control.blocks_drawn)+1;
\r
1126 float block_draw_ratio = 1.0;
\r
1127 if(draw_control.blocks_would_have_drawn != 0)
\r
1129 block_draw_ratio = (float)draw_control.blocks_drawn
\r
1130 / (float)draw_control.blocks_would_have_drawn;
\r
1133 // Calculate the average frametime in the case that all wanted
\r
1134 // blocks had been drawn
\r
1135 f32 frametime = added_frametime / added_frames / block_draw_ratio;
\r
1137 added_frametime = 0.0;
\r
1140 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
1141 float wanted_frametime = 1.0 / wanted_fps;
\r
1143 f32 wanted_frametime_change = wanted_frametime - frametime;
\r
1144 //dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
\r
1146 // If needed frametime change is small, just return
\r
1147 if(fabs(wanted_frametime_change) < wanted_frametime*0.4)
\r
1149 //dstream<<"ignoring small wanted_frametime_change"<<std::endl;
\r
1153 float range = draw_control.wanted_range;
\r
1154 float new_range = range;
\r
1156 static s16 range_old = 0;
\r
1157 static f32 frametime_old = 0;
\r
1159 float d_range = range - range_old;
\r
1160 f32 d_frametime = frametime - frametime_old;
\r
1161 // A sane default of 30ms per 50 nodes of range
\r
1162 static f32 time_per_range = 30. / 50;
\r
1165 time_per_range = d_frametime / d_range;
\r
1168 // The minimum allowed calculated frametime-range derivative:
\r
1169 // Practically this sets the maximum speed of changing the range.
\r
1170 // The lower this value, the higher the maximum changing speed.
\r
1171 // A low value here results in wobbly range (0.001)
\r
1172 // A high value here results in slow changing range (0.0025)
\r
1173 // SUGG: This could be dynamically adjusted so that when
\r
1174 // the camera is turning, this is lower
\r
1175 //float min_time_per_range = 0.0015;
\r
1176 float min_time_per_range = 0.0010;
\r
1177 //float min_time_per_range = 0.05 / range;
\r
1178 if(time_per_range < min_time_per_range)
\r
1180 time_per_range = min_time_per_range;
\r
1181 //dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
\r
1185 //dstream<<"time_per_range="<<time_per_range<<std::endl;
\r
1188 f32 wanted_range_change = wanted_frametime_change / time_per_range;
\r
1189 // Dampen the change a bit to kill oscillations
\r
1190 //wanted_range_change *= 0.9;
\r
1191 //wanted_range_change *= 0.75;
\r
1192 wanted_range_change *= 0.5;
\r
1193 //dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
\r
1195 // If needed range change is very small, just return
\r
1196 if(fabs(wanted_range_change) < 0.001)
\r
1198 //dstream<<"ignoring small wanted_range_change"<<std::endl;
\r
1202 new_range += wanted_range_change;
\r
1203 //dstream<<"new_range="<<new_range/*<<std::endl*/;
\r
1205 //float new_range_unclamped = new_range;
\r
1206 if(new_range < range_min)
\r
1207 new_range = range_min;
\r
1208 if(new_range > range_max)
\r
1209 new_range = range_max;
\r
1211 /*if(new_range != new_range_unclamped)
\r
1212 dstream<<", clamped to "<<new_range<<std::endl;
\r
1214 dstream<<std::endl;*/
\r
1216 draw_control.wanted_range = new_range;
\r
1218 range_old = new_range;
\r
1219 frametime_old = frametime;
\r
1222 void draw_hotbar(video::IVideoDriver *driver, gui::IGUIFont *font,
\r
1223 v2s32 centerlowerpos, s32 imgsize, s32 itemcount,
\r
1224 Inventory *inventory)
\r
1226 InventoryList *mainlist = inventory->getList("main");
\r
1227 if(mainlist == NULL)
\r
1229 dstream<<"WARNING: draw_hotbar(): mainlist == NULL"<<std::endl;
\r
1233 s32 padding = imgsize/12;
\r
1234 //s32 height = imgsize + padding*2;
\r
1235 s32 width = itemcount*(imgsize+padding*2);
\r
1237 // Position of upper left corner of bar
\r
1238 v2s32 pos = centerlowerpos - v2s32(width/2, imgsize+padding*2);
\r
1240 // Draw background color
\r
1241 /*core::rect<s32> barrect(0,0,width,height);
\r
1243 video::SColor bgcolor(255,128,128,128);
\r
1244 driver->draw2DRectangle(bgcolor, barrect, NULL);*/
\r
1246 core::rect<s32> imgrect(0,0,imgsize,imgsize);
\r
1248 for(s32 i=0; i<itemcount; i++)
\r
1250 InventoryItem *item = mainlist->getItem(i);
\r
1252 core::rect<s32> rect = imgrect + pos
\r
1253 + v2s32(padding+i*(imgsize+padding*2), padding);
\r
1255 if(g_selected_item == i)
\r
1257 driver->draw2DRectangle(video::SColor(255,255,0,0),
\r
1258 core::rect<s32>(rect.UpperLeftCorner - v2s32(1,1)*padding,
\r
1259 rect.LowerRightCorner + v2s32(1,1)*padding),
\r
1264 video::SColor bgcolor2(128,0,0,0);
\r
1265 driver->draw2DRectangle(bgcolor2, rect, NULL);
\r
1270 drawInventoryItem(driver, font, item, rect, NULL);
\r
1276 video::ITexture *g_map_plot_texture = NULL;
\r
1277 float g_map_plot_texture_scale = 4;
\r
1279 void updateMapPlotTexture(v2f centerpos, video::IVideoDriver* driver,
\r
1285 core::dimension2d<u32> dim(640,480);
\r
1286 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
\r
1288 for(u32 y=0; y<dim.Height; y++)
\r
1289 for(u32 x=0; x<dim.Width; x++)
\r
1291 v2f pf = v2f(x, dim.Height-y) - v2f(dim.Width, dim.Height)/2;
\r
1292 pf *= g_map_plot_texture_scale;
\r
1294 double h = base_rock_level_2d(client->getMapSeed(), pf);
\r
1297 /*s32 ux = x - centerpos.X / g_map_plot_texture_scale;
\r
1298 s32 uy = y - centerpos.Y / g_map_plot_texture_scale;*/
\r
1300 // Screen coordinates that are based on multiples of
\r
1301 // 1000/g_map_plot_texture_scale and never negative
\r
1302 u32 ux = x + (u32)(1000/g_map_plot_texture_scale) * 10;
\r
1303 u32 uy = y + (u32)(1000/g_map_plot_texture_scale) * 10;
\r
1304 // Offset to center of image
\r
1305 ux -= dim.Width/2;
\r
1306 uy -= dim.Height/2;
\r
1308 if(uy % (u32)(1000/g_map_plot_texture_scale) == 0
\r
1309 || ux % (u32)(1000/g_map_plot_texture_scale) == 0)
\r
1310 c.set(255, 255, 255, 255);
\r
1311 else if(uy % (u32)(100/g_map_plot_texture_scale) == 0
\r
1312 || ux % (u32)(100/g_map_plot_texture_scale) == 0)
\r
1313 c.set(255, 160, 160, 160);
\r
1314 else if(h < WATER_LEVEL - 0.5) // Water
\r
1315 c.set(255, 50, 50, 255);
\r
1317 else if(get_have_sand_ground(client->getMapSeed(), pf)
\r
1318 || (h < WATER_LEVEL + 2
\r
1319 && get_have_sand_coast(client->getMapSeed(), pf)))
\r
1323 h = 1.0 - exp(-h);
\r
1325 video::SColor c1(255,237,201,175);
\r
1326 //video::SColor c2(255,20,20,20);
\r
1327 video::SColor c2(255,150,0,0);
\r
1328 c = c2.getInterpolated(c1, h);
\r
1334 h = 1.0 - exp(-h);
\r
1336 video::SColor c1(255,110,185,90);
\r
1337 //video::SColor c2(255,20,20,20);
\r
1338 video::SColor c2(255,150,0,0);
\r
1339 c = c2.getInterpolated(c1, h);
\r
1344 else if(get_have_sand_ground(client->getMapSeed(), pf))
\r
1348 h = 1.0 - exp(-h);
\r
1350 video::SColor c1(255,237,201,175);
\r
1351 //video::SColor c2(255,20,20,20);
\r
1352 video::SColor c2(255,150,0,0);
\r
1353 c = c2.getInterpolated(c1, h);
\r
1357 else if(h < WATER_LEVEL + 2
\r
1358 && get_have_sand_coast(client->getMapSeed(), pf))
\r
1359 c.set(255, 237, 201, 175);
\r
1361 else if(h < WATER_LEVEL + 10)
\r
1362 c.set(255, 50, 150, 50); // Green
\r
1363 else if(h < WATER_LEVEL + 20)
\r
1364 c.set(255, 110, 185, 50); // Yellowish green
\r
1365 else if(h < WATER_LEVEL + 40)
\r
1366 c.set(255, 180, 210, 50); // Greenish yellow
\r
1367 else if(h < WATER_LEVEL + 60)
\r
1368 c.set(255, 220, 220, 50); // Yellow
\r
1369 else if(h < WATER_LEVEL + 80)
\r
1370 c.set(255, 200, 200, 110); // Yellowish white
\r
1371 else if(h < WATER_LEVEL + 100)
\r
1372 c.set(255, 190, 190, 190); // Grey
\r
1374 c.set(255, 255, 255, 255); // White
\r
1382 h = 1.0 - exp(-h);
\r
1384 video::SColor c1(255,200,200,50);
\r
1385 video::SColor c2(255,0,150,0);
\r
1386 c = c1.getInterpolated(c2, h);
\r
1388 /*u32 a = (u32)(h*255);
\r
1392 c.set(255, a, a, a);*/
\r
1396 if(h >= WATER_LEVEL - 0.5
\r
1397 && get_have_sand_ground(client->getMapSeed(), pf))
\r
1399 video::SColor c1(255,237,201,175);
\r
1400 c = c.getInterpolated(c1, 0.5);
\r
1404 double tf = get_turbulence_factor_2d(client->getMapSeed(), pf);
\r
1407 video::SColor c1(255,255,0,0);
\r
1408 c = c.getInterpolated(c1, 1.0-(0.5*tf));
\r
1411 img->setPixel(x, y, c);
\r
1413 g_map_plot_texture = driver->addTexture("map_plot", img);
\r
1415 assert(g_map_plot_texture);
\r
1426 ChatLine(const std::wstring &a_text):
\r
1432 std::wstring text;
\r
1435 // These are defined global so that they're not optimized too much.
\r
1436 // Can't change them to volatile.
\r
1441 std::string tempstring;
\r
1442 std::string tempstring2;
\r
1447 dstream<<"The following test should take around 20ms."<<std::endl;
\r
1448 TimeTaker timer("Testing std::string speed");
\r
1449 const u32 jj = 10000;
\r
1450 for(u32 j=0; j<jj; j++)
\r
1454 const u32 ii = 10;
\r
1455 for(u32 i=0; i<ii; i++){
\r
1456 tempstring2 += "asd";
\r
1458 for(u32 i=0; i<ii+1; i++){
\r
1459 tempstring += "asd";
\r
1460 if(tempstring == tempstring2)
\r
1466 dstream<<"All of the following tests should take around 100ms each."
\r
1470 TimeTaker timer("Testing floating-point conversion speed");
\r
1472 for(u32 i=0; i<4000000; i++){
\r
1479 TimeTaker timer("Testing floating-point vector speed");
\r
1481 tempv3f1 = v3f(1,2,3);
\r
1482 tempv3f2 = v3f(4,5,6);
\r
1483 for(u32 i=0; i<10000000; i++){
\r
1484 tempf += tempv3f1.dotProduct(tempv3f2);
\r
1485 tempv3f2 += v3f(7,8,9);
\r
1490 TimeTaker timer("Testing core::map speed");
\r
1492 core::map<v2s16, f32> map1;
\r
1495 for(s16 y=0; y<ii; y++){
\r
1496 for(s16 x=0; x<ii; x++){
\r
1497 map1.insert(v2s16(x,y), tempf);
\r
1501 for(s16 y=ii-1; y>=0; y--){
\r
1502 for(s16 x=0; x<ii; x++){
\r
1503 tempf = map1[v2s16(x,y)];
\r
1509 dstream<<"Around 5000/ms should do well here."<<std::endl;
\r
1510 TimeTaker timer("Testing mutex speed");
\r
1523 // Do at least 10ms
\r
1524 while(timer.getTime() < 10);
\r
1526 u32 dtime = timer.stop();
\r
1527 u32 per_ms = n / dtime;
\r
1528 std::cout<<"Done. "<<dtime<<"ms, "
\r
1529 <<per_ms<<"/ms"<<std::endl;
\r
1533 int main(int argc, char *argv[])
\r
1536 Parse command line
\r
1539 // List all allowed options
\r
1540 core::map<std::string, ValueSpec> allowed_options;
\r
1541 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1542 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1543 "Run server directly"));
\r
1544 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1545 "Load configuration from specified file"));
\r
1546 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1547 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1548 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1549 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1550 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1551 allowed_options.insert("map-dir", ValueSpec(VALUETYPE_STRING));
\r
1553 allowed_options.insert("dstream-on-stderr", ValueSpec(VALUETYPE_FLAG));
\r
1555 allowed_options.insert("speedtests", ValueSpec(VALUETYPE_FLAG));
\r
1557 Settings cmd_args;
\r
1559 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1561 if(ret == false || cmd_args.getFlag("help"))
\r
1563 dstream<<"Allowed options:"<<std::endl;
\r
1564 for(core::map<std::string, ValueSpec>::Iterator
\r
1565 i = allowed_options.getIterator();
\r
1566 i.atEnd() == false; i++)
\r
1568 dstream<<" --"<<i.getNode()->getKey();
\r
1569 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1574 dstream<<" <value>";
\r
1576 dstream<<std::endl;
\r
1578 if(i.getNode()->getValue().help != NULL)
\r
1580 dstream<<" "<<i.getNode()->getValue().help
\r
1585 return cmd_args.getFlag("help") ? 0 : 1;
\r
1589 Low-level initialization
\r
1592 bool disable_stderr = false;
\r
1594 if(cmd_args.getFlag("dstream-on-stderr") == false)
\r
1595 disable_stderr = true;
\r
1598 // Initialize debug streams
\r
1599 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1600 // Initialize debug stacks
\r
1601 debug_stacks_init();
\r
1603 DSTACK(__FUNCTION_NAME);
\r
1605 porting::signal_handler_init();
\r
1606 bool &kill = *porting::signal_handler_killstatus();
\r
1608 porting::initializePaths();
\r
1609 // Create user data directory
\r
1610 fs::CreateDir(porting::path_userdata);
\r
1612 // C-style stuff initialization
\r
1613 initializeMaterialProperties();
\r
1616 BEGIN_DEBUG_EXCEPTION_HANDLER
\r
1618 // Print startup message
\r
1619 dstream<<DTIME<<"minetest-c55"
\r
1620 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1621 <<", "<<BUILD_INFO
\r
1625 Basic initialization
\r
1628 // Initialize default settings
\r
1629 set_default_settings();
\r
1631 // Set locale. This is for forcing '.' as the decimal point.
\r
1632 std::locale::global(std::locale("C"));
\r
1633 // This enables printing all characters in bitmap font
\r
1634 setlocale(LC_CTYPE, "en_US");
\r
1636 // Initialize sockets
\r
1638 atexit(sockets_cleanup);
\r
1648 // Path of configuration file in use
\r
1649 std::string configpath = "";
\r
1651 if(cmd_args.exists("config"))
\r
1653 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1656 dstream<<"Could not read configuration from \""
\r
1657 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1660 configpath = cmd_args.get("config");
\r
1664 core::array<std::string> filenames;
\r
1665 filenames.push_back(porting::path_userdata + "/minetest.conf");
\r
1666 #ifdef RUN_IN_PLACE
\r
1667 filenames.push_back(porting::path_userdata + "/../minetest.conf");
\r
1670 for(u32 i=0; i<filenames.size(); i++)
\r
1672 bool r = g_settings.readConfigFile(filenames[i].c_str());
\r
1675 configpath = filenames[i];
\r
1680 // If no path found, use the first one (menu creates the file)
\r
1681 if(configpath == "")
\r
1682 configpath = filenames[0];
\r
1685 // Initialize random seed
\r
1690 Pre-initialize some stuff with a dummy irrlicht wrapper.
\r
1692 These are needed for unit tests at least.
\r
1695 // Initial call with g_texturesource not set.
\r
1702 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1703 || cmd_args.getFlag("enable-unittests") == true)
\r
1708 /*for(s16 y=-100; y<100; y++)
\r
1709 for(s16 x=-100; x<100; x++)
\r
1711 std::cout<<noise2d_gradient((double)x/10,(double)y/10, 32415)<<std::endl;
\r
1721 if(cmd_args.exists("port"))
\r
1722 port = cmd_args.getU16("port");
\r
1723 else if(cmd_args.exists("port"))
\r
1724 port = g_settings.getU16("port");
\r
1727 std::string map_dir = porting::path_userdata+"/map";
\r
1728 if(cmd_args.exists("map-dir"))
\r
1729 map_dir = cmd_args.get("map-dir");
\r
1730 else if(g_settings.exists("map-dir"))
\r
1731 map_dir = g_settings.get("map-dir");
\r
1733 // Run dedicated server if asked to
\r
1734 if(cmd_args.getFlag("server"))
\r
1736 DSTACK("Dedicated server branch");
\r
1739 Server server(map_dir.c_str());
\r
1740 server.start(port);
\r
1743 dedicated_server_loop(server, kill);
\r
1752 // Address to connect to
\r
1753 std::string address = "";
\r
1755 if(cmd_args.exists("address"))
\r
1757 address = cmd_args.get("address");
\r
1761 address = g_settings.get("address");
\r
1764 std::string playername = g_settings.get("name");
\r
1766 // Resolution selection
\r
1768 bool fullscreen = false;
\r
1769 u16 screenW = g_settings.getU16("screenW");
\r
1770 u16 screenH = g_settings.getU16("screenH");
\r
1772 // Determine driver
\r
1774 video::E_DRIVER_TYPE driverType;
\r
1776 std::string driverstring = g_settings.get("video_driver");
\r
1778 if(driverstring == "null")
\r
1779 driverType = video::EDT_NULL;
\r
1780 else if(driverstring == "software")
\r
1781 driverType = video::EDT_SOFTWARE;
\r
1782 else if(driverstring == "burningsvideo")
\r
1783 driverType = video::EDT_BURNINGSVIDEO;
\r
1784 else if(driverstring == "direct3d8")
\r
1785 driverType = video::EDT_DIRECT3D8;
\r
1786 else if(driverstring == "direct3d9")
\r
1787 driverType = video::EDT_DIRECT3D9;
\r
1788 else if(driverstring == "opengl")
\r
1789 driverType = video::EDT_OPENGL;
\r
1792 dstream<<"WARNING: Invalid video_driver specified; defaulting "
\r
1793 "to opengl"<<std::endl;
\r
1794 driverType = video::EDT_OPENGL;
\r
1797 // create device and exit if creation failed
\r
1799 MyEventReceiver receiver;
\r
1801 IrrlichtDevice *device;
\r
1802 device = createDevice(driverType,
\r
1803 core::dimension2d<u32>(screenW, screenH),
\r
1804 16, fullscreen, false, false, &receiver);
\r
1807 return 1; // could not create selected driver.
\r
1809 g_device = device;
\r
1810 g_irrlicht = new IrrlichtWrapper(device);
\r
1811 TextureSource *texturesource = new TextureSource(device);
\r
1812 g_texturesource = texturesource;
\r
1815 Speed tests (done after irrlicht is loaded to get timer)
\r
1817 if(cmd_args.getFlag("speedtests"))
\r
1819 dstream<<"Running speed tests"<<std::endl;
\r
1824 device->setResizable(true);
\r
1826 bool random_input = g_settings.getBool("random_input")
\r
1827 || cmd_args.getFlag("random-input");
\r
1829 g_input = new RandomInputHandler();
\r
1831 g_input = new RealInputHandler(device, &receiver);
\r
1834 Continue initialization
\r
1837 video::IVideoDriver* driver = device->getVideoDriver();
\r
1840 This changes the minimum allowed number of vertices in a VBO.
\r
1843 //driver->setMinHardwareBufferVertexCount(50);
\r
1845 scene::ISceneManager* smgr = device->getSceneManager();
\r
1847 guienv = device->getGUIEnvironment();
\r
1848 gui::IGUISkin* skin = guienv->getSkin();
\r
1849 gui::IGUIFont* font = guienv->getFont(porting::getDataPath("fontlucida.png").c_str());
\r
1851 skin->setFont(font);
\r
1853 dstream<<"WARNING: Font file was not found."
\r
1854 " Using default font."<<std::endl;
\r
1855 // If font was not found, this will get us one
\r
1856 font = skin->getFont();
\r
1859 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1860 dstream<<"text_height="<<text_height<<std::endl;
\r
1862 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1863 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1864 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1865 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1866 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1867 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1870 Preload some textures and stuff
\r
1873 init_content_inventory_texture_paths();
\r
1874 init_mapnode(); // Second call with g_texturesource set
\r
1882 We need some kind of a root node to be able to add
\r
1883 custom gui elements directly on the screen.
\r
1884 Otherwise they won't be automatically drawn.
\r
1886 guiroot = guienv->addStaticText(L"",
\r
1887 core::rect<s32>(0, 0, 10000, 10000));
\r
1889 // First line of debug text
\r
1890 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1892 core::rect<s32>(5, 5, 795, 5+text_height),
\r
1894 // Second line of debug text
\r
1895 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1897 core::rect<s32>(5, 5+(text_height+5)*1, 795, (5+text_height)*2),
\r
1900 // At the middle of the screen
\r
1901 // Object infos are shown in this
\r
1902 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1904 core::rect<s32>(0,0,400,text_height+5) + v2s32(100,200),
\r
1908 gui::IGUIStaticText *guitext_chat = guienv->addStaticText(
\r
1910 core::rect<s32>(0,0,0,0),
\r
1911 false, false); // Disable word wrap as of now
\r
1913 //guitext_chat->setBackgroundColor(video::SColor(96,0,0,0));
\r
1914 core::list<ChatLine> chat_lines;
\r
1917 If an error occurs, this is set to something and the
\r
1918 menu-game loop is restarted. It is then displayed before
\r
1921 std::wstring error_message = L"";
\r
1926 while(g_device->run() && kill == false)
\r
1929 // This is used for catching disconnects
\r
1934 Out-of-game menu loop.
\r
1936 Loop quits when menu returns proper parameters.
\r
1938 while(kill == false)
\r
1940 // Cursor can be non-visible when coming from the game
\r
1941 device->getCursorControl()->setVisible(true);
\r
1942 // Some stuff are left to scene manager when coming from the game
\r
1943 // (map at least?)
\r
1945 // Reset or hide the debug gui texts
\r
1946 guitext->setText(L"Minetest-c55");
\r
1947 guitext2->setVisible(false);
\r
1948 guitext_info->setVisible(false);
\r
1949 guitext_chat->setVisible(false);
\r
1951 // Initialize menu data
\r
1952 MainMenuData menudata;
\r
1953 menudata.address = narrow_to_wide(address);
\r
1954 menudata.name = narrow_to_wide(playername);
\r
1955 menudata.port = narrow_to_wide(itos(port));
\r
1956 menudata.creative_mode = g_settings.getBool("creative_mode");
\r
1958 GUIMainMenu *menu =
\r
1959 new GUIMainMenu(guienv, guiroot, -1,
\r
1960 &g_menumgr, &menudata, &g_gamecallback);
\r
1961 menu->allowFocusRemoval(true);
\r
1963 if(error_message != L"")
\r
1965 GUIMessageMenu *menu2 =
\r
1966 new GUIMessageMenu(guienv, guiroot, -1,
\r
1967 &g_menumgr, error_message.c_str());
\r
1969 error_message = L"";
\r
1972 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
1974 dstream<<"Created main menu"<<std::endl;
\r
1976 while(g_device->run() && kill == false)
\r
1978 // Run global IrrlichtWrapper's main thread processing stuff
\r
1979 g_irrlicht->Run();
\r
1981 if(menu->getStatus() == true)
\r
1984 //driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1985 driver->beginScene(true, true, video::SColor(255,128,128,128));
\r
1986 guienv->drawAll();
\r
1987 driver->endScene();
\r
1990 // Break out of menu-game loop to shut down cleanly
\r
1991 if(g_device->run() == false || kill == true)
\r
1994 dstream<<"Dropping main menu"<<std::endl;
\r
1998 // Delete map if requested
\r
1999 if(menudata.delete_map)
\r
2001 bool r = fs::RecursiveDeleteContent(map_dir);
\r
2003 error_message = L"Delete failed";
\r
2007 playername = wide_to_narrow(menudata.name);
\r
2008 address = wide_to_narrow(menudata.address);
\r
2009 port = stoi(wide_to_narrow(menudata.port));
\r
2010 g_settings.set("creative_mode", itos(menudata.creative_mode));
\r
2012 // Check for valid parameters, restart menu if invalid.
\r
2013 if(playername == "")
\r
2015 error_message = L"Name required.";
\r
2020 g_settings.set("name", playername);
\r
2021 g_settings.set("address", address);
\r
2022 g_settings.set("port", itos(port));
\r
2023 // Update configuration file
\r
2024 if(configpath != "")
\r
2025 g_settings.updateConfigFile(configpath.c_str());
\r
2027 // Continue to game
\r
2031 // Break out of menu-game loop to shut down cleanly
\r
2032 if(g_device->run() == false)
\r
2036 Make a scope here so that the client and the server and other
\r
2037 stuff gets removed when disconnected or the irrlicht device
\r
2042 // This is set to true at the end of the scope
\r
2043 g_irrlicht->Shutdown(false);
\r
2046 Draw "Loading" screen
\r
2048 const wchar_t *text = L"Loading and connecting...";
\r
2049 core::vector2d<s32> center(screenW/2, screenH/2);
\r
2050 core::vector2d<s32> textsize(300, text_height);
\r
2051 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
2053 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
2054 text, textrect, false, false);
\r
2055 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
2057 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2058 guienv->drawAll();
\r
2059 driver->endScene();
\r
2061 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
2065 SharedPtr will delete it when it goes out of scope.
\r
2067 SharedPtr<Server> server;
\r
2068 if(address == ""){
\r
2069 server = new Server(map_dir);
\r
2070 server->start(port);
\r
2077 Client client(device, playername.c_str(), draw_control);
\r
2079 g_client = &client;
\r
2081 Address connect_address(0,0,0,0, port);
\r
2084 //connect_address.Resolve("localhost");
\r
2085 connect_address.setAddress(127,0,0,1);
\r
2087 connect_address.Resolve(address.c_str());
\r
2089 catch(ResolveError &e)
\r
2091 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
2093 error_message = L"Couldn't resolve address";
\r
2094 gui_loadingtext->remove();
\r
2098 dstream<<DTIME<<"Connecting to server at ";
\r
2099 connect_address.print(&dstream);
\r
2100 dstream<<std::endl;
\r
2101 client.connect(connect_address);
\r
2104 while(client.connectedAndInitialized() == false)
\r
2107 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2108 guienv->drawAll();
\r
2109 driver->endScene();
\r
2111 // Update client and server
\r
2115 if(server != NULL)
\r
2116 server->step(0.1);
\r
2122 catch(con::PeerNotFoundException &e)
\r
2124 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
2126 error_message = L"Connection timed out.";
\r
2127 gui_loadingtext->remove();
\r
2134 /*scene::ISceneNode* skybox;
\r
2135 skybox = smgr->addSkyBoxSceneNode(
\r
2136 driver->getTexture(porting::getDataPath("skybox2.png").c_str()),
\r
2137 driver->getTexture(porting::getDataPath("skybox3.png").c_str()),
\r
2138 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2139 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2140 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2141 driver->getTexture(porting::getDataPath("skybox1.png").c_str()));*/
\r
2144 Create the camera node
\r
2147 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
2148 0, // Camera parent
\r
2149 v3f(BS*100, BS*2, BS*100), // Look from
\r
2150 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
2154 if(camera == NULL)
\r
2157 //video::SColor skycolor = video::SColor(255,90,140,200);
\r
2158 //video::SColor skycolor = video::SColor(255,166,202,244);
\r
2159 video::SColor skycolor = video::SColor(255,120,185,244);
\r
2161 camera->setFOV(FOV_ANGLE);
\r
2163 // Just so big a value that everything rendered is visible
\r
2164 camera->setFarValue(100000*BS);
\r
2167 Lighting test code. Doesn't quite work this way.
\r
2168 The CPU-computed lighting is good.
\r
2172 smgr->addLightSceneNode(NULL,
\r
2173 v3f(0, BS*1000000, 0),
\r
2174 video::SColorf(0.3,0.3,0.3),
\r
2177 smgr->setAmbientLight(video::SColorf(0.0, 0.0, 0.0));
\r
2179 scene::ILightSceneNode *light = smgr->addLightSceneNode(camera,
\r
2180 v3f(0, 0, 0), video::SColorf(0.5,0.5,0.5), BS*4);
\r
2183 f32 camera_yaw = 0; // "right/left"
\r
2184 f32 camera_pitch = 0; // "up/down"
\r
2190 gui_loadingtext->remove();
\r
2193 Add some gui stuff
\r
2196 /*GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
2197 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);*/
\r
2198 /*GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
2199 (guienv, NULL, v2s32(0, 0), quickinv_itemcount, &local_inventory);*/
\r
2201 // Test the text input system
\r
2202 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_menumgr,
\r
2204 /*GUIMessageMenu *menu =
\r
2205 new GUIMessageMenu(guienv, guiroot, -1,
\r
2210 // Launch pause menu
\r
2211 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
2212 &g_menumgr))->drop();
\r
2215 guitext2->setVisible(true);
\r
2216 guitext_info->setVisible(true);
\r
2217 guitext_chat->setVisible(true);
\r
2219 //s32 guitext_chat_pad_bottom = 70;
\r
2221 v2u32 screensize(0,0);
\r
2222 v2u32 last_screensize(0,0);
\r
2225 Some statistics are collected in these
\r
2228 u32 beginscenetime = 0;
\r
2229 u32 scenetime = 0;
\r
2230 u32 endscenetime = 0;
\r
2233 //throw con::PeerNotFoundException("lol");
\r
2235 core::list<float> frametime_log;
\r
2241 bool first_loop_after_window_activation = true;
\r
2243 // Time is in milliseconds
\r
2244 // NOTE: getRealTime() causes strange problems in wine (imprecision?)
\r
2245 // NOTE: So we have to use getTime() and call run()s between them
\r
2246 u32 lasttime = device->getTimer()->getTime();
\r
2248 while(device->run() && kill == false)
\r
2250 if(g_disconnect_requested)
\r
2252 g_disconnect_requested = false;
\r
2257 Run global IrrlichtWrapper's main thread processing stuff
\r
2259 g_irrlicht->Run();
\r
2262 Process TextureSource's queue
\r
2264 texturesource->processQueue();
\r
2267 Random calculations
\r
2269 last_screensize = screensize;
\r
2270 screensize = driver->getScreenSize();
\r
2271 v2s32 displaycenter(screensize.X/2,screensize.Y/2);
\r
2272 //bool screensize_changed = screensize != last_screensize;
\r
2274 // Hilight boxes collected during the loop and displayed
\r
2275 core::list< core::aabbox3d<f32> > hilightboxes;
\r
2278 std::wstring infotext;
\r
2280 // When screen size changes, update positions and sizes of stuff
\r
2281 /*if(screensize_changed)
\r
2283 v2s32 pos(displaycenter.X-((quickinv_itemcount-1)*quickinv_spacing+quickinv_size)/2, screensize.Y-quickinv_spacing);
\r
2284 quick_inventory->updatePosition(pos);
\r
2287 //TimeTaker //timer1("//timer1");
\r
2289 // Time of frame without fps limit
\r
2293 // not using getRealTime is necessary for wine
\r
2294 u32 time = device->getTimer()->getTime();
\r
2295 if(time > lasttime)
\r
2296 busytime_u32 = time - lasttime;
\r
2299 busytime = busytime_u32 / 1000.0;
\r
2302 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
2304 // Necessary for device->getTimer()->getTime()
\r
2311 updateViewingRange(busytime, &client);
\r
2318 float fps_max = g_settings.getFloat("fps_max");
\r
2319 u32 frametime_min = 1000./fps_max;
\r
2321 if(busytime_u32 < frametime_min)
\r
2323 u32 sleeptime = frametime_min - busytime_u32;
\r
2324 device->sleep(sleeptime);
\r
2328 // Necessary for device->getTimer()->getTime()
\r
2332 Time difference calculation
\r
2334 f32 dtime; // in seconds
\r
2336 u32 time = device->getTimer()->getTime();
\r
2337 if(time > lasttime)
\r
2338 dtime = (time - lasttime) / 1000.0;
\r
2344 Log frametime for visualization
\r
2346 frametime_log.push_back(dtime);
\r
2347 if(frametime_log.size() > 100)
\r
2349 core::list<float>::Iterator i = frametime_log.begin();
\r
2350 frametime_log.erase(i);
\r
2354 Visualize frametime in terminal
\r
2356 /*for(u32 i=0; i<dtime*400; i++)
\r
2358 std::cout<<std::endl;*/
\r
2361 Time average and jitter calculation
\r
2364 static f32 dtime_avg1 = 0.0;
\r
2365 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
2366 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
2368 static f32 dtime_jitter1_max_sample = 0.0;
\r
2369 static f32 dtime_jitter1_max_fraction = 0.0;
\r
2371 static f32 jitter1_max = 0.0;
\r
2372 static f32 counter = 0.0;
\r
2373 if(dtime_jitter1 > jitter1_max)
\r
2374 jitter1_max = dtime_jitter1;
\r
2379 dtime_jitter1_max_sample = jitter1_max;
\r
2380 dtime_jitter1_max_fraction
\r
2381 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
2382 jitter1_max = 0.0;
\r
2387 Busytime average and jitter calculation
\r
2390 static f32 busytime_avg1 = 0.0;
\r
2391 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
2392 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
2394 static f32 busytime_jitter1_max_sample = 0.0;
\r
2395 static f32 busytime_jitter1_min_sample = 0.0;
\r
2397 static f32 jitter1_max = 0.0;
\r
2398 static f32 jitter1_min = 0.0;
\r
2399 static f32 counter = 0.0;
\r
2400 if(busytime_jitter1 > jitter1_max)
\r
2401 jitter1_max = busytime_jitter1;
\r
2402 if(busytime_jitter1 < jitter1_min)
\r
2403 jitter1_min = busytime_jitter1;
\r
2405 if(counter > 0.0){
\r
2407 busytime_jitter1_max_sample = jitter1_max;
\r
2408 busytime_jitter1_min_sample = jitter1_min;
\r
2409 jitter1_max = 0.0;
\r
2410 jitter1_min = 0.0;
\r
2415 Debug info for client
\r
2418 static float counter = 0.0;
\r
2423 client.printDebugInfo(std::cout);
\r
2428 Input handler step()
\r
2430 g_input->step(dtime);
\r
2437 Player speed control
\r
2446 bool a_superspeed,
\r
2450 PlayerControl control(
\r
2451 g_input->isKeyDown(irr::KEY_KEY_W),
\r
2452 g_input->isKeyDown(irr::KEY_KEY_S),
\r
2453 g_input->isKeyDown(irr::KEY_KEY_A),
\r
2454 g_input->isKeyDown(irr::KEY_KEY_D),
\r
2455 g_input->isKeyDown(irr::KEY_SPACE),
\r
2456 g_input->isKeyDown(irr::KEY_KEY_E),
\r
2457 g_input->isKeyDown(irr::KEY_LSHIFT)
\r
2458 || g_input->isKeyDown(irr::KEY_RSHIFT),
\r
2462 client.setPlayerControl(control);
\r
2466 Process environment
\r
2470 //TimeTaker timer("client.step(dtime)");
\r
2471 client.step(dtime);
\r
2472 //client.step(dtime_avg1);
\r
2475 if(server != NULL)
\r
2477 //TimeTaker timer("server->step(dtime)");
\r
2478 server->step(dtime);
\r
2481 v3f player_position = client.getPlayerPosition();
\r
2483 //TimeTaker //timer2("//timer2");
\r
2486 Mouse and camera control
\r
2489 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
2492 device->getCursorControl()->setVisible(false);
\r
2494 if(first_loop_after_window_activation){
\r
2495 //std::cout<<"window active, first loop"<<std::endl;
\r
2496 first_loop_after_window_activation = false;
\r
2499 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
2500 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
2501 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
2502 camera_yaw -= dx*0.2;
\r
2503 camera_pitch += dy*0.2;
\r
2504 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
2505 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
2507 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
2510 device->getCursorControl()->setVisible(true);
\r
2512 //std::cout<<"window inactive"<<std::endl;
\r
2513 first_loop_after_window_activation = true;
\r
2516 camera_yaw = wrapDegrees(camera_yaw);
\r
2517 camera_pitch = wrapDegrees(camera_pitch);
\r
2519 v3f camera_direction = v3f(0,0,1);
\r
2520 camera_direction.rotateYZBy(camera_pitch);
\r
2521 camera_direction.rotateXZBy(camera_yaw);
\r
2523 // This is at the height of the eyes of the current figure
\r
2524 //v3f camera_position = player_position + v3f(0, BS+BS/2, 0);
\r
2525 // This is more like in minecraft
\r
2526 v3f camera_position = player_position + v3f(0, BS+BS*0.625, 0);
\r
2528 camera->setPosition(camera_position);
\r
2529 // *100.0 helps in large map coordinates
\r
2530 camera->setTarget(camera_position + camera_direction * 100.0);
\r
2532 if(FIELD_OF_VIEW_TEST){
\r
2533 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
2536 //TimeTaker timer("client.updateCamera");
\r
2537 client.updateCamera(camera_position, camera_direction);
\r
2541 //TimeTaker //timer3("//timer3");
\r
2544 Calculate what block is the crosshair pointing to
\r
2547 //u32 t1 = device->getTimer()->getRealTime();
\r
2549 //f32 d = 4; // max. distance
\r
2550 f32 d = 4; // max. distance
\r
2551 core::line3d<f32> shootline(camera_position,
\r
2552 camera_position + camera_direction * BS * (d+1));
\r
2554 MapBlockObject *selected_object = client.getSelectedObject
\r
2555 (d*BS, camera_position, shootline);
\r
2558 If it's pointing to a MapBlockObject
\r
2561 if(selected_object != NULL)
\r
2563 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
2565 core::aabbox3d<f32> box_on_map
\r
2566 = selected_object->getSelectionBoxOnMap();
\r
2568 hilightboxes.push_back(box_on_map);
\r
2570 infotext = narrow_to_wide(selected_object->infoText());
\r
2572 if(g_input->getLeftClicked())
\r
2574 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
2575 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
2576 selected_object->getId(), g_selected_item);
\r
2578 else if(g_input->getRightClicked())
\r
2580 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
2582 Check if we want to modify the object ourselves
\r
2584 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
2586 dstream<<"Sign object right-clicked"<<std::endl;
\r
2588 if(random_input == false)
\r
2590 // Get a new text for it
\r
2592 TextDest *dest = new TextDestSign(
\r
2593 selected_object->getBlock()->getPos(),
\r
2594 selected_object->getId(),
\r
2597 SignObject *sign_object = (SignObject*)selected_object;
\r
2599 std::wstring wtext =
\r
2600 narrow_to_wide(sign_object->getText());
\r
2602 (new GUITextInputMenu(guienv, guiroot, -1,
\r
2608 Otherwise pass the event to the server as-is
\r
2612 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
2613 selected_object->getId(), g_selected_item);
\r
2617 else // selected_object == NULL
\r
2621 Find out which node we are pointing at
\r
2624 bool nodefound = false;
\r
2626 v3s16 neighbourpos;
\r
2627 core::aabbox3d<f32> nodehilightbox;
\r
2628 f32 mindistance = BS * 1001;
\r
2630 v3s16 pos_i = floatToInt(player_position, BS);
\r
2632 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2636 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2637 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2638 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2639 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2640 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2641 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2643 for(s16 y = ystart; y <= yend; y++)
\r
2644 for(s16 z = zstart; z <= zend; z++)
\r
2645 for(s16 x = xstart; x <= xend; x++)
\r
2650 n = client.getNode(v3s16(x,y,z));
\r
2651 if(content_pointable(n.d) == false)
\r
2654 catch(InvalidPositionException &e)
\r
2660 v3f npf = intToFloat(np, BS);
\r
2665 v3s16(0,0,1), // back
\r
2666 v3s16(0,1,0), // top
\r
2667 v3s16(1,0,0), // right
\r
2668 v3s16(0,0,-1), // front
\r
2669 v3s16(0,-1,0), // bottom
\r
2670 v3s16(-1,0,0), // left
\r
2676 if(n.d == CONTENT_TORCH)
\r
2678 v3s16 dir = unpackDir(n.dir);
\r
2679 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2680 dir_f *= BS/2 - BS/6 - BS/20;
\r
2681 v3f cpf = npf + dir_f;
\r
2682 f32 distance = (cpf - camera_position).getLength();
\r
2684 core::aabbox3d<f32> box;
\r
2687 if(dir == v3s16(0,-1,0))
\r
2689 box = core::aabbox3d<f32>(
\r
2690 npf - v3f(BS/6, BS/2, BS/6),
\r
2691 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2695 else if(dir == v3s16(0,1,0))
\r
2697 box = core::aabbox3d<f32>(
\r
2698 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2699 npf + v3f(BS/6, BS/2, BS/6)
\r
2705 box = core::aabbox3d<f32>(
\r
2706 cpf - v3f(BS/6, BS/3, BS/6),
\r
2707 cpf + v3f(BS/6, BS/3, BS/6)
\r
2711 if(distance < mindistance)
\r
2713 if(box.intersectsWithLine(shootline))
\r
2717 neighbourpos = np;
\r
2718 mindistance = distance;
\r
2719 nodehilightbox = box;
\r
2723 else if(n.d == CONTENT_SIGN_WALL)
\r
2725 v3s16 dir = unpackDir(n.dir);
\r
2726 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2727 dir_f *= BS/2 - BS/6 - BS/20;
\r
2728 v3f cpf = npf + dir_f;
\r
2729 f32 distance = (cpf - camera_position).getLength();
\r
2733 v3f(BS*0.42,-BS*0.35,-BS*0.4),
\r
2734 v3f(BS*0.49, BS*0.35, BS*0.4),
\r
2737 for(s32 i=0; i<2; i++)
\r
2739 if(dir == v3s16(1,0,0))
\r
2740 vertices[i].rotateXZBy(0);
\r
2741 if(dir == v3s16(-1,0,0))
\r
2742 vertices[i].rotateXZBy(180);
\r
2743 if(dir == v3s16(0,0,1))
\r
2744 vertices[i].rotateXZBy(90);
\r
2745 if(dir == v3s16(0,0,-1))
\r
2746 vertices[i].rotateXZBy(-90);
\r
2747 if(dir == v3s16(0,-1,0))
\r
2748 vertices[i].rotateXYBy(-90);
\r
2749 if(dir == v3s16(0,1,0))
\r
2750 vertices[i].rotateXYBy(90);
\r
2752 vertices[i] += npf;
\r
2755 core::aabbox3d<f32> box;
\r
2757 box = core::aabbox3d<f32>(vertices[0]);
\r
2758 box.addInternalPoint(vertices[1]);
\r
2760 if(distance < mindistance)
\r
2762 if(box.intersectsWithLine(shootline))
\r
2766 neighbourpos = np;
\r
2767 mindistance = distance;
\r
2768 nodehilightbox = box;
\r
2777 for(u16 i=0; i<6; i++)
\r
2779 v3f dir_f = v3f(dirs[i].X,
\r
2780 dirs[i].Y, dirs[i].Z);
\r
2781 v3f centerpoint = npf + dir_f * BS/2;
\r
2783 (centerpoint - camera_position).getLength();
\r
2785 if(distance < mindistance)
\r
2787 core::CMatrix4<f32> m;
\r
2788 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2790 // This is the back face
\r
2791 v3f corners[2] = {
\r
2792 v3f(BS/2, BS/2, BS/2),
\r
2793 v3f(-BS/2, -BS/2, BS/2+d)
\r
2796 for(u16 j=0; j<2; j++)
\r
2798 m.rotateVect(corners[j]);
\r
2799 corners[j] += npf;
\r
2802 core::aabbox3d<f32> facebox(corners[0]);
\r
2803 facebox.addInternalPoint(corners[1]);
\r
2805 if(facebox.intersectsWithLine(shootline))
\r
2809 neighbourpos = np + dirs[i];
\r
2810 mindistance = distance;
\r
2812 //nodehilightbox = facebox;
\r
2814 const float d = 0.502;
\r
2815 core::aabbox3d<f32> nodebox
\r
2816 (-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
\r
2817 v3f nodepos_f = intToFloat(nodepos, BS);
\r
2818 nodebox.MinEdge += nodepos_f;
\r
2819 nodebox.MaxEdge += nodepos_f;
\r
2820 nodehilightbox = nodebox;
\r
2822 } // if distance < mindistance
\r
2824 } // regular block
\r
2827 static float nodig_delay_counter = 0.0;
\r
2831 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2833 static float dig_time = 0.0;
\r
2834 static u16 dig_index = 0;
\r
2837 Visualize selection
\r
2840 hilightboxes.push_back(nodehilightbox);
\r
2843 Check information text of node
\r
2846 NodeMetadata *meta = client.getNodeMetadata(nodepos);
\r
2849 infotext = narrow_to_wide(meta->infoText());
\r
2852 //MapNode node = client.getNode(nodepos);
\r
2858 if(g_input->getLeftReleased())
\r
2860 client.clearTempMod(nodepos);
\r
2864 if(nodig_delay_counter > 0.0)
\r
2866 nodig_delay_counter -= dtime;
\r
2870 if(nodepos != nodepos_old)
\r
2872 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2873 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2875 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2877 client.clearTempMod(nodepos_old);
\r
2882 if(g_input->getLeftClicked() ||
\r
2883 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2885 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2886 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2888 if(g_input->getLeftClicked())
\r
2890 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2892 if(g_input->getLeftState())
\r
2894 MapNode n = client.getNode(nodepos);
\r
2896 // Get tool name. Default is "" = bare hands
\r
2897 std::string toolname = "";
\r
2898 InventoryList *mlist = local_inventory.getList("main");
\r
2901 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2902 if(item && (std::string)item->getName() == "ToolItem")
\r
2904 ToolItem *titem = (ToolItem*)item;
\r
2905 toolname = titem->getToolName();
\r
2909 // Get digging properties for material and tool
\r
2910 u8 material = n.d;
\r
2911 DiggingProperties prop =
\r
2912 getDiggingProperties(material, toolname);
\r
2914 float dig_time_complete = 0.0;
\r
2916 if(prop.diggable == false)
\r
2918 /*dstream<<"Material "<<(int)material
\r
2919 <<" not diggable with \""
\r
2920 <<toolname<<"\""<<std::endl;*/
\r
2921 // I guess nobody will wait for this long
\r
2922 dig_time_complete = 10000000.0;
\r
2926 dig_time_complete = prop.time;
\r
2929 if(dig_time_complete >= 0.001)
\r
2931 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2932 * dig_time/dig_time_complete);
\r
2934 // This is for torches
\r
2937 dig_index = CRACK_ANIMATION_LENGTH;
\r
2940 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2942 //TimeTaker timer("client.setTempMod");
\r
2943 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2944 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2948 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2949 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2950 client.clearTempMod(nodepos);
\r
2951 client.removeNode(nodepos);
\r
2955 nodig_delay_counter = dig_time_complete
\r
2956 / (float)CRACK_ANIMATION_LENGTH;
\r
2958 // We don't want a corresponding delay to
\r
2959 // very time consuming nodes
\r
2960 if(nodig_delay_counter > 0.5)
\r
2962 nodig_delay_counter = 0.5;
\r
2964 // We want a slight delay to very little
\r
2965 // time consuming nodes
\r
2966 float mindelay = 0.15;
\r
2967 if(nodig_delay_counter < mindelay)
\r
2969 nodig_delay_counter = mindelay;
\r
2973 dig_time += dtime;
\r
2977 if(g_input->getRightClicked())
\r
2979 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2981 if(meta && meta->typeId() == CONTENT_SIGN_WALL)
\r
2983 dstream<<"Sign node right-clicked"<<std::endl;
\r
2985 if(random_input == false)
\r
2987 // Get a new text for it
\r
2989 TextDest *dest = new TextDestSignNode(nodepos, &client);
\r
2991 SignNodeMetadata *signmeta = (SignNodeMetadata*)meta;
\r
2993 std::wstring wtext =
\r
2994 narrow_to_wide(signmeta->getText());
\r
2996 (new GUITextInputMenu(guienv, guiroot, -1,
\r
3003 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
3007 nodepos_old = nodepos;
\r
3012 } // selected_object == NULL
\r
3014 g_input->resetLeftClicked();
\r
3015 g_input->resetRightClicked();
\r
3017 if(g_input->getLeftReleased())
\r
3019 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
3021 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
3023 if(g_input->getRightReleased())
\r
3025 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
3029 g_input->resetLeftReleased();
\r
3030 g_input->resetRightReleased();
\r
3033 Calculate stuff for drawing
\r
3036 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
3038 u32 daynight_ratio = client.getDayNightRatio();
\r
3039 u8 l = decode_light((daynight_ratio * LIGHT_SUN) / 1000);
\r
3040 video::SColor bgcolor = video::SColor(
\r
3042 skycolor.getRed() * l / 255,
\r
3043 skycolor.getGreen() * l / 255,
\r
3044 skycolor.getBlue() * l / 255);
\r
3050 if(g_settings.getBool("enable_fog") == true)
\r
3052 //f32 range = draw_control.wanted_range * BS + MAP_BLOCKSIZE/2*BS;
\r
3053 f32 range = draw_control.wanted_range * BS + 0.8*MAP_BLOCKSIZE*BS;
\r
3054 //f32 range = draw_control.wanted_range * BS + 0.0*MAP_BLOCKSIZE*BS;
\r
3055 if(draw_control.range_all)
\r
3056 range = 100000*BS;
\r
3060 video::EFT_FOG_LINEAR,
\r
3064 false, // pixel fog
\r
3065 false // range fog
\r
3072 video::EFT_FOG_LINEAR,
\r
3076 false, // pixel fog
\r
3077 false // range fog
\r
3083 Update gui stuff (0ms)
\r
3086 //TimeTaker guiupdatetimer("Gui updating");
\r
3089 static float drawtime_avg = 0;
\r
3090 drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
\r
3091 static float beginscenetime_avg = 0;
\r
3092 beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
\r
3093 static float scenetime_avg = 0;
\r
3094 scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
\r
3095 static float endscenetime_avg = 0;
\r
3096 endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;
\r
3098 char temptext[300];
\r
3099 snprintf(temptext, 300, "Minetest-c55 ("
\r
3101 ", R: range_all=%i"
\r
3103 " drawtime=%.0f, beginscenetime=%.0f"
\r
3104 ", scenetime=%.0f, endscenetime=%.0f",
\r
3106 draw_control.range_all,
\r
3108 beginscenetime_avg,
\r
3113 guitext->setText(narrow_to_wide(temptext).c_str());
\r
3117 char temptext[300];
\r
3118 snprintf(temptext, 300,
\r
3119 "(% .1f, % .1f, % .1f)"
\r
3120 " (% .3f < btime_jitter < % .3f"
\r
3121 ", dtime_jitter = % .1f %%"
\r
3122 ", v_range = %.1f)",
\r
3123 player_position.X/BS,
\r
3124 player_position.Y/BS,
\r
3125 player_position.Z/BS,
\r
3126 busytime_jitter1_min_sample,
\r
3127 busytime_jitter1_max_sample,
\r
3128 dtime_jitter1_max_fraction * 100.0,
\r
3129 draw_control.wanted_range
\r
3132 guitext2->setText(narrow_to_wide(temptext).c_str());
\r
3136 guitext_info->setText(infotext.c_str());
\r
3140 Get chat messages from client
\r
3143 // Get new messages
\r
3144 std::wstring message;
\r
3145 while(client.getChatMessage(message))
\r
3147 chat_lines.push_back(ChatLine(message));
\r
3148 /*if(chat_lines.size() > 6)
\r
3150 core::list<ChatLine>::Iterator
\r
3151 i = chat_lines.begin();
\r
3152 chat_lines.erase(i);
\r
3155 // Append them to form the whole static text and throw
\r
3156 // it to the gui element
\r
3157 std::wstring whole;
\r
3158 // This will correspond to the line number counted from
\r
3159 // top to bottom, from size-1 to 0
\r
3160 s16 line_number = chat_lines.size();
\r
3161 // Count of messages to be removed from the top
\r
3162 u16 to_be_removed_count = 0;
\r
3163 for(core::list<ChatLine>::Iterator
\r
3164 i = chat_lines.begin();
\r
3165 i != chat_lines.end(); i++)
\r
3167 // After this, line number is valid for this loop
\r
3170 (*i).age += dtime;
\r
3172 This results in a maximum age of 60*6 to the
\r
3173 lowermost line and a maximum of 6 lines
\r
3175 float allowed_age = (6-line_number) * 60.0;
\r
3177 if((*i).age > allowed_age)
\r
3179 to_be_removed_count++;
\r
3182 whole += (*i).text + L'\n';
\r
3184 for(u16 i=0; i<to_be_removed_count; i++)
\r
3186 core::list<ChatLine>::Iterator
\r
3187 it = chat_lines.begin();
\r
3188 chat_lines.erase(it);
\r
3190 guitext_chat->setText(whole.c_str());
\r
3192 // Update gui element size and position
\r
3194 /*core::rect<s32> rect(
\r
3196 screensize.Y - guitext_chat_pad_bottom
\r
3197 - text_height*chat_lines.size(),
\r
3198 screensize.X - 10,
\r
3199 screensize.Y - guitext_chat_pad_bottom
\r
3201 core::rect<s32> rect(
\r
3204 screensize.X - 10,
\r
3205 50 + text_height*chat_lines.size()
\r
3208 guitext_chat->setRelativePosition(rect);
\r
3210 if(chat_lines.size() == 0)
\r
3211 guitext_chat->setVisible(false);
\r
3213 guitext_chat->setVisible(true);
\r
3220 static u16 old_selected_item = 65535;
\r
3221 if(client.getLocalInventoryUpdated()
\r
3222 || g_selected_item != old_selected_item)
\r
3224 old_selected_item = g_selected_item;
\r
3225 //std::cout<<"Updating local inventory"<<std::endl;
\r
3226 client.getLocalInventory(local_inventory);
\r
3227 /*quick_inventory->setSelection(g_selected_item);
\r
3228 quick_inventory->update();*/
\r
3232 Send actions returned by the inventory menu
\r
3234 while(inventory_action_queue.size() != 0)
\r
3236 InventoryAction *a = inventory_action_queue.pop_front();
\r
3238 client.sendInventoryAction(a);
\r
3247 TimeTaker drawtimer("Drawing");
\r
3251 TimeTaker timer("beginScene");
\r
3252 driver->beginScene(true, true, bgcolor);
\r
3253 //driver->beginScene(false, true, bgcolor);
\r
3254 beginscenetime = timer.stop(true);
\r
3259 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
3262 TimeTaker timer("smgr");
\r
3264 scenetime = timer.stop(true);
\r
3268 //TimeTaker timer9("auxiliary drawings");
\r
3272 //TimeTaker //timer10("//timer10");
\r
3274 video::SMaterial m;
\r
3275 //m.Thickness = 10;
\r
3277 m.Lighting = false;
\r
3278 driver->setMaterial(m);
\r
3280 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
3282 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
3283 i != hilightboxes.end(); i++)
\r
3285 /*std::cout<<"hilightbox min="
\r
3286 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
3288 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
3290 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
3296 if(g_settings.getBool("frametime_graph") == true)
\r
3299 for(core::list<float>::Iterator
\r
3300 i = frametime_log.begin();
\r
3301 i != frametime_log.end();
\r
3304 driver->draw2DLine(v2s32(x,50),
\r
3305 v2s32(x,50+(*i)*1000),
\r
3306 video::SColor(255,255,255,255));
\r
3314 if(g_show_map_plot && g_map_plot_texture)
\r
3316 core::dimension2d<u32> drawdim(640,480);
\r
3317 core::rect<s32> dest(v2s32(0,0), drawdim);
\r
3319 (screensize.X-drawdim.Width)/2,
\r
3320 (screensize.Y-drawdim.Height)/2
\r
3322 core::rect<s32> source(v2s32(0,0), g_map_plot_texture->getSize());
\r
3323 driver->draw2DImage(g_map_plot_texture, dest, source);
\r
3329 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
3330 displaycenter + core::vector2d<s32>(10,0),
\r
3331 video::SColor(255,255,255,255));
\r
3332 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
3333 displaycenter + core::vector2d<s32>(0,10),
\r
3334 video::SColor(255,255,255,255));
\r
3339 //TimeTaker //timer11("//timer11");
\r
3345 guienv->drawAll();
\r
3351 draw_hotbar(driver, font, v2s32(displaycenter.X, screensize.Y),
\r
3352 hotbar_imagesize, hotbar_itemcount, &local_inventory);
\r
3357 TimeTaker timer("endScene");
\r
3358 driver->endScene();
\r
3359 endscenetime = timer.stop(true);
\r
3362 drawtime = drawtimer.stop(true);
\r
3370 Refresh map plot if player has moved considerably
\r
3372 if(g_refresh_map_plot)
\r
3374 static v3f old_player_pos = v3f(1,1,1) * 10000000;
\r
3375 v3f p = client.getPlayerPosition() / BS;
\r
3376 if(old_player_pos.getDistanceFrom(p) > 4 * g_map_plot_texture_scale)
\r
3378 updateMapPlotTexture(v2f(p.X,p.Z), driver, &client);
\r
3379 old_player_pos = p;
\r
3381 g_refresh_map_plot = false;
\r
3385 static s16 lastFPS = 0;
\r
3386 //u16 fps = driver->getFPS();
\r
3387 u16 fps = (1.0/dtime_avg1);
\r
3389 if (lastFPS != fps)
\r
3391 core::stringw str = L"Minetest [";
\r
3392 str += driver->getName();
\r
3396 device->setWindowCaption(str.c_str());
\r
3402 device->yield();*/
\r
3405 //delete quick_inventory;
\r
3408 Disable texture fetches and other stuff that is queued
\r
3409 to be processed by the main loop.
\r
3411 This has to be done before client goes out of scope.
\r
3413 g_irrlicht->Shutdown(true);
\r
3415 } // client and server are deleted at this point
\r
3418 catch(con::PeerNotFoundException &e)
\r
3420 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
3421 error_message = L"Connection timed out.";
\r
3424 } // Menu-game loop
\r
3429 In the end, delete the Irrlicht device.
\r
3434 Update configuration file
\r
3436 /*if(configpath != "")
\r
3438 g_settings.updateConfigFile(configpath.c_str());
\r
3441 END_DEBUG_EXCEPTION_HANDLER
\r
3443 debugstreams_deinit();
\r