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 ======================================================================
\r
195 Setting this to 1 enables a special camera mode that forces
\r
196 the renderers to think that the camera statically points from
\r
197 the starting place to a static direction.
\r
199 This allows one to move around with the player and see what
\r
200 is actually drawn behind solid things and behind the player.
\r
202 #define FIELD_OF_VIEW_TEST 0
\r
204 #ifdef UNITTEST_DISABLE
\r
206 #pragma message ("Disabling unit tests")
\r
208 #warning "Disabling unit tests"
\r
210 // Disable unit tests
\r
211 #define ENABLE_TESTS 0
\r
213 // Enable unit tests
\r
214 #define ENABLE_TESTS 1
\r
218 #pragma comment(lib, "Irrlicht.lib")
\r
219 #pragma comment(lib, "jthread.lib")
\r
220 #pragma comment(lib, "zlibwapi.lib")
\r
221 // This would get rid of the console window
\r
222 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
225 #include <iostream>
\r
227 #include <jmutexautolock.h>
\r
228 #include <locale.h>
\r
229 #include "common_irrlicht.h"
\r
232 #include "player.h"
\r
235 #include "environment.h"
\r
236 #include "server.h"
\r
237 #include "client.h"
\r
238 #include "serialization.h"
\r
239 #include "constants.h"
\r
240 #include "strfnd.h"
\r
241 #include "porting.h"
\r
242 #include "irrlichtwrapper.h"
\r
243 #include "gettime.h"
\r
244 #include "porting.h"
\r
245 #include "guiPauseMenu.h"
\r
246 #include "guiInventoryMenu.h"
\r
247 #include "guiTextInputMenu.h"
\r
248 #include "materials.h"
\r
249 #include "guiMessageMenu.h"
\r
251 IrrlichtWrapper *g_irrlicht;
\r
253 // All range-related stuff below is locked behind this
\r
254 JMutex g_range_mutex;
\r
256 // Blocks are viewed in this range from the player
\r
257 s16 g_viewing_range_nodes = 60;
\r
258 //s16 g_viewing_range_nodes = 0;
\r
260 // This is updated by the client's fetchBlocks routine
\r
261 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
263 // If true, the preceding value has no meaning and all blocks
\r
264 // already existing in memory are drawn
\r
265 bool g_viewing_range_all = false;
\r
267 // This is the freetime ratio imposed by the dynamic viewing
\r
268 // range changing code.
\r
269 // It is controlled by the main loop to the smallest value that
\r
270 // inhibits glitches (dtime jitter) in the main loop.
\r
271 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
275 These are loaded from the config file.
\r
278 Settings g_settings;
\r
280 extern void set_default_settings();
\r
286 IrrlichtDevice *g_device = NULL;
\r
287 Client *g_client = NULL;
\r
292 gui::IGUIEnvironment* guienv = NULL;
\r
293 gui::IGUIStaticText *guiroot = NULL;
\r
294 int g_active_menu_count = 0;
\r
296 bool noMenuActive()
\r
298 return (g_active_menu_count == 0);
\r
301 // Inventory actions from the menu are buffered here before sending
\r
302 Queue<InventoryAction*> inventory_action_queue;
\r
303 // This is a copy of the inventory that the client's environment has
\r
304 Inventory local_inventory;
\r
306 u16 g_selected_item = 0;
\r
313 std::ostream *dout_con_ptr = &dummyout;
\r
314 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
315 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
316 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
317 //std::ostream *dout_con_ptr = &dstream;
\r
318 //std::ostream *derr_con_ptr = &dstream;
\r
321 std::ostream *dout_server_ptr = &dstream;
\r
322 std::ostream *derr_server_ptr = &dstream;
\r
325 std::ostream *dout_client_ptr = &dstream;
\r
326 std::ostream *derr_client_ptr = &dstream;
\r
329 gettime.h implementation
\r
335 Use irrlicht because it is more precise than porting.h's
\r
338 if(g_irrlicht == NULL)
\r
340 return g_irrlicht->getTime();
\r
347 struct TextDestSign : public TextDest
\r
349 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
351 m_blockpos = blockpos;
\r
355 void gotText(std::wstring text)
\r
357 std::string ntext = wide_to_narrow(text);
\r
358 dstream<<"Changing text of a sign object: "
\r
359 <<ntext<<std::endl;
\r
360 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
368 struct TextDestChat : public TextDest
\r
370 TextDestChat(Client *client)
\r
374 void gotText(std::wstring text)
\r
376 m_client->sendChatMessage(text);
\r
377 m_client->addChatMessage(text);
\r
383 class MyEventReceiver : public IEventReceiver
\r
386 // This is the one method that we have to implement
\r
387 virtual bool OnEvent(const SEvent& event)
\r
390 React to nothing here if a menu is active
\r
392 if(noMenuActive() == false)
\r
398 // Remember whether each key is down or up
\r
399 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
401 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
403 if(event.KeyInput.PressedDown)
\r
405 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
411 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
413 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
415 dstream<<DTIME<<"MyEventReceiver: "
\r
416 <<"Launching pause menu"<<std::endl;
\r
417 // It will delete itself by itself
\r
418 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
419 &g_active_menu_count))->drop();
\r
422 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
424 dstream<<DTIME<<"MyEventReceiver: "
\r
425 <<"Launching inventory"<<std::endl;
\r
426 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
427 &local_inventory, &inventory_action_queue,
\r
428 &g_active_menu_count))->drop();
\r
431 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
433 TextDest *dest = new TextDestChat(g_client);
\r
435 (new GUITextInputMenu(guienv, guiroot, -1,
\r
436 &g_active_menu_count, dest,
\r
441 // Material selection
\r
442 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
444 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
447 g_selected_item = 0;
\r
448 dstream<<DTIME<<"Selected item: "
\r
449 <<g_selected_item<<std::endl;
\r
452 // Viewing range selection
\r
453 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
455 JMutexAutoLock lock(g_range_mutex);
\r
456 if(g_viewing_range_all)
\r
458 g_viewing_range_all = false;
\r
459 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
463 g_viewing_range_all = true;
\r
464 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
468 // Print debug stacks
\r
469 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
471 dstream<<"-----------------------------------------"
\r
473 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
474 dstream<<"-----------------------------------------"
\r
476 debug_stacks_print();
\r
481 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
483 if(noMenuActive() == false)
\r
485 left_active = false;
\r
486 middle_active = false;
\r
487 right_active = false;
\r
491 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
492 left_active = event.MouseInput.isLeftPressed();
\r
493 middle_active = event.MouseInput.isMiddlePressed();
\r
494 right_active = event.MouseInput.isRightPressed();
\r
496 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
498 leftclicked = true;
\r
500 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
502 rightclicked = true;
\r
504 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
506 leftreleased = true;
\r
508 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
510 rightreleased = true;
\r
512 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
514 /*dstream<<"event.MouseInput.Wheel="
\r
515 <<event.MouseInput.Wheel<<std::endl;*/
\r
516 if(event.MouseInput.Wheel < 0)
\r
518 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
521 g_selected_item = 0;
\r
523 else if(event.MouseInput.Wheel > 0)
\r
525 if(g_selected_item > 0)
\r
528 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
537 // This is used to check whether a key is being held down
\r
538 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
540 return keyIsDown[keyCode];
\r
545 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
546 keyIsDown[i] = false;
\r
548 leftclicked = false;
\r
549 rightclicked = false;
\r
550 leftreleased = false;
\r
551 rightreleased = false;
\r
553 left_active = false;
\r
554 middle_active = false;
\r
555 right_active = false;
\r
566 bool rightreleased;
\r
569 bool middle_active;
\r
573 // We use this array to store the current state of each key
\r
574 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
577 IrrlichtDevice *m_device;
\r
586 virtual ~InputHandler()
\r
590 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
592 virtual v2s32 getMousePos() = 0;
\r
593 virtual void setMousePos(s32 x, s32 y) = 0;
\r
595 virtual bool getLeftState() = 0;
\r
596 virtual bool getRightState() = 0;
\r
598 virtual bool getLeftClicked() = 0;
\r
599 virtual bool getRightClicked() = 0;
\r
600 virtual void resetLeftClicked() = 0;
\r
601 virtual void resetRightClicked() = 0;
\r
603 virtual bool getLeftReleased() = 0;
\r
604 virtual bool getRightReleased() = 0;
\r
605 virtual void resetLeftReleased() = 0;
\r
606 virtual void resetRightReleased() = 0;
\r
608 virtual void step(float dtime) {};
\r
610 virtual void clear() {};
\r
613 InputHandler *g_input = NULL;
\r
615 class RealInputHandler : public InputHandler
\r
618 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
620 m_receiver(receiver)
\r
623 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
625 return m_receiver->IsKeyDown(keyCode);
\r
627 virtual v2s32 getMousePos()
\r
629 return m_device->getCursorControl()->getPosition();
\r
631 virtual void setMousePos(s32 x, s32 y)
\r
633 m_device->getCursorControl()->setPosition(x, y);
\r
636 virtual bool getLeftState()
\r
638 return m_receiver->left_active;
\r
640 virtual bool getRightState()
\r
642 return m_receiver->right_active;
\r
645 virtual bool getLeftClicked()
\r
647 return m_receiver->leftclicked;
\r
649 virtual bool getRightClicked()
\r
651 return m_receiver->rightclicked;
\r
653 virtual void resetLeftClicked()
\r
655 m_receiver->leftclicked = false;
\r
657 virtual void resetRightClicked()
\r
659 m_receiver->rightclicked = false;
\r
662 virtual bool getLeftReleased()
\r
664 return m_receiver->leftreleased;
\r
666 virtual bool getRightReleased()
\r
668 return m_receiver->rightreleased;
\r
670 virtual void resetLeftReleased()
\r
672 m_receiver->leftreleased = false;
\r
674 virtual void resetRightReleased()
\r
676 m_receiver->rightreleased = false;
\r
681 resetRightClicked();
\r
682 resetLeftClicked();
\r
685 IrrlichtDevice *m_device;
\r
686 MyEventReceiver *m_receiver;
\r
689 class RandomInputHandler : public InputHandler
\r
692 RandomInputHandler()
\r
694 leftclicked = false;
\r
695 rightclicked = false;
\r
696 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
697 keydown[i] = false;
\r
699 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
701 return keydown[keyCode];
\r
703 virtual v2s32 getMousePos()
\r
707 virtual void setMousePos(s32 x, s32 y)
\r
709 mousepos = v2s32(x,y);
\r
712 virtual bool getLeftState()
\r
716 virtual bool getRightState()
\r
721 virtual bool getLeftClicked()
\r
723 return leftclicked;
\r
725 virtual bool getRightClicked()
\r
727 return rightclicked;
\r
729 virtual void resetLeftClicked()
\r
731 leftclicked = false;
\r
733 virtual void resetRightClicked()
\r
735 rightclicked = false;
\r
738 virtual bool getLeftReleased()
\r
742 virtual bool getRightReleased()
\r
746 virtual void resetLeftReleased()
\r
749 virtual void resetRightReleased()
\r
753 virtual void step(float dtime)
\r
756 static float counter1 = 0;
\r
760 counter1 = 0.1*Rand(1,10);
\r
761 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
762 g_selected_material++;
\r
764 g_selected_material = 0;*/
\r
765 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
768 g_selected_item = 0;
\r
772 static float counter1 = 0;
\r
776 counter1 = 0.1*Rand(1, 40);
\r
777 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
781 static float counter1 = 0;
\r
785 counter1 = 0.1*Rand(1, 40);
\r
786 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
790 static float counter1 = 0;
\r
794 counter1 = 0.1*Rand(1, 40);
\r
795 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
799 static float counter1 = 0;
\r
803 counter1 = 0.1*Rand(1, 40);
\r
804 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
808 static float counter1 = 0;
\r
812 counter1 = 0.1*Rand(1, 20);
\r
813 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
817 static float counter1 = 0;
\r
821 counter1 = 0.1*Rand(1, 30);
\r
822 leftclicked = true;
\r
826 static float counter1 = 0;
\r
830 counter1 = 0.1*Rand(1, 20);
\r
831 rightclicked = true;
\r
834 mousepos += mousespeed;
\r
837 s32 Rand(s32 min, s32 max)
\r
839 return (rand()%(max-min+1))+min;
\r
842 bool keydown[KEY_KEY_CODES_COUNT];
\r
849 void updateViewingRange(f32 frametime, Client *client)
\r
851 // Range_all messes up frametime_avg
\r
852 if(g_viewing_range_all == true)
\r
855 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
857 // Initialize to the target value
\r
858 static float frametime_avg = 1.0/wanted_fps;
\r
859 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
860 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
862 static f32 counter = 0;
\r
864 counter -= frametime;
\r
867 //counter = 1.0; //seconds
\r
868 counter = 0.5; //seconds
\r
870 //float freetime_ratio = 0.2;
\r
871 //float freetime_ratio = 0.4;
\r
872 float freetime_ratio = FREETIME_RATIO;
\r
874 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
876 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
878 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
879 + frametime_avg / frametime_wanted / 2.0;*/
\r
881 //float fraction = frametime_avg / frametime_wanted;
\r
883 static bool fraction_is_good = false;
\r
885 //float fraction_good_threshold = 0.1;
\r
886 //float fraction_bad_threshold = 0.25;
\r
887 float fraction_good_threshold = 0.075;
\r
888 float fraction_bad_threshold = 0.125;
\r
889 float fraction_limit;
\r
890 // Use high limit if fraction is good AND the fraction would
\r
891 // lower the range. We want to keep the range fairly high.
\r
892 if(fraction_is_good && fraction > 1.0)
\r
893 fraction_limit = fraction_bad_threshold;
\r
895 fraction_limit = fraction_good_threshold;
\r
897 if(fabs(fraction - 1.0) < fraction_limit)
\r
899 fraction_is_good = true;
\r
904 fraction_is_good = false;
\r
907 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
908 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
909 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
910 <<" faction = "<<fraction<<std::endl;*/
\r
912 JMutexAutoLock lock(g_range_mutex);
\r
914 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
915 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
917 s16 n = (float)g_viewing_range_nodes / fraction;
\r
918 if(n < viewing_range_nodes_min)
\r
919 n = viewing_range_nodes_min;
\r
920 if(n > viewing_range_nodes_max)
\r
921 n = viewing_range_nodes_max;
\r
923 bool can_change = true;
\r
925 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
926 can_change = false;
\r
929 g_viewing_range_nodes = n;
\r
931 /*dstream<<"g_viewing_range_nodes = "
\r
932 <<g_viewing_range_nodes<<std::endl;*/
\r
935 class GUIQuickInventory : public IEventReceiver
\r
939 gui::IGUIEnvironment* env,
\r
940 gui::IGUIElement* parent,
\r
943 Inventory *inventory):
\r
944 m_itemcount(itemcount),
\r
945 m_inventory(inventory)
\r
947 core::rect<s32> imgsize(0,0,48,48);
\r
948 core::rect<s32> textsize(0,0,48,16);
\r
949 v2s32 spacing(0, 64);
\r
950 for(s32 i=0; i<m_itemcount; i++)
\r
952 m_images.push_back(env->addImage(
\r
953 imgsize + pos + spacing*i
\r
955 m_images[i]->setScaleImage(true);
\r
956 m_texts.push_back(env->addStaticText(
\r
958 textsize + pos + spacing*i,
\r
961 m_texts[i]->setBackgroundColor(
\r
962 video::SColor(128,0,0,0));
\r
963 m_texts[i]->setTextAlignment(
\r
965 gui::EGUIA_UPPERLEFT);
\r
969 virtual bool OnEvent(const SEvent& event)
\r
974 void setSelection(s32 i)
\r
983 start = m_selection - m_itemcount / 2;
\r
985 InventoryList *mainlist = m_inventory->getList("main");
\r
987 for(s32 i=0; i<m_itemcount; i++)
\r
991 if(j > (s32)mainlist->getSize() - 1)
\r
992 j -= mainlist->getSize();
\r
994 j += mainlist->getSize();
\r
996 InventoryItem *item = mainlist->getItem(j);
\r
1000 m_images[i]->setImage(NULL);
\r
1003 if(m_selection == j)
\r
1004 swprintf(t, 10, L"<-");
\r
1006 swprintf(t, 10, L"");
\r
1007 m_texts[i]->setText(t);
\r
1009 // The next ifs will segfault with a NULL pointer
\r
1014 m_images[i]->setImage(item->getImage());
\r
1017 if(m_selection == j)
\r
1018 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1020 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1021 m_texts[i]->setText(t);
\r
1027 core::array<gui::IGUIStaticText*> m_texts;
\r
1028 core::array<gui::IGUIImage*> m_images;
\r
1029 Inventory *m_inventory;
\r
1040 ChatLine(const std::wstring &a_text):
\r
1046 std::wstring text;
\r
1049 int main(int argc, char *argv[])
\r
1052 Low-level initialization
\r
1055 bool disable_stderr = false;
\r
1057 disable_stderr = true;
\r
1060 // Initialize debug streams
\r
1061 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1062 // Initialize debug stacks
\r
1063 debug_stacks_init();
\r
1065 DSTACK(__FUNCTION_NAME);
\r
1067 initializeMaterialProperties();
\r
1073 Parse command line
\r
1076 // List all allowed options
\r
1077 core::map<std::string, ValueSpec> allowed_options;
\r
1078 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1079 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1080 "Run server directly"));
\r
1081 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1082 "Load configuration from specified file"));
\r
1083 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1084 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1085 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1086 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1087 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1089 Settings cmd_args;
\r
1091 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1093 if(ret == false || cmd_args.getFlag("help"))
\r
1095 dstream<<"Allowed options:"<<std::endl;
\r
1096 for(core::map<std::string, ValueSpec>::Iterator
\r
1097 i = allowed_options.getIterator();
\r
1098 i.atEnd() == false; i++)
\r
1100 dstream<<" --"<<i.getNode()->getKey();
\r
1101 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1106 dstream<<" <value>";
\r
1108 dstream<<std::endl;
\r
1110 if(i.getNode()->getValue().help != NULL)
\r
1112 dstream<<" "<<i.getNode()->getValue().help
\r
1117 return cmd_args.getFlag("help") ? 0 : 1;
\r
1122 Basic initialization
\r
1125 // Initialize default settings
\r
1126 set_default_settings();
\r
1128 // Print startup message
\r
1129 dstream<<DTIME<<"minetest-c55"
\r
1130 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1131 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1134 // Set locale. This is for forcing '.' as the decimal point.
\r
1135 std::locale::global(std::locale("C"));
\r
1136 // This enables printing all characters in bitmap font
\r
1137 setlocale(LC_CTYPE, "en_US");
\r
1139 // Initialize sockets
\r
1141 atexit(sockets_cleanup);
\r
1151 // Path of configuration file in use
\r
1152 std::string configpath = "";
\r
1154 if(cmd_args.exists("config"))
\r
1156 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1159 dstream<<"Could not read configuration from \""
\r
1160 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1163 configpath = cmd_args.get("config");
\r
1167 const char *filenames[2] =
\r
1169 "../minetest.conf",
\r
1170 "../../minetest.conf"
\r
1173 for(u32 i=0; i<2; i++)
\r
1175 bool r = g_settings.readConfigFile(filenames[i]);
\r
1178 configpath = filenames[i];
\r
1184 // Initialize random seed
\r
1190 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1191 || cmd_args.getFlag("enable-unittests") == true)
\r
1197 Global range mutex
\r
1199 g_range_mutex.Init();
\r
1200 assert(g_range_mutex.IsInitialized());
\r
1202 // Read map parameters from settings
\r
1204 HMParams hm_params;
\r
1205 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1206 hm_params.randmax = g_settings.get("height_randmax");
\r
1207 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1208 hm_params.base = g_settings.get("height_base");
\r
1210 MapParams map_params;
\r
1211 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1212 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1218 std::cout<<std::endl<<std::endl;
\r
1221 <<" .__ __ __ "<<std::endl
\r
1222 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1223 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1224 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1225 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1226 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1229 std::cout<<std::endl;
\r
1230 //char templine[100];
\r
1234 if(cmd_args.exists("port"))
\r
1236 port = cmd_args.getU16("port");
\r
1240 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1241 std::cout<<"-> "<<port<<std::endl;
\r
1244 if(cmd_args.getFlag("server"))
\r
1246 DSTACK("Dedicated server branch");
\r
1248 std::cout<<std::endl;
\r
1249 std::cout<<"========================"<<std::endl;
\r
1250 std::cout<<"Running dedicated server"<<std::endl;
\r
1251 std::cout<<"========================"<<std::endl;
\r
1252 std::cout<<std::endl;
\r
1254 Server server("../map", hm_params, map_params);
\r
1255 server.start(port);
\r
1259 // This is kind of a hack but can be done like this
\r
1260 // because server.step() is very light
\r
1262 server.step(0.030);
\r
1264 static int counter = 0;
\r
1270 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1271 core::list<PlayerInfo>::Iterator i;
\r
1272 static u32 sum_old = 0;
\r
1273 u32 sum = PIChecksum(list);
\r
1274 if(sum != sum_old)
\r
1276 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1277 for(i=list.begin(); i!=list.end(); i++)
\r
1279 i->PrintLine(&std::cout);
\r
1289 bool hosting = false;
\r
1290 char connect_name[100] = "";
\r
1292 if(cmd_args.exists("address"))
\r
1294 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1296 else if(is_yes(g_settings.get("host_game")) == false)
\r
1298 if(g_settings.get("address") != "")
\r
1300 std::cout<<g_settings.get("address")<<std::endl;
\r
1301 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1305 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1306 std::cin.getline(connect_name, 100);
\r
1310 if(connect_name[0] == 0){
\r
1311 snprintf(connect_name, 100, "127.0.0.1");
\r
1316 std::cout<<"> Hosting game"<<std::endl;
\r
1318 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1320 char playername[PLAYERNAME_SIZE] = "";
\r
1321 if(g_settings.get("name") != "")
\r
1323 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1327 std::cout<<"Name of player: ";
\r
1328 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1330 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1333 Resolution selection
\r
1336 bool fullscreen = false;
\r
1337 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1338 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1342 MyEventReceiver receiver;
\r
1344 video::E_DRIVER_TYPE driverType;
\r
1347 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1348 driverType = video::EDT_OPENGL;
\r
1350 driverType = video::EDT_OPENGL;
\r
1353 // create device and exit if creation failed
\r
1355 IrrlichtDevice *device;
\r
1356 device = createDevice(driverType,
\r
1357 core::dimension2d<u32>(screenW, screenH),
\r
1358 16, fullscreen, false, false, &receiver);
\r
1361 return 1; // could not create selected driver.
\r
1363 g_device = device;
\r
1364 g_irrlicht = new IrrlichtWrapper(device);
\r
1366 //g_device = device;
\r
1368 device->setResizable(true);
\r
1370 bool random_input = g_settings.getBool("random_input")
\r
1371 || cmd_args.getFlag("random-input");
\r
1373 g_input = new RandomInputHandler();
\r
1375 g_input = new RealInputHandler(device, &receiver);
\r
1378 Continue initialization
\r
1381 video::IVideoDriver* driver = device->getVideoDriver();
\r
1384 This changes the minimum allowed number of vertices in a VBO
\r
1386 //driver->setMinHardwareBufferVertexCount(1);
\r
1388 scene::ISceneManager* smgr = device->getSceneManager();
\r
1390 guienv = device->getGUIEnvironment();
\r
1391 gui::IGUISkin* skin = guienv->getSkin();
\r
1392 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1394 skin->setFont(font);
\r
1396 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1397 dstream<<"text_height="<<text_height<<std::endl;
\r
1399 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1400 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1401 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1402 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1403 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1404 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1406 const wchar_t *text = L"Loading and connecting...";
\r
1407 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1408 core::vector2d<s32> textsize(300, text_height);
\r
1409 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1411 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1412 text, textrect, false, false);
\r
1413 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1415 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1416 guienv->drawAll();
\r
1417 driver->endScene();
\r
1420 Preload some textures
\r
1423 tile_materials_preload(g_irrlicht);
\r
1426 Make a scope here for the client so that it gets removed
\r
1427 before the irrlicht device
\r
1431 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1436 SharedPtr<Server> server;
\r
1438 server = new Server("../map", hm_params, map_params);
\r
1439 server->start(port);
\r
1446 Client client(device, playername,
\r
1448 g_viewing_range_nodes,
\r
1449 g_viewing_range_all);
\r
1451 g_client = &client;
\r
1453 Address connect_address(0,0,0,0, port);
\r
1455 connect_address.Resolve(connect_name);
\r
1457 catch(ResolveError &e)
\r
1459 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1463 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1464 client.connect(connect_address);
\r
1467 while(client.connectedAndInitialized() == false)
\r
1470 if(server != NULL){
\r
1471 server->step(0.1);
\r
1476 catch(con::PeerNotFoundException &e)
\r
1478 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1485 /*scene::ISceneNode* skybox;
\r
1486 skybox = smgr->addSkyBoxSceneNode(
\r
1487 driver->getTexture("../data/skybox2.png"),
\r
1488 driver->getTexture("../data/skybox3.png"),
\r
1489 driver->getTexture("../data/skybox1.png"),
\r
1490 driver->getTexture("../data/skybox1.png"),
\r
1491 driver->getTexture("../data/skybox1.png"),
\r
1492 driver->getTexture("../data/skybox1.png"));*/
\r
1495 Create the camera node
\r
1498 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1499 0, // Camera parent
\r
1500 v3f(BS*100, BS*2, BS*100), // Look from
\r
1501 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1505 if(camera == NULL)
\r
1508 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1510 camera->setFOV(FOV_ANGLE);
\r
1512 // Just so big a value that everything rendered is visible
\r
1513 camera->setFarValue(100000*BS);
\r
1515 f32 camera_yaw = 0; // "right/left"
\r
1516 f32 camera_pitch = 0; // "up/down"
\r
1522 gui_loadingtext->remove();
\r
1525 Add some gui stuff
\r
1528 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1529 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1532 We need some kind of a root node to be able to add
\r
1533 custom elements directly on the screen.
\r
1534 Otherwise they won't be automatically drawn.
\r
1536 guiroot = guienv->addStaticText(L"",
\r
1537 core::rect<s32>(0, 0, 10000, 10000));
\r
1539 // Test the text input system
\r
1540 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1542 /*GUIMessageMenu *menu =
\r
1543 new GUIMessageMenu(guienv, guiroot, -1,
\r
1544 &g_active_menu_count,
\r
1548 // Launch pause menu
\r
1549 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1550 &g_active_menu_count))->drop();
\r
1552 // First line of debug text
\r
1553 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1555 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1557 // Second line of debug text
\r
1558 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1560 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1563 // At the middle of the screen
\r
1564 // Object infos are shown in this
\r
1565 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1567 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1571 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1572 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1573 core::rect<s32>(70, 60, 795, 150),
\r
1575 chat_guitext->setBackgroundColor(video::SColor(96,0,0,0));
\r
1576 core::list<ChatLine> chat_lines;
\r
1579 Some statistics are collected in these
\r
1582 u32 beginscenetime = 0;
\r
1583 u32 scenetime = 0;
\r
1584 u32 endscenetime = 0;
\r
1587 //throw con::PeerNotFoundException("lol");
\r
1593 bool first_loop_after_window_activation = true;
\r
1595 // Time is in milliseconds
\r
1596 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1597 // NOTE: Have to call run() between calls of this to update the timer
\r
1598 u32 lasttime = device->getTimer()->getTime();
\r
1600 while(device->run())
\r
1603 Run global IrrlichtWrapper's main thread processing stuff
\r
1605 g_irrlicht->Run();
\r
1608 Random calculations
\r
1610 v2u32 screensize = driver->getScreenSize();
\r
1611 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1613 // Hilight boxes collected during the loop and displayed
\r
1614 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1617 std::wstring infotext;
\r
1619 //TimeTaker //timer1("//timer1");
\r
1621 // Time of frame without fps limit
\r
1625 // not using getRealTime is necessary for wine
\r
1626 u32 time = device->getTimer()->getTime();
\r
1627 if(time > lasttime)
\r
1628 busytime_u32 = time - lasttime;
\r
1631 busytime = busytime_u32 / 1000.0;
\r
1634 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1636 // Absolutelu necessary for wine!
\r
1643 updateViewingRange(busytime, &client);
\r
1650 float fps_max = g_settings.getFloat("fps_max");
\r
1651 u32 frametime_min = 1000./fps_max;
\r
1653 if(busytime_u32 < frametime_min)
\r
1655 u32 sleeptime = frametime_min - busytime_u32;
\r
1656 device->sleep(sleeptime);
\r
1660 // Absolutelu necessary for wine!
\r
1664 Time difference calculation
\r
1666 f32 dtime; // in seconds
\r
1668 u32 time = device->getTimer()->getTime();
\r
1669 if(time > lasttime)
\r
1670 dtime = (time - lasttime) / 1000.0;
\r
1676 Time average and jitter calculation
\r
1679 static f32 dtime_avg1 = 0.0;
\r
1680 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1681 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1683 static f32 dtime_jitter1_max_sample = 0.0;
\r
1684 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1686 static f32 jitter1_max = 0.0;
\r
1687 static f32 counter = 0.0;
\r
1688 if(dtime_jitter1 > jitter1_max)
\r
1689 jitter1_max = dtime_jitter1;
\r
1694 dtime_jitter1_max_sample = jitter1_max;
\r
1695 dtime_jitter1_max_fraction
\r
1696 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1697 jitter1_max = 0.0;
\r
1700 Control freetime ratio
\r
1702 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1704 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1705 g_freetime_ratio += 0.01;
\r
1709 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1710 g_freetime_ratio -= 0.01;
\r
1716 Busytime average and jitter calculation
\r
1719 static f32 busytime_avg1 = 0.0;
\r
1720 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1721 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1723 static f32 busytime_jitter1_max_sample = 0.0;
\r
1724 static f32 busytime_jitter1_min_sample = 0.0;
\r
1726 static f32 jitter1_max = 0.0;
\r
1727 static f32 jitter1_min = 0.0;
\r
1728 static f32 counter = 0.0;
\r
1729 if(busytime_jitter1 > jitter1_max)
\r
1730 jitter1_max = busytime_jitter1;
\r
1731 if(busytime_jitter1 < jitter1_min)
\r
1732 jitter1_min = busytime_jitter1;
\r
1734 if(counter > 0.0){
\r
1736 busytime_jitter1_max_sample = jitter1_max;
\r
1737 busytime_jitter1_min_sample = jitter1_min;
\r
1738 jitter1_max = 0.0;
\r
1739 jitter1_min = 0.0;
\r
1744 Debug info for client
\r
1747 static float counter = 0.0;
\r
1752 client.printDebugInfo(std::cout);
\r
1757 Input handler step()
\r
1759 g_input->step(dtime);
\r
1762 Player speed control
\r
1771 bool a_superspeed,
\r
1774 PlayerControl control(
\r
1775 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1776 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1777 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1778 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1779 g_input->isKeyDown(irr::KEY_SPACE),
\r
1780 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1784 client.setPlayerControl(control);
\r
1788 Process environment
\r
1792 //TimeTaker timer("client.step(dtime)");
\r
1793 client.step(dtime);
\r
1794 //client.step(dtime_avg1);
\r
1797 if(server != NULL)
\r
1799 //TimeTaker timer("server->step(dtime)");
\r
1800 server->step(dtime);
\r
1803 v3f player_position = client.getPlayerPosition();
\r
1805 //TimeTaker //timer2("//timer2");
\r
1808 Mouse and camera control
\r
1811 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1814 device->getCursorControl()->setVisible(false);
\r
1816 if(first_loop_after_window_activation){
\r
1817 //std::cout<<"window active, first loop"<<std::endl;
\r
1818 first_loop_after_window_activation = false;
\r
1821 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1822 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1823 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1824 camera_yaw -= dx*0.2;
\r
1825 camera_pitch += dy*0.2;
\r
1826 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1827 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1829 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1832 device->getCursorControl()->setVisible(true);
\r
1834 //std::cout<<"window inactive"<<std::endl;
\r
1835 first_loop_after_window_activation = true;
\r
1838 camera_yaw = wrapDegrees(camera_yaw);
\r
1839 camera_pitch = wrapDegrees(camera_pitch);
\r
1841 v3f camera_direction = v3f(0,0,1);
\r
1842 camera_direction.rotateYZBy(camera_pitch);
\r
1843 camera_direction.rotateXZBy(camera_yaw);
\r
1845 v3f camera_position =
\r
1846 player_position + v3f(0, BS+BS/2, 0);
\r
1848 camera->setPosition(camera_position);
\r
1849 // *100.0 helps in large map coordinates
\r
1850 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1852 if(FIELD_OF_VIEW_TEST){
\r
1853 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1854 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1857 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1858 //TimeTaker timer("client.updateCamera");
\r
1859 client.updateCamera(camera_position, camera_direction);
\r
1863 //TimeTaker //timer3("//timer3");
\r
1866 Calculate what block is the crosshair pointing to
\r
1869 //u32 t1 = device->getTimer()->getRealTime();
\r
1871 //f32 d = 4; // max. distance
\r
1872 f32 d = 4; // max. distance
\r
1873 core::line3d<f32> shootline(camera_position,
\r
1874 camera_position + camera_direction * BS * (d+1));
\r
1876 MapBlockObject *selected_object = client.getSelectedObject
\r
1877 (d*BS, camera_position, shootline);
\r
1880 If it's pointing to a MapBlockObject
\r
1883 if(selected_object != NULL)
\r
1885 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1887 core::aabbox3d<f32> box_on_map
\r
1888 = selected_object->getSelectionBoxOnMap();
\r
1890 hilightboxes.push_back(box_on_map);
\r
1892 infotext = narrow_to_wide(selected_object->infoText());
\r
1894 if(g_input->getLeftClicked())
\r
1896 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1897 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1898 selected_object->getId(), g_selected_item);
\r
1900 else if(g_input->getRightClicked())
\r
1902 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1904 Check if we want to modify the object ourselves
\r
1906 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1908 dstream<<"Sign object right-clicked"<<std::endl;
\r
1910 if(random_input == false)
\r
1912 // Get a new text for it
\r
1914 TextDest *dest = new TextDestSign(
\r
1915 selected_object->getBlock()->getPos(),
\r
1916 selected_object->getId(),
\r
1919 SignObject *sign_object = (SignObject*)selected_object;
\r
1921 std::wstring wtext =
\r
1922 narrow_to_wide(sign_object->getText());
\r
1924 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1925 &g_active_menu_count, dest,
\r
1930 Otherwise pass the event to the server as-is
\r
1934 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1935 selected_object->getId(), g_selected_item);
\r
1939 else // selected_object == NULL
\r
1943 Find out which node we are pointing at
\r
1946 bool nodefound = false;
\r
1948 v3s16 neighbourpos;
\r
1949 core::aabbox3d<f32> nodefacebox;
\r
1950 f32 mindistance = BS * 1001;
\r
1952 v3s16 pos_i = floatToInt(player_position);
\r
1954 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1958 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1959 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1960 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1961 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1962 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1963 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1965 for(s16 y = ystart; y <= yend; y++)
\r
1966 for(s16 z = zstart; z <= zend; z++)
\r
1967 for(s16 x = xstart; x <= xend; x++)
\r
1972 n = client.getNode(v3s16(x,y,z));
\r
1973 if(content_pointable(n.d) == false)
\r
1976 catch(InvalidPositionException &e)
\r
1982 v3f npf = intToFloat(np);
\r
1987 v3s16(0,0,1), // back
\r
1988 v3s16(0,1,0), // top
\r
1989 v3s16(1,0,0), // right
\r
1990 v3s16(0,0,-1), // front
\r
1991 v3s16(0,-1,0), // bottom
\r
1992 v3s16(-1,0,0), // left
\r
1998 if(n.d == CONTENT_TORCH)
\r
2000 v3s16 dir = unpackDir(n.dir);
\r
2001 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2002 dir_f *= BS/2 - BS/6 - BS/20;
\r
2003 v3f cpf = npf + dir_f;
\r
2004 f32 distance = (cpf - camera_position).getLength();
\r
2006 core::aabbox3d<f32> box;
\r
2009 if(dir == v3s16(0,-1,0))
\r
2011 box = core::aabbox3d<f32>(
\r
2012 npf - v3f(BS/6, BS/2, BS/6),
\r
2013 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2017 else if(dir == v3s16(0,1,0))
\r
2019 box = core::aabbox3d<f32>(
\r
2020 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2021 npf + v3f(BS/6, BS/2, BS/6)
\r
2027 box = core::aabbox3d<f32>(
\r
2028 cpf - v3f(BS/6, BS/3, BS/6),
\r
2029 cpf + v3f(BS/6, BS/3, BS/6)
\r
2033 if(distance < mindistance)
\r
2035 if(box.intersectsWithLine(shootline))
\r
2039 neighbourpos = np;
\r
2040 mindistance = distance;
\r
2041 nodefacebox = box;
\r
2050 for(u16 i=0; i<6; i++)
\r
2052 v3f dir_f = v3f(dirs[i].X,
\r
2053 dirs[i].Y, dirs[i].Z);
\r
2054 v3f centerpoint = npf + dir_f * BS/2;
\r
2056 (centerpoint - camera_position).getLength();
\r
2058 if(distance < mindistance)
\r
2060 core::CMatrix4<f32> m;
\r
2061 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2063 // This is the back face
\r
2064 v3f corners[2] = {
\r
2065 v3f(BS/2, BS/2, BS/2),
\r
2066 v3f(-BS/2, -BS/2, BS/2+d)
\r
2069 for(u16 j=0; j<2; j++)
\r
2071 m.rotateVect(corners[j]);
\r
2072 corners[j] += npf;
\r
2075 core::aabbox3d<f32> facebox(corners[0]);
\r
2076 facebox.addInternalPoint(corners[1]);
\r
2078 if(facebox.intersectsWithLine(shootline))
\r
2082 neighbourpos = np + dirs[i];
\r
2083 mindistance = distance;
\r
2084 nodefacebox = facebox;
\r
2086 } // if distance < mindistance
\r
2088 } // regular block
\r
2091 static float nodig_delay_counter = 0.0;
\r
2095 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2097 static float dig_time = 0.0;
\r
2098 static u16 dig_index = 0;
\r
2100 hilightboxes.push_back(nodefacebox);
\r
2102 if(g_input->getLeftReleased())
\r
2104 client.clearTempMod(nodepos);
\r
2108 if(nodig_delay_counter > 0.0)
\r
2110 nodig_delay_counter -= dtime;
\r
2114 if(nodepos != nodepos_old)
\r
2116 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2117 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2119 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2121 client.clearTempMod(nodepos_old);
\r
2126 if(g_input->getLeftClicked() ||
\r
2127 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2129 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2130 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2132 if(g_input->getLeftClicked())
\r
2134 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2136 if(g_input->getLeftState())
\r
2138 MapNode n = client.getNode(nodepos);
\r
2140 // Get tool name. Default is "" = bare hands
\r
2141 std::string toolname = "";
\r
2142 InventoryList *mlist = local_inventory.getList("main");
\r
2145 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2146 if(item && (std::string)item->getName() == "ToolItem")
\r
2148 ToolItem *titem = (ToolItem*)item;
\r
2149 toolname = titem->getToolName();
\r
2153 // Get digging properties for material and tool
\r
2154 u8 material = n.d;
\r
2155 DiggingProperties prop =
\r
2156 getDiggingProperties(material, toolname);
\r
2158 float dig_time_complete = 0.0;
\r
2160 if(prop.diggable == false)
\r
2162 /*dstream<<"Material "<<(int)material
\r
2163 <<" not diggable with \""
\r
2164 <<toolname<<"\""<<std::endl;*/
\r
2165 // I guess nobody will wait for this long
\r
2166 dig_time_complete = 10000000.0;
\r
2170 dig_time_complete = prop.time;
\r
2173 if(dig_time_complete >= 0.001)
\r
2175 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2176 * dig_time/dig_time_complete);
\r
2178 // This is for torches
\r
2181 dig_index = CRACK_ANIMATION_LENGTH;
\r
2184 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2186 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2187 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2191 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2192 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2193 client.clearTempMod(nodepos);
\r
2194 client.removeNode(nodepos);
\r
2198 nodig_delay_counter = dig_time_complete
\r
2199 / (float)CRACK_ANIMATION_LENGTH;
\r
2201 // We don't want a corresponding delay to
\r
2202 // very time consuming nodes
\r
2203 if(nodig_delay_counter > 0.5)
\r
2205 nodig_delay_counter = 0.5;
\r
2207 // We want a slight delay to very little
\r
2208 // time consuming nodes
\r
2209 //float mindelay = 0.15;
\r
2210 float mindelay = 0.20;
\r
2211 if(nodig_delay_counter < mindelay)
\r
2213 nodig_delay_counter = mindelay;
\r
2217 dig_time += dtime;
\r
2221 if(g_input->getRightClicked())
\r
2223 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2224 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2227 nodepos_old = nodepos;
\r
2232 } // selected_object == NULL
\r
2234 g_input->resetLeftClicked();
\r
2235 g_input->resetRightClicked();
\r
2237 if(g_input->getLeftReleased())
\r
2239 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2241 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2243 if(g_input->getRightReleased())
\r
2245 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2249 g_input->resetLeftReleased();
\r
2250 g_input->resetRightReleased();
\r
2253 Calculate stuff for drawing
\r
2256 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2258 u32 daynight_ratio = client.getDayNightRatio();
\r
2259 video::SColor bgcolor = video::SColor(
\r
2261 skycolor.getRed() * daynight_ratio / 1000,
\r
2262 skycolor.getGreen() * daynight_ratio / 1000,
\r
2263 skycolor.getBlue() * daynight_ratio / 1000);
\r
2269 if(g_settings.getBool("enable_fog") == true)
\r
2271 f32 range = g_viewing_range_nodes * BS;
\r
2272 if(g_viewing_range_all)
\r
2273 range = 100000*BS;
\r
2277 video::EFT_FOG_LINEAR,
\r
2281 false, // pixel fog
\r
2282 false // range fog
\r
2288 Update gui stuff (0ms)
\r
2291 //TimeTaker guiupdatetimer("Gui updating");
\r
2294 wchar_t temptext[150];
\r
2296 static float drawtime_avg = 0;
\r
2297 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2298 static float beginscenetime_avg = 0;
\r
2299 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2300 static float scenetime_avg = 0;
\r
2301 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2302 static float endscenetime_avg = 0;
\r
2303 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2305 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2307 L", R: range_all=%i"
\r
2309 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2311 g_viewing_range_all,
\r
2313 beginscenetime_avg,
\r
2318 guitext->setText(temptext);
\r
2322 wchar_t temptext[150];
\r
2323 swprintf(temptext, 150,
\r
2324 L"(% .1f, % .1f, % .1f)"
\r
2325 L" (% .3f < btime_jitter < % .3f"
\r
2326 L", dtime_jitter = % .1f %%)",
\r
2327 player_position.X/BS,
\r
2328 player_position.Y/BS,
\r
2329 player_position.Z/BS,
\r
2330 busytime_jitter1_min_sample,
\r
2331 busytime_jitter1_max_sample,
\r
2332 dtime_jitter1_max_fraction * 100.0
\r
2335 guitext2->setText(temptext);
\r
2339 guitext_info->setText(infotext.c_str());
\r
2343 Get chat messages from client
\r
2346 // Get new messages
\r
2347 std::wstring message;
\r
2348 while(client.getChatMessage(message))
\r
2350 chat_lines.push_back(ChatLine(message));
\r
2351 /*if(chat_lines.size() > 6)
\r
2353 core::list<ChatLine>::Iterator
\r
2354 i = chat_lines.begin();
\r
2355 chat_lines.erase(i);
\r
2358 // Append them to form the whole static text and throw
\r
2359 // it to the gui element
\r
2360 std::wstring whole;
\r
2361 // This will correspond to the line number counted from
\r
2362 // top to bottom, from size-1 to 0
\r
2363 s16 line_number = chat_lines.size();
\r
2364 // Count of messages to be removed from the top
\r
2365 u16 to_be_removed_count = 0;
\r
2366 for(core::list<ChatLine>::Iterator
\r
2367 i = chat_lines.begin();
\r
2368 i != chat_lines.end(); i++)
\r
2370 // After this, line number is valid for this loop
\r
2373 (*i).age += dtime;
\r
2375 This results in a maximum age of 60*6 to the
\r
2376 lowermost line and a maximum of 6 lines
\r
2378 float allowed_age = (6-line_number) * 60.0;
\r
2380 if((*i).age > allowed_age)
\r
2382 to_be_removed_count++;
\r
2385 whole += (*i).text + L'\n';
\r
2387 for(u16 i=0; i<to_be_removed_count; i++)
\r
2389 core::list<ChatLine>::Iterator
\r
2390 it = chat_lines.begin();
\r
2391 chat_lines.erase(it);
\r
2393 chat_guitext->setText(whole.c_str());
\r
2394 // Update gui element size and position
\r
2395 core::rect<s32> rect(
\r
2397 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2398 screensize.X - 10,
\r
2401 chat_guitext->setRelativePosition(rect);
\r
2403 if(chat_lines.size() == 0)
\r
2404 chat_guitext->setVisible(false);
\r
2406 chat_guitext->setVisible(true);
\r
2413 static u16 old_selected_item = 65535;
\r
2414 if(client.getLocalInventoryUpdated()
\r
2415 || g_selected_item != old_selected_item)
\r
2417 old_selected_item = g_selected_item;
\r
2418 //std::cout<<"Updating local inventory"<<std::endl;
\r
2419 client.getLocalInventory(local_inventory);
\r
2420 quick_inventory->setSelection(g_selected_item);
\r
2421 quick_inventory->update();
\r
2425 Send actions returned by the inventory menu
\r
2427 while(inventory_action_queue.size() != 0)
\r
2429 InventoryAction *a = inventory_action_queue.pop_front();
\r
2431 client.sendInventoryAction(a);
\r
2440 TimeTaker drawtimer("Drawing");
\r
2444 TimeTaker timer("beginScene");
\r
2445 driver->beginScene(true, true, bgcolor);
\r
2446 //driver->beginScene(false, true, bgcolor);
\r
2447 beginscenetime = timer.stop(true);
\r
2452 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2455 TimeTaker timer("smgr");
\r
2457 scenetime = timer.stop(true);
\r
2461 //TimeTaker timer9("auxiliary drawings");
\r
2465 //TimeTaker //timer10("//timer10");
\r
2467 video::SMaterial m;
\r
2469 m.Lighting = false;
\r
2470 driver->setMaterial(m);
\r
2472 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2474 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2475 i != hilightboxes.end(); i++)
\r
2477 /*std::cout<<"hilightbox min="
\r
2478 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2480 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2482 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2488 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2489 displaycenter + core::vector2d<s32>(10,0),
\r
2490 video::SColor(255,255,255,255));
\r
2491 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2492 displaycenter + core::vector2d<s32>(0,10),
\r
2493 video::SColor(255,255,255,255));
\r
2498 //TimeTaker //timer11("//timer11");
\r
2504 guienv->drawAll();
\r
2508 TimeTaker timer("endScene");
\r
2509 driver->endScene();
\r
2510 endscenetime = timer.stop(true);
\r
2513 drawtime = drawtimer.stop(true);
\r
2519 static s16 lastFPS = 0;
\r
2520 //u16 fps = driver->getFPS();
\r
2521 u16 fps = (1.0/dtime_avg1);
\r
2523 if (lastFPS != fps)
\r
2525 core::stringw str = L"Minetest [";
\r
2526 str += driver->getName();
\r
2530 device->setWindowCaption(str.c_str());
\r
2536 device->yield();*/
\r
2539 delete quick_inventory;
\r
2541 } // client is deleted at this point
\r
2546 In the end, delete the Irrlicht device.
\r
2551 Update configuration file
\r
2553 /*if(configpath != "")
\r
2555 g_settings.updateConfigFile(configpath.c_str());
\r
2559 catch(con::PeerNotFoundException &e)
\r
2561 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2565 GUIMessageMenu *menu =
\r
2566 new GUIMessageMenu(guienv, guiroot, -1,
\r
2567 &g_active_menu_count,
\r
2568 L"Connection timed out");
\r
2570 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
2572 dstream<<"Created menu"<<std::endl;
\r
2574 while(g_device->run() && menu->getStatus() == false)
\r
2576 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2577 guienv->drawAll();
\r
2578 driver->endScene();
\r
2581 dstream<<"Dropping menu"<<std::endl;
\r
2586 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2588 This is what has to be done in every thread to get suitable debug info
\r
2590 catch(std::exception &e)
\r
2592 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2593 <<e.what()<<std::endl;
\r
2598 debugstreams_deinit();
\r