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
23 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
24 is an apparanet memory leak in irrlicht when using it (not sure)
\r
26 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
27 NOTE: Global locale is now set at initialization
\r
29 SUGGESTION: add a second lighting value to the MS nibble of param of
\r
30 air to tell how bright the air node is when there is no sunlight.
\r
31 When day changes to night, these two values can be interpolated.
\r
33 TODO: Fix address to be ipv6 compatible
\r
35 TODO: ESC Pause mode in which the cursor is not kept at the center of window.
\r
36 TODO: Stop player if focus of window is taken away (go to pause mode)
\r
37 TODO: Optimize and fix makeFastFace or whatever it's called
\r
38 - Face calculation is the source of CPU usage on the client
\r
39 SUGGESTION: The client will calculate and send lighting changes and
\r
40 the server will randomly check some of them and kick the client out
\r
41 if it fails to calculate them right.
\r
42 - Actually, it could just start ignoring them and calculate them
\r
44 SUGGESTION: Combine MapBlock's face caches to so big pieces that VBO
\r
46 - That is >500 vertices
\r
48 TODO: Better dungeons
\r
49 TODO: There should be very slight natural caves also, starting from
\r
50 only a straightened-up cliff
\r
52 TODO: Changing of block with mouse wheel or something
\r
57 - One single map container with ids as keys
\r
60 TODO: - Keep track of the place of the mob in the last few hundreth's
\r
61 of a second - then, if a player hits it, take the value that is
\r
62 avg_rtt/2 before the moment the packet is received.
\r
65 SUGGESTION: Modify client to calculate single changes asynchronously
\r
67 TODO: Moving players more smoothly. Calculate moving animation from
\r
68 data sent by server.
\r
70 TODO: There are some lighting-related todos and fixmes in
\r
71 ServerMap::emergeBlock
\r
73 TODO: Make a dirt node and use it under water
\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: Set server to automatically find a good spawning place in some
\r
85 place where there is water and land.
\r
86 - Map to have a getWalkableNear(p)
\r
88 TODO: Transfer more blocks in a single packet
\r
89 SUGG: A blockdata combiner class, to which blocks are added and at
\r
90 destruction it sends all the stuff in as few packets as possible.
\r
92 TODO: If player is on ground, mainly fetch ground-level blocks
\r
93 TODO: Fetch stuff mainly from the viewing direction
\r
95 TODO: Expose Connection's seqnums and ACKs to server and client.
\r
96 - This enables saving many packets and making a faster connection
\r
97 - This also enables server to check if client has received the
\r
98 most recent block sent, for example.
\r
100 TODO: Add a sane bandwidth throttling system to Connection
\r
102 FIXME: There still are *some* tiny glitches in lighting as seen from
\r
103 the client side. The server calculates them right but sometimes
\r
104 they don't get transferred properly.
\r
105 - Server probably checks that a block is not sent, then continues
\r
106 to sending it, then the emerge thread marks it as unsent and then
\r
107 the sender sends the block as it was before emerging?
\r
108 TODO: How about adding a "revision" field to MapBlocks?
\r
110 SUGG: More fine-grained control of client's dumping of blocks from
\r
112 - ...What does this mean in the first place?
\r
114 TODO: Somehow prioritize the sending of blocks and combine the block
\r
116 - Take two blocks to be sent next from each client and assign
\r
117 a priority value to them
\r
118 - Priority is the same as distance from player
\r
119 - Take the highest priority ones and send them. Send as many as
\r
120 fits in the global send queue maximum length (sum of lengths
\r
122 TODO: Make the amount of blocks sending to client and the total
\r
123 amount of blocks dynamically limited. Transferring blocks is the
\r
124 main network eater of this system, so it is the one that has
\r
125 to be throttled so that RTTs stay low.
\r
126 FIXME: There is a bug that sometimes the EmergeThread bumps to
\r
127 the client's emerge counter being already 0, and also the
\r
128 sending queue size of the client can float to 1 or 2, which
\r
129 stops the map from loading at all.
\r
130 - A quick hack could be applied to ignore the error of
\r
131 being at 0 and timing out old entries
\r
132 SUGG: Make client send GOTBLOCKS before updating meshes
\r
134 TODO: Server to load starting inventory from disk
\r
136 TODO: PLayers to only be hidden when the client quits.
\r
137 TODO: - Players to be saved on disk, with inventory
\r
138 TODO: Players to be saved as text in map/players/<name>
\r
140 SUGGESTION: A map editing mode (similar to dedicated server mode)
\r
142 TODO: Maybe: Create a face calculation queue on the client that is
\r
143 processed in a separate thread
\r
144 TODO: Make client's mesh updates to happen in a thread similar to
\r
145 server's EmergeThread.
\r
146 - This is not really needed, mesh update is really fast
\r
147 - Instead, the lighting update can be slow
\r
148 - So, this todo is not really a todo. It is a not-todo.
\r
149 SUGG: Make server to send all modified blocks after a node change
\r
150 after all the stuff including lighting have been updated
\r
152 TODO: Make fetching sector's blocks more efficient when rendering
\r
153 sectors that have very large amounts of blocks (on client)
\r
155 TODO: Make the video backend selectable
\r
157 Block object server side:
\r
158 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
159 - For all blocks in the buffer, objects are stepped(). This
\r
160 means they are active.
\r
161 - TODO: A global active buffer is needed for the server
\r
162 - TODO: All blocks going in and out of the buffer are recorded.
\r
163 - TODO: For outgoing blocks, a timestamp is written.
\r
164 - TODO: For incoming blocks, the time difference is calculated and
\r
165 objects are stepped according to it.
\r
166 TODO: A timestamp to blocks
\r
168 SUGG: Add a time value to the param of footstepped grass and check it
\r
169 against a global timer when a block is accessed, to make old
\r
173 TODO: Add config parameters for server's sending and generating distance
\r
175 TODO: Copy the text of the last picked sign to inventory in creative
\r
178 TODO: Untie client network operations from framerate
\r
180 SUGG: Make a copy of close-range environment on client for showing
\r
181 on screen, with minimal mutexes to slow the main loop down
\r
183 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
184 it by sending more stuff in a single packet.
\r
185 - Add a packet queue to RemoteClient, from which packets will be
\r
186 combined with object data packets
\r
187 - This is not exactly trivial: the object data packets are
\r
188 sometimes very big by themselves
\r
190 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
191 - This will allow saving ages of rats on disk but not sending
\r
194 TODO: Get rid of GotSplitPacketException
\r
198 TODO: Check what goes wrong with caching map to disk (Kray)
\r
200 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
203 ======================================================================
\r
205 TODO: Implement lighting using VoxelManipulator
\r
207 ======================================================================
\r
212 Setting this to 1 enables a special camera mode that forces
\r
213 the renderers to think that the camera statically points from
\r
214 the starting place to a static direction.
\r
216 This allows one to move around with the player and see what
\r
217 is actually drawn behind solid things and behind the player.
\r
219 #define FIELD_OF_VIEW_TEST 0
\r
221 #ifdef UNITTEST_DISABLE
\r
223 #pragma message ("Disabling unit tests")
\r
225 #warning "Disabling unit tests"
\r
227 // Disable unit tests
\r
228 #define ENABLE_TESTS 0
\r
230 // Enable unit tests
\r
231 #define ENABLE_TESTS 1
\r
235 #pragma comment(lib, "Irrlicht.lib")
\r
236 #pragma comment(lib, "jthread.lib")
\r
237 // This would get rid of the console window
\r
238 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
242 #define WIN32_LEAN_AND_MEAN
\r
243 #include <windows.h>
\r
244 #define sleep_ms(x) Sleep(x)
\r
246 #include <unistd.h>
\r
247 #define sleep_ms(x) usleep(x*1000)
\r
250 #include <iostream>
\r
253 #include <jmutexautolock.h>
\r
254 #include "common_irrlicht.h"
\r
257 #include "player.h"
\r
260 #include "environment.h"
\r
261 #include "server.h"
\r
262 #include "client.h"
\r
263 #include "serialization.h"
\r
264 #include "constants.h"
\r
265 #include "strfnd.h"
\r
266 #include "porting.h"
\r
267 #include <locale.h>
\r
269 IrrlichtDevice *g_device = NULL;
\r
271 const char *g_material_filenames[MATERIALS_COUNT] =
\r
273 "../data/stone.png",
\r
274 "../data/grass.png",
\r
275 "../data/water.png",
\r
276 "../data/light.png",
\r
277 "../data/tree.png",
\r
278 "../data/leaves.png",
\r
279 "../data/grass_footsteps.png",
\r
280 "../data/mese.png",
\r
284 video::SMaterial g_materials[MATERIALS_COUNT];
\r
285 //video::SMaterial g_mesh_materials[3];
\r
287 // All range-related stuff below is locked behind this
\r
288 JMutex g_range_mutex;
\r
290 // Blocks are viewed in this range from the player
\r
291 s16 g_viewing_range_nodes = 60;
\r
293 // This is updated by the client's fetchBlocks routine
\r
294 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
296 // If true, the preceding value has no meaning and all blocks
\r
297 // already existing in memory are drawn
\r
298 bool g_viewing_range_all = false;
\r
300 // This is the freetime ratio imposed by the dynamic viewing
\r
301 // range changing code.
\r
302 // It is controlled by the main loop to the smallest value that
\r
303 // inhibits glitches (dtime jitter) in the main loop.
\r
304 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
309 These are loaded from the config file.
\r
312 Settings g_settings;
\r
314 // Sets default settings
\r
315 void set_default_settings()
\r
317 g_settings.set("dedicated_server", "");
\r
320 g_settings.set("wanted_fps", "30");
\r
321 g_settings.set("fps_max", "60");
\r
322 g_settings.set("viewing_range_nodes_max", "300");
\r
323 g_settings.set("viewing_range_nodes_min", "20");
\r
324 g_settings.set("screenW", "");
\r
325 g_settings.set("screenH", "");
\r
326 g_settings.set("host_game", "");
\r
327 g_settings.set("port", "");
\r
328 g_settings.set("address", "");
\r
329 g_settings.set("name", "");
\r
330 g_settings.set("random_input", "false");
\r
331 g_settings.set("client_delete_unused_sectors_timeout", "1200");
\r
334 g_settings.set("creative_mode", "false");
\r
335 g_settings.set("heightmap_blocksize", "128");
\r
336 g_settings.set("height_randmax", "constant 70.0");
\r
337 g_settings.set("height_randfactor", "constant 0.6");
\r
338 g_settings.set("height_base", "linear 0 35 0");
\r
339 g_settings.set("plants_amount", "1.0");
\r
340 g_settings.set("ravines_amount", "1.0");
\r
341 g_settings.set("objectdata_interval", "0.2");
\r
342 g_settings.set("active_object_range", "2");
\r
343 g_settings.set("max_simultaneous_block_sends_per_client", "1");
\r
344 g_settings.set("max_simultaneous_block_sends_server_total", "4");
\r
351 //u16 g_selected_material = 0;
\r
352 u16 g_selected_item = 0;
\r
354 bool g_esc_pressed = false;
\r
356 std::wstring g_text_buffer;
\r
357 bool g_text_buffer_accepted = false;
\r
359 // When true, the mouse and keyboard are grabbed
\r
360 bool g_game_focused = true;
\r
367 std::ostream *dout_con_ptr = &dummyout;
\r
368 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
369 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
370 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
371 //std::ostream *dout_con_ptr = &dstream;
\r
372 //std::ostream *derr_con_ptr = &dstream;
\r
375 std::ostream *dout_server_ptr = &dstream;
\r
376 std::ostream *derr_server_ptr = &dstream;
\r
379 std::ostream *dout_client_ptr = &dstream;
\r
380 std::ostream *derr_client_ptr = &dstream;
\r
387 JMutex g_timestamp_mutex;
\r
388 //std::string g_timestamp;
\r
390 std::string getTimestamp()
\r
392 if(g_timestamp_mutex.IsInitialized()==false)
\r
394 JMutexAutoLock lock(g_timestamp_mutex);
\r
395 //return g_timestamp;
\r
396 time_t t = time(NULL);
\r
397 struct tm *tm = localtime(&t);
\r
399 strftime(cs, 20, "%H:%M:%S", tm);
\r
403 class MyEventReceiver : public IEventReceiver
\r
406 // This is the one method that we have to implement
\r
407 virtual bool OnEvent(const SEvent& event)
\r
409 // Remember whether each key is down or up
\r
410 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
412 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
414 if(event.KeyInput.PressedDown)
\r
416 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
417 if(g_game_focused == false)
\r
419 s16 key = event.KeyInput.Key;
\r
420 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
422 g_text_buffer_accepted = true;
\r
424 else if(key == irr::KEY_BACK)
\r
426 if(g_text_buffer.size() > 0)
\r
427 g_text_buffer = g_text_buffer.substr
\r
428 (0, g_text_buffer.size()-1);
\r
432 wchar_t wc = event.KeyInput.Char;
\r
434 g_text_buffer += wc;
\r
438 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
440 if(g_game_focused == true)
\r
442 dstream<<DTIME<<"ESC pressed"<<std::endl;
\r
443 g_esc_pressed = true;
\r
447 // Material selection
\r
448 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
450 if(g_game_focused == true)
\r
452 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
455 g_selected_item = 0;
\r
456 dstream<<DTIME<<"Selected item: "
\r
457 <<g_selected_item<<std::endl;
\r
461 // Viewing range selection
\r
462 if(event.KeyInput.Key == irr::KEY_KEY_R
\r
465 JMutexAutoLock lock(g_range_mutex);
\r
466 if(g_viewing_range_all)
\r
468 g_viewing_range_all = false;
\r
469 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
473 g_viewing_range_all = true;
\r
474 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
478 // Print debug stacks
\r
479 if(event.KeyInput.Key == irr::KEY_KEY_P
\r
482 dstream<<"-----------------------------------------"
\r
484 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
485 dstream<<"-----------------------------------------"
\r
487 debug_stacks_print();
\r
492 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
494 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
496 leftclicked = true;
\r
498 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
500 rightclicked = true;
\r
502 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
504 /*dstream<<"event.MouseInput.Wheel="
\r
505 <<event.MouseInput.Wheel<<std::endl;*/
\r
506 if(event.MouseInput.Wheel < 0)
\r
508 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
511 g_selected_item = 0;
\r
513 else if(event.MouseInput.Wheel > 0)
\r
515 if(g_selected_item > 0)
\r
518 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
526 // This is used to check whether a key is being held down
\r
527 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
529 return keyIsDown[keyCode];
\r
534 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
535 keyIsDown[i] = false;
\r
536 leftclicked = false;
\r
537 rightclicked = false;
\r
543 // We use this array to store the current state of each key
\r
544 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
555 virtual ~InputHandler()
\r
558 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
559 virtual v2s32 getMousePos() = 0;
\r
560 virtual void setMousePos(s32 x, s32 y) = 0;
\r
561 virtual bool getLeftClicked() = 0;
\r
562 virtual bool getRightClicked() = 0;
\r
563 virtual void resetLeftClicked() = 0;
\r
564 virtual void resetRightClicked() = 0;
\r
566 virtual void step(float dtime) {};
\r
568 virtual void clear() {};
\r
571 InputHandler *g_input = NULL;
\r
576 g_game_focused = true;
\r
581 g_game_focused = false;
\r
584 class RealInputHandler : public InputHandler
\r
587 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
589 m_receiver(receiver)
\r
592 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
594 return m_receiver->IsKeyDown(keyCode);
\r
596 virtual v2s32 getMousePos()
\r
598 return m_device->getCursorControl()->getPosition();
\r
600 virtual void setMousePos(s32 x, s32 y)
\r
602 m_device->getCursorControl()->setPosition(x, y);
\r
605 virtual bool getLeftClicked()
\r
607 if(g_game_focused == false)
\r
609 return m_receiver->leftclicked;
\r
611 virtual bool getRightClicked()
\r
613 if(g_game_focused == false)
\r
615 return m_receiver->rightclicked;
\r
617 virtual void resetLeftClicked()
\r
619 m_receiver->leftclicked = false;
\r
621 virtual void resetRightClicked()
\r
623 m_receiver->rightclicked = false;
\r
628 resetRightClicked();
\r
629 resetLeftClicked();
\r
632 IrrlichtDevice *m_device;
\r
633 MyEventReceiver *m_receiver;
\r
636 class RandomInputHandler : public InputHandler
\r
639 RandomInputHandler()
\r
641 leftclicked = false;
\r
642 rightclicked = false;
\r
643 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
644 keydown[i] = false;
\r
646 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
648 return keydown[keyCode];
\r
650 virtual v2s32 getMousePos()
\r
654 virtual void setMousePos(s32 x, s32 y)
\r
656 mousepos = v2s32(x,y);
\r
659 virtual bool getLeftClicked()
\r
661 return leftclicked;
\r
663 virtual bool getRightClicked()
\r
665 return rightclicked;
\r
667 virtual void resetLeftClicked()
\r
669 leftclicked = false;
\r
671 virtual void resetRightClicked()
\r
673 rightclicked = false;
\r
676 virtual void step(float dtime)
\r
679 static float counter1 = 0;
\r
683 counter1 = 0.1*Rand(1,10);
\r
684 /*if(g_selected_material < USEFUL_MATERIAL_COUNT-1)
\r
685 g_selected_material++;
\r
687 g_selected_material = 0;*/
\r
688 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
691 g_selected_item = 0;
\r
695 static float counter1 = 0;
\r
699 counter1 = 0.1*Rand(1, 40);
\r
700 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
704 static float counter1 = 0;
\r
708 counter1 = 0.1*Rand(1, 40);
\r
709 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
713 static float counter1 = 0;
\r
717 counter1 = 0.1*Rand(1, 40);
\r
718 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
722 static float counter1 = 0;
\r
726 counter1 = 0.1*Rand(1, 40);
\r
727 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
731 static float counter1 = 0;
\r
735 counter1 = 0.1*Rand(1, 20);
\r
736 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
740 static float counter1 = 0;
\r
744 counter1 = 0.1*Rand(1, 30);
\r
745 leftclicked = true;
\r
749 static float counter1 = 0;
\r
753 counter1 = 0.1*Rand(1, 20);
\r
754 rightclicked = true;
\r
757 mousepos += mousespeed;
\r
760 s32 Rand(s32 min, s32 max)
\r
762 return (rand()%(max-min+1))+min;
\r
765 bool keydown[KEY_KEY_CODES_COUNT];
\r
772 void updateViewingRange(f32 frametime, Client *client)
\r
774 // Range_all messes up frametime_avg
\r
775 if(g_viewing_range_all == true)
\r
778 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
780 // Initialize to the target value
\r
781 static float frametime_avg = 1.0/wanted_fps;
\r
782 frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
784 static f32 counter = 0;
\r
786 counter -= frametime;
\r
789 //counter = 1.0; //seconds
\r
790 counter = 0.5; //seconds
\r
792 //float freetime_ratio = 0.2;
\r
793 //float freetime_ratio = 0.4;
\r
794 float freetime_ratio = FREETIME_RATIO;
\r
796 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
798 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
800 static bool fraction_is_good = false;
\r
802 float fraction_good_threshold = 0.1;
\r
803 float fraction_bad_threshold = 0.25;
\r
804 float fraction_limit;
\r
805 // Use high limit if fraction is good AND the fraction would
\r
806 // lower the range. We want to keep the range fairly high.
\r
807 if(fraction_is_good && fraction > 1.0)
\r
808 fraction_limit = fraction_bad_threshold;
\r
810 fraction_limit = fraction_good_threshold;
\r
812 if(fabs(fraction - 1.0) < fraction_limit)
\r
814 fraction_is_good = true;
\r
819 fraction_is_good = false;
\r
822 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
823 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
824 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
825 <<" faction = "<<fraction<<std::endl;*/
\r
827 JMutexAutoLock lock(g_range_mutex);
\r
829 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
830 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
832 s16 n = (float)g_viewing_range_nodes / fraction;
\r
833 if(n < viewing_range_nodes_min)
\r
834 n = viewing_range_nodes_min;
\r
835 if(n > viewing_range_nodes_max)
\r
836 n = viewing_range_nodes_max;
\r
838 bool can_change = true;
\r
840 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
841 can_change = false;
\r
844 g_viewing_range_nodes = n;
\r
846 /*dstream<<"g_viewing_range_nodes = "
\r
847 <<g_viewing_range_nodes<<std::endl;*/
\r
850 class GUIQuickInventory : public IEventReceiver
\r
854 gui::IGUIEnvironment* env,
\r
855 gui::IGUIElement* parent,
\r
858 Inventory *inventory):
\r
859 m_itemcount(itemcount),
\r
860 m_inventory(inventory)
\r
862 core::rect<s32> imgsize(0,0,48,48);
\r
863 core::rect<s32> textsize(0,0,48,16);
\r
864 v2s32 spacing(0, 64);
\r
865 for(s32 i=0; i<m_itemcount; i++)
\r
867 m_images.push_back(env->addImage(
\r
868 imgsize + pos + spacing*i
\r
870 m_images[i]->setScaleImage(true);
\r
871 m_texts.push_back(env->addStaticText(
\r
873 textsize + pos + spacing*i,
\r
876 m_texts[i]->setBackgroundColor(
\r
877 video::SColor(128,0,0,0));
\r
878 m_texts[i]->setTextAlignment(
\r
880 gui::EGUIA_UPPERLEFT);
\r
884 virtual bool OnEvent(const SEvent& event)
\r
889 void setSelection(s32 i)
\r
898 start = m_selection - m_itemcount / 2;
\r
900 for(s32 i=0; i<m_itemcount; i++)
\r
904 if(j > (s32)m_inventory->getSize() - 1)
\r
905 j -= m_inventory->getSize();
\r
907 j += m_inventory->getSize();
\r
909 InventoryItem *item = m_inventory->getItem(j);
\r
913 m_images[i]->setImage(NULL);
\r
916 if(m_selection == j)
\r
917 swprintf(t, 10, L"<-");
\r
919 swprintf(t, 10, L"");
\r
920 m_texts[i]->setText(t);
\r
922 // The next ifs will segfault with a NULL pointer
\r
927 m_images[i]->setImage(item->getImage());
\r
930 if(m_selection == j)
\r
931 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
933 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
934 m_texts[i]->setText(t);
\r
940 core::array<gui::IGUIStaticText*> m_texts;
\r
941 core::array<gui::IGUIImage*> m_images;
\r
942 Inventory *m_inventory;
\r
946 int main(int argc, char *argv[])
\r
949 Low-level initialization
\r
952 bool disable_stderr = false;
\r
954 disable_stderr = true;
\r
957 // Initialize debug streams
\r
958 debugstreams_init(disable_stderr, DEBUGFILE);
\r
959 // Initialize debug stacks
\r
960 debug_stacks_init();
\r
962 DSTACK(__FUNCTION_NAME);
\r
968 Basic initialization
\r
971 // Initialize default settings
\r
972 set_default_settings();
\r
974 // Print startup message
\r
975 dstream<<DTIME<<"minetest-c55"
\r
976 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
977 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
980 // Set locale. This is for forcing '.' as the decimal point.
\r
981 std::locale::global(std::locale("C"));
\r
982 // This enables printing all characters in bitmap font
\r
983 setlocale(LC_CTYPE, "en_US");
\r
985 // Initialize sockets
\r
987 atexit(sockets_cleanup);
\r
989 // Initialize timestamp mutex
\r
990 g_timestamp_mutex.Init();
\r
1004 // Read config file
\r
1008 g_settings.readConfigFile(argv[1]);
\r
1012 const char *filenames[2] =
\r
1014 "../minetest.conf",
\r
1015 "../../minetest.conf"
\r
1018 for(u32 i=0; i<2; i++)
\r
1020 bool r = g_settings.readConfigFile(filenames[i]);
\r
1026 // Initialize random seed
\r
1029 g_range_mutex.Init();
\r
1030 assert(g_range_mutex.IsInitialized());
\r
1032 // Read map parameters from settings
\r
1034 HMParams hm_params;
\r
1035 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1036 hm_params.randmax = g_settings.get("height_randmax");
\r
1037 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1038 hm_params.base = g_settings.get("height_base");
\r
1040 MapParams map_params;
\r
1041 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1042 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1048 std::cout<<std::endl<<std::endl;
\r
1049 char templine[100];
\r
1052 bool dedicated = g_settings.getBoolAsk
\r
1053 ("dedicated_server", "Dedicated server?", false);
\r
1054 std::cout<<"dedicated = "<<dedicated<<std::endl;
\r
1057 u16 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1058 std::cout<<"-> "<<port<<std::endl;
\r
1062 DSTACK("Dedicated server branch");
\r
1064 std::cout<<std::endl;
\r
1065 std::cout<<"========================"<<std::endl;
\r
1066 std::cout<<"Running dedicated server"<<std::endl;
\r
1067 std::cout<<"========================"<<std::endl;
\r
1068 std::cout<<std::endl;
\r
1070 Server server("../map", hm_params, map_params);
\r
1071 server.start(port);
\r
1075 // This is kind of a hack but can be done like this
\r
1076 // because server.step() is very light
\r
1078 server.step(0.030);
\r
1080 static int counter = 0;
\r
1086 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1087 core::list<PlayerInfo>::Iterator i;
\r
1088 static u32 sum_old = 0;
\r
1089 u32 sum = PIChecksum(list);
\r
1090 if(sum != sum_old)
\r
1092 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1093 for(i=list.begin(); i!=list.end(); i++)
\r
1095 i->PrintLine(&std::cout);
\r
1105 bool hosting = false;
\r
1106 char connect_name[100] = "";
\r
1108 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1109 if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1111 std::cout<<g_settings.get("address")<<std::endl;
\r
1112 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1116 std::cin.getline(connect_name, 100);
\r
1119 if(connect_name[0] == 0){
\r
1120 snprintf(connect_name, 100, "127.0.0.1");
\r
1125 std::cout<<"-> hosting"<<std::endl;
\r
1127 std::cout<<"-> "<<connect_name<<std::endl;
\r
1129 char playername[PLAYERNAME_SIZE] = "";
\r
1130 if(g_settings.get("name") != "")
\r
1132 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1136 std::cout<<"Name of player: ";
\r
1137 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1139 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1142 Resolution selection
\r
1147 bool fullscreen = false;
\r
1149 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1151 screenW = atoi(g_settings.get("screenW").c_str());
\r
1152 screenH = atoi(g_settings.get("screenH").c_str());
\r
1156 u16 resolutions[][3] = {
\r
1157 //W, H, fullscreen
\r
1168 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1170 for(u16 i=0; i<res_count; i++)
\r
1172 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1173 <<resolutions[i][1];
\r
1174 if(resolutions[i][2])
\r
1175 std::cout<<" fullscreen"<<std::endl;
\r
1177 std::cout<<" windowed"<<std::endl;
\r
1179 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1180 std::cin.getline(templine, 100);
\r
1183 if(templine[0] == 0)
\r
1186 r0 = atoi(templine);
\r
1188 if(r0 > res_count || r0 == 0)
\r
1194 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1195 <<resolutions[i][1];
\r
1196 if(resolutions[i][2])
\r
1197 std::cout<<" fullscreen"<<std::endl;
\r
1199 std::cout<<" windowed"<<std::endl;
\r
1202 screenW = resolutions[r0-1][0];
\r
1203 screenH = resolutions[r0-1][1];
\r
1204 fullscreen = resolutions[r0-1][2];
\r
1209 MyEventReceiver receiver;
\r
1211 video::E_DRIVER_TYPE driverType;
\r
1214 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1215 driverType = video::EDT_OPENGL;
\r
1217 driverType = video::EDT_OPENGL;
\r
1220 // create device and exit if creation failed
\r
1222 IrrlichtDevice *device;
\r
1223 device = createDevice(driverType,
\r
1224 core::dimension2d<u32>(screenW, screenH),
\r
1225 16, fullscreen, false, false, &receiver);
\r
1227 /*device = createDevice(driverType,
\r
1228 core::dimension2d<u32>(screenW, screenH),
\r
1229 16, fullscreen, false, true, &receiver);*/
\r
1232 return 1; // could not create selected driver.
\r
1234 g_device = device;
\r
1236 device->setResizable(true);
\r
1238 if(g_settings.getBool("random_input"))
\r
1239 g_input = new RandomInputHandler();
\r
1241 g_input = new RealInputHandler(device, &receiver);
\r
1244 Continue initialization
\r
1247 video::IVideoDriver* driver = device->getVideoDriver();
\r
1248 // These make the textures not to show at all
\r
1249 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1250 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1252 //driver->setMinHardwareBufferVertexCount(1);
\r
1254 scene::ISceneManager* smgr = device->getSceneManager();
\r
1256 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1257 gui::IGUISkin* skin = guienv->getSkin();
\r
1258 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1260 skin->setFont(font);
\r
1261 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1262 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1263 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1264 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1265 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1266 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1268 const wchar_t *text = L"Loading and connecting...";
\r
1269 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1270 core::dimension2d<u32> textd = font->getDimension(text);
\r
1271 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1272 // Have to add a bit to disable the text from word wrapping
\r
1273 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1274 core::vector2d<s32> textsize(300, textd.Height);
\r
1275 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1277 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1278 text, textrect, false, false);
\r
1279 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1281 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1282 guienv->drawAll();
\r
1283 driver->endScene();
\r
1286 Initialize material array
\r
1289 //video::SMaterial g_materials[MATERIALS_COUNT];
\r
1290 for(u16 i=0; i<MATERIALS_COUNT; i++)
\r
1292 g_materials[i].Lighting = false;
\r
1293 g_materials[i].BackfaceCulling = false;
\r
1295 const char *filename = g_material_filenames[i];
\r
1296 if(filename != NULL){
\r
1297 video::ITexture *t = driver->getTexture(filename);
\r
1299 std::cout<<DTIME<<"Texture could not be loaded: \""
\r
1300 <<filename<<"\""<<std::endl;
\r
1303 g_materials[i].setTexture(0, driver->getTexture(filename));
\r
1305 //g_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);
\r
1306 g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1307 //g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
\r
1308 //g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1309 if(i == MATERIAL_WATER)
\r
1311 g_materials[i].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
\r
1312 //g_materials[i].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
\r
1316 /*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
\r
1317 g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
\r
1318 g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));
\r
1319 for(u32 i=0; i<3; i++)
\r
1321 g_mesh_materials[i].Lighting = false;
\r
1322 g_mesh_materials[i].BackfaceCulling = false;
\r
1323 g_mesh_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1324 g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1327 // Make a scope here for the client so that it gets removed
\r
1328 // before the irrlicht device
\r
1331 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1336 SharedPtr<Server> server;
\r
1338 server = new Server("../map", hm_params, map_params);
\r
1339 server->start(port);
\r
1346 // TODO: Get rid of the g_materials parameter or it's globalness
\r
1347 Client client(device, g_materials,
\r
1348 g_settings.getFloat("client_delete_unused_sectors_timeout"),
\r
1351 Address connect_address(0,0,0,0, port);
\r
1353 connect_address.Resolve(connect_name);
\r
1355 catch(ResolveError &e)
\r
1357 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1361 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1362 client.connect(connect_address);
\r
1365 while(client.connectedAndInitialized() == false)
\r
1368 if(server != NULL){
\r
1369 server->step(0.1);
\r
1374 catch(con::PeerNotFoundException &e)
\r
1376 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1381 Create the camera node
\r
1384 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1385 0, // Camera parent
\r
1386 v3f(BS*100, BS*2, BS*100), // Look from
\r
1387 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1391 if(camera == NULL)
\r
1394 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1396 camera->setFOV(FOV_ANGLE);
\r
1398 // Just so big a value that everything rendered is visible
\r
1399 camera->setFarValue(100000*BS);
\r
1401 /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1402 f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1404 camera->setFarValue(range);
\r
1408 video::EFT_FOG_LINEAR,
\r
1416 f32 camera_yaw = 0; // "right/left"
\r
1417 f32 camera_pitch = 0; // "up/down"
\r
1419 gui_loadingtext->remove();
\r
1422 Add some gui stuff
\r
1425 // First line of debug text
\r
1426 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1428 core::rect<s32>(5, 5, 5+600, 5+textsize.Y),
\r
1430 // Second line of debug text
\r
1431 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1433 core::rect<s32>(5, 5+(textsize.Y+5)*1, 5+600, (5+textsize.Y)*2),
\r
1436 // At the middle of the screen
\r
1437 // Object infos are shown in this
\r
1438 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1440 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1443 // This is a copy of the inventory that the client's environment has
\r
1444 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1446 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1447 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1450 Some statistics are collected in these
\r
1453 u32 scenetime = 0;
\r
1454 u32 endscenetime = 0;
\r
1462 virtual void sendText(std::string text) = 0;
\r
1465 struct TextDestSign : public TextDest
\r
1467 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1469 m_blockpos = blockpos;
\r
1471 m_client = client;
\r
1473 void sendText(std::string text)
\r
1475 dstream<<"Changing text of a sign object: "
\r
1476 <<text<<std::endl;
\r
1477 m_client->sendSignText(m_blockpos, m_id, text);
\r
1485 TextDest *textbuf_dest = NULL;
\r
1487 //gui::IGUIWindow* input_window = NULL;
\r
1488 gui::IGUIStaticText* input_guitext = NULL;
\r
1494 bool first_loop_after_window_activation = true;
\r
1496 // Time is in milliseconds
\r
1497 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1498 // NOTE: Have to call run() between calls of this to update the timer
\r
1499 u32 lasttime = device->getTimer()->getTime();
\r
1501 while(device->run())
\r
1503 // Hilight boxes collected during the loop and displayed
\r
1504 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1507 std::wstring infotext;
\r
1509 //TimeTaker //timer1("//timer1", device);
\r
1511 // Time of frame without fps limit
\r
1515 // not using getRealTime is necessary for wine
\r
1516 u32 time = device->getTimer()->getTime();
\r
1517 if(time > lasttime)
\r
1518 busytime_u32 = time - lasttime;
\r
1521 busytime = busytime_u32 / 1000.0;
\r
1524 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1526 // Absolutelu necessary for wine!
\r
1533 //updateViewingRange(dtime, &client);
\r
1534 updateViewingRange(busytime, &client);
\r
1541 float fps_max = g_settings.getFloat("fps_max");
\r
1542 u32 frametime_min = 1000./fps_max;
\r
1544 if(busytime_u32 < frametime_min)
\r
1546 u32 sleeptime = frametime_min - busytime_u32;
\r
1547 device->sleep(sleeptime);
\r
1551 // Absolutelu necessary for wine!
\r
1555 Time difference calculation
\r
1557 f32 dtime; // in seconds
\r
1559 u32 time = device->getTimer()->getTime();
\r
1560 if(time > lasttime)
\r
1561 dtime = (time - lasttime) / 1000.0;
\r
1567 Time average and jitter calculation
\r
1570 static f32 dtime_avg1 = 0.0;
\r
1571 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1572 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1574 static f32 dtime_jitter1_max_sample = 0.0;
\r
1575 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1577 static f32 jitter1_max = 0.0;
\r
1578 static f32 counter = 0.0;
\r
1579 if(dtime_jitter1 > jitter1_max)
\r
1580 jitter1_max = dtime_jitter1;
\r
1585 dtime_jitter1_max_sample = jitter1_max;
\r
1586 dtime_jitter1_max_fraction
\r
1587 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1588 jitter1_max = 0.0;
\r
1591 Control freetime ratio
\r
1593 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1595 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1596 g_freetime_ratio += 0.01;
\r
1600 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1601 g_freetime_ratio -= 0.01;
\r
1607 Busytime average and jitter calculation
\r
1610 static f32 busytime_avg1 = 0.0;
\r
1611 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1612 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1614 static f32 busytime_jitter1_max_sample = 0.0;
\r
1615 static f32 busytime_jitter1_min_sample = 0.0;
\r
1617 static f32 jitter1_max = 0.0;
\r
1618 static f32 jitter1_min = 0.0;
\r
1619 static f32 counter = 0.0;
\r
1620 if(busytime_jitter1 > jitter1_max)
\r
1621 jitter1_max = busytime_jitter1;
\r
1622 if(busytime_jitter1 < jitter1_min)
\r
1623 jitter1_min = busytime_jitter1;
\r
1625 if(counter > 0.0){
\r
1627 busytime_jitter1_max_sample = jitter1_max;
\r
1628 busytime_jitter1_min_sample = jitter1_min;
\r
1629 jitter1_max = 0.0;
\r
1630 jitter1_min = 0.0;
\r
1635 Debug info for client
\r
1638 static float counter = 0.0;
\r
1643 client.printDebugInfo(std::cout);
\r
1648 Input handler step()
\r
1650 g_input->step(dtime);
\r
1661 Player speed control
\r
1664 if(g_game_focused)
\r
1671 bool a_superspeed,
\r
1674 PlayerControl control(
\r
1675 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1676 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1677 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1678 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1679 g_input->isKeyDown(irr::KEY_SPACE),
\r
1680 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1684 client.setPlayerControl(control);
\r
1688 // Set every key to inactive
\r
1689 PlayerControl control;
\r
1690 client.setPlayerControl(control);
\r
1695 Process environment
\r
1699 //TimeTaker timer("client.step(dtime)", device);
\r
1700 client.step(dtime);
\r
1701 //client.step(dtime_avg1);
\r
1704 if(server != NULL)
\r
1706 //TimeTaker timer("server->step(dtime)", device);
\r
1707 server->step(dtime);
\r
1710 v3f player_position = client.getPlayerPosition();
\r
1712 //TimeTaker //timer2("//timer2", device);
\r
1715 Mouse and camera control
\r
1718 if(device->isWindowActive() && g_game_focused)
\r
1720 device->getCursorControl()->setVisible(false);
\r
1722 if(first_loop_after_window_activation){
\r
1723 //std::cout<<"window active, first loop"<<std::endl;
\r
1724 first_loop_after_window_activation = false;
\r
1727 s32 dx = g_input->getMousePos().X - 320;
\r
1728 s32 dy = g_input->getMousePos().Y - 240;
\r
1729 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1730 camera_yaw -= dx*0.2;
\r
1731 camera_pitch += dy*0.2;
\r
1732 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1733 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1735 g_input->setMousePos(320, 240);
\r
1738 device->getCursorControl()->setVisible(true);
\r
1740 //std::cout<<"window inactive"<<std::endl;
\r
1741 first_loop_after_window_activation = true;
\r
1744 camera_yaw = wrapDegrees(camera_yaw);
\r
1745 camera_pitch = wrapDegrees(camera_pitch);
\r
1747 v3f camera_direction = v3f(0,0,1);
\r
1748 camera_direction.rotateYZBy(camera_pitch);
\r
1749 camera_direction.rotateXZBy(camera_yaw);
\r
1751 v3f camera_position =
\r
1752 player_position + v3f(0, BS+BS/2, 0);
\r
1754 camera->setPosition(camera_position);
\r
1755 // *100.0 helps in large map coordinates
\r
1756 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1758 if(FIELD_OF_VIEW_TEST){
\r
1759 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1760 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1763 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1764 //TimeTaker timer("client.updateCamera", device);
\r
1765 client.updateCamera(camera_position, camera_direction);
\r
1769 //TimeTaker //timer3("//timer3", device);
\r
1772 Calculate what block is the crosshair pointing to
\r
1775 //u32 t1 = device->getTimer()->getRealTime();
\r
1777 //f32 d = 4; // max. distance
\r
1778 f32 d = 4; // max. distance
\r
1779 core::line3d<f32> shootline(camera_position,
\r
1780 camera_position + camera_direction * BS * (d+1));
\r
1782 MapBlockObject *selected_object = client.getSelectedObject
\r
1783 (d*BS, camera_position, shootline);
\r
1785 if(selected_object != NULL)
\r
1787 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1789 core::aabbox3d<f32> box_on_map
\r
1790 = selected_object->getSelectionBoxOnMap();
\r
1792 hilightboxes.push_back(box_on_map);
\r
1794 infotext = narrow_to_wide(selected_object->infoText());
\r
1796 if(g_input->getLeftClicked())
\r
1798 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1799 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1800 selected_object->getId(), g_selected_item);
\r
1802 else if(g_input->getRightClicked())
\r
1804 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1806 Check if we want to modify the object ourselves
\r
1808 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1810 dstream<<"Sign object right-clicked"<<std::endl;
\r
1814 input_guitext = guienv->addStaticText(L"",
\r
1815 core::rect<s32>(150,100,350,120),
\r
1817 false, // wordwrap?
\r
1820 input_guitext->setDrawBackground(true);
\r
1822 g_text_buffer = L"";
\r
1823 g_text_buffer_accepted = false;
\r
1824 textbuf_dest = new TextDestSign(
\r
1825 selected_object->getBlock()->getPos(),
\r
1826 selected_object->getId(),
\r
1830 Otherwise pass the event to the server as-is
\r
1834 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1835 selected_object->getId(), g_selected_item);
\r
1839 else // selected_object == NULL
\r
1842 bool nodefound = false;
\r
1844 v3s16 neighbourpos;
\r
1845 core::aabbox3d<f32> nodefacebox;
\r
1846 f32 mindistance = BS * 1001;
\r
1848 v3s16 pos_i = floatToInt(player_position);
\r
1850 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1854 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1855 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1856 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1857 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1858 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1859 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1861 for(s16 y = ystart; y <= yend; y++){
\r
1862 for(s16 z = zstart; z <= zend; z++){
\r
1863 for(s16 x = xstart; x <= xend; x++)
\r
1866 if(client.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){
\r
1869 }catch(InvalidPositionException &e){
\r
1874 v3f npf = intToFloat(np);
\r
1878 v3s16 directions[6] = {
\r
1879 v3s16(0,0,1), // back
\r
1880 v3s16(0,1,0), // top
\r
1881 v3s16(1,0,0), // right
\r
1887 for(u16 i=0; i<6; i++){
\r
1889 v3f dir_f = v3f(directions[i].X,
\r
1890 directions[i].Y, directions[i].Z);
\r
1891 v3f centerpoint = npf + dir_f * BS/2;
\r
1893 (centerpoint - camera_position).getLength();
\r
1895 if(distance < mindistance){
\r
1896 //std::cout<<DTIME<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;
\r
1897 //std::cout<<DTIME<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;
\r
1898 core::CMatrix4<f32> m;
\r
1899 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
1901 // This is the back face
\r
1902 v3f corners[2] = {
\r
1903 v3f(BS/2, BS/2, BS/2),
\r
1904 v3f(-BS/2, -BS/2, BS/2+d)
\r
1907 for(u16 j=0; j<2; j++){
\r
1908 m.rotateVect(corners[j]);
\r
1909 corners[j] += npf;
\r
1910 //std::cout<<DTIME<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;
\r
1913 //core::aabbox3d<f32> facebox(corners[0],corners[1]);
\r
1914 core::aabbox3d<f32> facebox(corners[0]);
\r
1915 facebox.addInternalPoint(corners[1]);
\r
1917 if(facebox.intersectsWithLine(shootline)){
\r
1920 neighbourpos = np + directions[i];
\r
1921 mindistance = distance;
\r
1922 nodefacebox = facebox;
\r
1930 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
1931 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
1932 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
1934 static v3s16 nodepos_old(-1,-1,-1);
\r
1935 if(nodepos != nodepos_old){
\r
1936 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
1937 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
1938 nodepos_old = nodepos;
\r
1940 /*wchar_t positiontext[20];
\r
1941 swprintf(positiontext, 20, L"(%i,%i,%i)",
\r
1942 nodepos.X, nodepos.Y, nodepos.Z);
\r
1943 positiontextgui->setText(positiontext);*/
\r
1946 hilightboxes.push_back(nodefacebox);
\r
1948 if(g_input->getLeftClicked())
\r
1950 //std::cout<<DTIME<<"Removing node"<<std::endl;
\r
1951 //client.removeNode(nodepos);
\r
1952 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
1953 client.clickGround(0, nodepos, neighbourpos, g_selected_item);
\r
1955 if(g_input->getRightClicked())
\r
1957 //std::cout<<DTIME<<"Placing node"<<std::endl;
\r
1958 //client.addNodeFromInventory(neighbourpos, g_selected_item);
\r
1959 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
1960 client.clickGround(1, nodepos, neighbourpos, g_selected_item);
\r
1964 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
1965 //positiontextgui->setText(L"");
\r
1968 } // selected_object == NULL
\r
1970 g_input->resetLeftClicked();
\r
1971 g_input->resetRightClicked();
\r
1974 Calculate stuff for drawing
\r
1977 v2u32 screensize = driver->getScreenSize();
\r
1978 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1980 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
1983 Update gui stuff (0ms)
\r
1986 //TimeTaker guiupdatetimer("Gui updating", device);
\r
1989 wchar_t temptext[100];
\r
1991 static float drawtime_avg = 0;
\r
1992 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
1993 static float scenetime_avg = 0;
\r
1994 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
1995 static float endscenetime_avg = 0;
\r
1996 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
1998 swprintf(temptext, 100, L"Minetest-c55 ("
\r
2000 L", R: range_all=%i"
\r
2002 L" drawtime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2004 g_viewing_range_all,
\r
2010 guitext->setText(temptext);
\r
2014 wchar_t temptext[100];
\r
2015 /*swprintf(temptext, 100,
\r
2017 L"% .3f < btime_jitter < % .3f"
\r
2018 L", dtime_jitter = % .1f %%"
\r
2019 //L", ftime_ratio = % .3f"
\r
2021 busytime_jitter1_min_sample,
\r
2022 busytime_jitter1_max_sample,
\r
2023 dtime_jitter1_max_fraction * 100.0
\r
2024 //g_freetime_ratio
\r
2026 swprintf(temptext, 100,
\r
2027 L"(% .1f, % .1f, % .1f)"
\r
2028 L" (% .3f < btime_jitter < % .3f"
\r
2029 L", dtime_jitter = % .1f %%)",
\r
2030 player_position.X/BS,
\r
2031 player_position.Y/BS,
\r
2032 player_position.Z/BS,
\r
2033 busytime_jitter1_min_sample,
\r
2034 busytime_jitter1_max_sample,
\r
2035 dtime_jitter1_max_fraction * 100.0
\r
2038 guitext2->setText(temptext);
\r
2042 /*wchar_t temptext[100];
\r
2043 swprintf(temptext, 100,
\r
2044 SWPRINTF_CHARSTRING,
\r
2045 infotext.substr(0,99).c_str()
\r
2048 guitext_info->setText(temptext);*/
\r
2050 guitext_info->setText(infotext.c_str());
\r
2057 static u16 old_selected_item = 65535;
\r
2058 if(client.getLocalInventoryUpdated()
\r
2059 || g_selected_item != old_selected_item)
\r
2061 old_selected_item = g_selected_item;
\r
2062 //std::cout<<"Updating local inventory"<<std::endl;
\r
2063 client.getLocalInventory(local_inventory);
\r
2064 quick_inventory->setSelection(g_selected_item);
\r
2065 quick_inventory->update();
\r
2068 if(input_guitext != NULL)
\r
2070 /*wchar_t temptext[100];
\r
2071 swprintf(temptext, 100,
\r
2072 SWPRINTF_CHARSTRING,
\r
2073 g_text_buffer.substr(0,99).c_str()
\r
2075 input_guitext->setText(g_text_buffer.c_str());
\r
2081 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2083 input_guitext->remove();
\r
2084 input_guitext = NULL;
\r
2086 if(textbuf_dest != NULL)
\r
2088 std::string text = wide_to_narrow(g_text_buffer);
\r
2089 dstream<<"Sending text: "<<text<<std::endl;
\r
2090 textbuf_dest->sendText(text);
\r
2091 delete textbuf_dest;
\r
2092 textbuf_dest = NULL;
\r
2098 //guiupdatetimer.stop();
\r
2104 TimeTaker drawtimer("Drawing", device);
\r
2107 Background color is choosen based on whether the player is
\r
2108 much beyond the initial ground level
\r
2110 /*video::SColor bgcolor;
\r
2111 v3s16 p0 = Map::floatToInt(player_position);
\r
2112 // Does this make short random delays?
\r
2113 // NOTE: no need for this, sky doesn't show underground with
\r
2115 bool is_underground = client.isNodeUnderground(p0);
\r
2116 //bool is_underground = false;
\r
2117 if(is_underground == false)
\r
2118 bgcolor = video::SColor(255,90,140,200);
\r
2120 bgcolor = video::SColor(255,0,0,0);*/
\r
2122 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2123 video::SColor bgcolor = skycolor;
\r
2126 driver->beginScene(true, true, bgcolor);
\r
2130 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2133 TimeTaker timer("smgr", device);
\r
2135 scenetime = timer.stop(true);
\r
2139 //TimeTaker timer9("auxiliary drawings", device);
\r
2142 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2143 displaycenter + core::vector2d<s32>(10,0),
\r
2144 video::SColor(255,255,255,255));
\r
2145 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2146 displaycenter + core::vector2d<s32>(0,10),
\r
2147 video::SColor(255,255,255,255));
\r
2150 //TimeTaker //timer10("//timer10", device);
\r
2152 video::SMaterial m;
\r
2154 m.Lighting = false;
\r
2155 driver->setMaterial(m);
\r
2157 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2159 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2160 i != hilightboxes.end(); i++)
\r
2162 /*std::cout<<"hilightbox min="
\r
2163 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2165 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2167 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2173 //TimeTaker //timer11("//timer11", device);
\r
2179 guienv->drawAll();
\r
2183 TimeTaker timer("endScene", device);
\r
2184 driver->endScene();
\r
2185 endscenetime = timer.stop(true);
\r
2188 drawtime = drawtimer.stop(true);
\r
2194 static s16 lastFPS = 0;
\r
2195 //u16 fps = driver->getFPS();
\r
2196 u16 fps = (1.0/dtime_avg1);
\r
2198 if (lastFPS != fps)
\r
2200 core::stringw str = L"Minetest [";
\r
2201 str += driver->getName();
\r
2205 device->setWindowCaption(str.c_str());
\r
2211 device->yield();*/
\r
2214 } // client is deleted at this point
\r
2219 In the end, delete the Irrlicht device.
\r
2224 catch(con::PeerNotFoundException &e)
\r
2226 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2228 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2230 This is what has to be done in every thread to get suitable debug info
\r
2232 catch(std::exception &e)
\r
2234 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2235 <<e.what()<<std::endl;
\r
2240 debugstreams_deinit();
\r