3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
\r
5 This program is free software; you can redistribute it and/or modify
\r
6 it under the terms of the GNU General Public License as published by
\r
7 the Free Software Foundation; either version 2 of the License, or
\r
8 (at your option) any later version.
\r
10 This program is distributed in the hope that it will be useful,
\r
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 GNU General Public License for more details.
\r
15 You should have received a copy of the GNU General Public License along
\r
16 with this program; if not, write to the Free Software Foundation, Inc.,
\r
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
21 =============================== NOTES ==============================
\r
22 NOTE: Things starting with TODO are sometimes only suggestions.
\r
24 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
25 is an apparanet memory leak in irrlicht when using it (not sure)
\r
27 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
28 NOTE: Global locale is now set at initialization
\r
30 SUGG: Fix address to be ipv6 compatible
\r
32 FIXME: When a new sector is generated, it may change the ground level
\r
33 of it's and it's neighbors border that two blocks that are
\r
34 above and below each other and that are generated before and
\r
35 after the sector heightmap generation (order doesn't matter),
\r
36 can have a small gap between each other at the border.
\r
37 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
38 using for UnlimitedHeightmap? (getting all neighbors
\r
41 SUGG: Transfer more blocks in a single packet
\r
42 SUGG: A blockdata combiner class, to which blocks are added and at
\r
43 destruction it sends all the stuff in as few packets as possible.
\r
45 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
46 SUGG: Fetch stuff mainly from the viewing direction
\r
48 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
49 - This enables saving many packets and making a faster connection
\r
50 - This also enables server to check if client has received the
\r
51 most recent block sent, for example.
\r
52 SUGG: Add a sane bandwidth throttling system to Connection
\r
54 SUGG: More fine-grained control of client's dumping of blocks from
\r
56 - ...What does this mean in the first place?
\r
58 SUGG: A map editing mode (similar to dedicated server mode)
\r
60 SUGG: Add a time value to the param of footstepped grass and check it
\r
61 against a global timer when a block is accessed, to make old
\r
64 SUGG: Make a copy of close-range environment on client for showing
\r
65 on screen, with minimal mutexes to slow down the main loop
\r
67 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
68 it by sending more stuff in a single packet.
\r
69 - Add a packet queue to RemoteClient, from which packets will be
\r
70 combined with object data packets
\r
71 - This is not exactly trivial: the object data packets are
\r
72 sometimes very big by themselves
\r
74 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
75 - This will allow saving ages of rats on disk but not sending
\r
78 SUGG: Implement lighting using VoxelManipulator
\r
79 - Would it be significantly faster?
\r
81 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
82 - Does their position get saved to a border value or something?
\r
83 - Does this happen anymore?
\r
85 SUGG: MovingObject::move and Player::move are basically the same.
\r
88 SUGG: Implement a "Fast check queue" (a queue with a map for checking
\r
89 if something is already in it)
\r
90 - Use it in active block queue in water flowing
\r
92 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
94 SUGG: A version number to blocks, which increments when the block is
\r
95 modified (node add/remove, water update, lighting update)
\r
96 - This can then be used to make sure the most recent version of
\r
97 a block has been sent to client
\r
99 SUGG: Make the amount of blocks sending to client and the total
\r
100 amount of blocks dynamically limited. Transferring blocks is the
\r
101 main network eater of this system, so it is the one that has
\r
102 to be throttled so that RTTs stay low.
\r
104 SUGG: Meshes of blocks could be split into 6 meshes facing into
\r
105 different directions and then only those drawn that need to be
\r
106 - Also an 1-dimensional tile map would be nice probably
\r
108 TODO: Untie client network operations from framerate
\r
109 - Needs some input queues or something
\r
110 - Not really necessary?
\r
112 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
114 - That is >500 vertices
\r
116 TODO: Better dungeons
\r
119 TODO: Startup and configuration menu
\r
121 TODO: There are some lighting-related todos and fixmes in
\r
122 ServerMap::emergeBlock
\r
124 TODO: Proper handling of spawning place (try to find something that
\r
125 is not in the middle of an ocean (some land to stand on at
\r
126 least) and save it in map config.
\r
128 TODO: Players to only be hidden when the client quits.
\r
129 TODO: - Players to be saved on disk, with inventory
\r
130 TODO: Players to be saved as text in map/players/<name>
\r
131 TODO: Player inventory to be saved on disk
\r
133 TODO: Make fetching sector's blocks more efficient when rendering
\r
134 sectors that have very large amounts of blocks (on client)
\r
136 TODO: Make the video backend selectable
\r
138 Block object server side:
\r
139 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
140 - For all blocks in the buffer, objects are stepped(). This
\r
141 means they are active.
\r
142 - TODO: A global active buffer is needed for the server
\r
143 - TODO: A timestamp to blocks
\r
144 - TODO: All blocks going in and out of the buffer are recorded.
\r
145 - TODO: For outgoing blocks, timestamp is written.
\r
146 - TODO: For incoming blocks, time difference is calculated and
\r
147 objects are stepped according to it.
\r
149 TODO: Copy the text of the last picked sign to inventory in creative
\r
152 TODO: Get rid of GotSplitPacketException
\r
154 TODO: Check what goes wrong with caching map to disk (Kray)
\r
156 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
158 TODO: TOSERVER_LEAVE
\r
160 TODO: Better handling of objects and mobs
\r
162 - There has to be some way to do it with less spaghetti code
\r
163 - Make separate classes for client and server
\r
164 - Client should not discriminate between blocks, server should
\r
165 - Make other players utilize the same framework
\r
166 - This is also needed for objects that don't get sent to client
\r
167 but are used for triggers etc
\r
169 TODO: Draw big amounts of torches better (that is, throw them in the
\r
170 same meshbuffer (can the meshcollector class be used?))
\r
172 TODO: Check if the usage of Client::isFetchingBlocks() in
\r
173 updateViewingRange() actually does something
\r
175 TODO: Make an option to the server to disable building and digging near
\r
176 the starting position
\r
178 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
179 need an additional metadata field for the texts
\r
180 - This is also needed for item container chests
\r
181 TODO: There has to be some better way to handle static objects than to
\r
182 send them all the time. This affects signs and item objects.
\r
185 ======================================================================
\r
187 TODO: Tool capability table: Which materials, at what speed, how much
\r
189 TODO: Transferring of the table from server to client
\r
191 ======================================================================
\r
196 Setting this to 1 enables a special camera mode that forces
\r
197 the renderers to think that the camera statically points from
\r
198 the starting place to a static direction.
\r
200 This allows one to move around with the player and see what
\r
201 is actually drawn behind solid things and behind the player.
\r
203 #define FIELD_OF_VIEW_TEST 0
\r
205 #ifdef UNITTEST_DISABLE
\r
207 #pragma message ("Disabling unit tests")
\r
209 #warning "Disabling unit tests"
\r
211 // Disable unit tests
\r
212 #define ENABLE_TESTS 0
\r
214 // Enable unit tests
\r
215 #define ENABLE_TESTS 1
\r
219 #pragma comment(lib, "Irrlicht.lib")
\r
220 #pragma comment(lib, "jthread.lib")
\r
221 #pragma comment(lib, "zlibwapi.lib")
\r
222 // This would get rid of the console window
\r
223 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
226 #include <iostream>
\r
228 #include <jmutexautolock.h>
\r
229 #include <locale.h>
\r
230 #include "common_irrlicht.h"
\r
233 #include "player.h"
\r
236 #include "environment.h"
\r
237 #include "server.h"
\r
238 #include "client.h"
\r
239 #include "serialization.h"
\r
240 #include "constants.h"
\r
241 #include "strfnd.h"
\r
242 #include "porting.h"
\r
243 #include "irrlichtwrapper.h"
\r
244 #include "gettime.h"
\r
245 #include "porting.h"
\r
246 #include "guiPauseMenu.h"
\r
247 #include "guiInventoryMenu.h"
\r
248 #include "guiTextInputMenu.h"
\r
249 #include "materials.h"
\r
250 #include "guiMessageMenu.h"
\r
252 IrrlichtWrapper *g_irrlicht;
\r
254 // All range-related stuff below is locked behind this
\r
255 JMutex g_range_mutex;
\r
257 // Blocks are viewed in this range from the player
\r
258 s16 g_viewing_range_nodes = 60;
\r
259 //s16 g_viewing_range_nodes = 0;
\r
261 // This is updated by the client's fetchBlocks routine
\r
262 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
264 // If true, the preceding value has no meaning and all blocks
\r
265 // already existing in memory are drawn
\r
266 bool g_viewing_range_all = false;
\r
268 // This is the freetime ratio imposed by the dynamic viewing
\r
269 // range changing code.
\r
270 // It is controlled by the main loop to the smallest value that
\r
271 // inhibits glitches (dtime jitter) in the main loop.
\r
272 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
276 These are loaded from the config file.
\r
279 Settings g_settings;
\r
281 extern void set_default_settings();
\r
287 IrrlichtDevice *g_device = NULL;
\r
288 Client *g_client = NULL;
\r
293 gui::IGUIEnvironment* guienv = NULL;
\r
294 gui::IGUIStaticText *guiroot = NULL;
\r
295 int g_active_menu_count = 0;
\r
297 bool noMenuActive()
\r
299 return (g_active_menu_count == 0);
\r
302 // Inventory actions from the menu are buffered here before sending
\r
303 Queue<InventoryAction*> inventory_action_queue;
\r
304 // This is a copy of the inventory that the client's environment has
\r
305 Inventory local_inventory;
\r
307 u16 g_selected_item = 0;
\r
314 std::ostream *dout_con_ptr = &dummyout;
\r
315 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
316 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
317 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
318 //std::ostream *dout_con_ptr = &dstream;
\r
319 //std::ostream *derr_con_ptr = &dstream;
\r
322 std::ostream *dout_server_ptr = &dstream;
\r
323 std::ostream *derr_server_ptr = &dstream;
\r
326 std::ostream *dout_client_ptr = &dstream;
\r
327 std::ostream *derr_client_ptr = &dstream;
\r
330 gettime.h implementation
\r
336 Use irrlicht because it is more precise than porting.h's
\r
339 if(g_irrlicht == NULL)
\r
341 return g_irrlicht->getTime();
\r
348 struct TextDestSign : public TextDest
\r
350 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
352 m_blockpos = blockpos;
\r
356 void gotText(std::wstring text)
\r
358 std::string ntext = wide_to_narrow(text);
\r
359 dstream<<"Changing text of a sign object: "
\r
360 <<ntext<<std::endl;
\r
361 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
369 struct TextDestChat : public TextDest
\r
371 TextDestChat(Client *client)
\r
375 void gotText(std::wstring text)
\r
377 m_client->sendChatMessage(text);
\r
378 m_client->addChatMessage(text);
\r
384 class MyEventReceiver : public IEventReceiver
\r
387 // This is the one method that we have to implement
\r
388 virtual bool OnEvent(const SEvent& event)
\r
391 React to nothing here if a menu is active
\r
393 if(noMenuActive() == false)
\r
399 // Remember whether each key is down or up
\r
400 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
402 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
404 if(event.KeyInput.PressedDown)
\r
406 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
412 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
414 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
416 dstream<<DTIME<<"MyEventReceiver: "
\r
417 <<"Launching pause menu"<<std::endl;
\r
418 // It will delete itself by itself
\r
419 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
420 &g_active_menu_count))->drop();
\r
423 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
425 dstream<<DTIME<<"MyEventReceiver: "
\r
426 <<"Launching inventory"<<std::endl;
\r
427 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
428 &local_inventory, &inventory_action_queue,
\r
429 &g_active_menu_count))->drop();
\r
432 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
434 TextDest *dest = new TextDestChat(g_client);
\r
436 (new GUITextInputMenu(guienv, guiroot, -1,
\r
437 &g_active_menu_count, dest,
\r
442 // Material selection
\r
443 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
445 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
448 g_selected_item = 0;
\r
449 dstream<<DTIME<<"Selected item: "
\r
450 <<g_selected_item<<std::endl;
\r
453 // Viewing range selection
\r
454 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
456 JMutexAutoLock lock(g_range_mutex);
\r
457 if(g_viewing_range_all)
\r
459 g_viewing_range_all = false;
\r
460 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
464 g_viewing_range_all = true;
\r
465 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
469 // Print debug stacks
\r
470 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
472 dstream<<"-----------------------------------------"
\r
474 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
475 dstream<<"-----------------------------------------"
\r
477 debug_stacks_print();
\r
482 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
484 if(noMenuActive() == false)
\r
486 left_active = false;
\r
487 middle_active = false;
\r
488 right_active = false;
\r
492 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
493 left_active = event.MouseInput.isLeftPressed();
\r
494 middle_active = event.MouseInput.isMiddlePressed();
\r
495 right_active = event.MouseInput.isRightPressed();
\r
497 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
499 leftclicked = true;
\r
501 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
503 rightclicked = true;
\r
505 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
507 leftreleased = true;
\r
509 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
511 rightreleased = true;
\r
513 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
515 /*dstream<<"event.MouseInput.Wheel="
\r
516 <<event.MouseInput.Wheel<<std::endl;*/
\r
517 if(event.MouseInput.Wheel < 0)
\r
519 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
522 g_selected_item = 0;
\r
524 else if(event.MouseInput.Wheel > 0)
\r
526 if(g_selected_item > 0)
\r
529 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
538 // This is used to check whether a key is being held down
\r
539 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
541 return keyIsDown[keyCode];
\r
546 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
547 keyIsDown[i] = false;
\r
549 leftclicked = false;
\r
550 rightclicked = false;
\r
551 leftreleased = false;
\r
552 rightreleased = false;
\r
554 left_active = false;
\r
555 middle_active = false;
\r
556 right_active = false;
\r
567 bool rightreleased;
\r
570 bool middle_active;
\r
574 // We use this array to store the current state of each key
\r
575 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
578 IrrlichtDevice *m_device;
\r
587 virtual ~InputHandler()
\r
591 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
593 virtual v2s32 getMousePos() = 0;
\r
594 virtual void setMousePos(s32 x, s32 y) = 0;
\r
596 virtual bool getLeftState() = 0;
\r
597 virtual bool getRightState() = 0;
\r
599 virtual bool getLeftClicked() = 0;
\r
600 virtual bool getRightClicked() = 0;
\r
601 virtual void resetLeftClicked() = 0;
\r
602 virtual void resetRightClicked() = 0;
\r
604 virtual bool getLeftReleased() = 0;
\r
605 virtual bool getRightReleased() = 0;
\r
606 virtual void resetLeftReleased() = 0;
\r
607 virtual void resetRightReleased() = 0;
\r
609 virtual void step(float dtime) {};
\r
611 virtual void clear() {};
\r
614 InputHandler *g_input = NULL;
\r
616 class RealInputHandler : public InputHandler
\r
619 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
621 m_receiver(receiver)
\r
624 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
626 return m_receiver->IsKeyDown(keyCode);
\r
628 virtual v2s32 getMousePos()
\r
630 return m_device->getCursorControl()->getPosition();
\r
632 virtual void setMousePos(s32 x, s32 y)
\r
634 m_device->getCursorControl()->setPosition(x, y);
\r
637 virtual bool getLeftState()
\r
639 return m_receiver->left_active;
\r
641 virtual bool getRightState()
\r
643 return m_receiver->right_active;
\r
646 virtual bool getLeftClicked()
\r
648 return m_receiver->leftclicked;
\r
650 virtual bool getRightClicked()
\r
652 return m_receiver->rightclicked;
\r
654 virtual void resetLeftClicked()
\r
656 m_receiver->leftclicked = false;
\r
658 virtual void resetRightClicked()
\r
660 m_receiver->rightclicked = false;
\r
663 virtual bool getLeftReleased()
\r
665 return m_receiver->leftreleased;
\r
667 virtual bool getRightReleased()
\r
669 return m_receiver->rightreleased;
\r
671 virtual void resetLeftReleased()
\r
673 m_receiver->leftreleased = false;
\r
675 virtual void resetRightReleased()
\r
677 m_receiver->rightreleased = false;
\r
682 resetRightClicked();
\r
683 resetLeftClicked();
\r
686 IrrlichtDevice *m_device;
\r
687 MyEventReceiver *m_receiver;
\r
690 class RandomInputHandler : public InputHandler
\r
693 RandomInputHandler()
\r
695 leftclicked = false;
\r
696 rightclicked = false;
\r
697 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
698 keydown[i] = false;
\r
700 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
702 return keydown[keyCode];
\r
704 virtual v2s32 getMousePos()
\r
708 virtual void setMousePos(s32 x, s32 y)
\r
710 mousepos = v2s32(x,y);
\r
713 virtual bool getLeftState()
\r
717 virtual bool getRightState()
\r
722 virtual bool getLeftClicked()
\r
724 return leftclicked;
\r
726 virtual bool getRightClicked()
\r
728 return rightclicked;
\r
730 virtual void resetLeftClicked()
\r
732 leftclicked = false;
\r
734 virtual void resetRightClicked()
\r
736 rightclicked = false;
\r
739 virtual bool getLeftReleased()
\r
743 virtual bool getRightReleased()
\r
747 virtual void resetLeftReleased()
\r
750 virtual void resetRightReleased()
\r
754 virtual void step(float dtime)
\r
757 static float counter1 = 0;
\r
761 counter1 = 0.1*Rand(1,10);
\r
762 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
763 g_selected_material++;
\r
765 g_selected_material = 0;*/
\r
766 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
769 g_selected_item = 0;
\r
773 static float counter1 = 0;
\r
777 counter1 = 0.1*Rand(1, 40);
\r
778 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
782 static float counter1 = 0;
\r
786 counter1 = 0.1*Rand(1, 40);
\r
787 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
791 static float counter1 = 0;
\r
795 counter1 = 0.1*Rand(1, 40);
\r
796 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
800 static float counter1 = 0;
\r
804 counter1 = 0.1*Rand(1, 40);
\r
805 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
809 static float counter1 = 0;
\r
813 counter1 = 0.1*Rand(1, 20);
\r
814 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
818 static float counter1 = 0;
\r
822 counter1 = 0.1*Rand(1, 30);
\r
823 leftclicked = true;
\r
827 static float counter1 = 0;
\r
831 counter1 = 0.1*Rand(1, 20);
\r
832 rightclicked = true;
\r
835 mousepos += mousespeed;
\r
838 s32 Rand(s32 min, s32 max)
\r
840 return (rand()%(max-min+1))+min;
\r
843 bool keydown[KEY_KEY_CODES_COUNT];
\r
850 void updateViewingRange(f32 frametime, Client *client)
\r
852 // Range_all messes up frametime_avg
\r
853 if(g_viewing_range_all == true)
\r
856 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
858 // Initialize to the target value
\r
859 static float frametime_avg = 1.0/wanted_fps;
\r
860 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
861 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
863 static f32 counter = 0;
\r
865 counter -= frametime;
\r
868 //counter = 1.0; //seconds
\r
869 counter = 0.5; //seconds
\r
871 //float freetime_ratio = 0.2;
\r
872 //float freetime_ratio = 0.4;
\r
873 float freetime_ratio = FREETIME_RATIO;
\r
875 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
877 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
879 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
880 + frametime_avg / frametime_wanted / 2.0;*/
\r
882 //float fraction = frametime_avg / frametime_wanted;
\r
884 static bool fraction_is_good = false;
\r
886 //float fraction_good_threshold = 0.1;
\r
887 //float fraction_bad_threshold = 0.25;
\r
888 float fraction_good_threshold = 0.075;
\r
889 float fraction_bad_threshold = 0.125;
\r
890 float fraction_limit;
\r
891 // Use high limit if fraction is good AND the fraction would
\r
892 // lower the range. We want to keep the range fairly high.
\r
893 if(fraction_is_good && fraction > 1.0)
\r
894 fraction_limit = fraction_bad_threshold;
\r
896 fraction_limit = fraction_good_threshold;
\r
898 if(fabs(fraction - 1.0) < fraction_limit)
\r
900 fraction_is_good = true;
\r
905 fraction_is_good = false;
\r
908 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
909 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
910 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
911 <<" faction = "<<fraction<<std::endl;*/
\r
913 JMutexAutoLock lock(g_range_mutex);
\r
915 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
916 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
918 s16 n = (float)g_viewing_range_nodes / fraction;
\r
919 if(n < viewing_range_nodes_min)
\r
920 n = viewing_range_nodes_min;
\r
921 if(n > viewing_range_nodes_max)
\r
922 n = viewing_range_nodes_max;
\r
924 bool can_change = true;
\r
926 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
927 can_change = false;
\r
930 g_viewing_range_nodes = n;
\r
932 /*dstream<<"g_viewing_range_nodes = "
\r
933 <<g_viewing_range_nodes<<std::endl;*/
\r
936 class GUIQuickInventory : public IEventReceiver
\r
940 gui::IGUIEnvironment* env,
\r
941 gui::IGUIElement* parent,
\r
944 Inventory *inventory):
\r
945 m_itemcount(itemcount),
\r
946 m_inventory(inventory)
\r
948 core::rect<s32> imgsize(0,0,48,48);
\r
949 core::rect<s32> textsize(0,0,48,16);
\r
950 v2s32 spacing(0, 64);
\r
951 for(s32 i=0; i<m_itemcount; i++)
\r
953 m_images.push_back(env->addImage(
\r
954 imgsize + pos + spacing*i
\r
956 m_images[i]->setScaleImage(true);
\r
957 m_texts.push_back(env->addStaticText(
\r
959 textsize + pos + spacing*i,
\r
962 m_texts[i]->setBackgroundColor(
\r
963 video::SColor(128,0,0,0));
\r
964 m_texts[i]->setTextAlignment(
\r
966 gui::EGUIA_UPPERLEFT);
\r
970 virtual bool OnEvent(const SEvent& event)
\r
975 void setSelection(s32 i)
\r
984 start = m_selection - m_itemcount / 2;
\r
986 InventoryList *mainlist = m_inventory->getList("main");
\r
988 for(s32 i=0; i<m_itemcount; i++)
\r
992 if(j > (s32)mainlist->getSize() - 1)
\r
993 j -= mainlist->getSize();
\r
995 j += mainlist->getSize();
\r
997 InventoryItem *item = mainlist->getItem(j);
\r
1001 m_images[i]->setImage(NULL);
\r
1004 if(m_selection == j)
\r
1005 swprintf(t, 10, L"<-");
\r
1007 swprintf(t, 10, L"");
\r
1008 m_texts[i]->setText(t);
\r
1010 // The next ifs will segfault with a NULL pointer
\r
1015 m_images[i]->setImage(item->getImage());
\r
1018 if(m_selection == j)
\r
1019 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1021 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1022 m_texts[i]->setText(t);
\r
1028 core::array<gui::IGUIStaticText*> m_texts;
\r
1029 core::array<gui::IGUIImage*> m_images;
\r
1030 Inventory *m_inventory;
\r
1041 ChatLine(const std::wstring &a_text):
\r
1047 std::wstring text;
\r
1050 int main(int argc, char *argv[])
\r
1053 Low-level initialization
\r
1056 bool disable_stderr = false;
\r
1058 disable_stderr = true;
\r
1061 // Initialize debug streams
\r
1062 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1063 // Initialize debug stacks
\r
1064 debug_stacks_init();
\r
1066 DSTACK(__FUNCTION_NAME);
\r
1068 initializeMaterialProperties();
\r
1074 Parse command line
\r
1077 // List all allowed options
\r
1078 core::map<std::string, ValueSpec> allowed_options;
\r
1079 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1080 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1081 "Run server directly"));
\r
1082 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1083 "Load configuration from specified file"));
\r
1084 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1085 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1086 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1087 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1088 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1090 Settings cmd_args;
\r
1092 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1094 if(ret == false || cmd_args.getFlag("help"))
\r
1096 dstream<<"Allowed options:"<<std::endl;
\r
1097 for(core::map<std::string, ValueSpec>::Iterator
\r
1098 i = allowed_options.getIterator();
\r
1099 i.atEnd() == false; i++)
\r
1101 dstream<<" --"<<i.getNode()->getKey();
\r
1102 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1107 dstream<<" <value>";
\r
1109 dstream<<std::endl;
\r
1111 if(i.getNode()->getValue().help != NULL)
\r
1113 dstream<<" "<<i.getNode()->getValue().help
\r
1118 return cmd_args.getFlag("help") ? 0 : 1;
\r
1123 Basic initialization
\r
1126 // Initialize default settings
\r
1127 set_default_settings();
\r
1129 // Print startup message
\r
1130 dstream<<DTIME<<"minetest-c55"
\r
1131 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1132 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1135 // Set locale. This is for forcing '.' as the decimal point.
\r
1136 std::locale::global(std::locale("C"));
\r
1137 // This enables printing all characters in bitmap font
\r
1138 setlocale(LC_CTYPE, "en_US");
\r
1140 // Initialize sockets
\r
1142 atexit(sockets_cleanup);
\r
1152 // Path of configuration file in use
\r
1153 std::string configpath = "";
\r
1155 if(cmd_args.exists("config"))
\r
1157 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1160 dstream<<"Could not read configuration from \""
\r
1161 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1164 configpath = cmd_args.get("config");
\r
1168 const char *filenames[2] =
\r
1170 "../minetest.conf",
\r
1171 "../../minetest.conf"
\r
1174 for(u32 i=0; i<2; i++)
\r
1176 bool r = g_settings.readConfigFile(filenames[i]);
\r
1179 configpath = filenames[i];
\r
1185 // Initialize random seed
\r
1191 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1192 || cmd_args.getFlag("enable-unittests") == true)
\r
1198 Global range mutex
\r
1200 g_range_mutex.Init();
\r
1201 assert(g_range_mutex.IsInitialized());
\r
1203 // Read map parameters from settings
\r
1205 HMParams hm_params;
\r
1206 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1207 hm_params.randmax = g_settings.get("height_randmax");
\r
1208 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1209 hm_params.base = g_settings.get("height_base");
\r
1211 MapParams map_params;
\r
1212 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1213 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1219 std::cout<<std::endl<<std::endl;
\r
1222 <<" .__ __ __ "<<std::endl
\r
1223 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1224 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1225 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1226 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1227 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1230 std::cout<<std::endl;
\r
1231 //char templine[100];
\r
1235 if(cmd_args.exists("port"))
\r
1237 port = cmd_args.getU16("port");
\r
1241 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1242 std::cout<<"-> "<<port<<std::endl;
\r
1245 if(cmd_args.getFlag("server"))
\r
1247 DSTACK("Dedicated server branch");
\r
1249 std::cout<<std::endl;
\r
1250 std::cout<<"========================"<<std::endl;
\r
1251 std::cout<<"Running dedicated server"<<std::endl;
\r
1252 std::cout<<"========================"<<std::endl;
\r
1253 std::cout<<std::endl;
\r
1255 Server server("../map", hm_params, map_params);
\r
1256 server.start(port);
\r
1260 // This is kind of a hack but can be done like this
\r
1261 // because server.step() is very light
\r
1263 server.step(0.030);
\r
1265 static int counter = 0;
\r
1271 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1272 core::list<PlayerInfo>::Iterator i;
\r
1273 static u32 sum_old = 0;
\r
1274 u32 sum = PIChecksum(list);
\r
1275 if(sum != sum_old)
\r
1277 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1278 for(i=list.begin(); i!=list.end(); i++)
\r
1280 i->PrintLine(&std::cout);
\r
1290 bool hosting = false;
\r
1291 char connect_name[100] = "";
\r
1293 if(cmd_args.exists("address"))
\r
1295 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1297 else if(is_yes(g_settings.get("host_game")) == false)
\r
1299 if(g_settings.get("address") != "")
\r
1301 std::cout<<g_settings.get("address")<<std::endl;
\r
1302 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1306 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1307 std::cin.getline(connect_name, 100);
\r
1311 if(connect_name[0] == 0){
\r
1312 snprintf(connect_name, 100, "127.0.0.1");
\r
1317 std::cout<<"> Hosting game"<<std::endl;
\r
1319 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1321 char playername[PLAYERNAME_SIZE] = "";
\r
1322 if(g_settings.get("name") != "")
\r
1324 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1328 std::cout<<"Name of player: ";
\r
1329 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1331 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1334 Resolution selection
\r
1337 bool fullscreen = false;
\r
1338 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1339 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1343 MyEventReceiver receiver;
\r
1345 video::E_DRIVER_TYPE driverType;
\r
1348 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1349 driverType = video::EDT_OPENGL;
\r
1351 driverType = video::EDT_OPENGL;
\r
1354 // create device and exit if creation failed
\r
1356 IrrlichtDevice *device;
\r
1357 device = createDevice(driverType,
\r
1358 core::dimension2d<u32>(screenW, screenH),
\r
1359 16, fullscreen, false, false, &receiver);
\r
1362 return 1; // could not create selected driver.
\r
1364 g_device = device;
\r
1365 g_irrlicht = new IrrlichtWrapper(device);
\r
1367 //g_device = device;
\r
1369 device->setResizable(true);
\r
1371 bool random_input = g_settings.getBool("random_input")
\r
1372 || cmd_args.getFlag("random-input");
\r
1374 g_input = new RandomInputHandler();
\r
1376 g_input = new RealInputHandler(device, &receiver);
\r
1379 Continue initialization
\r
1382 video::IVideoDriver* driver = device->getVideoDriver();
\r
1385 This changes the minimum allowed number of vertices in a VBO
\r
1387 //driver->setMinHardwareBufferVertexCount(1);
\r
1389 scene::ISceneManager* smgr = device->getSceneManager();
\r
1391 guienv = device->getGUIEnvironment();
\r
1392 gui::IGUISkin* skin = guienv->getSkin();
\r
1393 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1395 skin->setFont(font);
\r
1397 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1398 dstream<<"text_height="<<text_height<<std::endl;
\r
1400 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1401 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1402 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1403 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1404 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1405 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1407 const wchar_t *text = L"Loading and connecting...";
\r
1408 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1409 core::vector2d<s32> textsize(300, text_height);
\r
1410 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1412 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1413 text, textrect, false, false);
\r
1414 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1416 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1417 guienv->drawAll();
\r
1418 driver->endScene();
\r
1421 Preload some textures
\r
1424 tile_materials_preload(g_irrlicht);
\r
1427 Make a scope here for the client so that it gets removed
\r
1428 before the irrlicht device
\r
1432 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1437 SharedPtr<Server> server;
\r
1439 server = new Server("../map", hm_params, map_params);
\r
1440 server->start(port);
\r
1447 Client client(device, playername,
\r
1449 g_viewing_range_nodes,
\r
1450 g_viewing_range_all);
\r
1452 g_client = &client;
\r
1454 Address connect_address(0,0,0,0, port);
\r
1456 connect_address.Resolve(connect_name);
\r
1458 catch(ResolveError &e)
\r
1460 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1464 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1465 client.connect(connect_address);
\r
1468 while(client.connectedAndInitialized() == false)
\r
1471 if(server != NULL){
\r
1472 server->step(0.1);
\r
1477 catch(con::PeerNotFoundException &e)
\r
1479 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1486 /*scene::ISceneNode* skybox;
\r
1487 skybox = smgr->addSkyBoxSceneNode(
\r
1488 driver->getTexture("../data/skybox2.png"),
\r
1489 driver->getTexture("../data/skybox3.png"),
\r
1490 driver->getTexture("../data/skybox1.png"),
\r
1491 driver->getTexture("../data/skybox1.png"),
\r
1492 driver->getTexture("../data/skybox1.png"),
\r
1493 driver->getTexture("../data/skybox1.png"));*/
\r
1496 Create the camera node
\r
1499 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1500 0, // Camera parent
\r
1501 v3f(BS*100, BS*2, BS*100), // Look from
\r
1502 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1506 if(camera == NULL)
\r
1509 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1511 camera->setFOV(FOV_ANGLE);
\r
1513 // Just so big a value that everything rendered is visible
\r
1514 camera->setFarValue(100000*BS);
\r
1516 f32 camera_yaw = 0; // "right/left"
\r
1517 f32 camera_pitch = 0; // "up/down"
\r
1523 gui_loadingtext->remove();
\r
1526 Add some gui stuff
\r
1529 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1530 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1533 We need some kind of a root node to be able to add
\r
1534 custom elements directly on the screen.
\r
1535 Otherwise they won't be automatically drawn.
\r
1537 guiroot = guienv->addStaticText(L"",
\r
1538 core::rect<s32>(0, 0, 10000, 10000));
\r
1540 // Test the text input system
\r
1541 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1543 /*GUIMessageMenu *menu =
\r
1544 new GUIMessageMenu(guienv, guiroot, -1,
\r
1545 &g_active_menu_count,
\r
1549 // Launch pause menu
\r
1550 /*(new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
1551 &g_active_menu_count))->drop();*/
\r
1553 // First line of debug text
\r
1554 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1556 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1558 // Second line of debug text
\r
1559 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1561 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1564 // At the middle of the screen
\r
1565 // Object infos are shown in this
\r
1566 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1568 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1572 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1573 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1574 core::rect<s32>(70, 60, 795, 150),
\r
1576 chat_guitext->setBackgroundColor(video::SColor(96,0,0,0));
\r
1577 core::list<ChatLine> chat_lines;
\r
1580 Some statistics are collected in these
\r
1583 u32 beginscenetime = 0;
\r
1584 u32 scenetime = 0;
\r
1585 u32 endscenetime = 0;
\r
1588 //throw con::PeerNotFoundException("lol");
\r
1594 bool first_loop_after_window_activation = true;
\r
1596 // Time is in milliseconds
\r
1597 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1598 // NOTE: Have to call run() between calls of this to update the timer
\r
1599 u32 lasttime = device->getTimer()->getTime();
\r
1601 while(device->run())
\r
1604 Run global IrrlichtWrapper's main thread processing stuff
\r
1606 g_irrlicht->Run();
\r
1609 Random calculations
\r
1611 v2u32 screensize = driver->getScreenSize();
\r
1612 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1614 // Hilight boxes collected during the loop and displayed
\r
1615 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1618 std::wstring infotext;
\r
1620 //TimeTaker //timer1("//timer1");
\r
1622 // Time of frame without fps limit
\r
1626 // not using getRealTime is necessary for wine
\r
1627 u32 time = device->getTimer()->getTime();
\r
1628 if(time > lasttime)
\r
1629 busytime_u32 = time - lasttime;
\r
1632 busytime = busytime_u32 / 1000.0;
\r
1635 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1637 // Absolutelu necessary for wine!
\r
1644 updateViewingRange(busytime, &client);
\r
1651 float fps_max = g_settings.getFloat("fps_max");
\r
1652 u32 frametime_min = 1000./fps_max;
\r
1654 if(busytime_u32 < frametime_min)
\r
1656 u32 sleeptime = frametime_min - busytime_u32;
\r
1657 device->sleep(sleeptime);
\r
1661 // Absolutelu necessary for wine!
\r
1665 Time difference calculation
\r
1667 f32 dtime; // in seconds
\r
1669 u32 time = device->getTimer()->getTime();
\r
1670 if(time > lasttime)
\r
1671 dtime = (time - lasttime) / 1000.0;
\r
1677 Time average and jitter calculation
\r
1680 static f32 dtime_avg1 = 0.0;
\r
1681 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1682 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1684 static f32 dtime_jitter1_max_sample = 0.0;
\r
1685 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1687 static f32 jitter1_max = 0.0;
\r
1688 static f32 counter = 0.0;
\r
1689 if(dtime_jitter1 > jitter1_max)
\r
1690 jitter1_max = dtime_jitter1;
\r
1695 dtime_jitter1_max_sample = jitter1_max;
\r
1696 dtime_jitter1_max_fraction
\r
1697 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1698 jitter1_max = 0.0;
\r
1701 Control freetime ratio
\r
1703 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1705 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1706 g_freetime_ratio += 0.01;
\r
1710 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1711 g_freetime_ratio -= 0.01;
\r
1717 Busytime average and jitter calculation
\r
1720 static f32 busytime_avg1 = 0.0;
\r
1721 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1722 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1724 static f32 busytime_jitter1_max_sample = 0.0;
\r
1725 static f32 busytime_jitter1_min_sample = 0.0;
\r
1727 static f32 jitter1_max = 0.0;
\r
1728 static f32 jitter1_min = 0.0;
\r
1729 static f32 counter = 0.0;
\r
1730 if(busytime_jitter1 > jitter1_max)
\r
1731 jitter1_max = busytime_jitter1;
\r
1732 if(busytime_jitter1 < jitter1_min)
\r
1733 jitter1_min = busytime_jitter1;
\r
1735 if(counter > 0.0){
\r
1737 busytime_jitter1_max_sample = jitter1_max;
\r
1738 busytime_jitter1_min_sample = jitter1_min;
\r
1739 jitter1_max = 0.0;
\r
1740 jitter1_min = 0.0;
\r
1745 Debug info for client
\r
1748 static float counter = 0.0;
\r
1753 client.printDebugInfo(std::cout);
\r
1758 Input handler step()
\r
1760 g_input->step(dtime);
\r
1763 Player speed control
\r
1772 bool a_superspeed,
\r
1775 PlayerControl control(
\r
1776 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1777 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1778 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1779 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1780 g_input->isKeyDown(irr::KEY_SPACE),
\r
1781 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1785 client.setPlayerControl(control);
\r
1789 Process environment
\r
1793 //TimeTaker timer("client.step(dtime)");
\r
1794 client.step(dtime);
\r
1795 //client.step(dtime_avg1);
\r
1798 if(server != NULL)
\r
1800 //TimeTaker timer("server->step(dtime)");
\r
1801 server->step(dtime);
\r
1804 v3f player_position = client.getPlayerPosition();
\r
1806 //TimeTaker //timer2("//timer2");
\r
1809 Mouse and camera control
\r
1812 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1815 device->getCursorControl()->setVisible(false);
\r
1817 if(first_loop_after_window_activation){
\r
1818 //std::cout<<"window active, first loop"<<std::endl;
\r
1819 first_loop_after_window_activation = false;
\r
1822 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1823 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1824 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1825 camera_yaw -= dx*0.2;
\r
1826 camera_pitch += dy*0.2;
\r
1827 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1828 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1830 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1833 device->getCursorControl()->setVisible(true);
\r
1835 //std::cout<<"window inactive"<<std::endl;
\r
1836 first_loop_after_window_activation = true;
\r
1839 camera_yaw = wrapDegrees(camera_yaw);
\r
1840 camera_pitch = wrapDegrees(camera_pitch);
\r
1842 v3f camera_direction = v3f(0,0,1);
\r
1843 camera_direction.rotateYZBy(camera_pitch);
\r
1844 camera_direction.rotateXZBy(camera_yaw);
\r
1846 v3f camera_position =
\r
1847 player_position + v3f(0, BS+BS/2, 0);
\r
1849 camera->setPosition(camera_position);
\r
1850 // *100.0 helps in large map coordinates
\r
1851 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1853 if(FIELD_OF_VIEW_TEST){
\r
1854 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1855 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1858 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1859 //TimeTaker timer("client.updateCamera");
\r
1860 client.updateCamera(camera_position, camera_direction);
\r
1864 //TimeTaker //timer3("//timer3");
\r
1867 Calculate what block is the crosshair pointing to
\r
1870 //u32 t1 = device->getTimer()->getRealTime();
\r
1872 //f32 d = 4; // max. distance
\r
1873 f32 d = 4; // max. distance
\r
1874 core::line3d<f32> shootline(camera_position,
\r
1875 camera_position + camera_direction * BS * (d+1));
\r
1877 MapBlockObject *selected_object = client.getSelectedObject
\r
1878 (d*BS, camera_position, shootline);
\r
1881 If it's pointing to a MapBlockObject
\r
1884 if(selected_object != NULL)
\r
1886 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1888 core::aabbox3d<f32> box_on_map
\r
1889 = selected_object->getSelectionBoxOnMap();
\r
1891 hilightboxes.push_back(box_on_map);
\r
1893 infotext = narrow_to_wide(selected_object->infoText());
\r
1895 if(g_input->getLeftClicked())
\r
1897 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1898 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1899 selected_object->getId(), g_selected_item);
\r
1901 else if(g_input->getRightClicked())
\r
1903 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1905 Check if we want to modify the object ourselves
\r
1907 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1909 dstream<<"Sign object right-clicked"<<std::endl;
\r
1911 if(random_input == false)
\r
1913 // Get a new text for it
\r
1915 TextDest *dest = new TextDestSign(
\r
1916 selected_object->getBlock()->getPos(),
\r
1917 selected_object->getId(),
\r
1920 SignObject *sign_object = (SignObject*)selected_object;
\r
1922 std::wstring wtext =
\r
1923 narrow_to_wide(sign_object->getText());
\r
1925 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1926 &g_active_menu_count, dest,
\r
1931 Otherwise pass the event to the server as-is
\r
1935 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1936 selected_object->getId(), g_selected_item);
\r
1940 else // selected_object == NULL
\r
1944 Find out which node we are pointing at
\r
1947 bool nodefound = false;
\r
1949 v3s16 neighbourpos;
\r
1950 core::aabbox3d<f32> nodefacebox;
\r
1951 f32 mindistance = BS * 1001;
\r
1953 v3s16 pos_i = floatToInt(player_position);
\r
1955 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1959 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1960 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1961 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1962 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1963 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1964 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1966 for(s16 y = ystart; y <= yend; y++)
\r
1967 for(s16 z = zstart; z <= zend; z++)
\r
1968 for(s16 x = xstart; x <= xend; x++)
\r
1973 n = client.getNode(v3s16(x,y,z));
\r
1974 if(content_pointable(n.d) == false)
\r
1977 catch(InvalidPositionException &e)
\r
1983 v3f npf = intToFloat(np);
\r
1988 v3s16(0,0,1), // back
\r
1989 v3s16(0,1,0), // top
\r
1990 v3s16(1,0,0), // right
\r
1991 v3s16(0,0,-1), // front
\r
1992 v3s16(0,-1,0), // bottom
\r
1993 v3s16(-1,0,0), // left
\r
1999 if(n.d == CONTENT_TORCH)
\r
2001 v3s16 dir = unpackDir(n.dir);
\r
2002 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2003 dir_f *= BS/2 - BS/6 - BS/20;
\r
2004 v3f cpf = npf + dir_f;
\r
2005 f32 distance = (cpf - camera_position).getLength();
\r
2007 core::aabbox3d<f32> box;
\r
2010 if(dir == v3s16(0,-1,0))
\r
2012 box = core::aabbox3d<f32>(
\r
2013 npf - v3f(BS/6, BS/2, BS/6),
\r
2014 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2018 else if(dir == v3s16(0,1,0))
\r
2020 box = core::aabbox3d<f32>(
\r
2021 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2022 npf + v3f(BS/6, BS/2, BS/6)
\r
2028 box = core::aabbox3d<f32>(
\r
2029 cpf - v3f(BS/6, BS/3, BS/6),
\r
2030 cpf + v3f(BS/6, BS/3, BS/6)
\r
2034 if(distance < mindistance)
\r
2036 if(box.intersectsWithLine(shootline))
\r
2040 neighbourpos = np;
\r
2041 mindistance = distance;
\r
2042 nodefacebox = box;
\r
2051 for(u16 i=0; i<6; i++)
\r
2053 v3f dir_f = v3f(dirs[i].X,
\r
2054 dirs[i].Y, dirs[i].Z);
\r
2055 v3f centerpoint = npf + dir_f * BS/2;
\r
2057 (centerpoint - camera_position).getLength();
\r
2059 if(distance < mindistance)
\r
2061 core::CMatrix4<f32> m;
\r
2062 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2064 // This is the back face
\r
2065 v3f corners[2] = {
\r
2066 v3f(BS/2, BS/2, BS/2),
\r
2067 v3f(-BS/2, -BS/2, BS/2+d)
\r
2070 for(u16 j=0; j<2; j++)
\r
2072 m.rotateVect(corners[j]);
\r
2073 corners[j] += npf;
\r
2076 core::aabbox3d<f32> facebox(corners[0]);
\r
2077 facebox.addInternalPoint(corners[1]);
\r
2079 if(facebox.intersectsWithLine(shootline))
\r
2083 neighbourpos = np + dirs[i];
\r
2084 mindistance = distance;
\r
2085 nodefacebox = facebox;
\r
2087 } // if distance < mindistance
\r
2089 } // regular block
\r
2092 static float nodig_delay_counter = 0.0;
\r
2096 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2098 static float dig_time = 0.0;
\r
2099 static u16 dig_index = 0;
\r
2101 hilightboxes.push_back(nodefacebox);
\r
2103 if(g_input->getLeftReleased())
\r
2105 client.clearTempMod(nodepos);
\r
2109 if(nodig_delay_counter > 0.0)
\r
2111 nodig_delay_counter -= dtime;
\r
2115 if(nodepos != nodepos_old)
\r
2117 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2118 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2120 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2122 client.clearTempMod(nodepos_old);
\r
2127 if(g_input->getLeftClicked() ||
\r
2128 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2130 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2131 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2133 if(g_input->getLeftClicked())
\r
2135 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2137 if(g_input->getLeftState())
\r
2139 MapNode n = client.getNode(nodepos);
\r
2141 // Get tool name. Default is "" = bare hands
\r
2142 std::string toolname = "";
\r
2143 InventoryList *mlist = local_inventory.getList("main");
\r
2146 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2147 if(item && (std::string)item->getName() == "ToolItem")
\r
2149 ToolItem *titem = (ToolItem*)item;
\r
2150 toolname = titem->getToolName();
\r
2154 // Get digging properties for material and tool
\r
2155 u8 material = n.d;
\r
2156 DiggingProperties prop =
\r
2157 getDiggingProperties(material, toolname);
\r
2159 float dig_time_complete = 0.0;
\r
2161 if(prop.diggable == false)
\r
2163 /*dstream<<"Material "<<(int)material
\r
2164 <<" not diggable with \""
\r
2165 <<toolname<<"\""<<std::endl;*/
\r
2166 // I guess nobody will wait for this long
\r
2167 dig_time_complete = 10000000.0;
\r
2171 dig_time_complete = prop.time;
\r
2174 if(dig_time_complete >= 0.001)
\r
2176 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2177 * dig_time/dig_time_complete);
\r
2179 // This is for torches
\r
2182 dig_index = CRACK_ANIMATION_LENGTH;
\r
2185 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2187 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2188 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2192 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2193 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2194 client.clearTempMod(nodepos);
\r
2195 client.removeNode(nodepos);
\r
2199 nodig_delay_counter = dig_time_complete
\r
2200 / (float)CRACK_ANIMATION_LENGTH;
\r
2202 // We don't want a corresponding delay to
\r
2203 // very time consuming nodes
\r
2204 if(nodig_delay_counter > 0.5)
\r
2206 nodig_delay_counter = 0.5;
\r
2208 // We want a slight delay to very little
\r
2209 // time consuming nodes
\r
2210 //float mindelay = 0.15;
\r
2211 float mindelay = 0.20;
\r
2212 if(nodig_delay_counter < mindelay)
\r
2214 nodig_delay_counter = mindelay;
\r
2218 dig_time += dtime;
\r
2222 if(g_input->getRightClicked())
\r
2224 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2225 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2228 nodepos_old = nodepos;
\r
2233 } // selected_object == NULL
\r
2235 g_input->resetLeftClicked();
\r
2236 g_input->resetRightClicked();
\r
2238 if(g_input->getLeftReleased())
\r
2240 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2242 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2244 if(g_input->getRightReleased())
\r
2246 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2250 g_input->resetLeftReleased();
\r
2251 g_input->resetRightReleased();
\r
2254 Calculate stuff for drawing
\r
2257 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2259 u32 daynight_ratio = client.getDayNightRatio();
\r
2260 video::SColor bgcolor = video::SColor(
\r
2262 skycolor.getRed() * daynight_ratio / 1000,
\r
2263 skycolor.getGreen() * daynight_ratio / 1000,
\r
2264 skycolor.getBlue() * daynight_ratio / 1000);
\r
2270 if(g_settings.getBool("enable_fog") == true)
\r
2272 f32 range = g_viewing_range_nodes * BS;
\r
2273 if(g_viewing_range_all)
\r
2274 range = 100000*BS;
\r
2278 video::EFT_FOG_LINEAR,
\r
2282 false, // pixel fog
\r
2283 false // range fog
\r
2289 Update gui stuff (0ms)
\r
2292 //TimeTaker guiupdatetimer("Gui updating");
\r
2295 wchar_t temptext[150];
\r
2297 static float drawtime_avg = 0;
\r
2298 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2299 static float beginscenetime_avg = 0;
\r
2300 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2301 static float scenetime_avg = 0;
\r
2302 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2303 static float endscenetime_avg = 0;
\r
2304 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2306 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2308 L", R: range_all=%i"
\r
2310 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2312 g_viewing_range_all,
\r
2314 beginscenetime_avg,
\r
2319 guitext->setText(temptext);
\r
2323 wchar_t temptext[150];
\r
2324 swprintf(temptext, 150,
\r
2325 L"(% .1f, % .1f, % .1f)"
\r
2326 L" (% .3f < btime_jitter < % .3f"
\r
2327 L", dtime_jitter = % .1f %%)",
\r
2328 player_position.X/BS,
\r
2329 player_position.Y/BS,
\r
2330 player_position.Z/BS,
\r
2331 busytime_jitter1_min_sample,
\r
2332 busytime_jitter1_max_sample,
\r
2333 dtime_jitter1_max_fraction * 100.0
\r
2336 guitext2->setText(temptext);
\r
2340 guitext_info->setText(infotext.c_str());
\r
2344 Get chat messages from client
\r
2347 // Get new messages
\r
2348 std::wstring message;
\r
2349 while(client.getChatMessage(message))
\r
2351 chat_lines.push_back(ChatLine(message));
\r
2352 if(chat_lines.size() > 5)
\r
2354 core::list<ChatLine>::Iterator
\r
2355 i = chat_lines.begin();
\r
2356 chat_lines.erase(i);
\r
2359 // Append them to form the whole static text and throw
\r
2360 // it to the gui element
\r
2361 std::wstring whole;
\r
2362 u16 to_be_removed_count = 0;
\r
2363 for(core::list<ChatLine>::Iterator
\r
2364 i = chat_lines.begin();
\r
2365 i != chat_lines.end(); i++)
\r
2367 (*i).age += dtime;
\r
2368 if((*i).age > 300.0)
\r
2370 to_be_removed_count++;
\r
2373 whole += (*i).text + L'\n';
\r
2375 for(u16 i=0; i<to_be_removed_count; i++)
\r
2377 core::list<ChatLine>::Iterator
\r
2378 it = chat_lines.begin();
\r
2379 chat_lines.erase(it);
\r
2381 chat_guitext->setText(whole.c_str());
\r
2382 // Update gui element size and position
\r
2383 core::rect<s32> rect(
\r
2385 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2386 screensize.X - 10,
\r
2389 chat_guitext->setRelativePosition(rect);
\r
2391 if(chat_lines.size() == 0)
\r
2392 chat_guitext->setVisible(false);
\r
2394 chat_guitext->setVisible(true);
\r
2401 static u16 old_selected_item = 65535;
\r
2402 if(client.getLocalInventoryUpdated()
\r
2403 || g_selected_item != old_selected_item)
\r
2405 old_selected_item = g_selected_item;
\r
2406 //std::cout<<"Updating local inventory"<<std::endl;
\r
2407 client.getLocalInventory(local_inventory);
\r
2408 quick_inventory->setSelection(g_selected_item);
\r
2409 quick_inventory->update();
\r
2413 Send actions returned by the inventory menu
\r
2415 while(inventory_action_queue.size() != 0)
\r
2417 InventoryAction *a = inventory_action_queue.pop_front();
\r
2419 client.sendInventoryAction(a);
\r
2428 TimeTaker drawtimer("Drawing");
\r
2432 TimeTaker timer("beginScene");
\r
2433 driver->beginScene(true, true, bgcolor);
\r
2434 //driver->beginScene(false, true, bgcolor);
\r
2435 beginscenetime = timer.stop(true);
\r
2440 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2443 TimeTaker timer("smgr");
\r
2445 scenetime = timer.stop(true);
\r
2449 //TimeTaker timer9("auxiliary drawings");
\r
2453 //TimeTaker //timer10("//timer10");
\r
2455 video::SMaterial m;
\r
2457 m.Lighting = false;
\r
2458 driver->setMaterial(m);
\r
2460 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2462 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2463 i != hilightboxes.end(); i++)
\r
2465 /*std::cout<<"hilightbox min="
\r
2466 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2468 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2470 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2476 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2477 displaycenter + core::vector2d<s32>(10,0),
\r
2478 video::SColor(255,255,255,255));
\r
2479 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2480 displaycenter + core::vector2d<s32>(0,10),
\r
2481 video::SColor(255,255,255,255));
\r
2486 //TimeTaker //timer11("//timer11");
\r
2492 guienv->drawAll();
\r
2496 TimeTaker timer("endScene");
\r
2497 driver->endScene();
\r
2498 endscenetime = timer.stop(true);
\r
2501 drawtime = drawtimer.stop(true);
\r
2507 static s16 lastFPS = 0;
\r
2508 //u16 fps = driver->getFPS();
\r
2509 u16 fps = (1.0/dtime_avg1);
\r
2511 if (lastFPS != fps)
\r
2513 core::stringw str = L"Minetest [";
\r
2514 str += driver->getName();
\r
2518 device->setWindowCaption(str.c_str());
\r
2524 device->yield();*/
\r
2527 delete quick_inventory;
\r
2529 } // client is deleted at this point
\r
2534 In the end, delete the Irrlicht device.
\r
2539 Update configuration file
\r
2541 /*if(configpath != "")
\r
2543 g_settings.updateConfigFile(configpath.c_str());
\r
2547 catch(con::PeerNotFoundException &e)
\r
2549 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2553 GUIMessageMenu *menu =
\r
2554 new GUIMessageMenu(guienv, guiroot, -1,
\r
2555 &g_active_menu_count,
\r
2556 L"Connection timed out");
\r
2558 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
2560 dstream<<"Created menu"<<std::endl;
\r
2562 while(g_device->run() && menu->getStatus() == false)
\r
2564 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
2565 guienv->drawAll();
\r
2566 driver->endScene();
\r
2569 dstream<<"Dropping menu"<<std::endl;
\r
2574 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2576 This is what has to be done in every thread to get suitable debug info
\r
2578 catch(std::exception &e)
\r
2580 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2581 <<e.what()<<std::endl;
\r
2586 debugstreams_deinit();
\r