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
190 TODO: A mapper to map contents to tile names (for each side)
\r
192 ======================================================================
\r
197 Setting this to 1 enables a special camera mode that forces
\r
198 the renderers to think that the camera statically points from
\r
199 the starting place to a static direction.
\r
201 This allows one to move around with the player and see what
\r
202 is actually drawn behind solid things and behind the player.
\r
204 #define FIELD_OF_VIEW_TEST 0
\r
206 #ifdef UNITTEST_DISABLE
\r
208 #pragma message ("Disabling unit tests")
\r
210 #warning "Disabling unit tests"
\r
212 // Disable unit tests
\r
213 #define ENABLE_TESTS 0
\r
215 // Enable unit tests
\r
216 #define ENABLE_TESTS 1
\r
220 #pragma comment(lib, "Irrlicht.lib")
\r
221 #pragma comment(lib, "jthread.lib")
\r
222 #pragma comment(lib, "zlibwapi.lib")
\r
223 // This would get rid of the console window
\r
224 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
228 #define WIN32_LEAN_AND_MEAN
\r
229 #include <windows.h>
\r
230 #define sleep_ms(x) Sleep(x)
\r
232 #include <unistd.h>
\r
233 #define sleep_ms(x) usleep(x*1000)
\r
236 #include <iostream>
\r
239 #include <jmutexautolock.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 <locale.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
284 // This is updated by the client's fetchBlocks routine
\r
285 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
287 // If true, the preceding value has no meaning and all blocks
\r
288 // already existing in memory are drawn
\r
289 bool g_viewing_range_all = false;
\r
291 // This is the freetime ratio imposed by the dynamic viewing
\r
292 // range changing code.
\r
293 // It is controlled by the main loop to the smallest value that
\r
294 // inhibits glitches (dtime jitter) in the main loop.
\r
295 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
300 These are loaded from the config file.
\r
303 Settings g_settings;
\r
305 // Sets default settings
\r
306 void set_default_settings()
\r
308 g_settings.set("dedicated_server", "");
\r
311 g_settings.set("wanted_fps", "30");
\r
312 g_settings.set("fps_max", "60");
\r
313 g_settings.set("viewing_range_nodes_max", "300");
\r
314 g_settings.set("viewing_range_nodes_min", "50");
\r
315 g_settings.set("screenW", "");
\r
316 g_settings.set("screenH", "");
\r
317 g_settings.set("host_game", "");
\r
318 g_settings.set("port", "");
\r
319 g_settings.set("address", "");
\r
320 g_settings.set("name", "");
\r
321 g_settings.set("random_input", "false");
\r
322 g_settings.set("client_delete_unused_sectors_timeout", "1200");
\r
323 g_settings.set("max_block_send_distance", "8");
\r
324 g_settings.set("max_block_generate_distance", "6");
\r
327 g_settings.set("creative_mode", "false");
\r
328 g_settings.set("heightmap_blocksize", "32");
\r
329 g_settings.set("height_randmax", "constant 50.0");
\r
330 g_settings.set("height_randfactor", "constant 0.6");
\r
331 g_settings.set("height_base", "linear 0 0 0");
\r
332 g_settings.set("plants_amount", "1.0");
\r
333 g_settings.set("ravines_amount", "1.0");
\r
334 g_settings.set("objectdata_interval", "0.2");
\r
335 g_settings.set("active_object_range", "2");
\r
336 g_settings.set("max_simultaneous_block_sends_per_client", "1");
\r
337 g_settings.set("max_simultaneous_block_sends_server_total", "4");
\r
338 g_settings.set("disable_water_climb", "true");
\r
339 g_settings.set("endless_water", "true");
\r
346 //u16 g_selected_material = 0;
\r
347 u16 g_selected_item = 0;
\r
349 bool g_esc_pressed = false;
\r
351 std::wstring g_text_buffer;
\r
352 bool g_text_buffer_accepted = false;
\r
354 // When true, the mouse and keyboard are grabbed
\r
355 bool g_game_focused = true;
\r
362 std::ostream *dout_con_ptr = &dummyout;
\r
363 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
364 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
365 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
366 //std::ostream *dout_con_ptr = &dstream;
\r
367 //std::ostream *derr_con_ptr = &dstream;
\r
370 std::ostream *dout_server_ptr = &dstream;
\r
371 std::ostream *derr_server_ptr = &dstream;
\r
374 std::ostream *dout_client_ptr = &dstream;
\r
375 std::ostream *derr_client_ptr = &dstream;
\r
382 JMutex g_timestamp_mutex;
\r
383 //std::string g_timestamp;
\r
385 std::string getTimestamp()
\r
387 if(g_timestamp_mutex.IsInitialized()==false)
\r
389 JMutexAutoLock lock(g_timestamp_mutex);
\r
390 //return g_timestamp;
\r
391 time_t t = time(NULL);
\r
392 struct tm *tm = localtime(&t);
\r
394 strftime(cs, 20, "%H:%M:%S", tm);
\r
398 class MyEventReceiver : public IEventReceiver
\r
401 // This is the one method that we have to implement
\r
402 virtual bool OnEvent(const SEvent& event)
\r
404 // Remember whether each key is down or up
\r
405 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
407 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
409 if(event.KeyInput.PressedDown)
\r
411 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
412 if(g_game_focused == false)
\r
414 s16 key = event.KeyInput.Key;
\r
415 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
417 g_text_buffer_accepted = true;
\r
419 else if(key == irr::KEY_BACK)
\r
421 if(g_text_buffer.size() > 0)
\r
422 g_text_buffer = g_text_buffer.substr
\r
423 (0, g_text_buffer.size()-1);
\r
427 wchar_t wc = event.KeyInput.Char;
\r
429 g_text_buffer += wc;
\r
433 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\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_limit;
\r
800 // Use high limit if fraction is good AND the fraction would
\r
801 // lower the range. We want to keep the range fairly high.
\r
802 if(fraction_is_good && fraction > 1.0)
\r
803 fraction_limit = fraction_bad_threshold;
\r
805 fraction_limit = fraction_good_threshold;
\r
807 if(fabs(fraction - 1.0) < fraction_limit)
\r
809 fraction_is_good = true;
\r
814 fraction_is_good = false;
\r
817 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
818 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
819 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
820 <<" faction = "<<fraction<<std::endl;*/
\r
822 JMutexAutoLock lock(g_range_mutex);
\r
824 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
825 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
827 s16 n = (float)g_viewing_range_nodes / fraction;
\r
828 if(n < viewing_range_nodes_min)
\r
829 n = viewing_range_nodes_min;
\r
830 if(n > viewing_range_nodes_max)
\r
831 n = viewing_range_nodes_max;
\r
833 bool can_change = true;
\r
835 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
836 can_change = false;
\r
839 g_viewing_range_nodes = n;
\r
841 /*dstream<<"g_viewing_range_nodes = "
\r
842 <<g_viewing_range_nodes<<std::endl;*/
\r
845 class GUIQuickInventory : public IEventReceiver
\r
849 gui::IGUIEnvironment* env,
\r
850 gui::IGUIElement* parent,
\r
853 Inventory *inventory):
\r
854 m_itemcount(itemcount),
\r
855 m_inventory(inventory)
\r
857 core::rect<s32> imgsize(0,0,48,48);
\r
858 core::rect<s32> textsize(0,0,48,16);
\r
859 v2s32 spacing(0, 64);
\r
860 for(s32 i=0; i<m_itemcount; i++)
\r
862 m_images.push_back(env->addImage(
\r
863 imgsize + pos + spacing*i
\r
865 m_images[i]->setScaleImage(true);
\r
866 m_texts.push_back(env->addStaticText(
\r
868 textsize + pos + spacing*i,
\r
871 m_texts[i]->setBackgroundColor(
\r
872 video::SColor(128,0,0,0));
\r
873 m_texts[i]->setTextAlignment(
\r
875 gui::EGUIA_UPPERLEFT);
\r
879 virtual bool OnEvent(const SEvent& event)
\r
884 void setSelection(s32 i)
\r
893 start = m_selection - m_itemcount / 2;
\r
895 for(s32 i=0; i<m_itemcount; i++)
\r
899 if(j > (s32)m_inventory->getSize() - 1)
\r
900 j -= m_inventory->getSize();
\r
902 j += m_inventory->getSize();
\r
904 InventoryItem *item = m_inventory->getItem(j);
\r
908 m_images[i]->setImage(NULL);
\r
911 if(m_selection == j)
\r
912 swprintf(t, 10, L"<-");
\r
914 swprintf(t, 10, L"");
\r
915 m_texts[i]->setText(t);
\r
917 // The next ifs will segfault with a NULL pointer
\r
922 m_images[i]->setImage(item->getImage());
\r
925 if(m_selection == j)
\r
926 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
928 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
929 m_texts[i]->setText(t);
\r
935 core::array<gui::IGUIStaticText*> m_texts;
\r
936 core::array<gui::IGUIImage*> m_images;
\r
937 Inventory *m_inventory;
\r
941 int main(int argc, char *argv[])
\r
944 Low-level initialization
\r
947 bool disable_stderr = false;
\r
949 disable_stderr = true;
\r
952 // Initialize debug streams
\r
953 debugstreams_init(disable_stderr, DEBUGFILE);
\r
954 // Initialize debug stacks
\r
955 debug_stacks_init();
\r
957 DSTACK(__FUNCTION_NAME);
\r
963 Basic initialization
\r
966 // Initialize default settings
\r
967 set_default_settings();
\r
969 // Print startup message
\r
970 dstream<<DTIME<<"minetest-c55"
\r
971 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
972 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
975 // Set locale. This is for forcing '.' as the decimal point.
\r
976 std::locale::global(std::locale("C"));
\r
977 // This enables printing all characters in bitmap font
\r
978 setlocale(LC_CTYPE, "en_US");
\r
980 // Initialize sockets
\r
982 atexit(sockets_cleanup);
\r
984 // Initialize timestamp mutex
\r
985 g_timestamp_mutex.Init();
\r
999 // Read config file
\r
1003 g_settings.readConfigFile(argv[1]);
\r
1007 const char *filenames[2] =
\r
1009 "../minetest.conf",
\r
1010 "../../minetest.conf"
\r
1013 for(u32 i=0; i<2; i++)
\r
1015 bool r = g_settings.readConfigFile(filenames[i]);
\r
1021 // Initialize random seed
\r
1024 g_range_mutex.Init();
\r
1025 assert(g_range_mutex.IsInitialized());
\r
1027 // Read map parameters from settings
\r
1029 HMParams hm_params;
\r
1030 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1031 hm_params.randmax = g_settings.get("height_randmax");
\r
1032 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1033 hm_params.base = g_settings.get("height_base");
\r
1035 MapParams map_params;
\r
1036 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1037 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1043 std::cout<<std::endl<<std::endl;
\r
1046 <<" .__ __ __ "<<std::endl
\r
1047 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1048 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1049 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1050 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1051 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1053 <<"Now with more waterish water!"
\r
1056 std::cout<<std::endl;
\r
1057 char templine[100];
\r
1060 bool dedicated = g_settings.getBoolAsk
\r
1061 ("dedicated_server", "Dedicated server?", false);
\r
1062 std::cout<<"dedicated = "<<dedicated<<std::endl;
\r
1065 u16 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1066 std::cout<<"-> "<<port<<std::endl;
\r
1070 DSTACK("Dedicated server branch");
\r
1072 std::cout<<std::endl;
\r
1073 std::cout<<"========================"<<std::endl;
\r
1074 std::cout<<"Running dedicated server"<<std::endl;
\r
1075 std::cout<<"========================"<<std::endl;
\r
1076 std::cout<<std::endl;
\r
1078 Server server("../map", hm_params, map_params);
\r
1079 server.start(port);
\r
1083 // This is kind of a hack but can be done like this
\r
1084 // because server.step() is very light
\r
1086 server.step(0.030);
\r
1088 static int counter = 0;
\r
1094 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1095 core::list<PlayerInfo>::Iterator i;
\r
1096 static u32 sum_old = 0;
\r
1097 u32 sum = PIChecksum(list);
\r
1098 if(sum != sum_old)
\r
1100 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1101 for(i=list.begin(); i!=list.end(); i++)
\r
1103 i->PrintLine(&std::cout);
\r
1113 bool hosting = false;
\r
1114 char connect_name[100] = "";
\r
1116 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1117 if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1119 std::cout<<g_settings.get("address")<<std::endl;
\r
1120 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1124 std::cin.getline(connect_name, 100);
\r
1127 if(connect_name[0] == 0){
\r
1128 snprintf(connect_name, 100, "127.0.0.1");
\r
1133 std::cout<<"-> hosting"<<std::endl;
\r
1135 std::cout<<"-> "<<connect_name<<std::endl;
\r
1137 char playername[PLAYERNAME_SIZE] = "";
\r
1138 if(g_settings.get("name") != "")
\r
1140 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1144 std::cout<<"Name of player: ";
\r
1145 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1147 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1150 Resolution selection
\r
1155 bool fullscreen = false;
\r
1157 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1159 screenW = atoi(g_settings.get("screenW").c_str());
\r
1160 screenH = atoi(g_settings.get("screenH").c_str());
\r
1164 u16 resolutions[][3] = {
\r
1165 //W, H, fullscreen
\r
1176 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1178 for(u16 i=0; i<res_count; i++)
\r
1180 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1181 <<resolutions[i][1];
\r
1182 if(resolutions[i][2])
\r
1183 std::cout<<" fullscreen"<<std::endl;
\r
1185 std::cout<<" windowed"<<std::endl;
\r
1187 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1188 std::cin.getline(templine, 100);
\r
1191 if(templine[0] == 0)
\r
1194 r0 = atoi(templine);
\r
1196 if(r0 > res_count || r0 == 0)
\r
1202 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1203 <<resolutions[i][1];
\r
1204 if(resolutions[i][2])
\r
1205 std::cout<<" fullscreen"<<std::endl;
\r
1207 std::cout<<" windowed"<<std::endl;
\r
1210 screenW = resolutions[r0-1][0];
\r
1211 screenH = resolutions[r0-1][1];
\r
1212 fullscreen = resolutions[r0-1][2];
\r
1217 MyEventReceiver receiver;
\r
1219 video::E_DRIVER_TYPE driverType;
\r
1222 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1223 driverType = video::EDT_OPENGL;
\r
1225 driverType = video::EDT_OPENGL;
\r
1228 // create device and exit if creation failed
\r
1230 IrrlichtDevice *device;
\r
1231 device = createDevice(driverType,
\r
1232 core::dimension2d<u32>(screenW, screenH),
\r
1233 16, fullscreen, false, false, &receiver);
\r
1235 /*device = createDevice(driverType,
\r
1236 core::dimension2d<u32>(screenW, screenH),
\r
1237 16, fullscreen, false, true, &receiver);*/
\r
1240 return 1; // could not create selected driver.
\r
1242 g_device = device;
\r
1244 device->setResizable(true);
\r
1246 if(g_settings.getBool("random_input"))
\r
1247 g_input = new RandomInputHandler();
\r
1249 g_input = new RealInputHandler(device, &receiver);
\r
1252 Continue initialization
\r
1255 video::IVideoDriver* driver = device->getVideoDriver();
\r
1256 // These make the textures not to show at all
\r
1257 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1258 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1260 //driver->setMinHardwareBufferVertexCount(1);
\r
1262 scene::ISceneManager* smgr = device->getSceneManager();
\r
1264 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1265 gui::IGUISkin* skin = guienv->getSkin();
\r
1266 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1268 skin->setFont(font);
\r
1269 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1270 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1271 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1272 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1273 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1274 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1276 const wchar_t *text = L"Loading and connecting...";
\r
1277 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1278 core::dimension2d<u32> textd = font->getDimension(text);
\r
1279 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1280 // Have to add a bit to disable the text from word wrapping
\r
1281 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1282 core::vector2d<s32> textsize(300, textd.Height);
\r
1283 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1285 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1286 text, textrect, false, false);
\r
1287 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1289 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1290 guienv->drawAll();
\r
1291 driver->endScene();
\r
1294 Initialize material array
\r
1297 /*//video::SMaterial g_materials[MATERIALS_COUNT];
\r
1298 for(u16 i=0; i<MATERIALS_COUNT; i++)
\r
1300 g_materials[i].Lighting = false;
\r
1301 g_materials[i].BackfaceCulling = false;
\r
1303 const char *filename = g_content_filenames[i];
\r
1304 if(filename != NULL){
\r
1305 video::ITexture *t = driver->getTexture(filename);
\r
1307 std::cout<<DTIME<<"Texture could not be loaded: \""
\r
1308 <<filename<<"\""<<std::endl;
\r
1311 g_materials[i].setTexture(0, driver->getTexture(filename));
\r
1313 //g_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);
\r
1314 g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1315 //g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
\r
1316 //g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1319 g_materials[CONTENT_WATER].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
\r
1320 //g_materials[CONTENT_WATER].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
\r
1321 g_materials[CONTENT_OCEAN].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
\r
1324 /*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
\r
1325 g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
\r
1326 g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));
\r
1327 for(u32 i=0; i<3; i++)
\r
1329 g_mesh_materials[i].Lighting = false;
\r
1330 g_mesh_materials[i].BackfaceCulling = false;
\r
1331 g_mesh_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1332 g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1336 Preload some random textures that are used in threads
\r
1339 g_texturecache.set("torch", driver->getTexture("../data/torch.png"));
\r
1340 g_texturecache.set("torch_on_floor", driver->getTexture("../data/torch_on_floor.png"));
\r
1341 g_texturecache.set("torch_on_ceiling", driver->getTexture("../data/torch_on_ceiling.png"));
\r
1344 Load tile textures
\r
1346 for(s32 i=0; i<TILES_COUNT; i++)
\r
1348 if(g_tile_texture_names[i] == NULL)
\r
1350 std::string name = g_tile_texture_names[i];
\r
1351 std::string filename;
\r
1352 filename += "../data/";
\r
1354 filename += ".png";
\r
1355 g_texturecache.set(name, driver->getTexture(filename.c_str()));
\r
1358 tile_materials_preload(g_texturecache);
\r
1361 Make a scope here for the client so that it gets removed
\r
1362 before the irrlicht device
\r
1366 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1371 SharedPtr<Server> server;
\r
1373 server = new Server("../map", hm_params, map_params);
\r
1374 server->start(port);
\r
1381 Client client(device, playername);
\r
1383 Address connect_address(0,0,0,0, port);
\r
1385 connect_address.Resolve(connect_name);
\r
1387 catch(ResolveError &e)
\r
1389 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1393 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1394 client.connect(connect_address);
\r
1397 while(client.connectedAndInitialized() == false)
\r
1400 if(server != NULL){
\r
1401 server->step(0.1);
\r
1406 catch(con::PeerNotFoundException &e)
\r
1408 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1415 scene::ISceneNode* skybox;
\r
1416 skybox = smgr->addSkyBoxSceneNode(
\r
1417 driver->getTexture("../data/skybox2.png"),
\r
1418 driver->getTexture("../data/skybox3.png"),
\r
1419 driver->getTexture("../data/skybox1.png"),
\r
1420 driver->getTexture("../data/skybox1.png"),
\r
1421 driver->getTexture("../data/skybox1.png"),
\r
1422 driver->getTexture("../data/skybox1.png"));
\r
1425 Create the camera node
\r
1428 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1429 0, // Camera parent
\r
1430 v3f(BS*100, BS*2, BS*100), // Look from
\r
1431 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1435 if(camera == NULL)
\r
1438 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1440 camera->setFOV(FOV_ANGLE);
\r
1442 // Just so big a value that everything rendered is visible
\r
1443 camera->setFarValue(100000*BS);
\r
1445 /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1446 f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1448 camera->setFarValue(range);
\r
1452 video::EFT_FOG_LINEAR,
\r
1460 f32 camera_yaw = 0; // "right/left"
\r
1461 f32 camera_pitch = 0; // "up/down"
\r
1463 gui_loadingtext->remove();
\r
1466 Add some gui stuff
\r
1469 // First line of debug text
\r
1470 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1472 core::rect<s32>(5, 5, 5+600, 5+textsize.Y),
\r
1474 // Second line of debug text
\r
1475 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1477 core::rect<s32>(5, 5+(textsize.Y+5)*1, 5+600, (5+textsize.Y)*2),
\r
1480 // At the middle of the screen
\r
1481 // Object infos are shown in this
\r
1482 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1484 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1487 // This is a copy of the inventory that the client's environment has
\r
1488 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1490 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1491 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1494 Some statistics are collected in these
\r
1497 u32 scenetime = 0;
\r
1498 u32 endscenetime = 0;
\r
1506 virtual void sendText(std::string text) = 0;
\r
1509 struct TextDestSign : public TextDest
\r
1511 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1513 m_blockpos = blockpos;
\r
1515 m_client = client;
\r
1517 void sendText(std::string text)
\r
1519 dstream<<"Changing text of a sign object: "
\r
1520 <<text<<std::endl;
\r
1521 m_client->sendSignText(m_blockpos, m_id, text);
\r
1529 TextDest *textbuf_dest = NULL;
\r
1531 //gui::IGUIWindow* input_window = NULL;
\r
1532 gui::IGUIStaticText* input_guitext = NULL;
\r
1538 bool first_loop_after_window_activation = true;
\r
1540 // Time is in milliseconds
\r
1541 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1542 // NOTE: Have to call run() between calls of this to update the timer
\r
1543 u32 lasttime = device->getTimer()->getTime();
\r
1545 while(device->run())
\r
1547 // Hilight boxes collected during the loop and displayed
\r
1548 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1551 std::wstring infotext;
\r
1553 //TimeTaker //timer1("//timer1", device);
\r
1555 // Time of frame without fps limit
\r
1559 // not using getRealTime is necessary for wine
\r
1560 u32 time = device->getTimer()->getTime();
\r
1561 if(time > lasttime)
\r
1562 busytime_u32 = time - lasttime;
\r
1565 busytime = busytime_u32 / 1000.0;
\r
1568 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1570 // Absolutelu necessary for wine!
\r
1577 //updateViewingRange(dtime, &client);
\r
1578 updateViewingRange(busytime, &client);
\r
1585 float fps_max = g_settings.getFloat("fps_max");
\r
1586 u32 frametime_min = 1000./fps_max;
\r
1588 if(busytime_u32 < frametime_min)
\r
1590 u32 sleeptime = frametime_min - busytime_u32;
\r
1591 device->sleep(sleeptime);
\r
1595 // Absolutelu necessary for wine!
\r
1599 Time difference calculation
\r
1601 f32 dtime; // in seconds
\r
1603 u32 time = device->getTimer()->getTime();
\r
1604 if(time > lasttime)
\r
1605 dtime = (time - lasttime) / 1000.0;
\r
1611 Time average and jitter calculation
\r
1614 static f32 dtime_avg1 = 0.0;
\r
1615 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1616 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1618 static f32 dtime_jitter1_max_sample = 0.0;
\r
1619 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1621 static f32 jitter1_max = 0.0;
\r
1622 static f32 counter = 0.0;
\r
1623 if(dtime_jitter1 > jitter1_max)
\r
1624 jitter1_max = dtime_jitter1;
\r
1629 dtime_jitter1_max_sample = jitter1_max;
\r
1630 dtime_jitter1_max_fraction
\r
1631 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1632 jitter1_max = 0.0;
\r
1635 Control freetime ratio
\r
1637 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1639 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1640 g_freetime_ratio += 0.01;
\r
1644 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1645 g_freetime_ratio -= 0.01;
\r
1651 Busytime average and jitter calculation
\r
1654 static f32 busytime_avg1 = 0.0;
\r
1655 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1656 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1658 static f32 busytime_jitter1_max_sample = 0.0;
\r
1659 static f32 busytime_jitter1_min_sample = 0.0;
\r
1661 static f32 jitter1_max = 0.0;
\r
1662 static f32 jitter1_min = 0.0;
\r
1663 static f32 counter = 0.0;
\r
1664 if(busytime_jitter1 > jitter1_max)
\r
1665 jitter1_max = busytime_jitter1;
\r
1666 if(busytime_jitter1 < jitter1_min)
\r
1667 jitter1_min = busytime_jitter1;
\r
1669 if(counter > 0.0){
\r
1671 busytime_jitter1_max_sample = jitter1_max;
\r
1672 busytime_jitter1_min_sample = jitter1_min;
\r
1673 jitter1_max = 0.0;
\r
1674 jitter1_min = 0.0;
\r
1679 Debug info for client
\r
1682 static float counter = 0.0;
\r
1687 client.printDebugInfo(std::cout);
\r
1692 Input handler step()
\r
1694 g_input->step(dtime);
\r
1705 Player speed control
\r
1708 if(g_game_focused)
\r
1715 bool a_superspeed,
\r
1718 PlayerControl control(
\r
1719 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1720 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1721 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1722 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1723 g_input->isKeyDown(irr::KEY_SPACE),
\r
1724 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1728 client.setPlayerControl(control);
\r
1732 // Set every key to inactive
\r
1733 PlayerControl control;
\r
1734 client.setPlayerControl(control);
\r
1739 Process environment
\r
1743 //TimeTaker timer("client.step(dtime)", device);
\r
1744 client.step(dtime);
\r
1745 //client.step(dtime_avg1);
\r
1748 if(server != NULL)
\r
1750 //TimeTaker timer("server->step(dtime)", device);
\r
1751 server->step(dtime);
\r
1754 v3f player_position = client.getPlayerPosition();
\r
1756 //TimeTaker //timer2("//timer2", device);
\r
1759 Mouse and camera control
\r
1762 if(device->isWindowActive() && g_game_focused)
\r
1764 device->getCursorControl()->setVisible(false);
\r
1766 if(first_loop_after_window_activation){
\r
1767 //std::cout<<"window active, first loop"<<std::endl;
\r
1768 first_loop_after_window_activation = false;
\r
1771 s32 dx = g_input->getMousePos().X - 320;
\r
1772 s32 dy = g_input->getMousePos().Y - 240;
\r
1773 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1774 camera_yaw -= dx*0.2;
\r
1775 camera_pitch += dy*0.2;
\r
1776 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1777 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1779 g_input->setMousePos(320, 240);
\r
1782 device->getCursorControl()->setVisible(true);
\r
1784 //std::cout<<"window inactive"<<std::endl;
\r
1785 first_loop_after_window_activation = true;
\r
1788 camera_yaw = wrapDegrees(camera_yaw);
\r
1789 camera_pitch = wrapDegrees(camera_pitch);
\r
1791 v3f camera_direction = v3f(0,0,1);
\r
1792 camera_direction.rotateYZBy(camera_pitch);
\r
1793 camera_direction.rotateXZBy(camera_yaw);
\r
1795 v3f camera_position =
\r
1796 player_position + v3f(0, BS+BS/2, 0);
\r
1798 camera->setPosition(camera_position);
\r
1799 // *100.0 helps in large map coordinates
\r
1800 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1802 if(FIELD_OF_VIEW_TEST){
\r
1803 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1804 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1807 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1808 //TimeTaker timer("client.updateCamera", device);
\r
1809 client.updateCamera(camera_position, camera_direction);
\r
1813 //TimeTaker //timer3("//timer3", device);
\r
1816 Calculate what block is the crosshair pointing to
\r
1819 //u32 t1 = device->getTimer()->getRealTime();
\r
1821 //f32 d = 4; // max. distance
\r
1822 f32 d = 4; // max. distance
\r
1823 core::line3d<f32> shootline(camera_position,
\r
1824 camera_position + camera_direction * BS * (d+1));
\r
1826 MapBlockObject *selected_object = client.getSelectedObject
\r
1827 (d*BS, camera_position, shootline);
\r
1829 if(selected_object != NULL)
\r
1831 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1833 core::aabbox3d<f32> box_on_map
\r
1834 = selected_object->getSelectionBoxOnMap();
\r
1836 hilightboxes.push_back(box_on_map);
\r
1838 infotext = narrow_to_wide(selected_object->infoText());
\r
1840 if(g_input->getLeftClicked())
\r
1842 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1843 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1844 selected_object->getId(), g_selected_item);
\r
1846 else if(g_input->getRightClicked())
\r
1848 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1850 Check if we want to modify the object ourselves
\r
1852 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1854 dstream<<"Sign object right-clicked"<<std::endl;
\r
1858 input_guitext = guienv->addStaticText(L"",
\r
1859 core::rect<s32>(150,100,350,120),
\r
1861 false, // wordwrap?
\r
1864 input_guitext->setDrawBackground(true);
\r
1866 g_text_buffer = L"";
\r
1867 g_text_buffer_accepted = false;
\r
1868 textbuf_dest = new TextDestSign(
\r
1869 selected_object->getBlock()->getPos(),
\r
1870 selected_object->getId(),
\r
1874 Otherwise pass the event to the server as-is
\r
1878 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1879 selected_object->getId(), g_selected_item);
\r
1883 else // selected_object == NULL
\r
1886 bool nodefound = false;
\r
1888 v3s16 neighbourpos;
\r
1889 core::aabbox3d<f32> nodefacebox;
\r
1890 f32 mindistance = BS * 1001;
\r
1892 v3s16 pos_i = floatToInt(player_position);
\r
1894 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1898 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1899 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1900 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1901 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1902 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1903 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1905 for(s16 y = ystart; y <= yend; y++)
\r
1906 for(s16 z = zstart; z <= zend; z++)
\r
1907 for(s16 x = xstart; x <= xend; x++)
\r
1912 n = client.getNode(v3s16(x,y,z));
\r
1913 if(content_pointable(n.d) == false)
\r
1916 catch(InvalidPositionException &e)
\r
1922 v3f npf = intToFloat(np);
\r
1927 v3s16(0,0,1), // back
\r
1928 v3s16(0,1,0), // top
\r
1929 v3s16(1,0,0), // right
\r
1930 v3s16(0,0,-1), // front
\r
1931 v3s16(0,-1,0), // bottom
\r
1932 v3s16(-1,0,0), // left
\r
1938 if(n.d == CONTENT_TORCH)
\r
1940 v3s16 dir = unpackDir(n.dir);
\r
1941 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
1942 dir_f *= BS/2 - BS/6 - BS/20;
\r
1943 v3f cpf = npf + dir_f;
\r
1944 f32 distance = (cpf - camera_position).getLength();
\r
1946 core::aabbox3d<f32> box;
\r
1949 if(dir == v3s16(0,-1,0))
\r
1951 box = core::aabbox3d<f32>(
\r
1952 npf - v3f(BS/6, BS/2, BS/6),
\r
1953 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
1957 else if(dir == v3s16(0,1,0))
\r
1959 box = core::aabbox3d<f32>(
\r
1960 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
1961 npf + v3f(BS/6, BS/2, BS/6)
\r
1967 box = core::aabbox3d<f32>(
\r
1968 cpf - v3f(BS/6, BS/3, BS/6),
\r
1969 cpf + v3f(BS/6, BS/3, BS/6)
\r
1973 if(distance < mindistance)
\r
1975 if(box.intersectsWithLine(shootline))
\r
1979 neighbourpos = np;
\r
1980 mindistance = distance;
\r
1981 nodefacebox = box;
\r
1990 for(u16 i=0; i<6; i++)
\r
1992 v3f dir_f = v3f(dirs[i].X,
\r
1993 dirs[i].Y, dirs[i].Z);
\r
1994 v3f centerpoint = npf + dir_f * BS/2;
\r
1996 (centerpoint - camera_position).getLength();
\r
1998 if(distance < mindistance)
\r
2000 core::CMatrix4<f32> m;
\r
2001 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2003 // This is the back face
\r
2004 v3f corners[2] = {
\r
2005 v3f(BS/2, BS/2, BS/2),
\r
2006 v3f(-BS/2, -BS/2, BS/2+d)
\r
2009 for(u16 j=0; j<2; j++)
\r
2011 m.rotateVect(corners[j]);
\r
2012 corners[j] += npf;
\r
2015 core::aabbox3d<f32> facebox(corners[0]);
\r
2016 facebox.addInternalPoint(corners[1]);
\r
2018 if(facebox.intersectsWithLine(shootline))
\r
2022 neighbourpos = np + dirs[i];
\r
2023 mindistance = distance;
\r
2024 nodefacebox = facebox;
\r
2026 } // if distance < mindistance
\r
2028 } // regular block
\r
2033 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
2034 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2035 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
2037 static v3s16 nodepos_old(-1,-1,-1);
\r
2038 if(nodepos != nodepos_old){
\r
2039 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2040 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2041 nodepos_old = nodepos;
\r
2043 /*wchar_t positiontext[20];
\r
2044 swprintf(positiontext, 20, L"(%i,%i,%i)",
\r
2045 nodepos.X, nodepos.Y, nodepos.Z);
\r
2046 positiontextgui->setText(positiontext);*/
\r
2049 hilightboxes.push_back(nodefacebox);
\r
2051 if(g_input->getLeftClicked())
\r
2053 //std::cout<<DTIME<<"Removing node"<<std::endl;
\r
2054 //client.removeNode(nodepos);
\r
2055 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2056 client.clickGround(0, nodepos, neighbourpos, g_selected_item);
\r
2058 if(g_input->getRightClicked())
\r
2060 //std::cout<<DTIME<<"Placing node"<<std::endl;
\r
2061 //client.addNodeFromInventory(neighbourpos, g_selected_item);
\r
2062 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2063 client.clickGround(1, nodepos, neighbourpos, g_selected_item);
\r
2067 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
2068 //positiontextgui->setText(L"");
\r
2071 } // selected_object == NULL
\r
2073 g_input->resetLeftClicked();
\r
2074 g_input->resetRightClicked();
\r
2077 Calculate stuff for drawing
\r
2080 v2u32 screensize = driver->getScreenSize();
\r
2081 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
2083 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2086 Update gui stuff (0ms)
\r
2089 //TimeTaker guiupdatetimer("Gui updating", device);
\r
2092 wchar_t temptext[100];
\r
2094 static float drawtime_avg = 0;
\r
2095 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2096 static float scenetime_avg = 0;
\r
2097 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2098 static float endscenetime_avg = 0;
\r
2099 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2101 swprintf(temptext, 100, L"Minetest-c55 ("
\r
2103 L", R: range_all=%i"
\r
2105 L" drawtime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2107 g_viewing_range_all,
\r
2113 guitext->setText(temptext);
\r
2117 wchar_t temptext[100];
\r
2118 /*swprintf(temptext, 100,
\r
2120 L"% .3f < btime_jitter < % .3f"
\r
2121 L", dtime_jitter = % .1f %%"
\r
2122 //L", ftime_ratio = % .3f"
\r
2124 busytime_jitter1_min_sample,
\r
2125 busytime_jitter1_max_sample,
\r
2126 dtime_jitter1_max_fraction * 100.0
\r
2127 //g_freetime_ratio
\r
2129 swprintf(temptext, 100,
\r
2130 L"(% .1f, % .1f, % .1f)"
\r
2131 L" (% .3f < btime_jitter < % .3f"
\r
2132 L", dtime_jitter = % .1f %%)",
\r
2133 player_position.X/BS,
\r
2134 player_position.Y/BS,
\r
2135 player_position.Z/BS,
\r
2136 busytime_jitter1_min_sample,
\r
2137 busytime_jitter1_max_sample,
\r
2138 dtime_jitter1_max_fraction * 100.0
\r
2141 guitext2->setText(temptext);
\r
2145 /*wchar_t temptext[100];
\r
2146 swprintf(temptext, 100,
\r
2147 SWPRINTF_CHARSTRING,
\r
2148 infotext.substr(0,99).c_str()
\r
2151 guitext_info->setText(temptext);*/
\r
2153 guitext_info->setText(infotext.c_str());
\r
2160 static u16 old_selected_item = 65535;
\r
2161 if(client.getLocalInventoryUpdated()
\r
2162 || g_selected_item != old_selected_item)
\r
2164 old_selected_item = g_selected_item;
\r
2165 //std::cout<<"Updating local inventory"<<std::endl;
\r
2166 client.getLocalInventory(local_inventory);
\r
2167 quick_inventory->setSelection(g_selected_item);
\r
2168 quick_inventory->update();
\r
2171 if(input_guitext != NULL)
\r
2173 /*wchar_t temptext[100];
\r
2174 swprintf(temptext, 100,
\r
2175 SWPRINTF_CHARSTRING,
\r
2176 g_text_buffer.substr(0,99).c_str()
\r
2178 input_guitext->setText(g_text_buffer.c_str());
\r
2184 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2186 input_guitext->remove();
\r
2187 input_guitext = NULL;
\r
2189 if(textbuf_dest != NULL)
\r
2191 std::string text = wide_to_narrow(g_text_buffer);
\r
2192 dstream<<"Sending text: "<<text<<std::endl;
\r
2193 textbuf_dest->sendText(text);
\r
2194 delete textbuf_dest;
\r
2195 textbuf_dest = NULL;
\r
2201 //guiupdatetimer.stop();
\r
2207 TimeTaker drawtimer("Drawing", device);
\r
2210 Background color is choosen based on whether the player is
\r
2211 much beyond the initial ground level
\r
2213 /*video::SColor bgcolor;
\r
2214 v3s16 p0 = Map::floatToInt(player_position);
\r
2215 // Does this make short random delays?
\r
2216 // NOTE: no need for this, sky doesn't show underground with
\r
2218 bool is_underground = client.isNodeUnderground(p0);
\r
2219 //bool is_underground = false;
\r
2220 if(is_underground == false)
\r
2221 bgcolor = video::SColor(255,90,140,200);
\r
2223 bgcolor = video::SColor(255,0,0,0);*/
\r
2225 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2226 video::SColor bgcolor = skycolor;
\r
2229 driver->beginScene(true, true, bgcolor);
\r
2233 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2236 TimeTaker timer("smgr", device);
\r
2238 scenetime = timer.stop(true);
\r
2242 //TimeTaker timer9("auxiliary drawings", device);
\r
2245 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2246 displaycenter + core::vector2d<s32>(10,0),
\r
2247 video::SColor(255,255,255,255));
\r
2248 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2249 displaycenter + core::vector2d<s32>(0,10),
\r
2250 video::SColor(255,255,255,255));
\r
2253 //TimeTaker //timer10("//timer10", device);
\r
2255 video::SMaterial m;
\r
2257 m.Lighting = false;
\r
2258 driver->setMaterial(m);
\r
2260 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2262 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2263 i != hilightboxes.end(); i++)
\r
2265 /*std::cout<<"hilightbox min="
\r
2266 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2268 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2270 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2276 //TimeTaker //timer11("//timer11", device);
\r
2282 guienv->drawAll();
\r
2286 TimeTaker timer("endScene", device);
\r
2287 driver->endScene();
\r
2288 endscenetime = timer.stop(true);
\r
2291 drawtime = drawtimer.stop(true);
\r
2297 static s16 lastFPS = 0;
\r
2298 //u16 fps = driver->getFPS();
\r
2299 u16 fps = (1.0/dtime_avg1);
\r
2301 if (lastFPS != fps)
\r
2303 core::stringw str = L"Minetest [";
\r
2304 str += driver->getName();
\r
2308 device->setWindowCaption(str.c_str());
\r
2314 device->yield();*/
\r
2317 } // client is deleted at this point
\r
2322 In the end, delete the Irrlicht device.
\r
2327 catch(con::PeerNotFoundException &e)
\r
2329 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2331 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2333 This is what has to be done in every thread to get suitable debug info
\r
2335 catch(std::exception &e)
\r
2337 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2338 <<e.what()<<std::endl;
\r
2343 debugstreams_deinit();
\r