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 SUGGESTION: add a second lighting value to the MS nibble of param of
\r
31 air to tell how bright the air node is when there is no sunlight.
\r
32 When day changes to night, these two values can be interpolated.
\r
34 TODO: Fix address to be ipv6 compatible
\r
36 TODO: ESC Pause mode in which the cursor is not kept at the center of window.
\r
37 TODO: Stop player if focus of window is taken away (go to pause mode)
\r
38 TODO: Optimize and fix makeFastFace or whatever it's called
\r
39 - Face calculation is the source of CPU usage on the client
\r
40 SUGGESTION: The client will calculate and send lighting changes and
\r
41 the server will randomly check some of them and kick the client out
\r
42 if it fails to calculate them right.
\r
43 - Actually, it could just start ignoring them and calculate them
\r
45 SUGGESTION: Combine MapBlock's face caches to so big pieces that VBO
\r
47 - That is >500 vertices
\r
49 TODO: Better dungeons
\r
50 TODO: There should be very slight natural caves also, starting from
\r
51 only a straightened-up cliff
\r
53 TODO: Changing of block with mouse wheel or something
\r
58 - One single map container with ids as keys
\r
61 TODO: - Keep track of the place of the mob in the last few hundreth's
\r
62 of a second - then, if a player hits it, take the value that is
\r
63 avg_rtt/2 before the moment the packet is received.
\r
66 SUGGESTION: Modify client to calculate single changes asynchronously
\r
68 TODO: Moving players more smoothly. Calculate moving animation
\r
69 in a way that doesn't make the player jump to the right place
\r
70 immediately when the server sends a new position
\r
72 TODO: There are some lighting-related todos and fixmes in
\r
73 ServerMap::emergeBlock
\r
75 FIXME: When a new sector is generated, it may change the ground level
\r
76 of it's and it's neighbors border that two blocks that are
\r
77 above and below each other and that are generated before and
\r
78 after the sector heightmap generation (order doesn't matter),
\r
79 can have a small gap between each other at the border.
\r
80 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
81 using for UnlimitedHeightmap? (getting all neighbors
\r
84 TODO: Proper handling of spawning place (try to find something that
\r
85 is not in the middle of an ocean (some land to stand on at
\r
86 least) and save it in map config.
\r
87 SUGG: Set server to automatically find a good spawning place in some
\r
88 place where there is water and land.
\r
89 - Map to have a getWalkableNear(p)
\r
90 - Is this a good idea? It's part of the game to find a good place.
\r
92 TODO: Transfer more blocks in a single packet
\r
93 SUGG: A blockdata combiner class, to which blocks are added and at
\r
94 destruction it sends all the stuff in as few packets as possible.
\r
96 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
97 SUGG: Fetch stuff mainly from the viewing direction
\r
99 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
100 - This enables saving many packets and making a faster connection
\r
101 - This also enables server to check if client has received the
\r
102 most recent block sent, for example.
\r
103 TODO: Add a sane bandwidth throttling system to Connection
\r
105 SUGG: More fine-grained control of client's dumping of blocks from
\r
107 - ...What does this mean in the first place?
\r
109 TODO: Make the amount of blocks sending to client and the total
\r
110 amount of blocks dynamically limited. Transferring blocks is the
\r
111 main network eater of this system, so it is the one that has
\r
112 to be throttled so that RTTs stay low.
\r
114 TODO: Server to load starting inventory from disk
\r
116 TODO: PLayers to only be hidden when the client quits.
\r
117 TODO: - Players to be saved on disk, with inventory
\r
118 TODO: Players to be saved as text in map/players/<name>
\r
120 SUGG: A map editing mode (similar to dedicated server mode)
\r
122 TODO: Make fetching sector's blocks more efficient when rendering
\r
123 sectors that have very large amounts of blocks (on client)
\r
125 TODO: Make the video backend selectable
\r
127 Block object server side:
\r
128 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
129 - For all blocks in the buffer, objects are stepped(). This
\r
130 means they are active.
\r
131 - TODO: A global active buffer is needed for the server
\r
132 - TODO: All blocks going in and out of the buffer are recorded.
\r
133 - TODO: For outgoing blocks, a timestamp is written.
\r
134 - TODO: For incoming blocks, the time difference is calculated and
\r
135 objects are stepped according to it.
\r
136 TODO: A timestamp to blocks
\r
138 SUGG: Add a time value to the param of footstepped grass and check it
\r
139 against a global timer when a block is accessed, to make old
\r
142 TODO: Add config parameters for server's sending and generating distance
\r
144 TODO: Copy the text of the last picked sign to inventory in creative
\r
147 TODO: Untie client network operations from framerate
\r
148 - Needs some input queues or something
\r
150 SUGG: Make a copy of close-range environment on client for showing
\r
151 on screen, with minimal mutexes to slow down the main loop
\r
153 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
154 it by sending more stuff in a single packet.
\r
155 - Add a packet queue to RemoteClient, from which packets will be
\r
156 combined with object data packets
\r
157 - This is not exactly trivial: the object data packets are
\r
158 sometimes very big by themselves
\r
160 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
161 - This will allow saving ages of rats on disk but not sending
\r
164 TODO: Get rid of GotSplitPacketException
\r
166 SUGG: Implement lighting using VoxelManipulator
\r
167 - Would it be significantly faster?
\r
169 TODO: Check what goes wrong with caching map to disk (Kray)
\r
171 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
173 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
174 - Does their position get saved to a border value or something?
\r
175 - Does this happen anymore?
\r
177 SUGG: MovingObject::move and Player::move are basically the same.
\r
180 SUGG: Implement a "Fast check queue" (a queue with a map for checking
\r
181 if something is already in it)
\r
182 - Use it in active block queue in water flowing
\r
184 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
185 need an additional metadata field for the texts
\r
188 ======================================================================
\r
191 ======================================================================
\r
196 Setting this to 1 enables a special camera mode that forces
\r
197 the renderers to think that the camera statically points from
\r
198 the starting place to a static direction.
\r
200 This allows one to move around with the player and see what
\r
201 is actually drawn behind solid things and behind the player.
\r
203 #define FIELD_OF_VIEW_TEST 0
\r
205 #ifdef UNITTEST_DISABLE
\r
207 #pragma message ("Disabling unit tests")
\r
209 #warning "Disabling unit tests"
\r
211 // Disable unit tests
\r
212 #define ENABLE_TESTS 0
\r
214 // Enable unit tests
\r
215 #define ENABLE_TESTS 1
\r
219 #pragma comment(lib, "Irrlicht.lib")
\r
220 #pragma comment(lib, "jthread.lib")
\r
221 #pragma comment(lib, "zlibwapi.lib")
\r
222 // This would get rid of the console window
\r
223 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
227 #define WIN32_LEAN_AND_MEAN
\r
228 #include <windows.h>
\r
229 #define sleep_ms(x) Sleep(x)
\r
231 #include <unistd.h>
\r
232 #define sleep_ms(x) usleep(x*1000)
\r
235 #include <iostream>
\r
238 #include <jmutexautolock.h>
\r
239 #include <locale.h>
\r
240 #include "common_irrlicht.h"
\r
243 #include "player.h"
\r
246 #include "environment.h"
\r
247 #include "server.h"
\r
248 #include "client.h"
\r
249 #include "serialization.h"
\r
250 #include "constants.h"
\r
251 #include "strfnd.h"
\r
252 #include "porting.h"
\r
253 #include "guiPauseMenu.h"
\r
255 IrrlichtDevice *g_device = NULL;
\r
257 /*const char *g_content_filenames[MATERIALS_COUNT] =
\r
259 "../data/stone.png",
\r
260 "../data/grass.png",
\r
261 "../data/water.png",
\r
262 "../data/torch_on_floor.png",
\r
263 "../data/tree.png",
\r
264 "../data/leaves.png",
\r
265 "../data/grass_footsteps.png",
\r
266 "../data/mese.png",
\r
268 "../data/water.png", // CONTENT_OCEAN
\r
272 video::SMaterial g_materials[MATERIALS_COUNT];*/
\r
275 TextureCache g_texturecache;
\r
278 // All range-related stuff below is locked behind this
\r
279 JMutex g_range_mutex;
\r
281 // Blocks are viewed in this range from the player
\r
282 s16 g_viewing_range_nodes = 60;
\r
283 //s16 g_viewing_range_nodes = 0;
\r
285 // This is updated by the client's fetchBlocks routine
\r
286 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
288 // If true, the preceding value has no meaning and all blocks
\r
289 // already existing in memory are drawn
\r
290 bool g_viewing_range_all = false;
\r
292 // This is the freetime ratio imposed by the dynamic viewing
\r
293 // range changing code.
\r
294 // It is controlled by the main loop to the smallest value that
\r
295 // inhibits glitches (dtime jitter) in the main loop.
\r
296 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
301 These are loaded from the config file.
\r
304 Settings g_settings;
\r
306 // Sets default settings
\r
307 void set_default_settings()
\r
310 g_settings.setDefault("wanted_fps", "30");
\r
311 g_settings.setDefault("fps_max", "60");
\r
312 g_settings.setDefault("viewing_range_nodes_max", "300");
\r
313 g_settings.setDefault("viewing_range_nodes_min", "35");
\r
314 g_settings.setDefault("screenW", "");
\r
315 g_settings.setDefault("screenH", "");
\r
316 g_settings.setDefault("host_game", "");
\r
317 g_settings.setDefault("port", "");
\r
318 g_settings.setDefault("address", "");
\r
319 g_settings.setDefault("name", "");
\r
320 g_settings.setDefault("random_input", "false");
\r
321 g_settings.setDefault("client_delete_unused_sectors_timeout", "1200");
\r
322 g_settings.setDefault("max_block_send_distance", "8");
\r
323 g_settings.setDefault("max_block_generate_distance", "6");
\r
326 g_settings.setDefault("creative_mode", "false");
\r
327 g_settings.setDefault("heightmap_blocksize", "32");
\r
328 g_settings.setDefault("height_randmax", "constant 50.0");
\r
329 g_settings.setDefault("height_randfactor", "constant 0.6");
\r
330 g_settings.setDefault("height_base", "linear 0 0 0");
\r
331 g_settings.setDefault("plants_amount", "1.0");
\r
332 g_settings.setDefault("ravines_amount", "1.0");
\r
333 g_settings.setDefault("objectdata_interval", "0.2");
\r
334 g_settings.setDefault("active_object_range", "2");
\r
335 g_settings.setDefault("max_simultaneous_block_sends_per_client", "1");
\r
336 g_settings.setDefault("max_simultaneous_block_sends_server_total", "4");
\r
337 g_settings.setDefault("disable_water_climb", "true");
\r
338 g_settings.setDefault("endless_water", "true");
\r
345 //u16 g_selected_material = 0;
\r
346 u16 g_selected_item = 0;
\r
348 bool g_esc_pressed = false;
\r
350 std::wstring g_text_buffer;
\r
351 bool g_text_buffer_accepted = false;
\r
353 // When true, the mouse and keyboard are grabbed
\r
354 bool g_game_focused = true;
\r
361 std::ostream *dout_con_ptr = &dummyout;
\r
362 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
363 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
364 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
365 //std::ostream *dout_con_ptr = &dstream;
\r
366 //std::ostream *derr_con_ptr = &dstream;
\r
369 std::ostream *dout_server_ptr = &dstream;
\r
370 std::ostream *derr_server_ptr = &dstream;
\r
373 std::ostream *dout_client_ptr = &dstream;
\r
374 std::ostream *derr_client_ptr = &dstream;
\r
381 JMutex g_timestamp_mutex;
\r
382 //std::string g_timestamp;
\r
384 std::string getTimestamp()
\r
386 if(g_timestamp_mutex.IsInitialized()==false)
\r
388 JMutexAutoLock lock(g_timestamp_mutex);
\r
389 //return g_timestamp;
\r
390 time_t t = time(NULL);
\r
391 struct tm *tm = localtime(&t);
\r
393 strftime(cs, 20, "%H:%M:%S", tm);
\r
397 class MyEventReceiver : public IEventReceiver
\r
400 // This is the one method that we have to implement
\r
401 virtual bool OnEvent(const SEvent& event)
\r
403 // Remember whether each key is down or up
\r
404 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
406 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
408 if(event.KeyInput.PressedDown)
\r
410 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
411 if(g_game_focused == false)
\r
413 s16 key = event.KeyInput.Key;
\r
414 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
416 g_text_buffer_accepted = true;
\r
418 else if(key == irr::KEY_BACK)
\r
420 if(g_text_buffer.size() > 0)
\r
421 g_text_buffer = g_text_buffer.substr
\r
422 (0, g_text_buffer.size()-1);
\r
426 wchar_t wc = event.KeyInput.Char;
\r
428 g_text_buffer += wc;
\r
432 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
434 //TODO: Not used anymore?
\r
435 if(g_game_focused == true)
\r
437 dstream<<DTIME<<"ESC pressed"<<std::endl;
\r
438 g_esc_pressed = true;
\r
442 // Material selection
\r
443 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
445 if(g_game_focused == true)
\r
447 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
450 g_selected_item = 0;
\r
451 dstream<<DTIME<<"Selected item: "
\r
452 <<g_selected_item<<std::endl;
\r
456 // Viewing range selection
\r
457 if(event.KeyInput.Key == irr::KEY_KEY_R
\r
460 JMutexAutoLock lock(g_range_mutex);
\r
461 if(g_viewing_range_all)
\r
463 g_viewing_range_all = false;
\r
464 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
468 g_viewing_range_all = true;
\r
469 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
473 // Print debug stacks
\r
474 if(event.KeyInput.Key == irr::KEY_KEY_P
\r
477 dstream<<"-----------------------------------------"
\r
479 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
480 dstream<<"-----------------------------------------"
\r
482 debug_stacks_print();
\r
487 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\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_MOUSE_WHEEL)
\r
499 /*dstream<<"event.MouseInput.Wheel="
\r
500 <<event.MouseInput.Wheel<<std::endl;*/
\r
501 if(event.MouseInput.Wheel < 0)
\r
503 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
506 g_selected_item = 0;
\r
508 else if(event.MouseInput.Wheel > 0)
\r
510 if(g_selected_item > 0)
\r
513 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
521 // This is used to check whether a key is being held down
\r
522 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
524 return keyIsDown[keyCode];
\r
529 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
530 keyIsDown[i] = false;
\r
531 leftclicked = false;
\r
532 rightclicked = false;
\r
538 // We use this array to store the current state of each key
\r
539 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
550 virtual ~InputHandler()
\r
553 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
554 virtual v2s32 getMousePos() = 0;
\r
555 virtual void setMousePos(s32 x, s32 y) = 0;
\r
556 virtual bool getLeftClicked() = 0;
\r
557 virtual bool getRightClicked() = 0;
\r
558 virtual void resetLeftClicked() = 0;
\r
559 virtual void resetRightClicked() = 0;
\r
561 virtual void step(float dtime) {};
\r
563 virtual void clear() {};
\r
566 InputHandler *g_input = NULL;
\r
571 g_game_focused = true;
\r
576 g_game_focused = false;
\r
579 class RealInputHandler : public InputHandler
\r
582 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
584 m_receiver(receiver)
\r
587 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
589 return m_receiver->IsKeyDown(keyCode);
\r
591 virtual v2s32 getMousePos()
\r
593 return m_device->getCursorControl()->getPosition();
\r
595 virtual void setMousePos(s32 x, s32 y)
\r
597 m_device->getCursorControl()->setPosition(x, y);
\r
600 virtual bool getLeftClicked()
\r
602 if(g_game_focused == false)
\r
604 return m_receiver->leftclicked;
\r
606 virtual bool getRightClicked()
\r
608 if(g_game_focused == false)
\r
610 return m_receiver->rightclicked;
\r
612 virtual void resetLeftClicked()
\r
614 m_receiver->leftclicked = false;
\r
616 virtual void resetRightClicked()
\r
618 m_receiver->rightclicked = false;
\r
623 resetRightClicked();
\r
624 resetLeftClicked();
\r
627 IrrlichtDevice *m_device;
\r
628 MyEventReceiver *m_receiver;
\r
631 class RandomInputHandler : public InputHandler
\r
634 RandomInputHandler()
\r
636 leftclicked = false;
\r
637 rightclicked = false;
\r
638 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
639 keydown[i] = false;
\r
641 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
643 return keydown[keyCode];
\r
645 virtual v2s32 getMousePos()
\r
649 virtual void setMousePos(s32 x, s32 y)
\r
651 mousepos = v2s32(x,y);
\r
654 virtual bool getLeftClicked()
\r
656 return leftclicked;
\r
658 virtual bool getRightClicked()
\r
660 return rightclicked;
\r
662 virtual void resetLeftClicked()
\r
664 leftclicked = false;
\r
666 virtual void resetRightClicked()
\r
668 rightclicked = false;
\r
671 virtual void step(float dtime)
\r
674 static float counter1 = 0;
\r
678 counter1 = 0.1*Rand(1,10);
\r
679 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
680 g_selected_material++;
\r
682 g_selected_material = 0;*/
\r
683 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
686 g_selected_item = 0;
\r
690 static float counter1 = 0;
\r
694 counter1 = 0.1*Rand(1, 40);
\r
695 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
699 static float counter1 = 0;
\r
703 counter1 = 0.1*Rand(1, 40);
\r
704 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
708 static float counter1 = 0;
\r
712 counter1 = 0.1*Rand(1, 40);
\r
713 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
717 static float counter1 = 0;
\r
721 counter1 = 0.1*Rand(1, 40);
\r
722 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
726 static float counter1 = 0;
\r
730 counter1 = 0.1*Rand(1, 20);
\r
731 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
735 static float counter1 = 0;
\r
739 counter1 = 0.1*Rand(1, 30);
\r
740 leftclicked = true;
\r
744 static float counter1 = 0;
\r
748 counter1 = 0.1*Rand(1, 20);
\r
749 rightclicked = true;
\r
752 mousepos += mousespeed;
\r
755 s32 Rand(s32 min, s32 max)
\r
757 return (rand()%(max-min+1))+min;
\r
760 bool keydown[KEY_KEY_CODES_COUNT];
\r
767 void updateViewingRange(f32 frametime, Client *client)
\r
769 // Range_all messes up frametime_avg
\r
770 if(g_viewing_range_all == true)
\r
773 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
775 // Initialize to the target value
\r
776 static float frametime_avg = 1.0/wanted_fps;
\r
777 frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
779 static f32 counter = 0;
\r
781 counter -= frametime;
\r
784 //counter = 1.0; //seconds
\r
785 counter = 0.5; //seconds
\r
787 //float freetime_ratio = 0.2;
\r
788 //float freetime_ratio = 0.4;
\r
789 float freetime_ratio = FREETIME_RATIO;
\r
791 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
793 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
795 static bool fraction_is_good = false;
\r
797 float fraction_good_threshold = 0.1;
\r
798 //float fraction_bad_threshold = 0.25;
\r
799 float fraction_bad_threshold = 0.1;
\r
800 float fraction_limit;
\r
801 // Use high limit if fraction is good AND the fraction would
\r
802 // lower the range. We want to keep the range fairly high.
\r
803 if(fraction_is_good && fraction > 1.0)
\r
804 fraction_limit = fraction_bad_threshold;
\r
806 fraction_limit = fraction_good_threshold;
\r
808 if(fabs(fraction - 1.0) < fraction_limit)
\r
810 fraction_is_good = true;
\r
815 fraction_is_good = false;
\r
818 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
819 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
820 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
821 <<" faction = "<<fraction<<std::endl;*/
\r
823 JMutexAutoLock lock(g_range_mutex);
\r
825 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
826 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
828 s16 n = (float)g_viewing_range_nodes / fraction;
\r
829 if(n < viewing_range_nodes_min)
\r
830 n = viewing_range_nodes_min;
\r
831 if(n > viewing_range_nodes_max)
\r
832 n = viewing_range_nodes_max;
\r
834 bool can_change = true;
\r
836 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
837 can_change = false;
\r
840 g_viewing_range_nodes = n;
\r
842 /*dstream<<"g_viewing_range_nodes = "
\r
843 <<g_viewing_range_nodes<<std::endl;*/
\r
846 class GUIQuickInventory : public IEventReceiver
\r
850 gui::IGUIEnvironment* env,
\r
851 gui::IGUIElement* parent,
\r
854 Inventory *inventory):
\r
855 m_itemcount(itemcount),
\r
856 m_inventory(inventory)
\r
858 core::rect<s32> imgsize(0,0,48,48);
\r
859 core::rect<s32> textsize(0,0,48,16);
\r
860 v2s32 spacing(0, 64);
\r
861 for(s32 i=0; i<m_itemcount; i++)
\r
863 m_images.push_back(env->addImage(
\r
864 imgsize + pos + spacing*i
\r
866 m_images[i]->setScaleImage(true);
\r
867 m_texts.push_back(env->addStaticText(
\r
869 textsize + pos + spacing*i,
\r
872 m_texts[i]->setBackgroundColor(
\r
873 video::SColor(128,0,0,0));
\r
874 m_texts[i]->setTextAlignment(
\r
876 gui::EGUIA_UPPERLEFT);
\r
880 virtual bool OnEvent(const SEvent& event)
\r
885 void setSelection(s32 i)
\r
894 start = m_selection - m_itemcount / 2;
\r
896 for(s32 i=0; i<m_itemcount; i++)
\r
900 if(j > (s32)m_inventory->getSize() - 1)
\r
901 j -= m_inventory->getSize();
\r
903 j += m_inventory->getSize();
\r
905 InventoryItem *item = m_inventory->getItem(j);
\r
909 m_images[i]->setImage(NULL);
\r
912 if(m_selection == j)
\r
913 swprintf(t, 10, L"<-");
\r
915 swprintf(t, 10, L"");
\r
916 m_texts[i]->setText(t);
\r
918 // The next ifs will segfault with a NULL pointer
\r
923 m_images[i]->setImage(item->getImage());
\r
926 if(m_selection == j)
\r
927 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
929 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
930 m_texts[i]->setText(t);
\r
936 core::array<gui::IGUIStaticText*> m_texts;
\r
937 core::array<gui::IGUIImage*> m_images;
\r
938 Inventory *m_inventory;
\r
942 int main(int argc, char *argv[])
\r
945 Low-level initialization
\r
948 bool disable_stderr = false;
\r
950 disable_stderr = true;
\r
953 // Initialize debug streams
\r
954 debugstreams_init(disable_stderr, DEBUGFILE);
\r
955 // Initialize debug stacks
\r
956 debug_stacks_init();
\r
958 DSTACK(__FUNCTION_NAME);
\r
968 core::map<std::string, ValueSpec> allowed_options;
\r
969 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
970 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
971 "Run server directly"));
\r
972 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
973 "Load configuration from specified file"));
\r
974 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
978 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
980 if(ret == false || cmd_args.getFlag("help"))
\r
982 dstream<<"Allowed options:"<<std::endl;
\r
983 for(core::map<std::string, ValueSpec>::Iterator
\r
984 i = allowed_options.getIterator();
\r
985 i.atEnd() == false; i++)
\r
987 dstream<<" --"<<i.getNode()->getKey();
\r
988 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
993 dstream<<" <value>";
\r
995 dstream<<std::endl;
\r
997 if(i.getNode()->getValue().help != NULL)
\r
999 dstream<<" "<<i.getNode()->getValue().help
\r
1004 return cmd_args.getFlag("help") ? 0 : 1;
\r
1009 Basic initialization
\r
1012 // Initialize default settings
\r
1013 set_default_settings();
\r
1015 // Print startup message
\r
1016 dstream<<DTIME<<"minetest-c55"
\r
1017 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1018 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1021 // Set locale. This is for forcing '.' as the decimal point.
\r
1022 std::locale::global(std::locale("C"));
\r
1023 // This enables printing all characters in bitmap font
\r
1024 setlocale(LC_CTYPE, "en_US");
\r
1026 // Initialize sockets
\r
1028 atexit(sockets_cleanup);
\r
1030 // Initialize timestamp mutex
\r
1031 g_timestamp_mutex.Init();
\r
1049 // Path of configuration file in use
\r
1050 std::string configpath = "";
\r
1052 if(cmd_args.exists("config"))
\r
1054 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1057 dstream<<"Could not read configuration from \""
\r
1058 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1061 configpath = cmd_args.get("config");
\r
1065 const char *filenames[2] =
\r
1067 "../minetest.conf",
\r
1068 "../../minetest.conf"
\r
1071 for(u32 i=0; i<2; i++)
\r
1073 bool r = g_settings.readConfigFile(filenames[i]);
\r
1076 configpath = filenames[i];
\r
1082 // Initialize random seed
\r
1085 g_range_mutex.Init();
\r
1086 assert(g_range_mutex.IsInitialized());
\r
1088 // Read map parameters from settings
\r
1090 HMParams hm_params;
\r
1091 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1092 hm_params.randmax = g_settings.get("height_randmax");
\r
1093 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1094 hm_params.base = g_settings.get("height_base");
\r
1096 MapParams map_params;
\r
1097 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1098 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1104 std::cout<<std::endl<<std::endl;
\r
1107 <<" .__ __ __ "<<std::endl
\r
1108 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1109 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1110 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1111 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1112 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1114 <<"Now with more waterish water!"
\r
1117 std::cout<<std::endl;
\r
1118 char templine[100];
\r
1122 if(cmd_args.exists("port"))
\r
1124 port = cmd_args.getU16("port");
\r
1128 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1129 std::cout<<"-> "<<port<<std::endl;
\r
1132 if(cmd_args.getFlag("server"))
\r
1134 DSTACK("Dedicated server branch");
\r
1136 std::cout<<std::endl;
\r
1137 std::cout<<"========================"<<std::endl;
\r
1138 std::cout<<"Running dedicated server"<<std::endl;
\r
1139 std::cout<<"========================"<<std::endl;
\r
1140 std::cout<<std::endl;
\r
1142 Server server("../map", hm_params, map_params);
\r
1143 server.start(port);
\r
1147 // This is kind of a hack but can be done like this
\r
1148 // because server.step() is very light
\r
1150 server.step(0.030);
\r
1152 static int counter = 0;
\r
1158 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1159 core::list<PlayerInfo>::Iterator i;
\r
1160 static u32 sum_old = 0;
\r
1161 u32 sum = PIChecksum(list);
\r
1162 if(sum != sum_old)
\r
1164 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1165 for(i=list.begin(); i!=list.end(); i++)
\r
1167 i->PrintLine(&std::cout);
\r
1177 bool hosting = false;
\r
1178 char connect_name[100] = "";
\r
1180 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1181 if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1183 std::cout<<g_settings.get("address")<<std::endl;
\r
1184 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1188 std::cin.getline(connect_name, 100);
\r
1191 if(connect_name[0] == 0){
\r
1192 snprintf(connect_name, 100, "127.0.0.1");
\r
1197 std::cout<<"-> hosting"<<std::endl;
\r
1199 std::cout<<"-> "<<connect_name<<std::endl;
\r
1201 char playername[PLAYERNAME_SIZE] = "";
\r
1202 if(g_settings.get("name") != "")
\r
1204 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1208 std::cout<<"Name of player: ";
\r
1209 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1211 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1214 Resolution selection
\r
1219 bool fullscreen = false;
\r
1221 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1223 screenW = atoi(g_settings.get("screenW").c_str());
\r
1224 screenH = atoi(g_settings.get("screenH").c_str());
\r
1228 u16 resolutions[][3] = {
\r
1229 //W, H, fullscreen
\r
1240 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1242 for(u16 i=0; i<res_count; i++)
\r
1244 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1245 <<resolutions[i][1];
\r
1246 if(resolutions[i][2])
\r
1247 std::cout<<" fullscreen"<<std::endl;
\r
1249 std::cout<<" windowed"<<std::endl;
\r
1251 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1252 std::cin.getline(templine, 100);
\r
1255 if(templine[0] == 0)
\r
1258 r0 = atoi(templine);
\r
1260 if(r0 > res_count || r0 == 0)
\r
1266 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1267 <<resolutions[i][1];
\r
1268 if(resolutions[i][2])
\r
1269 std::cout<<" fullscreen"<<std::endl;
\r
1271 std::cout<<" windowed"<<std::endl;
\r
1274 screenW = resolutions[r0-1][0];
\r
1275 screenH = resolutions[r0-1][1];
\r
1276 fullscreen = resolutions[r0-1][2];
\r
1281 MyEventReceiver receiver;
\r
1283 video::E_DRIVER_TYPE driverType;
\r
1286 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1287 driverType = video::EDT_OPENGL;
\r
1289 driverType = video::EDT_OPENGL;
\r
1292 // create device and exit if creation failed
\r
1294 IrrlichtDevice *device;
\r
1295 device = createDevice(driverType,
\r
1296 core::dimension2d<u32>(screenW, screenH),
\r
1297 16, fullscreen, false, false, &receiver);
\r
1299 /*device = createDevice(driverType,
\r
1300 core::dimension2d<u32>(screenW, screenH),
\r
1301 16, fullscreen, false, true, &receiver);*/
\r
1304 return 1; // could not create selected driver.
\r
1306 g_device = device;
\r
1308 device->setResizable(true);
\r
1310 if(g_settings.getBool("random_input"))
\r
1311 g_input = new RandomInputHandler();
\r
1313 g_input = new RealInputHandler(device, &receiver);
\r
1316 Continue initialization
\r
1319 video::IVideoDriver* driver = device->getVideoDriver();
\r
1320 // These make the textures not to show at all
\r
1321 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1322 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1324 //driver->setMinHardwareBufferVertexCount(1);
\r
1326 scene::ISceneManager* smgr = device->getSceneManager();
\r
1329 guiPauseMenu pauseMenu(device, &receiver);
\r
1331 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1332 gui::IGUISkin* skin = guienv->getSkin();
\r
1333 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1335 skin->setFont(font);
\r
1336 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1337 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1338 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1339 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1340 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1341 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1343 const wchar_t *text = L"Loading and connecting...";
\r
1344 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1345 core::dimension2d<u32> textd = font->getDimension(text);
\r
1346 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1347 // Have to add a bit to disable the text from word wrapping
\r
1348 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1349 core::vector2d<s32> textsize(300, textd.Height);
\r
1350 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1352 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1353 text, textrect, false, false);
\r
1354 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1356 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1357 guienv->drawAll();
\r
1358 driver->endScene();
\r
1361 Preload some random textures that are used in threads
\r
1364 g_texturecache.set("torch", driver->getTexture("../data/torch.png"));
\r
1365 g_texturecache.set("torch_on_floor", driver->getTexture("../data/torch_on_floor.png"));
\r
1366 g_texturecache.set("torch_on_ceiling", driver->getTexture("../data/torch_on_ceiling.png"));
\r
1369 Load tile textures
\r
1371 for(s32 i=0; i<TILES_COUNT; i++)
\r
1373 if(g_tile_texture_names[i] == NULL)
\r
1375 std::string name = g_tile_texture_names[i];
\r
1376 std::string filename;
\r
1377 filename += "../data/";
\r
1379 filename += ".png";
\r
1380 g_texturecache.set(name, driver->getTexture(filename.c_str()));
\r
1383 tile_materials_preload(g_texturecache);
\r
1386 Make a scope here for the client so that it gets removed
\r
1387 before the irrlicht device
\r
1391 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1396 SharedPtr<Server> server;
\r
1398 server = new Server("../map", hm_params, map_params);
\r
1399 server->start(port);
\r
1406 Client client(device, playername);
\r
1408 Address connect_address(0,0,0,0, port);
\r
1410 connect_address.Resolve(connect_name);
\r
1412 catch(ResolveError &e)
\r
1414 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1418 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1419 client.connect(connect_address);
\r
1422 while(client.connectedAndInitialized() == false)
\r
1425 if(server != NULL){
\r
1426 server->step(0.1);
\r
1431 catch(con::PeerNotFoundException &e)
\r
1433 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1440 scene::ISceneNode* skybox;
\r
1441 skybox = smgr->addSkyBoxSceneNode(
\r
1442 driver->getTexture("../data/skybox2.png"),
\r
1443 driver->getTexture("../data/skybox3.png"),
\r
1444 driver->getTexture("../data/skybox1.png"),
\r
1445 driver->getTexture("../data/skybox1.png"),
\r
1446 driver->getTexture("../data/skybox1.png"),
\r
1447 driver->getTexture("../data/skybox1.png"));
\r
1450 Create the camera node
\r
1453 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1454 0, // Camera parent
\r
1455 v3f(BS*100, BS*2, BS*100), // Look from
\r
1456 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1460 if(camera == NULL)
\r
1463 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1465 camera->setFOV(FOV_ANGLE);
\r
1467 // Just so big a value that everything rendered is visible
\r
1468 camera->setFarValue(100000*BS);
\r
1470 /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1471 f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1473 camera->setFarValue(range);
\r
1477 video::EFT_FOG_LINEAR,
\r
1485 f32 camera_yaw = 0; // "right/left"
\r
1486 f32 camera_pitch = 0; // "up/down"
\r
1492 gui_loadingtext->remove();
\r
1494 pauseMenu.setVisible(true);
\r
1497 Add some gui stuff
\r
1500 // First line of debug text
\r
1501 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1503 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1505 // Second line of debug text
\r
1506 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1508 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1511 // At the middle of the screen
\r
1512 // Object infos are shown in this
\r
1513 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1515 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1518 // This is a copy of the inventory that the client's environment has
\r
1519 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1521 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1522 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1525 Some statistics are collected in these
\r
1528 u32 beginscenetime = 0;
\r
1529 u32 scenetime = 0;
\r
1530 u32 endscenetime = 0;
\r
1538 virtual void sendText(std::string text) = 0;
\r
1541 struct TextDestSign : public TextDest
\r
1543 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1545 m_blockpos = blockpos;
\r
1547 m_client = client;
\r
1549 void sendText(std::string text)
\r
1551 dstream<<"Changing text of a sign object: "
\r
1552 <<text<<std::endl;
\r
1553 m_client->sendSignText(m_blockpos, m_id, text);
\r
1561 TextDest *textbuf_dest = NULL;
\r
1563 //gui::IGUIWindow* input_window = NULL;
\r
1564 gui::IGUIStaticText* input_guitext = NULL;
\r
1570 bool first_loop_after_window_activation = true;
\r
1572 // Time is in milliseconds
\r
1573 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1574 // NOTE: Have to call run() between calls of this to update the timer
\r
1575 u32 lasttime = device->getTimer()->getTime();
\r
1577 while(device->run())
\r
1580 Random calculations
\r
1582 v2u32 screensize = driver->getScreenSize();
\r
1583 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1585 // Hilight boxes collected during the loop and displayed
\r
1586 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1589 std::wstring infotext;
\r
1591 //TimeTaker //timer1("//timer1", device);
\r
1593 // Time of frame without fps limit
\r
1597 // not using getRealTime is necessary for wine
\r
1598 u32 time = device->getTimer()->getTime();
\r
1599 if(time > lasttime)
\r
1600 busytime_u32 = time - lasttime;
\r
1603 busytime = busytime_u32 / 1000.0;
\r
1606 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1608 // Absolutelu necessary for wine!
\r
1615 //updateViewingRange(dtime, &client);
\r
1616 updateViewingRange(busytime, &client);
\r
1623 float fps_max = g_settings.getFloat("fps_max");
\r
1624 u32 frametime_min = 1000./fps_max;
\r
1626 if(busytime_u32 < frametime_min)
\r
1628 u32 sleeptime = frametime_min - busytime_u32;
\r
1629 device->sleep(sleeptime);
\r
1633 // Absolutelu necessary for wine!
\r
1637 Time difference calculation
\r
1639 f32 dtime; // in seconds
\r
1641 u32 time = device->getTimer()->getTime();
\r
1642 if(time > lasttime)
\r
1643 dtime = (time - lasttime) / 1000.0;
\r
1649 Time average and jitter calculation
\r
1652 static f32 dtime_avg1 = 0.0;
\r
1653 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1654 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1656 static f32 dtime_jitter1_max_sample = 0.0;
\r
1657 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1659 static f32 jitter1_max = 0.0;
\r
1660 static f32 counter = 0.0;
\r
1661 if(dtime_jitter1 > jitter1_max)
\r
1662 jitter1_max = dtime_jitter1;
\r
1667 dtime_jitter1_max_sample = jitter1_max;
\r
1668 dtime_jitter1_max_fraction
\r
1669 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1670 jitter1_max = 0.0;
\r
1673 Control freetime ratio
\r
1675 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1677 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1678 g_freetime_ratio += 0.01;
\r
1682 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1683 g_freetime_ratio -= 0.01;
\r
1689 Busytime average and jitter calculation
\r
1692 static f32 busytime_avg1 = 0.0;
\r
1693 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1694 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1696 static f32 busytime_jitter1_max_sample = 0.0;
\r
1697 static f32 busytime_jitter1_min_sample = 0.0;
\r
1699 static f32 jitter1_max = 0.0;
\r
1700 static f32 jitter1_min = 0.0;
\r
1701 static f32 counter = 0.0;
\r
1702 if(busytime_jitter1 > jitter1_max)
\r
1703 jitter1_max = busytime_jitter1;
\r
1704 if(busytime_jitter1 < jitter1_min)
\r
1705 jitter1_min = busytime_jitter1;
\r
1707 if(counter > 0.0){
\r
1709 busytime_jitter1_max_sample = jitter1_max;
\r
1710 busytime_jitter1_min_sample = jitter1_min;
\r
1711 jitter1_max = 0.0;
\r
1712 jitter1_min = 0.0;
\r
1717 Debug info for client
\r
1720 static float counter = 0.0;
\r
1725 client.printDebugInfo(std::cout);
\r
1730 Input handler step()
\r
1732 g_input->step(dtime);
\r
1737 /*if(g_esc_pressed)
\r
1743 Player speed control
\r
1746 if(g_game_focused)
\r
1753 bool a_superspeed,
\r
1756 PlayerControl control(
\r
1757 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1758 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1759 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1760 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1761 g_input->isKeyDown(irr::KEY_SPACE),
\r
1762 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1766 client.setPlayerControl(control);
\r
1770 // Set every key to inactive
\r
1771 PlayerControl control;
\r
1772 client.setPlayerControl(control);
\r
1777 Process environment
\r
1781 //TimeTaker timer("client.step(dtime)", device);
\r
1782 client.step(dtime);
\r
1783 //client.step(dtime_avg1);
\r
1786 if(server != NULL)
\r
1788 //TimeTaker timer("server->step(dtime)", device);
\r
1789 server->step(dtime);
\r
1792 v3f player_position = client.getPlayerPosition();
\r
1794 //TimeTaker //timer2("//timer2", device);
\r
1797 Mouse and camera control
\r
1800 if(device->isWindowActive() && g_game_focused && !pauseMenu.isVisible())
\r
1802 device->getCursorControl()->setVisible(false);
\r
1804 if(first_loop_after_window_activation){
\r
1805 //std::cout<<"window active, first loop"<<std::endl;
\r
1806 first_loop_after_window_activation = false;
\r
1809 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1810 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1811 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1812 camera_yaw -= dx*0.2;
\r
1813 camera_pitch += dy*0.2;
\r
1814 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1815 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1817 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1820 device->getCursorControl()->setVisible(true);
\r
1822 //std::cout<<"window inactive"<<std::endl;
\r
1823 first_loop_after_window_activation = true;
\r
1826 camera_yaw = wrapDegrees(camera_yaw);
\r
1827 camera_pitch = wrapDegrees(camera_pitch);
\r
1829 v3f camera_direction = v3f(0,0,1);
\r
1830 camera_direction.rotateYZBy(camera_pitch);
\r
1831 camera_direction.rotateXZBy(camera_yaw);
\r
1833 v3f camera_position =
\r
1834 player_position + v3f(0, BS+BS/2, 0);
\r
1836 camera->setPosition(camera_position);
\r
1837 // *100.0 helps in large map coordinates
\r
1838 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1840 if(FIELD_OF_VIEW_TEST){
\r
1841 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1842 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1845 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1846 //TimeTaker timer("client.updateCamera", device);
\r
1847 client.updateCamera(camera_position, camera_direction);
\r
1851 //TimeTaker //timer3("//timer3", device);
\r
1854 Calculate what block is the crosshair pointing to
\r
1857 //u32 t1 = device->getTimer()->getRealTime();
\r
1859 //f32 d = 4; // max. distance
\r
1860 f32 d = 4; // max. distance
\r
1861 core::line3d<f32> shootline(camera_position,
\r
1862 camera_position + camera_direction * BS * (d+1));
\r
1864 MapBlockObject *selected_object = client.getSelectedObject
\r
1865 (d*BS, camera_position, shootline);
\r
1867 if(selected_object != NULL)
\r
1869 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1871 core::aabbox3d<f32> box_on_map
\r
1872 = selected_object->getSelectionBoxOnMap();
\r
1874 hilightboxes.push_back(box_on_map);
\r
1876 infotext = narrow_to_wide(selected_object->infoText());
\r
1878 if(g_input->getLeftClicked())
\r
1880 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1881 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1882 selected_object->getId(), g_selected_item);
\r
1884 else if(g_input->getRightClicked())
\r
1886 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1888 Check if we want to modify the object ourselves
\r
1890 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1892 dstream<<"Sign object right-clicked"<<std::endl;
\r
1896 input_guitext = guienv->addStaticText(L"",
\r
1897 core::rect<s32>(150,100,350,120),
\r
1899 false, // wordwrap?
\r
1902 input_guitext->setDrawBackground(true);
\r
1904 g_text_buffer = L"";
\r
1905 g_text_buffer_accepted = false;
\r
1906 textbuf_dest = new TextDestSign(
\r
1907 selected_object->getBlock()->getPos(),
\r
1908 selected_object->getId(),
\r
1912 Otherwise pass the event to the server as-is
\r
1916 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1917 selected_object->getId(), g_selected_item);
\r
1921 else // selected_object == NULL
\r
1924 bool nodefound = false;
\r
1926 v3s16 neighbourpos;
\r
1927 core::aabbox3d<f32> nodefacebox;
\r
1928 f32 mindistance = BS * 1001;
\r
1930 v3s16 pos_i = floatToInt(player_position);
\r
1932 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1936 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1937 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1938 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1939 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1940 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1941 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1943 for(s16 y = ystart; y <= yend; y++)
\r
1944 for(s16 z = zstart; z <= zend; z++)
\r
1945 for(s16 x = xstart; x <= xend; x++)
\r
1950 n = client.getNode(v3s16(x,y,z));
\r
1951 if(content_pointable(n.d) == false)
\r
1954 catch(InvalidPositionException &e)
\r
1960 v3f npf = intToFloat(np);
\r
1965 v3s16(0,0,1), // back
\r
1966 v3s16(0,1,0), // top
\r
1967 v3s16(1,0,0), // right
\r
1968 v3s16(0,0,-1), // front
\r
1969 v3s16(0,-1,0), // bottom
\r
1970 v3s16(-1,0,0), // left
\r
1976 if(n.d == CONTENT_TORCH)
\r
1978 v3s16 dir = unpackDir(n.dir);
\r
1979 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
1980 dir_f *= BS/2 - BS/6 - BS/20;
\r
1981 v3f cpf = npf + dir_f;
\r
1982 f32 distance = (cpf - camera_position).getLength();
\r
1984 core::aabbox3d<f32> box;
\r
1987 if(dir == v3s16(0,-1,0))
\r
1989 box = core::aabbox3d<f32>(
\r
1990 npf - v3f(BS/6, BS/2, BS/6),
\r
1991 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
1995 else if(dir == v3s16(0,1,0))
\r
1997 box = core::aabbox3d<f32>(
\r
1998 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
1999 npf + v3f(BS/6, BS/2, BS/6)
\r
2005 box = core::aabbox3d<f32>(
\r
2006 cpf - v3f(BS/6, BS/3, BS/6),
\r
2007 cpf + v3f(BS/6, BS/3, BS/6)
\r
2011 if(distance < mindistance)
\r
2013 if(box.intersectsWithLine(shootline))
\r
2017 neighbourpos = np;
\r
2018 mindistance = distance;
\r
2019 nodefacebox = box;
\r
2028 for(u16 i=0; i<6; i++)
\r
2030 v3f dir_f = v3f(dirs[i].X,
\r
2031 dirs[i].Y, dirs[i].Z);
\r
2032 v3f centerpoint = npf + dir_f * BS/2;
\r
2034 (centerpoint - camera_position).getLength();
\r
2036 if(distance < mindistance)
\r
2038 core::CMatrix4<f32> m;
\r
2039 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2041 // This is the back face
\r
2042 v3f corners[2] = {
\r
2043 v3f(BS/2, BS/2, BS/2),
\r
2044 v3f(-BS/2, -BS/2, BS/2+d)
\r
2047 for(u16 j=0; j<2; j++)
\r
2049 m.rotateVect(corners[j]);
\r
2050 corners[j] += npf;
\r
2053 core::aabbox3d<f32> facebox(corners[0]);
\r
2054 facebox.addInternalPoint(corners[1]);
\r
2056 if(facebox.intersectsWithLine(shootline))
\r
2060 neighbourpos = np + dirs[i];
\r
2061 mindistance = distance;
\r
2062 nodefacebox = facebox;
\r
2064 } // if distance < mindistance
\r
2066 } // regular block
\r
2071 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
2072 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2073 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
2075 static v3s16 nodepos_old(-1,-1,-1);
\r
2076 if(nodepos != nodepos_old){
\r
2077 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2078 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2079 nodepos_old = nodepos;
\r
2081 /*wchar_t positiontext[20];
\r
2082 swprintf(positiontext, 20, L"(%i,%i,%i)",
\r
2083 nodepos.X, nodepos.Y, nodepos.Z);
\r
2084 positiontextgui->setText(positiontext);*/
\r
2087 hilightboxes.push_back(nodefacebox);
\r
2089 if(g_input->getLeftClicked())
\r
2091 //std::cout<<DTIME<<"Removing node"<<std::endl;
\r
2092 //client.removeNode(nodepos);
\r
2093 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2094 client.pressGround(0, nodepos, neighbourpos, g_selected_item);
\r
2096 if(g_input->getRightClicked())
\r
2098 //std::cout<<DTIME<<"Placing node"<<std::endl;
\r
2099 //client.addNodeFromInventory(neighbourpos, g_selected_item);
\r
2100 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2101 client.pressGround(1, nodepos, neighbourpos, g_selected_item);
\r
2105 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
2106 //positiontextgui->setText(L"");
\r
2109 } // selected_object == NULL
\r
2111 g_input->resetLeftClicked();
\r
2112 g_input->resetRightClicked();
\r
2115 Calculate stuff for drawing
\r
2118 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2120 /*f32 range = g_viewing_range_nodes * BS;
\r
2121 if(g_viewing_range_all)
\r
2122 range = 100000*BS;
\r
2126 video::EFT_FOG_LINEAR,
\r
2130 false, // pixel fog
\r
2131 false // range fog
\r
2136 Update gui stuff (0ms)
\r
2139 //TimeTaker guiupdatetimer("Gui updating", device);
\r
2142 wchar_t temptext[150];
\r
2144 static float drawtime_avg = 0;
\r
2145 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2146 static float beginscenetime_avg = 0;
\r
2147 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2148 static float scenetime_avg = 0;
\r
2149 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2150 static float endscenetime_avg = 0;
\r
2151 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2153 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2155 L", R: range_all=%i"
\r
2157 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2159 g_viewing_range_all,
\r
2161 beginscenetime_avg,
\r
2166 guitext->setText(temptext);
\r
2170 wchar_t temptext[150];
\r
2171 swprintf(temptext, 150,
\r
2172 L"(% .1f, % .1f, % .1f)"
\r
2173 L" (% .3f < btime_jitter < % .3f"
\r
2174 L", dtime_jitter = % .1f %%)",
\r
2175 player_position.X/BS,
\r
2176 player_position.Y/BS,
\r
2177 player_position.Z/BS,
\r
2178 busytime_jitter1_min_sample,
\r
2179 busytime_jitter1_max_sample,
\r
2180 dtime_jitter1_max_fraction * 100.0
\r
2183 guitext2->setText(temptext);
\r
2187 /*wchar_t temptext[100];
\r
2188 swprintf(temptext, 100,
\r
2189 SWPRINTF_CHARSTRING,
\r
2190 infotext.substr(0,99).c_str()
\r
2193 guitext_info->setText(temptext);*/
\r
2195 guitext_info->setText(infotext.c_str());
\r
2202 static u16 old_selected_item = 65535;
\r
2203 if(client.getLocalInventoryUpdated()
\r
2204 || g_selected_item != old_selected_item)
\r
2206 old_selected_item = g_selected_item;
\r
2207 //std::cout<<"Updating local inventory"<<std::endl;
\r
2208 client.getLocalInventory(local_inventory);
\r
2209 quick_inventory->setSelection(g_selected_item);
\r
2210 quick_inventory->update();
\r
2213 if(input_guitext != NULL)
\r
2215 /*wchar_t temptext[100];
\r
2216 swprintf(temptext, 100,
\r
2217 SWPRINTF_CHARSTRING,
\r
2218 g_text_buffer.substr(0,99).c_str()
\r
2220 input_guitext->setText(g_text_buffer.c_str());
\r
2226 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2228 input_guitext->remove();
\r
2229 input_guitext = NULL;
\r
2231 if(textbuf_dest != NULL)
\r
2233 std::string text = wide_to_narrow(g_text_buffer);
\r
2234 dstream<<"Sending text: "<<text<<std::endl;
\r
2235 textbuf_dest->sendText(text);
\r
2236 delete textbuf_dest;
\r
2237 textbuf_dest = NULL;
\r
2243 //guiupdatetimer.stop();
\r
2249 TimeTaker drawtimer("Drawing", device);
\r
2252 Background color is choosen based on whether the player is
\r
2253 much beyond the initial ground level
\r
2255 /*video::SColor bgcolor;
\r
2256 v3s16 p0 = Map::floatToInt(player_position);
\r
2257 // Does this make short random delays?
\r
2258 // NOTE: no need for this, sky doesn't show underground with
\r
2260 bool is_underground = client.isNodeUnderground(p0);
\r
2261 //bool is_underground = false;
\r
2262 if(is_underground == false)
\r
2263 bgcolor = video::SColor(255,90,140,200);
\r
2265 bgcolor = video::SColor(255,0,0,0);*/
\r
2267 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2268 video::SColor bgcolor = skycolor;
\r
2271 TimeTaker timer("beginScene", device);
\r
2272 //driver->beginScene(true, true, bgcolor);
\r
2273 driver->beginScene(false, true, bgcolor);
\r
2274 beginscenetime = timer.stop(true);
\r
2279 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2282 TimeTaker timer("smgr", device);
\r
2284 scenetime = timer.stop(true);
\r
2288 //TimeTaker timer9("auxiliary drawings", device);
\r
2291 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2292 displaycenter + core::vector2d<s32>(10,0),
\r
2293 video::SColor(255,255,255,255));
\r
2294 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2295 displaycenter + core::vector2d<s32>(0,10),
\r
2296 video::SColor(255,255,255,255));
\r
2299 //TimeTaker //timer10("//timer10", device);
\r
2301 video::SMaterial m;
\r
2303 m.Lighting = false;
\r
2304 driver->setMaterial(m);
\r
2306 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2308 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2309 i != hilightboxes.end(); i++)
\r
2311 /*std::cout<<"hilightbox min="
\r
2312 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2314 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2316 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2322 //TimeTaker //timer11("//timer11", device);
\r
2328 guienv->drawAll();
\r
2332 TimeTaker timer("endScene", device);
\r
2333 driver->endScene();
\r
2334 endscenetime = timer.stop(true);
\r
2337 drawtime = drawtimer.stop(true);
\r
2343 static s16 lastFPS = 0;
\r
2344 //u16 fps = driver->getFPS();
\r
2345 u16 fps = (1.0/dtime_avg1);
\r
2347 if (lastFPS != fps)
\r
2349 core::stringw str = L"Minetest [";
\r
2350 str += driver->getName();
\r
2354 device->setWindowCaption(str.c_str());
\r
2360 device->yield();*/
\r
2363 } // client is deleted at this point
\r
2368 In the end, delete the Irrlicht device.
\r
2373 Update configuration file
\r
2375 if(configpath != "")
\r
2377 g_settings.updateConfigFile(configpath.c_str());
\r
2381 catch(con::PeerNotFoundException &e)
\r
2383 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2385 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2387 This is what has to be done in every thread to get suitable debug info
\r
2389 catch(std::exception &e)
\r
2391 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2392 <<e.what()<<std::endl;
\r
2397 debugstreams_deinit();
\r