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
95 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
97 SUGG: A version number to blocks, which increments when the block is
\r
98 modified (node add/remove, water update, lighting update)
\r
99 - This can then be used to make sure the most recent version of
\r
100 a block has been sent to client
\r
102 TODO: Stop player if focus of window is taken away (go to pause mode)
\r
104 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
106 - That is >500 vertices
\r
108 TODO: Better dungeons
\r
115 - One single map container with ids as keys
\r
118 TODO: - Keep track of the place of the mob in the last few hundreth's
\r
119 of a second - then, if a player hits it, take the value that is
\r
120 avg_rtt/2 before the moment the packet is received.
\r
123 TODO: Moving players more smoothly. Calculate moving animation
\r
124 in a way that doesn't make the player jump to the right place
\r
125 immediately when the server sends a new position
\r
127 TODO: There are some lighting-related todos and fixmes in
\r
128 ServerMap::emergeBlock
\r
130 TODO: Proper handling of spawning place (try to find something that
\r
131 is not in the middle of an ocean (some land to stand on at
\r
132 least) and save it in map config.
\r
134 TODO: Make the amount of blocks sending to client and the total
\r
135 amount of blocks dynamically limited. Transferring blocks is the
\r
136 main network eater of this system, so it is the one that has
\r
137 to be throttled so that RTTs stay low.
\r
139 TODO: Server to load starting inventory from disk
\r
141 TODO: Players to only be hidden when the client quits.
\r
142 TODO: - Players to be saved on disk, with inventory
\r
143 TODO: Players to be saved as text in map/players/<name>
\r
145 TODO: Make fetching sector's blocks more efficient when rendering
\r
146 sectors that have very large amounts of blocks (on client)
\r
148 TODO: Make the video backend selectable
\r
150 Block object server side:
\r
151 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
152 - For all blocks in the buffer, objects are stepped(). This
\r
153 means they are active.
\r
154 - TODO: A global active buffer is needed for the server
\r
155 - TODO: A timestamp to blocks
\r
156 - TODO: All blocks going in and out of the buffer are recorded.
\r
157 - TODO: For outgoing blocks, timestamp is written.
\r
158 - TODO: For incoming blocks, time difference is calculated and
\r
159 objects are stepped according to it.
\r
161 TODO: Add config parameters for server's sending and generating distance
\r
163 TODO: Copy the text of the last picked sign to inventory in creative
\r
166 TODO: Untie client network operations from framerate
\r
167 - Needs some input queues or something
\r
169 TODO: Get rid of GotSplitPacketException
\r
171 TODO: Check what goes wrong with caching map to disk (Kray)
\r
173 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
175 TODO: Node cracking animation when digging
\r
176 - TODO: A way to generate new textures by combining textures
\r
177 - TODO: Mesh update to fetch cracked faces from the former
\r
180 ======================================================================
\r
182 TODO: Add a second lighting value to the MS nibble of param of
\r
183 air to tell how bright the air node is when there is no sunlight.
\r
184 When day changes to night, these two values can be interpolated.
\r
185 - The biggest job is to add support to the lighting routines
\r
187 ======================================================================
\r
192 Setting this to 1 enables a special camera mode that forces
\r
193 the renderers to think that the camera statically points from
\r
194 the starting place to a static direction.
\r
196 This allows one to move around with the player and see what
\r
197 is actually drawn behind solid things and behind the player.
\r
199 #define FIELD_OF_VIEW_TEST 0
\r
201 #ifdef UNITTEST_DISABLE
\r
203 #pragma message ("Disabling unit tests")
\r
205 #warning "Disabling unit tests"
\r
207 // Disable unit tests
\r
208 #define ENABLE_TESTS 0
\r
210 // Enable unit tests
\r
211 #define ENABLE_TESTS 1
\r
215 #pragma comment(lib, "Irrlicht.lib")
\r
216 #pragma comment(lib, "jthread.lib")
\r
217 #pragma comment(lib, "zlibwapi.lib")
\r
218 // This would get rid of the console window
\r
219 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
223 #define WIN32_LEAN_AND_MEAN
\r
224 #include <windows.h>
\r
225 #define sleep_ms(x) Sleep(x)
\r
227 #include <unistd.h>
\r
228 #define sleep_ms(x) usleep(x*1000)
\r
231 #include <iostream>
\r
234 #include <jmutexautolock.h>
\r
235 #include <locale.h>
\r
236 #include "common_irrlicht.h"
\r
239 #include "player.h"
\r
242 #include "environment.h"
\r
243 #include "server.h"
\r
244 #include "client.h"
\r
245 #include "serialization.h"
\r
246 #include "constants.h"
\r
247 #include "strfnd.h"
\r
248 #include "porting.h"
\r
249 #include "guiPauseMenu.h"
\r
251 IrrlichtDevice *g_device = NULL;
\r
253 /*const char *g_content_filenames[MATERIALS_COUNT] =
\r
255 "../data/stone.png",
\r
256 "../data/grass.png",
\r
257 "../data/water.png",
\r
258 "../data/torch_on_floor.png",
\r
259 "../data/tree.png",
\r
260 "../data/leaves.png",
\r
261 "../data/grass_footsteps.png",
\r
262 "../data/mese.png",
\r
264 "../data/water.png", // CONTENT_OCEAN
\r
268 video::SMaterial g_materials[MATERIALS_COUNT];*/
\r
271 TextureCache g_texturecache;
\r
274 // All range-related stuff below is locked behind this
\r
275 JMutex g_range_mutex;
\r
277 // Blocks are viewed in this range from the player
\r
278 s16 g_viewing_range_nodes = 60;
\r
279 //s16 g_viewing_range_nodes = 0;
\r
281 // This is updated by the client's fetchBlocks routine
\r
282 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
284 // If true, the preceding value has no meaning and all blocks
\r
285 // already existing in memory are drawn
\r
286 bool g_viewing_range_all = false;
\r
288 // This is the freetime ratio imposed by the dynamic viewing
\r
289 // range changing code.
\r
290 // It is controlled by the main loop to the smallest value that
\r
291 // inhibits glitches (dtime jitter) in the main loop.
\r
292 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
297 These are loaded from the config file.
\r
300 Settings g_settings;
\r
302 // Sets default settings
\r
303 void set_default_settings()
\r
306 g_settings.setDefault("wanted_fps", "30");
\r
307 g_settings.setDefault("fps_max", "60");
\r
308 g_settings.setDefault("viewing_range_nodes_max", "300");
\r
309 g_settings.setDefault("viewing_range_nodes_min", "35");
\r
310 g_settings.setDefault("screenW", "");
\r
311 g_settings.setDefault("screenH", "");
\r
312 g_settings.setDefault("host_game", "");
\r
313 g_settings.setDefault("port", "");
\r
314 g_settings.setDefault("address", "");
\r
315 g_settings.setDefault("name", "");
\r
316 g_settings.setDefault("random_input", "false");
\r
317 g_settings.setDefault("client_delete_unused_sectors_timeout", "1200");
\r
318 g_settings.setDefault("max_block_send_distance", "8");
\r
319 g_settings.setDefault("max_block_generate_distance", "6");
\r
322 g_settings.setDefault("creative_mode", "false");
\r
323 g_settings.setDefault("heightmap_blocksize", "32");
\r
324 g_settings.setDefault("height_randmax", "constant 50.0");
\r
325 g_settings.setDefault("height_randfactor", "constant 0.6");
\r
326 g_settings.setDefault("height_base", "linear 0 0 0");
\r
327 g_settings.setDefault("plants_amount", "1.0");
\r
328 g_settings.setDefault("ravines_amount", "1.0");
\r
329 g_settings.setDefault("objectdata_interval", "0.2");
\r
330 g_settings.setDefault("active_object_range", "2");
\r
331 g_settings.setDefault("max_simultaneous_block_sends_per_client", "1");
\r
332 g_settings.setDefault("max_simultaneous_block_sends_server_total", "4");
\r
333 g_settings.setDefault("disable_water_climb", "true");
\r
334 g_settings.setDefault("endless_water", "true");
\r
341 //u16 g_selected_material = 0;
\r
342 u16 g_selected_item = 0;
\r
344 bool g_esc_pressed = false;
\r
346 std::wstring g_text_buffer;
\r
347 bool g_text_buffer_accepted = false;
\r
349 // When true, the mouse and keyboard are grabbed
\r
350 bool g_game_focused = true;
\r
357 std::ostream *dout_con_ptr = &dummyout;
\r
358 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
359 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
360 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
361 //std::ostream *dout_con_ptr = &dstream;
\r
362 //std::ostream *derr_con_ptr = &dstream;
\r
365 std::ostream *dout_server_ptr = &dstream;
\r
366 std::ostream *derr_server_ptr = &dstream;
\r
369 std::ostream *dout_client_ptr = &dstream;
\r
370 std::ostream *derr_client_ptr = &dstream;
\r
377 JMutex g_timestamp_mutex;
\r
378 //std::string g_timestamp;
\r
380 std::string getTimestamp()
\r
382 if(g_timestamp_mutex.IsInitialized()==false)
\r
384 JMutexAutoLock lock(g_timestamp_mutex);
\r
385 //return g_timestamp;
\r
386 time_t t = time(NULL);
\r
387 struct tm *tm = localtime(&t);
\r
389 strftime(cs, 20, "%H:%M:%S", tm);
\r
393 class MyEventReceiver : public IEventReceiver
\r
396 // This is the one method that we have to implement
\r
397 virtual bool OnEvent(const SEvent& event)
\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
407 if(g_game_focused == false)
\r
409 s16 key = event.KeyInput.Key;
\r
410 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
412 g_text_buffer_accepted = true;
\r
414 else if(key == irr::KEY_BACK)
\r
416 if(g_text_buffer.size() > 0)
\r
417 g_text_buffer = g_text_buffer.substr
\r
418 (0, g_text_buffer.size()-1);
\r
422 wchar_t wc = event.KeyInput.Char;
\r
424 g_text_buffer += wc;
\r
428 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
430 //TODO: Not used anymore?
\r
431 if(g_game_focused == true)
\r
433 dstream<<DTIME<<"ESC pressed"<<std::endl;
\r
434 g_esc_pressed = true;
\r
438 // Material selection
\r
439 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
441 if(g_game_focused == true)
\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
452 // Viewing range selection
\r
453 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
473 dstream<<"-----------------------------------------"
\r
475 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
476 dstream<<"-----------------------------------------"
\r
478 debug_stacks_print();
\r
483 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
485 left_active = event.MouseInput.isLeftPressed();
\r
486 middle_active = event.MouseInput.isMiddlePressed();
\r
487 right_active = event.MouseInput.isRightPressed();
\r
489 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
491 leftclicked = true;
\r
493 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
495 rightclicked = true;
\r
497 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
499 leftreleased = true;
\r
501 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
503 rightreleased = true;
\r
505 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
507 /*dstream<<"event.MouseInput.Wheel="
\r
508 <<event.MouseInput.Wheel<<std::endl;*/
\r
509 if(event.MouseInput.Wheel < 0)
\r
511 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
514 g_selected_item = 0;
\r
516 else if(event.MouseInput.Wheel > 0)
\r
518 if(g_selected_item > 0)
\r
521 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
529 // This is used to check whether a key is being held down
\r
530 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
532 return keyIsDown[keyCode];
\r
537 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
538 keyIsDown[i] = false;
\r
539 leftclicked = false;
\r
540 rightclicked = false;
\r
541 leftreleased = false;
\r
542 rightreleased = false;
\r
544 left_active = false;
\r
545 middle_active = false;
\r
546 right_active = false;
\r
552 bool rightreleased;
\r
555 bool middle_active;
\r
559 // We use this array to store the current state of each key
\r
560 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
571 virtual ~InputHandler()
\r
575 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
577 virtual v2s32 getMousePos() = 0;
\r
578 virtual void setMousePos(s32 x, s32 y) = 0;
\r
580 virtual bool getLeftState() = 0;
\r
581 virtual bool getRightState() = 0;
\r
583 virtual bool getLeftClicked() = 0;
\r
584 virtual bool getRightClicked() = 0;
\r
585 virtual void resetLeftClicked() = 0;
\r
586 virtual void resetRightClicked() = 0;
\r
588 virtual bool getLeftReleased() = 0;
\r
589 virtual bool getRightReleased() = 0;
\r
590 virtual void resetLeftReleased() = 0;
\r
591 virtual void resetRightReleased() = 0;
\r
593 virtual void step(float dtime) {};
\r
595 virtual void clear() {};
\r
598 InputHandler *g_input = NULL;
\r
603 g_game_focused = true;
\r
608 g_game_focused = false;
\r
611 class RealInputHandler : public InputHandler
\r
614 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
616 m_receiver(receiver)
\r
619 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
621 return m_receiver->IsKeyDown(keyCode);
\r
623 virtual v2s32 getMousePos()
\r
625 return m_device->getCursorControl()->getPosition();
\r
627 virtual void setMousePos(s32 x, s32 y)
\r
629 m_device->getCursorControl()->setPosition(x, y);
\r
632 virtual bool getLeftState()
\r
634 return m_receiver->left_active;
\r
636 virtual bool getRightState()
\r
638 return m_receiver->right_active;
\r
641 virtual bool getLeftClicked()
\r
643 if(g_game_focused == false)
\r
645 return m_receiver->leftclicked;
\r
647 virtual bool getRightClicked()
\r
649 if(g_game_focused == false)
\r
651 return m_receiver->rightclicked;
\r
653 virtual void resetLeftClicked()
\r
655 m_receiver->leftclicked = false;
\r
657 virtual void resetRightClicked()
\r
659 m_receiver->rightclicked = false;
\r
662 virtual bool getLeftReleased()
\r
664 if(g_game_focused == false)
\r
666 return m_receiver->leftreleased;
\r
668 virtual bool getRightReleased()
\r
670 if(g_game_focused == false)
\r
672 return m_receiver->rightreleased;
\r
674 virtual void resetLeftReleased()
\r
676 m_receiver->leftreleased = false;
\r
678 virtual void resetRightReleased()
\r
680 m_receiver->rightreleased = false;
\r
685 resetRightClicked();
\r
686 resetLeftClicked();
\r
689 IrrlichtDevice *m_device;
\r
690 MyEventReceiver *m_receiver;
\r
693 class RandomInputHandler : public InputHandler
\r
696 RandomInputHandler()
\r
698 leftclicked = false;
\r
699 rightclicked = false;
\r
700 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
701 keydown[i] = false;
\r
703 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
705 return keydown[keyCode];
\r
707 virtual v2s32 getMousePos()
\r
711 virtual void setMousePos(s32 x, s32 y)
\r
713 mousepos = v2s32(x,y);
\r
716 virtual bool getLeftState()
\r
720 virtual bool getRightState()
\r
725 virtual bool getLeftClicked()
\r
727 return leftclicked;
\r
729 virtual bool getRightClicked()
\r
731 return rightclicked;
\r
733 virtual void resetLeftClicked()
\r
735 leftclicked = false;
\r
737 virtual void resetRightClicked()
\r
739 rightclicked = false;
\r
742 virtual bool getLeftReleased()
\r
746 virtual bool getRightReleased()
\r
750 virtual void resetLeftReleased()
\r
753 virtual void resetRightReleased()
\r
757 virtual void step(float dtime)
\r
760 static float counter1 = 0;
\r
764 counter1 = 0.1*Rand(1,10);
\r
765 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
766 g_selected_material++;
\r
768 g_selected_material = 0;*/
\r
769 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
772 g_selected_item = 0;
\r
776 static float counter1 = 0;
\r
780 counter1 = 0.1*Rand(1, 40);
\r
781 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
785 static float counter1 = 0;
\r
789 counter1 = 0.1*Rand(1, 40);
\r
790 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
794 static float counter1 = 0;
\r
798 counter1 = 0.1*Rand(1, 40);
\r
799 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
803 static float counter1 = 0;
\r
807 counter1 = 0.1*Rand(1, 40);
\r
808 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
812 static float counter1 = 0;
\r
816 counter1 = 0.1*Rand(1, 20);
\r
817 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
821 static float counter1 = 0;
\r
825 counter1 = 0.1*Rand(1, 30);
\r
826 leftclicked = true;
\r
830 static float counter1 = 0;
\r
834 counter1 = 0.1*Rand(1, 20);
\r
835 rightclicked = true;
\r
838 mousepos += mousespeed;
\r
841 s32 Rand(s32 min, s32 max)
\r
843 return (rand()%(max-min+1))+min;
\r
846 bool keydown[KEY_KEY_CODES_COUNT];
\r
853 void updateViewingRange(f32 frametime, Client *client)
\r
855 // Range_all messes up frametime_avg
\r
856 if(g_viewing_range_all == true)
\r
859 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
861 // Initialize to the target value
\r
862 static float frametime_avg = 1.0/wanted_fps;
\r
863 frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
865 static f32 counter = 0;
\r
867 counter -= frametime;
\r
870 //counter = 1.0; //seconds
\r
871 counter = 0.5; //seconds
\r
873 //float freetime_ratio = 0.2;
\r
874 //float freetime_ratio = 0.4;
\r
875 float freetime_ratio = FREETIME_RATIO;
\r
877 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
879 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
881 static bool fraction_is_good = false;
\r
883 float fraction_good_threshold = 0.1;
\r
884 //float fraction_bad_threshold = 0.25;
\r
885 float fraction_bad_threshold = 0.1;
\r
886 float fraction_limit;
\r
887 // Use high limit if fraction is good AND the fraction would
\r
888 // lower the range. We want to keep the range fairly high.
\r
889 if(fraction_is_good && fraction > 1.0)
\r
890 fraction_limit = fraction_bad_threshold;
\r
892 fraction_limit = fraction_good_threshold;
\r
894 if(fabs(fraction - 1.0) < fraction_limit)
\r
896 fraction_is_good = true;
\r
901 fraction_is_good = false;
\r
904 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
905 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
906 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
907 <<" faction = "<<fraction<<std::endl;*/
\r
909 JMutexAutoLock lock(g_range_mutex);
\r
911 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
912 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
914 s16 n = (float)g_viewing_range_nodes / fraction;
\r
915 if(n < viewing_range_nodes_min)
\r
916 n = viewing_range_nodes_min;
\r
917 if(n > viewing_range_nodes_max)
\r
918 n = viewing_range_nodes_max;
\r
920 bool can_change = true;
\r
922 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
923 can_change = false;
\r
926 g_viewing_range_nodes = n;
\r
928 /*dstream<<"g_viewing_range_nodes = "
\r
929 <<g_viewing_range_nodes<<std::endl;*/
\r
932 class GUIQuickInventory : public IEventReceiver
\r
936 gui::IGUIEnvironment* env,
\r
937 gui::IGUIElement* parent,
\r
940 Inventory *inventory):
\r
941 m_itemcount(itemcount),
\r
942 m_inventory(inventory)
\r
944 core::rect<s32> imgsize(0,0,48,48);
\r
945 core::rect<s32> textsize(0,0,48,16);
\r
946 v2s32 spacing(0, 64);
\r
947 for(s32 i=0; i<m_itemcount; i++)
\r
949 m_images.push_back(env->addImage(
\r
950 imgsize + pos + spacing*i
\r
952 m_images[i]->setScaleImage(true);
\r
953 m_texts.push_back(env->addStaticText(
\r
955 textsize + pos + spacing*i,
\r
958 m_texts[i]->setBackgroundColor(
\r
959 video::SColor(128,0,0,0));
\r
960 m_texts[i]->setTextAlignment(
\r
962 gui::EGUIA_UPPERLEFT);
\r
966 virtual bool OnEvent(const SEvent& event)
\r
971 void setSelection(s32 i)
\r
980 start = m_selection - m_itemcount / 2;
\r
982 for(s32 i=0; i<m_itemcount; i++)
\r
986 if(j > (s32)m_inventory->getSize() - 1)
\r
987 j -= m_inventory->getSize();
\r
989 j += m_inventory->getSize();
\r
991 InventoryItem *item = m_inventory->getItem(j);
\r
995 m_images[i]->setImage(NULL);
\r
998 if(m_selection == j)
\r
999 swprintf(t, 10, L"<-");
\r
1001 swprintf(t, 10, L"");
\r
1002 m_texts[i]->setText(t);
\r
1004 // The next ifs will segfault with a NULL pointer
\r
1009 m_images[i]->setImage(item->getImage());
\r
1012 if(m_selection == j)
\r
1013 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1015 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1016 m_texts[i]->setText(t);
\r
1022 core::array<gui::IGUIStaticText*> m_texts;
\r
1023 core::array<gui::IGUIImage*> m_images;
\r
1024 Inventory *m_inventory;
\r
1028 int main(int argc, char *argv[])
\r
1031 Low-level initialization
\r
1034 bool disable_stderr = false;
\r
1036 disable_stderr = true;
\r
1039 // Initialize debug streams
\r
1040 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1041 // Initialize debug stacks
\r
1042 debug_stacks_init();
\r
1044 DSTACK(__FUNCTION_NAME);
\r
1050 Parse command line
\r
1054 core::map<std::string, ValueSpec> allowed_options;
\r
1055 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1056 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1057 "Run server directly"));
\r
1058 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1059 "Load configuration from specified file"));
\r
1060 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1062 Settings cmd_args;
\r
1064 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1066 if(ret == false || cmd_args.getFlag("help"))
\r
1068 dstream<<"Allowed options:"<<std::endl;
\r
1069 for(core::map<std::string, ValueSpec>::Iterator
\r
1070 i = allowed_options.getIterator();
\r
1071 i.atEnd() == false; i++)
\r
1073 dstream<<" --"<<i.getNode()->getKey();
\r
1074 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1079 dstream<<" <value>";
\r
1081 dstream<<std::endl;
\r
1083 if(i.getNode()->getValue().help != NULL)
\r
1085 dstream<<" "<<i.getNode()->getValue().help
\r
1090 return cmd_args.getFlag("help") ? 0 : 1;
\r
1095 Basic initialization
\r
1098 // Initialize default settings
\r
1099 set_default_settings();
\r
1101 // Print startup message
\r
1102 dstream<<DTIME<<"minetest-c55"
\r
1103 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1104 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1107 // Set locale. This is for forcing '.' as the decimal point.
\r
1108 std::locale::global(std::locale("C"));
\r
1109 // This enables printing all characters in bitmap font
\r
1110 setlocale(LC_CTYPE, "en_US");
\r
1112 // Initialize sockets
\r
1114 atexit(sockets_cleanup);
\r
1116 // Initialize timestamp mutex
\r
1117 g_timestamp_mutex.Init();
\r
1135 // Path of configuration file in use
\r
1136 std::string configpath = "";
\r
1138 if(cmd_args.exists("config"))
\r
1140 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1143 dstream<<"Could not read configuration from \""
\r
1144 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1147 configpath = cmd_args.get("config");
\r
1151 const char *filenames[2] =
\r
1153 "../minetest.conf",
\r
1154 "../../minetest.conf"
\r
1157 for(u32 i=0; i<2; i++)
\r
1159 bool r = g_settings.readConfigFile(filenames[i]);
\r
1162 configpath = filenames[i];
\r
1168 // Initialize random seed
\r
1171 g_range_mutex.Init();
\r
1172 assert(g_range_mutex.IsInitialized());
\r
1174 // Read map parameters from settings
\r
1176 HMParams hm_params;
\r
1177 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1178 hm_params.randmax = g_settings.get("height_randmax");
\r
1179 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1180 hm_params.base = g_settings.get("height_base");
\r
1182 MapParams map_params;
\r
1183 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1184 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1190 std::cout<<std::endl<<std::endl;
\r
1193 <<" .__ __ __ "<<std::endl
\r
1194 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1195 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1196 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1197 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1198 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1200 <<"Now with more waterish water!"
\r
1203 std::cout<<std::endl;
\r
1204 char templine[100];
\r
1208 if(cmd_args.exists("port"))
\r
1210 port = cmd_args.getU16("port");
\r
1214 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1215 std::cout<<"-> "<<port<<std::endl;
\r
1218 if(cmd_args.getFlag("server"))
\r
1220 DSTACK("Dedicated server branch");
\r
1222 std::cout<<std::endl;
\r
1223 std::cout<<"========================"<<std::endl;
\r
1224 std::cout<<"Running dedicated server"<<std::endl;
\r
1225 std::cout<<"========================"<<std::endl;
\r
1226 std::cout<<std::endl;
\r
1228 Server server("../map", hm_params, map_params);
\r
1229 server.start(port);
\r
1233 // This is kind of a hack but can be done like this
\r
1234 // because server.step() is very light
\r
1236 server.step(0.030);
\r
1238 static int counter = 0;
\r
1244 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1245 core::list<PlayerInfo>::Iterator i;
\r
1246 static u32 sum_old = 0;
\r
1247 u32 sum = PIChecksum(list);
\r
1248 if(sum != sum_old)
\r
1250 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1251 for(i=list.begin(); i!=list.end(); i++)
\r
1253 i->PrintLine(&std::cout);
\r
1263 bool hosting = false;
\r
1264 char connect_name[100] = "";
\r
1266 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1267 if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1269 std::cout<<g_settings.get("address")<<std::endl;
\r
1270 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1274 std::cin.getline(connect_name, 100);
\r
1277 if(connect_name[0] == 0){
\r
1278 snprintf(connect_name, 100, "127.0.0.1");
\r
1283 std::cout<<"-> hosting"<<std::endl;
\r
1285 std::cout<<"-> "<<connect_name<<std::endl;
\r
1287 char playername[PLAYERNAME_SIZE] = "";
\r
1288 if(g_settings.get("name") != "")
\r
1290 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1294 std::cout<<"Name of player: ";
\r
1295 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1297 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1300 Resolution selection
\r
1305 bool fullscreen = false;
\r
1307 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1309 screenW = atoi(g_settings.get("screenW").c_str());
\r
1310 screenH = atoi(g_settings.get("screenH").c_str());
\r
1314 u16 resolutions[][3] = {
\r
1315 //W, H, fullscreen
\r
1326 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1328 for(u16 i=0; i<res_count; i++)
\r
1330 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1331 <<resolutions[i][1];
\r
1332 if(resolutions[i][2])
\r
1333 std::cout<<" fullscreen"<<std::endl;
\r
1335 std::cout<<" windowed"<<std::endl;
\r
1337 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1338 std::cin.getline(templine, 100);
\r
1341 if(templine[0] == 0)
\r
1344 r0 = atoi(templine);
\r
1346 if(r0 > res_count || r0 == 0)
\r
1352 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1353 <<resolutions[i][1];
\r
1354 if(resolutions[i][2])
\r
1355 std::cout<<" fullscreen"<<std::endl;
\r
1357 std::cout<<" windowed"<<std::endl;
\r
1360 screenW = resolutions[r0-1][0];
\r
1361 screenH = resolutions[r0-1][1];
\r
1362 fullscreen = resolutions[r0-1][2];
\r
1367 MyEventReceiver receiver;
\r
1369 video::E_DRIVER_TYPE driverType;
\r
1372 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1373 driverType = video::EDT_OPENGL;
\r
1375 driverType = video::EDT_OPENGL;
\r
1378 // create device and exit if creation failed
\r
1380 IrrlichtDevice *device;
\r
1381 device = createDevice(driverType,
\r
1382 core::dimension2d<u32>(screenW, screenH),
\r
1383 16, fullscreen, false, false, &receiver);
\r
1385 /*device = createDevice(driverType,
\r
1386 core::dimension2d<u32>(screenW, screenH),
\r
1387 16, fullscreen, false, true, &receiver);*/
\r
1390 return 1; // could not create selected driver.
\r
1392 g_device = device;
\r
1394 device->setResizable(true);
\r
1396 if(g_settings.getBool("random_input"))
\r
1397 g_input = new RandomInputHandler();
\r
1399 g_input = new RealInputHandler(device, &receiver);
\r
1402 Continue initialization
\r
1405 video::IVideoDriver* driver = device->getVideoDriver();
\r
1406 // These make the textures not to show at all
\r
1407 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1408 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1410 //driver->setMinHardwareBufferVertexCount(1);
\r
1412 scene::ISceneManager* smgr = device->getSceneManager();
\r
1415 guiPauseMenu pauseMenu(device, &receiver);
\r
1417 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1418 gui::IGUISkin* skin = guienv->getSkin();
\r
1419 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1421 skin->setFont(font);
\r
1422 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1423 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1424 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1425 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1426 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1427 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1429 const wchar_t *text = L"Loading and connecting...";
\r
1430 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1431 core::dimension2d<u32> textd = font->getDimension(text);
\r
1432 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1433 // Have to add a bit to disable the text from word wrapping
\r
1434 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1435 core::vector2d<s32> textsize(300, textd.Height);
\r
1436 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1438 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1439 text, textrect, false, false);
\r
1440 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1442 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1443 guienv->drawAll();
\r
1444 driver->endScene();
\r
1447 Preload some random textures that are used in threads
\r
1450 g_texturecache.set("torch", driver->getTexture("../data/torch.png"));
\r
1451 g_texturecache.set("torch_on_floor", driver->getTexture("../data/torch_on_floor.png"));
\r
1452 g_texturecache.set("torch_on_ceiling", driver->getTexture("../data/torch_on_ceiling.png"));
\r
1455 Load tile textures
\r
1457 for(s32 i=0; i<TILES_COUNT; i++)
\r
1459 if(g_tile_texture_names[i] == NULL)
\r
1461 std::string name = g_tile_texture_names[i];
\r
1462 std::string filename;
\r
1463 filename += "../data/";
\r
1465 filename += ".png";
\r
1466 g_texturecache.set(name, driver->getTexture(filename.c_str()));
\r
1469 tile_materials_preload(g_texturecache);
\r
1472 Make a scope here for the client so that it gets removed
\r
1473 before the irrlicht device
\r
1477 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1482 SharedPtr<Server> server;
\r
1484 server = new Server("../map", hm_params, map_params);
\r
1485 server->start(port);
\r
1492 Client client(device, playername);
\r
1494 Address connect_address(0,0,0,0, port);
\r
1496 connect_address.Resolve(connect_name);
\r
1498 catch(ResolveError &e)
\r
1500 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1504 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1505 client.connect(connect_address);
\r
1508 while(client.connectedAndInitialized() == false)
\r
1511 if(server != NULL){
\r
1512 server->step(0.1);
\r
1517 catch(con::PeerNotFoundException &e)
\r
1519 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1526 scene::ISceneNode* skybox;
\r
1527 skybox = smgr->addSkyBoxSceneNode(
\r
1528 driver->getTexture("../data/skybox2.png"),
\r
1529 driver->getTexture("../data/skybox3.png"),
\r
1530 driver->getTexture("../data/skybox1.png"),
\r
1531 driver->getTexture("../data/skybox1.png"),
\r
1532 driver->getTexture("../data/skybox1.png"),
\r
1533 driver->getTexture("../data/skybox1.png"));
\r
1536 Create the camera node
\r
1539 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1540 0, // Camera parent
\r
1541 v3f(BS*100, BS*2, BS*100), // Look from
\r
1542 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1546 if(camera == NULL)
\r
1549 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1551 camera->setFOV(FOV_ANGLE);
\r
1553 // Just so big a value that everything rendered is visible
\r
1554 camera->setFarValue(100000*BS);
\r
1556 /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1557 f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1559 camera->setFarValue(range);
\r
1563 video::EFT_FOG_LINEAR,
\r
1571 f32 camera_yaw = 0; // "right/left"
\r
1572 f32 camera_pitch = 0; // "up/down"
\r
1578 gui_loadingtext->remove();
\r
1580 pauseMenu.setVisible(true);
\r
1583 Add some gui stuff
\r
1586 // First line of debug text
\r
1587 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1589 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1591 // Second line of debug text
\r
1592 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1594 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1597 // At the middle of the screen
\r
1598 // Object infos are shown in this
\r
1599 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1601 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1604 // This is a copy of the inventory that the client's environment has
\r
1605 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1607 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1608 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1611 Some statistics are collected in these
\r
1614 u32 beginscenetime = 0;
\r
1615 u32 scenetime = 0;
\r
1616 u32 endscenetime = 0;
\r
1624 virtual void sendText(std::string text) = 0;
\r
1627 struct TextDestSign : public TextDest
\r
1629 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1631 m_blockpos = blockpos;
\r
1633 m_client = client;
\r
1635 void sendText(std::string text)
\r
1637 dstream<<"Changing text of a sign object: "
\r
1638 <<text<<std::endl;
\r
1639 m_client->sendSignText(m_blockpos, m_id, text);
\r
1647 TextDest *textbuf_dest = NULL;
\r
1649 //gui::IGUIWindow* input_window = NULL;
\r
1650 gui::IGUIStaticText* input_guitext = NULL;
\r
1661 bool first_loop_after_window_activation = true;
\r
1663 // Time is in milliseconds
\r
1664 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1665 // NOTE: Have to call run() between calls of this to update the timer
\r
1666 u32 lasttime = device->getTimer()->getTime();
\r
1668 while(device->run())
\r
1671 Random calculations
\r
1673 v2u32 screensize = driver->getScreenSize();
\r
1674 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1676 // Hilight boxes collected during the loop and displayed
\r
1677 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1680 std::wstring infotext;
\r
1682 //TimeTaker //timer1("//timer1", device);
\r
1684 // Time of frame without fps limit
\r
1688 // not using getRealTime is necessary for wine
\r
1689 u32 time = device->getTimer()->getTime();
\r
1690 if(time > lasttime)
\r
1691 busytime_u32 = time - lasttime;
\r
1694 busytime = busytime_u32 / 1000.0;
\r
1697 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1699 // Absolutelu necessary for wine!
\r
1706 //updateViewingRange(dtime, &client);
\r
1707 updateViewingRange(busytime, &client);
\r
1714 float fps_max = g_settings.getFloat("fps_max");
\r
1715 u32 frametime_min = 1000./fps_max;
\r
1717 if(busytime_u32 < frametime_min)
\r
1719 u32 sleeptime = frametime_min - busytime_u32;
\r
1720 device->sleep(sleeptime);
\r
1724 // Absolutelu necessary for wine!
\r
1728 Time difference calculation
\r
1730 f32 dtime; // in seconds
\r
1732 u32 time = device->getTimer()->getTime();
\r
1733 if(time > lasttime)
\r
1734 dtime = (time - lasttime) / 1000.0;
\r
1740 Time average and jitter calculation
\r
1743 static f32 dtime_avg1 = 0.0;
\r
1744 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1745 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1747 static f32 dtime_jitter1_max_sample = 0.0;
\r
1748 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1750 static f32 jitter1_max = 0.0;
\r
1751 static f32 counter = 0.0;
\r
1752 if(dtime_jitter1 > jitter1_max)
\r
1753 jitter1_max = dtime_jitter1;
\r
1758 dtime_jitter1_max_sample = jitter1_max;
\r
1759 dtime_jitter1_max_fraction
\r
1760 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1761 jitter1_max = 0.0;
\r
1764 Control freetime ratio
\r
1766 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1768 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1769 g_freetime_ratio += 0.01;
\r
1773 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1774 g_freetime_ratio -= 0.01;
\r
1780 Busytime average and jitter calculation
\r
1783 static f32 busytime_avg1 = 0.0;
\r
1784 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1785 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1787 static f32 busytime_jitter1_max_sample = 0.0;
\r
1788 static f32 busytime_jitter1_min_sample = 0.0;
\r
1790 static f32 jitter1_max = 0.0;
\r
1791 static f32 jitter1_min = 0.0;
\r
1792 static f32 counter = 0.0;
\r
1793 if(busytime_jitter1 > jitter1_max)
\r
1794 jitter1_max = busytime_jitter1;
\r
1795 if(busytime_jitter1 < jitter1_min)
\r
1796 jitter1_min = busytime_jitter1;
\r
1798 if(counter > 0.0){
\r
1800 busytime_jitter1_max_sample = jitter1_max;
\r
1801 busytime_jitter1_min_sample = jitter1_min;
\r
1802 jitter1_max = 0.0;
\r
1803 jitter1_min = 0.0;
\r
1808 Debug info for client
\r
1811 static float counter = 0.0;
\r
1816 client.printDebugInfo(std::cout);
\r
1821 Input handler step()
\r
1823 g_input->step(dtime);
\r
1828 /*if(g_esc_pressed)
\r
1834 Player speed control
\r
1837 if(g_game_focused)
\r
1844 bool a_superspeed,
\r
1847 PlayerControl control(
\r
1848 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1849 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1850 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1851 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1852 g_input->isKeyDown(irr::KEY_SPACE),
\r
1853 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1857 client.setPlayerControl(control);
\r
1861 // Set every key to inactive
\r
1862 PlayerControl control;
\r
1863 client.setPlayerControl(control);
\r
1868 Process environment
\r
1872 //TimeTaker timer("client.step(dtime)", device);
\r
1873 client.step(dtime);
\r
1874 //client.step(dtime_avg1);
\r
1877 if(server != NULL)
\r
1879 //TimeTaker timer("server->step(dtime)", device);
\r
1880 server->step(dtime);
\r
1883 v3f player_position = client.getPlayerPosition();
\r
1885 //TimeTaker //timer2("//timer2", device);
\r
1888 Mouse and camera control
\r
1891 if(device->isWindowActive() && g_game_focused && !pauseMenu.isVisible())
\r
1893 device->getCursorControl()->setVisible(false);
\r
1895 if(first_loop_after_window_activation){
\r
1896 //std::cout<<"window active, first loop"<<std::endl;
\r
1897 first_loop_after_window_activation = false;
\r
1900 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1901 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1902 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1903 camera_yaw -= dx*0.2;
\r
1904 camera_pitch += dy*0.2;
\r
1905 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1906 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1908 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1911 device->getCursorControl()->setVisible(true);
\r
1913 //std::cout<<"window inactive"<<std::endl;
\r
1914 first_loop_after_window_activation = true;
\r
1917 camera_yaw = wrapDegrees(camera_yaw);
\r
1918 camera_pitch = wrapDegrees(camera_pitch);
\r
1920 v3f camera_direction = v3f(0,0,1);
\r
1921 camera_direction.rotateYZBy(camera_pitch);
\r
1922 camera_direction.rotateXZBy(camera_yaw);
\r
1924 v3f camera_position =
\r
1925 player_position + v3f(0, BS+BS/2, 0);
\r
1927 camera->setPosition(camera_position);
\r
1928 // *100.0 helps in large map coordinates
\r
1929 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1931 if(FIELD_OF_VIEW_TEST){
\r
1932 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1933 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1936 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1937 //TimeTaker timer("client.updateCamera", device);
\r
1938 client.updateCamera(camera_position, camera_direction);
\r
1942 //TimeTaker //timer3("//timer3", device);
\r
1945 Calculate what block is the crosshair pointing to
\r
1948 //u32 t1 = device->getTimer()->getRealTime();
\r
1950 //f32 d = 4; // max. distance
\r
1951 f32 d = 4; // max. distance
\r
1952 core::line3d<f32> shootline(camera_position,
\r
1953 camera_position + camera_direction * BS * (d+1));
\r
1955 MapBlockObject *selected_object = client.getSelectedObject
\r
1956 (d*BS, camera_position, shootline);
\r
1958 if(selected_object != NULL)
\r
1960 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1962 core::aabbox3d<f32> box_on_map
\r
1963 = selected_object->getSelectionBoxOnMap();
\r
1965 hilightboxes.push_back(box_on_map);
\r
1967 infotext = narrow_to_wide(selected_object->infoText());
\r
1969 if(g_input->getLeftClicked())
\r
1971 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1972 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1973 selected_object->getId(), g_selected_item);
\r
1975 else if(g_input->getRightClicked())
\r
1977 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1979 Check if we want to modify the object ourselves
\r
1981 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1983 dstream<<"Sign object right-clicked"<<std::endl;
\r
1987 input_guitext = guienv->addStaticText(L"",
\r
1988 core::rect<s32>(150,100,350,120),
\r
1990 false, // wordwrap?
\r
1993 input_guitext->setDrawBackground(true);
\r
1995 g_text_buffer = L"";
\r
1996 g_text_buffer_accepted = false;
\r
1997 textbuf_dest = new TextDestSign(
\r
1998 selected_object->getBlock()->getPos(),
\r
1999 selected_object->getId(),
\r
2003 Otherwise pass the event to the server as-is
\r
2007 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
2008 selected_object->getId(), g_selected_item);
\r
2012 else // selected_object == NULL
\r
2016 Find out which node we are pointing at
\r
2019 bool nodefound = false;
\r
2021 v3s16 neighbourpos;
\r
2022 core::aabbox3d<f32> nodefacebox;
\r
2023 f32 mindistance = BS * 1001;
\r
2025 v3s16 pos_i = floatToInt(player_position);
\r
2027 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2031 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2032 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2033 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2034 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2035 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2036 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2038 for(s16 y = ystart; y <= yend; y++)
\r
2039 for(s16 z = zstart; z <= zend; z++)
\r
2040 for(s16 x = xstart; x <= xend; x++)
\r
2045 n = client.getNode(v3s16(x,y,z));
\r
2046 if(content_pointable(n.d) == false)
\r
2049 catch(InvalidPositionException &e)
\r
2055 v3f npf = intToFloat(np);
\r
2060 v3s16(0,0,1), // back
\r
2061 v3s16(0,1,0), // top
\r
2062 v3s16(1,0,0), // right
\r
2063 v3s16(0,0,-1), // front
\r
2064 v3s16(0,-1,0), // bottom
\r
2065 v3s16(-1,0,0), // left
\r
2071 if(n.d == CONTENT_TORCH)
\r
2073 v3s16 dir = unpackDir(n.dir);
\r
2074 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2075 dir_f *= BS/2 - BS/6 - BS/20;
\r
2076 v3f cpf = npf + dir_f;
\r
2077 f32 distance = (cpf - camera_position).getLength();
\r
2079 core::aabbox3d<f32> box;
\r
2082 if(dir == v3s16(0,-1,0))
\r
2084 box = core::aabbox3d<f32>(
\r
2085 npf - v3f(BS/6, BS/2, BS/6),
\r
2086 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2090 else if(dir == v3s16(0,1,0))
\r
2092 box = core::aabbox3d<f32>(
\r
2093 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2094 npf + v3f(BS/6, BS/2, BS/6)
\r
2100 box = core::aabbox3d<f32>(
\r
2101 cpf - v3f(BS/6, BS/3, BS/6),
\r
2102 cpf + v3f(BS/6, BS/3, BS/6)
\r
2106 if(distance < mindistance)
\r
2108 if(box.intersectsWithLine(shootline))
\r
2112 neighbourpos = np;
\r
2113 mindistance = distance;
\r
2114 nodefacebox = box;
\r
2123 for(u16 i=0; i<6; i++)
\r
2125 v3f dir_f = v3f(dirs[i].X,
\r
2126 dirs[i].Y, dirs[i].Z);
\r
2127 v3f centerpoint = npf + dir_f * BS/2;
\r
2129 (centerpoint - camera_position).getLength();
\r
2131 if(distance < mindistance)
\r
2133 core::CMatrix4<f32> m;
\r
2134 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2136 // This is the back face
\r
2137 v3f corners[2] = {
\r
2138 v3f(BS/2, BS/2, BS/2),
\r
2139 v3f(-BS/2, -BS/2, BS/2+d)
\r
2142 for(u16 j=0; j<2; j++)
\r
2144 m.rotateVect(corners[j]);
\r
2145 corners[j] += npf;
\r
2148 core::aabbox3d<f32> facebox(corners[0]);
\r
2149 facebox.addInternalPoint(corners[1]);
\r
2151 if(facebox.intersectsWithLine(shootline))
\r
2155 neighbourpos = np + dirs[i];
\r
2156 mindistance = distance;
\r
2157 nodefacebox = facebox;
\r
2159 } // if distance < mindistance
\r
2161 } // regular block
\r
2164 /*static v3s16 oldnodepos;
\r
2165 static bool oldnodefound = false;*/
\r
2169 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
2170 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2171 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
2173 static v3s16 nodepos_old(-1,-1,-1);
\r
2174 if(nodepos != nodepos_old){
\r
2175 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2176 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2179 hilightboxes.push_back(nodefacebox);
\r
2181 //if(g_input->getLeftClicked())
\r
2182 if(g_input->getLeftClicked() ||
\r
2183 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2185 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2186 client.pressGround(0, nodepos, neighbourpos, g_selected_item);
\r
2188 if(g_input->getRightClicked())
\r
2189 /*if(g_input->getRightClicked() ||
\r
2190 (g_input->getRightState() && nodepos != nodepos_old))*/
\r
2192 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2193 client.pressGround(1, nodepos, neighbourpos, g_selected_item);
\r
2196 nodepos_old = nodepos;
\r
2199 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
2200 //positiontextgui->setText(L"");
\r
2203 /*oldnodefound = nodefound;
\r
2204 oldnodepos = nodepos;*/
\r
2206 } // selected_object == NULL
\r
2208 g_input->resetLeftClicked();
\r
2209 g_input->resetRightClicked();
\r
2211 if(g_input->getLeftReleased())
\r
2213 std::cout<<DTIME<<"Left released"<<std::endl;
\r
2214 client.stopDigging();
\r
2216 if(g_input->getRightReleased())
\r
2218 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2222 g_input->resetLeftReleased();
\r
2223 g_input->resetRightReleased();
\r
2226 Calculate stuff for drawing
\r
2229 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2231 /*f32 range = g_viewing_range_nodes * BS;
\r
2232 if(g_viewing_range_all)
\r
2233 range = 100000*BS;
\r
2237 video::EFT_FOG_LINEAR,
\r
2241 false, // pixel fog
\r
2242 false // range fog
\r
2247 Update gui stuff (0ms)
\r
2250 //TimeTaker guiupdatetimer("Gui updating", device);
\r
2253 wchar_t temptext[150];
\r
2255 static float drawtime_avg = 0;
\r
2256 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2257 static float beginscenetime_avg = 0;
\r
2258 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2259 static float scenetime_avg = 0;
\r
2260 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2261 static float endscenetime_avg = 0;
\r
2262 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2264 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2266 L", R: range_all=%i"
\r
2268 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2270 g_viewing_range_all,
\r
2272 beginscenetime_avg,
\r
2277 guitext->setText(temptext);
\r
2281 wchar_t temptext[150];
\r
2282 swprintf(temptext, 150,
\r
2283 L"(% .1f, % .1f, % .1f)"
\r
2284 L" (% .3f < btime_jitter < % .3f"
\r
2285 L", dtime_jitter = % .1f %%)",
\r
2286 player_position.X/BS,
\r
2287 player_position.Y/BS,
\r
2288 player_position.Z/BS,
\r
2289 busytime_jitter1_min_sample,
\r
2290 busytime_jitter1_max_sample,
\r
2291 dtime_jitter1_max_fraction * 100.0
\r
2294 guitext2->setText(temptext);
\r
2298 /*wchar_t temptext[100];
\r
2299 swprintf(temptext, 100,
\r
2300 SWPRINTF_CHARSTRING,
\r
2301 infotext.substr(0,99).c_str()
\r
2304 guitext_info->setText(temptext);*/
\r
2306 guitext_info->setText(infotext.c_str());
\r
2313 static u16 old_selected_item = 65535;
\r
2314 if(client.getLocalInventoryUpdated()
\r
2315 || g_selected_item != old_selected_item)
\r
2317 old_selected_item = g_selected_item;
\r
2318 //std::cout<<"Updating local inventory"<<std::endl;
\r
2319 client.getLocalInventory(local_inventory);
\r
2320 quick_inventory->setSelection(g_selected_item);
\r
2321 quick_inventory->update();
\r
2324 if(input_guitext != NULL)
\r
2326 /*wchar_t temptext[100];
\r
2327 swprintf(temptext, 100,
\r
2328 SWPRINTF_CHARSTRING,
\r
2329 g_text_buffer.substr(0,99).c_str()
\r
2331 input_guitext->setText(g_text_buffer.c_str());
\r
2337 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2339 input_guitext->remove();
\r
2340 input_guitext = NULL;
\r
2342 if(textbuf_dest != NULL)
\r
2344 std::string text = wide_to_narrow(g_text_buffer);
\r
2345 dstream<<"Sending text: "<<text<<std::endl;
\r
2346 textbuf_dest->sendText(text);
\r
2347 delete textbuf_dest;
\r
2348 textbuf_dest = NULL;
\r
2354 //guiupdatetimer.stop();
\r
2360 TimeTaker drawtimer("Drawing", device);
\r
2363 Background color is choosen based on whether the player is
\r
2364 much beyond the initial ground level
\r
2366 /*video::SColor bgcolor;
\r
2367 v3s16 p0 = Map::floatToInt(player_position);
\r
2368 // Does this make short random delays?
\r
2369 // NOTE: no need for this, sky doesn't show underground with
\r
2371 bool is_underground = client.isNodeUnderground(p0);
\r
2372 //bool is_underground = false;
\r
2373 if(is_underground == false)
\r
2374 bgcolor = video::SColor(255,90,140,200);
\r
2376 bgcolor = video::SColor(255,0,0,0);*/
\r
2378 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2379 video::SColor bgcolor = skycolor;
\r
2382 TimeTaker timer("beginScene", device);
\r
2383 //driver->beginScene(true, true, bgcolor);
\r
2384 driver->beginScene(false, true, bgcolor);
\r
2385 beginscenetime = timer.stop(true);
\r
2390 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2393 TimeTaker timer("smgr", device);
\r
2395 scenetime = timer.stop(true);
\r
2399 //TimeTaker timer9("auxiliary drawings", device);
\r
2402 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2403 displaycenter + core::vector2d<s32>(10,0),
\r
2404 video::SColor(255,255,255,255));
\r
2405 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2406 displaycenter + core::vector2d<s32>(0,10),
\r
2407 video::SColor(255,255,255,255));
\r
2410 //TimeTaker //timer10("//timer10", device);
\r
2412 video::SMaterial m;
\r
2414 m.Lighting = false;
\r
2415 driver->setMaterial(m);
\r
2417 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2419 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2420 i != hilightboxes.end(); i++)
\r
2422 /*std::cout<<"hilightbox min="
\r
2423 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2425 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2427 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2433 //TimeTaker //timer11("//timer11", device);
\r
2439 guienv->drawAll();
\r
2443 TimeTaker timer("endScene", device);
\r
2444 driver->endScene();
\r
2445 endscenetime = timer.stop(true);
\r
2448 drawtime = drawtimer.stop(true);
\r
2454 static s16 lastFPS = 0;
\r
2455 //u16 fps = driver->getFPS();
\r
2456 u16 fps = (1.0/dtime_avg1);
\r
2458 if (lastFPS != fps)
\r
2460 core::stringw str = L"Minetest [";
\r
2461 str += driver->getName();
\r
2465 device->setWindowCaption(str.c_str());
\r
2471 device->yield();*/
\r
2474 } // client is deleted at this point
\r
2479 In the end, delete the Irrlicht device.
\r
2484 Update configuration file
\r
2486 if(configpath != "")
\r
2488 g_settings.updateConfigFile(configpath.c_str());
\r
2492 catch(con::PeerNotFoundException &e)
\r
2494 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2496 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2498 This is what has to be done in every thread to get suitable debug info
\r
2500 catch(std::exception &e)
\r
2502 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2503 <<e.what()<<std::endl;
\r
2508 debugstreams_deinit();
\r