3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
\r
5 This program is free software; you can redistribute it and/or modify
\r
6 it under the terms of the GNU General Public License as published by
\r
7 the Free Software Foundation; either version 2 of the License, or
\r
8 (at your option) any later version.
\r
10 This program is distributed in the hope that it will be useful,
\r
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 GNU General Public License for more details.
\r
15 You should have received a copy of the GNU General Public License along
\r
16 with this program; if not, write to the Free Software Foundation, Inc.,
\r
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
21 =============================== NOTES ==============================
\r
22 NOTE: Things starting with TODO are sometimes only suggestions.
\r
24 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
25 is an apparanet memory leak in irrlicht when using it (not sure)
\r
27 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
28 NOTE: Global locale is now set at initialization
\r
30 SUGG: Fix address to be ipv6 compatible
\r
32 FIXME: When a new sector is generated, it may change the ground level
\r
33 of it's and it's neighbors border that two blocks that are
\r
34 above and below each other and that are generated before and
\r
35 after the sector heightmap generation (order doesn't matter),
\r
36 can have a small gap between each other at the border.
\r
37 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
38 using for UnlimitedHeightmap? (getting all neighbors
\r
41 SUGG: Transfer more blocks in a single packet
\r
42 SUGG: A blockdata combiner class, to which blocks are added and at
\r
43 destruction it sends all the stuff in as few packets as possible.
\r
45 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
46 SUGG: Fetch stuff mainly from the viewing direction
\r
48 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
49 - This enables saving many packets and making a faster connection
\r
50 - This also enables server to check if client has received the
\r
51 most recent block sent, for example.
\r
52 SUGG: Add a sane bandwidth throttling system to Connection
\r
54 SUGG: More fine-grained control of client's dumping of blocks from
\r
56 - ...What does this mean in the first place?
\r
58 SUGG: A map editing mode (similar to dedicated server mode)
\r
60 SUGG: Add a time value to the param of footstepped grass and check it
\r
61 against a global timer when a block is accessed, to make old
\r
64 SUGG: Make a copy of close-range environment on client for showing
\r
65 on screen, with minimal mutexes to slow down the main loop
\r
67 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
68 it by sending more stuff in a single packet.
\r
69 - Add a packet queue to RemoteClient, from which packets will be
\r
70 combined with object data packets
\r
71 - This is not exactly trivial: the object data packets are
\r
72 sometimes very big by themselves
\r
74 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
75 - This will allow saving ages of rats on disk but not sending
\r
78 SUGG: Implement lighting using VoxelManipulator
\r
79 - Would it be significantly faster?
\r
81 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
82 - Does their position get saved to a border value or something?
\r
83 - Does this happen anymore?
\r
85 SUGG: MovingObject::move and Player::move are basically the same.
\r
88 SUGG: Implement a "Fast check queue" (a queue with a map for checking
\r
89 if something is already in it)
\r
90 - Use it in active block queue in water flowing
\r
92 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
93 need an additional metadata field for the texts
\r
94 - This is also needed for item container chests
\r
96 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
98 SUGG: A version number to blocks, which increments when the block is
\r
99 modified (node add/remove, water update, lighting update)
\r
100 - This can then be used to make sure the most recent version of
\r
101 a block has been sent to client
\r
103 SUGG: Make the amount of blocks sending to client and the total
\r
104 amount of blocks dynamically limited. Transferring blocks is the
\r
105 main network eater of this system, so it is the one that has
\r
106 to be throttled so that RTTs stay low.
\r
108 SUGG: Meshes of blocks could be split into 6 meshes facing into
\r
109 different directions and then only those drawn that need to be
\r
110 - Also an 1-dimensional tile map would be nice probably
\r
112 TODO: Untie client network operations from framerate
\r
113 - Needs some input queues or something
\r
114 - Not really necessary?
\r
116 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
118 - That is >500 vertices
\r
120 TODO: Better dungeons
\r
123 TODO: Startup and configuration menu
\r
125 TODO: There are some lighting-related todos and fixmes in
\r
126 ServerMap::emergeBlock
\r
128 TODO: Proper handling of spawning place (try to find something that
\r
129 is not in the middle of an ocean (some land to stand on at
\r
130 least) and save it in map config.
\r
132 TODO: Players to only be hidden when the client quits.
\r
133 TODO: - Players to be saved on disk, with inventory
\r
134 TODO: Players to be saved as text in map/players/<name>
\r
135 TODO: Player inventory to be saved on disk
\r
137 TODO: Make fetching sector's blocks more efficient when rendering
\r
138 sectors that have very large amounts of blocks (on client)
\r
140 TODO: Make the video backend selectable
\r
142 Block object server side:
\r
143 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
144 - For all blocks in the buffer, objects are stepped(). This
\r
145 means they are active.
\r
146 - TODO: A global active buffer is needed for the server
\r
147 - TODO: A timestamp to blocks
\r
148 - TODO: All blocks going in and out of the buffer are recorded.
\r
149 - TODO: For outgoing blocks, timestamp is written.
\r
150 - TODO: For incoming blocks, time difference is calculated and
\r
151 objects are stepped according to it.
\r
153 TODO: Copy the text of the last picked sign to inventory in creative
\r
156 TODO: Get rid of GotSplitPacketException
\r
158 TODO: Check what goes wrong with caching map to disk (Kray)
\r
160 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
162 TODO: TOSERVER_LEAVE
\r
164 TODO: Better handling of objects and mobs
\r
166 - There has to be some way to do it with less spaghetti code
\r
167 - Make separate classes for client and server
\r
168 - Client should not discriminate between blocks, server should
\r
169 - Make other players utilize the same framework
\r
170 - This is also needed for objects that don't get sent to client
\r
171 but are used for triggers etc
\r
173 TODO: Draw big amounts of torches better (that is, throw them in the
\r
174 same meshbuffer (can the meshcollector class be used?))
\r
176 TODO: Check if the usage of Client::isFetchingBlocks() in
\r
177 updateViewingRange() actually does something
\r
179 TODO: Make an option to the server to disable building and digging near
\r
180 the starting position
\r
183 ======================================================================
\r
185 TODO: Tool capability table: Which materials, at what speed, how much
\r
187 TODO: Transferring of the table from server to client
\r
189 ======================================================================
\r
194 Setting this to 1 enables a special camera mode that forces
\r
195 the renderers to think that the camera statically points from
\r
196 the starting place to a static direction.
\r
198 This allows one to move around with the player and see what
\r
199 is actually drawn behind solid things and behind the player.
\r
201 #define FIELD_OF_VIEW_TEST 0
\r
203 #ifdef UNITTEST_DISABLE
\r
205 #pragma message ("Disabling unit tests")
\r
207 #warning "Disabling unit tests"
\r
209 // Disable unit tests
\r
210 #define ENABLE_TESTS 0
\r
212 // Enable unit tests
\r
213 #define ENABLE_TESTS 1
\r
217 #pragma comment(lib, "Irrlicht.lib")
\r
218 #pragma comment(lib, "jthread.lib")
\r
219 #pragma comment(lib, "zlibwapi.lib")
\r
220 // This would get rid of the console window
\r
221 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
224 #include <iostream>
\r
226 #include <jmutexautolock.h>
\r
227 #include <locale.h>
\r
228 #include "common_irrlicht.h"
\r
231 #include "player.h"
\r
234 #include "environment.h"
\r
235 #include "server.h"
\r
236 #include "client.h"
\r
237 #include "serialization.h"
\r
238 #include "constants.h"
\r
239 #include "strfnd.h"
\r
240 #include "porting.h"
\r
241 #include "irrlichtwrapper.h"
\r
242 #include "gettime.h"
\r
243 #include "porting.h"
\r
244 #include "guiPauseMenu.h"
\r
245 #include "guiInventoryMenu.h"
\r
246 #include "guiTextInputMenu.h"
\r
247 #include "materials.h"
\r
249 IrrlichtWrapper *g_irrlicht;
\r
251 // All range-related stuff below is locked behind this
\r
252 JMutex g_range_mutex;
\r
254 // Blocks are viewed in this range from the player
\r
255 s16 g_viewing_range_nodes = 60;
\r
256 //s16 g_viewing_range_nodes = 0;
\r
258 // This is updated by the client's fetchBlocks routine
\r
259 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
261 // If true, the preceding value has no meaning and all blocks
\r
262 // already existing in memory are drawn
\r
263 bool g_viewing_range_all = false;
\r
265 // This is the freetime ratio imposed by the dynamic viewing
\r
266 // range changing code.
\r
267 // It is controlled by the main loop to the smallest value that
\r
268 // inhibits glitches (dtime jitter) in the main loop.
\r
269 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
273 These are loaded from the config file.
\r
276 Settings g_settings;
\r
278 extern void set_default_settings();
\r
284 IrrlichtDevice *g_device = NULL;
\r
285 Client *g_client = NULL;
\r
290 gui::IGUIEnvironment* guienv = NULL;
\r
291 gui::IGUIStaticText *guiroot = NULL;
\r
292 int g_active_menu_count = 0;
\r
294 bool noMenuActive()
\r
296 return (g_active_menu_count == 0);
\r
299 // Inventory actions from the menu are buffered here before sending
\r
300 Queue<InventoryAction*> inventory_action_queue;
\r
301 // This is a copy of the inventory that the client's environment has
\r
302 Inventory local_inventory;
\r
304 u16 g_selected_item = 0;
\r
311 std::ostream *dout_con_ptr = &dummyout;
\r
312 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
313 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
314 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
315 //std::ostream *dout_con_ptr = &dstream;
\r
316 //std::ostream *derr_con_ptr = &dstream;
\r
319 std::ostream *dout_server_ptr = &dstream;
\r
320 std::ostream *derr_server_ptr = &dstream;
\r
323 std::ostream *dout_client_ptr = &dstream;
\r
324 std::ostream *derr_client_ptr = &dstream;
\r
327 gettime.h implementation
\r
333 Use irrlicht because it is more precise than porting.h's
\r
336 if(g_irrlicht == NULL)
\r
338 return g_irrlicht->getTime();
\r
345 struct TextDestSign : public TextDest
\r
347 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
349 m_blockpos = blockpos;
\r
353 void gotText(std::wstring text)
\r
355 std::string ntext = wide_to_narrow(text);
\r
356 dstream<<"Changing text of a sign object: "
\r
357 <<ntext<<std::endl;
\r
358 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
366 struct TextDestChat : public TextDest
\r
368 TextDestChat(Client *client)
\r
372 void gotText(std::wstring text)
\r
374 m_client->sendChatMessage(text);
\r
375 m_client->addChatMessage(text);
\r
381 class MyEventReceiver : public IEventReceiver
\r
384 // This is the one method that we have to implement
\r
385 virtual bool OnEvent(const SEvent& event)
\r
388 React to nothing here if a menu is active
\r
390 if(noMenuActive() == false)
\r
396 // Remember whether each key is down or up
\r
397 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
399 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
401 if(event.KeyInput.PressedDown)
\r
403 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
409 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
411 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
413 dstream<<DTIME<<"MyEventReceiver: "
\r
414 <<"Launching pause menu"<<std::endl;
\r
415 // It will delete itself by itself
\r
416 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
417 &g_active_menu_count))->drop();
\r
420 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
422 dstream<<DTIME<<"MyEventReceiver: "
\r
423 <<"Launching inventory"<<std::endl;
\r
424 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
425 &local_inventory, &inventory_action_queue,
\r
426 &g_active_menu_count))->drop();
\r
429 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
431 TextDest *dest = new TextDestChat(g_client);
\r
433 (new GUITextInputMenu(guienv, guiroot, -1,
\r
434 &g_active_menu_count, dest,
\r
439 // Material selection
\r
440 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
442 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
445 g_selected_item = 0;
\r
446 dstream<<DTIME<<"Selected item: "
\r
447 <<g_selected_item<<std::endl;
\r
450 // Viewing range selection
\r
451 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
453 JMutexAutoLock lock(g_range_mutex);
\r
454 if(g_viewing_range_all)
\r
456 g_viewing_range_all = false;
\r
457 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
461 g_viewing_range_all = true;
\r
462 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
466 // Print debug stacks
\r
467 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
469 dstream<<"-----------------------------------------"
\r
471 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
472 dstream<<"-----------------------------------------"
\r
474 debug_stacks_print();
\r
479 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
481 if(noMenuActive() == false)
\r
483 left_active = false;
\r
484 middle_active = false;
\r
485 right_active = false;
\r
489 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
490 left_active = event.MouseInput.isLeftPressed();
\r
491 middle_active = event.MouseInput.isMiddlePressed();
\r
492 right_active = event.MouseInput.isRightPressed();
\r
494 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
496 leftclicked = true;
\r
498 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
500 rightclicked = true;
\r
502 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
504 leftreleased = true;
\r
506 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
508 rightreleased = true;
\r
510 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
512 /*dstream<<"event.MouseInput.Wheel="
\r
513 <<event.MouseInput.Wheel<<std::endl;*/
\r
514 if(event.MouseInput.Wheel < 0)
\r
516 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
519 g_selected_item = 0;
\r
521 else if(event.MouseInput.Wheel > 0)
\r
523 if(g_selected_item > 0)
\r
526 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
535 // This is used to check whether a key is being held down
\r
536 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
538 return keyIsDown[keyCode];
\r
543 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
544 keyIsDown[i] = false;
\r
546 leftclicked = false;
\r
547 rightclicked = false;
\r
548 leftreleased = false;
\r
549 rightreleased = false;
\r
551 left_active = false;
\r
552 middle_active = false;
\r
553 right_active = false;
\r
564 bool rightreleased;
\r
567 bool middle_active;
\r
571 // We use this array to store the current state of each key
\r
572 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
575 IrrlichtDevice *m_device;
\r
584 virtual ~InputHandler()
\r
588 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
590 virtual v2s32 getMousePos() = 0;
\r
591 virtual void setMousePos(s32 x, s32 y) = 0;
\r
593 virtual bool getLeftState() = 0;
\r
594 virtual bool getRightState() = 0;
\r
596 virtual bool getLeftClicked() = 0;
\r
597 virtual bool getRightClicked() = 0;
\r
598 virtual void resetLeftClicked() = 0;
\r
599 virtual void resetRightClicked() = 0;
\r
601 virtual bool getLeftReleased() = 0;
\r
602 virtual bool getRightReleased() = 0;
\r
603 virtual void resetLeftReleased() = 0;
\r
604 virtual void resetRightReleased() = 0;
\r
606 virtual void step(float dtime) {};
\r
608 virtual void clear() {};
\r
611 InputHandler *g_input = NULL;
\r
613 class RealInputHandler : public InputHandler
\r
616 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
618 m_receiver(receiver)
\r
621 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
623 return m_receiver->IsKeyDown(keyCode);
\r
625 virtual v2s32 getMousePos()
\r
627 return m_device->getCursorControl()->getPosition();
\r
629 virtual void setMousePos(s32 x, s32 y)
\r
631 m_device->getCursorControl()->setPosition(x, y);
\r
634 virtual bool getLeftState()
\r
636 return m_receiver->left_active;
\r
638 virtual bool getRightState()
\r
640 return m_receiver->right_active;
\r
643 virtual bool getLeftClicked()
\r
645 return m_receiver->leftclicked;
\r
647 virtual bool getRightClicked()
\r
649 return m_receiver->rightclicked;
\r
651 virtual void resetLeftClicked()
\r
653 m_receiver->leftclicked = false;
\r
655 virtual void resetRightClicked()
\r
657 m_receiver->rightclicked = false;
\r
660 virtual bool getLeftReleased()
\r
662 return m_receiver->leftreleased;
\r
664 virtual bool getRightReleased()
\r
666 return m_receiver->rightreleased;
\r
668 virtual void resetLeftReleased()
\r
670 m_receiver->leftreleased = false;
\r
672 virtual void resetRightReleased()
\r
674 m_receiver->rightreleased = false;
\r
679 resetRightClicked();
\r
680 resetLeftClicked();
\r
683 IrrlichtDevice *m_device;
\r
684 MyEventReceiver *m_receiver;
\r
687 class RandomInputHandler : public InputHandler
\r
690 RandomInputHandler()
\r
692 leftclicked = false;
\r
693 rightclicked = false;
\r
694 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
695 keydown[i] = false;
\r
697 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
699 return keydown[keyCode];
\r
701 virtual v2s32 getMousePos()
\r
705 virtual void setMousePos(s32 x, s32 y)
\r
707 mousepos = v2s32(x,y);
\r
710 virtual bool getLeftState()
\r
714 virtual bool getRightState()
\r
719 virtual bool getLeftClicked()
\r
721 return leftclicked;
\r
723 virtual bool getRightClicked()
\r
725 return rightclicked;
\r
727 virtual void resetLeftClicked()
\r
729 leftclicked = false;
\r
731 virtual void resetRightClicked()
\r
733 rightclicked = false;
\r
736 virtual bool getLeftReleased()
\r
740 virtual bool getRightReleased()
\r
744 virtual void resetLeftReleased()
\r
747 virtual void resetRightReleased()
\r
751 virtual void step(float dtime)
\r
754 static float counter1 = 0;
\r
758 counter1 = 0.1*Rand(1,10);
\r
759 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
760 g_selected_material++;
\r
762 g_selected_material = 0;*/
\r
763 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
766 g_selected_item = 0;
\r
770 static float counter1 = 0;
\r
774 counter1 = 0.1*Rand(1, 40);
\r
775 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
779 static float counter1 = 0;
\r
783 counter1 = 0.1*Rand(1, 40);
\r
784 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
788 static float counter1 = 0;
\r
792 counter1 = 0.1*Rand(1, 40);
\r
793 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
797 static float counter1 = 0;
\r
801 counter1 = 0.1*Rand(1, 40);
\r
802 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
806 static float counter1 = 0;
\r
810 counter1 = 0.1*Rand(1, 20);
\r
811 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
815 static float counter1 = 0;
\r
819 counter1 = 0.1*Rand(1, 30);
\r
820 leftclicked = true;
\r
824 static float counter1 = 0;
\r
828 counter1 = 0.1*Rand(1, 20);
\r
829 rightclicked = true;
\r
832 mousepos += mousespeed;
\r
835 s32 Rand(s32 min, s32 max)
\r
837 return (rand()%(max-min+1))+min;
\r
840 bool keydown[KEY_KEY_CODES_COUNT];
\r
847 void updateViewingRange(f32 frametime, Client *client)
\r
849 // Range_all messes up frametime_avg
\r
850 if(g_viewing_range_all == true)
\r
853 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
855 // Initialize to the target value
\r
856 static float frametime_avg = 1.0/wanted_fps;
\r
857 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
858 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
860 static f32 counter = 0;
\r
862 counter -= frametime;
\r
865 //counter = 1.0; //seconds
\r
866 counter = 0.5; //seconds
\r
868 //float freetime_ratio = 0.2;
\r
869 //float freetime_ratio = 0.4;
\r
870 float freetime_ratio = FREETIME_RATIO;
\r
872 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
874 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
876 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
877 + frametime_avg / frametime_wanted / 2.0;*/
\r
879 //float fraction = frametime_avg / frametime_wanted;
\r
881 static bool fraction_is_good = false;
\r
883 //float fraction_good_threshold = 0.1;
\r
884 //float fraction_bad_threshold = 0.25;
\r
885 float fraction_good_threshold = 0.075;
\r
886 float fraction_bad_threshold = 0.125;
\r
887 float fraction_limit;
\r
888 // Use high limit if fraction is good AND the fraction would
\r
889 // lower the range. We want to keep the range fairly high.
\r
890 if(fraction_is_good && fraction > 1.0)
\r
891 fraction_limit = fraction_bad_threshold;
\r
893 fraction_limit = fraction_good_threshold;
\r
895 if(fabs(fraction - 1.0) < fraction_limit)
\r
897 fraction_is_good = true;
\r
902 fraction_is_good = false;
\r
905 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
906 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
907 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
908 <<" faction = "<<fraction<<std::endl;*/
\r
910 JMutexAutoLock lock(g_range_mutex);
\r
912 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
913 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
915 s16 n = (float)g_viewing_range_nodes / fraction;
\r
916 if(n < viewing_range_nodes_min)
\r
917 n = viewing_range_nodes_min;
\r
918 if(n > viewing_range_nodes_max)
\r
919 n = viewing_range_nodes_max;
\r
921 bool can_change = true;
\r
923 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
924 can_change = false;
\r
927 g_viewing_range_nodes = n;
\r
929 /*dstream<<"g_viewing_range_nodes = "
\r
930 <<g_viewing_range_nodes<<std::endl;*/
\r
933 class GUIQuickInventory : public IEventReceiver
\r
937 gui::IGUIEnvironment* env,
\r
938 gui::IGUIElement* parent,
\r
941 Inventory *inventory):
\r
942 m_itemcount(itemcount),
\r
943 m_inventory(inventory)
\r
945 core::rect<s32> imgsize(0,0,48,48);
\r
946 core::rect<s32> textsize(0,0,48,16);
\r
947 v2s32 spacing(0, 64);
\r
948 for(s32 i=0; i<m_itemcount; i++)
\r
950 m_images.push_back(env->addImage(
\r
951 imgsize + pos + spacing*i
\r
953 m_images[i]->setScaleImage(true);
\r
954 m_texts.push_back(env->addStaticText(
\r
956 textsize + pos + spacing*i,
\r
959 m_texts[i]->setBackgroundColor(
\r
960 video::SColor(128,0,0,0));
\r
961 m_texts[i]->setTextAlignment(
\r
963 gui::EGUIA_UPPERLEFT);
\r
967 virtual bool OnEvent(const SEvent& event)
\r
972 void setSelection(s32 i)
\r
981 start = m_selection - m_itemcount / 2;
\r
983 InventoryList *mainlist = m_inventory->getList("main");
\r
985 for(s32 i=0; i<m_itemcount; i++)
\r
989 if(j > (s32)mainlist->getSize() - 1)
\r
990 j -= mainlist->getSize();
\r
992 j += mainlist->getSize();
\r
994 InventoryItem *item = mainlist->getItem(j);
\r
998 m_images[i]->setImage(NULL);
\r
1001 if(m_selection == j)
\r
1002 swprintf(t, 10, L"<-");
\r
1004 swprintf(t, 10, L"");
\r
1005 m_texts[i]->setText(t);
\r
1007 // The next ifs will segfault with a NULL pointer
\r
1012 m_images[i]->setImage(item->getImage());
\r
1015 if(m_selection == j)
\r
1016 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1018 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1019 m_texts[i]->setText(t);
\r
1025 core::array<gui::IGUIStaticText*> m_texts;
\r
1026 core::array<gui::IGUIImage*> m_images;
\r
1027 Inventory *m_inventory;
\r
1038 ChatLine(const std::wstring &a_text):
\r
1044 std::wstring text;
\r
1047 int main(int argc, char *argv[])
\r
1050 Low-level initialization
\r
1053 bool disable_stderr = false;
\r
1055 disable_stderr = true;
\r
1058 // Initialize debug streams
\r
1059 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1060 // Initialize debug stacks
\r
1061 debug_stacks_init();
\r
1063 DSTACK(__FUNCTION_NAME);
\r
1065 initializeMaterialProperties();
\r
1071 Parse command line
\r
1074 // List all allowed options
\r
1075 core::map<std::string, ValueSpec> allowed_options;
\r
1076 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1077 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1078 "Run server directly"));
\r
1079 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1080 "Load configuration from specified file"));
\r
1081 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1082 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1083 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1084 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1085 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1087 Settings cmd_args;
\r
1089 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1091 if(ret == false || cmd_args.getFlag("help"))
\r
1093 dstream<<"Allowed options:"<<std::endl;
\r
1094 for(core::map<std::string, ValueSpec>::Iterator
\r
1095 i = allowed_options.getIterator();
\r
1096 i.atEnd() == false; i++)
\r
1098 dstream<<" --"<<i.getNode()->getKey();
\r
1099 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1104 dstream<<" <value>";
\r
1106 dstream<<std::endl;
\r
1108 if(i.getNode()->getValue().help != NULL)
\r
1110 dstream<<" "<<i.getNode()->getValue().help
\r
1115 return cmd_args.getFlag("help") ? 0 : 1;
\r
1120 Basic initialization
\r
1123 // Initialize default settings
\r
1124 set_default_settings();
\r
1126 // Print startup message
\r
1127 dstream<<DTIME<<"minetest-c55"
\r
1128 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1129 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1132 // Set locale. This is for forcing '.' as the decimal point.
\r
1133 std::locale::global(std::locale("C"));
\r
1134 // This enables printing all characters in bitmap font
\r
1135 setlocale(LC_CTYPE, "en_US");
\r
1137 // Initialize sockets
\r
1139 atexit(sockets_cleanup);
\r
1149 // Path of configuration file in use
\r
1150 std::string configpath = "";
\r
1152 if(cmd_args.exists("config"))
\r
1154 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1157 dstream<<"Could not read configuration from \""
\r
1158 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1161 configpath = cmd_args.get("config");
\r
1165 const char *filenames[2] =
\r
1167 "../minetest.conf",
\r
1168 "../../minetest.conf"
\r
1171 for(u32 i=0; i<2; i++)
\r
1173 bool r = g_settings.readConfigFile(filenames[i]);
\r
1176 configpath = filenames[i];
\r
1182 // Initialize random seed
\r
1188 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1189 || cmd_args.getFlag("enable-unittests") == true)
\r
1195 Global range mutex
\r
1197 g_range_mutex.Init();
\r
1198 assert(g_range_mutex.IsInitialized());
\r
1200 // Read map parameters from settings
\r
1202 HMParams hm_params;
\r
1203 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1204 hm_params.randmax = g_settings.get("height_randmax");
\r
1205 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1206 hm_params.base = g_settings.get("height_base");
\r
1208 MapParams map_params;
\r
1209 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1210 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1216 std::cout<<std::endl<<std::endl;
\r
1219 <<" .__ __ __ "<<std::endl
\r
1220 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1221 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1222 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1223 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1224 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1227 std::cout<<std::endl;
\r
1228 //char templine[100];
\r
1232 if(cmd_args.exists("port"))
\r
1234 port = cmd_args.getU16("port");
\r
1238 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1239 std::cout<<"-> "<<port<<std::endl;
\r
1242 if(cmd_args.getFlag("server"))
\r
1244 DSTACK("Dedicated server branch");
\r
1246 std::cout<<std::endl;
\r
1247 std::cout<<"========================"<<std::endl;
\r
1248 std::cout<<"Running dedicated server"<<std::endl;
\r
1249 std::cout<<"========================"<<std::endl;
\r
1250 std::cout<<std::endl;
\r
1252 Server server("../map", hm_params, map_params);
\r
1253 server.start(port);
\r
1257 // This is kind of a hack but can be done like this
\r
1258 // because server.step() is very light
\r
1260 server.step(0.030);
\r
1262 static int counter = 0;
\r
1268 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1269 core::list<PlayerInfo>::Iterator i;
\r
1270 static u32 sum_old = 0;
\r
1271 u32 sum = PIChecksum(list);
\r
1272 if(sum != sum_old)
\r
1274 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1275 for(i=list.begin(); i!=list.end(); i++)
\r
1277 i->PrintLine(&std::cout);
\r
1287 bool hosting = false;
\r
1288 char connect_name[100] = "";
\r
1290 if(cmd_args.exists("address"))
\r
1292 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1294 else if(is_yes(g_settings.get("host_game")) == false)
\r
1296 if(g_settings.get("address") != "")
\r
1298 std::cout<<g_settings.get("address")<<std::endl;
\r
1299 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1303 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1304 std::cin.getline(connect_name, 100);
\r
1308 if(connect_name[0] == 0){
\r
1309 snprintf(connect_name, 100, "127.0.0.1");
\r
1314 std::cout<<"> Hosting game"<<std::endl;
\r
1316 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1318 char playername[PLAYERNAME_SIZE] = "";
\r
1319 if(g_settings.get("name") != "")
\r
1321 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1325 std::cout<<"Name of player: ";
\r
1326 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1328 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1331 Resolution selection
\r
1334 bool fullscreen = false;
\r
1335 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1336 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1340 MyEventReceiver receiver;
\r
1342 video::E_DRIVER_TYPE driverType;
\r
1345 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1346 driverType = video::EDT_OPENGL;
\r
1348 driverType = video::EDT_OPENGL;
\r
1351 // create device and exit if creation failed
\r
1353 IrrlichtDevice *device;
\r
1354 device = createDevice(driverType,
\r
1355 core::dimension2d<u32>(screenW, screenH),
\r
1356 16, fullscreen, false, false, &receiver);
\r
1359 return 1; // could not create selected driver.
\r
1361 g_device = device;
\r
1362 g_irrlicht = new IrrlichtWrapper(device);
\r
1364 //g_device = device;
\r
1366 device->setResizable(true);
\r
1368 bool random_input = g_settings.getBool("random_input")
\r
1369 || cmd_args.getFlag("random-input");
\r
1371 g_input = new RandomInputHandler();
\r
1373 g_input = new RealInputHandler(device, &receiver);
\r
1376 Continue initialization
\r
1379 video::IVideoDriver* driver = device->getVideoDriver();
\r
1382 This changes the minimum allowed number of vertices in a VBO
\r
1384 //driver->setMinHardwareBufferVertexCount(1);
\r
1386 scene::ISceneManager* smgr = device->getSceneManager();
\r
1388 guienv = device->getGUIEnvironment();
\r
1389 gui::IGUISkin* skin = guienv->getSkin();
\r
1390 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1392 skin->setFont(font);
\r
1394 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1395 dstream<<"text_height="<<text_height<<std::endl;
\r
1397 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1398 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1399 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1400 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1401 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1402 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1404 const wchar_t *text = L"Loading and connecting...";
\r
1405 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1406 core::vector2d<s32> textsize(300, text_height);
\r
1407 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1409 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1410 text, textrect, false, false);
\r
1411 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1413 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1414 guienv->drawAll();
\r
1415 driver->endScene();
\r
1418 Preload some textures
\r
1421 tile_materials_preload(g_irrlicht);
\r
1424 Make a scope here for the client so that it gets removed
\r
1425 before the irrlicht device
\r
1429 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1434 SharedPtr<Server> server;
\r
1436 server = new Server("../map", hm_params, map_params);
\r
1437 server->start(port);
\r
1444 Client client(device, playername,
\r
1446 g_viewing_range_nodes,
\r
1447 g_viewing_range_all);
\r
1449 g_client = &client;
\r
1451 Address connect_address(0,0,0,0, port);
\r
1453 connect_address.Resolve(connect_name);
\r
1455 catch(ResolveError &e)
\r
1457 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1461 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1462 client.connect(connect_address);
\r
1465 while(client.connectedAndInitialized() == false)
\r
1468 if(server != NULL){
\r
1469 server->step(0.1);
\r
1474 catch(con::PeerNotFoundException &e)
\r
1476 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1483 /*scene::ISceneNode* skybox;
\r
1484 skybox = smgr->addSkyBoxSceneNode(
\r
1485 driver->getTexture("../data/skybox2.png"),
\r
1486 driver->getTexture("../data/skybox3.png"),
\r
1487 driver->getTexture("../data/skybox1.png"),
\r
1488 driver->getTexture("../data/skybox1.png"),
\r
1489 driver->getTexture("../data/skybox1.png"),
\r
1490 driver->getTexture("../data/skybox1.png"));*/
\r
1493 Create the camera node
\r
1496 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1497 0, // Camera parent
\r
1498 v3f(BS*100, BS*2, BS*100), // Look from
\r
1499 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1503 if(camera == NULL)
\r
1506 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1508 camera->setFOV(FOV_ANGLE);
\r
1510 // Just so big a value that everything rendered is visible
\r
1511 camera->setFarValue(100000*BS);
\r
1513 f32 camera_yaw = 0; // "right/left"
\r
1514 f32 camera_pitch = 0; // "up/down"
\r
1520 gui_loadingtext->remove();
\r
1523 Add some gui stuff
\r
1526 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1527 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1530 We need some kind of a root node to be able to add
\r
1531 custom elements directly on the screen.
\r
1532 Otherwise they won't be automatically drawn.
\r
1534 guiroot = guienv->addStaticText(L"",
\r
1535 core::rect<s32>(0, 0, 10000, 10000));
\r
1537 // Test the text input system
\r
1538 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1541 // Launch pause menu
\r
1542 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1543 &g_active_menu_count))->drop();
\r
1545 // First line of debug text
\r
1546 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1548 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1550 // Second line of debug text
\r
1551 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1553 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1556 // At the middle of the screen
\r
1557 // Object infos are shown in this
\r
1558 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1560 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1564 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1565 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1566 core::rect<s32>(70, 60, 795, 150),
\r
1568 core::list<ChatLine> chat_lines;
\r
1571 Some statistics are collected in these
\r
1574 u32 beginscenetime = 0;
\r
1575 u32 scenetime = 0;
\r
1576 u32 endscenetime = 0;
\r
1582 bool first_loop_after_window_activation = true;
\r
1584 // Time is in milliseconds
\r
1585 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1586 // NOTE: Have to call run() between calls of this to update the timer
\r
1587 u32 lasttime = device->getTimer()->getTime();
\r
1589 while(device->run())
\r
1592 Run global IrrlichtWrapper's main thread processing stuff
\r
1594 g_irrlicht->Run();
\r
1597 Random calculations
\r
1599 v2u32 screensize = driver->getScreenSize();
\r
1600 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1602 // Hilight boxes collected during the loop and displayed
\r
1603 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1606 std::wstring infotext;
\r
1608 //TimeTaker //timer1("//timer1");
\r
1610 // Time of frame without fps limit
\r
1614 // not using getRealTime is necessary for wine
\r
1615 u32 time = device->getTimer()->getTime();
\r
1616 if(time > lasttime)
\r
1617 busytime_u32 = time - lasttime;
\r
1620 busytime = busytime_u32 / 1000.0;
\r
1623 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1625 // Absolutelu necessary for wine!
\r
1632 updateViewingRange(busytime, &client);
\r
1639 float fps_max = g_settings.getFloat("fps_max");
\r
1640 u32 frametime_min = 1000./fps_max;
\r
1642 if(busytime_u32 < frametime_min)
\r
1644 u32 sleeptime = frametime_min - busytime_u32;
\r
1645 device->sleep(sleeptime);
\r
1649 // Absolutelu necessary for wine!
\r
1653 Time difference calculation
\r
1655 f32 dtime; // in seconds
\r
1657 u32 time = device->getTimer()->getTime();
\r
1658 if(time > lasttime)
\r
1659 dtime = (time - lasttime) / 1000.0;
\r
1665 Time average and jitter calculation
\r
1668 static f32 dtime_avg1 = 0.0;
\r
1669 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1670 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1672 static f32 dtime_jitter1_max_sample = 0.0;
\r
1673 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1675 static f32 jitter1_max = 0.0;
\r
1676 static f32 counter = 0.0;
\r
1677 if(dtime_jitter1 > jitter1_max)
\r
1678 jitter1_max = dtime_jitter1;
\r
1683 dtime_jitter1_max_sample = jitter1_max;
\r
1684 dtime_jitter1_max_fraction
\r
1685 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1686 jitter1_max = 0.0;
\r
1689 Control freetime ratio
\r
1691 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1693 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1694 g_freetime_ratio += 0.01;
\r
1698 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1699 g_freetime_ratio -= 0.01;
\r
1705 Busytime average and jitter calculation
\r
1708 static f32 busytime_avg1 = 0.0;
\r
1709 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1710 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1712 static f32 busytime_jitter1_max_sample = 0.0;
\r
1713 static f32 busytime_jitter1_min_sample = 0.0;
\r
1715 static f32 jitter1_max = 0.0;
\r
1716 static f32 jitter1_min = 0.0;
\r
1717 static f32 counter = 0.0;
\r
1718 if(busytime_jitter1 > jitter1_max)
\r
1719 jitter1_max = busytime_jitter1;
\r
1720 if(busytime_jitter1 < jitter1_min)
\r
1721 jitter1_min = busytime_jitter1;
\r
1723 if(counter > 0.0){
\r
1725 busytime_jitter1_max_sample = jitter1_max;
\r
1726 busytime_jitter1_min_sample = jitter1_min;
\r
1727 jitter1_max = 0.0;
\r
1728 jitter1_min = 0.0;
\r
1733 Debug info for client
\r
1736 static float counter = 0.0;
\r
1741 client.printDebugInfo(std::cout);
\r
1746 Input handler step()
\r
1748 g_input->step(dtime);
\r
1751 Player speed control
\r
1760 bool a_superspeed,
\r
1763 PlayerControl control(
\r
1764 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1765 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1766 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1767 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1768 g_input->isKeyDown(irr::KEY_SPACE),
\r
1769 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1773 client.setPlayerControl(control);
\r
1777 Process environment
\r
1781 //TimeTaker timer("client.step(dtime)");
\r
1782 client.step(dtime);
\r
1783 //client.step(dtime_avg1);
\r
1786 if(server != NULL)
\r
1788 //TimeTaker timer("server->step(dtime)");
\r
1789 server->step(dtime);
\r
1792 v3f player_position = client.getPlayerPosition();
\r
1794 //TimeTaker //timer2("//timer2");
\r
1797 Mouse and camera control
\r
1800 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1803 device->getCursorControl()->setVisible(false);
\r
1805 if(first_loop_after_window_activation){
\r
1806 //std::cout<<"window active, first loop"<<std::endl;
\r
1807 first_loop_after_window_activation = false;
\r
1810 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1811 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1812 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1813 camera_yaw -= dx*0.2;
\r
1814 camera_pitch += dy*0.2;
\r
1815 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1816 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1818 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1821 device->getCursorControl()->setVisible(true);
\r
1823 //std::cout<<"window inactive"<<std::endl;
\r
1824 first_loop_after_window_activation = true;
\r
1827 camera_yaw = wrapDegrees(camera_yaw);
\r
1828 camera_pitch = wrapDegrees(camera_pitch);
\r
1830 v3f camera_direction = v3f(0,0,1);
\r
1831 camera_direction.rotateYZBy(camera_pitch);
\r
1832 camera_direction.rotateXZBy(camera_yaw);
\r
1834 v3f camera_position =
\r
1835 player_position + v3f(0, BS+BS/2, 0);
\r
1837 camera->setPosition(camera_position);
\r
1838 // *100.0 helps in large map coordinates
\r
1839 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1841 if(FIELD_OF_VIEW_TEST){
\r
1842 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1843 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1846 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1847 //TimeTaker timer("client.updateCamera");
\r
1848 client.updateCamera(camera_position, camera_direction);
\r
1852 //TimeTaker //timer3("//timer3");
\r
1855 Calculate what block is the crosshair pointing to
\r
1858 //u32 t1 = device->getTimer()->getRealTime();
\r
1860 //f32 d = 4; // max. distance
\r
1861 f32 d = 4; // max. distance
\r
1862 core::line3d<f32> shootline(camera_position,
\r
1863 camera_position + camera_direction * BS * (d+1));
\r
1865 MapBlockObject *selected_object = client.getSelectedObject
\r
1866 (d*BS, camera_position, shootline);
\r
1869 If it's pointing to a MapBlockObject
\r
1872 if(selected_object != NULL)
\r
1874 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1876 core::aabbox3d<f32> box_on_map
\r
1877 = selected_object->getSelectionBoxOnMap();
\r
1879 hilightboxes.push_back(box_on_map);
\r
1881 infotext = narrow_to_wide(selected_object->infoText());
\r
1883 if(g_input->getLeftClicked())
\r
1885 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1886 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1887 selected_object->getId(), g_selected_item);
\r
1889 else if(g_input->getRightClicked())
\r
1891 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1893 Check if we want to modify the object ourselves
\r
1895 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1897 dstream<<"Sign object right-clicked"<<std::endl;
\r
1899 if(random_input == false)
\r
1901 // Get a new text for it
\r
1903 TextDest *dest = new TextDestSign(
\r
1904 selected_object->getBlock()->getPos(),
\r
1905 selected_object->getId(),
\r
1908 SignObject *sign_object = (SignObject*)selected_object;
\r
1910 std::wstring wtext =
\r
1911 narrow_to_wide(sign_object->getText());
\r
1913 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1914 &g_active_menu_count, dest,
\r
1919 Otherwise pass the event to the server as-is
\r
1923 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1924 selected_object->getId(), g_selected_item);
\r
1928 else // selected_object == NULL
\r
1932 Find out which node we are pointing at
\r
1935 bool nodefound = false;
\r
1937 v3s16 neighbourpos;
\r
1938 core::aabbox3d<f32> nodefacebox;
\r
1939 f32 mindistance = BS * 1001;
\r
1941 v3s16 pos_i = floatToInt(player_position);
\r
1943 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1947 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1948 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1949 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1950 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1951 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1952 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1954 for(s16 y = ystart; y <= yend; y++)
\r
1955 for(s16 z = zstart; z <= zend; z++)
\r
1956 for(s16 x = xstart; x <= xend; x++)
\r
1961 n = client.getNode(v3s16(x,y,z));
\r
1962 if(content_pointable(n.d) == false)
\r
1965 catch(InvalidPositionException &e)
\r
1971 v3f npf = intToFloat(np);
\r
1976 v3s16(0,0,1), // back
\r
1977 v3s16(0,1,0), // top
\r
1978 v3s16(1,0,0), // right
\r
1979 v3s16(0,0,-1), // front
\r
1980 v3s16(0,-1,0), // bottom
\r
1981 v3s16(-1,0,0), // left
\r
1987 if(n.d == CONTENT_TORCH)
\r
1989 v3s16 dir = unpackDir(n.dir);
\r
1990 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
1991 dir_f *= BS/2 - BS/6 - BS/20;
\r
1992 v3f cpf = npf + dir_f;
\r
1993 f32 distance = (cpf - camera_position).getLength();
\r
1995 core::aabbox3d<f32> box;
\r
1998 if(dir == v3s16(0,-1,0))
\r
2000 box = core::aabbox3d<f32>(
\r
2001 npf - v3f(BS/6, BS/2, BS/6),
\r
2002 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2006 else if(dir == v3s16(0,1,0))
\r
2008 box = core::aabbox3d<f32>(
\r
2009 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2010 npf + v3f(BS/6, BS/2, BS/6)
\r
2016 box = core::aabbox3d<f32>(
\r
2017 cpf - v3f(BS/6, BS/3, BS/6),
\r
2018 cpf + v3f(BS/6, BS/3, BS/6)
\r
2022 if(distance < mindistance)
\r
2024 if(box.intersectsWithLine(shootline))
\r
2028 neighbourpos = np;
\r
2029 mindistance = distance;
\r
2030 nodefacebox = box;
\r
2039 for(u16 i=0; i<6; i++)
\r
2041 v3f dir_f = v3f(dirs[i].X,
\r
2042 dirs[i].Y, dirs[i].Z);
\r
2043 v3f centerpoint = npf + dir_f * BS/2;
\r
2045 (centerpoint - camera_position).getLength();
\r
2047 if(distance < mindistance)
\r
2049 core::CMatrix4<f32> m;
\r
2050 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2052 // This is the back face
\r
2053 v3f corners[2] = {
\r
2054 v3f(BS/2, BS/2, BS/2),
\r
2055 v3f(-BS/2, -BS/2, BS/2+d)
\r
2058 for(u16 j=0; j<2; j++)
\r
2060 m.rotateVect(corners[j]);
\r
2061 corners[j] += npf;
\r
2064 core::aabbox3d<f32> facebox(corners[0]);
\r
2065 facebox.addInternalPoint(corners[1]);
\r
2067 if(facebox.intersectsWithLine(shootline))
\r
2071 neighbourpos = np + dirs[i];
\r
2072 mindistance = distance;
\r
2073 nodefacebox = facebox;
\r
2075 } // if distance < mindistance
\r
2077 } // regular block
\r
2080 static float nodig_delay_counter = 0.0;
\r
2084 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2086 static float dig_time = 0.0;
\r
2087 static u16 dig_index = 0;
\r
2089 hilightboxes.push_back(nodefacebox);
\r
2091 if(g_input->getLeftReleased())
\r
2093 client.clearTempMod(nodepos);
\r
2097 if(nodig_delay_counter > 0.0)
\r
2099 nodig_delay_counter -= dtime;
\r
2103 if(nodepos != nodepos_old)
\r
2105 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2106 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2108 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2110 client.clearTempMod(nodepos_old);
\r
2115 if(g_input->getLeftClicked() ||
\r
2116 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2118 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2119 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2121 if(g_input->getLeftClicked())
\r
2123 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2125 if(g_input->getLeftState())
\r
2127 MapNode n = client.getNode(nodepos);
\r
2129 // Get tool name. Default is "" = bare hands
\r
2130 std::string toolname = "";
\r
2131 InventoryList *mlist = local_inventory.getList("main");
\r
2134 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2135 if(item && (std::string)item->getName() == "ToolItem")
\r
2137 ToolItem *titem = (ToolItem*)item;
\r
2138 toolname = titem->getToolName();
\r
2142 // Get digging properties for material and tool
\r
2143 u8 material = n.d;
\r
2144 DiggingProperties prop =
\r
2145 getDiggingProperties(material, toolname);
\r
2147 float dig_time_complete = 0.0;
\r
2149 if(prop.diggable == false)
\r
2151 /*dstream<<"Material "<<(int)material
\r
2152 <<" not diggable with \""
\r
2153 <<toolname<<"\""<<std::endl;*/
\r
2154 // I guess nobody will wait for this long
\r
2155 dig_time_complete = 10000000.0;
\r
2159 dig_time_complete = prop.time;
\r
2162 if(dig_time_complete >= 0.001)
\r
2164 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2165 * dig_time/dig_time_complete);
\r
2167 // This is for torches
\r
2170 dig_index = CRACK_ANIMATION_LENGTH;
\r
2173 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2175 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2176 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2180 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2181 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2182 client.clearTempMod(nodepos);
\r
2183 client.removeNode(nodepos);
\r
2187 nodig_delay_counter = dig_time_complete
\r
2188 / (float)CRACK_ANIMATION_LENGTH;
\r
2190 // We don't want a corresponding delay to
\r
2191 // very time consuming nodes
\r
2192 if(nodig_delay_counter > 0.5)
\r
2194 nodig_delay_counter = 0.5;
\r
2196 // We want a slight delay to very little
\r
2197 // time consuming nodes
\r
2198 //float mindelay = 0.15;
\r
2199 float mindelay = 0.20;
\r
2200 if(nodig_delay_counter < mindelay)
\r
2202 nodig_delay_counter = mindelay;
\r
2206 dig_time += dtime;
\r
2210 if(g_input->getRightClicked())
\r
2212 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2213 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2216 nodepos_old = nodepos;
\r
2221 } // selected_object == NULL
\r
2223 g_input->resetLeftClicked();
\r
2224 g_input->resetRightClicked();
\r
2226 if(g_input->getLeftReleased())
\r
2228 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2230 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2232 if(g_input->getRightReleased())
\r
2234 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2238 g_input->resetLeftReleased();
\r
2239 g_input->resetRightReleased();
\r
2242 Calculate stuff for drawing
\r
2245 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2247 u32 daynight_ratio = client.getDayNightRatio();
\r
2248 video::SColor bgcolor = video::SColor(
\r
2250 skycolor.getRed() * daynight_ratio / 1000,
\r
2251 skycolor.getGreen() * daynight_ratio / 1000,
\r
2252 skycolor.getBlue() * daynight_ratio / 1000);
\r
2258 if(g_settings.getBool("enable_fog") == true)
\r
2260 f32 range = g_viewing_range_nodes * BS;
\r
2261 if(g_viewing_range_all)
\r
2262 range = 100000*BS;
\r
2266 video::EFT_FOG_LINEAR,
\r
2270 false, // pixel fog
\r
2271 false // range fog
\r
2277 Update gui stuff (0ms)
\r
2280 //TimeTaker guiupdatetimer("Gui updating");
\r
2283 wchar_t temptext[150];
\r
2285 static float drawtime_avg = 0;
\r
2286 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2287 static float beginscenetime_avg = 0;
\r
2288 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2289 static float scenetime_avg = 0;
\r
2290 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2291 static float endscenetime_avg = 0;
\r
2292 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2294 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2296 L", R: range_all=%i"
\r
2298 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2300 g_viewing_range_all,
\r
2302 beginscenetime_avg,
\r
2307 guitext->setText(temptext);
\r
2311 wchar_t temptext[150];
\r
2312 swprintf(temptext, 150,
\r
2313 L"(% .1f, % .1f, % .1f)"
\r
2314 L" (% .3f < btime_jitter < % .3f"
\r
2315 L", dtime_jitter = % .1f %%)",
\r
2316 player_position.X/BS,
\r
2317 player_position.Y/BS,
\r
2318 player_position.Z/BS,
\r
2319 busytime_jitter1_min_sample,
\r
2320 busytime_jitter1_max_sample,
\r
2321 dtime_jitter1_max_fraction * 100.0
\r
2324 guitext2->setText(temptext);
\r
2328 guitext_info->setText(infotext.c_str());
\r
2332 Get chat messages from client
\r
2335 // Get new messages
\r
2336 std::wstring message;
\r
2337 while(client.getChatMessage(message))
\r
2339 chat_lines.push_back(ChatLine(message));
\r
2340 if(chat_lines.size() > 7)
\r
2342 core::list<ChatLine>::Iterator
\r
2343 i = chat_lines.begin();
\r
2344 chat_lines.erase(i);
\r
2347 // Append them to form the whole static text and throw
\r
2348 // it to the gui element
\r
2349 std::wstring whole;
\r
2350 u16 to_be_removed_count = 0;
\r
2351 for(core::list<ChatLine>::Iterator
\r
2352 i = chat_lines.begin();
\r
2353 i != chat_lines.end(); i++)
\r
2355 (*i).age += dtime;
\r
2356 if((*i).age > 30.0)
\r
2358 to_be_removed_count++;
\r
2361 whole += (*i).text + L'\n';
\r
2363 for(u16 i=0; i<to_be_removed_count; i++)
\r
2365 core::list<ChatLine>::Iterator
\r
2366 it = chat_lines.begin();
\r
2367 chat_lines.erase(it);
\r
2369 chat_guitext->setText(whole.c_str());
\r
2370 // Update gui element size and position
\r
2371 core::rect<s32> rect(
\r
2373 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2374 screensize.X - 10,
\r
2377 chat_guitext->setRelativePosition(rect);
\r
2384 static u16 old_selected_item = 65535;
\r
2385 if(client.getLocalInventoryUpdated()
\r
2386 || g_selected_item != old_selected_item)
\r
2388 old_selected_item = g_selected_item;
\r
2389 //std::cout<<"Updating local inventory"<<std::endl;
\r
2390 client.getLocalInventory(local_inventory);
\r
2391 quick_inventory->setSelection(g_selected_item);
\r
2392 quick_inventory->update();
\r
2396 Send actions returned by the inventory menu
\r
2398 while(inventory_action_queue.size() != 0)
\r
2400 InventoryAction *a = inventory_action_queue.pop_front();
\r
2402 client.sendInventoryAction(a);
\r
2411 TimeTaker drawtimer("Drawing");
\r
2415 TimeTaker timer("beginScene");
\r
2416 driver->beginScene(true, true, bgcolor);
\r
2417 //driver->beginScene(false, true, bgcolor);
\r
2418 beginscenetime = timer.stop(true);
\r
2423 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2426 TimeTaker timer("smgr");
\r
2428 scenetime = timer.stop(true);
\r
2432 //TimeTaker timer9("auxiliary drawings");
\r
2436 //TimeTaker //timer10("//timer10");
\r
2438 video::SMaterial m;
\r
2440 m.Lighting = false;
\r
2441 driver->setMaterial(m);
\r
2443 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2445 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2446 i != hilightboxes.end(); i++)
\r
2448 /*std::cout<<"hilightbox min="
\r
2449 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2451 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2453 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2459 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2460 displaycenter + core::vector2d<s32>(10,0),
\r
2461 video::SColor(255,255,255,255));
\r
2462 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2463 displaycenter + core::vector2d<s32>(0,10),
\r
2464 video::SColor(255,255,255,255));
\r
2469 //TimeTaker //timer11("//timer11");
\r
2475 guienv->drawAll();
\r
2479 TimeTaker timer("endScene");
\r
2480 driver->endScene();
\r
2481 endscenetime = timer.stop(true);
\r
2484 drawtime = drawtimer.stop(true);
\r
2490 static s16 lastFPS = 0;
\r
2491 //u16 fps = driver->getFPS();
\r
2492 u16 fps = (1.0/dtime_avg1);
\r
2494 if (lastFPS != fps)
\r
2496 core::stringw str = L"Minetest [";
\r
2497 str += driver->getName();
\r
2501 device->setWindowCaption(str.c_str());
\r
2507 device->yield();*/
\r
2510 delete quick_inventory;
\r
2512 } // client is deleted at this point
\r
2517 In the end, delete the Irrlicht device.
\r
2522 Update configuration file
\r
2524 /*if(configpath != "")
\r
2526 g_settings.updateConfigFile(configpath.c_str());
\r
2530 catch(con::PeerNotFoundException &e)
\r
2532 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2534 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2536 This is what has to be done in every thread to get suitable debug info
\r
2538 catch(std::exception &e)
\r
2540 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2541 <<e.what()<<std::endl;
\r
2546 debugstreams_deinit();
\r