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
179 TODO: Add server unused sector deletion settings to settings
\r
181 TODO: TOSERVER_LEAVE
\r
184 ======================================================================
\r
186 ======================================================================
\r
191 Setting this to 1 enables a special camera mode that forces
\r
192 the renderers to think that the camera statically points from
\r
193 the starting place to a static direction.
\r
195 This allows one to move around with the player and see what
\r
196 is actually drawn behind solid things and behind the player.
\r
198 #define FIELD_OF_VIEW_TEST 0
\r
200 #ifdef UNITTEST_DISABLE
\r
202 #pragma message ("Disabling unit tests")
\r
204 #warning "Disabling unit tests"
\r
206 // Disable unit tests
\r
207 #define ENABLE_TESTS 0
\r
209 // Enable unit tests
\r
210 #define ENABLE_TESTS 1
\r
214 #pragma comment(lib, "Irrlicht.lib")
\r
215 #pragma comment(lib, "jthread.lib")
\r
216 #pragma comment(lib, "zlibwapi.lib")
\r
217 // This would get rid of the console window
\r
218 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
222 #define WIN32_LEAN_AND_MEAN
\r
223 #include <windows.h>
\r
224 #define sleep_ms(x) Sleep(x)
\r
226 #include <unistd.h>
\r
227 #define sleep_ms(x) usleep(x*1000)
\r
230 #include <iostream>
\r
233 #include <jmutexautolock.h>
\r
234 #include <locale.h>
\r
235 #include "common_irrlicht.h"
\r
238 #include "player.h"
\r
241 #include "environment.h"
\r
242 #include "server.h"
\r
243 #include "client.h"
\r
244 #include "serialization.h"
\r
245 #include "constants.h"
\r
246 #include "strfnd.h"
\r
247 #include "porting.h"
\r
248 #include "guiPauseMenu.h"
\r
250 IrrlichtDevice *g_device = NULL;
\r
252 /*const char *g_content_filenames[MATERIALS_COUNT] =
\r
254 "../data/stone.png",
\r
255 "../data/grass.png",
\r
256 "../data/water.png",
\r
257 "../data/torch_on_floor.png",
\r
258 "../data/tree.png",
\r
259 "../data/leaves.png",
\r
260 "../data/grass_footsteps.png",
\r
261 "../data/mese.png",
\r
263 "../data/water.png", // CONTENT_OCEAN
\r
267 video::SMaterial g_materials[MATERIALS_COUNT];*/
\r
270 TextureCache g_texturecache;
\r
273 // All range-related stuff below is locked behind this
\r
274 JMutex g_range_mutex;
\r
276 // Blocks are viewed in this range from the player
\r
277 s16 g_viewing_range_nodes = 60;
\r
278 //s16 g_viewing_range_nodes = 0;
\r
280 // This is updated by the client's fetchBlocks routine
\r
281 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
283 // If true, the preceding value has no meaning and all blocks
\r
284 // already existing in memory are drawn
\r
285 bool g_viewing_range_all = false;
\r
287 // This is the freetime ratio imposed by the dynamic viewing
\r
288 // range changing code.
\r
289 // It is controlled by the main loop to the smallest value that
\r
290 // inhibits glitches (dtime jitter) in the main loop.
\r
291 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
296 These are loaded from the config file.
\r
299 Settings g_settings;
\r
301 // Sets default settings
\r
302 void set_default_settings()
\r
305 g_settings.setDefault("wanted_fps", "30");
\r
306 g_settings.setDefault("fps_max", "60");
\r
307 g_settings.setDefault("viewing_range_nodes_max", "300");
\r
308 g_settings.setDefault("viewing_range_nodes_min", "35");
\r
309 g_settings.setDefault("screenW", "");
\r
310 g_settings.setDefault("screenH", "");
\r
311 g_settings.setDefault("host_game", "");
\r
312 g_settings.setDefault("port", "");
\r
313 g_settings.setDefault("address", "");
\r
314 g_settings.setDefault("name", "");
\r
315 g_settings.setDefault("random_input", "false");
\r
316 g_settings.setDefault("client_delete_unused_sectors_timeout", "1200");
\r
317 g_settings.setDefault("enable_fog", "true");
\r
320 g_settings.setDefault("creative_mode", "false");
\r
321 g_settings.setDefault("heightmap_blocksize", "32");
\r
322 g_settings.setDefault("height_randmax", "constant 50.0");
\r
323 g_settings.setDefault("height_randfactor", "constant 0.6");
\r
324 g_settings.setDefault("height_base", "linear 0 0 0");
\r
325 g_settings.setDefault("plants_amount", "1.0");
\r
326 g_settings.setDefault("ravines_amount", "1.0");
\r
327 g_settings.setDefault("objectdata_interval", "0.2");
\r
328 g_settings.setDefault("active_object_range", "2");
\r
329 g_settings.setDefault("max_simultaneous_block_sends_per_client", "1");
\r
330 g_settings.setDefault("max_simultaneous_block_sends_server_total", "4");
\r
331 g_settings.setDefault("disable_water_climb", "true");
\r
332 g_settings.setDefault("endless_water", "true");
\r
333 g_settings.setDefault("max_block_send_distance", "5");
\r
334 g_settings.setDefault("max_block_generate_distance", "4");
\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
864 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
866 static f32 counter = 0;
\r
868 counter -= frametime;
\r
871 //counter = 1.0; //seconds
\r
872 counter = 0.5; //seconds
\r
874 //float freetime_ratio = 0.2;
\r
875 //float freetime_ratio = 0.4;
\r
876 float freetime_ratio = FREETIME_RATIO;
\r
878 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
880 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
882 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
883 + frametime_avg / frametime_wanted / 2.0;*/
\r
885 //float fraction = frametime_avg / frametime_wanted;
\r
887 static bool fraction_is_good = false;
\r
889 float fraction_good_threshold = 0.1;
\r
890 //float fraction_bad_threshold = 0.25;
\r
891 float fraction_bad_threshold = 0.1;
\r
892 float fraction_limit;
\r
893 // Use high limit if fraction is good AND the fraction would
\r
894 // lower the range. We want to keep the range fairly high.
\r
895 if(fraction_is_good && fraction > 1.0)
\r
896 fraction_limit = fraction_bad_threshold;
\r
898 fraction_limit = fraction_good_threshold;
\r
900 if(fabs(fraction - 1.0) < fraction_limit)
\r
902 fraction_is_good = true;
\r
907 fraction_is_good = false;
\r
910 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
911 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
912 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
913 <<" faction = "<<fraction<<std::endl;*/
\r
915 JMutexAutoLock lock(g_range_mutex);
\r
917 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
918 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
920 s16 n = (float)g_viewing_range_nodes / fraction;
\r
921 if(n < viewing_range_nodes_min)
\r
922 n = viewing_range_nodes_min;
\r
923 if(n > viewing_range_nodes_max)
\r
924 n = viewing_range_nodes_max;
\r
926 bool can_change = true;
\r
928 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
929 can_change = false;
\r
932 g_viewing_range_nodes = n;
\r
934 /*dstream<<"g_viewing_range_nodes = "
\r
935 <<g_viewing_range_nodes<<std::endl;*/
\r
938 class GUIQuickInventory : public IEventReceiver
\r
942 gui::IGUIEnvironment* env,
\r
943 gui::IGUIElement* parent,
\r
946 Inventory *inventory):
\r
947 m_itemcount(itemcount),
\r
948 m_inventory(inventory)
\r
950 core::rect<s32> imgsize(0,0,48,48);
\r
951 core::rect<s32> textsize(0,0,48,16);
\r
952 v2s32 spacing(0, 64);
\r
953 for(s32 i=0; i<m_itemcount; i++)
\r
955 m_images.push_back(env->addImage(
\r
956 imgsize + pos + spacing*i
\r
958 m_images[i]->setScaleImage(true);
\r
959 m_texts.push_back(env->addStaticText(
\r
961 textsize + pos + spacing*i,
\r
964 m_texts[i]->setBackgroundColor(
\r
965 video::SColor(128,0,0,0));
\r
966 m_texts[i]->setTextAlignment(
\r
968 gui::EGUIA_UPPERLEFT);
\r
972 virtual bool OnEvent(const SEvent& event)
\r
977 void setSelection(s32 i)
\r
986 start = m_selection - m_itemcount / 2;
\r
988 for(s32 i=0; i<m_itemcount; i++)
\r
992 if(j > (s32)m_inventory->getSize() - 1)
\r
993 j -= m_inventory->getSize();
\r
995 j += m_inventory->getSize();
\r
997 InventoryItem *item = m_inventory->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
1034 int main(int argc, char *argv[])
\r
1037 Low-level initialization
\r
1040 bool disable_stderr = false;
\r
1042 disable_stderr = true;
\r
1045 // Initialize debug streams
\r
1046 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1047 // Initialize debug stacks
\r
1048 debug_stacks_init();
\r
1050 DSTACK(__FUNCTION_NAME);
\r
1056 Parse command line
\r
1059 // List all allowed options
\r
1060 core::map<std::string, ValueSpec> allowed_options;
\r
1061 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1062 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1063 "Run server directly"));
\r
1064 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1065 "Load configuration from specified file"));
\r
1066 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1067 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1068 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1069 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1070 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1072 Settings cmd_args;
\r
1074 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1076 if(ret == false || cmd_args.getFlag("help"))
\r
1078 dstream<<"Allowed options:"<<std::endl;
\r
1079 for(core::map<std::string, ValueSpec>::Iterator
\r
1080 i = allowed_options.getIterator();
\r
1081 i.atEnd() == false; i++)
\r
1083 dstream<<" --"<<i.getNode()->getKey();
\r
1084 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1089 dstream<<" <value>";
\r
1091 dstream<<std::endl;
\r
1093 if(i.getNode()->getValue().help != NULL)
\r
1095 dstream<<" "<<i.getNode()->getValue().help
\r
1100 return cmd_args.getFlag("help") ? 0 : 1;
\r
1105 Basic initialization
\r
1108 // Initialize default settings
\r
1109 set_default_settings();
\r
1111 // Print startup message
\r
1112 dstream<<DTIME<<"minetest-c55"
\r
1113 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1114 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1117 // Set locale. This is for forcing '.' as the decimal point.
\r
1118 std::locale::global(std::locale("C"));
\r
1119 // This enables printing all characters in bitmap font
\r
1120 setlocale(LC_CTYPE, "en_US");
\r
1122 // Initialize sockets
\r
1124 atexit(sockets_cleanup);
\r
1126 // Initialize timestamp mutex
\r
1127 g_timestamp_mutex.Init();
\r
1137 // Path of configuration file in use
\r
1138 std::string configpath = "";
\r
1140 if(cmd_args.exists("config"))
\r
1142 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1145 dstream<<"Could not read configuration from \""
\r
1146 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1149 configpath = cmd_args.get("config");
\r
1153 const char *filenames[2] =
\r
1155 "../minetest.conf",
\r
1156 "../../minetest.conf"
\r
1159 for(u32 i=0; i<2; i++)
\r
1161 bool r = g_settings.readConfigFile(filenames[i]);
\r
1164 configpath = filenames[i];
\r
1170 // Initialize random seed
\r
1176 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1177 || cmd_args.getFlag("enable-unittests") == true)
\r
1183 Global range mutex
\r
1185 g_range_mutex.Init();
\r
1186 assert(g_range_mutex.IsInitialized());
\r
1188 // Read map parameters from settings
\r
1190 HMParams hm_params;
\r
1191 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1192 hm_params.randmax = g_settings.get("height_randmax");
\r
1193 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1194 hm_params.base = g_settings.get("height_base");
\r
1196 MapParams map_params;
\r
1197 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1198 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1204 std::cout<<std::endl<<std::endl;
\r
1207 <<" .__ __ __ "<<std::endl
\r
1208 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1209 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1210 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1211 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1212 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1214 <<"Now with more waterish water!"
\r
1217 std::cout<<std::endl;
\r
1218 char templine[100];
\r
1222 if(cmd_args.exists("port"))
\r
1224 port = cmd_args.getU16("port");
\r
1228 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1229 std::cout<<"-> "<<port<<std::endl;
\r
1232 if(cmd_args.getFlag("server"))
\r
1234 DSTACK("Dedicated server branch");
\r
1236 std::cout<<std::endl;
\r
1237 std::cout<<"========================"<<std::endl;
\r
1238 std::cout<<"Running dedicated server"<<std::endl;
\r
1239 std::cout<<"========================"<<std::endl;
\r
1240 std::cout<<std::endl;
\r
1242 Server server("../map", hm_params, map_params);
\r
1243 server.start(port);
\r
1247 // This is kind of a hack but can be done like this
\r
1248 // because server.step() is very light
\r
1250 server.step(0.030);
\r
1252 static int counter = 0;
\r
1258 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1259 core::list<PlayerInfo>::Iterator i;
\r
1260 static u32 sum_old = 0;
\r
1261 u32 sum = PIChecksum(list);
\r
1262 if(sum != sum_old)
\r
1264 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1265 for(i=list.begin(); i!=list.end(); i++)
\r
1267 i->PrintLine(&std::cout);
\r
1277 bool hosting = false;
\r
1278 char connect_name[100] = "";
\r
1280 if(cmd_args.exists("address"))
\r
1282 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1284 else if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1286 std::cout<<g_settings.get("address")<<std::endl;
\r
1287 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1291 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1292 std::cin.getline(connect_name, 100);
\r
1295 if(connect_name[0] == 0){
\r
1296 snprintf(connect_name, 100, "127.0.0.1");
\r
1301 std::cout<<"> Hosting game"<<std::endl;
\r
1303 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1305 char playername[PLAYERNAME_SIZE] = "";
\r
1306 if(g_settings.get("name") != "")
\r
1308 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1312 std::cout<<"Name of player: ";
\r
1313 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1315 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1318 Resolution selection
\r
1323 bool fullscreen = false;
\r
1325 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1327 screenW = atoi(g_settings.get("screenW").c_str());
\r
1328 screenH = atoi(g_settings.get("screenH").c_str());
\r
1332 u16 resolutions[][3] = {
\r
1333 //W, H, fullscreen
\r
1344 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1346 for(u16 i=0; i<res_count; i++)
\r
1348 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1349 <<resolutions[i][1];
\r
1350 if(resolutions[i][2])
\r
1351 std::cout<<" fullscreen"<<std::endl;
\r
1353 std::cout<<" windowed"<<std::endl;
\r
1355 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1356 std::cin.getline(templine, 100);
\r
1359 if(templine[0] == 0)
\r
1362 r0 = atoi(templine);
\r
1364 if(r0 > res_count || r0 == 0)
\r
1370 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1371 <<resolutions[i][1];
\r
1372 if(resolutions[i][2])
\r
1373 std::cout<<" fullscreen"<<std::endl;
\r
1375 std::cout<<" windowed"<<std::endl;
\r
1378 screenW = resolutions[r0-1][0];
\r
1379 screenH = resolutions[r0-1][1];
\r
1380 fullscreen = resolutions[r0-1][2];
\r
1385 MyEventReceiver receiver;
\r
1387 video::E_DRIVER_TYPE driverType;
\r
1390 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1391 driverType = video::EDT_OPENGL;
\r
1393 driverType = video::EDT_OPENGL;
\r
1396 // create device and exit if creation failed
\r
1398 IrrlichtDevice *device;
\r
1399 device = createDevice(driverType,
\r
1400 core::dimension2d<u32>(screenW, screenH),
\r
1401 16, fullscreen, false, false, &receiver);
\r
1403 /*device = createDevice(driverType,
\r
1404 core::dimension2d<u32>(screenW, screenH),
\r
1405 16, fullscreen, false, true, &receiver);*/
\r
1408 return 1; // could not create selected driver.
\r
1410 g_device = device;
\r
1412 device->setResizable(true);
\r
1414 bool random_input = g_settings.getBool("random_input")
\r
1415 || cmd_args.getFlag("random-input");
\r
1417 g_input = new RandomInputHandler();
\r
1419 g_input = new RealInputHandler(device, &receiver);
\r
1422 Continue initialization
\r
1425 video::IVideoDriver* driver = device->getVideoDriver();
\r
1426 // These make the textures not to show at all
\r
1427 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1428 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1430 //driver->setMinHardwareBufferVertexCount(1);
\r
1432 scene::ISceneManager* smgr = device->getSceneManager();
\r
1435 guiPauseMenu pauseMenu(device, &receiver);
\r
1437 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1438 gui::IGUISkin* skin = guienv->getSkin();
\r
1439 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1441 skin->setFont(font);
\r
1442 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1443 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1444 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1445 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1446 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1447 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1449 const wchar_t *text = L"Loading and connecting...";
\r
1450 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1451 core::dimension2d<u32> textd = font->getDimension(text);
\r
1452 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1453 // Have to add a bit to disable the text from word wrapping
\r
1454 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1455 core::vector2d<s32> textsize(300, textd.Height);
\r
1456 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1458 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1459 text, textrect, false, false);
\r
1460 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1462 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1463 guienv->drawAll();
\r
1464 driver->endScene();
\r
1467 Preload some random textures that are used in threads
\r
1470 g_texturecache.set("torch", driver->getTexture("../data/torch.png"));
\r
1471 g_texturecache.set("torch_on_floor", driver->getTexture("../data/torch_on_floor.png"));
\r
1472 g_texturecache.set("torch_on_ceiling", driver->getTexture("../data/torch_on_ceiling.png"));
\r
1475 Load tile textures
\r
1477 for(s32 i=0; i<TILES_COUNT; i++)
\r
1479 if(g_tile_texture_names[i] == NULL)
\r
1481 std::string name = g_tile_texture_names[i];
\r
1482 std::string filename;
\r
1483 filename += "../data/";
\r
1485 filename += ".png";
\r
1486 g_texturecache.set(name, driver->getTexture(filename.c_str()));
\r
1489 tile_materials_preload(g_texturecache);
\r
1492 Make a scope here for the client so that it gets removed
\r
1493 before the irrlicht device
\r
1497 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1502 SharedPtr<Server> server;
\r
1504 server = new Server("../map", hm_params, map_params);
\r
1505 server->start(port);
\r
1512 Client client(device, playername,
\r
1514 g_viewing_range_nodes,
\r
1515 g_viewing_range_all);
\r
1517 Address connect_address(0,0,0,0, port);
\r
1519 connect_address.Resolve(connect_name);
\r
1521 catch(ResolveError &e)
\r
1523 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1527 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1528 client.connect(connect_address);
\r
1531 while(client.connectedAndInitialized() == false)
\r
1534 if(server != NULL){
\r
1535 server->step(0.1);
\r
1540 catch(con::PeerNotFoundException &e)
\r
1542 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1549 /*scene::ISceneNode* skybox;
\r
1550 skybox = smgr->addSkyBoxSceneNode(
\r
1551 driver->getTexture("../data/skybox2.png"),
\r
1552 driver->getTexture("../data/skybox3.png"),
\r
1553 driver->getTexture("../data/skybox1.png"),
\r
1554 driver->getTexture("../data/skybox1.png"),
\r
1555 driver->getTexture("../data/skybox1.png"),
\r
1556 driver->getTexture("../data/skybox1.png"));*/
\r
1559 Create the camera node
\r
1562 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1563 0, // Camera parent
\r
1564 v3f(BS*100, BS*2, BS*100), // Look from
\r
1565 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1569 if(camera == NULL)
\r
1572 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1574 camera->setFOV(FOV_ANGLE);
\r
1576 // Just so big a value that everything rendered is visible
\r
1577 camera->setFarValue(100000*BS);
\r
1579 f32 camera_yaw = 0; // "right/left"
\r
1580 f32 camera_pitch = 0; // "up/down"
\r
1586 gui_loadingtext->remove();
\r
1588 pauseMenu.setVisible(true);
\r
1591 Add some gui stuff
\r
1594 // First line of debug text
\r
1595 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1597 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1599 // Second line of debug text
\r
1600 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1602 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1605 // At the middle of the screen
\r
1606 // Object infos are shown in this
\r
1607 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1609 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1612 // This is a copy of the inventory that the client's environment has
\r
1613 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1615 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1616 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1619 Some statistics are collected in these
\r
1622 u32 beginscenetime = 0;
\r
1623 u32 scenetime = 0;
\r
1624 u32 endscenetime = 0;
\r
1632 virtual void sendText(std::string text) = 0;
\r
1635 struct TextDestSign : public TextDest
\r
1637 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1639 m_blockpos = blockpos;
\r
1641 m_client = client;
\r
1643 void sendText(std::string text)
\r
1645 dstream<<"Changing text of a sign object: "
\r
1646 <<text<<std::endl;
\r
1647 m_client->sendSignText(m_blockpos, m_id, text);
\r
1655 TextDest *textbuf_dest = NULL;
\r
1657 //gui::IGUIWindow* input_window = NULL;
\r
1658 gui::IGUIStaticText* input_guitext = NULL;
\r
1669 bool first_loop_after_window_activation = true;
\r
1671 // Time is in milliseconds
\r
1672 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1673 // NOTE: Have to call run() between calls of this to update the timer
\r
1674 u32 lasttime = device->getTimer()->getTime();
\r
1676 while(device->run())
\r
1679 Random calculations
\r
1681 v2u32 screensize = driver->getScreenSize();
\r
1682 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1684 // Hilight boxes collected during the loop and displayed
\r
1685 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1688 std::wstring infotext;
\r
1690 //TimeTaker //timer1("//timer1", device);
\r
1692 // Time of frame without fps limit
\r
1696 // not using getRealTime is necessary for wine
\r
1697 u32 time = device->getTimer()->getTime();
\r
1698 if(time > lasttime)
\r
1699 busytime_u32 = time - lasttime;
\r
1702 busytime = busytime_u32 / 1000.0;
\r
1705 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1707 // Absolutelu necessary for wine!
\r
1714 //updateViewingRange(dtime, &client);
\r
1715 updateViewingRange(busytime, &client);
\r
1722 float fps_max = g_settings.getFloat("fps_max");
\r
1723 u32 frametime_min = 1000./fps_max;
\r
1725 if(busytime_u32 < frametime_min)
\r
1727 u32 sleeptime = frametime_min - busytime_u32;
\r
1728 device->sleep(sleeptime);
\r
1732 // Absolutelu necessary for wine!
\r
1736 Time difference calculation
\r
1738 f32 dtime; // in seconds
\r
1740 u32 time = device->getTimer()->getTime();
\r
1741 if(time > lasttime)
\r
1742 dtime = (time - lasttime) / 1000.0;
\r
1748 Time average and jitter calculation
\r
1751 static f32 dtime_avg1 = 0.0;
\r
1752 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1753 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1755 static f32 dtime_jitter1_max_sample = 0.0;
\r
1756 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1758 static f32 jitter1_max = 0.0;
\r
1759 static f32 counter = 0.0;
\r
1760 if(dtime_jitter1 > jitter1_max)
\r
1761 jitter1_max = dtime_jitter1;
\r
1766 dtime_jitter1_max_sample = jitter1_max;
\r
1767 dtime_jitter1_max_fraction
\r
1768 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1769 jitter1_max = 0.0;
\r
1772 Control freetime ratio
\r
1774 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1776 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1777 g_freetime_ratio += 0.01;
\r
1781 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1782 g_freetime_ratio -= 0.01;
\r
1788 Busytime average and jitter calculation
\r
1791 static f32 busytime_avg1 = 0.0;
\r
1792 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1793 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1795 static f32 busytime_jitter1_max_sample = 0.0;
\r
1796 static f32 busytime_jitter1_min_sample = 0.0;
\r
1798 static f32 jitter1_max = 0.0;
\r
1799 static f32 jitter1_min = 0.0;
\r
1800 static f32 counter = 0.0;
\r
1801 if(busytime_jitter1 > jitter1_max)
\r
1802 jitter1_max = busytime_jitter1;
\r
1803 if(busytime_jitter1 < jitter1_min)
\r
1804 jitter1_min = busytime_jitter1;
\r
1806 if(counter > 0.0){
\r
1808 busytime_jitter1_max_sample = jitter1_max;
\r
1809 busytime_jitter1_min_sample = jitter1_min;
\r
1810 jitter1_max = 0.0;
\r
1811 jitter1_min = 0.0;
\r
1816 Debug info for client
\r
1819 static float counter = 0.0;
\r
1824 client.printDebugInfo(std::cout);
\r
1829 Input handler step()
\r
1831 g_input->step(dtime);
\r
1836 /*if(g_esc_pressed)
\r
1842 Player speed control
\r
1845 if(g_game_focused)
\r
1852 bool a_superspeed,
\r
1855 PlayerControl control(
\r
1856 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1857 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1858 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1859 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1860 g_input->isKeyDown(irr::KEY_SPACE),
\r
1861 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1865 client.setPlayerControl(control);
\r
1869 // Set every key to inactive
\r
1870 PlayerControl control;
\r
1871 client.setPlayerControl(control);
\r
1876 Process environment
\r
1880 //TimeTaker timer("client.step(dtime)", device);
\r
1881 client.step(dtime);
\r
1882 //client.step(dtime_avg1);
\r
1885 if(server != NULL)
\r
1887 //TimeTaker timer("server->step(dtime)", device);
\r
1888 server->step(dtime);
\r
1891 v3f player_position = client.getPlayerPosition();
\r
1893 //TimeTaker //timer2("//timer2", device);
\r
1896 Mouse and camera control
\r
1899 if((device->isWindowActive() && g_game_focused && !pauseMenu.isVisible())
\r
1903 device->getCursorControl()->setVisible(false);
\r
1905 if(first_loop_after_window_activation){
\r
1906 //std::cout<<"window active, first loop"<<std::endl;
\r
1907 first_loop_after_window_activation = false;
\r
1910 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1911 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1912 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1913 camera_yaw -= dx*0.2;
\r
1914 camera_pitch += dy*0.2;
\r
1915 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1916 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1918 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1921 device->getCursorControl()->setVisible(true);
\r
1923 //std::cout<<"window inactive"<<std::endl;
\r
1924 first_loop_after_window_activation = true;
\r
1927 camera_yaw = wrapDegrees(camera_yaw);
\r
1928 camera_pitch = wrapDegrees(camera_pitch);
\r
1930 v3f camera_direction = v3f(0,0,1);
\r
1931 camera_direction.rotateYZBy(camera_pitch);
\r
1932 camera_direction.rotateXZBy(camera_yaw);
\r
1934 v3f camera_position =
\r
1935 player_position + v3f(0, BS+BS/2, 0);
\r
1937 camera->setPosition(camera_position);
\r
1938 // *100.0 helps in large map coordinates
\r
1939 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1941 if(FIELD_OF_VIEW_TEST){
\r
1942 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1943 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1946 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1947 //TimeTaker timer("client.updateCamera", device);
\r
1948 client.updateCamera(camera_position, camera_direction);
\r
1952 //TimeTaker //timer3("//timer3", device);
\r
1955 Calculate what block is the crosshair pointing to
\r
1958 //u32 t1 = device->getTimer()->getRealTime();
\r
1960 //f32 d = 4; // max. distance
\r
1961 f32 d = 4; // max. distance
\r
1962 core::line3d<f32> shootline(camera_position,
\r
1963 camera_position + camera_direction * BS * (d+1));
\r
1965 MapBlockObject *selected_object = client.getSelectedObject
\r
1966 (d*BS, camera_position, shootline);
\r
1968 if(selected_object != NULL)
\r
1970 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1972 core::aabbox3d<f32> box_on_map
\r
1973 = selected_object->getSelectionBoxOnMap();
\r
1975 hilightboxes.push_back(box_on_map);
\r
1977 infotext = narrow_to_wide(selected_object->infoText());
\r
1979 if(g_input->getLeftClicked())
\r
1981 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1982 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1983 selected_object->getId(), g_selected_item);
\r
1985 else if(g_input->getRightClicked())
\r
1987 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1989 Check if we want to modify the object ourselves
\r
1991 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1993 dstream<<"Sign object right-clicked"<<std::endl;
\r
1997 input_guitext = guienv->addStaticText(L"",
\r
1998 core::rect<s32>(150,100,350,120),
\r
2000 false, // wordwrap?
\r
2003 input_guitext->setDrawBackground(true);
\r
2007 g_text_buffer = L"ASD LOL 8)";
\r
2008 g_text_buffer_accepted = true;
\r
2012 g_text_buffer = L"";
\r
2013 g_text_buffer_accepted = false;
\r
2016 textbuf_dest = new TextDestSign(
\r
2017 selected_object->getBlock()->getPos(),
\r
2018 selected_object->getId(),
\r
2022 Otherwise pass the event to the server as-is
\r
2026 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
2027 selected_object->getId(), g_selected_item);
\r
2031 else // selected_object == NULL
\r
2035 Find out which node we are pointing at
\r
2038 bool nodefound = false;
\r
2040 v3s16 neighbourpos;
\r
2041 core::aabbox3d<f32> nodefacebox;
\r
2042 f32 mindistance = BS * 1001;
\r
2044 v3s16 pos_i = floatToInt(player_position);
\r
2046 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2050 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2051 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2052 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2053 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2054 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2055 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2057 for(s16 y = ystart; y <= yend; y++)
\r
2058 for(s16 z = zstart; z <= zend; z++)
\r
2059 for(s16 x = xstart; x <= xend; x++)
\r
2064 n = client.getNode(v3s16(x,y,z));
\r
2065 if(content_pointable(n.d) == false)
\r
2068 catch(InvalidPositionException &e)
\r
2074 v3f npf = intToFloat(np);
\r
2079 v3s16(0,0,1), // back
\r
2080 v3s16(0,1,0), // top
\r
2081 v3s16(1,0,0), // right
\r
2082 v3s16(0,0,-1), // front
\r
2083 v3s16(0,-1,0), // bottom
\r
2084 v3s16(-1,0,0), // left
\r
2090 if(n.d == CONTENT_TORCH)
\r
2092 v3s16 dir = unpackDir(n.dir);
\r
2093 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2094 dir_f *= BS/2 - BS/6 - BS/20;
\r
2095 v3f cpf = npf + dir_f;
\r
2096 f32 distance = (cpf - camera_position).getLength();
\r
2098 core::aabbox3d<f32> box;
\r
2101 if(dir == v3s16(0,-1,0))
\r
2103 box = core::aabbox3d<f32>(
\r
2104 npf - v3f(BS/6, BS/2, BS/6),
\r
2105 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2109 else if(dir == v3s16(0,1,0))
\r
2111 box = core::aabbox3d<f32>(
\r
2112 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2113 npf + v3f(BS/6, BS/2, BS/6)
\r
2119 box = core::aabbox3d<f32>(
\r
2120 cpf - v3f(BS/6, BS/3, BS/6),
\r
2121 cpf + v3f(BS/6, BS/3, BS/6)
\r
2125 if(distance < mindistance)
\r
2127 if(box.intersectsWithLine(shootline))
\r
2131 neighbourpos = np;
\r
2132 mindistance = distance;
\r
2133 nodefacebox = box;
\r
2142 for(u16 i=0; i<6; i++)
\r
2144 v3f dir_f = v3f(dirs[i].X,
\r
2145 dirs[i].Y, dirs[i].Z);
\r
2146 v3f centerpoint = npf + dir_f * BS/2;
\r
2148 (centerpoint - camera_position).getLength();
\r
2150 if(distance < mindistance)
\r
2152 core::CMatrix4<f32> m;
\r
2153 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2155 // This is the back face
\r
2156 v3f corners[2] = {
\r
2157 v3f(BS/2, BS/2, BS/2),
\r
2158 v3f(-BS/2, -BS/2, BS/2+d)
\r
2161 for(u16 j=0; j<2; j++)
\r
2163 m.rotateVect(corners[j]);
\r
2164 corners[j] += npf;
\r
2167 core::aabbox3d<f32> facebox(corners[0]);
\r
2168 facebox.addInternalPoint(corners[1]);
\r
2170 if(facebox.intersectsWithLine(shootline))
\r
2174 neighbourpos = np + dirs[i];
\r
2175 mindistance = distance;
\r
2176 nodefacebox = facebox;
\r
2178 } // if distance < mindistance
\r
2180 } // regular block
\r
2183 /*static v3s16 oldnodepos;
\r
2184 static bool oldnodefound = false;*/
\r
2188 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
2189 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2190 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
2192 static v3s16 nodepos_old(-1,-1,-1);
\r
2193 if(nodepos != nodepos_old){
\r
2194 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2195 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2198 hilightboxes.push_back(nodefacebox);
\r
2200 //if(g_input->getLeftClicked())
\r
2201 if(g_input->getLeftClicked() ||
\r
2202 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2204 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2205 client.pressGround(0, nodepos, neighbourpos, g_selected_item);
\r
2207 if(g_input->getRightClicked())
\r
2208 /*if(g_input->getRightClicked() ||
\r
2209 (g_input->getRightState() && nodepos != nodepos_old))*/
\r
2211 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2212 client.pressGround(1, nodepos, neighbourpos, g_selected_item);
\r
2215 nodepos_old = nodepos;
\r
2218 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
2219 //positiontextgui->setText(L"");
\r
2222 /*oldnodefound = nodefound;
\r
2223 oldnodepos = nodepos;*/
\r
2225 } // selected_object == NULL
\r
2227 g_input->resetLeftClicked();
\r
2228 g_input->resetRightClicked();
\r
2230 if(g_input->getLeftReleased())
\r
2232 std::cout<<DTIME<<"Left released"<<std::endl;
\r
2233 client.stopDigging();
\r
2235 if(g_input->getRightReleased())
\r
2237 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2241 g_input->resetLeftReleased();
\r
2242 g_input->resetRightReleased();
\r
2245 Calculate stuff for drawing
\r
2248 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2250 // Background color is choosen based on whether the player is
\r
2251 // much beyond the initial ground level
\r
2252 /*video::SColor bgcolor;
\r
2253 v3s16 p0 = Map::floatToInt(player_position);
\r
2254 // Does this make short random delays?
\r
2255 // NOTE: no need for this, sky doesn't show underground with
\r
2257 bool is_underground = client.isNodeUnderground(p0);
\r
2258 //bool is_underground = false;
\r
2259 if(is_underground == false)
\r
2260 bgcolor = video::SColor(255,90,140,200);
\r
2262 bgcolor = video::SColor(255,0,0,0);*/
\r
2264 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2265 //video::SColor bgcolor = skycolor;
\r
2267 //s32 daynight_i = client.getDayNightIndex();
\r
2268 //video::SColor bgcolor = skycolor[daynight_i];
\r
2270 u32 daynight_ratio = client.getDayNightRatio();
\r
2271 video::SColor bgcolor = video::SColor(
\r
2273 skycolor.getRed() * daynight_ratio / 1000,
\r
2274 skycolor.getGreen() * daynight_ratio / 1000,
\r
2275 skycolor.getBlue() * daynight_ratio / 1000);
\r
2281 if(g_settings.getBool("enable_fog") == true)
\r
2283 f32 range = g_viewing_range_nodes * BS;
\r
2284 if(g_viewing_range_all)
\r
2285 range = 100000*BS;
\r
2289 video::EFT_FOG_LINEAR,
\r
2293 false, // pixel fog
\r
2294 false // range fog
\r
2300 Update gui stuff (0ms)
\r
2303 //TimeTaker guiupdatetimer("Gui updating", device);
\r
2306 wchar_t temptext[150];
\r
2308 static float drawtime_avg = 0;
\r
2309 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2310 static float beginscenetime_avg = 0;
\r
2311 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2312 static float scenetime_avg = 0;
\r
2313 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2314 static float endscenetime_avg = 0;
\r
2315 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2317 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2319 L", R: range_all=%i"
\r
2321 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2323 g_viewing_range_all,
\r
2325 beginscenetime_avg,
\r
2330 guitext->setText(temptext);
\r
2334 wchar_t temptext[150];
\r
2335 swprintf(temptext, 150,
\r
2336 L"(% .1f, % .1f, % .1f)"
\r
2337 L" (% .3f < btime_jitter < % .3f"
\r
2338 L", dtime_jitter = % .1f %%)",
\r
2339 player_position.X/BS,
\r
2340 player_position.Y/BS,
\r
2341 player_position.Z/BS,
\r
2342 busytime_jitter1_min_sample,
\r
2343 busytime_jitter1_max_sample,
\r
2344 dtime_jitter1_max_fraction * 100.0
\r
2347 guitext2->setText(temptext);
\r
2351 /*wchar_t temptext[100];
\r
2352 swprintf(temptext, 100,
\r
2353 SWPRINTF_CHARSTRING,
\r
2354 infotext.substr(0,99).c_str()
\r
2357 guitext_info->setText(temptext);*/
\r
2359 guitext_info->setText(infotext.c_str());
\r
2366 static u16 old_selected_item = 65535;
\r
2367 if(client.getLocalInventoryUpdated()
\r
2368 || g_selected_item != old_selected_item)
\r
2370 old_selected_item = g_selected_item;
\r
2371 //std::cout<<"Updating local inventory"<<std::endl;
\r
2372 client.getLocalInventory(local_inventory);
\r
2373 quick_inventory->setSelection(g_selected_item);
\r
2374 quick_inventory->update();
\r
2377 if(input_guitext != NULL)
\r
2379 /*wchar_t temptext[100];
\r
2380 swprintf(temptext, 100,
\r
2381 SWPRINTF_CHARSTRING,
\r
2382 g_text_buffer.substr(0,99).c_str()
\r
2384 input_guitext->setText(g_text_buffer.c_str());
\r
2390 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2392 input_guitext->remove();
\r
2393 input_guitext = NULL;
\r
2395 if(textbuf_dest != NULL)
\r
2397 std::string text = wide_to_narrow(g_text_buffer);
\r
2398 dstream<<"Sending text: "<<text<<std::endl;
\r
2399 textbuf_dest->sendText(text);
\r
2400 delete textbuf_dest;
\r
2401 textbuf_dest = NULL;
\r
2407 //guiupdatetimer.stop();
\r
2413 TimeTaker drawtimer("Drawing", device);
\r
2417 TimeTaker timer("beginScene", device);
\r
2418 driver->beginScene(true, true, bgcolor);
\r
2419 //driver->beginScene(false, true, bgcolor);
\r
2420 beginscenetime = timer.stop(true);
\r
2425 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2428 TimeTaker timer("smgr", device);
\r
2430 scenetime = timer.stop(true);
\r
2434 //TimeTaker timer9("auxiliary drawings", device);
\r
2437 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2438 displaycenter + core::vector2d<s32>(10,0),
\r
2439 video::SColor(255,255,255,255));
\r
2440 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2441 displaycenter + core::vector2d<s32>(0,10),
\r
2442 video::SColor(255,255,255,255));
\r
2445 //TimeTaker //timer10("//timer10", device);
\r
2447 video::SMaterial m;
\r
2449 m.Lighting = false;
\r
2450 driver->setMaterial(m);
\r
2452 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2454 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2455 i != hilightboxes.end(); i++)
\r
2457 /*std::cout<<"hilightbox min="
\r
2458 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2460 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2462 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2468 //TimeTaker //timer11("//timer11", device);
\r
2474 guienv->drawAll();
\r
2478 TimeTaker timer("endScene", device);
\r
2479 driver->endScene();
\r
2480 endscenetime = timer.stop(true);
\r
2483 drawtime = drawtimer.stop(true);
\r
2489 static s16 lastFPS = 0;
\r
2490 //u16 fps = driver->getFPS();
\r
2491 u16 fps = (1.0/dtime_avg1);
\r
2493 if (lastFPS != fps)
\r
2495 core::stringw str = L"Minetest [";
\r
2496 str += driver->getName();
\r
2500 device->setWindowCaption(str.c_str());
\r
2506 device->yield();*/
\r
2509 delete quick_inventory;
\r
2511 } // client is deleted at this point
\r
2516 In the end, delete the Irrlicht device.
\r
2521 Update configuration file
\r
2523 if(configpath != "")
\r
2525 g_settings.updateConfigFile(configpath.c_str());
\r
2529 catch(con::PeerNotFoundException &e)
\r
2531 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2533 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2535 This is what has to be done in every thread to get suitable debug info
\r
2537 catch(std::exception &e)
\r
2539 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2540 <<e.what()<<std::endl;
\r
2545 debugstreams_deinit();
\r