3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
\r
5 This program is free software; you can redistribute it and/or modify
\r
6 it under the terms of the GNU General Public License as published by
\r
7 the Free Software Foundation; either version 2 of the License, or
\r
8 (at your option) any later version.
\r
10 This program is distributed in the hope that it will be useful,
\r
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 GNU General Public License for more details.
\r
15 You should have received a copy of the GNU General Public License along
\r
16 with this program; if not, write to the Free Software Foundation, Inc.,
\r
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
21 =============================== NOTES ==============================
\r
22 NOTE: Things starting with TODO are sometimes only suggestions.
\r
24 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
25 is an apparanet memory leak in irrlicht when using it (not sure)
\r
27 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
28 NOTE: Global locale is now set at initialization
\r
30 SUGG: Fix address to be ipv6 compatible
\r
32 FIXME: When a new sector is generated, it may change the ground level
\r
33 of it's and it's neighbors border that two blocks that are
\r
34 above and below each other and that are generated before and
\r
35 after the sector heightmap generation (order doesn't matter),
\r
36 can have a small gap between each other at the border.
\r
37 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
38 using for UnlimitedHeightmap? (getting all neighbors
\r
41 SUGG: Transfer more blocks in a single packet
\r
42 SUGG: A blockdata combiner class, to which blocks are added and at
\r
43 destruction it sends all the stuff in as few packets as possible.
\r
45 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
46 SUGG: Fetch stuff mainly from the viewing direction
\r
48 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
49 - This enables saving many packets and making a faster connection
\r
50 - This also enables server to check if client has received the
\r
51 most recent block sent, for example.
\r
52 SUGG: Add a sane bandwidth throttling system to Connection
\r
54 SUGG: More fine-grained control of client's dumping of blocks from
\r
56 - ...What does this mean in the first place?
\r
58 SUGG: A map editing mode (similar to dedicated server mode)
\r
60 SUGG: Add a time value to the param of footstepped grass and check it
\r
61 against a global timer when a block is accessed, to make old
\r
64 SUGG: Make a copy of close-range environment on client for showing
\r
65 on screen, with minimal mutexes to slow down the main loop
\r
67 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
68 it by sending more stuff in a single packet.
\r
69 - Add a packet queue to RemoteClient, from which packets will be
\r
70 combined with object data packets
\r
71 - This is not exactly trivial: the object data packets are
\r
72 sometimes very big by themselves
\r
74 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
75 - This will allow saving ages of rats on disk but not sending
\r
78 SUGG: Implement lighting using VoxelManipulator
\r
79 - Would it be significantly faster?
\r
81 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
82 - Does their position get saved to a border value or something?
\r
83 - Does this happen anymore?
\r
85 SUGG: MovingObject::move and Player::move are basically the same.
\r
88 SUGG: Implement a "Fast check queue" (a queue with a map for checking
\r
89 if something is already in it)
\r
90 - Use it in active block queue in water flowing
\r
92 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
94 SUGG: A version number to blocks, which increments when the block is
\r
95 modified (node add/remove, water update, lighting update)
\r
96 - This can then be used to make sure the most recent version of
\r
97 a block has been sent to client
\r
99 SUGG: Make the amount of blocks sending to client and the total
\r
100 amount of blocks dynamically limited. Transferring blocks is the
\r
101 main network eater of this system, so it is the one that has
\r
102 to be throttled so that RTTs stay low.
\r
104 SUGG: Meshes of blocks could be split into 6 meshes facing into
\r
105 different directions and then only those drawn that need to be
\r
106 - Also an 1-dimensional tile map would be nice probably
\r
108 TODO: Untie client network operations from framerate
\r
109 - Needs some input queues or something
\r
110 - Not really necessary?
\r
112 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
114 - That is >500 vertices
\r
116 TODO: Better dungeons
\r
119 TODO: Startup and configuration menu
\r
121 TODO: There are some lighting-related todos and fixmes in
\r
122 ServerMap::emergeBlock
\r
124 TODO: Proper handling of spawning place (try to find something that
\r
125 is not in the middle of an ocean (some land to stand on at
\r
126 least) and save it in map config.
\r
128 TODO: Players to only be hidden when the client quits.
\r
129 TODO: - Players to be saved on disk, with inventory
\r
130 TODO: Players to be saved as text in map/players/<name>
\r
131 TODO: Player inventory to be saved on disk
\r
133 TODO: Make fetching sector's blocks more efficient when rendering
\r
134 sectors that have very large amounts of blocks (on client)
\r
136 TODO: Make the video backend selectable
\r
138 TODO: Copy the text of the last picked sign to inventory in creative
\r
141 TODO: Get rid of GotSplitPacketException
\r
143 TODO: Check what goes wrong with caching map to disk (Kray)
\r
145 Block object server side:
\r
146 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
147 - For all blocks in the buffer, objects are stepped(). This
\r
148 means they are active.
\r
149 - TODO: A global active buffer is needed for the server
\r
150 - TODO: A timestamp to blocks
\r
151 - TODO: All blocks going in and out of the buffer are recorded.
\r
152 - TODO: For outgoing blocks, timestamp is written.
\r
153 - TODO: For incoming blocks, time difference is calculated and
\r
154 objects are stepped according to it.
\r
156 TODO: Better handling of objects and mobs
\r
158 - There has to be some way to do it with less spaghetti code
\r
159 - Make separate classes for client and server
\r
160 - Client should not discriminate between blocks, server should
\r
161 - Make other players utilize the same framework
\r
162 - This is also needed for objects that don't get sent to client
\r
163 but are used for triggers etc
\r
165 TODO: Draw big amounts of torches better (that is, throw them in the
\r
166 same meshbuffer (can the meshcollector class be used?))
\r
168 TODO: Check if the usage of Client::isFetchingBlocks() in
\r
169 updateViewingRange() actually does something
\r
170 NOTE: It isn't used anymore after the rewrite.
\r
172 TODO: Make an option to the server to disable building and digging near
\r
173 the starting position
\r
175 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
176 need an additional metadata field for the texts
\r
177 - This is also needed for item container chests
\r
178 TODO: There has to be some better way to handle static objects than to
\r
179 send them all the time. This affects signs and item objects.
\r
181 TODO: When server sees that client is removing an inexistent block or
\r
182 adding a block to an existent position, resend the MapBlock.
\r
184 TODO: Map generator: add other materials underground (mud)
\r
187 ======================================================================
\r
189 ======================================================================
\r
194 Setting this to 1 enables a special camera mode that forces
\r
195 the renderers to think that the camera statically points from
\r
196 the starting place to a static direction.
\r
198 This allows one to move around with the player and see what
\r
199 is actually drawn behind solid things and behind the player.
\r
201 #define FIELD_OF_VIEW_TEST 0
\r
203 #ifdef UNITTEST_DISABLE
\r
205 #pragma message ("Disabling unit tests")
\r
207 #warning "Disabling unit tests"
\r
209 // Disable unit tests
\r
210 #define ENABLE_TESTS 0
\r
212 // Enable unit tests
\r
213 #define ENABLE_TESTS 1
\r
217 #pragma comment(lib, "Irrlicht.lib")
\r
218 #pragma comment(lib, "jthread.lib")
\r
219 #pragma comment(lib, "zlibwapi.lib")
\r
220 // This would get rid of the console window
\r
221 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
224 #include <iostream>
\r
226 #include <jmutexautolock.h>
\r
227 #include <locale.h>
\r
228 #include "common_irrlicht.h"
\r
231 #include "player.h"
\r
234 #include "environment.h"
\r
235 #include "server.h"
\r
236 #include "client.h"
\r
237 #include "serialization.h"
\r
238 #include "constants.h"
\r
239 #include "strfnd.h"
\r
240 #include "porting.h"
\r
241 #include "irrlichtwrapper.h"
\r
242 #include "gettime.h"
\r
243 #include "porting.h"
\r
244 #include "guiPauseMenu.h"
\r
245 #include "guiInventoryMenu.h"
\r
246 #include "guiTextInputMenu.h"
\r
247 #include "materials.h"
\r
248 #include "guiMessageMenu.h"
\r
250 IrrlichtWrapper *g_irrlicht;
\r
252 MapDrawControl draw_control;
\r
256 These are loaded from the config file.
\r
259 Settings g_settings;
\r
261 extern void set_default_settings();
\r
267 IrrlichtDevice *g_device = NULL;
\r
268 Client *g_client = NULL;
\r
273 gui::IGUIEnvironment* guienv = NULL;
\r
274 gui::IGUIStaticText *guiroot = NULL;
\r
275 int g_active_menu_count = 0;
\r
277 bool noMenuActive()
\r
279 return (g_active_menu_count == 0);
\r
282 // Inventory actions from the menu are buffered here before sending
\r
283 Queue<InventoryAction*> inventory_action_queue;
\r
284 // This is a copy of the inventory that the client's environment has
\r
285 Inventory local_inventory;
\r
287 u16 g_selected_item = 0;
\r
294 std::ostream *dout_con_ptr = &dummyout;
\r
295 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
296 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
297 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
298 //std::ostream *dout_con_ptr = &dstream;
\r
299 //std::ostream *derr_con_ptr = &dstream;
\r
302 std::ostream *dout_server_ptr = &dstream;
\r
303 std::ostream *derr_server_ptr = &dstream;
\r
306 std::ostream *dout_client_ptr = &dstream;
\r
307 std::ostream *derr_client_ptr = &dstream;
\r
310 gettime.h implementation
\r
316 Use irrlicht because it is more precise than porting.h's
\r
319 if(g_irrlicht == NULL)
\r
321 return g_irrlicht->getTime();
\r
328 struct TextDestSign : public TextDest
\r
330 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
332 m_blockpos = blockpos;
\r
336 void gotText(std::wstring text)
\r
338 std::string ntext = wide_to_narrow(text);
\r
339 dstream<<"Changing text of a sign object: "
\r
340 <<ntext<<std::endl;
\r
341 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
349 struct TextDestChat : public TextDest
\r
351 TextDestChat(Client *client)
\r
355 void gotText(std::wstring text)
\r
357 m_client->sendChatMessage(text);
\r
358 m_client->addChatMessage(text);
\r
364 class MyEventReceiver : public IEventReceiver
\r
367 // This is the one method that we have to implement
\r
368 virtual bool OnEvent(const SEvent& event)
\r
371 React to nothing here if a menu is active
\r
373 if(noMenuActive() == false)
\r
379 // Remember whether each key is down or up
\r
380 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
382 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
384 if(event.KeyInput.PressedDown)
\r
386 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
392 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
394 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
396 dstream<<DTIME<<"MyEventReceiver: "
\r
397 <<"Launching pause menu"<<std::endl;
\r
398 // It will delete itself by itself
\r
399 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
400 &g_active_menu_count))->drop();
\r
403 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
405 dstream<<DTIME<<"MyEventReceiver: "
\r
406 <<"Launching inventory"<<std::endl;
\r
407 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
408 &local_inventory, &inventory_action_queue,
\r
409 &g_active_menu_count))->drop();
\r
412 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
414 TextDest *dest = new TextDestChat(g_client);
\r
416 (new GUITextInputMenu(guienv, guiroot, -1,
\r
417 &g_active_menu_count, dest,
\r
422 // Material selection
\r
423 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
425 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
428 g_selected_item = 0;
\r
429 dstream<<DTIME<<"Selected item: "
\r
430 <<g_selected_item<<std::endl;
\r
433 // Viewing range selection
\r
434 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
436 if(draw_control.range_all)
\r
438 draw_control.range_all = false;
\r
439 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
443 draw_control.range_all = true;
\r
444 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
448 // Print debug stacks
\r
449 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
451 dstream<<"-----------------------------------------"
\r
453 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
454 dstream<<"-----------------------------------------"
\r
456 debug_stacks_print();
\r
461 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
463 if(noMenuActive() == false)
\r
465 left_active = false;
\r
466 middle_active = false;
\r
467 right_active = false;
\r
471 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
472 left_active = event.MouseInput.isLeftPressed();
\r
473 middle_active = event.MouseInput.isMiddlePressed();
\r
474 right_active = event.MouseInput.isRightPressed();
\r
476 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
478 leftclicked = true;
\r
480 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
482 rightclicked = true;
\r
484 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
486 leftreleased = true;
\r
488 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
490 rightreleased = true;
\r
492 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
494 /*dstream<<"event.MouseInput.Wheel="
\r
495 <<event.MouseInput.Wheel<<std::endl;*/
\r
496 if(event.MouseInput.Wheel < 0)
\r
498 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
501 g_selected_item = 0;
\r
503 else if(event.MouseInput.Wheel > 0)
\r
505 if(g_selected_item > 0)
\r
508 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
517 // This is used to check whether a key is being held down
\r
518 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
520 return keyIsDown[keyCode];
\r
525 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
526 keyIsDown[i] = false;
\r
528 leftclicked = false;
\r
529 rightclicked = false;
\r
530 leftreleased = false;
\r
531 rightreleased = false;
\r
533 left_active = false;
\r
534 middle_active = false;
\r
535 right_active = false;
\r
546 bool rightreleased;
\r
549 bool middle_active;
\r
553 // We use this array to store the current state of each key
\r
554 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
557 IrrlichtDevice *m_device;
\r
566 virtual ~InputHandler()
\r
570 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
572 virtual v2s32 getMousePos() = 0;
\r
573 virtual void setMousePos(s32 x, s32 y) = 0;
\r
575 virtual bool getLeftState() = 0;
\r
576 virtual bool getRightState() = 0;
\r
578 virtual bool getLeftClicked() = 0;
\r
579 virtual bool getRightClicked() = 0;
\r
580 virtual void resetLeftClicked() = 0;
\r
581 virtual void resetRightClicked() = 0;
\r
583 virtual bool getLeftReleased() = 0;
\r
584 virtual bool getRightReleased() = 0;
\r
585 virtual void resetLeftReleased() = 0;
\r
586 virtual void resetRightReleased() = 0;
\r
588 virtual void step(float dtime) {};
\r
590 virtual void clear() {};
\r
593 InputHandler *g_input = NULL;
\r
595 class RealInputHandler : public InputHandler
\r
598 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
600 m_receiver(receiver)
\r
603 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
605 return m_receiver->IsKeyDown(keyCode);
\r
607 virtual v2s32 getMousePos()
\r
609 return m_device->getCursorControl()->getPosition();
\r
611 virtual void setMousePos(s32 x, s32 y)
\r
613 m_device->getCursorControl()->setPosition(x, y);
\r
616 virtual bool getLeftState()
\r
618 return m_receiver->left_active;
\r
620 virtual bool getRightState()
\r
622 return m_receiver->right_active;
\r
625 virtual bool getLeftClicked()
\r
627 return m_receiver->leftclicked;
\r
629 virtual bool getRightClicked()
\r
631 return m_receiver->rightclicked;
\r
633 virtual void resetLeftClicked()
\r
635 m_receiver->leftclicked = false;
\r
637 virtual void resetRightClicked()
\r
639 m_receiver->rightclicked = false;
\r
642 virtual bool getLeftReleased()
\r
644 return m_receiver->leftreleased;
\r
646 virtual bool getRightReleased()
\r
648 return m_receiver->rightreleased;
\r
650 virtual void resetLeftReleased()
\r
652 m_receiver->leftreleased = false;
\r
654 virtual void resetRightReleased()
\r
656 m_receiver->rightreleased = false;
\r
661 resetRightClicked();
\r
662 resetLeftClicked();
\r
665 IrrlichtDevice *m_device;
\r
666 MyEventReceiver *m_receiver;
\r
669 class RandomInputHandler : public InputHandler
\r
672 RandomInputHandler()
\r
674 leftclicked = false;
\r
675 rightclicked = false;
\r
676 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
677 keydown[i] = false;
\r
679 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
681 return keydown[keyCode];
\r
683 virtual v2s32 getMousePos()
\r
687 virtual void setMousePos(s32 x, s32 y)
\r
689 mousepos = v2s32(x,y);
\r
692 virtual bool getLeftState()
\r
696 virtual bool getRightState()
\r
701 virtual bool getLeftClicked()
\r
703 return leftclicked;
\r
705 virtual bool getRightClicked()
\r
707 return rightclicked;
\r
709 virtual void resetLeftClicked()
\r
711 leftclicked = false;
\r
713 virtual void resetRightClicked()
\r
715 rightclicked = false;
\r
718 virtual bool getLeftReleased()
\r
722 virtual bool getRightReleased()
\r
726 virtual void resetLeftReleased()
\r
729 virtual void resetRightReleased()
\r
733 virtual void step(float dtime)
\r
736 static float counter1 = 0;
\r
740 counter1 = 0.1*Rand(1,10);
\r
741 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
742 g_selected_material++;
\r
744 g_selected_material = 0;*/
\r
745 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
748 g_selected_item = 0;
\r
752 static float counter1 = 0;
\r
756 counter1 = 0.1*Rand(1, 40);
\r
757 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
761 static float counter1 = 0;
\r
765 counter1 = 0.1*Rand(1, 40);
\r
766 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
770 static float counter1 = 0;
\r
774 counter1 = 0.1*Rand(1, 40);
\r
775 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
779 static float counter1 = 0;
\r
783 counter1 = 0.1*Rand(1, 40);
\r
784 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
788 static float counter1 = 0;
\r
792 counter1 = 0.1*Rand(1, 20);
\r
793 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
797 static float counter1 = 0;
\r
801 counter1 = 0.1*Rand(1, 30);
\r
802 leftclicked = true;
\r
806 static float counter1 = 0;
\r
810 counter1 = 0.1*Rand(1, 20);
\r
811 rightclicked = true;
\r
814 mousepos += mousespeed;
\r
817 s32 Rand(s32 min, s32 max)
\r
819 return (myrand()%(max-min+1))+min;
\r
822 bool keydown[KEY_KEY_CODES_COUNT];
\r
829 void updateViewingRange(f32 frametime_in, Client *client)
\r
831 if(draw_control.range_all == true)
\r
834 static f32 added_frametime = 0;
\r
835 static s16 added_frames = 0;
\r
837 added_frametime += frametime_in;
\r
840 // Actually this counter kind of sucks because frametime is busytime
\r
841 static f32 counter = 0;
\r
842 counter -= frametime_in;
\r
848 /*dstream<<__FUNCTION_NAME
\r
849 <<": Collected "<<added_frames<<" frames, total of "
\r
850 <<added_frametime<<"s."<<std::endl;*/
\r
852 /*dstream<<"draw_control.blocks_drawn="
\r
853 <<draw_control.blocks_drawn
\r
854 <<", draw_control.blocks_would_have_drawn="
\r
855 <<draw_control.blocks_would_have_drawn
\r
858 float range_min = g_settings.getS16("viewing_range_nodes_min");
\r
859 float range_max = g_settings.getS16("viewing_range_nodes_max");
\r
861 draw_control.wanted_min_range = range_min;
\r
862 draw_control.wanted_max_blocks = (1.2*draw_control.blocks_drawn)+1;
\r
864 float block_draw_ratio = 1.0;
\r
865 if(draw_control.blocks_would_have_drawn != 0)
\r
867 block_draw_ratio = (float)draw_control.blocks_drawn
\r
868 / (float)draw_control.blocks_would_have_drawn;
\r
871 // Calculate the average frametime in the case that all wanted
\r
872 // blocks had been drawn
\r
873 f32 frametime = added_frametime / added_frames / block_draw_ratio;
\r
875 added_frametime = 0.0;
\r
878 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
879 float wanted_frametime = 1.0 / wanted_fps;
\r
881 f32 wanted_frametime_change = wanted_frametime - frametime;
\r
882 //dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
\r
884 // If needed frametime change is very small, just return
\r
885 if(fabs(wanted_frametime_change) < wanted_frametime*0.2)
\r
887 //dstream<<"ignoring small wanted_frametime_change"<<std::endl;
\r
891 float range = draw_control.wanted_range;
\r
892 float new_range = range;
\r
894 static s16 range_old = 0;
\r
895 static f32 frametime_old = 0;
\r
897 float d_range = range - range_old;
\r
898 f32 d_frametime = frametime - frametime_old;
\r
899 // A sane default of 30ms per 50 nodes of range
\r
900 static f32 time_per_range = 30. / 50;
\r
903 time_per_range = d_frametime / d_range;
\r
906 // The minimum allowed calculated frametime-range derivative:
\r
907 // Practically this sets the maximum speed of changing the range.
\r
908 // The lower this value, the higher the maximum changing speed.
\r
909 // A low value here results in wobbly range (0.001)
\r
910 // A high value here results in slow changing range (0.0025)
\r
911 // SUGG: This could be dynamically adjusted so that when
\r
912 // the camera is turning, this is lower
\r
913 //float min_time_per_range = 0.0015;
\r
914 float min_time_per_range = 0.0010;
\r
915 //float min_time_per_range = 0.05 / range;
\r
916 if(time_per_range < min_time_per_range)
\r
918 time_per_range = min_time_per_range;
\r
919 //dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
\r
923 //dstream<<"time_per_range="<<time_per_range<<std::endl;
\r
926 f32 wanted_range_change = wanted_frametime_change / time_per_range;
\r
927 // Dampen the change a bit to kill oscillations
\r
928 //wanted_range_change *= 0.9;
\r
929 //wanted_range_change *= 0.75;
\r
930 wanted_range_change *= 0.5;
\r
931 //dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
\r
933 // If needed range change is very small, just return
\r
934 if(fabs(wanted_range_change) < 0.001)
\r
936 //dstream<<"ignoring small wanted_range_change"<<std::endl;
\r
940 new_range += wanted_range_change;
\r
941 //dstream<<"new_range="<<new_range/*<<std::endl*/;
\r
943 //float new_range_unclamped = new_range;
\r
944 if(new_range < range_min)
\r
945 new_range = range_min;
\r
946 if(new_range > range_max)
\r
947 new_range = range_max;
\r
949 /*if(new_range != new_range_unclamped)
\r
950 dstream<<", clamped to "<<new_range<<std::endl;
\r
952 dstream<<std::endl;*/
\r
954 draw_control.wanted_range = new_range;
\r
956 range_old = new_range;
\r
957 frametime_old = frametime;
\r
960 class GUIQuickInventory : public IEventReceiver
\r
964 gui::IGUIEnvironment* env,
\r
965 gui::IGUIElement* parent,
\r
968 Inventory *inventory):
\r
969 m_itemcount(itemcount),
\r
970 m_inventory(inventory)
\r
972 core::rect<s32> imgsize(0,0,48,48);
\r
973 core::rect<s32> textsize(0,0,48,16);
\r
974 v2s32 spacing(0, 64);
\r
975 for(s32 i=0; i<m_itemcount; i++)
\r
977 m_images.push_back(env->addImage(
\r
978 imgsize + pos + spacing*i
\r
980 m_images[i]->setScaleImage(true);
\r
981 m_texts.push_back(env->addStaticText(
\r
983 textsize + pos + spacing*i,
\r
986 m_texts[i]->setBackgroundColor(
\r
987 video::SColor(128,0,0,0));
\r
988 m_texts[i]->setTextAlignment(
\r
990 gui::EGUIA_UPPERLEFT);
\r
994 virtual bool OnEvent(const SEvent& event)
\r
999 void setSelection(s32 i)
\r
1008 start = m_selection - m_itemcount / 2;
\r
1010 InventoryList *mainlist = m_inventory->getList("main");
\r
1012 for(s32 i=0; i<m_itemcount; i++)
\r
1014 s32 j = i + start;
\r
1016 if(j > (s32)mainlist->getSize() - 1)
\r
1017 j -= mainlist->getSize();
\r
1019 j += mainlist->getSize();
\r
1021 InventoryItem *item = mainlist->getItem(j);
\r
1025 m_images[i]->setImage(NULL);
\r
1028 if(m_selection == j)
\r
1029 swprintf(t, 10, L"<-");
\r
1031 swprintf(t, 10, L"");
\r
1032 m_texts[i]->setText(t);
\r
1034 // The next ifs will segfault with a NULL pointer
\r
1039 m_images[i]->setImage(item->getImage());
\r
1042 if(m_selection == j)
\r
1043 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1045 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1046 m_texts[i]->setText(t);
\r
1052 core::array<gui::IGUIStaticText*> m_texts;
\r
1053 core::array<gui::IGUIImage*> m_images;
\r
1054 Inventory *m_inventory;
\r
1065 ChatLine(const std::wstring &a_text):
\r
1071 std::wstring text;
\r
1074 int main(int argc, char *argv[])
\r
1077 Low-level initialization
\r
1080 bool disable_stderr = false;
\r
1082 disable_stderr = true;
\r
1085 // Initialize debug streams
\r
1086 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1087 // Initialize debug stacks
\r
1088 debug_stacks_init();
\r
1090 DSTACK(__FUNCTION_NAME);
\r
1092 initializeMaterialProperties();
\r
1094 BEGIN_DEBUG_EXCEPTION_HANDLER
\r
1100 Parse command line
\r
1103 // List all allowed options
\r
1104 core::map<std::string, ValueSpec> allowed_options;
\r
1105 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1106 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1107 "Run server directly"));
\r
1108 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1109 "Load configuration from specified file"));
\r
1110 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1111 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1112 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1113 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1114 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1116 Settings cmd_args;
\r
1118 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1120 if(ret == false || cmd_args.getFlag("help"))
\r
1122 dstream<<"Allowed options:"<<std::endl;
\r
1123 for(core::map<std::string, ValueSpec>::Iterator
\r
1124 i = allowed_options.getIterator();
\r
1125 i.atEnd() == false; i++)
\r
1127 dstream<<" --"<<i.getNode()->getKey();
\r
1128 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1133 dstream<<" <value>";
\r
1135 dstream<<std::endl;
\r
1137 if(i.getNode()->getValue().help != NULL)
\r
1139 dstream<<" "<<i.getNode()->getValue().help
\r
1144 return cmd_args.getFlag("help") ? 0 : 1;
\r
1149 Basic initialization
\r
1152 // Initialize default settings
\r
1153 set_default_settings();
\r
1155 // Print startup message
\r
1156 dstream<<DTIME<<"minetest-c55"
\r
1157 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1158 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1161 // Set locale. This is for forcing '.' as the decimal point.
\r
1162 std::locale::global(std::locale("C"));
\r
1163 // This enables printing all characters in bitmap font
\r
1164 setlocale(LC_CTYPE, "en_US");
\r
1166 // Initialize sockets
\r
1168 atexit(sockets_cleanup);
\r
1178 // Path of configuration file in use
\r
1179 std::string configpath = "";
\r
1181 if(cmd_args.exists("config"))
\r
1183 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1186 dstream<<"Could not read configuration from \""
\r
1187 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1190 configpath = cmd_args.get("config");
\r
1194 const char *filenames[2] =
\r
1196 "../minetest.conf",
\r
1197 "../../minetest.conf"
\r
1200 for(u32 i=0; i<2; i++)
\r
1202 bool r = g_settings.readConfigFile(filenames[i]);
\r
1205 configpath = filenames[i];
\r
1211 // Initialize random seed
\r
1218 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1219 || cmd_args.getFlag("enable-unittests") == true)
\r
1224 // Read map parameters from settings
\r
1226 HMParams hm_params;
\r
1227 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1228 hm_params.randmax = g_settings.get("height_randmax");
\r
1229 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1230 hm_params.base = g_settings.get("height_base");
\r
1232 MapParams map_params;
\r
1233 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1234 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1240 std::cout<<std::endl<<std::endl;
\r
1243 <<" .__ __ __ "<<std::endl
\r
1244 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1245 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1246 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1247 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1248 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1251 std::cout<<std::endl;
\r
1252 //char templine[100];
\r
1256 if(cmd_args.exists("port"))
\r
1258 port = cmd_args.getU16("port");
\r
1262 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1263 std::cout<<"-> "<<port<<std::endl;
\r
1266 if(cmd_args.getFlag("server"))
\r
1268 DSTACK("Dedicated server branch");
\r
1270 std::cout<<std::endl;
\r
1271 std::cout<<"========================"<<std::endl;
\r
1272 std::cout<<"Running dedicated server"<<std::endl;
\r
1273 std::cout<<"========================"<<std::endl;
\r
1274 std::cout<<std::endl;
\r
1276 Server server("../map", hm_params, map_params);
\r
1277 server.start(port);
\r
1281 // This is kind of a hack but can be done like this
\r
1282 // because server.step() is very light
\r
1284 server.step(0.030);
\r
1286 static int counter = 0;
\r
1292 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1293 core::list<PlayerInfo>::Iterator i;
\r
1294 static u32 sum_old = 0;
\r
1295 u32 sum = PIChecksum(list);
\r
1296 if(sum != sum_old)
\r
1298 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1299 for(i=list.begin(); i!=list.end(); i++)
\r
1301 i->PrintLine(&std::cout);
\r
1311 bool hosting = false;
\r
1312 char connect_name[100] = "";
\r
1314 if(cmd_args.exists("address"))
\r
1316 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1318 else if(is_yes(g_settings.get("host_game")) == false)
\r
1320 if(g_settings.get("address") != "")
\r
1322 std::cout<<g_settings.get("address")<<std::endl;
\r
1323 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1327 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1328 std::cin.getline(connect_name, 100);
\r
1332 if(connect_name[0] == 0){
\r
1333 snprintf(connect_name, 100, "127.0.0.1");
\r
1338 std::cout<<"> Hosting game"<<std::endl;
\r
1340 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1342 char playername[PLAYERNAME_SIZE] = "";
\r
1343 if(g_settings.get("name") != "")
\r
1345 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1349 std::cout<<"Name of player: ";
\r
1350 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1352 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1355 Resolution selection
\r
1358 bool fullscreen = false;
\r
1359 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1360 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1364 MyEventReceiver receiver;
\r
1366 video::E_DRIVER_TYPE driverType;
\r
1369 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1370 driverType = video::EDT_OPENGL;
\r
1372 driverType = video::EDT_OPENGL;
\r
1375 // create device and exit if creation failed
\r
1377 IrrlichtDevice *device;
\r
1378 device = createDevice(driverType,
\r
1379 core::dimension2d<u32>(screenW, screenH),
\r
1380 16, fullscreen, false, false, &receiver);
\r
1383 return 1; // could not create selected driver.
\r
1385 g_device = device;
\r
1386 g_irrlicht = new IrrlichtWrapper(device);
\r
1388 //g_device = device;
\r
1390 device->setResizable(true);
\r
1392 bool random_input = g_settings.getBool("random_input")
\r
1393 || cmd_args.getFlag("random-input");
\r
1395 g_input = new RandomInputHandler();
\r
1397 g_input = new RealInputHandler(device, &receiver);
\r
1400 Continue initialization
\r
1403 video::IVideoDriver* driver = device->getVideoDriver();
\r
1406 This changes the minimum allowed number of vertices in a VBO
\r
1408 //driver->setMinHardwareBufferVertexCount(50);
\r
1410 scene::ISceneManager* smgr = device->getSceneManager();
\r
1412 guienv = device->getGUIEnvironment();
\r
1413 gui::IGUISkin* skin = guienv->getSkin();
\r
1414 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1416 skin->setFont(font);
\r
1418 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1419 dstream<<"text_height="<<text_height<<std::endl;
\r
1421 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1422 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1423 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1424 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1425 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1426 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1428 const wchar_t *text = L"Loading and connecting...";
\r
1429 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1430 core::vector2d<s32> textsize(300, text_height);
\r
1431 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1433 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1434 text, textrect, false, false);
\r
1435 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1437 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1438 guienv->drawAll();
\r
1439 driver->endScene();
\r
1442 Preload some textures
\r
1445 tile_materials_preload(g_irrlicht);
\r
1448 Make a scope here for the client so that it gets removed
\r
1449 before the irrlicht device
\r
1453 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1458 SharedPtr<Server> server;
\r
1460 server = new Server("../map", hm_params, map_params);
\r
1461 server->start(port);
\r
1468 Client client(device, playername, draw_control);
\r
1470 g_client = &client;
\r
1472 Address connect_address(0,0,0,0, port);
\r
1474 connect_address.Resolve(connect_name);
\r
1476 catch(ResolveError &e)
\r
1478 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1482 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1483 client.connect(connect_address);
\r
1486 while(client.connectedAndInitialized() == false)
\r
1489 if(server != NULL){
\r
1490 server->step(0.1);
\r
1495 catch(con::PeerNotFoundException &e)
\r
1497 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1504 /*scene::ISceneNode* skybox;
\r
1505 skybox = smgr->addSkyBoxSceneNode(
\r
1506 driver->getTexture("../data/skybox2.png"),
\r
1507 driver->getTexture("../data/skybox3.png"),
\r
1508 driver->getTexture("../data/skybox1.png"),
\r
1509 driver->getTexture("../data/skybox1.png"),
\r
1510 driver->getTexture("../data/skybox1.png"),
\r
1511 driver->getTexture("../data/skybox1.png"));*/
\r
1514 Create the camera node
\r
1517 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1518 0, // Camera parent
\r
1519 v3f(BS*100, BS*2, BS*100), // Look from
\r
1520 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1524 if(camera == NULL)
\r
1527 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1529 camera->setFOV(FOV_ANGLE);
\r
1531 // Just so big a value that everything rendered is visible
\r
1532 camera->setFarValue(100000*BS);
\r
1534 f32 camera_yaw = 0; // "right/left"
\r
1535 f32 camera_pitch = 0; // "up/down"
\r
1541 gui_loadingtext->remove();
\r
1544 Add some gui stuff
\r
1547 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1548 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1551 We need some kind of a root node to be able to add
\r
1552 custom elements directly on the screen.
\r
1553 Otherwise they won't be automatically drawn.
\r
1555 guiroot = guienv->addStaticText(L"",
\r
1556 core::rect<s32>(0, 0, 10000, 10000));
\r
1558 // Test the text input system
\r
1559 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1561 /*GUIMessageMenu *menu =
\r
1562 new GUIMessageMenu(guienv, guiroot, -1,
\r
1563 &g_active_menu_count,
\r
1567 // Launch pause menu
\r
1568 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1569 &g_active_menu_count))->drop();
\r
1571 // First line of debug text
\r
1572 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1574 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1576 // Second line of debug text
\r
1577 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1579 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1582 // At the middle of the screen
\r
1583 // Object infos are shown in this
\r
1584 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1586 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1590 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1591 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1592 core::rect<s32>(70, 60, 795, 150),
\r
1594 chat_guitext->setBackgroundColor(video::SColor(96,0,0,0));
\r
1595 core::list<ChatLine> chat_lines;
\r
1598 Some statistics are collected in these
\r
1601 u32 beginscenetime = 0;
\r
1602 u32 scenetime = 0;
\r
1603 u32 endscenetime = 0;
\r
1606 //throw con::PeerNotFoundException("lol");
\r
1612 bool first_loop_after_window_activation = true;
\r
1614 // Time is in milliseconds
\r
1615 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1616 // NOTE: Have to call run() between calls of this to update the timer
\r
1617 u32 lasttime = device->getTimer()->getTime();
\r
1619 while(device->run())
\r
1622 Run global IrrlichtWrapper's main thread processing stuff
\r
1624 g_irrlicht->Run();
\r
1627 Random calculations
\r
1629 v2u32 screensize = driver->getScreenSize();
\r
1630 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1632 // Hilight boxes collected during the loop and displayed
\r
1633 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1636 std::wstring infotext;
\r
1638 //TimeTaker //timer1("//timer1");
\r
1640 // Time of frame without fps limit
\r
1644 // not using getRealTime is necessary for wine
\r
1645 u32 time = device->getTimer()->getTime();
\r
1646 if(time > lasttime)
\r
1647 busytime_u32 = time - lasttime;
\r
1650 busytime = busytime_u32 / 1000.0;
\r
1653 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1655 // Absolutelu necessary for wine!
\r
1662 updateViewingRange(busytime, &client);
\r
1669 float fps_max = g_settings.getFloat("fps_max");
\r
1670 u32 frametime_min = 1000./fps_max;
\r
1672 if(busytime_u32 < frametime_min)
\r
1674 u32 sleeptime = frametime_min - busytime_u32;
\r
1675 device->sleep(sleeptime);
\r
1679 // Absolutelu necessary for wine!
\r
1683 Time difference calculation
\r
1685 f32 dtime; // in seconds
\r
1687 u32 time = device->getTimer()->getTime();
\r
1688 if(time > lasttime)
\r
1689 dtime = (time - lasttime) / 1000.0;
\r
1695 Time average and jitter calculation
\r
1698 static f32 dtime_avg1 = 0.0;
\r
1699 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1700 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1702 static f32 dtime_jitter1_max_sample = 0.0;
\r
1703 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1705 static f32 jitter1_max = 0.0;
\r
1706 static f32 counter = 0.0;
\r
1707 if(dtime_jitter1 > jitter1_max)
\r
1708 jitter1_max = dtime_jitter1;
\r
1713 dtime_jitter1_max_sample = jitter1_max;
\r
1714 dtime_jitter1_max_fraction
\r
1715 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1716 jitter1_max = 0.0;
\r
1719 Control freetime ratio
\r
1721 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1723 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1724 g_freetime_ratio += 0.01;
\r
1728 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1729 g_freetime_ratio -= 0.01;
\r
1735 Busytime average and jitter calculation
\r
1738 static f32 busytime_avg1 = 0.0;
\r
1739 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1740 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1742 static f32 busytime_jitter1_max_sample = 0.0;
\r
1743 static f32 busytime_jitter1_min_sample = 0.0;
\r
1745 static f32 jitter1_max = 0.0;
\r
1746 static f32 jitter1_min = 0.0;
\r
1747 static f32 counter = 0.0;
\r
1748 if(busytime_jitter1 > jitter1_max)
\r
1749 jitter1_max = busytime_jitter1;
\r
1750 if(busytime_jitter1 < jitter1_min)
\r
1751 jitter1_min = busytime_jitter1;
\r
1753 if(counter > 0.0){
\r
1755 busytime_jitter1_max_sample = jitter1_max;
\r
1756 busytime_jitter1_min_sample = jitter1_min;
\r
1757 jitter1_max = 0.0;
\r
1758 jitter1_min = 0.0;
\r
1763 Debug info for client
\r
1766 static float counter = 0.0;
\r
1771 client.printDebugInfo(std::cout);
\r
1776 Input handler step()
\r
1778 g_input->step(dtime);
\r
1781 Player speed control
\r
1790 bool a_superspeed,
\r
1793 PlayerControl control(
\r
1794 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1795 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1796 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1797 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1798 g_input->isKeyDown(irr::KEY_SPACE),
\r
1799 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1803 client.setPlayerControl(control);
\r
1807 Process environment
\r
1811 //TimeTaker timer("client.step(dtime)");
\r
1812 client.step(dtime);
\r
1813 //client.step(dtime_avg1);
\r
1816 if(server != NULL)
\r
1818 //TimeTaker timer("server->step(dtime)");
\r
1819 server->step(dtime);
\r
1822 v3f player_position = client.getPlayerPosition();
\r
1824 //TimeTaker //timer2("//timer2");
\r
1827 Mouse and camera control
\r
1830 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1833 device->getCursorControl()->setVisible(false);
\r
1835 if(first_loop_after_window_activation){
\r
1836 //std::cout<<"window active, first loop"<<std::endl;
\r
1837 first_loop_after_window_activation = false;
\r
1840 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1841 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1842 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1843 camera_yaw -= dx*0.2;
\r
1844 camera_pitch += dy*0.2;
\r
1845 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1846 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1848 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1851 device->getCursorControl()->setVisible(true);
\r
1853 //std::cout<<"window inactive"<<std::endl;
\r
1854 first_loop_after_window_activation = true;
\r
1857 camera_yaw = wrapDegrees(camera_yaw);
\r
1858 camera_pitch = wrapDegrees(camera_pitch);
\r
1860 v3f camera_direction = v3f(0,0,1);
\r
1861 camera_direction.rotateYZBy(camera_pitch);
\r
1862 camera_direction.rotateXZBy(camera_yaw);
\r
1864 v3f camera_position =
\r
1865 player_position + v3f(0, BS+BS/2, 0);
\r
1867 camera->setPosition(camera_position);
\r
1868 // *100.0 helps in large map coordinates
\r
1869 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1871 if(FIELD_OF_VIEW_TEST){
\r
1872 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1873 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1876 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1877 //TimeTaker timer("client.updateCamera");
\r
1878 client.updateCamera(camera_position, camera_direction);
\r
1882 //TimeTaker //timer3("//timer3");
\r
1885 Calculate what block is the crosshair pointing to
\r
1888 //u32 t1 = device->getTimer()->getRealTime();
\r
1890 //f32 d = 4; // max. distance
\r
1891 f32 d = 4; // max. distance
\r
1892 core::line3d<f32> shootline(camera_position,
\r
1893 camera_position + camera_direction * BS * (d+1));
\r
1895 MapBlockObject *selected_object = client.getSelectedObject
\r
1896 (d*BS, camera_position, shootline);
\r
1899 If it's pointing to a MapBlockObject
\r
1902 if(selected_object != NULL)
\r
1904 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1906 core::aabbox3d<f32> box_on_map
\r
1907 = selected_object->getSelectionBoxOnMap();
\r
1909 hilightboxes.push_back(box_on_map);
\r
1911 infotext = narrow_to_wide(selected_object->infoText());
\r
1913 if(g_input->getLeftClicked())
\r
1915 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1916 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1917 selected_object->getId(), g_selected_item);
\r
1919 else if(g_input->getRightClicked())
\r
1921 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1923 Check if we want to modify the object ourselves
\r
1925 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1927 dstream<<"Sign object right-clicked"<<std::endl;
\r
1929 if(random_input == false)
\r
1931 // Get a new text for it
\r
1933 TextDest *dest = new TextDestSign(
\r
1934 selected_object->getBlock()->getPos(),
\r
1935 selected_object->getId(),
\r
1938 SignObject *sign_object = (SignObject*)selected_object;
\r
1940 std::wstring wtext =
\r
1941 narrow_to_wide(sign_object->getText());
\r
1943 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1944 &g_active_menu_count, dest,
\r
1949 Otherwise pass the event to the server as-is
\r
1953 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1954 selected_object->getId(), g_selected_item);
\r
1958 else // selected_object == NULL
\r
1962 Find out which node we are pointing at
\r
1965 bool nodefound = false;
\r
1967 v3s16 neighbourpos;
\r
1968 core::aabbox3d<f32> nodefacebox;
\r
1969 f32 mindistance = BS * 1001;
\r
1971 v3s16 pos_i = floatToInt(player_position);
\r
1973 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1977 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1978 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1979 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1980 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1981 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1982 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1984 for(s16 y = ystart; y <= yend; y++)
\r
1985 for(s16 z = zstart; z <= zend; z++)
\r
1986 for(s16 x = xstart; x <= xend; x++)
\r
1991 n = client.getNode(v3s16(x,y,z));
\r
1992 if(content_pointable(n.d) == false)
\r
1995 catch(InvalidPositionException &e)
\r
2001 v3f npf = intToFloat(np);
\r
2006 v3s16(0,0,1), // back
\r
2007 v3s16(0,1,0), // top
\r
2008 v3s16(1,0,0), // right
\r
2009 v3s16(0,0,-1), // front
\r
2010 v3s16(0,-1,0), // bottom
\r
2011 v3s16(-1,0,0), // left
\r
2017 if(n.d == CONTENT_TORCH)
\r
2019 v3s16 dir = unpackDir(n.dir);
\r
2020 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2021 dir_f *= BS/2 - BS/6 - BS/20;
\r
2022 v3f cpf = npf + dir_f;
\r
2023 f32 distance = (cpf - camera_position).getLength();
\r
2025 core::aabbox3d<f32> box;
\r
2028 if(dir == v3s16(0,-1,0))
\r
2030 box = core::aabbox3d<f32>(
\r
2031 npf - v3f(BS/6, BS/2, BS/6),
\r
2032 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2036 else if(dir == v3s16(0,1,0))
\r
2038 box = core::aabbox3d<f32>(
\r
2039 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2040 npf + v3f(BS/6, BS/2, BS/6)
\r
2046 box = core::aabbox3d<f32>(
\r
2047 cpf - v3f(BS/6, BS/3, BS/6),
\r
2048 cpf + v3f(BS/6, BS/3, BS/6)
\r
2052 if(distance < mindistance)
\r
2054 if(box.intersectsWithLine(shootline))
\r
2058 neighbourpos = np;
\r
2059 mindistance = distance;
\r
2060 nodefacebox = box;
\r
2069 for(u16 i=0; i<6; i++)
\r
2071 v3f dir_f = v3f(dirs[i].X,
\r
2072 dirs[i].Y, dirs[i].Z);
\r
2073 v3f centerpoint = npf + dir_f * BS/2;
\r
2075 (centerpoint - camera_position).getLength();
\r
2077 if(distance < mindistance)
\r
2079 core::CMatrix4<f32> m;
\r
2080 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2082 // This is the back face
\r
2083 v3f corners[2] = {
\r
2084 v3f(BS/2, BS/2, BS/2),
\r
2085 v3f(-BS/2, -BS/2, BS/2+d)
\r
2088 for(u16 j=0; j<2; j++)
\r
2090 m.rotateVect(corners[j]);
\r
2091 corners[j] += npf;
\r
2094 core::aabbox3d<f32> facebox(corners[0]);
\r
2095 facebox.addInternalPoint(corners[1]);
\r
2097 if(facebox.intersectsWithLine(shootline))
\r
2101 neighbourpos = np + dirs[i];
\r
2102 mindistance = distance;
\r
2103 nodefacebox = facebox;
\r
2105 } // if distance < mindistance
\r
2107 } // regular block
\r
2110 static float nodig_delay_counter = 0.0;
\r
2114 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2116 static float dig_time = 0.0;
\r
2117 static u16 dig_index = 0;
\r
2119 hilightboxes.push_back(nodefacebox);
\r
2121 if(g_input->getLeftReleased())
\r
2123 client.clearTempMod(nodepos);
\r
2127 if(nodig_delay_counter > 0.0)
\r
2129 nodig_delay_counter -= dtime;
\r
2133 if(nodepos != nodepos_old)
\r
2135 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2136 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2138 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2140 client.clearTempMod(nodepos_old);
\r
2145 if(g_input->getLeftClicked() ||
\r
2146 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2148 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2149 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2151 if(g_input->getLeftClicked())
\r
2153 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2155 if(g_input->getLeftState())
\r
2157 MapNode n = client.getNode(nodepos);
\r
2159 // Get tool name. Default is "" = bare hands
\r
2160 std::string toolname = "";
\r
2161 InventoryList *mlist = local_inventory.getList("main");
\r
2164 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2165 if(item && (std::string)item->getName() == "ToolItem")
\r
2167 ToolItem *titem = (ToolItem*)item;
\r
2168 toolname = titem->getToolName();
\r
2172 // Get digging properties for material and tool
\r
2173 u8 material = n.d;
\r
2174 DiggingProperties prop =
\r
2175 getDiggingProperties(material, toolname);
\r
2177 float dig_time_complete = 0.0;
\r
2179 if(prop.diggable == false)
\r
2181 /*dstream<<"Material "<<(int)material
\r
2182 <<" not diggable with \""
\r
2183 <<toolname<<"\""<<std::endl;*/
\r
2184 // I guess nobody will wait for this long
\r
2185 dig_time_complete = 10000000.0;
\r
2189 dig_time_complete = prop.time;
\r
2192 if(dig_time_complete >= 0.001)
\r
2194 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2195 * dig_time/dig_time_complete);
\r
2197 // This is for torches
\r
2200 dig_index = CRACK_ANIMATION_LENGTH;
\r
2203 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2205 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2206 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2210 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2211 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2212 client.clearTempMod(nodepos);
\r
2213 client.removeNode(nodepos);
\r
2217 nodig_delay_counter = dig_time_complete
\r
2218 / (float)CRACK_ANIMATION_LENGTH;
\r
2220 // We don't want a corresponding delay to
\r
2221 // very time consuming nodes
\r
2222 if(nodig_delay_counter > 0.5)
\r
2224 nodig_delay_counter = 0.5;
\r
2226 // We want a slight delay to very little
\r
2227 // time consuming nodes
\r
2228 //float mindelay = 0.15;
\r
2229 float mindelay = 0.20;
\r
2230 if(nodig_delay_counter < mindelay)
\r
2232 nodig_delay_counter = mindelay;
\r
2236 dig_time += dtime;
\r
2240 if(g_input->getRightClicked())
\r
2242 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2243 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2246 nodepos_old = nodepos;
\r
2251 } // selected_object == NULL
\r
2253 g_input->resetLeftClicked();
\r
2254 g_input->resetRightClicked();
\r
2256 if(g_input->getLeftReleased())
\r
2258 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2260 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2262 if(g_input->getRightReleased())
\r
2264 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2268 g_input->resetLeftReleased();
\r
2269 g_input->resetRightReleased();
\r
2272 Calculate stuff for drawing
\r
2275 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2277 u32 daynight_ratio = client.getDayNightRatio();
\r
2278 video::SColor bgcolor = video::SColor(
\r
2280 skycolor.getRed() * daynight_ratio / 1000,
\r
2281 skycolor.getGreen() * daynight_ratio / 1000,
\r
2282 skycolor.getBlue() * daynight_ratio / 1000);
\r
2288 if(g_settings.getBool("enable_fog") == true)
\r
2290 f32 range = draw_control.wanted_range * BS;
\r
2291 if(draw_control.range_all)
\r
2292 range = 100000*BS;
\r
2296 video::EFT_FOG_LINEAR,
\r
2300 false, // pixel fog
\r
2301 false // range fog
\r
2307 Update gui stuff (0ms)
\r
2310 //TimeTaker guiupdatetimer("Gui updating");
\r
2313 wchar_t temptext[150];
\r
2315 static float drawtime_avg = 0;
\r
2316 drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
\r
2317 static float beginscenetime_avg = 0;
\r
2318 beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
\r
2319 static float scenetime_avg = 0;
\r
2320 scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
\r
2321 static float endscenetime_avg = 0;
\r
2322 endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;
\r
2324 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2326 L", R: range_all=%i"
\r
2328 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2330 draw_control.range_all,
\r
2332 beginscenetime_avg,
\r
2337 guitext->setText(temptext);
\r
2341 wchar_t temptext[150];
\r
2342 swprintf(temptext, 150,
\r
2343 L"(% .1f, % .1f, % .1f)"
\r
2344 L" (% .3f < btime_jitter < % .3f"
\r
2345 L", dtime_jitter = % .1f %%"
\r
2346 L", v_range = %.1f)",
\r
2347 player_position.X/BS,
\r
2348 player_position.Y/BS,
\r
2349 player_position.Z/BS,
\r
2350 busytime_jitter1_min_sample,
\r
2351 busytime_jitter1_max_sample,
\r
2352 dtime_jitter1_max_fraction * 100.0,
\r
2353 draw_control.wanted_range
\r
2356 guitext2->setText(temptext);
\r
2360 guitext_info->setText(infotext.c_str());
\r
2364 Get chat messages from client
\r
2367 // Get new messages
\r
2368 std::wstring message;
\r
2369 while(client.getChatMessage(message))
\r
2371 chat_lines.push_back(ChatLine(message));
\r
2372 /*if(chat_lines.size() > 6)
\r
2374 core::list<ChatLine>::Iterator
\r
2375 i = chat_lines.begin();
\r
2376 chat_lines.erase(i);
\r
2379 // Append them to form the whole static text and throw
\r
2380 // it to the gui element
\r
2381 std::wstring whole;
\r
2382 // This will correspond to the line number counted from
\r
2383 // top to bottom, from size-1 to 0
\r
2384 s16 line_number = chat_lines.size();
\r
2385 // Count of messages to be removed from the top
\r
2386 u16 to_be_removed_count = 0;
\r
2387 for(core::list<ChatLine>::Iterator
\r
2388 i = chat_lines.begin();
\r
2389 i != chat_lines.end(); i++)
\r
2391 // After this, line number is valid for this loop
\r
2394 (*i).age += dtime;
\r
2396 This results in a maximum age of 60*6 to the
\r
2397 lowermost line and a maximum of 6 lines
\r
2399 float allowed_age = (6-line_number) * 60.0;
\r
2401 if((*i).age > allowed_age)
\r
2403 to_be_removed_count++;
\r
2406 whole += (*i).text + L'\n';
\r
2408 for(u16 i=0; i<to_be_removed_count; i++)
\r
2410 core::list<ChatLine>::Iterator
\r
2411 it = chat_lines.begin();
\r
2412 chat_lines.erase(it);
\r
2414 chat_guitext->setText(whole.c_str());
\r
2415 // Update gui element size and position
\r
2416 core::rect<s32> rect(
\r
2418 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2419 screensize.X - 10,
\r
2422 chat_guitext->setRelativePosition(rect);
\r
2424 if(chat_lines.size() == 0)
\r
2425 chat_guitext->setVisible(false);
\r
2427 chat_guitext->setVisible(true);
\r
2434 static u16 old_selected_item = 65535;
\r
2435 if(client.getLocalInventoryUpdated()
\r
2436 || g_selected_item != old_selected_item)
\r
2438 old_selected_item = g_selected_item;
\r
2439 //std::cout<<"Updating local inventory"<<std::endl;
\r
2440 client.getLocalInventory(local_inventory);
\r
2441 quick_inventory->setSelection(g_selected_item);
\r
2442 quick_inventory->update();
\r
2446 Send actions returned by the inventory menu
\r
2448 while(inventory_action_queue.size() != 0)
\r
2450 InventoryAction *a = inventory_action_queue.pop_front();
\r
2452 client.sendInventoryAction(a);
\r
2461 TimeTaker drawtimer("Drawing");
\r
2465 TimeTaker timer("beginScene");
\r
2466 driver->beginScene(true, true, bgcolor);
\r
2467 //driver->beginScene(false, true, bgcolor);
\r
2468 beginscenetime = timer.stop(true);
\r
2473 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2476 TimeTaker timer("smgr");
\r
2478 scenetime = timer.stop(true);
\r
2482 //TimeTaker timer9("auxiliary drawings");
\r
2486 //TimeTaker //timer10("//timer10");
\r
2488 video::SMaterial m;
\r
2490 m.Lighting = false;
\r
2491 driver->setMaterial(m);
\r
2493 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2495 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2496 i != hilightboxes.end(); i++)
\r
2498 /*std::cout<<"hilightbox min="
\r
2499 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2501 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2503 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2509 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2510 displaycenter + core::vector2d<s32>(10,0),
\r
2511 video::SColor(255,255,255,255));
\r
2512 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2513 displaycenter + core::vector2d<s32>(0,10),
\r
2514 video::SColor(255,255,255,255));
\r
2519 //TimeTaker //timer11("//timer11");
\r
2525 guienv->drawAll();
\r
2529 TimeTaker timer("endScene");
\r
2530 driver->endScene();
\r
2531 endscenetime = timer.stop(true);
\r
2534 drawtime = drawtimer.stop(true);
\r
2540 static s16 lastFPS = 0;
\r
2541 //u16 fps = driver->getFPS();
\r
2542 u16 fps = (1.0/dtime_avg1);
\r
2544 if (lastFPS != fps)
\r
2546 core::stringw str = L"Minetest [";
\r
2547 str += driver->getName();
\r
2551 device->setWindowCaption(str.c_str());
\r
2557 device->yield();*/
\r
2560 delete quick_inventory;
\r
2562 } // client is deleted at this point
\r
2567 In the end, delete the Irrlicht device.
\r
2572 Update configuration file
\r
2574 /*if(configpath != "")
\r
2576 g_settings.updateConfigFile(configpath.c_str());
\r
2580 catch(con::PeerNotFoundException &e)
\r
2582 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2586 GUIMessageMenu *menu =
\r
2587 new GUIMessageMenu(guienv, guiroot, -1,
\r
2588 &g_active_menu_count,
\r
2589 L"Connection timed out");
\r
2591 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
2593 dstream<<"Created menu"<<std::endl;
\r
2595 while(g_device->run() && menu->getStatus() == false)
\r
2597 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2598 guienv->drawAll();
\r
2599 driver->endScene();
\r
2602 dstream<<"Dropping menu"<<std::endl;
\r
2608 END_DEBUG_EXCEPTION_HANDLER
\r
2610 debugstreams_deinit();
\r