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: Signs could be done in the same way as torches. For this, blocks
\r
93 need an additional metadata field for the texts
\r
94 - This is also needed for item container chests
\r
96 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
98 SUGG: A version number to blocks, which increments when the block is
\r
99 modified (node add/remove, water update, lighting update)
\r
100 - This can then be used to make sure the most recent version of
\r
101 a block has been sent to client
\r
103 SUGG: Make the amount of blocks sending to client and the total
\r
104 amount of blocks dynamically limited. Transferring blocks is the
\r
105 main network eater of this system, so it is the one that has
\r
106 to be throttled so that RTTs stay low.
\r
108 SUGG: Meshes of blocks could be split into 6 meshes facing into
\r
109 different directions and then only those drawn that need to be
\r
110 - Also an 1-dimensional tile map would be nice probably
\r
112 TODO: Untie client network operations from framerate
\r
113 - Needs some input queues or something
\r
114 - Not really necessary?
\r
116 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
118 - That is >500 vertices
\r
120 TODO: Better dungeons
\r
123 TODO: Startup and configuration menu
\r
125 TODO: There are some lighting-related todos and fixmes in
\r
126 ServerMap::emergeBlock
\r
128 TODO: Proper handling of spawning place (try to find something that
\r
129 is not in the middle of an ocean (some land to stand on at
\r
130 least) and save it in map config.
\r
132 TODO: Players to only be hidden when the client quits.
\r
133 TODO: - Players to be saved on disk, with inventory
\r
134 TODO: Players to be saved as text in map/players/<name>
\r
135 TODO: Player inventory to be saved on disk
\r
137 TODO: Make fetching sector's blocks more efficient when rendering
\r
138 sectors that have very large amounts of blocks (on client)
\r
140 TODO: Make the video backend selectable
\r
142 Block object server side:
\r
143 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
144 - For all blocks in the buffer, objects are stepped(). This
\r
145 means they are active.
\r
146 - TODO: A global active buffer is needed for the server
\r
147 - TODO: A timestamp to blocks
\r
148 - TODO: All blocks going in and out of the buffer are recorded.
\r
149 - TODO: For outgoing blocks, timestamp is written.
\r
150 - TODO: For incoming blocks, time difference is calculated and
\r
151 objects are stepped according to it.
\r
153 TODO: Copy the text of the last picked sign to inventory in creative
\r
156 TODO: Get rid of GotSplitPacketException
\r
158 TODO: Check what goes wrong with caching map to disk (Kray)
\r
160 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
162 TODO: TOSERVER_LEAVE
\r
164 TODO: Better handling of objects and mobs
\r
166 - There has to be some way to do it with less spaghetti code
\r
167 - Make separate classes for client and server
\r
168 - Client should not discriminate between blocks, server should
\r
169 - Make other players utilize the same framework
\r
171 TODO: Draw big amounts of torches better (that is, throw them in the
\r
172 same meshbuffer (can the meshcollector class be used?))
\r
174 TODO: Check if the usage of Client::isFetchingBlocks() in
\r
175 updateViewingRange() actually does something
\r
178 ======================================================================
\r
182 - Actually, tool items should be just a little special MapBlockItems
\r
184 ======================================================================
\r
189 Setting this to 1 enables a special camera mode that forces
\r
190 the renderers to think that the camera statically points from
\r
191 the starting place to a static direction.
\r
193 This allows one to move around with the player and see what
\r
194 is actually drawn behind solid things and behind the player.
\r
196 #define FIELD_OF_VIEW_TEST 0
\r
198 #ifdef UNITTEST_DISABLE
\r
200 #pragma message ("Disabling unit tests")
\r
202 #warning "Disabling unit tests"
\r
204 // Disable unit tests
\r
205 #define ENABLE_TESTS 0
\r
207 // Enable unit tests
\r
208 #define ENABLE_TESTS 1
\r
212 #pragma comment(lib, "Irrlicht.lib")
\r
213 #pragma comment(lib, "jthread.lib")
\r
214 #pragma comment(lib, "zlibwapi.lib")
\r
215 // This would get rid of the console window
\r
216 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
219 #include <iostream>
\r
221 #include <jmutexautolock.h>
\r
222 #include <locale.h>
\r
223 #include "common_irrlicht.h"
\r
226 #include "player.h"
\r
229 #include "environment.h"
\r
230 #include "server.h"
\r
231 #include "client.h"
\r
232 #include "serialization.h"
\r
233 #include "constants.h"
\r
234 #include "strfnd.h"
\r
235 #include "porting.h"
\r
236 #include "irrlichtwrapper.h"
\r
237 #include "gettime.h"
\r
238 #include "porting.h"
\r
239 #include "guiPauseMenu.h"
\r
240 #include "guiInventoryMenu.h"
\r
241 #include "guiTextInputMenu.h"
\r
243 IrrlichtWrapper *g_irrlicht;
\r
245 // All range-related stuff below is locked behind this
\r
246 JMutex g_range_mutex;
\r
248 // Blocks are viewed in this range from the player
\r
249 s16 g_viewing_range_nodes = 60;
\r
250 //s16 g_viewing_range_nodes = 0;
\r
252 // This is updated by the client's fetchBlocks routine
\r
253 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
255 // If true, the preceding value has no meaning and all blocks
\r
256 // already existing in memory are drawn
\r
257 bool g_viewing_range_all = false;
\r
259 // This is the freetime ratio imposed by the dynamic viewing
\r
260 // range changing code.
\r
261 // It is controlled by the main loop to the smallest value that
\r
262 // inhibits glitches (dtime jitter) in the main loop.
\r
263 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
267 These are loaded from the config file.
\r
270 Settings g_settings;
\r
272 extern void set_default_settings();
\r
278 IrrlichtDevice *g_device = NULL;
\r
279 Client *g_client = NULL;
\r
284 gui::IGUIEnvironment* guienv = NULL;
\r
285 gui::IGUIStaticText *guiroot = NULL;
\r
286 int g_active_menu_count = 0;
\r
288 bool noMenuActive()
\r
290 return (g_active_menu_count == 0);
\r
293 // Inventory actions from the menu are buffered here before sending
\r
294 Queue<InventoryAction*> inventory_action_queue;
\r
295 // This is a copy of the inventory that the client's environment has
\r
296 Inventory local_inventory;
\r
298 u16 g_selected_item = 0;
\r
305 std::ostream *dout_con_ptr = &dummyout;
\r
306 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
307 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
308 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
309 //std::ostream *dout_con_ptr = &dstream;
\r
310 //std::ostream *derr_con_ptr = &dstream;
\r
313 std::ostream *dout_server_ptr = &dstream;
\r
314 std::ostream *derr_server_ptr = &dstream;
\r
317 std::ostream *dout_client_ptr = &dstream;
\r
318 std::ostream *derr_client_ptr = &dstream;
\r
321 gettime.h implementation
\r
327 Use irrlicht because it is more precise than porting.h's
\r
330 if(g_irrlicht == NULL)
\r
332 return g_irrlicht->getTime();
\r
339 struct TextDestSign : public TextDest
\r
341 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
343 m_blockpos = blockpos;
\r
347 void gotText(std::wstring text)
\r
349 std::string ntext = wide_to_narrow(text);
\r
350 dstream<<"Changing text of a sign object: "
\r
351 <<ntext<<std::endl;
\r
352 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
360 struct TextDestChat : public TextDest
\r
362 TextDestChat(Client *client)
\r
366 void gotText(std::wstring text)
\r
368 m_client->sendChatMessage(text);
\r
369 m_client->addChatMessage(text);
\r
375 class MyEventReceiver : public IEventReceiver
\r
378 // This is the one method that we have to implement
\r
379 virtual bool OnEvent(const SEvent& event)
\r
382 React to nothing here if a menu is active
\r
384 if(noMenuActive() == false)
\r
390 // Remember whether each key is down or up
\r
391 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
393 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
395 if(event.KeyInput.PressedDown)
\r
397 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
403 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
405 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
407 dstream<<DTIME<<"MyEventReceiver: "
\r
408 <<"Launching pause menu"<<std::endl;
\r
409 // It will delete itself by itself
\r
410 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
411 &g_active_menu_count))->drop();
\r
414 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
416 dstream<<DTIME<<"MyEventReceiver: "
\r
417 <<"Launching inventory"<<std::endl;
\r
418 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
419 &local_inventory, &inventory_action_queue,
\r
420 &g_active_menu_count))->drop();
\r
423 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
425 TextDest *dest = new TextDestChat(g_client);
\r
427 (new GUITextInputMenu(guienv, guiroot, -1,
\r
428 &g_active_menu_count, dest,
\r
433 // Material selection
\r
434 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
436 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
439 g_selected_item = 0;
\r
440 dstream<<DTIME<<"Selected item: "
\r
441 <<g_selected_item<<std::endl;
\r
444 // Viewing range selection
\r
445 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
447 JMutexAutoLock lock(g_range_mutex);
\r
448 if(g_viewing_range_all)
\r
450 g_viewing_range_all = false;
\r
451 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
455 g_viewing_range_all = true;
\r
456 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
460 // Print debug stacks
\r
461 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
463 dstream<<"-----------------------------------------"
\r
465 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
466 dstream<<"-----------------------------------------"
\r
468 debug_stacks_print();
\r
473 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
475 if(noMenuActive() == false)
\r
477 left_active = false;
\r
478 middle_active = false;
\r
479 right_active = false;
\r
483 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
484 left_active = event.MouseInput.isLeftPressed();
\r
485 middle_active = event.MouseInput.isMiddlePressed();
\r
486 right_active = event.MouseInput.isRightPressed();
\r
488 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
490 leftclicked = true;
\r
492 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
494 rightclicked = true;
\r
496 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
498 leftreleased = true;
\r
500 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
502 rightreleased = true;
\r
504 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
506 /*dstream<<"event.MouseInput.Wheel="
\r
507 <<event.MouseInput.Wheel<<std::endl;*/
\r
508 if(event.MouseInput.Wheel < 0)
\r
510 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
513 g_selected_item = 0;
\r
515 else if(event.MouseInput.Wheel > 0)
\r
517 if(g_selected_item > 0)
\r
520 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
529 // This is used to check whether a key is being held down
\r
530 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
532 return keyIsDown[keyCode];
\r
537 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
538 keyIsDown[i] = false;
\r
540 leftclicked = false;
\r
541 rightclicked = false;
\r
542 leftreleased = false;
\r
543 rightreleased = false;
\r
545 left_active = false;
\r
546 middle_active = false;
\r
547 right_active = false;
\r
558 bool rightreleased;
\r
561 bool middle_active;
\r
565 // We use this array to store the current state of each key
\r
566 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
569 IrrlichtDevice *m_device;
\r
578 virtual ~InputHandler()
\r
582 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
584 virtual v2s32 getMousePos() = 0;
\r
585 virtual void setMousePos(s32 x, s32 y) = 0;
\r
587 virtual bool getLeftState() = 0;
\r
588 virtual bool getRightState() = 0;
\r
590 virtual bool getLeftClicked() = 0;
\r
591 virtual bool getRightClicked() = 0;
\r
592 virtual void resetLeftClicked() = 0;
\r
593 virtual void resetRightClicked() = 0;
\r
595 virtual bool getLeftReleased() = 0;
\r
596 virtual bool getRightReleased() = 0;
\r
597 virtual void resetLeftReleased() = 0;
\r
598 virtual void resetRightReleased() = 0;
\r
600 virtual void step(float dtime) {};
\r
602 virtual void clear() {};
\r
605 InputHandler *g_input = NULL;
\r
607 class RealInputHandler : public InputHandler
\r
610 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
612 m_receiver(receiver)
\r
615 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
617 return m_receiver->IsKeyDown(keyCode);
\r
619 virtual v2s32 getMousePos()
\r
621 return m_device->getCursorControl()->getPosition();
\r
623 virtual void setMousePos(s32 x, s32 y)
\r
625 m_device->getCursorControl()->setPosition(x, y);
\r
628 virtual bool getLeftState()
\r
630 return m_receiver->left_active;
\r
632 virtual bool getRightState()
\r
634 return m_receiver->right_active;
\r
637 virtual bool getLeftClicked()
\r
639 return m_receiver->leftclicked;
\r
641 virtual bool getRightClicked()
\r
643 return m_receiver->rightclicked;
\r
645 virtual void resetLeftClicked()
\r
647 m_receiver->leftclicked = false;
\r
649 virtual void resetRightClicked()
\r
651 m_receiver->rightclicked = false;
\r
654 virtual bool getLeftReleased()
\r
656 return m_receiver->leftreleased;
\r
658 virtual bool getRightReleased()
\r
660 return m_receiver->rightreleased;
\r
662 virtual void resetLeftReleased()
\r
664 m_receiver->leftreleased = false;
\r
666 virtual void resetRightReleased()
\r
668 m_receiver->rightreleased = false;
\r
673 resetRightClicked();
\r
674 resetLeftClicked();
\r
677 IrrlichtDevice *m_device;
\r
678 MyEventReceiver *m_receiver;
\r
681 class RandomInputHandler : public InputHandler
\r
684 RandomInputHandler()
\r
686 leftclicked = false;
\r
687 rightclicked = false;
\r
688 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
689 keydown[i] = false;
\r
691 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
693 return keydown[keyCode];
\r
695 virtual v2s32 getMousePos()
\r
699 virtual void setMousePos(s32 x, s32 y)
\r
701 mousepos = v2s32(x,y);
\r
704 virtual bool getLeftState()
\r
708 virtual bool getRightState()
\r
713 virtual bool getLeftClicked()
\r
715 return leftclicked;
\r
717 virtual bool getRightClicked()
\r
719 return rightclicked;
\r
721 virtual void resetLeftClicked()
\r
723 leftclicked = false;
\r
725 virtual void resetRightClicked()
\r
727 rightclicked = false;
\r
730 virtual bool getLeftReleased()
\r
734 virtual bool getRightReleased()
\r
738 virtual void resetLeftReleased()
\r
741 virtual void resetRightReleased()
\r
745 virtual void step(float dtime)
\r
748 static float counter1 = 0;
\r
752 counter1 = 0.1*Rand(1,10);
\r
753 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
754 g_selected_material++;
\r
756 g_selected_material = 0;*/
\r
757 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
760 g_selected_item = 0;
\r
764 static float counter1 = 0;
\r
768 counter1 = 0.1*Rand(1, 40);
\r
769 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
773 static float counter1 = 0;
\r
777 counter1 = 0.1*Rand(1, 40);
\r
778 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
782 static float counter1 = 0;
\r
786 counter1 = 0.1*Rand(1, 40);
\r
787 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
791 static float counter1 = 0;
\r
795 counter1 = 0.1*Rand(1, 40);
\r
796 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
800 static float counter1 = 0;
\r
804 counter1 = 0.1*Rand(1, 20);
\r
805 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
809 static float counter1 = 0;
\r
813 counter1 = 0.1*Rand(1, 30);
\r
814 leftclicked = true;
\r
818 static float counter1 = 0;
\r
822 counter1 = 0.1*Rand(1, 20);
\r
823 rightclicked = true;
\r
826 mousepos += mousespeed;
\r
829 s32 Rand(s32 min, s32 max)
\r
831 return (rand()%(max-min+1))+min;
\r
834 bool keydown[KEY_KEY_CODES_COUNT];
\r
841 void updateViewingRange(f32 frametime, Client *client)
\r
843 // Range_all messes up frametime_avg
\r
844 if(g_viewing_range_all == true)
\r
847 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
849 // Initialize to the target value
\r
850 static float frametime_avg = 1.0/wanted_fps;
\r
851 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
852 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
854 static f32 counter = 0;
\r
856 counter -= frametime;
\r
859 //counter = 1.0; //seconds
\r
860 counter = 0.5; //seconds
\r
862 //float freetime_ratio = 0.2;
\r
863 //float freetime_ratio = 0.4;
\r
864 float freetime_ratio = FREETIME_RATIO;
\r
866 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
868 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
870 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
871 + frametime_avg / frametime_wanted / 2.0;*/
\r
873 //float fraction = frametime_avg / frametime_wanted;
\r
875 static bool fraction_is_good = false;
\r
877 //float fraction_good_threshold = 0.1;
\r
878 //float fraction_bad_threshold = 0.25;
\r
879 float fraction_good_threshold = 0.075;
\r
880 float fraction_bad_threshold = 0.125;
\r
881 float fraction_limit;
\r
882 // Use high limit if fraction is good AND the fraction would
\r
883 // lower the range. We want to keep the range fairly high.
\r
884 if(fraction_is_good && fraction > 1.0)
\r
885 fraction_limit = fraction_bad_threshold;
\r
887 fraction_limit = fraction_good_threshold;
\r
889 if(fabs(fraction - 1.0) < fraction_limit)
\r
891 fraction_is_good = true;
\r
896 fraction_is_good = false;
\r
899 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
900 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
901 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
902 <<" faction = "<<fraction<<std::endl;*/
\r
904 JMutexAutoLock lock(g_range_mutex);
\r
906 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
907 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
909 s16 n = (float)g_viewing_range_nodes / fraction;
\r
910 if(n < viewing_range_nodes_min)
\r
911 n = viewing_range_nodes_min;
\r
912 if(n > viewing_range_nodes_max)
\r
913 n = viewing_range_nodes_max;
\r
915 bool can_change = true;
\r
917 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
918 can_change = false;
\r
921 g_viewing_range_nodes = n;
\r
923 /*dstream<<"g_viewing_range_nodes = "
\r
924 <<g_viewing_range_nodes<<std::endl;*/
\r
927 class GUIQuickInventory : public IEventReceiver
\r
931 gui::IGUIEnvironment* env,
\r
932 gui::IGUIElement* parent,
\r
935 Inventory *inventory):
\r
936 m_itemcount(itemcount),
\r
937 m_inventory(inventory)
\r
939 core::rect<s32> imgsize(0,0,48,48);
\r
940 core::rect<s32> textsize(0,0,48,16);
\r
941 v2s32 spacing(0, 64);
\r
942 for(s32 i=0; i<m_itemcount; i++)
\r
944 m_images.push_back(env->addImage(
\r
945 imgsize + pos + spacing*i
\r
947 m_images[i]->setScaleImage(true);
\r
948 m_texts.push_back(env->addStaticText(
\r
950 textsize + pos + spacing*i,
\r
953 m_texts[i]->setBackgroundColor(
\r
954 video::SColor(128,0,0,0));
\r
955 m_texts[i]->setTextAlignment(
\r
957 gui::EGUIA_UPPERLEFT);
\r
961 virtual bool OnEvent(const SEvent& event)
\r
966 void setSelection(s32 i)
\r
975 start = m_selection - m_itemcount / 2;
\r
977 InventoryList *mainlist = m_inventory->getList("main");
\r
979 for(s32 i=0; i<m_itemcount; i++)
\r
983 if(j > (s32)mainlist->getSize() - 1)
\r
984 j -= mainlist->getSize();
\r
986 j += mainlist->getSize();
\r
988 InventoryItem *item = mainlist->getItem(j);
\r
992 m_images[i]->setImage(NULL);
\r
995 if(m_selection == j)
\r
996 swprintf(t, 10, L"<-");
\r
998 swprintf(t, 10, L"");
\r
999 m_texts[i]->setText(t);
\r
1001 // The next ifs will segfault with a NULL pointer
\r
1006 m_images[i]->setImage(item->getImage());
\r
1009 if(m_selection == j)
\r
1010 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1012 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1013 m_texts[i]->setText(t);
\r
1019 core::array<gui::IGUIStaticText*> m_texts;
\r
1020 core::array<gui::IGUIImage*> m_images;
\r
1021 Inventory *m_inventory;
\r
1025 int main(int argc, char *argv[])
\r
1028 Low-level initialization
\r
1031 bool disable_stderr = false;
\r
1033 disable_stderr = true;
\r
1036 // Initialize debug streams
\r
1037 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1038 // Initialize debug stacks
\r
1039 debug_stacks_init();
\r
1041 DSTACK(__FUNCTION_NAME);
\r
1047 Parse command line
\r
1050 // List all allowed options
\r
1051 core::map<std::string, ValueSpec> allowed_options;
\r
1052 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1053 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1054 "Run server directly"));
\r
1055 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1056 "Load configuration from specified file"));
\r
1057 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1058 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1059 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1060 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1061 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1063 Settings cmd_args;
\r
1065 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1067 if(ret == false || cmd_args.getFlag("help"))
\r
1069 dstream<<"Allowed options:"<<std::endl;
\r
1070 for(core::map<std::string, ValueSpec>::Iterator
\r
1071 i = allowed_options.getIterator();
\r
1072 i.atEnd() == false; i++)
\r
1074 dstream<<" --"<<i.getNode()->getKey();
\r
1075 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1080 dstream<<" <value>";
\r
1082 dstream<<std::endl;
\r
1084 if(i.getNode()->getValue().help != NULL)
\r
1086 dstream<<" "<<i.getNode()->getValue().help
\r
1091 return cmd_args.getFlag("help") ? 0 : 1;
\r
1096 Basic initialization
\r
1099 // Initialize default settings
\r
1100 set_default_settings();
\r
1102 // Print startup message
\r
1103 dstream<<DTIME<<"minetest-c55"
\r
1104 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1105 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1108 // Set locale. This is for forcing '.' as the decimal point.
\r
1109 std::locale::global(std::locale("C"));
\r
1110 // This enables printing all characters in bitmap font
\r
1111 setlocale(LC_CTYPE, "en_US");
\r
1113 // Initialize sockets
\r
1115 atexit(sockets_cleanup);
\r
1125 // Path of configuration file in use
\r
1126 std::string configpath = "";
\r
1128 if(cmd_args.exists("config"))
\r
1130 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1133 dstream<<"Could not read configuration from \""
\r
1134 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1137 configpath = cmd_args.get("config");
\r
1141 const char *filenames[2] =
\r
1143 "../minetest.conf",
\r
1144 "../../minetest.conf"
\r
1147 for(u32 i=0; i<2; i++)
\r
1149 bool r = g_settings.readConfigFile(filenames[i]);
\r
1152 configpath = filenames[i];
\r
1158 // Initialize random seed
\r
1164 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1165 || cmd_args.getFlag("enable-unittests") == true)
\r
1171 Global range mutex
\r
1173 g_range_mutex.Init();
\r
1174 assert(g_range_mutex.IsInitialized());
\r
1176 // Read map parameters from settings
\r
1178 HMParams hm_params;
\r
1179 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1180 hm_params.randmax = g_settings.get("height_randmax");
\r
1181 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1182 hm_params.base = g_settings.get("height_base");
\r
1184 MapParams map_params;
\r
1185 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1186 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1192 std::cout<<std::endl<<std::endl;
\r
1195 <<" .__ __ __ "<<std::endl
\r
1196 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1197 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1198 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1199 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1200 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1203 std::cout<<std::endl;
\r
1204 //char templine[100];
\r
1208 if(cmd_args.exists("port"))
\r
1210 port = cmd_args.getU16("port");
\r
1214 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1215 std::cout<<"-> "<<port<<std::endl;
\r
1218 if(cmd_args.getFlag("server"))
\r
1220 DSTACK("Dedicated server branch");
\r
1222 std::cout<<std::endl;
\r
1223 std::cout<<"========================"<<std::endl;
\r
1224 std::cout<<"Running dedicated server"<<std::endl;
\r
1225 std::cout<<"========================"<<std::endl;
\r
1226 std::cout<<std::endl;
\r
1228 Server server("../map", hm_params, map_params);
\r
1229 server.start(port);
\r
1233 // This is kind of a hack but can be done like this
\r
1234 // because server.step() is very light
\r
1236 server.step(0.030);
\r
1238 static int counter = 0;
\r
1244 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1245 core::list<PlayerInfo>::Iterator i;
\r
1246 static u32 sum_old = 0;
\r
1247 u32 sum = PIChecksum(list);
\r
1248 if(sum != sum_old)
\r
1250 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1251 for(i=list.begin(); i!=list.end(); i++)
\r
1253 i->PrintLine(&std::cout);
\r
1263 bool hosting = false;
\r
1264 char connect_name[100] = "";
\r
1266 if(cmd_args.exists("address"))
\r
1268 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1270 else if(is_yes(g_settings.get("host_game")) == false)
\r
1272 if(g_settings.get("address") != "")
\r
1274 std::cout<<g_settings.get("address")<<std::endl;
\r
1275 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1279 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1280 std::cin.getline(connect_name, 100);
\r
1284 if(connect_name[0] == 0){
\r
1285 snprintf(connect_name, 100, "127.0.0.1");
\r
1290 std::cout<<"> Hosting game"<<std::endl;
\r
1292 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1294 char playername[PLAYERNAME_SIZE] = "";
\r
1295 if(g_settings.get("name") != "")
\r
1297 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1301 std::cout<<"Name of player: ";
\r
1302 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1304 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1307 Resolution selection
\r
1310 bool fullscreen = false;
\r
1311 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1312 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1316 MyEventReceiver receiver;
\r
1318 video::E_DRIVER_TYPE driverType;
\r
1321 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1322 driverType = video::EDT_OPENGL;
\r
1324 driverType = video::EDT_OPENGL;
\r
1327 // create device and exit if creation failed
\r
1329 IrrlichtDevice *device;
\r
1330 device = createDevice(driverType,
\r
1331 core::dimension2d<u32>(screenW, screenH),
\r
1332 16, fullscreen, false, false, &receiver);
\r
1335 return 1; // could not create selected driver.
\r
1337 g_device = device;
\r
1338 g_irrlicht = new IrrlichtWrapper(device);
\r
1340 //g_device = device;
\r
1342 device->setResizable(true);
\r
1344 bool random_input = g_settings.getBool("random_input")
\r
1345 || cmd_args.getFlag("random-input");
\r
1347 g_input = new RandomInputHandler();
\r
1349 g_input = new RealInputHandler(device, &receiver);
\r
1352 Continue initialization
\r
1355 video::IVideoDriver* driver = device->getVideoDriver();
\r
1358 This changes the minimum allowed number of vertices in a VBO
\r
1360 //driver->setMinHardwareBufferVertexCount(1);
\r
1362 scene::ISceneManager* smgr = device->getSceneManager();
\r
1364 guienv = device->getGUIEnvironment();
\r
1365 gui::IGUISkin* skin = guienv->getSkin();
\r
1366 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1368 skin->setFont(font);
\r
1370 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1371 dstream<<"text_height="<<text_height<<std::endl;
\r
1373 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1374 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1375 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1376 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1377 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1378 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1380 const wchar_t *text = L"Loading and connecting...";
\r
1381 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1382 core::vector2d<s32> textsize(300, text_height);
\r
1383 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1385 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1386 text, textrect, false, false);
\r
1387 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1389 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1390 guienv->drawAll();
\r
1391 driver->endScene();
\r
1394 Preload some textures
\r
1397 tile_materials_preload(g_irrlicht);
\r
1400 Make a scope here for the client so that it gets removed
\r
1401 before the irrlicht device
\r
1405 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1410 SharedPtr<Server> server;
\r
1412 server = new Server("../map", hm_params, map_params);
\r
1413 server->start(port);
\r
1420 Client client(device, playername,
\r
1422 g_viewing_range_nodes,
\r
1423 g_viewing_range_all);
\r
1425 g_client = &client;
\r
1427 Address connect_address(0,0,0,0, port);
\r
1429 connect_address.Resolve(connect_name);
\r
1431 catch(ResolveError &e)
\r
1433 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1437 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1438 client.connect(connect_address);
\r
1441 while(client.connectedAndInitialized() == false)
\r
1444 if(server != NULL){
\r
1445 server->step(0.1);
\r
1450 catch(con::PeerNotFoundException &e)
\r
1452 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1459 /*scene::ISceneNode* skybox;
\r
1460 skybox = smgr->addSkyBoxSceneNode(
\r
1461 driver->getTexture("../data/skybox2.png"),
\r
1462 driver->getTexture("../data/skybox3.png"),
\r
1463 driver->getTexture("../data/skybox1.png"),
\r
1464 driver->getTexture("../data/skybox1.png"),
\r
1465 driver->getTexture("../data/skybox1.png"),
\r
1466 driver->getTexture("../data/skybox1.png"));*/
\r
1469 Create the camera node
\r
1472 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1473 0, // Camera parent
\r
1474 v3f(BS*100, BS*2, BS*100), // Look from
\r
1475 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1479 if(camera == NULL)
\r
1482 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1484 camera->setFOV(FOV_ANGLE);
\r
1486 // Just so big a value that everything rendered is visible
\r
1487 camera->setFarValue(100000*BS);
\r
1489 f32 camera_yaw = 0; // "right/left"
\r
1490 f32 camera_pitch = 0; // "up/down"
\r
1496 gui_loadingtext->remove();
\r
1499 Add some gui stuff
\r
1502 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1503 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1506 We need some kind of a root node to be able to add
\r
1507 custom elements directly on the screen.
\r
1508 Otherwise they won't be automatically drawn.
\r
1510 guiroot = guienv->addStaticText(L"",
\r
1511 core::rect<s32>(0, 0, 10000, 10000));
\r
1513 // Test the text input system
\r
1514 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1517 // First line of debug text
\r
1518 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1520 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1522 // Second line of debug text
\r
1523 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1525 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1528 // At the middle of the screen
\r
1529 // Object infos are shown in this
\r
1530 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1532 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1536 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1537 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1538 core::rect<s32>(70, 60, 795, 150),
\r
1540 core::list<std::wstring> chat_lines;
\r
1541 //chat_lines.push_back(L"Minetest-c55 up and running!");
\r
1544 Some statistics are collected in these
\r
1547 u32 beginscenetime = 0;
\r
1548 u32 scenetime = 0;
\r
1549 u32 endscenetime = 0;
\r
1555 bool first_loop_after_window_activation = true;
\r
1557 // Time is in milliseconds
\r
1558 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1559 // NOTE: Have to call run() between calls of this to update the timer
\r
1560 u32 lasttime = device->getTimer()->getTime();
\r
1562 while(device->run())
\r
1565 Run global IrrlichtWrapper's main thread processing stuff
\r
1567 g_irrlicht->Run();
\r
1570 Random calculations
\r
1572 v2u32 screensize = driver->getScreenSize();
\r
1573 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1575 // Hilight boxes collected during the loop and displayed
\r
1576 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1579 std::wstring infotext;
\r
1581 //TimeTaker //timer1("//timer1");
\r
1583 // Time of frame without fps limit
\r
1587 // not using getRealTime is necessary for wine
\r
1588 u32 time = device->getTimer()->getTime();
\r
1589 if(time > lasttime)
\r
1590 busytime_u32 = time - lasttime;
\r
1593 busytime = busytime_u32 / 1000.0;
\r
1596 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1598 // Absolutelu necessary for wine!
\r
1605 updateViewingRange(busytime, &client);
\r
1612 float fps_max = g_settings.getFloat("fps_max");
\r
1613 u32 frametime_min = 1000./fps_max;
\r
1615 if(busytime_u32 < frametime_min)
\r
1617 u32 sleeptime = frametime_min - busytime_u32;
\r
1618 device->sleep(sleeptime);
\r
1622 // Absolutelu necessary for wine!
\r
1626 Time difference calculation
\r
1628 f32 dtime; // in seconds
\r
1630 u32 time = device->getTimer()->getTime();
\r
1631 if(time > lasttime)
\r
1632 dtime = (time - lasttime) / 1000.0;
\r
1638 Time average and jitter calculation
\r
1641 static f32 dtime_avg1 = 0.0;
\r
1642 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1643 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1645 static f32 dtime_jitter1_max_sample = 0.0;
\r
1646 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1648 static f32 jitter1_max = 0.0;
\r
1649 static f32 counter = 0.0;
\r
1650 if(dtime_jitter1 > jitter1_max)
\r
1651 jitter1_max = dtime_jitter1;
\r
1656 dtime_jitter1_max_sample = jitter1_max;
\r
1657 dtime_jitter1_max_fraction
\r
1658 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1659 jitter1_max = 0.0;
\r
1662 Control freetime ratio
\r
1664 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1666 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1667 g_freetime_ratio += 0.01;
\r
1671 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1672 g_freetime_ratio -= 0.01;
\r
1678 Busytime average and jitter calculation
\r
1681 static f32 busytime_avg1 = 0.0;
\r
1682 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1683 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1685 static f32 busytime_jitter1_max_sample = 0.0;
\r
1686 static f32 busytime_jitter1_min_sample = 0.0;
\r
1688 static f32 jitter1_max = 0.0;
\r
1689 static f32 jitter1_min = 0.0;
\r
1690 static f32 counter = 0.0;
\r
1691 if(busytime_jitter1 > jitter1_max)
\r
1692 jitter1_max = busytime_jitter1;
\r
1693 if(busytime_jitter1 < jitter1_min)
\r
1694 jitter1_min = busytime_jitter1;
\r
1696 if(counter > 0.0){
\r
1698 busytime_jitter1_max_sample = jitter1_max;
\r
1699 busytime_jitter1_min_sample = jitter1_min;
\r
1700 jitter1_max = 0.0;
\r
1701 jitter1_min = 0.0;
\r
1706 Debug info for client
\r
1709 static float counter = 0.0;
\r
1714 client.printDebugInfo(std::cout);
\r
1719 Input handler step()
\r
1721 g_input->step(dtime);
\r
1724 Player speed control
\r
1733 bool a_superspeed,
\r
1736 PlayerControl control(
\r
1737 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1738 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1739 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1740 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1741 g_input->isKeyDown(irr::KEY_SPACE),
\r
1742 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1746 client.setPlayerControl(control);
\r
1750 Process environment
\r
1754 //TimeTaker timer("client.step(dtime)");
\r
1755 client.step(dtime);
\r
1756 //client.step(dtime_avg1);
\r
1759 if(server != NULL)
\r
1761 //TimeTaker timer("server->step(dtime)");
\r
1762 server->step(dtime);
\r
1765 v3f player_position = client.getPlayerPosition();
\r
1767 //TimeTaker //timer2("//timer2");
\r
1770 Mouse and camera control
\r
1773 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1776 device->getCursorControl()->setVisible(false);
\r
1778 if(first_loop_after_window_activation){
\r
1779 //std::cout<<"window active, first loop"<<std::endl;
\r
1780 first_loop_after_window_activation = false;
\r
1783 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1784 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1785 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1786 camera_yaw -= dx*0.2;
\r
1787 camera_pitch += dy*0.2;
\r
1788 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1789 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1791 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1794 device->getCursorControl()->setVisible(true);
\r
1796 //std::cout<<"window inactive"<<std::endl;
\r
1797 first_loop_after_window_activation = true;
\r
1800 camera_yaw = wrapDegrees(camera_yaw);
\r
1801 camera_pitch = wrapDegrees(camera_pitch);
\r
1803 v3f camera_direction = v3f(0,0,1);
\r
1804 camera_direction.rotateYZBy(camera_pitch);
\r
1805 camera_direction.rotateXZBy(camera_yaw);
\r
1807 v3f camera_position =
\r
1808 player_position + v3f(0, BS+BS/2, 0);
\r
1810 camera->setPosition(camera_position);
\r
1811 // *100.0 helps in large map coordinates
\r
1812 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1814 if(FIELD_OF_VIEW_TEST){
\r
1815 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1816 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1819 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1820 //TimeTaker timer("client.updateCamera");
\r
1821 client.updateCamera(camera_position, camera_direction);
\r
1825 //TimeTaker //timer3("//timer3");
\r
1828 Calculate what block is the crosshair pointing to
\r
1831 //u32 t1 = device->getTimer()->getRealTime();
\r
1833 //f32 d = 4; // max. distance
\r
1834 f32 d = 4; // max. distance
\r
1835 core::line3d<f32> shootline(camera_position,
\r
1836 camera_position + camera_direction * BS * (d+1));
\r
1838 MapBlockObject *selected_object = client.getSelectedObject
\r
1839 (d*BS, camera_position, shootline);
\r
1842 If it's pointing to a MapBlockObject
\r
1845 if(selected_object != NULL)
\r
1847 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1849 core::aabbox3d<f32> box_on_map
\r
1850 = selected_object->getSelectionBoxOnMap();
\r
1852 hilightboxes.push_back(box_on_map);
\r
1854 infotext = narrow_to_wide(selected_object->infoText());
\r
1856 if(g_input->getLeftClicked())
\r
1858 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1859 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1860 selected_object->getId(), g_selected_item);
\r
1862 else if(g_input->getRightClicked())
\r
1864 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1866 Check if we want to modify the object ourselves
\r
1868 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1870 dstream<<"Sign object right-clicked"<<std::endl;
\r
1872 if(random_input == false)
\r
1874 // Get a new text for it
\r
1876 TextDest *dest = new TextDestSign(
\r
1877 selected_object->getBlock()->getPos(),
\r
1878 selected_object->getId(),
\r
1881 SignObject *sign_object = (SignObject*)selected_object;
\r
1883 std::wstring wtext =
\r
1884 narrow_to_wide(sign_object->getText());
\r
1886 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1887 &g_active_menu_count, dest,
\r
1892 Otherwise pass the event to the server as-is
\r
1896 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1897 selected_object->getId(), g_selected_item);
\r
1901 else // selected_object == NULL
\r
1905 Find out which node we are pointing at
\r
1908 bool nodefound = false;
\r
1910 v3s16 neighbourpos;
\r
1911 core::aabbox3d<f32> nodefacebox;
\r
1912 f32 mindistance = BS * 1001;
\r
1914 v3s16 pos_i = floatToInt(player_position);
\r
1916 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1920 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1921 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1922 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1923 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1924 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1925 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1927 for(s16 y = ystart; y <= yend; y++)
\r
1928 for(s16 z = zstart; z <= zend; z++)
\r
1929 for(s16 x = xstart; x <= xend; x++)
\r
1934 n = client.getNode(v3s16(x,y,z));
\r
1935 if(content_pointable(n.d) == false)
\r
1938 catch(InvalidPositionException &e)
\r
1944 v3f npf = intToFloat(np);
\r
1949 v3s16(0,0,1), // back
\r
1950 v3s16(0,1,0), // top
\r
1951 v3s16(1,0,0), // right
\r
1952 v3s16(0,0,-1), // front
\r
1953 v3s16(0,-1,0), // bottom
\r
1954 v3s16(-1,0,0), // left
\r
1960 if(n.d == CONTENT_TORCH)
\r
1962 v3s16 dir = unpackDir(n.dir);
\r
1963 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
1964 dir_f *= BS/2 - BS/6 - BS/20;
\r
1965 v3f cpf = npf + dir_f;
\r
1966 f32 distance = (cpf - camera_position).getLength();
\r
1968 core::aabbox3d<f32> box;
\r
1971 if(dir == v3s16(0,-1,0))
\r
1973 box = core::aabbox3d<f32>(
\r
1974 npf - v3f(BS/6, BS/2, BS/6),
\r
1975 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
1979 else if(dir == v3s16(0,1,0))
\r
1981 box = core::aabbox3d<f32>(
\r
1982 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
1983 npf + v3f(BS/6, BS/2, BS/6)
\r
1989 box = core::aabbox3d<f32>(
\r
1990 cpf - v3f(BS/6, BS/3, BS/6),
\r
1991 cpf + v3f(BS/6, BS/3, BS/6)
\r
1995 if(distance < mindistance)
\r
1997 if(box.intersectsWithLine(shootline))
\r
2001 neighbourpos = np;
\r
2002 mindistance = distance;
\r
2003 nodefacebox = box;
\r
2012 for(u16 i=0; i<6; i++)
\r
2014 v3f dir_f = v3f(dirs[i].X,
\r
2015 dirs[i].Y, dirs[i].Z);
\r
2016 v3f centerpoint = npf + dir_f * BS/2;
\r
2018 (centerpoint - camera_position).getLength();
\r
2020 if(distance < mindistance)
\r
2022 core::CMatrix4<f32> m;
\r
2023 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2025 // This is the back face
\r
2026 v3f corners[2] = {
\r
2027 v3f(BS/2, BS/2, BS/2),
\r
2028 v3f(-BS/2, -BS/2, BS/2+d)
\r
2031 for(u16 j=0; j<2; j++)
\r
2033 m.rotateVect(corners[j]);
\r
2034 corners[j] += npf;
\r
2037 core::aabbox3d<f32> facebox(corners[0]);
\r
2038 facebox.addInternalPoint(corners[1]);
\r
2040 if(facebox.intersectsWithLine(shootline))
\r
2044 neighbourpos = np + dirs[i];
\r
2045 mindistance = distance;
\r
2046 nodefacebox = facebox;
\r
2048 } // if distance < mindistance
\r
2050 } // regular block
\r
2053 static float nodig_delay_counter = 0.0;
\r
2057 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2059 static float dig_time = 0.0;
\r
2060 static u16 dig_index = 0;
\r
2062 hilightboxes.push_back(nodefacebox);
\r
2064 if(g_input->getLeftReleased())
\r
2066 client.clearTempMod(nodepos);
\r
2070 if(nodig_delay_counter > 0.0)
\r
2072 nodig_delay_counter -= dtime;
\r
2076 if(nodepos != nodepos_old)
\r
2078 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2079 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2081 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2083 client.clearTempMod(nodepos_old);
\r
2088 if(g_input->getLeftClicked() ||
\r
2089 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2091 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2092 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2094 if(g_input->getLeftClicked())
\r
2096 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2098 if(g_input->getLeftState())
\r
2100 MapNode n = client.getNode(nodepos);
\r
2102 // TODO: Get this from some table that is sent by server
\r
2103 float dig_time_complete = 0.5;
\r
2104 if(n.d == CONTENT_STONE || n.d == CONTENT_COALSTONE)
\r
2106 dig_time_complete = 10.0;
\r
2108 InventoryList *mlist = local_inventory.getList("main");
\r
2111 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2112 if((std::string)item->getName() == "ToolItem")
\r
2114 ToolItem *titem = (ToolItem*)item;
\r
2115 if(titem->getToolName() == "WPick")
\r
2117 dig_time_complete = 1.2;
\r
2119 else if(titem->getToolName() == "STPick")
\r
2121 dig_time_complete = 0.6;
\r
2127 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2128 * dig_time/dig_time_complete);
\r
2130 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2132 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2133 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2137 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2138 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2139 client.clearTempMod(nodepos);
\r
2140 client.removeNode(nodepos);
\r
2144 nodig_delay_counter = dig_time_complete
\r
2145 / (float)CRACK_ANIMATION_LENGTH;
\r
2148 dig_time += dtime;
\r
2152 if(g_input->getRightClicked())
\r
2154 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2155 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2158 nodepos_old = nodepos;
\r
2163 } // selected_object == NULL
\r
2165 g_input->resetLeftClicked();
\r
2166 g_input->resetRightClicked();
\r
2168 if(g_input->getLeftReleased())
\r
2170 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2172 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2174 if(g_input->getRightReleased())
\r
2176 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2180 g_input->resetLeftReleased();
\r
2181 g_input->resetRightReleased();
\r
2184 Calculate stuff for drawing
\r
2187 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2189 u32 daynight_ratio = client.getDayNightRatio();
\r
2190 video::SColor bgcolor = video::SColor(
\r
2192 skycolor.getRed() * daynight_ratio / 1000,
\r
2193 skycolor.getGreen() * daynight_ratio / 1000,
\r
2194 skycolor.getBlue() * daynight_ratio / 1000);
\r
2200 if(g_settings.getBool("enable_fog") == true)
\r
2202 f32 range = g_viewing_range_nodes * BS;
\r
2203 if(g_viewing_range_all)
\r
2204 range = 100000*BS;
\r
2208 video::EFT_FOG_LINEAR,
\r
2212 false, // pixel fog
\r
2213 false // range fog
\r
2219 Update gui stuff (0ms)
\r
2222 //TimeTaker guiupdatetimer("Gui updating");
\r
2225 wchar_t temptext[150];
\r
2227 static float drawtime_avg = 0;
\r
2228 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2229 static float beginscenetime_avg = 0;
\r
2230 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2231 static float scenetime_avg = 0;
\r
2232 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2233 static float endscenetime_avg = 0;
\r
2234 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2236 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2238 L", R: range_all=%i"
\r
2240 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2242 g_viewing_range_all,
\r
2244 beginscenetime_avg,
\r
2249 guitext->setText(temptext);
\r
2253 wchar_t temptext[150];
\r
2254 swprintf(temptext, 150,
\r
2255 L"(% .1f, % .1f, % .1f)"
\r
2256 L" (% .3f < btime_jitter < % .3f"
\r
2257 L", dtime_jitter = % .1f %%)",
\r
2258 player_position.X/BS,
\r
2259 player_position.Y/BS,
\r
2260 player_position.Z/BS,
\r
2261 busytime_jitter1_min_sample,
\r
2262 busytime_jitter1_max_sample,
\r
2263 dtime_jitter1_max_fraction * 100.0
\r
2266 guitext2->setText(temptext);
\r
2270 guitext_info->setText(infotext.c_str());
\r
2274 Get chat messages from client
\r
2278 std::wstring message;
\r
2279 while(client.getChatMessage(message))
\r
2281 chat_lines.push_back(message);
\r
2282 if(chat_lines.size() > 5)
\r
2284 core::list<std::wstring>::Iterator
\r
2285 i = chat_lines.begin();
\r
2286 chat_lines.erase(i);
\r
2289 // Append them to form the whole static text and throw
\r
2290 // it to the gui element
\r
2291 std::wstring whole;
\r
2292 for(core::list<std::wstring>::Iterator
\r
2293 i = chat_lines.begin();
\r
2294 i != chat_lines.end(); i++)
\r
2296 whole += (*i) + L'\n';
\r
2298 chat_guitext->setText(whole.c_str());
\r
2299 // Update gui element size and position
\r
2300 core::rect<s32> rect(
\r
2302 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2303 screensize.X - 10,
\r
2306 chat_guitext->setRelativePosition(rect);
\r
2313 static u16 old_selected_item = 65535;
\r
2314 if(client.getLocalInventoryUpdated()
\r
2315 || g_selected_item != old_selected_item)
\r
2317 old_selected_item = g_selected_item;
\r
2318 //std::cout<<"Updating local inventory"<<std::endl;
\r
2319 client.getLocalInventory(local_inventory);
\r
2320 quick_inventory->setSelection(g_selected_item);
\r
2321 quick_inventory->update();
\r
2325 Send actions returned by the inventory menu
\r
2327 while(inventory_action_queue.size() != 0)
\r
2329 InventoryAction *a = inventory_action_queue.pop_front();
\r
2331 client.sendInventoryAction(a);
\r
2340 TimeTaker drawtimer("Drawing");
\r
2344 TimeTaker timer("beginScene");
\r
2345 driver->beginScene(true, true, bgcolor);
\r
2346 //driver->beginScene(false, true, bgcolor);
\r
2347 beginscenetime = timer.stop(true);
\r
2352 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2355 TimeTaker timer("smgr");
\r
2357 scenetime = timer.stop(true);
\r
2361 //TimeTaker timer9("auxiliary drawings");
\r
2365 //TimeTaker //timer10("//timer10");
\r
2367 video::SMaterial m;
\r
2369 m.Lighting = false;
\r
2370 driver->setMaterial(m);
\r
2372 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2374 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2375 i != hilightboxes.end(); i++)
\r
2377 /*std::cout<<"hilightbox min="
\r
2378 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2380 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2382 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2388 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2389 displaycenter + core::vector2d<s32>(10,0),
\r
2390 video::SColor(255,255,255,255));
\r
2391 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2392 displaycenter + core::vector2d<s32>(0,10),
\r
2393 video::SColor(255,255,255,255));
\r
2398 //TimeTaker //timer11("//timer11");
\r
2404 guienv->drawAll();
\r
2408 TimeTaker timer("endScene");
\r
2409 driver->endScene();
\r
2410 endscenetime = timer.stop(true);
\r
2413 drawtime = drawtimer.stop(true);
\r
2419 static s16 lastFPS = 0;
\r
2420 //u16 fps = driver->getFPS();
\r
2421 u16 fps = (1.0/dtime_avg1);
\r
2423 if (lastFPS != fps)
\r
2425 core::stringw str = L"Minetest [";
\r
2426 str += driver->getName();
\r
2430 device->setWindowCaption(str.c_str());
\r
2436 device->yield();*/
\r
2439 delete quick_inventory;
\r
2441 } // client is deleted at this point
\r
2446 In the end, delete the Irrlicht device.
\r
2451 Update configuration file
\r
2453 /*if(configpath != "")
\r
2455 g_settings.updateConfigFile(configpath.c_str());
\r
2459 catch(con::PeerNotFoundException &e)
\r
2461 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2463 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2465 This is what has to be done in every thread to get suitable debug info
\r
2467 catch(std::exception &e)
\r
2469 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2470 <<e.what()<<std::endl;
\r
2475 debugstreams_deinit();
\r