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 Block object server side:
\r
139 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
140 - For all blocks in the buffer, objects are stepped(). This
\r
141 means they are active.
\r
142 - TODO: A global active buffer is needed for the server
\r
143 - TODO: A timestamp to blocks
\r
144 - TODO: All blocks going in and out of the buffer are recorded.
\r
145 - TODO: For outgoing blocks, timestamp is written.
\r
146 - TODO: For incoming blocks, time difference is calculated and
\r
147 objects are stepped according to it.
\r
149 TODO: Copy the text of the last picked sign to inventory in creative
\r
152 TODO: Get rid of GotSplitPacketException
\r
154 TODO: Check what goes wrong with caching map to disk (Kray)
\r
156 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
158 TODO: TOSERVER_LEAVE
\r
160 TODO: Better handling of objects and mobs
\r
162 - There has to be some way to do it with less spaghetti code
\r
163 - Make separate classes for client and server
\r
164 - Client should not discriminate between blocks, server should
\r
165 - Make other players utilize the same framework
\r
166 - This is also needed for objects that don't get sent to client
\r
167 but are used for triggers etc
\r
169 TODO: Draw big amounts of torches better (that is, throw them in the
\r
170 same meshbuffer (can the meshcollector class be used?))
\r
172 TODO: Check if the usage of Client::isFetchingBlocks() in
\r
173 updateViewingRange() actually does something
\r
175 TODO: Make an option to the server to disable building and digging near
\r
176 the starting position
\r
178 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
179 need an additional metadata field for the texts
\r
180 - This is also needed for item container chests
\r
181 TODO: There has to be some better way to handle static objects than to
\r
182 send them all the time. This affects signs and item objects.
\r
185 ======================================================================
\r
187 TODO: When server sees that client is removing an inexistent block or
\r
188 adding a block to an existent position, resend the MapBlock.
\r
190 TODO: Fix viewing range updater's oscillation when there is large non-
\r
191 linearity in range-speed relation
\r
193 ======================================================================
\r
198 Setting this to 1 enables a special camera mode that forces
\r
199 the renderers to think that the camera statically points from
\r
200 the starting place to a static direction.
\r
202 This allows one to move around with the player and see what
\r
203 is actually drawn behind solid things and behind the player.
\r
205 #define FIELD_OF_VIEW_TEST 0
\r
207 #ifdef UNITTEST_DISABLE
\r
209 #pragma message ("Disabling unit tests")
\r
211 #warning "Disabling unit tests"
\r
213 // Disable unit tests
\r
214 #define ENABLE_TESTS 0
\r
216 // Enable unit tests
\r
217 #define ENABLE_TESTS 1
\r
221 #pragma comment(lib, "Irrlicht.lib")
\r
222 #pragma comment(lib, "jthread.lib")
\r
223 #pragma comment(lib, "zlibwapi.lib")
\r
224 // This would get rid of the console window
\r
225 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
228 #include <iostream>
\r
230 #include <jmutexautolock.h>
\r
231 #include <locale.h>
\r
232 #include "common_irrlicht.h"
\r
235 #include "player.h"
\r
238 #include "environment.h"
\r
239 #include "server.h"
\r
240 #include "client.h"
\r
241 #include "serialization.h"
\r
242 #include "constants.h"
\r
243 #include "strfnd.h"
\r
244 #include "porting.h"
\r
245 #include "irrlichtwrapper.h"
\r
246 #include "gettime.h"
\r
247 #include "porting.h"
\r
248 #include "guiPauseMenu.h"
\r
249 #include "guiInventoryMenu.h"
\r
250 #include "guiTextInputMenu.h"
\r
251 #include "materials.h"
\r
252 #include "guiMessageMenu.h"
\r
254 IrrlichtWrapper *g_irrlicht;
\r
256 // All range-related stuff below is locked behind this
\r
257 JMutex g_range_mutex;
\r
259 // Blocks are viewed in this range from the player
\r
260 s16 g_viewing_range_nodes = 60;
\r
261 //s16 g_viewing_range_nodes = 0;
\r
263 // This is updated by the client's fetchBlocks routine
\r
264 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
266 // If true, the preceding value has no meaning and all blocks
\r
267 // already existing in memory are drawn
\r
268 bool g_viewing_range_all = false;
\r
270 // This is the freetime ratio imposed by the dynamic viewing
\r
271 // range changing code.
\r
272 // It is controlled by the main loop to the smallest value that
\r
273 // inhibits glitches (dtime jitter) in the main loop.
\r
274 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
278 These are loaded from the config file.
\r
281 Settings g_settings;
\r
283 extern void set_default_settings();
\r
289 IrrlichtDevice *g_device = NULL;
\r
290 Client *g_client = NULL;
\r
295 gui::IGUIEnvironment* guienv = NULL;
\r
296 gui::IGUIStaticText *guiroot = NULL;
\r
297 int g_active_menu_count = 0;
\r
299 bool noMenuActive()
\r
301 return (g_active_menu_count == 0);
\r
304 // Inventory actions from the menu are buffered here before sending
\r
305 Queue<InventoryAction*> inventory_action_queue;
\r
306 // This is a copy of the inventory that the client's environment has
\r
307 Inventory local_inventory;
\r
309 u16 g_selected_item = 0;
\r
316 std::ostream *dout_con_ptr = &dummyout;
\r
317 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
318 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
319 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
320 //std::ostream *dout_con_ptr = &dstream;
\r
321 //std::ostream *derr_con_ptr = &dstream;
\r
324 std::ostream *dout_server_ptr = &dstream;
\r
325 std::ostream *derr_server_ptr = &dstream;
\r
328 std::ostream *dout_client_ptr = &dstream;
\r
329 std::ostream *derr_client_ptr = &dstream;
\r
332 gettime.h implementation
\r
338 Use irrlicht because it is more precise than porting.h's
\r
341 if(g_irrlicht == NULL)
\r
343 return g_irrlicht->getTime();
\r
350 struct TextDestSign : public TextDest
\r
352 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
354 m_blockpos = blockpos;
\r
358 void gotText(std::wstring text)
\r
360 std::string ntext = wide_to_narrow(text);
\r
361 dstream<<"Changing text of a sign object: "
\r
362 <<ntext<<std::endl;
\r
363 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
371 struct TextDestChat : public TextDest
\r
373 TextDestChat(Client *client)
\r
377 void gotText(std::wstring text)
\r
379 m_client->sendChatMessage(text);
\r
380 m_client->addChatMessage(text);
\r
386 class MyEventReceiver : public IEventReceiver
\r
389 // This is the one method that we have to implement
\r
390 virtual bool OnEvent(const SEvent& event)
\r
393 React to nothing here if a menu is active
\r
395 if(noMenuActive() == false)
\r
401 // Remember whether each key is down or up
\r
402 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
404 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
406 if(event.KeyInput.PressedDown)
\r
408 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
414 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
416 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
418 dstream<<DTIME<<"MyEventReceiver: "
\r
419 <<"Launching pause menu"<<std::endl;
\r
420 // It will delete itself by itself
\r
421 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
422 &g_active_menu_count))->drop();
\r
425 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
427 dstream<<DTIME<<"MyEventReceiver: "
\r
428 <<"Launching inventory"<<std::endl;
\r
429 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
430 &local_inventory, &inventory_action_queue,
\r
431 &g_active_menu_count))->drop();
\r
434 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
436 TextDest *dest = new TextDestChat(g_client);
\r
438 (new GUITextInputMenu(guienv, guiroot, -1,
\r
439 &g_active_menu_count, dest,
\r
444 // Material selection
\r
445 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
447 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
450 g_selected_item = 0;
\r
451 dstream<<DTIME<<"Selected item: "
\r
452 <<g_selected_item<<std::endl;
\r
455 // Viewing range selection
\r
456 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
458 JMutexAutoLock lock(g_range_mutex);
\r
459 if(g_viewing_range_all)
\r
461 g_viewing_range_all = false;
\r
462 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
466 g_viewing_range_all = true;
\r
467 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
471 // Print debug stacks
\r
472 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
474 dstream<<"-----------------------------------------"
\r
476 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
477 dstream<<"-----------------------------------------"
\r
479 debug_stacks_print();
\r
484 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
486 if(noMenuActive() == false)
\r
488 left_active = false;
\r
489 middle_active = false;
\r
490 right_active = false;
\r
494 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
495 left_active = event.MouseInput.isLeftPressed();
\r
496 middle_active = event.MouseInput.isMiddlePressed();
\r
497 right_active = event.MouseInput.isRightPressed();
\r
499 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
501 leftclicked = true;
\r
503 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
505 rightclicked = true;
\r
507 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
509 leftreleased = true;
\r
511 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
513 rightreleased = true;
\r
515 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
517 /*dstream<<"event.MouseInput.Wheel="
\r
518 <<event.MouseInput.Wheel<<std::endl;*/
\r
519 if(event.MouseInput.Wheel < 0)
\r
521 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
524 g_selected_item = 0;
\r
526 else if(event.MouseInput.Wheel > 0)
\r
528 if(g_selected_item > 0)
\r
531 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
540 // This is used to check whether a key is being held down
\r
541 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
543 return keyIsDown[keyCode];
\r
548 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
549 keyIsDown[i] = false;
\r
551 leftclicked = false;
\r
552 rightclicked = false;
\r
553 leftreleased = false;
\r
554 rightreleased = false;
\r
556 left_active = false;
\r
557 middle_active = false;
\r
558 right_active = false;
\r
569 bool rightreleased;
\r
572 bool middle_active;
\r
576 // We use this array to store the current state of each key
\r
577 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
580 IrrlichtDevice *m_device;
\r
589 virtual ~InputHandler()
\r
593 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
595 virtual v2s32 getMousePos() = 0;
\r
596 virtual void setMousePos(s32 x, s32 y) = 0;
\r
598 virtual bool getLeftState() = 0;
\r
599 virtual bool getRightState() = 0;
\r
601 virtual bool getLeftClicked() = 0;
\r
602 virtual bool getRightClicked() = 0;
\r
603 virtual void resetLeftClicked() = 0;
\r
604 virtual void resetRightClicked() = 0;
\r
606 virtual bool getLeftReleased() = 0;
\r
607 virtual bool getRightReleased() = 0;
\r
608 virtual void resetLeftReleased() = 0;
\r
609 virtual void resetRightReleased() = 0;
\r
611 virtual void step(float dtime) {};
\r
613 virtual void clear() {};
\r
616 InputHandler *g_input = NULL;
\r
618 class RealInputHandler : public InputHandler
\r
621 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
623 m_receiver(receiver)
\r
626 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
628 return m_receiver->IsKeyDown(keyCode);
\r
630 virtual v2s32 getMousePos()
\r
632 return m_device->getCursorControl()->getPosition();
\r
634 virtual void setMousePos(s32 x, s32 y)
\r
636 m_device->getCursorControl()->setPosition(x, y);
\r
639 virtual bool getLeftState()
\r
641 return m_receiver->left_active;
\r
643 virtual bool getRightState()
\r
645 return m_receiver->right_active;
\r
648 virtual bool getLeftClicked()
\r
650 return m_receiver->leftclicked;
\r
652 virtual bool getRightClicked()
\r
654 return m_receiver->rightclicked;
\r
656 virtual void resetLeftClicked()
\r
658 m_receiver->leftclicked = false;
\r
660 virtual void resetRightClicked()
\r
662 m_receiver->rightclicked = false;
\r
665 virtual bool getLeftReleased()
\r
667 return m_receiver->leftreleased;
\r
669 virtual bool getRightReleased()
\r
671 return m_receiver->rightreleased;
\r
673 virtual void resetLeftReleased()
\r
675 m_receiver->leftreleased = false;
\r
677 virtual void resetRightReleased()
\r
679 m_receiver->rightreleased = false;
\r
684 resetRightClicked();
\r
685 resetLeftClicked();
\r
688 IrrlichtDevice *m_device;
\r
689 MyEventReceiver *m_receiver;
\r
692 class RandomInputHandler : public InputHandler
\r
695 RandomInputHandler()
\r
697 leftclicked = false;
\r
698 rightclicked = false;
\r
699 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
700 keydown[i] = false;
\r
702 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
704 return keydown[keyCode];
\r
706 virtual v2s32 getMousePos()
\r
710 virtual void setMousePos(s32 x, s32 y)
\r
712 mousepos = v2s32(x,y);
\r
715 virtual bool getLeftState()
\r
719 virtual bool getRightState()
\r
724 virtual bool getLeftClicked()
\r
726 return leftclicked;
\r
728 virtual bool getRightClicked()
\r
730 return rightclicked;
\r
732 virtual void resetLeftClicked()
\r
734 leftclicked = false;
\r
736 virtual void resetRightClicked()
\r
738 rightclicked = false;
\r
741 virtual bool getLeftReleased()
\r
745 virtual bool getRightReleased()
\r
749 virtual void resetLeftReleased()
\r
752 virtual void resetRightReleased()
\r
756 virtual void step(float dtime)
\r
759 static float counter1 = 0;
\r
763 counter1 = 0.1*Rand(1,10);
\r
764 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
765 g_selected_material++;
\r
767 g_selected_material = 0;*/
\r
768 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
771 g_selected_item = 0;
\r
775 static float counter1 = 0;
\r
779 counter1 = 0.1*Rand(1, 40);
\r
780 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
784 static float counter1 = 0;
\r
788 counter1 = 0.1*Rand(1, 40);
\r
789 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
793 static float counter1 = 0;
\r
797 counter1 = 0.1*Rand(1, 40);
\r
798 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
802 static float counter1 = 0;
\r
806 counter1 = 0.1*Rand(1, 40);
\r
807 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
811 static float counter1 = 0;
\r
815 counter1 = 0.1*Rand(1, 20);
\r
816 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
820 static float counter1 = 0;
\r
824 counter1 = 0.1*Rand(1, 30);
\r
825 leftclicked = true;
\r
829 static float counter1 = 0;
\r
833 counter1 = 0.1*Rand(1, 20);
\r
834 rightclicked = true;
\r
837 mousepos += mousespeed;
\r
840 s32 Rand(s32 min, s32 max)
\r
842 return (rand()%(max-min+1))+min;
\r
845 bool keydown[KEY_KEY_CODES_COUNT];
\r
852 void updateViewingRange(f32 frametime, Client *client)
\r
854 // Range_all messes up frametime_avg
\r
855 if(g_viewing_range_all == true)
\r
858 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
860 // Initialize to the target value
\r
861 static float frametime_avg = 1.0/wanted_fps;
\r
862 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
863 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
865 static f32 counter = 0;
\r
867 counter -= frametime;
\r
870 //counter = 1.0; //seconds
\r
871 counter = 0.5; //seconds
\r
873 //float freetime_ratio = 0.2;
\r
874 //float freetime_ratio = 0.4;
\r
875 float freetime_ratio = FREETIME_RATIO;
\r
877 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
879 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
881 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
882 + frametime_avg / frametime_wanted / 2.0;*/
\r
884 //float fraction = frametime_avg / frametime_wanted;
\r
886 static bool fraction_is_good = false;
\r
888 //float fraction_good_threshold = 0.1;
\r
889 //float fraction_bad_threshold = 0.25;
\r
890 float fraction_good_threshold = 0.075;
\r
891 float fraction_bad_threshold = 0.125;
\r
892 float fraction_limit;
\r
893 // Use high limit if fraction is good AND the fraction would
\r
894 // lower the range. We want to keep the range fairly high.
\r
895 if(fraction_is_good && fraction > 1.0)
\r
896 fraction_limit = fraction_bad_threshold;
\r
898 fraction_limit = fraction_good_threshold;
\r
900 if(fabs(fraction - 1.0) < fraction_limit)
\r
902 fraction_is_good = true;
\r
907 fraction_is_good = false;
\r
910 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
911 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
912 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
913 <<" faction = "<<fraction<<std::endl;*/
\r
915 JMutexAutoLock lock(g_range_mutex);
\r
917 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
918 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
920 s16 n = (float)g_viewing_range_nodes / fraction;
\r
921 if(n < viewing_range_nodes_min)
\r
922 n = viewing_range_nodes_min;
\r
923 if(n > viewing_range_nodes_max)
\r
924 n = viewing_range_nodes_max;
\r
926 bool can_change = true;
\r
928 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
929 can_change = false;
\r
932 g_viewing_range_nodes = n;
\r
934 /*dstream<<"g_viewing_range_nodes = "
\r
935 <<g_viewing_range_nodes<<std::endl;*/
\r
938 class GUIQuickInventory : public IEventReceiver
\r
942 gui::IGUIEnvironment* env,
\r
943 gui::IGUIElement* parent,
\r
946 Inventory *inventory):
\r
947 m_itemcount(itemcount),
\r
948 m_inventory(inventory)
\r
950 core::rect<s32> imgsize(0,0,48,48);
\r
951 core::rect<s32> textsize(0,0,48,16);
\r
952 v2s32 spacing(0, 64);
\r
953 for(s32 i=0; i<m_itemcount; i++)
\r
955 m_images.push_back(env->addImage(
\r
956 imgsize + pos + spacing*i
\r
958 m_images[i]->setScaleImage(true);
\r
959 m_texts.push_back(env->addStaticText(
\r
961 textsize + pos + spacing*i,
\r
964 m_texts[i]->setBackgroundColor(
\r
965 video::SColor(128,0,0,0));
\r
966 m_texts[i]->setTextAlignment(
\r
968 gui::EGUIA_UPPERLEFT);
\r
972 virtual bool OnEvent(const SEvent& event)
\r
977 void setSelection(s32 i)
\r
986 start = m_selection - m_itemcount / 2;
\r
988 InventoryList *mainlist = m_inventory->getList("main");
\r
990 for(s32 i=0; i<m_itemcount; i++)
\r
994 if(j > (s32)mainlist->getSize() - 1)
\r
995 j -= mainlist->getSize();
\r
997 j += mainlist->getSize();
\r
999 InventoryItem *item = mainlist->getItem(j);
\r
1003 m_images[i]->setImage(NULL);
\r
1006 if(m_selection == j)
\r
1007 swprintf(t, 10, L"<-");
\r
1009 swprintf(t, 10, L"");
\r
1010 m_texts[i]->setText(t);
\r
1012 // The next ifs will segfault with a NULL pointer
\r
1017 m_images[i]->setImage(item->getImage());
\r
1020 if(m_selection == j)
\r
1021 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1023 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1024 m_texts[i]->setText(t);
\r
1030 core::array<gui::IGUIStaticText*> m_texts;
\r
1031 core::array<gui::IGUIImage*> m_images;
\r
1032 Inventory *m_inventory;
\r
1043 ChatLine(const std::wstring &a_text):
\r
1049 std::wstring text;
\r
1052 int main(int argc, char *argv[])
\r
1055 Low-level initialization
\r
1058 bool disable_stderr = false;
\r
1060 disable_stderr = true;
\r
1063 // Initialize debug streams
\r
1064 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1065 // Initialize debug stacks
\r
1066 debug_stacks_init();
\r
1068 DSTACK(__FUNCTION_NAME);
\r
1070 initializeMaterialProperties();
\r
1076 Parse command line
\r
1079 // List all allowed options
\r
1080 core::map<std::string, ValueSpec> allowed_options;
\r
1081 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1082 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1083 "Run server directly"));
\r
1084 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1085 "Load configuration from specified file"));
\r
1086 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1087 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1088 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1089 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1090 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1092 Settings cmd_args;
\r
1094 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1096 if(ret == false || cmd_args.getFlag("help"))
\r
1098 dstream<<"Allowed options:"<<std::endl;
\r
1099 for(core::map<std::string, ValueSpec>::Iterator
\r
1100 i = allowed_options.getIterator();
\r
1101 i.atEnd() == false; i++)
\r
1103 dstream<<" --"<<i.getNode()->getKey();
\r
1104 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1109 dstream<<" <value>";
\r
1111 dstream<<std::endl;
\r
1113 if(i.getNode()->getValue().help != NULL)
\r
1115 dstream<<" "<<i.getNode()->getValue().help
\r
1120 return cmd_args.getFlag("help") ? 0 : 1;
\r
1125 Basic initialization
\r
1128 // Initialize default settings
\r
1129 set_default_settings();
\r
1131 // Print startup message
\r
1132 dstream<<DTIME<<"minetest-c55"
\r
1133 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1134 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1137 // Set locale. This is for forcing '.' as the decimal point.
\r
1138 std::locale::global(std::locale("C"));
\r
1139 // This enables printing all characters in bitmap font
\r
1140 setlocale(LC_CTYPE, "en_US");
\r
1142 // Initialize sockets
\r
1144 atexit(sockets_cleanup);
\r
1154 // Path of configuration file in use
\r
1155 std::string configpath = "";
\r
1157 if(cmd_args.exists("config"))
\r
1159 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1162 dstream<<"Could not read configuration from \""
\r
1163 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1166 configpath = cmd_args.get("config");
\r
1170 const char *filenames[2] =
\r
1172 "../minetest.conf",
\r
1173 "../../minetest.conf"
\r
1176 for(u32 i=0; i<2; i++)
\r
1178 bool r = g_settings.readConfigFile(filenames[i]);
\r
1181 configpath = filenames[i];
\r
1187 // Initialize random seed
\r
1193 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1194 || cmd_args.getFlag("enable-unittests") == true)
\r
1200 Global range mutex
\r
1202 g_range_mutex.Init();
\r
1203 assert(g_range_mutex.IsInitialized());
\r
1205 // Read map parameters from settings
\r
1207 HMParams hm_params;
\r
1208 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1209 hm_params.randmax = g_settings.get("height_randmax");
\r
1210 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1211 hm_params.base = g_settings.get("height_base");
\r
1213 MapParams map_params;
\r
1214 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1215 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1221 std::cout<<std::endl<<std::endl;
\r
1224 <<" .__ __ __ "<<std::endl
\r
1225 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1226 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1227 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1228 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1229 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1232 std::cout<<std::endl;
\r
1233 //char templine[100];
\r
1237 if(cmd_args.exists("port"))
\r
1239 port = cmd_args.getU16("port");
\r
1243 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1244 std::cout<<"-> "<<port<<std::endl;
\r
1247 if(cmd_args.getFlag("server"))
\r
1249 DSTACK("Dedicated server branch");
\r
1251 std::cout<<std::endl;
\r
1252 std::cout<<"========================"<<std::endl;
\r
1253 std::cout<<"Running dedicated server"<<std::endl;
\r
1254 std::cout<<"========================"<<std::endl;
\r
1255 std::cout<<std::endl;
\r
1257 Server server("../map", hm_params, map_params);
\r
1258 server.start(port);
\r
1262 // This is kind of a hack but can be done like this
\r
1263 // because server.step() is very light
\r
1265 server.step(0.030);
\r
1267 static int counter = 0;
\r
1273 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1274 core::list<PlayerInfo>::Iterator i;
\r
1275 static u32 sum_old = 0;
\r
1276 u32 sum = PIChecksum(list);
\r
1277 if(sum != sum_old)
\r
1279 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1280 for(i=list.begin(); i!=list.end(); i++)
\r
1282 i->PrintLine(&std::cout);
\r
1292 bool hosting = false;
\r
1293 char connect_name[100] = "";
\r
1295 if(cmd_args.exists("address"))
\r
1297 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1299 else if(is_yes(g_settings.get("host_game")) == false)
\r
1301 if(g_settings.get("address") != "")
\r
1303 std::cout<<g_settings.get("address")<<std::endl;
\r
1304 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1308 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1309 std::cin.getline(connect_name, 100);
\r
1313 if(connect_name[0] == 0){
\r
1314 snprintf(connect_name, 100, "127.0.0.1");
\r
1319 std::cout<<"> Hosting game"<<std::endl;
\r
1321 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1323 char playername[PLAYERNAME_SIZE] = "";
\r
1324 if(g_settings.get("name") != "")
\r
1326 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1330 std::cout<<"Name of player: ";
\r
1331 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1333 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1336 Resolution selection
\r
1339 bool fullscreen = false;
\r
1340 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1341 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1345 MyEventReceiver receiver;
\r
1347 video::E_DRIVER_TYPE driverType;
\r
1350 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1351 driverType = video::EDT_OPENGL;
\r
1353 driverType = video::EDT_OPENGL;
\r
1356 // create device and exit if creation failed
\r
1358 IrrlichtDevice *device;
\r
1359 device = createDevice(driverType,
\r
1360 core::dimension2d<u32>(screenW, screenH),
\r
1361 16, fullscreen, false, false, &receiver);
\r
1364 return 1; // could not create selected driver.
\r
1366 g_device = device;
\r
1367 g_irrlicht = new IrrlichtWrapper(device);
\r
1369 //g_device = device;
\r
1371 device->setResizable(true);
\r
1373 bool random_input = g_settings.getBool("random_input")
\r
1374 || cmd_args.getFlag("random-input");
\r
1376 g_input = new RandomInputHandler();
\r
1378 g_input = new RealInputHandler(device, &receiver);
\r
1381 Continue initialization
\r
1384 video::IVideoDriver* driver = device->getVideoDriver();
\r
1387 This changes the minimum allowed number of vertices in a VBO
\r
1389 //driver->setMinHardwareBufferVertexCount(1);
\r
1391 scene::ISceneManager* smgr = device->getSceneManager();
\r
1393 guienv = device->getGUIEnvironment();
\r
1394 gui::IGUISkin* skin = guienv->getSkin();
\r
1395 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1397 skin->setFont(font);
\r
1399 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1400 dstream<<"text_height="<<text_height<<std::endl;
\r
1402 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1403 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1404 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1405 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1406 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1407 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1409 const wchar_t *text = L"Loading and connecting...";
\r
1410 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1411 core::vector2d<s32> textsize(300, text_height);
\r
1412 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1414 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1415 text, textrect, false, false);
\r
1416 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1418 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1419 guienv->drawAll();
\r
1420 driver->endScene();
\r
1423 Preload some textures
\r
1426 tile_materials_preload(g_irrlicht);
\r
1429 Make a scope here for the client so that it gets removed
\r
1430 before the irrlicht device
\r
1434 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1439 SharedPtr<Server> server;
\r
1441 server = new Server("../map", hm_params, map_params);
\r
1442 server->start(port);
\r
1449 Client client(device, playername,
\r
1451 g_viewing_range_nodes,
\r
1452 g_viewing_range_all);
\r
1454 g_client = &client;
\r
1456 Address connect_address(0,0,0,0, port);
\r
1458 connect_address.Resolve(connect_name);
\r
1460 catch(ResolveError &e)
\r
1462 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1466 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1467 client.connect(connect_address);
\r
1470 while(client.connectedAndInitialized() == false)
\r
1473 if(server != NULL){
\r
1474 server->step(0.1);
\r
1479 catch(con::PeerNotFoundException &e)
\r
1481 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1488 /*scene::ISceneNode* skybox;
\r
1489 skybox = smgr->addSkyBoxSceneNode(
\r
1490 driver->getTexture("../data/skybox2.png"),
\r
1491 driver->getTexture("../data/skybox3.png"),
\r
1492 driver->getTexture("../data/skybox1.png"),
\r
1493 driver->getTexture("../data/skybox1.png"),
\r
1494 driver->getTexture("../data/skybox1.png"),
\r
1495 driver->getTexture("../data/skybox1.png"));*/
\r
1498 Create the camera node
\r
1501 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1502 0, // Camera parent
\r
1503 v3f(BS*100, BS*2, BS*100), // Look from
\r
1504 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1508 if(camera == NULL)
\r
1511 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1513 camera->setFOV(FOV_ANGLE);
\r
1515 // Just so big a value that everything rendered is visible
\r
1516 camera->setFarValue(100000*BS);
\r
1518 f32 camera_yaw = 0; // "right/left"
\r
1519 f32 camera_pitch = 0; // "up/down"
\r
1525 gui_loadingtext->remove();
\r
1528 Add some gui stuff
\r
1531 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1532 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1535 We need some kind of a root node to be able to add
\r
1536 custom elements directly on the screen.
\r
1537 Otherwise they won't be automatically drawn.
\r
1539 guiroot = guienv->addStaticText(L"",
\r
1540 core::rect<s32>(0, 0, 10000, 10000));
\r
1542 // Test the text input system
\r
1543 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1545 /*GUIMessageMenu *menu =
\r
1546 new GUIMessageMenu(guienv, guiroot, -1,
\r
1547 &g_active_menu_count,
\r
1551 // Launch pause menu
\r
1552 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1553 &g_active_menu_count))->drop();
\r
1555 // First line of debug text
\r
1556 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1558 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1560 // Second line of debug text
\r
1561 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1563 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1566 // At the middle of the screen
\r
1567 // Object infos are shown in this
\r
1568 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1570 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1574 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1575 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1576 core::rect<s32>(70, 60, 795, 150),
\r
1578 chat_guitext->setBackgroundColor(video::SColor(96,0,0,0));
\r
1579 core::list<ChatLine> chat_lines;
\r
1582 Some statistics are collected in these
\r
1585 u32 beginscenetime = 0;
\r
1586 u32 scenetime = 0;
\r
1587 u32 endscenetime = 0;
\r
1590 //throw con::PeerNotFoundException("lol");
\r
1596 bool first_loop_after_window_activation = true;
\r
1598 // Time is in milliseconds
\r
1599 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1600 // NOTE: Have to call run() between calls of this to update the timer
\r
1601 u32 lasttime = device->getTimer()->getTime();
\r
1603 while(device->run())
\r
1606 Run global IrrlichtWrapper's main thread processing stuff
\r
1608 g_irrlicht->Run();
\r
1611 Random calculations
\r
1613 v2u32 screensize = driver->getScreenSize();
\r
1614 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1616 // Hilight boxes collected during the loop and displayed
\r
1617 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1620 std::wstring infotext;
\r
1622 //TimeTaker //timer1("//timer1");
\r
1624 // Time of frame without fps limit
\r
1628 // not using getRealTime is necessary for wine
\r
1629 u32 time = device->getTimer()->getTime();
\r
1630 if(time > lasttime)
\r
1631 busytime_u32 = time - lasttime;
\r
1634 busytime = busytime_u32 / 1000.0;
\r
1637 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1639 // Absolutelu necessary for wine!
\r
1646 updateViewingRange(busytime, &client);
\r
1653 float fps_max = g_settings.getFloat("fps_max");
\r
1654 u32 frametime_min = 1000./fps_max;
\r
1656 if(busytime_u32 < frametime_min)
\r
1658 u32 sleeptime = frametime_min - busytime_u32;
\r
1659 device->sleep(sleeptime);
\r
1663 // Absolutelu necessary for wine!
\r
1667 Time difference calculation
\r
1669 f32 dtime; // in seconds
\r
1671 u32 time = device->getTimer()->getTime();
\r
1672 if(time > lasttime)
\r
1673 dtime = (time - lasttime) / 1000.0;
\r
1679 Time average and jitter calculation
\r
1682 static f32 dtime_avg1 = 0.0;
\r
1683 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1684 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1686 static f32 dtime_jitter1_max_sample = 0.0;
\r
1687 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1689 static f32 jitter1_max = 0.0;
\r
1690 static f32 counter = 0.0;
\r
1691 if(dtime_jitter1 > jitter1_max)
\r
1692 jitter1_max = dtime_jitter1;
\r
1697 dtime_jitter1_max_sample = jitter1_max;
\r
1698 dtime_jitter1_max_fraction
\r
1699 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1700 jitter1_max = 0.0;
\r
1703 Control freetime ratio
\r
1705 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1707 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1708 g_freetime_ratio += 0.01;
\r
1712 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1713 g_freetime_ratio -= 0.01;
\r
1719 Busytime average and jitter calculation
\r
1722 static f32 busytime_avg1 = 0.0;
\r
1723 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1724 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1726 static f32 busytime_jitter1_max_sample = 0.0;
\r
1727 static f32 busytime_jitter1_min_sample = 0.0;
\r
1729 static f32 jitter1_max = 0.0;
\r
1730 static f32 jitter1_min = 0.0;
\r
1731 static f32 counter = 0.0;
\r
1732 if(busytime_jitter1 > jitter1_max)
\r
1733 jitter1_max = busytime_jitter1;
\r
1734 if(busytime_jitter1 < jitter1_min)
\r
1735 jitter1_min = busytime_jitter1;
\r
1737 if(counter > 0.0){
\r
1739 busytime_jitter1_max_sample = jitter1_max;
\r
1740 busytime_jitter1_min_sample = jitter1_min;
\r
1741 jitter1_max = 0.0;
\r
1742 jitter1_min = 0.0;
\r
1747 Debug info for client
\r
1750 static float counter = 0.0;
\r
1755 client.printDebugInfo(std::cout);
\r
1760 Input handler step()
\r
1762 g_input->step(dtime);
\r
1765 Player speed control
\r
1774 bool a_superspeed,
\r
1777 PlayerControl control(
\r
1778 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1779 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1780 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1781 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1782 g_input->isKeyDown(irr::KEY_SPACE),
\r
1783 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1787 client.setPlayerControl(control);
\r
1791 Process environment
\r
1795 //TimeTaker timer("client.step(dtime)");
\r
1796 client.step(dtime);
\r
1797 //client.step(dtime_avg1);
\r
1800 if(server != NULL)
\r
1802 //TimeTaker timer("server->step(dtime)");
\r
1803 server->step(dtime);
\r
1806 v3f player_position = client.getPlayerPosition();
\r
1808 //TimeTaker //timer2("//timer2");
\r
1811 Mouse and camera control
\r
1814 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1817 device->getCursorControl()->setVisible(false);
\r
1819 if(first_loop_after_window_activation){
\r
1820 //std::cout<<"window active, first loop"<<std::endl;
\r
1821 first_loop_after_window_activation = false;
\r
1824 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1825 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1826 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1827 camera_yaw -= dx*0.2;
\r
1828 camera_pitch += dy*0.2;
\r
1829 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1830 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1832 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1835 device->getCursorControl()->setVisible(true);
\r
1837 //std::cout<<"window inactive"<<std::endl;
\r
1838 first_loop_after_window_activation = true;
\r
1841 camera_yaw = wrapDegrees(camera_yaw);
\r
1842 camera_pitch = wrapDegrees(camera_pitch);
\r
1844 v3f camera_direction = v3f(0,0,1);
\r
1845 camera_direction.rotateYZBy(camera_pitch);
\r
1846 camera_direction.rotateXZBy(camera_yaw);
\r
1848 v3f camera_position =
\r
1849 player_position + v3f(0, BS+BS/2, 0);
\r
1851 camera->setPosition(camera_position);
\r
1852 // *100.0 helps in large map coordinates
\r
1853 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1855 if(FIELD_OF_VIEW_TEST){
\r
1856 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1857 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1860 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1861 //TimeTaker timer("client.updateCamera");
\r
1862 client.updateCamera(camera_position, camera_direction);
\r
1866 //TimeTaker //timer3("//timer3");
\r
1869 Calculate what block is the crosshair pointing to
\r
1872 //u32 t1 = device->getTimer()->getRealTime();
\r
1874 //f32 d = 4; // max. distance
\r
1875 f32 d = 4; // max. distance
\r
1876 core::line3d<f32> shootline(camera_position,
\r
1877 camera_position + camera_direction * BS * (d+1));
\r
1879 MapBlockObject *selected_object = client.getSelectedObject
\r
1880 (d*BS, camera_position, shootline);
\r
1883 If it's pointing to a MapBlockObject
\r
1886 if(selected_object != NULL)
\r
1888 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1890 core::aabbox3d<f32> box_on_map
\r
1891 = selected_object->getSelectionBoxOnMap();
\r
1893 hilightboxes.push_back(box_on_map);
\r
1895 infotext = narrow_to_wide(selected_object->infoText());
\r
1897 if(g_input->getLeftClicked())
\r
1899 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1900 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1901 selected_object->getId(), g_selected_item);
\r
1903 else if(g_input->getRightClicked())
\r
1905 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1907 Check if we want to modify the object ourselves
\r
1909 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1911 dstream<<"Sign object right-clicked"<<std::endl;
\r
1913 if(random_input == false)
\r
1915 // Get a new text for it
\r
1917 TextDest *dest = new TextDestSign(
\r
1918 selected_object->getBlock()->getPos(),
\r
1919 selected_object->getId(),
\r
1922 SignObject *sign_object = (SignObject*)selected_object;
\r
1924 std::wstring wtext =
\r
1925 narrow_to_wide(sign_object->getText());
\r
1927 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1928 &g_active_menu_count, dest,
\r
1933 Otherwise pass the event to the server as-is
\r
1937 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1938 selected_object->getId(), g_selected_item);
\r
1942 else // selected_object == NULL
\r
1946 Find out which node we are pointing at
\r
1949 bool nodefound = false;
\r
1951 v3s16 neighbourpos;
\r
1952 core::aabbox3d<f32> nodefacebox;
\r
1953 f32 mindistance = BS * 1001;
\r
1955 v3s16 pos_i = floatToInt(player_position);
\r
1957 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1961 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1962 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1963 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1964 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1965 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1966 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1968 for(s16 y = ystart; y <= yend; y++)
\r
1969 for(s16 z = zstart; z <= zend; z++)
\r
1970 for(s16 x = xstart; x <= xend; x++)
\r
1975 n = client.getNode(v3s16(x,y,z));
\r
1976 if(content_pointable(n.d) == false)
\r
1979 catch(InvalidPositionException &e)
\r
1985 v3f npf = intToFloat(np);
\r
1990 v3s16(0,0,1), // back
\r
1991 v3s16(0,1,0), // top
\r
1992 v3s16(1,0,0), // right
\r
1993 v3s16(0,0,-1), // front
\r
1994 v3s16(0,-1,0), // bottom
\r
1995 v3s16(-1,0,0), // left
\r
2001 if(n.d == CONTENT_TORCH)
\r
2003 v3s16 dir = unpackDir(n.dir);
\r
2004 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2005 dir_f *= BS/2 - BS/6 - BS/20;
\r
2006 v3f cpf = npf + dir_f;
\r
2007 f32 distance = (cpf - camera_position).getLength();
\r
2009 core::aabbox3d<f32> box;
\r
2012 if(dir == v3s16(0,-1,0))
\r
2014 box = core::aabbox3d<f32>(
\r
2015 npf - v3f(BS/6, BS/2, BS/6),
\r
2016 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2020 else if(dir == v3s16(0,1,0))
\r
2022 box = core::aabbox3d<f32>(
\r
2023 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2024 npf + v3f(BS/6, BS/2, BS/6)
\r
2030 box = core::aabbox3d<f32>(
\r
2031 cpf - v3f(BS/6, BS/3, BS/6),
\r
2032 cpf + v3f(BS/6, BS/3, BS/6)
\r
2036 if(distance < mindistance)
\r
2038 if(box.intersectsWithLine(shootline))
\r
2042 neighbourpos = np;
\r
2043 mindistance = distance;
\r
2044 nodefacebox = box;
\r
2053 for(u16 i=0; i<6; i++)
\r
2055 v3f dir_f = v3f(dirs[i].X,
\r
2056 dirs[i].Y, dirs[i].Z);
\r
2057 v3f centerpoint = npf + dir_f * BS/2;
\r
2059 (centerpoint - camera_position).getLength();
\r
2061 if(distance < mindistance)
\r
2063 core::CMatrix4<f32> m;
\r
2064 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2066 // This is the back face
\r
2067 v3f corners[2] = {
\r
2068 v3f(BS/2, BS/2, BS/2),
\r
2069 v3f(-BS/2, -BS/2, BS/2+d)
\r
2072 for(u16 j=0; j<2; j++)
\r
2074 m.rotateVect(corners[j]);
\r
2075 corners[j] += npf;
\r
2078 core::aabbox3d<f32> facebox(corners[0]);
\r
2079 facebox.addInternalPoint(corners[1]);
\r
2081 if(facebox.intersectsWithLine(shootline))
\r
2085 neighbourpos = np + dirs[i];
\r
2086 mindistance = distance;
\r
2087 nodefacebox = facebox;
\r
2089 } // if distance < mindistance
\r
2091 } // regular block
\r
2094 static float nodig_delay_counter = 0.0;
\r
2098 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2100 static float dig_time = 0.0;
\r
2101 static u16 dig_index = 0;
\r
2103 hilightboxes.push_back(nodefacebox);
\r
2105 if(g_input->getLeftReleased())
\r
2107 client.clearTempMod(nodepos);
\r
2111 if(nodig_delay_counter > 0.0)
\r
2113 nodig_delay_counter -= dtime;
\r
2117 if(nodepos != nodepos_old)
\r
2119 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2120 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2122 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2124 client.clearTempMod(nodepos_old);
\r
2129 if(g_input->getLeftClicked() ||
\r
2130 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2132 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2133 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2135 if(g_input->getLeftClicked())
\r
2137 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2139 if(g_input->getLeftState())
\r
2141 MapNode n = client.getNode(nodepos);
\r
2143 // Get tool name. Default is "" = bare hands
\r
2144 std::string toolname = "";
\r
2145 InventoryList *mlist = local_inventory.getList("main");
\r
2148 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2149 if(item && (std::string)item->getName() == "ToolItem")
\r
2151 ToolItem *titem = (ToolItem*)item;
\r
2152 toolname = titem->getToolName();
\r
2156 // Get digging properties for material and tool
\r
2157 u8 material = n.d;
\r
2158 DiggingProperties prop =
\r
2159 getDiggingProperties(material, toolname);
\r
2161 float dig_time_complete = 0.0;
\r
2163 if(prop.diggable == false)
\r
2165 /*dstream<<"Material "<<(int)material
\r
2166 <<" not diggable with \""
\r
2167 <<toolname<<"\""<<std::endl;*/
\r
2168 // I guess nobody will wait for this long
\r
2169 dig_time_complete = 10000000.0;
\r
2173 dig_time_complete = prop.time;
\r
2176 if(dig_time_complete >= 0.001)
\r
2178 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2179 * dig_time/dig_time_complete);
\r
2181 // This is for torches
\r
2184 dig_index = CRACK_ANIMATION_LENGTH;
\r
2187 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2189 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2190 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2194 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2195 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2196 client.clearTempMod(nodepos);
\r
2197 client.removeNode(nodepos);
\r
2201 nodig_delay_counter = dig_time_complete
\r
2202 / (float)CRACK_ANIMATION_LENGTH;
\r
2204 // We don't want a corresponding delay to
\r
2205 // very time consuming nodes
\r
2206 if(nodig_delay_counter > 0.5)
\r
2208 nodig_delay_counter = 0.5;
\r
2210 // We want a slight delay to very little
\r
2211 // time consuming nodes
\r
2212 //float mindelay = 0.15;
\r
2213 float mindelay = 0.20;
\r
2214 if(nodig_delay_counter < mindelay)
\r
2216 nodig_delay_counter = mindelay;
\r
2220 dig_time += dtime;
\r
2224 if(g_input->getRightClicked())
\r
2226 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2227 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2230 nodepos_old = nodepos;
\r
2235 } // selected_object == NULL
\r
2237 g_input->resetLeftClicked();
\r
2238 g_input->resetRightClicked();
\r
2240 if(g_input->getLeftReleased())
\r
2242 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2244 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2246 if(g_input->getRightReleased())
\r
2248 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2252 g_input->resetLeftReleased();
\r
2253 g_input->resetRightReleased();
\r
2256 Calculate stuff for drawing
\r
2259 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2261 u32 daynight_ratio = client.getDayNightRatio();
\r
2262 video::SColor bgcolor = video::SColor(
\r
2264 skycolor.getRed() * daynight_ratio / 1000,
\r
2265 skycolor.getGreen() * daynight_ratio / 1000,
\r
2266 skycolor.getBlue() * daynight_ratio / 1000);
\r
2272 if(g_settings.getBool("enable_fog") == true)
\r
2274 f32 range = g_viewing_range_nodes * BS;
\r
2275 if(g_viewing_range_all)
\r
2276 range = 100000*BS;
\r
2280 video::EFT_FOG_LINEAR,
\r
2284 false, // pixel fog
\r
2285 false // range fog
\r
2291 Update gui stuff (0ms)
\r
2294 //TimeTaker guiupdatetimer("Gui updating");
\r
2297 wchar_t temptext[150];
\r
2299 static float drawtime_avg = 0;
\r
2300 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2301 static float beginscenetime_avg = 0;
\r
2302 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2303 static float scenetime_avg = 0;
\r
2304 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2305 static float endscenetime_avg = 0;
\r
2306 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2308 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2310 L", R: range_all=%i"
\r
2312 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2314 g_viewing_range_all,
\r
2316 beginscenetime_avg,
\r
2321 guitext->setText(temptext);
\r
2325 wchar_t temptext[150];
\r
2326 swprintf(temptext, 150,
\r
2327 L"(% .1f, % .1f, % .1f)"
\r
2328 L" (% .3f < btime_jitter < % .3f"
\r
2329 L", dtime_jitter = % .1f %%)",
\r
2330 player_position.X/BS,
\r
2331 player_position.Y/BS,
\r
2332 player_position.Z/BS,
\r
2333 busytime_jitter1_min_sample,
\r
2334 busytime_jitter1_max_sample,
\r
2335 dtime_jitter1_max_fraction * 100.0
\r
2338 guitext2->setText(temptext);
\r
2342 guitext_info->setText(infotext.c_str());
\r
2346 Get chat messages from client
\r
2349 // Get new messages
\r
2350 std::wstring message;
\r
2351 while(client.getChatMessage(message))
\r
2353 chat_lines.push_back(ChatLine(message));
\r
2354 /*if(chat_lines.size() > 6)
\r
2356 core::list<ChatLine>::Iterator
\r
2357 i = chat_lines.begin();
\r
2358 chat_lines.erase(i);
\r
2361 // Append them to form the whole static text and throw
\r
2362 // it to the gui element
\r
2363 std::wstring whole;
\r
2364 // This will correspond to the line number counted from
\r
2365 // top to bottom, from size-1 to 0
\r
2366 s16 line_number = chat_lines.size();
\r
2367 // Count of messages to be removed from the top
\r
2368 u16 to_be_removed_count = 0;
\r
2369 for(core::list<ChatLine>::Iterator
\r
2370 i = chat_lines.begin();
\r
2371 i != chat_lines.end(); i++)
\r
2373 // After this, line number is valid for this loop
\r
2376 (*i).age += dtime;
\r
2378 This results in a maximum age of 60*6 to the
\r
2379 lowermost line and a maximum of 6 lines
\r
2381 float allowed_age = (6-line_number) * 60.0;
\r
2383 if((*i).age > allowed_age)
\r
2385 to_be_removed_count++;
\r
2388 whole += (*i).text + L'\n';
\r
2390 for(u16 i=0; i<to_be_removed_count; i++)
\r
2392 core::list<ChatLine>::Iterator
\r
2393 it = chat_lines.begin();
\r
2394 chat_lines.erase(it);
\r
2396 chat_guitext->setText(whole.c_str());
\r
2397 // Update gui element size and position
\r
2398 core::rect<s32> rect(
\r
2400 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2401 screensize.X - 10,
\r
2404 chat_guitext->setRelativePosition(rect);
\r
2406 if(chat_lines.size() == 0)
\r
2407 chat_guitext->setVisible(false);
\r
2409 chat_guitext->setVisible(true);
\r
2416 static u16 old_selected_item = 65535;
\r
2417 if(client.getLocalInventoryUpdated()
\r
2418 || g_selected_item != old_selected_item)
\r
2420 old_selected_item = g_selected_item;
\r
2421 //std::cout<<"Updating local inventory"<<std::endl;
\r
2422 client.getLocalInventory(local_inventory);
\r
2423 quick_inventory->setSelection(g_selected_item);
\r
2424 quick_inventory->update();
\r
2428 Send actions returned by the inventory menu
\r
2430 while(inventory_action_queue.size() != 0)
\r
2432 InventoryAction *a = inventory_action_queue.pop_front();
\r
2434 client.sendInventoryAction(a);
\r
2443 TimeTaker drawtimer("Drawing");
\r
2447 TimeTaker timer("beginScene");
\r
2448 driver->beginScene(true, true, bgcolor);
\r
2449 //driver->beginScene(false, true, bgcolor);
\r
2450 beginscenetime = timer.stop(true);
\r
2455 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2458 TimeTaker timer("smgr");
\r
2460 scenetime = timer.stop(true);
\r
2464 //TimeTaker timer9("auxiliary drawings");
\r
2468 //TimeTaker //timer10("//timer10");
\r
2470 video::SMaterial m;
\r
2472 m.Lighting = false;
\r
2473 driver->setMaterial(m);
\r
2475 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2477 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2478 i != hilightboxes.end(); i++)
\r
2480 /*std::cout<<"hilightbox min="
\r
2481 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2483 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2485 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2491 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2492 displaycenter + core::vector2d<s32>(10,0),
\r
2493 video::SColor(255,255,255,255));
\r
2494 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2495 displaycenter + core::vector2d<s32>(0,10),
\r
2496 video::SColor(255,255,255,255));
\r
2501 //TimeTaker //timer11("//timer11");
\r
2507 guienv->drawAll();
\r
2511 TimeTaker timer("endScene");
\r
2512 driver->endScene();
\r
2513 endscenetime = timer.stop(true);
\r
2516 drawtime = drawtimer.stop(true);
\r
2522 static s16 lastFPS = 0;
\r
2523 //u16 fps = driver->getFPS();
\r
2524 u16 fps = (1.0/dtime_avg1);
\r
2526 if (lastFPS != fps)
\r
2528 core::stringw str = L"Minetest [";
\r
2529 str += driver->getName();
\r
2533 device->setWindowCaption(str.c_str());
\r
2539 device->yield();*/
\r
2542 delete quick_inventory;
\r
2544 } // client is deleted at this point
\r
2549 In the end, delete the Irrlicht device.
\r
2554 Update configuration file
\r
2556 /*if(configpath != "")
\r
2558 g_settings.updateConfigFile(configpath.c_str());
\r
2562 catch(con::PeerNotFoundException &e)
\r
2564 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2568 GUIMessageMenu *menu =
\r
2569 new GUIMessageMenu(guienv, guiroot, -1,
\r
2570 &g_active_menu_count,
\r
2571 L"Connection timed out");
\r
2573 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
2575 dstream<<"Created menu"<<std::endl;
\r
2577 while(g_device->run() && menu->getStatus() == false)
\r
2579 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2580 guienv->drawAll();
\r
2581 driver->endScene();
\r
2584 dstream<<"Dropping menu"<<std::endl;
\r
2589 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2591 This is what has to be done in every thread to get suitable debug info
\r
2593 catch(std::exception &e)
\r
2595 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2596 <<e.what()<<std::endl;
\r
2601 debugstreams_deinit();
\r