2 (c) 2010 Perttu Ahola <celeron55@gmail.com>
\r
6 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
7 is an apparanet memory leak in irrlicht when using it
\r
9 SUGGESTION: add a second lighting value to the MS nibble of param of
\r
10 air to tell how bright the air node is when there is no sunlight.
\r
11 When day changes to night, these two values can be interpolated.
\r
12 TODO: Fix address to be ipv6 compatible
\r
14 TODO: ESC Pause mode in which the cursor is not kept at the center of window.
\r
15 TODO: Stop player if focus of window is taken away (go to pause mode)
\r
16 TODO: Optimize and fix makeFastFace or whatever it's called
\r
17 - Face calculation is the source of CPU usage on the client
\r
18 SUGGESTION: The client will calculate and send lighting changes and
\r
19 the server will randomly check some of them and kick the client out
\r
20 if it fails to calculate them right.
\r
21 - Actually, it could just start ignoring them and calculate them
\r
23 SUGGESTION: Combine MapBlock's face caches to so big pieces that VBO
\r
25 - That is >500 vertices
\r
27 TODO: Better dungeons
\r
28 TODO: There should be very slight natural caves also, starting from
\r
29 only a straightened-up cliff
\r
31 TODO: Changing of block with mouse wheel or something
\r
36 - One single map container with ids as keys
\r
39 TODO: - Keep track of the place of the mob in the last few hundreth's
\r
40 of a second - then, if a player hits it, take the value that is
\r
41 avg_rtt/2 before the moment the packet is received.
\r
44 SUGGESTION: Modify client to calculate single changes asynchronously
\r
46 TODO: Moving players more smoothly. Calculate moving animation from
\r
47 data sent by server.
\r
49 TODO: There are some lighting-related todos and fixmes in
\r
50 ServerMap::emergeBlock
\r
52 TODO: Make a dirt node and use it under water
\r
54 FIXME: When a new sector is generated, it may change the ground level
\r
55 of it's and it's neighbors border that two blocks that are
\r
56 above and below each other and that are generated before and
\r
57 after the sector heightmap generation (order doesn't matter),
\r
58 can have a small gap between each other at the border.
\r
59 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
60 using for UnlimitedHeightmap? (getting all neighbors
\r
63 TODO: Set server to automatically find a good spawning place in some
\r
64 place where there is water and land.
\r
65 - Map to have a getWalkableNear(p)
\r
67 TODO: Transfer more blocks in a single packet
\r
68 SUGG: A blockdata combiner class, to which blocks are added and at
\r
69 destruction it sends all the stuff in as few packets as possible.
\r
71 TODO: If player is on ground, mainly fetch ground-level blocks
\r
72 TODO: Fetch stuff mainly from the viewing direction
\r
74 TODO: Expose Connection's seqnums and ACKs to server and client.
\r
75 - This enables saving many packets and making a faster connection
\r
76 - This also enables server to check if client has received the
\r
77 most recent block sent, for example.
\r
79 SUGG: Add a time value to the param of footstepped grass and check it
\r
80 against a global timer when a block is accessed, to make old
\r
83 FIXME: There still are *some* tiny glitches in lighting as seen from
\r
84 the client side. The server calculates them right but sometimes
\r
85 they don't get transferred properly.
\r
86 - Server probably checks that a block is not sent, then continues
\r
87 to sending it, then the emerge thread marks it as unsent and then
\r
88 the sender sends the block as it was before emerging?
\r
89 TODO: How about adding a "revision" field to MapBlocks?
\r
91 TODO: More fine-grained control of client's dumping of blocks from
\r
94 TODO: Somehow prioritize the sending of blocks and combine the block
\r
96 - Take two blocks to be sent next from each client and assign
\r
97 a priority value to them
\r
98 - Priority is the same as distance from player
\r
99 - Take the highest priority ones and send them. Send as many as
\r
100 fits in the global send queue maximum length (sum of lengths
\r
102 TODO: Make the amount of blocks sending to client and the total
\r
103 amount of blocks dynamically limited. Transferring blocks is the
\r
104 main network eater of this system, so it is the one that has
\r
105 to be throttled so that RTTs stay low.
\r
106 FIXME: There is a bug that sometimes the EmergeThread bumps to
\r
107 the client's emerge counter being already 0, and also the
\r
108 sending queue size of the client can float to 1 or 2, which
\r
109 stops the map from loading at all.
\r
110 - A quick hack could be applied to ignore the error of
\r
111 being at 0 and timing out old entries
\r
112 SUGG: Make client send GOTBLOCKS before updating meshes
\r
114 TODO: Server to load starting inventory from disk
\r
116 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
117 NOTE: Global locale is now set at initialization
\r
119 TODO: PLayers to only be hidden when the client quits.
\r
120 TODO: - Players to be saved on disk, with inventory
\r
121 TODO: Players to be saved as text in map/players/<name>
\r
123 SUGGESTION: A map editing mode (similar to dedicated server mode)
\r
125 TODO: Maybe: Create a face calculation queue on the client that is
\r
126 processed in a separate thread
\r
127 TODO: Make client's mesh updates to happen in a thread similar to
\r
128 server's EmergeThread.
\r
129 - This is not really needed, mesh update is really fast
\r
130 - Instead, the lighting update can be slow
\r
131 - So, this todo is not really a todo. It is a not-todo.
\r
132 SUGG: Make server to send all modified blocks after a node change
\r
133 after all the stuff including lighting have been updated
\r
135 TODO: Make fetching sector's blocks more efficient when rendering
\r
136 sectors that have very large amounts of blocks (on client)
\r
138 TODO: Make the video backend selectable
\r
140 TODO: A timestamp to blocks
\r
143 - The server sends all active objects of the active blocks
\r
144 at constant intervals. They should fit in a few packets.
\r
145 - The client keeps track of what blocks at the moment are
\r
146 having active objects in them.
\r
147 - All blocks going in and out of the active buffer are recorded.
\r
148 - For outgoing blocks, objects are removed from the blocks
\r
150 - For incoming blocks, objects are added to the blocks and
\r
154 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
155 - For all blocks in the buffer, objects are stepped(). This
\r
156 means they are active.
\r
157 - All blocks going in and out of the buffer are recorded.
\r
158 - For outgoing blocks, a timestamp is written.
\r
159 - For incoming blocks, the time difference is calculated and
\r
160 objects are stepped according to it.
\r
162 TODO: Add config parameters for server's sending and generating distance
\r
164 TODO: Make amount of trees and other plants configurable
\r
165 - Save to a metafile
\r
167 TODO: Copy the text of the last picked sign to inventory in creative
\r
170 TODO: Untie client network operations from framerate
\r
172 TODO: Make a copy of close-range environment on client for showing
\r
173 on screen, with minimal mutexes to slow the main loop down
\r
175 TODO: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
176 it by sending more stuff in a single packet.
\r
179 ======================================================================
\r
182 ======================================================================
\r
187 Setting this to 1 enables a special camera mode that forces
\r
188 the renderers to think that the camera statically points from
\r
189 the starting place to a static direction.
\r
191 This allows one to move around with the player and see what
\r
192 is actually drawn behind solid things etc.
\r
194 #define FIELD_OF_VIEW_TEST 0
\r
196 #ifdef UNITTEST_DISABLE
\r
198 #pragma message ("Disabling unit tests")
\r
200 #warning "Disabling unit tests"
\r
202 // Disable unit tests
\r
203 #define ENABLE_TESTS 0
\r
205 // Enable unit tests
\r
206 #define ENABLE_TESTS 1
\r
210 #pragma comment(lib, "Irrlicht.lib")
\r
211 #pragma comment(lib, "jthread.lib")
\r
212 // This would get rid of the console window
\r
213 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
217 #define WIN32_LEAN_AND_MEAN
\r
218 #include <windows.h>
\r
219 #define sleep_ms(x) Sleep(x)
\r
221 #include <unistd.h>
\r
222 #define sleep_ms(x) usleep(x*1000)
\r
225 #include <iostream>
\r
228 #include <jmutexautolock.h>
\r
229 #include "common_irrlicht.h"
\r
232 #include "player.h"
\r
235 #include "environment.h"
\r
236 #include "server.h"
\r
237 #include "client.h"
\r
238 #include "serialization.h"
\r
239 #include "constants.h"
\r
240 #include "strfnd.h"
\r
241 #include "porting.h"
\r
242 #include <locale.h>
\r
244 IrrlichtDevice *g_device = NULL;
\r
246 const char *g_material_filenames[MATERIALS_COUNT] =
\r
248 "../data/stone.png",
\r
249 "../data/grass.png",
\r
250 "../data/water.png",
\r
251 "../data/light.png",
\r
252 "../data/tree.png",
\r
253 "../data/leaves.png",
\r
254 "../data/grass_footsteps.png",
\r
258 video::SMaterial g_materials[MATERIALS_COUNT];
\r
259 //video::SMaterial g_mesh_materials[3];
\r
261 // All range-related stuff below is locked behind this
\r
262 JMutex g_range_mutex;
\r
264 // Blocks are generated in this range from the player
\r
265 // This is limited vertically to half by Client::fetchBlocks()
\r
266 s16 g_forcedfetch_range_nodes = FORCEDFETCH_RANGE;
\r
268 // Blocks are viewed in this range from the player
\r
269 s16 g_viewing_range_nodes = 60;
\r
271 // This is updated by the client's fetchBlocks routine
\r
272 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
274 // If true, the preceding value has no meaning and all blocks
\r
275 // already existing in memory are drawn
\r
276 bool g_viewing_range_all = false;
\r
278 // This is the freetime ratio imposed by the dynamic viewing
\r
279 // range changing code.
\r
280 // It is controlled by the main loop to the smallest value that
\r
281 // inhibits glitches (dtime jitter) in the main loop.
\r
282 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
287 These are loaded from the config file.
\r
290 std::string g_dedicated_server;
\r
293 float g_wanted_fps = FPS_DEFAULT_WANTED;
\r
294 float g_fps_max = FPS_DEFAULT_MAX;
\r
295 s16 g_viewing_range_nodes_max = 300;
\r
296 s16 g_viewing_range_nodes_min = 20;
\r
297 std::string g_screenW;
\r
298 std::string g_screenH;
\r
299 std::string g_host_game;
\r
300 std::string g_port;
\r
301 std::string g_address;
\r
302 std::string g_name;
\r
303 bool g_random_input = false;
\r
304 float g_client_delete_unused_sectors_timeout = 1200;
\r
307 bool g_creative_mode = false;
\r
308 MapgenParams g_mapgen_params;
\r
314 //u16 g_selected_material = 0;
\r
315 u16 g_selected_item = 0;
\r
317 bool g_esc_pressed = false;
\r
319 std::wstring g_text_buffer;
\r
320 bool g_text_buffer_accepted = false;
\r
322 // When true, the mouse and keyboard are grabbed
\r
323 bool g_game_focused = true;
\r
330 std::ostream *dout_con_ptr = &dummyout;
\r
331 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
332 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
333 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
334 //std::ostream *dout_con_ptr = &dstream;
\r
335 //std::ostream *derr_con_ptr = &dstream;
\r
338 std::ostream *dout_server_ptr = &dstream;
\r
339 std::ostream *derr_server_ptr = &dstream;
\r
342 std::ostream *dout_client_ptr = &dstream;
\r
343 std::ostream *derr_client_ptr = &dstream;
\r
349 // Returns false on EOF
\r
350 bool parseConfigObject(std::istream &is)
\r
352 // float g_wanted_fps
\r
353 // s16 g_viewing_range_nodes_max
\r
359 std::getline(is, line);
\r
360 //dstream<<"got line: \""<<line<<"\""<<std::endl;
\r
362 std::string trimmedline = trim(line);
\r
365 if(trimmedline[0] == '#')
\r
368 //dstream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
\r
370 Strfnd sf(trim(line));
\r
372 std::string name = sf.next("=");
\r
378 std::string value = sf.next("\n");
\r
379 value = trim(value);
\r
381 dstream<<"Config name=\""<<name<<"\" value=\""
\r
382 <<value<<"\""<<std::endl;
\r
384 if(name == "dedicated_server")
\r
385 g_dedicated_server = value;
\r
388 else if(name == "wanted_fps")
\r
390 g_wanted_fps = atof(value.c_str());
\r
392 else if(name == "fps_max")
\r
394 g_fps_max = atof(value.c_str());
\r
396 else if(name == "viewing_range_nodes_max")
\r
398 s32 v = atoi(value.c_str());
\r
403 g_viewing_range_nodes_max = v;
\r
405 else if(name == "viewing_range_nodes_min")
\r
407 s32 v = atoi(value.c_str());
\r
412 g_viewing_range_nodes_min = v;
\r
414 else if(name=="screenW")
\r
416 else if(name=="screenH")
\r
418 else if(name == "host_game")
\r
419 g_host_game = value;
\r
420 else if(name == "port")
\r
422 else if(name == "address")
\r
424 else if(name == "name")
\r
426 else if(name == "random_input")
\r
427 g_random_input = is_yes(value);
\r
428 else if(name == "client_delete_unused_sectors_timeout")
\r
430 std::istringstream vis(value);
\r
431 //vis.imbue(std::locale("C"));
\r
432 vis>>g_client_delete_unused_sectors_timeout;
\r
436 else if(name == "creative_mode")
\r
437 g_creative_mode = is_yes(value);
\r
438 else if(name == "mapgen_heightmap_blocksize")
\r
440 s32 d = atoi(value.c_str());
\r
441 if(d > 0 && (d & (d-1)) == 0)
\r
442 g_mapgen_params.heightmap_blocksize = d;
\r
444 dstream<<"Invalid value in config file: \""
\r
445 <<line<<"\""<<std::endl;
\r
447 else if(name == "mapgen_height_randmax")
\r
448 g_mapgen_params.height_randmax = value;
\r
449 else if(name == "mapgen_height_randfactor")
\r
450 g_mapgen_params.height_randfactor = value;
\r
451 else if(name == "mapgen_height_base")
\r
452 g_mapgen_params.height_base = value;
\r
453 else if(name == "mapgen_plants_amount")
\r
454 g_mapgen_params.plants_amount = value;
\r
458 dstream<<"Unknown option in config file: \""
\r
459 <<line<<"\""<<std::endl;
\r
465 // Returns true on success
\r
466 bool readConfigFile(const char *filename)
\r
468 std::ifstream is(filename);
\r
469 if(is.good() == false)
\r
471 dstream<<DTIME<<"Error opening configuration file: "
\r
472 <<filename<<std::endl;
\r
476 dstream<<DTIME<<"Parsing configuration file: "
\r
477 <<filename<<std::endl;
\r
479 while(parseConfigObject(is));
\r
488 JMutex g_timestamp_mutex;
\r
489 //std::string g_timestamp;
\r
491 std::string getTimestamp()
\r
493 if(g_timestamp_mutex.IsInitialized()==false)
\r
495 JMutexAutoLock lock(g_timestamp_mutex);
\r
496 //return g_timestamp;
\r
497 time_t t = time(NULL);
\r
498 struct tm *tm = localtime(&t);
\r
500 strftime(cs, 20, "%H:%M:%S", tm);
\r
504 class MyEventReceiver : public IEventReceiver
\r
507 // This is the one method that we have to implement
\r
508 virtual bool OnEvent(const SEvent& event)
\r
510 // Remember whether each key is down or up
\r
511 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
513 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
515 if(event.KeyInput.PressedDown)
\r
517 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
518 if(g_game_focused == false)
\r
520 s16 key = event.KeyInput.Key;
\r
521 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
523 g_text_buffer_accepted = true;
\r
525 else if(key == irr::KEY_BACK)
\r
527 if(g_text_buffer.size() > 0)
\r
528 g_text_buffer = g_text_buffer.substr
\r
529 (0, g_text_buffer.size()-1);
\r
533 wchar_t wc = event.KeyInput.Char;
\r
535 g_text_buffer += wc;
\r
539 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
541 if(g_game_focused == true)
\r
543 dstream<<DTIME<<"ESC pressed"<<std::endl;
\r
544 g_esc_pressed = true;
\r
548 // Material selection
\r
549 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
551 if(g_game_focused == true)
\r
553 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
556 g_selected_item = 0;
\r
557 dstream<<DTIME<<"Selected item: "
\r
558 <<g_selected_item<<std::endl;
\r
562 // Viewing range selection
\r
563 if(event.KeyInput.Key == irr::KEY_KEY_R
\r
566 JMutexAutoLock lock(g_range_mutex);
\r
567 if(g_viewing_range_all)
\r
569 g_viewing_range_all = false;
\r
570 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
574 g_viewing_range_all = true;
\r
575 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
581 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
583 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
585 leftclicked = true;
\r
587 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
589 rightclicked = true;
\r
591 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
593 /*dstream<<"event.MouseInput.Wheel="
\r
594 <<event.MouseInput.Wheel<<std::endl;*/
\r
595 if(event.MouseInput.Wheel < 0)
\r
597 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
600 g_selected_item = 0;
\r
602 else if(event.MouseInput.Wheel > 0)
\r
604 if(g_selected_item > 0)
\r
607 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
615 // This is used to check whether a key is being held down
\r
616 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
618 return keyIsDown[keyCode];
\r
623 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
624 keyIsDown[i] = false;
\r
625 leftclicked = false;
\r
626 rightclicked = false;
\r
632 // We use this array to store the current state of each key
\r
633 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
644 virtual ~InputHandler()
\r
647 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
648 virtual v2s32 getMousePos() = 0;
\r
649 virtual void setMousePos(s32 x, s32 y) = 0;
\r
650 virtual bool getLeftClicked() = 0;
\r
651 virtual bool getRightClicked() = 0;
\r
652 virtual void resetLeftClicked() = 0;
\r
653 virtual void resetRightClicked() = 0;
\r
655 virtual void step(float dtime) {};
\r
657 virtual void clear() {};
\r
660 InputHandler *g_input = NULL;
\r
665 g_game_focused = true;
\r
670 g_game_focused = false;
\r
673 class RealInputHandler : public InputHandler
\r
676 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
678 m_receiver(receiver)
\r
681 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
683 return m_receiver->IsKeyDown(keyCode);
\r
685 virtual v2s32 getMousePos()
\r
687 return m_device->getCursorControl()->getPosition();
\r
689 virtual void setMousePos(s32 x, s32 y)
\r
691 m_device->getCursorControl()->setPosition(x, y);
\r
694 virtual bool getLeftClicked()
\r
696 if(g_game_focused == false)
\r
698 return m_receiver->leftclicked;
\r
700 virtual bool getRightClicked()
\r
702 if(g_game_focused == false)
\r
704 return m_receiver->rightclicked;
\r
706 virtual void resetLeftClicked()
\r
708 m_receiver->leftclicked = false;
\r
710 virtual void resetRightClicked()
\r
712 m_receiver->rightclicked = false;
\r
717 resetRightClicked();
\r
718 resetLeftClicked();
\r
721 IrrlichtDevice *m_device;
\r
722 MyEventReceiver *m_receiver;
\r
725 class RandomInputHandler : public InputHandler
\r
728 RandomInputHandler()
\r
730 leftclicked = false;
\r
731 rightclicked = false;
\r
732 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
733 keydown[i] = false;
\r
735 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
737 return keydown[keyCode];
\r
739 virtual v2s32 getMousePos()
\r
743 virtual void setMousePos(s32 x, s32 y)
\r
745 mousepos = v2s32(x,y);
\r
748 virtual bool getLeftClicked()
\r
750 return leftclicked;
\r
752 virtual bool getRightClicked()
\r
754 return rightclicked;
\r
756 virtual void resetLeftClicked()
\r
758 leftclicked = false;
\r
760 virtual void resetRightClicked()
\r
762 rightclicked = false;
\r
765 virtual void step(float dtime)
\r
768 static float counter1 = 0;
\r
772 counter1 = 0.1*Rand(1,10);
\r
773 /*if(g_selected_material < USEFUL_MATERIAL_COUNT-1)
\r
774 g_selected_material++;
\r
776 g_selected_material = 0;*/
\r
777 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
780 g_selected_item = 0;
\r
784 static float counter1 = 0;
\r
788 counter1 = 0.1*Rand(1, 40);
\r
789 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
793 static float counter1 = 0;
\r
797 counter1 = 0.1*Rand(1, 40);
\r
798 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
802 static float counter1 = 0;
\r
806 counter1 = 0.1*Rand(1, 40);
\r
807 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
811 static float counter1 = 0;
\r
815 counter1 = 0.1*Rand(1, 40);
\r
816 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
820 static float counter1 = 0;
\r
824 counter1 = 0.1*Rand(1, 20);
\r
825 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
829 static float counter1 = 0;
\r
833 counter1 = 0.1*Rand(1, 30);
\r
834 leftclicked = true;
\r
838 static float counter1 = 0;
\r
842 counter1 = 0.1*Rand(1, 20);
\r
843 rightclicked = true;
\r
846 mousepos += mousespeed;
\r
849 s32 Rand(s32 min, s32 max)
\r
851 return (rand()%(max-min+1))+min;
\r
854 bool keydown[KEY_KEY_CODES_COUNT];
\r
861 void updateViewingRange(f32 frametime, Client *client)
\r
863 // Range_all messes up frametime_avg
\r
864 if(g_viewing_range_all == true)
\r
867 // Initialize to the target value
\r
868 static float frametime_avg = 1.0/g_wanted_fps;
\r
869 frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
871 static f32 counter = 0;
\r
873 counter -= frametime;
\r
876 //counter = 1.0; //seconds
\r
877 counter = 0.5; //seconds
\r
879 //float freetime_ratio = 0.2;
\r
880 //float freetime_ratio = 0.4;
\r
881 float freetime_ratio = FREETIME_RATIO;
\r
883 float frametime_wanted = (1.0/(g_wanted_fps/(1.0-freetime_ratio)));
\r
885 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
887 static bool fraction_is_good = false;
\r
889 float fraction_good_threshold = 0.1;
\r
890 float fraction_bad_threshold = 0.25;
\r
891 float fraction_limit;
\r
892 // Use high limit if fraction is good AND the fraction would
\r
893 // lower the range. We want to keep the range fairly high.
\r
894 if(fraction_is_good && fraction > 1.0)
\r
895 fraction_limit = fraction_bad_threshold;
\r
897 fraction_limit = fraction_good_threshold;
\r
899 if(fabs(fraction - 1.0) < fraction_limit)
\r
901 fraction_is_good = true;
\r
906 fraction_is_good = false;
\r
909 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
910 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
911 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
912 <<" faction = "<<fraction<<std::endl;*/
\r
914 JMutexAutoLock lock(g_range_mutex);
\r
916 s16 n = (float)g_viewing_range_nodes / fraction;
\r
917 if(n < g_viewing_range_nodes_min)
\r
918 n = g_viewing_range_nodes_min;
\r
919 if(n > g_viewing_range_nodes_max)
\r
920 n = g_viewing_range_nodes_max;
\r
922 bool can_change = true;
\r
924 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
925 can_change = false;
\r
928 g_viewing_range_nodes = n;
\r
930 /*dstream<<"g_viewing_range_nodes = "
\r
931 <<g_viewing_range_nodes<<std::endl;*/
\r
934 class GUIQuickInventory : public IEventReceiver
\r
938 gui::IGUIEnvironment* env,
\r
939 gui::IGUIElement* parent,
\r
942 Inventory *inventory):
\r
943 m_itemcount(itemcount),
\r
944 m_inventory(inventory)
\r
946 core::rect<s32> imgsize(0,0,48,48);
\r
947 core::rect<s32> textsize(0,0,48,16);
\r
948 v2s32 spacing(0, 64);
\r
949 for(s32 i=0; i<m_itemcount; i++)
\r
951 m_images.push_back(env->addImage(
\r
952 imgsize + pos + spacing*i
\r
954 m_images[i]->setScaleImage(true);
\r
955 m_texts.push_back(env->addStaticText(
\r
957 textsize + pos + spacing*i,
\r
960 m_texts[i]->setBackgroundColor(
\r
961 video::SColor(128,0,0,0));
\r
962 m_texts[i]->setTextAlignment(
\r
964 gui::EGUIA_UPPERLEFT);
\r
968 virtual bool OnEvent(const SEvent& event)
\r
973 void setSelection(s32 i)
\r
982 start = m_selection - m_itemcount / 2;
\r
984 for(s32 i=0; i<m_itemcount; i++)
\r
988 if(j > (s32)m_inventory->getSize() - 1)
\r
989 j -= m_inventory->getSize();
\r
991 j += m_inventory->getSize();
\r
993 InventoryItem *item = m_inventory->getItem(j);
\r
997 m_images[i]->setImage(NULL);
\r
1000 if(m_selection == j)
\r
1001 swprintf(t, 10, L"<-");
\r
1003 swprintf(t, 10, L"");
\r
1004 m_texts[i]->setText(t);
\r
1006 // The next ifs will segfault with a NULL pointer
\r
1011 m_images[i]->setImage(item->getImage());
\r
1014 if(m_selection == j)
\r
1015 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1017 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1018 m_texts[i]->setText(t);
\r
1024 core::array<gui::IGUIStaticText*> m_texts;
\r
1025 core::array<gui::IGUIImage*> m_images;
\r
1026 Inventory *m_inventory;
\r
1030 int main(int argc, char *argv[])
\r
1033 Low-level initialization
\r
1036 bool disable_stderr = false;
\r
1038 disable_stderr = true;
\r
1041 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1042 debug_stacks_init();
\r
1045 DSTACK(__FUNCTION_NAME);
\r
1051 Basic initialization
\r
1054 dstream<<DTIME<<"minetest-c55"
\r
1055 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1056 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1059 // Set locale. This is for forcing '.' as the decimal point.
\r
1060 std::locale::global(std::locale("C"));
\r
1061 // This enables printing all characters in bitmap font
\r
1062 setlocale(LC_CTYPE, "en_US");
\r
1064 // Initialize sockets
\r
1066 atexit(sockets_cleanup);
\r
1068 // Initialize timestamp mutex
\r
1069 g_timestamp_mutex.Init();
\r
1083 // Read config file
\r
1087 readConfigFile(argv[1]);
\r
1091 const char *filenames[2] =
\r
1093 "../minetest.conf",
\r
1094 "../../minetest.conf"
\r
1097 for(u32 i=0; i<2; i++)
\r
1099 bool r = readConfigFile(filenames[i]);
\r
1105 // Initialize random seed
\r
1108 g_range_mutex.Init();
\r
1109 assert(g_range_mutex.IsInitialized());
\r
1115 std::cout<<std::endl<<std::endl;
\r
1116 char templine[100];
\r
1118 std::cout<<"Dedicated server? [y = yes]: ";
\r
1119 if(g_dedicated_server != "")
\r
1121 std::cout<<g_dedicated_server<<std::endl;
\r
1122 snprintf(templine, 100, "%s", g_dedicated_server.c_str());
\r
1126 std::cin.getline(templine, 100);
\r
1129 bool dedicated = false;
\r
1130 if(templine[0] == 'y')
\r
1133 std::cout<<"-> yes"<<std::endl;
\r
1135 std::cout<<"-> no"<<std::endl;
\r
1137 std::cout<<"Port [empty=30000]: ";
\r
1140 std::cout<<g_port<<std::endl;
\r
1141 snprintf(templine, 100, "%s", g_port.c_str());
\r
1145 std::cin.getline(templine, 100);
\r
1147 unsigned short port;
\r
1148 if(templine[0] == 0)
\r
1151 port = atoi(templine);
\r
1152 std::cout<<"-> "<<port<<std::endl;
\r
1156 DSTACK("Dedicated server branch");
\r
1158 std::cout<<std::endl;
\r
1159 std::cout<<"========================"<<std::endl;
\r
1160 std::cout<<"Running dedicated server"<<std::endl;
\r
1161 std::cout<<"========================"<<std::endl;
\r
1162 std::cout<<std::endl;
\r
1164 Server server("../map", g_creative_mode, g_mapgen_params);
\r
1165 server.start(port);
\r
1169 // This is kind of a hack but can be done like this
\r
1170 // because server.step() is very light
\r
1174 static int counter = 0;
\r
1180 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1181 core::list<PlayerInfo>::Iterator i;
\r
1182 static u32 sum_old = 0;
\r
1183 u32 sum = PIChecksum(list);
\r
1184 if(sum != sum_old)
\r
1186 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1187 for(i=list.begin(); i!=list.end(); i++)
\r
1189 i->PrintLine(&std::cout);
\r
1199 bool hosting = false;
\r
1200 char connect_name[100] = "";
\r
1202 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1203 if(g_address != "" && is_yes(g_host_game) == false)
\r
1205 std::cout<<g_address<<std::endl;
\r
1206 snprintf(connect_name, 100, "%s", g_address.c_str());
\r
1210 std::cin.getline(connect_name, 100);
\r
1213 if(connect_name[0] == 0){
\r
1214 snprintf(connect_name, 100, "127.0.0.1");
\r
1219 std::cout<<"-> hosting"<<std::endl;
\r
1221 std::cout<<"-> "<<connect_name<<std::endl;
\r
1223 char playername[PLAYERNAME_SIZE] = "";
\r
1226 snprintf(playername, PLAYERNAME_SIZE, "%s", g_name.c_str());
\r
1230 std::cout<<"Name of player: ";
\r
1231 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1233 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1236 Resolution selection
\r
1241 bool fullscreen = false;
\r
1243 if(g_screenW != "" && g_screenH != "")
\r
1245 screenW = atoi(g_screenW.c_str());
\r
1246 screenH = atoi(g_screenH.c_str());
\r
1250 u16 resolutions[][3] = {
\r
1251 //W, H, fullscreen
\r
1262 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1264 for(u16 i=0; i<res_count; i++)
\r
1266 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1267 <<resolutions[i][1];
\r
1268 if(resolutions[i][2])
\r
1269 std::cout<<" fullscreen"<<std::endl;
\r
1271 std::cout<<" windowed"<<std::endl;
\r
1273 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1274 std::cin.getline(templine, 100);
\r
1277 if(templine[0] == 0)
\r
1280 r0 = atoi(templine);
\r
1282 if(r0 > res_count || r0 == 0)
\r
1288 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1289 <<resolutions[i][1];
\r
1290 if(resolutions[i][2])
\r
1291 std::cout<<" fullscreen"<<std::endl;
\r
1293 std::cout<<" windowed"<<std::endl;
\r
1296 screenW = resolutions[r0-1][0];
\r
1297 screenH = resolutions[r0-1][1];
\r
1298 fullscreen = resolutions[r0-1][2];
\r
1303 MyEventReceiver receiver;
\r
1305 video::E_DRIVER_TYPE driverType;
\r
1308 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1309 driverType = video::EDT_OPENGL;
\r
1311 driverType = video::EDT_OPENGL;
\r
1314 // create device and exit if creation failed
\r
1316 IrrlichtDevice *device;
\r
1317 device = createDevice(driverType,
\r
1318 core::dimension2d<u32>(screenW, screenH),
\r
1319 16, fullscreen, false, false, &receiver);
\r
1321 /*device = createDevice(driverType,
\r
1322 core::dimension2d<u32>(screenW, screenH),
\r
1323 16, fullscreen, false, true, &receiver);*/
\r
1326 return 1; // could not create selected driver.
\r
1328 g_device = device;
\r
1330 device->setResizable(true);
\r
1332 if(g_random_input)
\r
1333 g_input = new RandomInputHandler();
\r
1335 g_input = new RealInputHandler(device, &receiver);
\r
1338 Continue initialization
\r
1341 video::IVideoDriver* driver = device->getVideoDriver();
\r
1342 // These make the textures not to show at all
\r
1343 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1344 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1346 //driver->setMinHardwareBufferVertexCount(1);
\r
1348 scene::ISceneManager* smgr = device->getSceneManager();
\r
1350 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1351 gui::IGUISkin* skin = guienv->getSkin();
\r
1352 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1354 skin->setFont(font);
\r
1355 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1356 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1357 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1358 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1359 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1360 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1362 const wchar_t *text = L"Loading and connecting...";
\r
1363 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1364 core::dimension2d<u32> textd = font->getDimension(text);
\r
1365 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1366 // Have to add a bit to disable the text from word wrapping
\r
1367 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1368 core::vector2d<s32> textsize(300, textd.Height);
\r
1369 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1371 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1372 text, textrect, false, false);
\r
1373 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1375 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1376 guienv->drawAll();
\r
1377 driver->endScene();
\r
1380 Initialize material array
\r
1383 //video::SMaterial g_materials[MATERIALS_COUNT];
\r
1384 for(u16 i=0; i<MATERIALS_COUNT; i++)
\r
1386 g_materials[i].Lighting = false;
\r
1387 g_materials[i].BackfaceCulling = false;
\r
1389 const char *filename = g_material_filenames[i];
\r
1390 if(filename != NULL){
\r
1391 video::ITexture *t = driver->getTexture(filename);
\r
1393 std::cout<<DTIME<<"Texture could not be loaded: \""
\r
1394 <<filename<<"\""<<std::endl;
\r
1397 g_materials[i].setTexture(0, driver->getTexture(filename));
\r
1399 //g_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);
\r
1400 g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1401 //g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
\r
1402 //g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1403 if(i == MATERIAL_WATER)
\r
1405 g_materials[i].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
\r
1406 //g_materials[i].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
\r
1410 /*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
\r
1411 g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
\r
1412 g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));
\r
1413 for(u32 i=0; i<3; i++)
\r
1415 g_mesh_materials[i].Lighting = false;
\r
1416 g_mesh_materials[i].BackfaceCulling = false;
\r
1417 g_mesh_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1418 g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1421 // Make a scope here for the client so that it gets removed
\r
1422 // before the irrlicht device
\r
1425 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1430 SharedPtr<Server> server;
\r
1432 server = new Server("../map", g_creative_mode, g_mapgen_params);
\r
1433 server->start(port);
\r
1440 // TODO: Get rid of the g_materials parameter or it's globalness
\r
1441 Client client(device, g_materials,
\r
1442 g_client_delete_unused_sectors_timeout,
\r
1445 Address connect_address(0,0,0,0, port);
\r
1447 connect_address.Resolve(connect_name);
\r
1449 catch(ResolveError &e)
\r
1451 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1455 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1456 client.connect(connect_address);
\r
1459 while(client.connectedAndInitialized() == false)
\r
1462 if(server != NULL){
\r
1463 server->step(0.1);
\r
1468 catch(con::PeerNotFoundException &e)
\r
1470 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1475 Create the camera node
\r
1478 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1479 0, // Camera parent
\r
1480 v3f(BS*100, BS*2, BS*100), // Look from
\r
1481 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1485 if(camera == NULL)
\r
1488 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1490 camera->setFOV(FOV_ANGLE);
\r
1492 // Just so big a value that everything rendered is visible
\r
1493 camera->setFarValue(100000*BS);
\r
1495 /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1496 f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1498 camera->setFarValue(range);
\r
1502 video::EFT_FOG_LINEAR,
\r
1510 f32 camera_yaw = 0; // "right/left"
\r
1511 f32 camera_pitch = 0; // "up/down"
\r
1513 gui_loadingtext->remove();
\r
1516 Add some gui stuff
\r
1519 // First line of debug text
\r
1520 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1522 core::rect<s32>(5, 5, 5+600, 5+textsize.Y),
\r
1524 // Second line of debug text
\r
1525 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1527 core::rect<s32>(5, 5+(textsize.Y+5)*1, 5+600, (5+textsize.Y)*2),
\r
1530 // At the middle of the screen
\r
1531 // Object infos are shown in this
\r
1532 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1534 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1537 // This is a copy of the inventory that the client's environment has
\r
1538 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1540 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1541 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1544 Some statistics are collected in these
\r
1547 u32 scenetime = 0;
\r
1548 u32 endscenetime = 0;
\r
1556 virtual void sendText(std::string text) = 0;
\r
1559 struct TextDestSign : public TextDest
\r
1561 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1563 m_blockpos = blockpos;
\r
1565 m_client = client;
\r
1567 void sendText(std::string text)
\r
1569 dstream<<"Changing text of a sign object: "
\r
1570 <<text<<std::endl;
\r
1571 m_client->sendSignText(m_blockpos, m_id, text);
\r
1579 TextDest *textbuf_dest = NULL;
\r
1581 //gui::IGUIWindow* input_window = NULL;
\r
1582 gui::IGUIStaticText* input_guitext = NULL;
\r
1588 bool first_loop_after_window_activation = true;
\r
1590 // Time is in milliseconds
\r
1591 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1592 // NOTE: Have to call run() between calls of this to update the timer
\r
1593 u32 lasttime = device->getTimer()->getTime();
\r
1595 while(device->run())
\r
1597 // Hilight boxes collected during the loop and displayed
\r
1598 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1601 std::wstring infotext;
\r
1603 //TimeTaker //timer1("//timer1", device);
\r
1605 // Time of frame without fps limit
\r
1609 // not using getRealTime is necessary for wine
\r
1610 u32 time = device->getTimer()->getTime();
\r
1611 if(time > lasttime)
\r
1612 busytime_u32 = time - lasttime;
\r
1615 busytime = busytime_u32 / 1000.0;
\r
1618 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1620 // Absolutelu necessary for wine!
\r
1627 //updateViewingRange(dtime, &client);
\r
1628 updateViewingRange(busytime, &client);
\r
1635 float fps_max = g_fps_max;
\r
1636 u32 frametime_min = 1000./fps_max;
\r
1638 if(busytime_u32 < frametime_min)
\r
1640 u32 sleeptime = frametime_min - busytime_u32;
\r
1641 device->sleep(sleeptime);
\r
1645 // Absolutelu necessary for wine!
\r
1649 Time difference calculation
\r
1651 f32 dtime; // in seconds
\r
1653 u32 time = device->getTimer()->getTime();
\r
1654 if(time > lasttime)
\r
1655 dtime = (time - lasttime) / 1000.0;
\r
1661 Time average and jitter calculation
\r
1664 static f32 dtime_avg1 = 0.0;
\r
1665 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1666 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1668 static f32 dtime_jitter1_max_sample = 0.0;
\r
1669 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1671 static f32 jitter1_max = 0.0;
\r
1672 static f32 counter = 0.0;
\r
1673 if(dtime_jitter1 > jitter1_max)
\r
1674 jitter1_max = dtime_jitter1;
\r
1679 dtime_jitter1_max_sample = jitter1_max;
\r
1680 dtime_jitter1_max_fraction
\r
1681 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1682 jitter1_max = 0.0;
\r
1685 Control freetime ratio
\r
1687 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1689 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1690 g_freetime_ratio += 0.01;
\r
1694 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1695 g_freetime_ratio -= 0.01;
\r
1701 Busytime average and jitter calculation
\r
1704 static f32 busytime_avg1 = 0.0;
\r
1705 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1706 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1708 static f32 busytime_jitter1_max_sample = 0.0;
\r
1709 static f32 busytime_jitter1_min_sample = 0.0;
\r
1711 static f32 jitter1_max = 0.0;
\r
1712 static f32 jitter1_min = 0.0;
\r
1713 static f32 counter = 0.0;
\r
1714 if(busytime_jitter1 > jitter1_max)
\r
1715 jitter1_max = busytime_jitter1;
\r
1716 if(busytime_jitter1 < jitter1_min)
\r
1717 jitter1_min = busytime_jitter1;
\r
1719 if(counter > 0.0){
\r
1721 busytime_jitter1_max_sample = jitter1_max;
\r
1722 busytime_jitter1_min_sample = jitter1_min;
\r
1723 jitter1_max = 0.0;
\r
1724 jitter1_min = 0.0;
\r
1729 Debug info for client
\r
1732 static float counter = 0.0;
\r
1737 client.printDebugInfo(std::cout);
\r
1742 Input handler step()
\r
1744 g_input->step(dtime);
\r
1755 Player speed control
\r
1758 if(g_game_focused)
\r
1765 bool a_superspeed,
\r
1768 PlayerControl control(
\r
1769 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1770 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1771 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1772 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1773 g_input->isKeyDown(irr::KEY_SPACE),
\r
1774 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1778 client.setPlayerControl(control);
\r
1782 // Set every key to inactive
\r
1783 PlayerControl control;
\r
1784 client.setPlayerControl(control);
\r
1789 Process environment
\r
1793 //TimeTaker timer("client.step(dtime)", device);
\r
1794 client.step(dtime);
\r
1795 //client.step(dtime_avg1);
\r
1798 if(server != NULL)
\r
1800 //TimeTaker timer("server->step(dtime)", device);
\r
1801 server->step(dtime);
\r
1804 v3f player_position = client.getPlayerPosition();
\r
1806 //TimeTaker //timer2("//timer2", device);
\r
1809 Mouse and camera control
\r
1812 if(device->isWindowActive() && g_game_focused)
\r
1814 device->getCursorControl()->setVisible(false);
\r
1816 if(first_loop_after_window_activation){
\r
1817 //std::cout<<"window active, first loop"<<std::endl;
\r
1818 first_loop_after_window_activation = false;
\r
1821 s32 dx = g_input->getMousePos().X - 320;
\r
1822 s32 dy = g_input->getMousePos().Y - 240;
\r
1823 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1824 camera_yaw -= dx*0.2;
\r
1825 camera_pitch += dy*0.2;
\r
1826 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1827 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1829 g_input->setMousePos(320, 240);
\r
1832 device->getCursorControl()->setVisible(true);
\r
1834 //std::cout<<"window inactive"<<std::endl;
\r
1835 first_loop_after_window_activation = true;
\r
1838 camera_yaw = wrapDegrees(camera_yaw);
\r
1839 camera_pitch = wrapDegrees(camera_pitch);
\r
1841 v3f camera_direction = v3f(0,0,1);
\r
1842 camera_direction.rotateYZBy(camera_pitch);
\r
1843 camera_direction.rotateXZBy(camera_yaw);
\r
1845 v3f camera_position =
\r
1846 player_position + v3f(0, BS+BS/2, 0);
\r
1848 camera->setPosition(camera_position);
\r
1849 // *100.0 helps in large map coordinates
\r
1850 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1852 if(FIELD_OF_VIEW_TEST){
\r
1853 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1854 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1857 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1858 //TimeTaker timer("client.updateCamera", device);
\r
1859 client.updateCamera(camera_position, camera_direction);
\r
1863 //TimeTaker //timer3("//timer3", device);
\r
1866 Calculate what block is the crosshair pointing to
\r
1869 //u32 t1 = device->getTimer()->getRealTime();
\r
1871 //f32 d = 4; // max. distance
\r
1872 f32 d = 4; // max. distance
\r
1873 core::line3d<f32> shootline(camera_position,
\r
1874 camera_position + camera_direction * BS * (d+1));
\r
1876 MapBlockObject *selected_object = client.getSelectedObject
\r
1877 (d*BS, camera_position, shootline);
\r
1879 if(selected_object != NULL)
\r
1881 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1883 core::aabbox3d<f32> box_on_map
\r
1884 = selected_object->getSelectionBoxOnMap();
\r
1886 hilightboxes.push_back(box_on_map);
\r
1888 infotext = narrow_to_wide(selected_object->infoText());
\r
1890 if(g_input->getLeftClicked())
\r
1892 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1893 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1894 selected_object->getId(), g_selected_item);
\r
1896 else if(g_input->getRightClicked())
\r
1898 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1900 Check if we want to modify the object ourselves
\r
1902 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1904 dstream<<"Sign object right-clicked"<<std::endl;
\r
1908 input_guitext = guienv->addStaticText(L"",
\r
1909 core::rect<s32>(150,100,350,120),
\r
1911 false, // wordwrap?
\r
1914 input_guitext->setDrawBackground(true);
\r
1916 g_text_buffer = L"";
\r
1917 g_text_buffer_accepted = false;
\r
1918 textbuf_dest = new TextDestSign(
\r
1919 selected_object->getBlock()->getPos(),
\r
1920 selected_object->getId(),
\r
1924 Otherwise pass the event to the server as-is
\r
1928 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1929 selected_object->getId(), g_selected_item);
\r
1933 else // selected_object == NULL
\r
1936 bool nodefound = false;
\r
1938 v3s16 neighbourpos;
\r
1939 core::aabbox3d<f32> nodefacebox;
\r
1940 f32 mindistance = BS * 1001;
\r
1942 v3s16 pos_i = floatToInt(player_position);
\r
1944 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1948 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1949 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1950 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1951 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1952 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1953 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1955 for(s16 y = ystart; y <= yend; y++){
\r
1956 for(s16 z = zstart; z <= zend; z++){
\r
1957 for(s16 x = xstart; x <= xend; x++)
\r
1960 if(client.getNode(v3s16(x,y,z)).d == MATERIAL_AIR){
\r
1963 }catch(InvalidPositionException &e){
\r
1968 v3f npf = intToFloat(np);
\r
1972 v3s16 directions[6] = {
\r
1973 v3s16(0,0,1), // back
\r
1974 v3s16(0,1,0), // top
\r
1975 v3s16(1,0,0), // right
\r
1981 for(u16 i=0; i<6; i++){
\r
1983 v3f dir_f = v3f(directions[i].X,
\r
1984 directions[i].Y, directions[i].Z);
\r
1985 v3f centerpoint = npf + dir_f * BS/2;
\r
1987 (centerpoint - camera_position).getLength();
\r
1989 if(distance < mindistance){
\r
1990 //std::cout<<DTIME<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;
\r
1991 //std::cout<<DTIME<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;
\r
1992 core::CMatrix4<f32> m;
\r
1993 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
1995 // This is the back face
\r
1996 v3f corners[2] = {
\r
1997 v3f(BS/2, BS/2, BS/2),
\r
1998 v3f(-BS/2, -BS/2, BS/2+d)
\r
2001 for(u16 j=0; j<2; j++){
\r
2002 m.rotateVect(corners[j]);
\r
2003 corners[j] += npf;
\r
2004 //std::cout<<DTIME<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;
\r
2007 //core::aabbox3d<f32> facebox(corners[0],corners[1]);
\r
2008 core::aabbox3d<f32> facebox(corners[0]);
\r
2009 facebox.addInternalPoint(corners[1]);
\r
2011 if(facebox.intersectsWithLine(shootline)){
\r
2014 neighbourpos = np + directions[i];
\r
2015 mindistance = distance;
\r
2016 nodefacebox = facebox;
\r
2024 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
2025 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2026 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
2028 static v3s16 nodepos_old(-1,-1,-1);
\r
2029 if(nodepos != nodepos_old){
\r
2030 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2031 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2032 nodepos_old = nodepos;
\r
2034 /*wchar_t positiontext[20];
\r
2035 swprintf(positiontext, 20, L"(%i,%i,%i)",
\r
2036 nodepos.X, nodepos.Y, nodepos.Z);
\r
2037 positiontextgui->setText(positiontext);*/
\r
2040 hilightboxes.push_back(nodefacebox);
\r
2042 if(g_input->getLeftClicked())
\r
2044 //std::cout<<DTIME<<"Removing node"<<std::endl;
\r
2045 //client.removeNode(nodepos);
\r
2046 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2047 client.clickGround(0, nodepos, neighbourpos, g_selected_item);
\r
2049 if(g_input->getRightClicked())
\r
2051 //std::cout<<DTIME<<"Placing node"<<std::endl;
\r
2052 //client.addNodeFromInventory(neighbourpos, g_selected_item);
\r
2053 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2054 client.clickGround(1, nodepos, neighbourpos, g_selected_item);
\r
2058 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
2059 //positiontextgui->setText(L"");
\r
2062 } // selected_object == NULL
\r
2064 g_input->resetLeftClicked();
\r
2065 g_input->resetRightClicked();
\r
2068 Calculate stuff for drawing
\r
2071 v2u32 screensize = driver->getScreenSize();
\r
2072 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
2074 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2077 Update gui stuff (0ms)
\r
2080 //TimeTaker guiupdatetimer("Gui updating", device);
\r
2083 wchar_t temptext[100];
\r
2085 static float drawtime_avg = 0;
\r
2086 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2087 static float scenetime_avg = 0;
\r
2088 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2089 static float endscenetime_avg = 0;
\r
2090 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2092 swprintf(temptext, 100, L"Minetest-c55 ("
\r
2094 L", R: range_all=%i"
\r
2096 L" drawtime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2098 g_viewing_range_all,
\r
2104 guitext->setText(temptext);
\r
2108 wchar_t temptext[100];
\r
2109 /*swprintf(temptext, 100,
\r
2111 L"% .3f < btime_jitter < % .3f"
\r
2112 L", dtime_jitter = % .1f %%"
\r
2113 //L", ftime_ratio = % .3f"
\r
2115 busytime_jitter1_min_sample,
\r
2116 busytime_jitter1_max_sample,
\r
2117 dtime_jitter1_max_fraction * 100.0
\r
2118 //g_freetime_ratio
\r
2120 swprintf(temptext, 100,
\r
2121 L"(% .1f, % .1f, % .1f)"
\r
2122 L" (% .3f < btime_jitter < % .3f"
\r
2123 L", dtime_jitter = % .1f %%)",
\r
2124 player_position.X/BS,
\r
2125 player_position.Y/BS,
\r
2126 player_position.Z/BS,
\r
2127 busytime_jitter1_min_sample,
\r
2128 busytime_jitter1_max_sample,
\r
2129 dtime_jitter1_max_fraction * 100.0
\r
2132 guitext2->setText(temptext);
\r
2136 /*wchar_t temptext[100];
\r
2137 swprintf(temptext, 100,
\r
2138 SWPRINTF_CHARSTRING,
\r
2139 infotext.substr(0,99).c_str()
\r
2142 guitext_info->setText(temptext);*/
\r
2144 guitext_info->setText(infotext.c_str());
\r
2151 static u16 old_selected_item = 65535;
\r
2152 if(client.getLocalInventoryUpdated()
\r
2153 || g_selected_item != old_selected_item)
\r
2155 old_selected_item = g_selected_item;
\r
2156 //std::cout<<"Updating local inventory"<<std::endl;
\r
2157 client.getLocalInventory(local_inventory);
\r
2158 quick_inventory->setSelection(g_selected_item);
\r
2159 quick_inventory->update();
\r
2162 if(input_guitext != NULL)
\r
2164 /*wchar_t temptext[100];
\r
2165 swprintf(temptext, 100,
\r
2166 SWPRINTF_CHARSTRING,
\r
2167 g_text_buffer.substr(0,99).c_str()
\r
2169 input_guitext->setText(g_text_buffer.c_str());
\r
2175 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2177 input_guitext->remove();
\r
2178 input_guitext = NULL;
\r
2180 if(textbuf_dest != NULL)
\r
2182 std::string text = wide_to_narrow(g_text_buffer);
\r
2183 dstream<<"Sending text: "<<text<<std::endl;
\r
2184 textbuf_dest->sendText(text);
\r
2185 delete textbuf_dest;
\r
2186 textbuf_dest = NULL;
\r
2192 //guiupdatetimer.stop();
\r
2198 TimeTaker drawtimer("Drawing", device);
\r
2201 Background color is choosen based on whether the player is
\r
2202 much beyond the initial ground level
\r
2204 /*video::SColor bgcolor;
\r
2205 v3s16 p0 = Map::floatToInt(player_position);
\r
2206 // Does this make short random delays?
\r
2207 // NOTE: no need for this, sky doesn't show underground with
\r
2209 bool is_underground = client.isNodeUnderground(p0);
\r
2210 //bool is_underground = false;
\r
2211 if(is_underground == false)
\r
2212 bgcolor = video::SColor(255,90,140,200);
\r
2214 bgcolor = video::SColor(255,0,0,0);*/
\r
2216 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2217 video::SColor bgcolor = skycolor;
\r
2220 driver->beginScene(true, true, bgcolor);
\r
2224 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2227 TimeTaker timer("smgr", device);
\r
2229 scenetime = timer.stop(true);
\r
2233 //TimeTaker timer9("auxiliary drawings", device);
\r
2236 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2237 displaycenter + core::vector2d<s32>(10,0),
\r
2238 video::SColor(255,255,255,255));
\r
2239 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2240 displaycenter + core::vector2d<s32>(0,10),
\r
2241 video::SColor(255,255,255,255));
\r
2244 //TimeTaker //timer10("//timer10", device);
\r
2246 video::SMaterial m;
\r
2248 m.Lighting = false;
\r
2249 driver->setMaterial(m);
\r
2251 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2253 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2254 i != hilightboxes.end(); i++)
\r
2256 /*std::cout<<"hilightbox min="
\r
2257 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2259 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2261 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2267 //TimeTaker //timer11("//timer11", device);
\r
2273 guienv->drawAll();
\r
2277 TimeTaker timer("endScene", device);
\r
2278 driver->endScene();
\r
2279 endscenetime = timer.stop(true);
\r
2282 drawtime = drawtimer.stop(true);
\r
2288 static s16 lastFPS = 0;
\r
2289 //u16 fps = driver->getFPS();
\r
2290 u16 fps = (1.0/dtime_avg1);
\r
2292 if (lastFPS != fps)
\r
2294 core::stringw str = L"Minetest [";
\r
2295 str += driver->getName();
\r
2299 device->setWindowCaption(str.c_str());
\r
2305 device->yield();*/
\r
2308 } // client is deleted at this point
\r
2313 In the end, delete the Irrlicht device.
\r
2318 catch(con::PeerNotFoundException &e)
\r
2320 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2322 #if CATCH_EXCEPTIONS
\r
2324 This is what has to be done in every thread to get suitable debug info
\r
2326 catch(std::exception &e)
\r
2328 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2329 <<e.what()<<std::endl;
\r
2334 debugstreams_deinit();
\r