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
248 #include "guiMessageMenu.h"
\r
250 IrrlichtWrapper *g_irrlicht;
\r
252 // All range-related stuff below is locked behind this
\r
253 JMutex g_range_mutex;
\r
255 // Blocks are viewed in this range from the player
\r
256 s16 g_viewing_range_nodes = 60;
\r
257 //s16 g_viewing_range_nodes = 0;
\r
259 // This is updated by the client's fetchBlocks routine
\r
260 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
262 // If true, the preceding value has no meaning and all blocks
\r
263 // already existing in memory are drawn
\r
264 bool g_viewing_range_all = false;
\r
266 // This is the freetime ratio imposed by the dynamic viewing
\r
267 // range changing code.
\r
268 // It is controlled by the main loop to the smallest value that
\r
269 // inhibits glitches (dtime jitter) in the main loop.
\r
270 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
274 These are loaded from the config file.
\r
277 Settings g_settings;
\r
279 extern void set_default_settings();
\r
285 IrrlichtDevice *g_device = NULL;
\r
286 Client *g_client = NULL;
\r
291 gui::IGUIEnvironment* guienv = NULL;
\r
292 gui::IGUIStaticText *guiroot = NULL;
\r
293 int g_active_menu_count = 0;
\r
295 bool noMenuActive()
\r
297 return (g_active_menu_count == 0);
\r
300 // Inventory actions from the menu are buffered here before sending
\r
301 Queue<InventoryAction*> inventory_action_queue;
\r
302 // This is a copy of the inventory that the client's environment has
\r
303 Inventory local_inventory;
\r
305 u16 g_selected_item = 0;
\r
312 std::ostream *dout_con_ptr = &dummyout;
\r
313 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
314 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
315 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
316 //std::ostream *dout_con_ptr = &dstream;
\r
317 //std::ostream *derr_con_ptr = &dstream;
\r
320 std::ostream *dout_server_ptr = &dstream;
\r
321 std::ostream *derr_server_ptr = &dstream;
\r
324 std::ostream *dout_client_ptr = &dstream;
\r
325 std::ostream *derr_client_ptr = &dstream;
\r
328 gettime.h implementation
\r
334 Use irrlicht because it is more precise than porting.h's
\r
337 if(g_irrlicht == NULL)
\r
339 return g_irrlicht->getTime();
\r
346 struct TextDestSign : public TextDest
\r
348 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
350 m_blockpos = blockpos;
\r
354 void gotText(std::wstring text)
\r
356 std::string ntext = wide_to_narrow(text);
\r
357 dstream<<"Changing text of a sign object: "
\r
358 <<ntext<<std::endl;
\r
359 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
367 struct TextDestChat : public TextDest
\r
369 TextDestChat(Client *client)
\r
373 void gotText(std::wstring text)
\r
375 m_client->sendChatMessage(text);
\r
376 m_client->addChatMessage(text);
\r
382 class MyEventReceiver : public IEventReceiver
\r
385 // This is the one method that we have to implement
\r
386 virtual bool OnEvent(const SEvent& event)
\r
389 React to nothing here if a menu is active
\r
391 if(noMenuActive() == false)
\r
397 // Remember whether each key is down or up
\r
398 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
400 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
402 if(event.KeyInput.PressedDown)
\r
404 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
410 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
412 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
414 dstream<<DTIME<<"MyEventReceiver: "
\r
415 <<"Launching pause menu"<<std::endl;
\r
416 // It will delete itself by itself
\r
417 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
418 &g_active_menu_count))->drop();
\r
421 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
423 dstream<<DTIME<<"MyEventReceiver: "
\r
424 <<"Launching inventory"<<std::endl;
\r
425 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
426 &local_inventory, &inventory_action_queue,
\r
427 &g_active_menu_count))->drop();
\r
430 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
432 TextDest *dest = new TextDestChat(g_client);
\r
434 (new GUITextInputMenu(guienv, guiroot, -1,
\r
435 &g_active_menu_count, dest,
\r
440 // Material selection
\r
441 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
443 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
446 g_selected_item = 0;
\r
447 dstream<<DTIME<<"Selected item: "
\r
448 <<g_selected_item<<std::endl;
\r
451 // Viewing range selection
\r
452 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
454 JMutexAutoLock lock(g_range_mutex);
\r
455 if(g_viewing_range_all)
\r
457 g_viewing_range_all = false;
\r
458 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
462 g_viewing_range_all = true;
\r
463 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
467 // Print debug stacks
\r
468 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
470 dstream<<"-----------------------------------------"
\r
472 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
473 dstream<<"-----------------------------------------"
\r
475 debug_stacks_print();
\r
480 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
482 if(noMenuActive() == false)
\r
484 left_active = false;
\r
485 middle_active = false;
\r
486 right_active = false;
\r
490 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
491 left_active = event.MouseInput.isLeftPressed();
\r
492 middle_active = event.MouseInput.isMiddlePressed();
\r
493 right_active = event.MouseInput.isRightPressed();
\r
495 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
497 leftclicked = true;
\r
499 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
501 rightclicked = true;
\r
503 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
505 leftreleased = true;
\r
507 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
509 rightreleased = true;
\r
511 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
513 /*dstream<<"event.MouseInput.Wheel="
\r
514 <<event.MouseInput.Wheel<<std::endl;*/
\r
515 if(event.MouseInput.Wheel < 0)
\r
517 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
520 g_selected_item = 0;
\r
522 else if(event.MouseInput.Wheel > 0)
\r
524 if(g_selected_item > 0)
\r
527 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
536 // This is used to check whether a key is being held down
\r
537 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
539 return keyIsDown[keyCode];
\r
544 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
545 keyIsDown[i] = false;
\r
547 leftclicked = false;
\r
548 rightclicked = false;
\r
549 leftreleased = false;
\r
550 rightreleased = false;
\r
552 left_active = false;
\r
553 middle_active = false;
\r
554 right_active = false;
\r
565 bool rightreleased;
\r
568 bool middle_active;
\r
572 // We use this array to store the current state of each key
\r
573 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
576 IrrlichtDevice *m_device;
\r
585 virtual ~InputHandler()
\r
589 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
591 virtual v2s32 getMousePos() = 0;
\r
592 virtual void setMousePos(s32 x, s32 y) = 0;
\r
594 virtual bool getLeftState() = 0;
\r
595 virtual bool getRightState() = 0;
\r
597 virtual bool getLeftClicked() = 0;
\r
598 virtual bool getRightClicked() = 0;
\r
599 virtual void resetLeftClicked() = 0;
\r
600 virtual void resetRightClicked() = 0;
\r
602 virtual bool getLeftReleased() = 0;
\r
603 virtual bool getRightReleased() = 0;
\r
604 virtual void resetLeftReleased() = 0;
\r
605 virtual void resetRightReleased() = 0;
\r
607 virtual void step(float dtime) {};
\r
609 virtual void clear() {};
\r
612 InputHandler *g_input = NULL;
\r
614 class RealInputHandler : public InputHandler
\r
617 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
619 m_receiver(receiver)
\r
622 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
624 return m_receiver->IsKeyDown(keyCode);
\r
626 virtual v2s32 getMousePos()
\r
628 return m_device->getCursorControl()->getPosition();
\r
630 virtual void setMousePos(s32 x, s32 y)
\r
632 m_device->getCursorControl()->setPosition(x, y);
\r
635 virtual bool getLeftState()
\r
637 return m_receiver->left_active;
\r
639 virtual bool getRightState()
\r
641 return m_receiver->right_active;
\r
644 virtual bool getLeftClicked()
\r
646 return m_receiver->leftclicked;
\r
648 virtual bool getRightClicked()
\r
650 return m_receiver->rightclicked;
\r
652 virtual void resetLeftClicked()
\r
654 m_receiver->leftclicked = false;
\r
656 virtual void resetRightClicked()
\r
658 m_receiver->rightclicked = false;
\r
661 virtual bool getLeftReleased()
\r
663 return m_receiver->leftreleased;
\r
665 virtual bool getRightReleased()
\r
667 return m_receiver->rightreleased;
\r
669 virtual void resetLeftReleased()
\r
671 m_receiver->leftreleased = false;
\r
673 virtual void resetRightReleased()
\r
675 m_receiver->rightreleased = false;
\r
680 resetRightClicked();
\r
681 resetLeftClicked();
\r
684 IrrlichtDevice *m_device;
\r
685 MyEventReceiver *m_receiver;
\r
688 class RandomInputHandler : public InputHandler
\r
691 RandomInputHandler()
\r
693 leftclicked = false;
\r
694 rightclicked = false;
\r
695 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
696 keydown[i] = false;
\r
698 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
700 return keydown[keyCode];
\r
702 virtual v2s32 getMousePos()
\r
706 virtual void setMousePos(s32 x, s32 y)
\r
708 mousepos = v2s32(x,y);
\r
711 virtual bool getLeftState()
\r
715 virtual bool getRightState()
\r
720 virtual bool getLeftClicked()
\r
722 return leftclicked;
\r
724 virtual bool getRightClicked()
\r
726 return rightclicked;
\r
728 virtual void resetLeftClicked()
\r
730 leftclicked = false;
\r
732 virtual void resetRightClicked()
\r
734 rightclicked = false;
\r
737 virtual bool getLeftReleased()
\r
741 virtual bool getRightReleased()
\r
745 virtual void resetLeftReleased()
\r
748 virtual void resetRightReleased()
\r
752 virtual void step(float dtime)
\r
755 static float counter1 = 0;
\r
759 counter1 = 0.1*Rand(1,10);
\r
760 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
761 g_selected_material++;
\r
763 g_selected_material = 0;*/
\r
764 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
767 g_selected_item = 0;
\r
771 static float counter1 = 0;
\r
775 counter1 = 0.1*Rand(1, 40);
\r
776 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
780 static float counter1 = 0;
\r
784 counter1 = 0.1*Rand(1, 40);
\r
785 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
789 static float counter1 = 0;
\r
793 counter1 = 0.1*Rand(1, 40);
\r
794 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
798 static float counter1 = 0;
\r
802 counter1 = 0.1*Rand(1, 40);
\r
803 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
807 static float counter1 = 0;
\r
811 counter1 = 0.1*Rand(1, 20);
\r
812 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
816 static float counter1 = 0;
\r
820 counter1 = 0.1*Rand(1, 30);
\r
821 leftclicked = true;
\r
825 static float counter1 = 0;
\r
829 counter1 = 0.1*Rand(1, 20);
\r
830 rightclicked = true;
\r
833 mousepos += mousespeed;
\r
836 s32 Rand(s32 min, s32 max)
\r
838 return (rand()%(max-min+1))+min;
\r
841 bool keydown[KEY_KEY_CODES_COUNT];
\r
848 void updateViewingRange(f32 frametime, Client *client)
\r
850 // Range_all messes up frametime_avg
\r
851 if(g_viewing_range_all == true)
\r
854 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
856 // Initialize to the target value
\r
857 static float frametime_avg = 1.0/wanted_fps;
\r
858 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
859 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
861 static f32 counter = 0;
\r
863 counter -= frametime;
\r
866 //counter = 1.0; //seconds
\r
867 counter = 0.5; //seconds
\r
869 //float freetime_ratio = 0.2;
\r
870 //float freetime_ratio = 0.4;
\r
871 float freetime_ratio = FREETIME_RATIO;
\r
873 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
875 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
877 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
878 + frametime_avg / frametime_wanted / 2.0;*/
\r
880 //float fraction = frametime_avg / frametime_wanted;
\r
882 static bool fraction_is_good = false;
\r
884 //float fraction_good_threshold = 0.1;
\r
885 //float fraction_bad_threshold = 0.25;
\r
886 float fraction_good_threshold = 0.075;
\r
887 float fraction_bad_threshold = 0.125;
\r
888 float fraction_limit;
\r
889 // Use high limit if fraction is good AND the fraction would
\r
890 // lower the range. We want to keep the range fairly high.
\r
891 if(fraction_is_good && fraction > 1.0)
\r
892 fraction_limit = fraction_bad_threshold;
\r
894 fraction_limit = fraction_good_threshold;
\r
896 if(fabs(fraction - 1.0) < fraction_limit)
\r
898 fraction_is_good = true;
\r
903 fraction_is_good = false;
\r
906 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
907 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
908 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
909 <<" faction = "<<fraction<<std::endl;*/
\r
911 JMutexAutoLock lock(g_range_mutex);
\r
913 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
914 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
916 s16 n = (float)g_viewing_range_nodes / fraction;
\r
917 if(n < viewing_range_nodes_min)
\r
918 n = viewing_range_nodes_min;
\r
919 if(n > viewing_range_nodes_max)
\r
920 n = viewing_range_nodes_max;
\r
922 bool can_change = true;
\r
924 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
925 can_change = false;
\r
928 g_viewing_range_nodes = n;
\r
930 /*dstream<<"g_viewing_range_nodes = "
\r
931 <<g_viewing_range_nodes<<std::endl;*/
\r
934 class GUIQuickInventory : public IEventReceiver
\r
938 gui::IGUIEnvironment* env,
\r
939 gui::IGUIElement* parent,
\r
942 Inventory *inventory):
\r
943 m_itemcount(itemcount),
\r
944 m_inventory(inventory)
\r
946 core::rect<s32> imgsize(0,0,48,48);
\r
947 core::rect<s32> textsize(0,0,48,16);
\r
948 v2s32 spacing(0, 64);
\r
949 for(s32 i=0; i<m_itemcount; i++)
\r
951 m_images.push_back(env->addImage(
\r
952 imgsize + pos + spacing*i
\r
954 m_images[i]->setScaleImage(true);
\r
955 m_texts.push_back(env->addStaticText(
\r
957 textsize + pos + spacing*i,
\r
960 m_texts[i]->setBackgroundColor(
\r
961 video::SColor(128,0,0,0));
\r
962 m_texts[i]->setTextAlignment(
\r
964 gui::EGUIA_UPPERLEFT);
\r
968 virtual bool OnEvent(const SEvent& event)
\r
973 void setSelection(s32 i)
\r
982 start = m_selection - m_itemcount / 2;
\r
984 InventoryList *mainlist = m_inventory->getList("main");
\r
986 for(s32 i=0; i<m_itemcount; i++)
\r
990 if(j > (s32)mainlist->getSize() - 1)
\r
991 j -= mainlist->getSize();
\r
993 j += mainlist->getSize();
\r
995 InventoryItem *item = mainlist->getItem(j);
\r
999 m_images[i]->setImage(NULL);
\r
1002 if(m_selection == j)
\r
1003 swprintf(t, 10, L"<-");
\r
1005 swprintf(t, 10, L"");
\r
1006 m_texts[i]->setText(t);
\r
1008 // The next ifs will segfault with a NULL pointer
\r
1013 m_images[i]->setImage(item->getImage());
\r
1016 if(m_selection == j)
\r
1017 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1019 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1020 m_texts[i]->setText(t);
\r
1026 core::array<gui::IGUIStaticText*> m_texts;
\r
1027 core::array<gui::IGUIImage*> m_images;
\r
1028 Inventory *m_inventory;
\r
1039 ChatLine(const std::wstring &a_text):
\r
1045 std::wstring text;
\r
1048 int main(int argc, char *argv[])
\r
1051 Low-level initialization
\r
1054 bool disable_stderr = false;
\r
1056 disable_stderr = true;
\r
1059 // Initialize debug streams
\r
1060 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1061 // Initialize debug stacks
\r
1062 debug_stacks_init();
\r
1064 DSTACK(__FUNCTION_NAME);
\r
1066 initializeMaterialProperties();
\r
1072 Parse command line
\r
1075 // List all allowed options
\r
1076 core::map<std::string, ValueSpec> allowed_options;
\r
1077 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1078 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1079 "Run server directly"));
\r
1080 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1081 "Load configuration from specified file"));
\r
1082 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1083 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1084 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1085 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1086 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1088 Settings cmd_args;
\r
1090 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1092 if(ret == false || cmd_args.getFlag("help"))
\r
1094 dstream<<"Allowed options:"<<std::endl;
\r
1095 for(core::map<std::string, ValueSpec>::Iterator
\r
1096 i = allowed_options.getIterator();
\r
1097 i.atEnd() == false; i++)
\r
1099 dstream<<" --"<<i.getNode()->getKey();
\r
1100 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1105 dstream<<" <value>";
\r
1107 dstream<<std::endl;
\r
1109 if(i.getNode()->getValue().help != NULL)
\r
1111 dstream<<" "<<i.getNode()->getValue().help
\r
1116 return cmd_args.getFlag("help") ? 0 : 1;
\r
1121 Basic initialization
\r
1124 // Initialize default settings
\r
1125 set_default_settings();
\r
1127 // Print startup message
\r
1128 dstream<<DTIME<<"minetest-c55"
\r
1129 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1130 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1133 // Set locale. This is for forcing '.' as the decimal point.
\r
1134 std::locale::global(std::locale("C"));
\r
1135 // This enables printing all characters in bitmap font
\r
1136 setlocale(LC_CTYPE, "en_US");
\r
1138 // Initialize sockets
\r
1140 atexit(sockets_cleanup);
\r
1150 // Path of configuration file in use
\r
1151 std::string configpath = "";
\r
1153 if(cmd_args.exists("config"))
\r
1155 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1158 dstream<<"Could not read configuration from \""
\r
1159 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1162 configpath = cmd_args.get("config");
\r
1166 const char *filenames[2] =
\r
1168 "../minetest.conf",
\r
1169 "../../minetest.conf"
\r
1172 for(u32 i=0; i<2; i++)
\r
1174 bool r = g_settings.readConfigFile(filenames[i]);
\r
1177 configpath = filenames[i];
\r
1183 // Initialize random seed
\r
1189 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1190 || cmd_args.getFlag("enable-unittests") == true)
\r
1196 Global range mutex
\r
1198 g_range_mutex.Init();
\r
1199 assert(g_range_mutex.IsInitialized());
\r
1201 // Read map parameters from settings
\r
1203 HMParams hm_params;
\r
1204 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1205 hm_params.randmax = g_settings.get("height_randmax");
\r
1206 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1207 hm_params.base = g_settings.get("height_base");
\r
1209 MapParams map_params;
\r
1210 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1211 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1217 std::cout<<std::endl<<std::endl;
\r
1220 <<" .__ __ __ "<<std::endl
\r
1221 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1222 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1223 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1224 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1225 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1228 std::cout<<std::endl;
\r
1229 //char templine[100];
\r
1233 if(cmd_args.exists("port"))
\r
1235 port = cmd_args.getU16("port");
\r
1239 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1240 std::cout<<"-> "<<port<<std::endl;
\r
1243 if(cmd_args.getFlag("server"))
\r
1245 DSTACK("Dedicated server branch");
\r
1247 std::cout<<std::endl;
\r
1248 std::cout<<"========================"<<std::endl;
\r
1249 std::cout<<"Running dedicated server"<<std::endl;
\r
1250 std::cout<<"========================"<<std::endl;
\r
1251 std::cout<<std::endl;
\r
1253 Server server("../map", hm_params, map_params);
\r
1254 server.start(port);
\r
1258 // This is kind of a hack but can be done like this
\r
1259 // because server.step() is very light
\r
1261 server.step(0.030);
\r
1263 static int counter = 0;
\r
1269 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1270 core::list<PlayerInfo>::Iterator i;
\r
1271 static u32 sum_old = 0;
\r
1272 u32 sum = PIChecksum(list);
\r
1273 if(sum != sum_old)
\r
1275 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1276 for(i=list.begin(); i!=list.end(); i++)
\r
1278 i->PrintLine(&std::cout);
\r
1288 bool hosting = false;
\r
1289 char connect_name[100] = "";
\r
1291 if(cmd_args.exists("address"))
\r
1293 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1295 else if(is_yes(g_settings.get("host_game")) == false)
\r
1297 if(g_settings.get("address") != "")
\r
1299 std::cout<<g_settings.get("address")<<std::endl;
\r
1300 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1304 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1305 std::cin.getline(connect_name, 100);
\r
1309 if(connect_name[0] == 0){
\r
1310 snprintf(connect_name, 100, "127.0.0.1");
\r
1315 std::cout<<"> Hosting game"<<std::endl;
\r
1317 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1319 char playername[PLAYERNAME_SIZE] = "";
\r
1320 if(g_settings.get("name") != "")
\r
1322 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1326 std::cout<<"Name of player: ";
\r
1327 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1329 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1332 Resolution selection
\r
1335 bool fullscreen = false;
\r
1336 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1337 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1341 MyEventReceiver receiver;
\r
1343 video::E_DRIVER_TYPE driverType;
\r
1346 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1347 driverType = video::EDT_OPENGL;
\r
1349 driverType = video::EDT_OPENGL;
\r
1352 // create device and exit if creation failed
\r
1354 IrrlichtDevice *device;
\r
1355 device = createDevice(driverType,
\r
1356 core::dimension2d<u32>(screenW, screenH),
\r
1357 16, fullscreen, false, false, &receiver);
\r
1360 return 1; // could not create selected driver.
\r
1362 g_device = device;
\r
1363 g_irrlicht = new IrrlichtWrapper(device);
\r
1365 //g_device = device;
\r
1367 device->setResizable(true);
\r
1369 bool random_input = g_settings.getBool("random_input")
\r
1370 || cmd_args.getFlag("random-input");
\r
1372 g_input = new RandomInputHandler();
\r
1374 g_input = new RealInputHandler(device, &receiver);
\r
1377 Continue initialization
\r
1380 video::IVideoDriver* driver = device->getVideoDriver();
\r
1383 This changes the minimum allowed number of vertices in a VBO
\r
1385 //driver->setMinHardwareBufferVertexCount(1);
\r
1387 scene::ISceneManager* smgr = device->getSceneManager();
\r
1389 guienv = device->getGUIEnvironment();
\r
1390 gui::IGUISkin* skin = guienv->getSkin();
\r
1391 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1393 skin->setFont(font);
\r
1395 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1396 dstream<<"text_height="<<text_height<<std::endl;
\r
1398 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1399 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1400 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1401 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1402 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1403 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1405 const wchar_t *text = L"Loading and connecting...";
\r
1406 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1407 core::vector2d<s32> textsize(300, text_height);
\r
1408 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1410 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1411 text, textrect, false, false);
\r
1412 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1414 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1415 guienv->drawAll();
\r
1416 driver->endScene();
\r
1419 Preload some textures
\r
1422 tile_materials_preload(g_irrlicht);
\r
1425 Make a scope here for the client so that it gets removed
\r
1426 before the irrlicht device
\r
1430 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1435 SharedPtr<Server> server;
\r
1437 server = new Server("../map", hm_params, map_params);
\r
1438 server->start(port);
\r
1445 Client client(device, playername,
\r
1447 g_viewing_range_nodes,
\r
1448 g_viewing_range_all);
\r
1450 g_client = &client;
\r
1452 Address connect_address(0,0,0,0, port);
\r
1454 connect_address.Resolve(connect_name);
\r
1456 catch(ResolveError &e)
\r
1458 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1462 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1463 client.connect(connect_address);
\r
1466 while(client.connectedAndInitialized() == false)
\r
1469 if(server != NULL){
\r
1470 server->step(0.1);
\r
1475 catch(con::PeerNotFoundException &e)
\r
1477 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1484 /*scene::ISceneNode* skybox;
\r
1485 skybox = smgr->addSkyBoxSceneNode(
\r
1486 driver->getTexture("../data/skybox2.png"),
\r
1487 driver->getTexture("../data/skybox3.png"),
\r
1488 driver->getTexture("../data/skybox1.png"),
\r
1489 driver->getTexture("../data/skybox1.png"),
\r
1490 driver->getTexture("../data/skybox1.png"),
\r
1491 driver->getTexture("../data/skybox1.png"));*/
\r
1494 Create the camera node
\r
1497 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1498 0, // Camera parent
\r
1499 v3f(BS*100, BS*2, BS*100), // Look from
\r
1500 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1504 if(camera == NULL)
\r
1507 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1509 camera->setFOV(FOV_ANGLE);
\r
1511 // Just so big a value that everything rendered is visible
\r
1512 camera->setFarValue(100000*BS);
\r
1514 f32 camera_yaw = 0; // "right/left"
\r
1515 f32 camera_pitch = 0; // "up/down"
\r
1521 gui_loadingtext->remove();
\r
1524 Add some gui stuff
\r
1527 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1528 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1531 We need some kind of a root node to be able to add
\r
1532 custom elements directly on the screen.
\r
1533 Otherwise they won't be automatically drawn.
\r
1535 guiroot = guienv->addStaticText(L"",
\r
1536 core::rect<s32>(0, 0, 10000, 10000));
\r
1538 // Test the text input system
\r
1539 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1541 /*GUIMessageMenu *menu =
\r
1542 new GUIMessageMenu(guienv, guiroot, -1,
\r
1543 &g_active_menu_count,
\r
1547 // Launch pause menu
\r
1548 /*(new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1549 &g_active_menu_count))->drop();*/
\r
1551 // First line of debug text
\r
1552 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1554 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1556 // Second line of debug text
\r
1557 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1559 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1562 // At the middle of the screen
\r
1563 // Object infos are shown in this
\r
1564 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1566 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1570 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1571 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1572 core::rect<s32>(70, 60, 795, 150),
\r
1574 chat_guitext->setBackgroundColor(video::SColor(96,0,0,0));
\r
1575 core::list<ChatLine> chat_lines;
\r
1578 Some statistics are collected in these
\r
1581 u32 beginscenetime = 0;
\r
1582 u32 scenetime = 0;
\r
1583 u32 endscenetime = 0;
\r
1586 //throw con::PeerNotFoundException("lol");
\r
1592 bool first_loop_after_window_activation = true;
\r
1594 // Time is in milliseconds
\r
1595 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1596 // NOTE: Have to call run() between calls of this to update the timer
\r
1597 u32 lasttime = device->getTimer()->getTime();
\r
1599 while(device->run())
\r
1602 Run global IrrlichtWrapper's main thread processing stuff
\r
1604 g_irrlicht->Run();
\r
1607 Random calculations
\r
1609 v2u32 screensize = driver->getScreenSize();
\r
1610 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1612 // Hilight boxes collected during the loop and displayed
\r
1613 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1616 std::wstring infotext;
\r
1618 //TimeTaker //timer1("//timer1");
\r
1620 // Time of frame without fps limit
\r
1624 // not using getRealTime is necessary for wine
\r
1625 u32 time = device->getTimer()->getTime();
\r
1626 if(time > lasttime)
\r
1627 busytime_u32 = time - lasttime;
\r
1630 busytime = busytime_u32 / 1000.0;
\r
1633 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1635 // Absolutelu necessary for wine!
\r
1642 updateViewingRange(busytime, &client);
\r
1649 float fps_max = g_settings.getFloat("fps_max");
\r
1650 u32 frametime_min = 1000./fps_max;
\r
1652 if(busytime_u32 < frametime_min)
\r
1654 u32 sleeptime = frametime_min - busytime_u32;
\r
1655 device->sleep(sleeptime);
\r
1659 // Absolutelu necessary for wine!
\r
1663 Time difference calculation
\r
1665 f32 dtime; // in seconds
\r
1667 u32 time = device->getTimer()->getTime();
\r
1668 if(time > lasttime)
\r
1669 dtime = (time - lasttime) / 1000.0;
\r
1675 Time average and jitter calculation
\r
1678 static f32 dtime_avg1 = 0.0;
\r
1679 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1680 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1682 static f32 dtime_jitter1_max_sample = 0.0;
\r
1683 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1685 static f32 jitter1_max = 0.0;
\r
1686 static f32 counter = 0.0;
\r
1687 if(dtime_jitter1 > jitter1_max)
\r
1688 jitter1_max = dtime_jitter1;
\r
1693 dtime_jitter1_max_sample = jitter1_max;
\r
1694 dtime_jitter1_max_fraction
\r
1695 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1696 jitter1_max = 0.0;
\r
1699 Control freetime ratio
\r
1701 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1703 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1704 g_freetime_ratio += 0.01;
\r
1708 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1709 g_freetime_ratio -= 0.01;
\r
1715 Busytime average and jitter calculation
\r
1718 static f32 busytime_avg1 = 0.0;
\r
1719 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1720 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1722 static f32 busytime_jitter1_max_sample = 0.0;
\r
1723 static f32 busytime_jitter1_min_sample = 0.0;
\r
1725 static f32 jitter1_max = 0.0;
\r
1726 static f32 jitter1_min = 0.0;
\r
1727 static f32 counter = 0.0;
\r
1728 if(busytime_jitter1 > jitter1_max)
\r
1729 jitter1_max = busytime_jitter1;
\r
1730 if(busytime_jitter1 < jitter1_min)
\r
1731 jitter1_min = busytime_jitter1;
\r
1733 if(counter > 0.0){
\r
1735 busytime_jitter1_max_sample = jitter1_max;
\r
1736 busytime_jitter1_min_sample = jitter1_min;
\r
1737 jitter1_max = 0.0;
\r
1738 jitter1_min = 0.0;
\r
1743 Debug info for client
\r
1746 static float counter = 0.0;
\r
1751 client.printDebugInfo(std::cout);
\r
1756 Input handler step()
\r
1758 g_input->step(dtime);
\r
1761 Player speed control
\r
1770 bool a_superspeed,
\r
1773 PlayerControl control(
\r
1774 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1775 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1776 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1777 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1778 g_input->isKeyDown(irr::KEY_SPACE),
\r
1779 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1783 client.setPlayerControl(control);
\r
1787 Process environment
\r
1791 //TimeTaker timer("client.step(dtime)");
\r
1792 client.step(dtime);
\r
1793 //client.step(dtime_avg1);
\r
1796 if(server != NULL)
\r
1798 //TimeTaker timer("server->step(dtime)");
\r
1799 server->step(dtime);
\r
1802 v3f player_position = client.getPlayerPosition();
\r
1804 //TimeTaker //timer2("//timer2");
\r
1807 Mouse and camera control
\r
1810 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1813 device->getCursorControl()->setVisible(false);
\r
1815 if(first_loop_after_window_activation){
\r
1816 //std::cout<<"window active, first loop"<<std::endl;
\r
1817 first_loop_after_window_activation = false;
\r
1820 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1821 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1822 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1823 camera_yaw -= dx*0.2;
\r
1824 camera_pitch += dy*0.2;
\r
1825 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1826 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1828 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1831 device->getCursorControl()->setVisible(true);
\r
1833 //std::cout<<"window inactive"<<std::endl;
\r
1834 first_loop_after_window_activation = true;
\r
1837 camera_yaw = wrapDegrees(camera_yaw);
\r
1838 camera_pitch = wrapDegrees(camera_pitch);
\r
1840 v3f camera_direction = v3f(0,0,1);
\r
1841 camera_direction.rotateYZBy(camera_pitch);
\r
1842 camera_direction.rotateXZBy(camera_yaw);
\r
1844 v3f camera_position =
\r
1845 player_position + v3f(0, BS+BS/2, 0);
\r
1847 camera->setPosition(camera_position);
\r
1848 // *100.0 helps in large map coordinates
\r
1849 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1851 if(FIELD_OF_VIEW_TEST){
\r
1852 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1853 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1856 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1857 //TimeTaker timer("client.updateCamera");
\r
1858 client.updateCamera(camera_position, camera_direction);
\r
1862 //TimeTaker //timer3("//timer3");
\r
1865 Calculate what block is the crosshair pointing to
\r
1868 //u32 t1 = device->getTimer()->getRealTime();
\r
1870 //f32 d = 4; // max. distance
\r
1871 f32 d = 4; // max. distance
\r
1872 core::line3d<f32> shootline(camera_position,
\r
1873 camera_position + camera_direction * BS * (d+1));
\r
1875 MapBlockObject *selected_object = client.getSelectedObject
\r
1876 (d*BS, camera_position, shootline);
\r
1879 If it's pointing to a MapBlockObject
\r
1882 if(selected_object != NULL)
\r
1884 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1886 core::aabbox3d<f32> box_on_map
\r
1887 = selected_object->getSelectionBoxOnMap();
\r
1889 hilightboxes.push_back(box_on_map);
\r
1891 infotext = narrow_to_wide(selected_object->infoText());
\r
1893 if(g_input->getLeftClicked())
\r
1895 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1896 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1897 selected_object->getId(), g_selected_item);
\r
1899 else if(g_input->getRightClicked())
\r
1901 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1903 Check if we want to modify the object ourselves
\r
1905 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1907 dstream<<"Sign object right-clicked"<<std::endl;
\r
1909 if(random_input == false)
\r
1911 // Get a new text for it
\r
1913 TextDest *dest = new TextDestSign(
\r
1914 selected_object->getBlock()->getPos(),
\r
1915 selected_object->getId(),
\r
1918 SignObject *sign_object = (SignObject*)selected_object;
\r
1920 std::wstring wtext =
\r
1921 narrow_to_wide(sign_object->getText());
\r
1923 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1924 &g_active_menu_count, dest,
\r
1929 Otherwise pass the event to the server as-is
\r
1933 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1934 selected_object->getId(), g_selected_item);
\r
1938 else // selected_object == NULL
\r
1942 Find out which node we are pointing at
\r
1945 bool nodefound = false;
\r
1947 v3s16 neighbourpos;
\r
1948 core::aabbox3d<f32> nodefacebox;
\r
1949 f32 mindistance = BS * 1001;
\r
1951 v3s16 pos_i = floatToInt(player_position);
\r
1953 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1957 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1958 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1959 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1960 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1961 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1962 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1964 for(s16 y = ystart; y <= yend; y++)
\r
1965 for(s16 z = zstart; z <= zend; z++)
\r
1966 for(s16 x = xstart; x <= xend; x++)
\r
1971 n = client.getNode(v3s16(x,y,z));
\r
1972 if(content_pointable(n.d) == false)
\r
1975 catch(InvalidPositionException &e)
\r
1981 v3f npf = intToFloat(np);
\r
1986 v3s16(0,0,1), // back
\r
1987 v3s16(0,1,0), // top
\r
1988 v3s16(1,0,0), // right
\r
1989 v3s16(0,0,-1), // front
\r
1990 v3s16(0,-1,0), // bottom
\r
1991 v3s16(-1,0,0), // left
\r
1997 if(n.d == CONTENT_TORCH)
\r
1999 v3s16 dir = unpackDir(n.dir);
\r
2000 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2001 dir_f *= BS/2 - BS/6 - BS/20;
\r
2002 v3f cpf = npf + dir_f;
\r
2003 f32 distance = (cpf - camera_position).getLength();
\r
2005 core::aabbox3d<f32> box;
\r
2008 if(dir == v3s16(0,-1,0))
\r
2010 box = core::aabbox3d<f32>(
\r
2011 npf - v3f(BS/6, BS/2, BS/6),
\r
2012 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2016 else if(dir == v3s16(0,1,0))
\r
2018 box = core::aabbox3d<f32>(
\r
2019 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2020 npf + v3f(BS/6, BS/2, BS/6)
\r
2026 box = core::aabbox3d<f32>(
\r
2027 cpf - v3f(BS/6, BS/3, BS/6),
\r
2028 cpf + v3f(BS/6, BS/3, BS/6)
\r
2032 if(distance < mindistance)
\r
2034 if(box.intersectsWithLine(shootline))
\r
2038 neighbourpos = np;
\r
2039 mindistance = distance;
\r
2040 nodefacebox = box;
\r
2049 for(u16 i=0; i<6; i++)
\r
2051 v3f dir_f = v3f(dirs[i].X,
\r
2052 dirs[i].Y, dirs[i].Z);
\r
2053 v3f centerpoint = npf + dir_f * BS/2;
\r
2055 (centerpoint - camera_position).getLength();
\r
2057 if(distance < mindistance)
\r
2059 core::CMatrix4<f32> m;
\r
2060 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2062 // This is the back face
\r
2063 v3f corners[2] = {
\r
2064 v3f(BS/2, BS/2, BS/2),
\r
2065 v3f(-BS/2, -BS/2, BS/2+d)
\r
2068 for(u16 j=0; j<2; j++)
\r
2070 m.rotateVect(corners[j]);
\r
2071 corners[j] += npf;
\r
2074 core::aabbox3d<f32> facebox(corners[0]);
\r
2075 facebox.addInternalPoint(corners[1]);
\r
2077 if(facebox.intersectsWithLine(shootline))
\r
2081 neighbourpos = np + dirs[i];
\r
2082 mindistance = distance;
\r
2083 nodefacebox = facebox;
\r
2085 } // if distance < mindistance
\r
2087 } // regular block
\r
2090 static float nodig_delay_counter = 0.0;
\r
2094 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2096 static float dig_time = 0.0;
\r
2097 static u16 dig_index = 0;
\r
2099 hilightboxes.push_back(nodefacebox);
\r
2101 if(g_input->getLeftReleased())
\r
2103 client.clearTempMod(nodepos);
\r
2107 if(nodig_delay_counter > 0.0)
\r
2109 nodig_delay_counter -= dtime;
\r
2113 if(nodepos != nodepos_old)
\r
2115 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2116 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2118 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2120 client.clearTempMod(nodepos_old);
\r
2125 if(g_input->getLeftClicked() ||
\r
2126 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2128 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2129 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2131 if(g_input->getLeftClicked())
\r
2133 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2135 if(g_input->getLeftState())
\r
2137 MapNode n = client.getNode(nodepos);
\r
2139 // Get tool name. Default is "" = bare hands
\r
2140 std::string toolname = "";
\r
2141 InventoryList *mlist = local_inventory.getList("main");
\r
2144 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2145 if(item && (std::string)item->getName() == "ToolItem")
\r
2147 ToolItem *titem = (ToolItem*)item;
\r
2148 toolname = titem->getToolName();
\r
2152 // Get digging properties for material and tool
\r
2153 u8 material = n.d;
\r
2154 DiggingProperties prop =
\r
2155 getDiggingProperties(material, toolname);
\r
2157 float dig_time_complete = 0.0;
\r
2159 if(prop.diggable == false)
\r
2161 /*dstream<<"Material "<<(int)material
\r
2162 <<" not diggable with \""
\r
2163 <<toolname<<"\""<<std::endl;*/
\r
2164 // I guess nobody will wait for this long
\r
2165 dig_time_complete = 10000000.0;
\r
2169 dig_time_complete = prop.time;
\r
2172 if(dig_time_complete >= 0.001)
\r
2174 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2175 * dig_time/dig_time_complete);
\r
2177 // This is for torches
\r
2180 dig_index = CRACK_ANIMATION_LENGTH;
\r
2183 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2185 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2186 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2190 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2191 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2192 client.clearTempMod(nodepos);
\r
2193 client.removeNode(nodepos);
\r
2197 nodig_delay_counter = dig_time_complete
\r
2198 / (float)CRACK_ANIMATION_LENGTH;
\r
2200 // We don't want a corresponding delay to
\r
2201 // very time consuming nodes
\r
2202 if(nodig_delay_counter > 0.5)
\r
2204 nodig_delay_counter = 0.5;
\r
2206 // We want a slight delay to very little
\r
2207 // time consuming nodes
\r
2208 //float mindelay = 0.15;
\r
2209 float mindelay = 0.20;
\r
2210 if(nodig_delay_counter < mindelay)
\r
2212 nodig_delay_counter = mindelay;
\r
2216 dig_time += dtime;
\r
2220 if(g_input->getRightClicked())
\r
2222 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2223 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2226 nodepos_old = nodepos;
\r
2231 } // selected_object == NULL
\r
2233 g_input->resetLeftClicked();
\r
2234 g_input->resetRightClicked();
\r
2236 if(g_input->getLeftReleased())
\r
2238 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2240 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2242 if(g_input->getRightReleased())
\r
2244 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2248 g_input->resetLeftReleased();
\r
2249 g_input->resetRightReleased();
\r
2252 Calculate stuff for drawing
\r
2255 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2257 u32 daynight_ratio = client.getDayNightRatio();
\r
2258 video::SColor bgcolor = video::SColor(
\r
2260 skycolor.getRed() * daynight_ratio / 1000,
\r
2261 skycolor.getGreen() * daynight_ratio / 1000,
\r
2262 skycolor.getBlue() * daynight_ratio / 1000);
\r
2268 if(g_settings.getBool("enable_fog") == true)
\r
2270 f32 range = g_viewing_range_nodes * BS;
\r
2271 if(g_viewing_range_all)
\r
2272 range = 100000*BS;
\r
2276 video::EFT_FOG_LINEAR,
\r
2280 false, // pixel fog
\r
2281 false // range fog
\r
2287 Update gui stuff (0ms)
\r
2290 //TimeTaker guiupdatetimer("Gui updating");
\r
2293 wchar_t temptext[150];
\r
2295 static float drawtime_avg = 0;
\r
2296 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2297 static float beginscenetime_avg = 0;
\r
2298 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2299 static float scenetime_avg = 0;
\r
2300 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2301 static float endscenetime_avg = 0;
\r
2302 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2304 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2306 L", R: range_all=%i"
\r
2308 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2310 g_viewing_range_all,
\r
2312 beginscenetime_avg,
\r
2317 guitext->setText(temptext);
\r
2321 wchar_t temptext[150];
\r
2322 swprintf(temptext, 150,
\r
2323 L"(% .1f, % .1f, % .1f)"
\r
2324 L" (% .3f < btime_jitter < % .3f"
\r
2325 L", dtime_jitter = % .1f %%)",
\r
2326 player_position.X/BS,
\r
2327 player_position.Y/BS,
\r
2328 player_position.Z/BS,
\r
2329 busytime_jitter1_min_sample,
\r
2330 busytime_jitter1_max_sample,
\r
2331 dtime_jitter1_max_fraction * 100.0
\r
2334 guitext2->setText(temptext);
\r
2338 guitext_info->setText(infotext.c_str());
\r
2342 Get chat messages from client
\r
2345 // Get new messages
\r
2346 std::wstring message;
\r
2347 while(client.getChatMessage(message))
\r
2349 chat_lines.push_back(ChatLine(message));
\r
2350 if(chat_lines.size() > 7)
\r
2352 core::list<ChatLine>::Iterator
\r
2353 i = chat_lines.begin();
\r
2354 chat_lines.erase(i);
\r
2357 // Append them to form the whole static text and throw
\r
2358 // it to the gui element
\r
2359 std::wstring whole;
\r
2360 u16 to_be_removed_count = 0;
\r
2361 for(core::list<ChatLine>::Iterator
\r
2362 i = chat_lines.begin();
\r
2363 i != chat_lines.end(); i++)
\r
2365 (*i).age += dtime;
\r
2366 if((*i).age > 300.0)
\r
2368 to_be_removed_count++;
\r
2371 whole += (*i).text + L'\n';
\r
2373 for(u16 i=0; i<to_be_removed_count; i++)
\r
2375 core::list<ChatLine>::Iterator
\r
2376 it = chat_lines.begin();
\r
2377 chat_lines.erase(it);
\r
2379 chat_guitext->setText(whole.c_str());
\r
2380 // Update gui element size and position
\r
2381 core::rect<s32> rect(
\r
2383 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2384 screensize.X - 10,
\r
2387 chat_guitext->setRelativePosition(rect);
\r
2394 static u16 old_selected_item = 65535;
\r
2395 if(client.getLocalInventoryUpdated()
\r
2396 || g_selected_item != old_selected_item)
\r
2398 old_selected_item = g_selected_item;
\r
2399 //std::cout<<"Updating local inventory"<<std::endl;
\r
2400 client.getLocalInventory(local_inventory);
\r
2401 quick_inventory->setSelection(g_selected_item);
\r
2402 quick_inventory->update();
\r
2406 Send actions returned by the inventory menu
\r
2408 while(inventory_action_queue.size() != 0)
\r
2410 InventoryAction *a = inventory_action_queue.pop_front();
\r
2412 client.sendInventoryAction(a);
\r
2421 TimeTaker drawtimer("Drawing");
\r
2425 TimeTaker timer("beginScene");
\r
2426 driver->beginScene(true, true, bgcolor);
\r
2427 //driver->beginScene(false, true, bgcolor);
\r
2428 beginscenetime = timer.stop(true);
\r
2433 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2436 TimeTaker timer("smgr");
\r
2438 scenetime = timer.stop(true);
\r
2442 //TimeTaker timer9("auxiliary drawings");
\r
2446 //TimeTaker //timer10("//timer10");
\r
2448 video::SMaterial m;
\r
2450 m.Lighting = false;
\r
2451 driver->setMaterial(m);
\r
2453 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2455 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2456 i != hilightboxes.end(); i++)
\r
2458 /*std::cout<<"hilightbox min="
\r
2459 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2461 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2463 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2469 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2470 displaycenter + core::vector2d<s32>(10,0),
\r
2471 video::SColor(255,255,255,255));
\r
2472 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2473 displaycenter + core::vector2d<s32>(0,10),
\r
2474 video::SColor(255,255,255,255));
\r
2479 //TimeTaker //timer11("//timer11");
\r
2485 guienv->drawAll();
\r
2489 TimeTaker timer("endScene");
\r
2490 driver->endScene();
\r
2491 endscenetime = timer.stop(true);
\r
2494 drawtime = drawtimer.stop(true);
\r
2500 static s16 lastFPS = 0;
\r
2501 //u16 fps = driver->getFPS();
\r
2502 u16 fps = (1.0/dtime_avg1);
\r
2504 if (lastFPS != fps)
\r
2506 core::stringw str = L"Minetest [";
\r
2507 str += driver->getName();
\r
2511 device->setWindowCaption(str.c_str());
\r
2517 device->yield();*/
\r
2520 delete quick_inventory;
\r
2522 } // client is deleted at this point
\r
2527 In the end, delete the Irrlicht device.
\r
2532 Update configuration file
\r
2534 /*if(configpath != "")
\r
2536 g_settings.updateConfigFile(configpath.c_str());
\r
2540 catch(con::PeerNotFoundException &e)
\r
2542 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2546 GUIMessageMenu *menu =
\r
2547 new GUIMessageMenu(guienv, guiroot, -1,
\r
2548 &g_active_menu_count,
\r
2549 L"Connection timed out");
\r
2551 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
2553 dstream<<"Created menu"<<std::endl;
\r
2555 while(g_device->run() && menu->getStatus() == false)
\r
2557 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2558 guienv->drawAll();
\r
2559 driver->endScene();
\r
2562 dstream<<"Dropping menu"<<std::endl;
\r
2567 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2569 This is what has to be done in every thread to get suitable debug info
\r
2571 catch(std::exception &e)
\r
2573 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2574 <<e.what()<<std::endl;
\r
2579 debugstreams_deinit();
\r