3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
\r
5 This program is free software; you can redistribute it and/or modify
\r
6 it under the terms of the GNU General Public License as published by
\r
7 the Free Software Foundation; either version 2 of the License, or
\r
8 (at your option) any later version.
\r
10 This program is distributed in the hope that it will be useful,
\r
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 GNU General Public License for more details.
\r
15 You should have received a copy of the GNU General Public License along
\r
16 with this program; if not, write to the Free Software Foundation, Inc.,
\r
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
21 =============================== NOTES ==============================
\r
22 NOTE: Things starting with TODO are sometimes only suggestions.
\r
24 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
25 is an apparanet memory leak in irrlicht when using it (not sure)
\r
27 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
28 NOTE: Global locale is now set at initialization
\r
30 SUGGESTION: add a second lighting value to the MS nibble of param of
\r
31 air to tell how bright the air node is when there is no sunlight.
\r
32 When day changes to night, these two values can be interpolated.
\r
34 TODO: Fix address to be ipv6 compatible
\r
36 TODO: ESC Pause mode in which the cursor is not kept at the center of window.
\r
37 TODO: Stop player if focus of window is taken away (go to pause mode)
\r
38 TODO: Optimize and fix makeFastFace or whatever it's called
\r
39 - Face calculation is the source of CPU usage on the client
\r
40 SUGGESTION: The client will calculate and send lighting changes and
\r
41 the server will randomly check some of them and kick the client out
\r
42 if it fails to calculate them right.
\r
43 - Actually, it could just start ignoring them and calculate them
\r
45 SUGGESTION: Combine MapBlock's face caches to so big pieces that VBO
\r
47 - That is >500 vertices
\r
49 TODO: Better dungeons
\r
50 TODO: There should be very slight natural caves also, starting from
\r
51 only a straightened-up cliff
\r
53 TODO: Changing of block with mouse wheel or something
\r
58 - One single map container with ids as keys
\r
61 TODO: - Keep track of the place of the mob in the last few hundreth's
\r
62 of a second - then, if a player hits it, take the value that is
\r
63 avg_rtt/2 before the moment the packet is received.
\r
66 SUGGESTION: Modify client to calculate single changes asynchronously
\r
68 TODO: Moving players more smoothly. Calculate moving animation
\r
69 in a way that doesn't make the player jump to the right place
\r
70 immediately when the server sends a new position
\r
72 TODO: There are some lighting-related todos and fixmes in
\r
73 ServerMap::emergeBlock
\r
75 FIXME: When a new sector is generated, it may change the ground level
\r
76 of it's and it's neighbors border that two blocks that are
\r
77 above and below each other and that are generated before and
\r
78 after the sector heightmap generation (order doesn't matter),
\r
79 can have a small gap between each other at the border.
\r
80 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
81 using for UnlimitedHeightmap? (getting all neighbors
\r
84 TODO: Proper handling of spawning place (try to find something that
\r
85 is not in the middle of an ocean (some land to stand on at
\r
86 least) and save it in map config.
\r
87 SUGG: Set server to automatically find a good spawning place in some
\r
88 place where there is water and land.
\r
89 - Map to have a getWalkableNear(p)
\r
90 - Is this a good idea? It's part of the game to find a good place.
\r
92 TODO: Transfer more blocks in a single packet
\r
93 SUGG: A blockdata combiner class, to which blocks are added and at
\r
94 destruction it sends all the stuff in as few packets as possible.
\r
96 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
97 SUGG: Fetch stuff mainly from the viewing direction
\r
99 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
100 - This enables saving many packets and making a faster connection
\r
101 - This also enables server to check if client has received the
\r
102 most recent block sent, for example.
\r
103 TODO: Add a sane bandwidth throttling system to Connection
\r
105 SUGG: More fine-grained control of client's dumping of blocks from
\r
107 - ...What does this mean in the first place?
\r
109 TODO: Make the amount of blocks sending to client and the total
\r
110 amount of blocks dynamically limited. Transferring blocks is the
\r
111 main network eater of this system, so it is the one that has
\r
112 to be throttled so that RTTs stay low.
\r
114 TODO: Server to load starting inventory from disk
\r
116 TODO: PLayers to only be hidden when the client quits.
\r
117 TODO: - Players to be saved on disk, with inventory
\r
118 TODO: Players to be saved as text in map/players/<name>
\r
120 SUGG: A map editing mode (similar to dedicated server mode)
\r
122 TODO: Make fetching sector's blocks more efficient when rendering
\r
123 sectors that have very large amounts of blocks (on client)
\r
125 TODO: Make the video backend selectable
\r
127 Block object server side:
\r
128 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
129 - For all blocks in the buffer, objects are stepped(). This
\r
130 means they are active.
\r
131 - TODO: A global active buffer is needed for the server
\r
132 - TODO: All blocks going in and out of the buffer are recorded.
\r
133 - TODO: For outgoing blocks, a timestamp is written.
\r
134 - TODO: For incoming blocks, the time difference is calculated and
\r
135 objects are stepped according to it.
\r
136 TODO: A timestamp to blocks
\r
138 SUGG: Add a time value to the param of footstepped grass and check it
\r
139 against a global timer when a block is accessed, to make old
\r
142 TODO: Add config parameters for server's sending and generating distance
\r
144 TODO: Copy the text of the last picked sign to inventory in creative
\r
147 TODO: Untie client network operations from framerate
\r
148 - Needs some input queues or something
\r
150 SUGG: Make a copy of close-range environment on client for showing
\r
151 on screen, with minimal mutexes to slow down the main loop
\r
153 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
154 it by sending more stuff in a single packet.
\r
155 - Add a packet queue to RemoteClient, from which packets will be
\r
156 combined with object data packets
\r
157 - This is not exactly trivial: the object data packets are
\r
158 sometimes very big by themselves
\r
160 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
161 - This will allow saving ages of rats on disk but not sending
\r
164 TODO: Get rid of GotSplitPacketException
\r
166 SUGG: Implement lighting using VoxelManipulator
\r
167 - Would it be significantly faster?
\r
169 TODO: Check what goes wrong with caching map to disk (Kray)
\r
171 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
173 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
174 - Does their position get saved to a border value or something?
\r
176 TODO: MovingObject::move and Player::move are basically the same.
\r
180 ======================================================================
\r
182 Water dynamics pseudo-code (block = MapNode):
\r
183 SUGG: Create separate flag table in VoxelManipulator to allow fast
\r
184 clearing of "modified" flags
\r
186 neighborCausedPressure(pos):
\r
188 dirs = {down, left, right, back, front, up}
\r
191 p = block_at(pos2).pressure
\r
192 if d.Y == 1 and p > min:
\r
194 if d.Y == -1 and p < max:
\r
200 # This should somehow update all changed pressure values
\r
201 # in an unknown body of water
\r
202 updateWaterPressure(pos):
\r
205 FIXME: This goes in an indefinite loop when there is an underwater
\r
210 #33333333x<- block removed from here
\r
233 Now, consider moving to the last block not allowed.
\r
235 Consider it a 3D case with a depth of 2. We're now at this situation.
\r
236 Note the additional blocking ## in the second depth plane.
\r
239 #111###### #111######
\r
240 #222##x2## #222##22##
\r
241 #333333333 #33333##33
\r
242 ########## ##########
\r
244 #111###### #111######
\r
245 #222##22## #222##x2##
\r
246 #333333333 #33333##33
\r
247 ########## ##########
\r
249 #111###### #111######
\r
250 #222##22## #222##2x##
\r
251 #333333333 #33333##33
\r
252 ########## ##########
\r
254 Now there is nowhere to go, without going to an already visited block,
\r
255 but the pressure calculated in here from neighboring blocks is >= 2,
\r
256 so it is not the final ending.
\r
258 We will back up to a state where there is somewhere to go to.
\r
261 #111###### #111######
\r
262 #222##22## #222##22##
\r
263 #333333x33 #33333##33
\r
264 ########## ##########
\r
266 Then just go on, avoiding already visited blocks:
\r
268 #111###### #111######
\r
269 #222##22## #222##22##
\r
270 #33333x333 #33333##33
\r
271 ########## ##########
\r
273 #111###### #111######
\r
274 #222##22## #222##22##
\r
275 #3333x3333 #33333##33
\r
276 ########## ##########
\r
278 #111###### #111######
\r
279 #222##22## #222##22##
\r
280 #333x33333 #33333##33
\r
281 ########## ##########
\r
283 #111###### #111######
\r
284 #222##22## #222##22##
\r
285 #33x333333 #33333##33
\r
286 ########## ##########
\r
288 #111###### #111######
\r
289 #22x##22## #222##22##
\r
290 #333333333 #33333##33
\r
291 ########## ##########
\r
293 #11x###### #111######
\r
294 #222##22## #222##22##
\r
295 #333333333 #33333##33
\r
296 ########## ##########
\r
298 "Blob". the air bubble finally got out of the water.
\r
299 Then return recursively to a state where there is air next to water,
\r
300 clear the visit flags and feed the neighbor of the water recursively
\r
303 #11 ###### #111######
\r
304 #222##22## #222##22##
\r
305 #333333333x #33333##33
\r
306 ########## ##########
\r
308 #11 ###### #111######
\r
309 #222##22## #222##22##
\r
310 #33333333x3 #33333##33
\r
311 ########## ##########
\r
316 # removed_pos: a position that has been changed from something to air
\r
317 flowWater(removed_pos):
\r
318 dirs = {top, left, right, back, front, bottom}
\r
319 selected_dir = None
\r
321 b2 = removed_pos + d
\r
323 # Ignore positions that don't have water
\r
324 if block_at(b2) != water:
\r
327 # Ignore positions that have already been checked
\r
328 if block_at(b2).checked:
\r
331 # If block is at top, select it always.
\r
336 # If block is at bottom, select it if it has enough pressure.
\r
337 # >= 3 needed for stability (and sanity)
\r
339 if block_at(b2).pressure >= 3:
\r
344 # Else block is at some side. select it if it has enough pressure.
\r
345 if block_at(b2).pressure >= 2:
\r
349 # If there is nothing to do anymore, return.
\r
350 if selected_dir == None
\r
353 b2 = removed_pos + selected_dir
\r
356 set_block(removed_pos, block_at(b2))
\r
357 set_block(b2, air_block)
\r
360 updateWaterPressure(removed_pos)
\r
362 # Flow water to the newly created empty position
\r
365 # Check empty positions around and try flowing water to them
\r
367 b3 = removed_pos + d
\r
368 # Ignore positions that are not air
\r
369 if block_at(b3) is not air:
\r
374 ======================================================================
\r
379 Setting this to 1 enables a special camera mode that forces
\r
380 the renderers to think that the camera statically points from
\r
381 the starting place to a static direction.
\r
383 This allows one to move around with the player and see what
\r
384 is actually drawn behind solid things and behind the player.
\r
386 #define FIELD_OF_VIEW_TEST 0
\r
388 #ifdef UNITTEST_DISABLE
\r
390 #pragma message ("Disabling unit tests")
\r
392 #warning "Disabling unit tests"
\r
394 // Disable unit tests
\r
395 #define ENABLE_TESTS 0
\r
397 // Enable unit tests
\r
398 #define ENABLE_TESTS 1
\r
402 #pragma comment(lib, "Irrlicht.lib")
\r
403 #pragma comment(lib, "jthread.lib")
\r
404 // This would get rid of the console window
\r
405 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
409 #define WIN32_LEAN_AND_MEAN
\r
410 #include <windows.h>
\r
411 #define sleep_ms(x) Sleep(x)
\r
413 #include <unistd.h>
\r
414 #define sleep_ms(x) usleep(x*1000)
\r
417 #include <iostream>
\r
420 #include <jmutexautolock.h>
\r
421 #include "common_irrlicht.h"
\r
424 #include "player.h"
\r
427 #include "environment.h"
\r
428 #include "server.h"
\r
429 #include "client.h"
\r
430 #include "serialization.h"
\r
431 #include "constants.h"
\r
432 #include "strfnd.h"
\r
433 #include "porting.h"
\r
434 #include <locale.h>
\r
436 IrrlichtDevice *g_device = NULL;
\r
438 const char *g_material_filenames[MATERIALS_COUNT] =
\r
440 "../data/stone.png",
\r
441 "../data/grass.png",
\r
442 "../data/water.png",
\r
443 "../data/light.png",
\r
444 "../data/tree.png",
\r
445 "../data/leaves.png",
\r
446 "../data/grass_footsteps.png",
\r
447 "../data/mese.png",
\r
451 video::SMaterial g_materials[MATERIALS_COUNT];
\r
452 //video::SMaterial g_mesh_materials[3];
\r
454 // All range-related stuff below is locked behind this
\r
455 JMutex g_range_mutex;
\r
457 // Blocks are viewed in this range from the player
\r
458 s16 g_viewing_range_nodes = 60;
\r
460 // This is updated by the client's fetchBlocks routine
\r
461 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
463 // If true, the preceding value has no meaning and all blocks
\r
464 // already existing in memory are drawn
\r
465 bool g_viewing_range_all = false;
\r
467 // This is the freetime ratio imposed by the dynamic viewing
\r
468 // range changing code.
\r
469 // It is controlled by the main loop to the smallest value that
\r
470 // inhibits glitches (dtime jitter) in the main loop.
\r
471 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
476 These are loaded from the config file.
\r
479 Settings g_settings;
\r
481 // Sets default settings
\r
482 void set_default_settings()
\r
484 g_settings.set("dedicated_server", "");
\r
487 g_settings.set("wanted_fps", "30");
\r
488 g_settings.set("fps_max", "60");
\r
489 g_settings.set("viewing_range_nodes_max", "300");
\r
490 g_settings.set("viewing_range_nodes_min", "20");
\r
491 g_settings.set("screenW", "");
\r
492 g_settings.set("screenH", "");
\r
493 g_settings.set("host_game", "");
\r
494 g_settings.set("port", "");
\r
495 g_settings.set("address", "");
\r
496 g_settings.set("name", "");
\r
497 g_settings.set("random_input", "false");
\r
498 g_settings.set("client_delete_unused_sectors_timeout", "1200");
\r
501 g_settings.set("creative_mode", "false");
\r
502 g_settings.set("heightmap_blocksize", "128");
\r
503 g_settings.set("height_randmax", "constant 70.0");
\r
504 g_settings.set("height_randfactor", "constant 0.6");
\r
505 g_settings.set("height_base", "linear 0 35 0");
\r
506 g_settings.set("plants_amount", "1.0");
\r
507 g_settings.set("ravines_amount", "1.0");
\r
508 g_settings.set("objectdata_interval", "0.2");
\r
509 g_settings.set("active_object_range", "2");
\r
510 g_settings.set("max_simultaneous_block_sends_per_client", "1");
\r
511 g_settings.set("max_simultaneous_block_sends_server_total", "4");
\r
518 //u16 g_selected_material = 0;
\r
519 u16 g_selected_item = 0;
\r
521 bool g_esc_pressed = false;
\r
523 std::wstring g_text_buffer;
\r
524 bool g_text_buffer_accepted = false;
\r
526 // When true, the mouse and keyboard are grabbed
\r
527 bool g_game_focused = true;
\r
534 std::ostream *dout_con_ptr = &dummyout;
\r
535 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
536 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
537 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
538 //std::ostream *dout_con_ptr = &dstream;
\r
539 //std::ostream *derr_con_ptr = &dstream;
\r
542 std::ostream *dout_server_ptr = &dstream;
\r
543 std::ostream *derr_server_ptr = &dstream;
\r
546 std::ostream *dout_client_ptr = &dstream;
\r
547 std::ostream *derr_client_ptr = &dstream;
\r
554 JMutex g_timestamp_mutex;
\r
555 //std::string g_timestamp;
\r
557 std::string getTimestamp()
\r
559 if(g_timestamp_mutex.IsInitialized()==false)
\r
561 JMutexAutoLock lock(g_timestamp_mutex);
\r
562 //return g_timestamp;
\r
563 time_t t = time(NULL);
\r
564 struct tm *tm = localtime(&t);
\r
566 strftime(cs, 20, "%H:%M:%S", tm);
\r
570 class MyEventReceiver : public IEventReceiver
\r
573 // This is the one method that we have to implement
\r
574 virtual bool OnEvent(const SEvent& event)
\r
576 // Remember whether each key is down or up
\r
577 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
579 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
581 if(event.KeyInput.PressedDown)
\r
583 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
584 if(g_game_focused == false)
\r
586 s16 key = event.KeyInput.Key;
\r
587 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
589 g_text_buffer_accepted = true;
\r
591 else if(key == irr::KEY_BACK)
\r
593 if(g_text_buffer.size() > 0)
\r
594 g_text_buffer = g_text_buffer.substr
\r
595 (0, g_text_buffer.size()-1);
\r
599 wchar_t wc = event.KeyInput.Char;
\r
601 g_text_buffer += wc;
\r
605 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
607 if(g_game_focused == true)
\r
609 dstream<<DTIME<<"ESC pressed"<<std::endl;
\r
610 g_esc_pressed = true;
\r
614 // Material selection
\r
615 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
617 if(g_game_focused == true)
\r
619 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
622 g_selected_item = 0;
\r
623 dstream<<DTIME<<"Selected item: "
\r
624 <<g_selected_item<<std::endl;
\r
628 // Viewing range selection
\r
629 if(event.KeyInput.Key == irr::KEY_KEY_R
\r
632 JMutexAutoLock lock(g_range_mutex);
\r
633 if(g_viewing_range_all)
\r
635 g_viewing_range_all = false;
\r
636 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
640 g_viewing_range_all = true;
\r
641 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
645 // Print debug stacks
\r
646 if(event.KeyInput.Key == irr::KEY_KEY_P
\r
649 dstream<<"-----------------------------------------"
\r
651 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
652 dstream<<"-----------------------------------------"
\r
654 debug_stacks_print();
\r
659 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
661 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
663 leftclicked = true;
\r
665 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
667 rightclicked = true;
\r
669 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
671 /*dstream<<"event.MouseInput.Wheel="
\r
672 <<event.MouseInput.Wheel<<std::endl;*/
\r
673 if(event.MouseInput.Wheel < 0)
\r
675 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
678 g_selected_item = 0;
\r
680 else if(event.MouseInput.Wheel > 0)
\r
682 if(g_selected_item > 0)
\r
685 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
693 // This is used to check whether a key is being held down
\r
694 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
696 return keyIsDown[keyCode];
\r
701 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
702 keyIsDown[i] = false;
\r
703 leftclicked = false;
\r
704 rightclicked = false;
\r
710 // We use this array to store the current state of each key
\r
711 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
722 virtual ~InputHandler()
\r
725 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
726 virtual v2s32 getMousePos() = 0;
\r
727 virtual void setMousePos(s32 x, s32 y) = 0;
\r
728 virtual bool getLeftClicked() = 0;
\r
729 virtual bool getRightClicked() = 0;
\r
730 virtual void resetLeftClicked() = 0;
\r
731 virtual void resetRightClicked() = 0;
\r
733 virtual void step(float dtime) {};
\r
735 virtual void clear() {};
\r
738 InputHandler *g_input = NULL;
\r
743 g_game_focused = true;
\r
748 g_game_focused = false;
\r
751 class RealInputHandler : public InputHandler
\r
754 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
756 m_receiver(receiver)
\r
759 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
761 return m_receiver->IsKeyDown(keyCode);
\r
763 virtual v2s32 getMousePos()
\r
765 return m_device->getCursorControl()->getPosition();
\r
767 virtual void setMousePos(s32 x, s32 y)
\r
769 m_device->getCursorControl()->setPosition(x, y);
\r
772 virtual bool getLeftClicked()
\r
774 if(g_game_focused == false)
\r
776 return m_receiver->leftclicked;
\r
778 virtual bool getRightClicked()
\r
780 if(g_game_focused == false)
\r
782 return m_receiver->rightclicked;
\r
784 virtual void resetLeftClicked()
\r
786 m_receiver->leftclicked = false;
\r
788 virtual void resetRightClicked()
\r
790 m_receiver->rightclicked = false;
\r
795 resetRightClicked();
\r
796 resetLeftClicked();
\r
799 IrrlichtDevice *m_device;
\r
800 MyEventReceiver *m_receiver;
\r
803 class RandomInputHandler : public InputHandler
\r
806 RandomInputHandler()
\r
808 leftclicked = false;
\r
809 rightclicked = false;
\r
810 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
811 keydown[i] = false;
\r
813 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
815 return keydown[keyCode];
\r
817 virtual v2s32 getMousePos()
\r
821 virtual void setMousePos(s32 x, s32 y)
\r
823 mousepos = v2s32(x,y);
\r
826 virtual bool getLeftClicked()
\r
828 return leftclicked;
\r
830 virtual bool getRightClicked()
\r
832 return rightclicked;
\r
834 virtual void resetLeftClicked()
\r
836 leftclicked = false;
\r
838 virtual void resetRightClicked()
\r
840 rightclicked = false;
\r
843 virtual void step(float dtime)
\r
846 static float counter1 = 0;
\r
850 counter1 = 0.1*Rand(1,10);
\r
851 /*if(g_selected_material < USEFUL_MATERIAL_COUNT-1)
\r
852 g_selected_material++;
\r
854 g_selected_material = 0;*/
\r
855 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
858 g_selected_item = 0;
\r
862 static float counter1 = 0;
\r
866 counter1 = 0.1*Rand(1, 40);
\r
867 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
871 static float counter1 = 0;
\r
875 counter1 = 0.1*Rand(1, 40);
\r
876 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
880 static float counter1 = 0;
\r
884 counter1 = 0.1*Rand(1, 40);
\r
885 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
889 static float counter1 = 0;
\r
893 counter1 = 0.1*Rand(1, 40);
\r
894 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
898 static float counter1 = 0;
\r
902 counter1 = 0.1*Rand(1, 20);
\r
903 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
907 static float counter1 = 0;
\r
911 counter1 = 0.1*Rand(1, 30);
\r
912 leftclicked = true;
\r
916 static float counter1 = 0;
\r
920 counter1 = 0.1*Rand(1, 20);
\r
921 rightclicked = true;
\r
924 mousepos += mousespeed;
\r
927 s32 Rand(s32 min, s32 max)
\r
929 return (rand()%(max-min+1))+min;
\r
932 bool keydown[KEY_KEY_CODES_COUNT];
\r
939 void updateViewingRange(f32 frametime, Client *client)
\r
941 // Range_all messes up frametime_avg
\r
942 if(g_viewing_range_all == true)
\r
945 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
947 // Initialize to the target value
\r
948 static float frametime_avg = 1.0/wanted_fps;
\r
949 frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
951 static f32 counter = 0;
\r
953 counter -= frametime;
\r
956 //counter = 1.0; //seconds
\r
957 counter = 0.5; //seconds
\r
959 //float freetime_ratio = 0.2;
\r
960 //float freetime_ratio = 0.4;
\r
961 float freetime_ratio = FREETIME_RATIO;
\r
963 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
965 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
967 static bool fraction_is_good = false;
\r
969 float fraction_good_threshold = 0.1;
\r
970 float fraction_bad_threshold = 0.25;
\r
971 float fraction_limit;
\r
972 // Use high limit if fraction is good AND the fraction would
\r
973 // lower the range. We want to keep the range fairly high.
\r
974 if(fraction_is_good && fraction > 1.0)
\r
975 fraction_limit = fraction_bad_threshold;
\r
977 fraction_limit = fraction_good_threshold;
\r
979 if(fabs(fraction - 1.0) < fraction_limit)
\r
981 fraction_is_good = true;
\r
986 fraction_is_good = false;
\r
989 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
990 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
991 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
992 <<" faction = "<<fraction<<std::endl;*/
\r
994 JMutexAutoLock lock(g_range_mutex);
\r
996 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
997 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
999 s16 n = (float)g_viewing_range_nodes / fraction;
\r
1000 if(n < viewing_range_nodes_min)
\r
1001 n = viewing_range_nodes_min;
\r
1002 if(n > viewing_range_nodes_max)
\r
1003 n = viewing_range_nodes_max;
\r
1005 bool can_change = true;
\r
1007 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
1008 can_change = false;
\r
1011 g_viewing_range_nodes = n;
\r
1013 /*dstream<<"g_viewing_range_nodes = "
\r
1014 <<g_viewing_range_nodes<<std::endl;*/
\r
1017 class GUIQuickInventory : public IEventReceiver
\r
1020 GUIQuickInventory(
\r
1021 gui::IGUIEnvironment* env,
\r
1022 gui::IGUIElement* parent,
\r
1025 Inventory *inventory):
\r
1026 m_itemcount(itemcount),
\r
1027 m_inventory(inventory)
\r
1029 core::rect<s32> imgsize(0,0,48,48);
\r
1030 core::rect<s32> textsize(0,0,48,16);
\r
1031 v2s32 spacing(0, 64);
\r
1032 for(s32 i=0; i<m_itemcount; i++)
\r
1034 m_images.push_back(env->addImage(
\r
1035 imgsize + pos + spacing*i
\r
1037 m_images[i]->setScaleImage(true);
\r
1038 m_texts.push_back(env->addStaticText(
\r
1040 textsize + pos + spacing*i,
\r
1043 m_texts[i]->setBackgroundColor(
\r
1044 video::SColor(128,0,0,0));
\r
1045 m_texts[i]->setTextAlignment(
\r
1046 gui::EGUIA_CENTER,
\r
1047 gui::EGUIA_UPPERLEFT);
\r
1051 virtual bool OnEvent(const SEvent& event)
\r
1056 void setSelection(s32 i)
\r
1065 start = m_selection - m_itemcount / 2;
\r
1067 for(s32 i=0; i<m_itemcount; i++)
\r
1069 s32 j = i + start;
\r
1071 if(j > (s32)m_inventory->getSize() - 1)
\r
1072 j -= m_inventory->getSize();
\r
1074 j += m_inventory->getSize();
\r
1076 InventoryItem *item = m_inventory->getItem(j);
\r
1080 m_images[i]->setImage(NULL);
\r
1083 if(m_selection == j)
\r
1084 swprintf(t, 10, L"<-");
\r
1086 swprintf(t, 10, L"");
\r
1087 m_texts[i]->setText(t);
\r
1089 // The next ifs will segfault with a NULL pointer
\r
1094 m_images[i]->setImage(item->getImage());
\r
1097 if(m_selection == j)
\r
1098 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1100 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1101 m_texts[i]->setText(t);
\r
1107 core::array<gui::IGUIStaticText*> m_texts;
\r
1108 core::array<gui::IGUIImage*> m_images;
\r
1109 Inventory *m_inventory;
\r
1113 int main(int argc, char *argv[])
\r
1116 Low-level initialization
\r
1119 bool disable_stderr = false;
\r
1121 disable_stderr = true;
\r
1124 // Initialize debug streams
\r
1125 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1126 // Initialize debug stacks
\r
1127 debug_stacks_init();
\r
1129 DSTACK(__FUNCTION_NAME);
\r
1135 Basic initialization
\r
1138 // Initialize default settings
\r
1139 set_default_settings();
\r
1141 // Print startup message
\r
1142 dstream<<DTIME<<"minetest-c55"
\r
1143 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1144 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1147 // Set locale. This is for forcing '.' as the decimal point.
\r
1148 std::locale::global(std::locale("C"));
\r
1149 // This enables printing all characters in bitmap font
\r
1150 setlocale(LC_CTYPE, "en_US");
\r
1152 // Initialize sockets
\r
1154 atexit(sockets_cleanup);
\r
1156 // Initialize timestamp mutex
\r
1157 g_timestamp_mutex.Init();
\r
1171 // Read config file
\r
1175 g_settings.readConfigFile(argv[1]);
\r
1179 const char *filenames[2] =
\r
1181 "../minetest.conf",
\r
1182 "../../minetest.conf"
\r
1185 for(u32 i=0; i<2; i++)
\r
1187 bool r = g_settings.readConfigFile(filenames[i]);
\r
1193 // Initialize random seed
\r
1196 g_range_mutex.Init();
\r
1197 assert(g_range_mutex.IsInitialized());
\r
1199 // Read map parameters from settings
\r
1201 HMParams hm_params;
\r
1202 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1203 hm_params.randmax = g_settings.get("height_randmax");
\r
1204 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1205 hm_params.base = g_settings.get("height_base");
\r
1207 MapParams map_params;
\r
1208 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1209 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1215 std::cout<<std::endl<<std::endl;
\r
1218 <<" .__ __ __ "<<std::endl
\r
1219 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1220 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1221 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1222 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1223 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1225 <<"Now with more waterish water!"
\r
1228 std::cout<<std::endl;
\r
1229 char templine[100];
\r
1232 bool dedicated = g_settings.getBoolAsk
\r
1233 ("dedicated_server", "Dedicated server?", false);
\r
1234 std::cout<<"dedicated = "<<dedicated<<std::endl;
\r
1237 u16 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1238 std::cout<<"-> "<<port<<std::endl;
\r
1242 DSTACK("Dedicated server branch");
\r
1244 std::cout<<std::endl;
\r
1245 std::cout<<"========================"<<std::endl;
\r
1246 std::cout<<"Running dedicated server"<<std::endl;
\r
1247 std::cout<<"========================"<<std::endl;
\r
1248 std::cout<<std::endl;
\r
1250 Server server("../map", hm_params, map_params);
\r
1251 server.start(port);
\r
1255 // This is kind of a hack but can be done like this
\r
1256 // because server.step() is very light
\r
1258 server.step(0.030);
\r
1260 static int counter = 0;
\r
1266 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1267 core::list<PlayerInfo>::Iterator i;
\r
1268 static u32 sum_old = 0;
\r
1269 u32 sum = PIChecksum(list);
\r
1270 if(sum != sum_old)
\r
1272 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1273 for(i=list.begin(); i!=list.end(); i++)
\r
1275 i->PrintLine(&std::cout);
\r
1285 bool hosting = false;
\r
1286 char connect_name[100] = "";
\r
1288 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1289 if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1291 std::cout<<g_settings.get("address")<<std::endl;
\r
1292 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1296 std::cin.getline(connect_name, 100);
\r
1299 if(connect_name[0] == 0){
\r
1300 snprintf(connect_name, 100, "127.0.0.1");
\r
1305 std::cout<<"-> hosting"<<std::endl;
\r
1307 std::cout<<"-> "<<connect_name<<std::endl;
\r
1309 char playername[PLAYERNAME_SIZE] = "";
\r
1310 if(g_settings.get("name") != "")
\r
1312 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1316 std::cout<<"Name of player: ";
\r
1317 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1319 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1322 Resolution selection
\r
1327 bool fullscreen = false;
\r
1329 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1331 screenW = atoi(g_settings.get("screenW").c_str());
\r
1332 screenH = atoi(g_settings.get("screenH").c_str());
\r
1336 u16 resolutions[][3] = {
\r
1337 //W, H, fullscreen
\r
1348 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1350 for(u16 i=0; i<res_count; i++)
\r
1352 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1353 <<resolutions[i][1];
\r
1354 if(resolutions[i][2])
\r
1355 std::cout<<" fullscreen"<<std::endl;
\r
1357 std::cout<<" windowed"<<std::endl;
\r
1359 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1360 std::cin.getline(templine, 100);
\r
1363 if(templine[0] == 0)
\r
1366 r0 = atoi(templine);
\r
1368 if(r0 > res_count || r0 == 0)
\r
1374 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1375 <<resolutions[i][1];
\r
1376 if(resolutions[i][2])
\r
1377 std::cout<<" fullscreen"<<std::endl;
\r
1379 std::cout<<" windowed"<<std::endl;
\r
1382 screenW = resolutions[r0-1][0];
\r
1383 screenH = resolutions[r0-1][1];
\r
1384 fullscreen = resolutions[r0-1][2];
\r
1389 MyEventReceiver receiver;
\r
1391 video::E_DRIVER_TYPE driverType;
\r
1394 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1395 driverType = video::EDT_OPENGL;
\r
1397 driverType = video::EDT_OPENGL;
\r
1400 // create device and exit if creation failed
\r
1402 IrrlichtDevice *device;
\r
1403 device = createDevice(driverType,
\r
1404 core::dimension2d<u32>(screenW, screenH),
\r
1405 16, fullscreen, false, false, &receiver);
\r
1407 /*device = createDevice(driverType,
\r
1408 core::dimension2d<u32>(screenW, screenH),
\r
1409 16, fullscreen, false, true, &receiver);*/
\r
1412 return 1; // could not create selected driver.
\r
1414 g_device = device;
\r
1416 device->setResizable(true);
\r
1418 if(g_settings.getBool("random_input"))
\r
1419 g_input = new RandomInputHandler();
\r
1421 g_input = new RealInputHandler(device, &receiver);
\r
1424 Continue initialization
\r
1427 video::IVideoDriver* driver = device->getVideoDriver();
\r
1428 // These make the textures not to show at all
\r
1429 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1430 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1432 //driver->setMinHardwareBufferVertexCount(1);
\r
1434 scene::ISceneManager* smgr = device->getSceneManager();
\r
1436 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1437 gui::IGUISkin* skin = guienv->getSkin();
\r
1438 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1440 skin->setFont(font);
\r
1441 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1442 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1443 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1444 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1445 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1446 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1448 const wchar_t *text = L"Loading and connecting...";
\r
1449 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1450 core::dimension2d<u32> textd = font->getDimension(text);
\r
1451 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1452 // Have to add a bit to disable the text from word wrapping
\r
1453 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1454 core::vector2d<s32> textsize(300, textd.Height);
\r
1455 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1457 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1458 text, textrect, false, false);
\r
1459 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1461 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1462 guienv->drawAll();
\r
1463 driver->endScene();
\r
1466 Initialize material array
\r
1469 //video::SMaterial g_materials[MATERIALS_COUNT];
\r
1470 for(u16 i=0; i<MATERIALS_COUNT; i++)
\r
1472 g_materials[i].Lighting = false;
\r
1473 g_materials[i].BackfaceCulling = false;
\r
1475 const char *filename = g_material_filenames[i];
\r
1476 if(filename != NULL){
\r
1477 video::ITexture *t = driver->getTexture(filename);
\r
1479 std::cout<<DTIME<<"Texture could not be loaded: \""
\r
1480 <<filename<<"\""<<std::endl;
\r
1483 g_materials[i].setTexture(0, driver->getTexture(filename));
\r
1485 //g_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);
\r
1486 g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1487 //g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
\r
1488 //g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1489 if(i == MATERIAL_WATER)
\r
1491 g_materials[i].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
\r
1492 //g_materials[i].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
\r
1496 /*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
\r
1497 g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
\r
1498 g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));
\r
1499 for(u32 i=0; i<3; i++)
\r
1501 g_mesh_materials[i].Lighting = false;
\r
1502 g_mesh_materials[i].BackfaceCulling = false;
\r
1503 g_mesh_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1504 g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1507 // Make a scope here for the client so that it gets removed
\r
1508 // before the irrlicht device
\r
1511 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1516 SharedPtr<Server> server;
\r
1518 server = new Server("../map", hm_params, map_params);
\r
1519 server->start(port);
\r
1526 // TODO: Get rid of the g_materials parameter or it's globalness
\r
1527 Client client(device, g_materials,
\r
1528 g_settings.getFloat("client_delete_unused_sectors_timeout"),
\r
1531 Address connect_address(0,0,0,0, port);
\r
1533 connect_address.Resolve(connect_name);
\r
1535 catch(ResolveError &e)
\r
1537 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1541 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1542 client.connect(connect_address);
\r
1545 while(client.connectedAndInitialized() == false)
\r
1548 if(server != NULL){
\r
1549 server->step(0.1);
\r
1554 catch(con::PeerNotFoundException &e)
\r
1556 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1561 Create the camera node
\r
1564 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1565 0, // Camera parent
\r
1566 v3f(BS*100, BS*2, BS*100), // Look from
\r
1567 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1571 if(camera == NULL)
\r
1574 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1576 camera->setFOV(FOV_ANGLE);
\r
1578 // Just so big a value that everything rendered is visible
\r
1579 camera->setFarValue(100000*BS);
\r
1581 /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1582 f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1584 camera->setFarValue(range);
\r
1588 video::EFT_FOG_LINEAR,
\r
1596 f32 camera_yaw = 0; // "right/left"
\r
1597 f32 camera_pitch = 0; // "up/down"
\r
1599 gui_loadingtext->remove();
\r
1602 Add some gui stuff
\r
1605 // First line of debug text
\r
1606 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1608 core::rect<s32>(5, 5, 5+600, 5+textsize.Y),
\r
1610 // Second line of debug text
\r
1611 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1613 core::rect<s32>(5, 5+(textsize.Y+5)*1, 5+600, (5+textsize.Y)*2),
\r
1616 // At the middle of the screen
\r
1617 // Object infos are shown in this
\r
1618 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1620 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1623 // This is a copy of the inventory that the client's environment has
\r
1624 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1626 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1627 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1630 Some statistics are collected in these
\r
1633 u32 scenetime = 0;
\r
1634 u32 endscenetime = 0;
\r
1642 virtual void sendText(std::string text) = 0;
\r
1645 struct TextDestSign : public TextDest
\r
1647 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1649 m_blockpos = blockpos;
\r
1651 m_client = client;
\r
1653 void sendText(std::string text)
\r
1655 dstream<<"Changing text of a sign object: "
\r
1656 <<text<<std::endl;
\r
1657 m_client->sendSignText(m_blockpos, m_id, text);
\r
1665 TextDest *textbuf_dest = NULL;
\r
1667 //gui::IGUIWindow* input_window = NULL;
\r
1668 gui::IGUIStaticText* input_guitext = NULL;
\r
1674 bool first_loop_after_window_activation = true;
\r
1676 // Time is in milliseconds
\r
1677 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1678 // NOTE: Have to call run() between calls of this to update the timer
\r
1679 u32 lasttime = device->getTimer()->getTime();
\r
1681 while(device->run())
\r
1683 // Hilight boxes collected during the loop and displayed
\r
1684 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1687 std::wstring infotext;
\r
1689 //TimeTaker //timer1("//timer1", device);
\r
1691 // Time of frame without fps limit
\r
1695 // not using getRealTime is necessary for wine
\r
1696 u32 time = device->getTimer()->getTime();
\r
1697 if(time > lasttime)
\r
1698 busytime_u32 = time - lasttime;
\r
1701 busytime = busytime_u32 / 1000.0;
\r
1704 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1706 // Absolutelu necessary for wine!
\r
1713 //updateViewingRange(dtime, &client);
\r
1714 updateViewingRange(busytime, &client);
\r
1721 float fps_max = g_settings.getFloat("fps_max");
\r
1722 u32 frametime_min = 1000./fps_max;
\r
1724 if(busytime_u32 < frametime_min)
\r
1726 u32 sleeptime = frametime_min - busytime_u32;
\r
1727 device->sleep(sleeptime);
\r
1731 // Absolutelu necessary for wine!
\r
1735 Time difference calculation
\r
1737 f32 dtime; // in seconds
\r
1739 u32 time = device->getTimer()->getTime();
\r
1740 if(time > lasttime)
\r
1741 dtime = (time - lasttime) / 1000.0;
\r
1747 Time average and jitter calculation
\r
1750 static f32 dtime_avg1 = 0.0;
\r
1751 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1752 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1754 static f32 dtime_jitter1_max_sample = 0.0;
\r
1755 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1757 static f32 jitter1_max = 0.0;
\r
1758 static f32 counter = 0.0;
\r
1759 if(dtime_jitter1 > jitter1_max)
\r
1760 jitter1_max = dtime_jitter1;
\r
1765 dtime_jitter1_max_sample = jitter1_max;
\r
1766 dtime_jitter1_max_fraction
\r
1767 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1768 jitter1_max = 0.0;
\r
1771 Control freetime ratio
\r
1773 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1775 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1776 g_freetime_ratio += 0.01;
\r
1780 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1781 g_freetime_ratio -= 0.01;
\r
1787 Busytime average and jitter calculation
\r
1790 static f32 busytime_avg1 = 0.0;
\r
1791 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1792 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1794 static f32 busytime_jitter1_max_sample = 0.0;
\r
1795 static f32 busytime_jitter1_min_sample = 0.0;
\r
1797 static f32 jitter1_max = 0.0;
\r
1798 static f32 jitter1_min = 0.0;
\r
1799 static f32 counter = 0.0;
\r
1800 if(busytime_jitter1 > jitter1_max)
\r
1801 jitter1_max = busytime_jitter1;
\r
1802 if(busytime_jitter1 < jitter1_min)
\r
1803 jitter1_min = busytime_jitter1;
\r
1805 if(counter > 0.0){
\r
1807 busytime_jitter1_max_sample = jitter1_max;
\r
1808 busytime_jitter1_min_sample = jitter1_min;
\r
1809 jitter1_max = 0.0;
\r
1810 jitter1_min = 0.0;
\r
1815 Debug info for client
\r
1818 static float counter = 0.0;
\r
1823 client.printDebugInfo(std::cout);
\r
1828 Input handler step()
\r
1830 g_input->step(dtime);
\r
1841 Player speed control
\r
1844 if(g_game_focused)
\r
1851 bool a_superspeed,
\r
1854 PlayerControl control(
\r
1855 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1856 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1857 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1858 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1859 g_input->isKeyDown(irr::KEY_SPACE),
\r
1860 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1864 client.setPlayerControl(control);
\r
1868 // Set every key to inactive
\r
1869 PlayerControl control;
\r
1870 client.setPlayerControl(control);
\r
1875 Process environment
\r
1879 //TimeTaker timer("client.step(dtime)", device);
\r
1880 client.step(dtime);
\r
1881 //client.step(dtime_avg1);
\r
1884 if(server != NULL)
\r
1886 //TimeTaker timer("server->step(dtime)", device);
\r
1887 server->step(dtime);
\r
1890 v3f player_position = client.getPlayerPosition();
\r
1892 //TimeTaker //timer2("//timer2", device);
\r
1895 Mouse and camera control
\r
1898 if(device->isWindowActive() && g_game_focused)
\r
1900 device->getCursorControl()->setVisible(false);
\r
1902 if(first_loop_after_window_activation){
\r
1903 //std::cout<<"window active, first loop"<<std::endl;
\r
1904 first_loop_after_window_activation = false;
\r
1907 s32 dx = g_input->getMousePos().X - 320;
\r
1908 s32 dy = g_input->getMousePos().Y - 240;
\r
1909 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1910 camera_yaw -= dx*0.2;
\r
1911 camera_pitch += dy*0.2;
\r
1912 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1913 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1915 g_input->setMousePos(320, 240);
\r
1918 device->getCursorControl()->setVisible(true);
\r
1920 //std::cout<<"window inactive"<<std::endl;
\r
1921 first_loop_after_window_activation = true;
\r
1924 camera_yaw = wrapDegrees(camera_yaw);
\r
1925 camera_pitch = wrapDegrees(camera_pitch);
\r
1927 v3f camera_direction = v3f(0,0,1);
\r
1928 camera_direction.rotateYZBy(camera_pitch);
\r
1929 camera_direction.rotateXZBy(camera_yaw);
\r
1931 v3f camera_position =
\r
1932 player_position + v3f(0, BS+BS/2, 0);
\r
1934 camera->setPosition(camera_position);
\r
1935 // *100.0 helps in large map coordinates
\r
1936 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1938 if(FIELD_OF_VIEW_TEST){
\r
1939 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1940 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1943 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1944 //TimeTaker timer("client.updateCamera", device);
\r
1945 client.updateCamera(camera_position, camera_direction);
\r
1949 //TimeTaker //timer3("//timer3", device);
\r
1952 Calculate what block is the crosshair pointing to
\r
1955 //u32 t1 = device->getTimer()->getRealTime();
\r
1957 //f32 d = 4; // max. distance
\r
1958 f32 d = 4; // max. distance
\r
1959 core::line3d<f32> shootline(camera_position,
\r
1960 camera_position + camera_direction * BS * (d+1));
\r
1962 MapBlockObject *selected_object = client.getSelectedObject
\r
1963 (d*BS, camera_position, shootline);
\r
1965 if(selected_object != NULL)
\r
1967 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1969 core::aabbox3d<f32> box_on_map
\r
1970 = selected_object->getSelectionBoxOnMap();
\r
1972 hilightboxes.push_back(box_on_map);
\r
1974 infotext = narrow_to_wide(selected_object->infoText());
\r
1976 if(g_input->getLeftClicked())
\r
1978 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1979 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1980 selected_object->getId(), g_selected_item);
\r
1982 else if(g_input->getRightClicked())
\r
1984 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1986 Check if we want to modify the object ourselves
\r
1988 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1990 dstream<<"Sign object right-clicked"<<std::endl;
\r
1994 input_guitext = guienv->addStaticText(L"",
\r
1995 core::rect<s32>(150,100,350,120),
\r
1997 false, // wordwrap?
\r
2000 input_guitext->setDrawBackground(true);
\r
2002 g_text_buffer = L"";
\r
2003 g_text_buffer_accepted = false;
\r
2004 textbuf_dest = new TextDestSign(
\r
2005 selected_object->getBlock()->getPos(),
\r
2006 selected_object->getId(),
\r
2010 Otherwise pass the event to the server as-is
\r
2014 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
2015 selected_object->getId(), g_selected_item);
\r
2019 else // selected_object == NULL
\r
2022 bool nodefound = false;
\r
2024 v3s16 neighbourpos;
\r
2025 core::aabbox3d<f32> nodefacebox;
\r
2026 f32 mindistance = BS * 1001;
\r
2028 v3s16 pos_i = floatToInt(player_position);
\r
2030 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2034 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2035 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2036 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2037 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2038 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2039 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2041 for(s16 y = ystart; y <= yend; y++){
\r
2042 for(s16 z = zstart; z <= zend; z++){
\r
2043 for(s16 x = xstart; x <= xend; x++)
\r
2046 if(material_pointable(client.getNode(v3s16(x,y,z)).d) == false)
\r
2048 }catch(InvalidPositionException &e){
\r
2053 v3f npf = intToFloat(np);
\r
2057 v3s16 directions[6] = {
\r
2058 v3s16(0,0,1), // back
\r
2059 v3s16(0,1,0), // top
\r
2060 v3s16(1,0,0), // right
\r
2066 for(u16 i=0; i<6; i++){
\r
2068 v3f dir_f = v3f(directions[i].X,
\r
2069 directions[i].Y, directions[i].Z);
\r
2070 v3f centerpoint = npf + dir_f * BS/2;
\r
2072 (centerpoint - camera_position).getLength();
\r
2074 if(distance < mindistance){
\r
2075 //std::cout<<DTIME<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;
\r
2076 //std::cout<<DTIME<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;
\r
2077 core::CMatrix4<f32> m;
\r
2078 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2080 // This is the back face
\r
2081 v3f corners[2] = {
\r
2082 v3f(BS/2, BS/2, BS/2),
\r
2083 v3f(-BS/2, -BS/2, BS/2+d)
\r
2086 for(u16 j=0; j<2; j++){
\r
2087 m.rotateVect(corners[j]);
\r
2088 corners[j] += npf;
\r
2089 //std::cout<<DTIME<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;
\r
2092 //core::aabbox3d<f32> facebox(corners[0],corners[1]);
\r
2093 core::aabbox3d<f32> facebox(corners[0]);
\r
2094 facebox.addInternalPoint(corners[1]);
\r
2096 if(facebox.intersectsWithLine(shootline)){
\r
2099 neighbourpos = np + directions[i];
\r
2100 mindistance = distance;
\r
2101 nodefacebox = facebox;
\r
2109 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
2110 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2111 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
2113 static v3s16 nodepos_old(-1,-1,-1);
\r
2114 if(nodepos != nodepos_old){
\r
2115 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2116 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2117 nodepos_old = nodepos;
\r
2119 /*wchar_t positiontext[20];
\r
2120 swprintf(positiontext, 20, L"(%i,%i,%i)",
\r
2121 nodepos.X, nodepos.Y, nodepos.Z);
\r
2122 positiontextgui->setText(positiontext);*/
\r
2125 hilightboxes.push_back(nodefacebox);
\r
2127 if(g_input->getLeftClicked())
\r
2129 //std::cout<<DTIME<<"Removing node"<<std::endl;
\r
2130 //client.removeNode(nodepos);
\r
2131 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2132 client.clickGround(0, nodepos, neighbourpos, g_selected_item);
\r
2134 if(g_input->getRightClicked())
\r
2136 //std::cout<<DTIME<<"Placing node"<<std::endl;
\r
2137 //client.addNodeFromInventory(neighbourpos, g_selected_item);
\r
2138 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2139 client.clickGround(1, nodepos, neighbourpos, g_selected_item);
\r
2143 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
2144 //positiontextgui->setText(L"");
\r
2147 } // selected_object == NULL
\r
2149 g_input->resetLeftClicked();
\r
2150 g_input->resetRightClicked();
\r
2153 Calculate stuff for drawing
\r
2156 v2u32 screensize = driver->getScreenSize();
\r
2157 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
2159 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2162 Update gui stuff (0ms)
\r
2165 //TimeTaker guiupdatetimer("Gui updating", device);
\r
2168 wchar_t temptext[100];
\r
2170 static float drawtime_avg = 0;
\r
2171 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2172 static float scenetime_avg = 0;
\r
2173 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2174 static float endscenetime_avg = 0;
\r
2175 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2177 swprintf(temptext, 100, L"Minetest-c55 ("
\r
2179 L", R: range_all=%i"
\r
2181 L" drawtime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2183 g_viewing_range_all,
\r
2189 guitext->setText(temptext);
\r
2193 wchar_t temptext[100];
\r
2194 /*swprintf(temptext, 100,
\r
2196 L"% .3f < btime_jitter < % .3f"
\r
2197 L", dtime_jitter = % .1f %%"
\r
2198 //L", ftime_ratio = % .3f"
\r
2200 busytime_jitter1_min_sample,
\r
2201 busytime_jitter1_max_sample,
\r
2202 dtime_jitter1_max_fraction * 100.0
\r
2203 //g_freetime_ratio
\r
2205 swprintf(temptext, 100,
\r
2206 L"(% .1f, % .1f, % .1f)"
\r
2207 L" (% .3f < btime_jitter < % .3f"
\r
2208 L", dtime_jitter = % .1f %%)",
\r
2209 player_position.X/BS,
\r
2210 player_position.Y/BS,
\r
2211 player_position.Z/BS,
\r
2212 busytime_jitter1_min_sample,
\r
2213 busytime_jitter1_max_sample,
\r
2214 dtime_jitter1_max_fraction * 100.0
\r
2217 guitext2->setText(temptext);
\r
2221 /*wchar_t temptext[100];
\r
2222 swprintf(temptext, 100,
\r
2223 SWPRINTF_CHARSTRING,
\r
2224 infotext.substr(0,99).c_str()
\r
2227 guitext_info->setText(temptext);*/
\r
2229 guitext_info->setText(infotext.c_str());
\r
2236 static u16 old_selected_item = 65535;
\r
2237 if(client.getLocalInventoryUpdated()
\r
2238 || g_selected_item != old_selected_item)
\r
2240 old_selected_item = g_selected_item;
\r
2241 //std::cout<<"Updating local inventory"<<std::endl;
\r
2242 client.getLocalInventory(local_inventory);
\r
2243 quick_inventory->setSelection(g_selected_item);
\r
2244 quick_inventory->update();
\r
2247 if(input_guitext != NULL)
\r
2249 /*wchar_t temptext[100];
\r
2250 swprintf(temptext, 100,
\r
2251 SWPRINTF_CHARSTRING,
\r
2252 g_text_buffer.substr(0,99).c_str()
\r
2254 input_guitext->setText(g_text_buffer.c_str());
\r
2260 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2262 input_guitext->remove();
\r
2263 input_guitext = NULL;
\r
2265 if(textbuf_dest != NULL)
\r
2267 std::string text = wide_to_narrow(g_text_buffer);
\r
2268 dstream<<"Sending text: "<<text<<std::endl;
\r
2269 textbuf_dest->sendText(text);
\r
2270 delete textbuf_dest;
\r
2271 textbuf_dest = NULL;
\r
2277 //guiupdatetimer.stop();
\r
2283 TimeTaker drawtimer("Drawing", device);
\r
2286 Background color is choosen based on whether the player is
\r
2287 much beyond the initial ground level
\r
2289 /*video::SColor bgcolor;
\r
2290 v3s16 p0 = Map::floatToInt(player_position);
\r
2291 // Does this make short random delays?
\r
2292 // NOTE: no need for this, sky doesn't show underground with
\r
2294 bool is_underground = client.isNodeUnderground(p0);
\r
2295 //bool is_underground = false;
\r
2296 if(is_underground == false)
\r
2297 bgcolor = video::SColor(255,90,140,200);
\r
2299 bgcolor = video::SColor(255,0,0,0);*/
\r
2301 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2302 video::SColor bgcolor = skycolor;
\r
2305 driver->beginScene(true, true, bgcolor);
\r
2309 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2312 TimeTaker timer("smgr", device);
\r
2314 scenetime = timer.stop(true);
\r
2318 //TimeTaker timer9("auxiliary drawings", device);
\r
2321 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2322 displaycenter + core::vector2d<s32>(10,0),
\r
2323 video::SColor(255,255,255,255));
\r
2324 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2325 displaycenter + core::vector2d<s32>(0,10),
\r
2326 video::SColor(255,255,255,255));
\r
2329 //TimeTaker //timer10("//timer10", device);
\r
2331 video::SMaterial m;
\r
2333 m.Lighting = false;
\r
2334 driver->setMaterial(m);
\r
2336 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2338 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2339 i != hilightboxes.end(); i++)
\r
2341 /*std::cout<<"hilightbox min="
\r
2342 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2344 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2346 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2352 //TimeTaker //timer11("//timer11", device);
\r
2358 guienv->drawAll();
\r
2362 TimeTaker timer("endScene", device);
\r
2363 driver->endScene();
\r
2364 endscenetime = timer.stop(true);
\r
2367 drawtime = drawtimer.stop(true);
\r
2373 static s16 lastFPS = 0;
\r
2374 //u16 fps = driver->getFPS();
\r
2375 u16 fps = (1.0/dtime_avg1);
\r
2377 if (lastFPS != fps)
\r
2379 core::stringw str = L"Minetest [";
\r
2380 str += driver->getName();
\r
2384 device->setWindowCaption(str.c_str());
\r
2390 device->yield();*/
\r
2393 } // client is deleted at this point
\r
2398 In the end, delete the Irrlicht device.
\r
2403 catch(con::PeerNotFoundException &e)
\r
2405 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2407 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2409 This is what has to be done in every thread to get suitable debug info
\r
2411 catch(std::exception &e)
\r
2413 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2414 <<e.what()<<std::endl;
\r
2419 debugstreams_deinit();
\r