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 SUGG: Fix address to be ipv6 compatible
\r
32 FIXME: When a new sector is generated, it may change the ground level
\r
33 of it's and it's neighbors border that two blocks that are
\r
34 above and below each other and that are generated before and
\r
35 after the sector heightmap generation (order doesn't matter),
\r
36 can have a small gap between each other at the border.
\r
37 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
38 using for UnlimitedHeightmap? (getting all neighbors
\r
41 SUGG: Transfer more blocks in a single packet
\r
42 SUGG: A blockdata combiner class, to which blocks are added and at
\r
43 destruction it sends all the stuff in as few packets as possible.
\r
45 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
46 SUGG: Fetch stuff mainly from the viewing direction
\r
48 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
49 - This enables saving many packets and making a faster connection
\r
50 - This also enables server to check if client has received the
\r
51 most recent block sent, for example.
\r
52 SUGG: Add a sane bandwidth throttling system to Connection
\r
54 SUGG: More fine-grained control of client's dumping of blocks from
\r
56 - ...What does this mean in the first place?
\r
58 SUGG: A map editing mode (similar to dedicated server mode)
\r
60 SUGG: Add a time value to the param of footstepped grass and check it
\r
61 against a global timer when a block is accessed, to make old
\r
64 SUGG: Make a copy of close-range environment on client for showing
\r
65 on screen, with minimal mutexes to slow down the main loop
\r
67 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
68 it by sending more stuff in a single packet.
\r
69 - Add a packet queue to RemoteClient, from which packets will be
\r
70 combined with object data packets
\r
71 - This is not exactly trivial: the object data packets are
\r
72 sometimes very big by themselves
\r
74 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
75 - This will allow saving ages of rats on disk but not sending
\r
78 SUGG: Implement lighting using VoxelManipulator
\r
79 - Would it be significantly faster?
\r
81 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
82 - Does their position get saved to a border value or something?
\r
83 - Does this happen anymore?
\r
85 SUGG: MovingObject::move and Player::move are basically the same.
\r
88 SUGG: Implement a "Fast check queue" (a queue with a map for checking
\r
89 if something is already in it)
\r
90 - Use it in active block queue in water flowing
\r
92 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
93 need an additional metadata field for the texts
\r
95 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
97 SUGG: A version number to blocks, which increments when the block is
\r
98 modified (node add/remove, water update, lighting update)
\r
99 - This can then be used to make sure the most recent version of
\r
100 a block has been sent to client
\r
102 TODO: Stop player if focus of window is taken away (go to pause mode)
\r
104 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
106 - That is >500 vertices
\r
108 TODO: Better dungeons
\r
115 - One single map container with ids as keys
\r
118 TODO: - Keep track of the place of the mob in the last few hundreth's
\r
119 of a second - then, if a player hits it, take the value that is
\r
120 avg_rtt/2 before the moment the packet is received.
\r
123 TODO: Moving players more smoothly. Calculate moving animation
\r
124 in a way that doesn't make the player jump to the right place
\r
125 immediately when the server sends a new position
\r
127 TODO: There are some lighting-related todos and fixmes in
\r
128 ServerMap::emergeBlock
\r
130 TODO: Proper handling of spawning place (try to find something that
\r
131 is not in the middle of an ocean (some land to stand on at
\r
132 least) and save it in map config.
\r
134 TODO: Make the amount of blocks sending to client and the total
\r
135 amount of blocks dynamically limited. Transferring blocks is the
\r
136 main network eater of this system, so it is the one that has
\r
137 to be throttled so that RTTs stay low.
\r
139 TODO: Server to load starting inventory from disk
\r
141 TODO: Players to only be hidden when the client quits.
\r
142 TODO: - Players to be saved on disk, with inventory
\r
143 TODO: Players to be saved as text in map/players/<name>
\r
145 TODO: Make fetching sector's blocks more efficient when rendering
\r
146 sectors that have very large amounts of blocks (on client)
\r
148 TODO: Make the video backend selectable
\r
150 Block object server side:
\r
151 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
152 - For all blocks in the buffer, objects are stepped(). This
\r
153 means they are active.
\r
154 - TODO: A global active buffer is needed for the server
\r
155 - TODO: A timestamp to blocks
\r
156 - TODO: All blocks going in and out of the buffer are recorded.
\r
157 - TODO: For outgoing blocks, timestamp is written.
\r
158 - TODO: For incoming blocks, time difference is calculated and
\r
159 objects are stepped according to it.
\r
161 TODO: Add config parameters for server's sending and generating distance
\r
163 TODO: Copy the text of the last picked sign to inventory in creative
\r
166 TODO: Untie client network operations from framerate
\r
167 - Needs some input queues or something
\r
169 TODO: Get rid of GotSplitPacketException
\r
171 TODO: Check what goes wrong with caching map to disk (Kray)
\r
173 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
175 TODO: Add server unused sector deletion settings to settings
\r
177 TODO: TOSERVER_LEAVE
\r
180 ======================================================================
\r
182 TODO: Node cracking animation when digging
\r
183 - TODO: A way to generate new textures by combining textures
\r
184 - TODO: Mesh update to fetch cracked faces from the former
\r
186 ======================================================================
\r
191 Setting this to 1 enables a special camera mode that forces
\r
192 the renderers to think that the camera statically points from
\r
193 the starting place to a static direction.
\r
195 This allows one to move around with the player and see what
\r
196 is actually drawn behind solid things and behind the player.
\r
198 #define FIELD_OF_VIEW_TEST 0
\r
200 #ifdef UNITTEST_DISABLE
\r
202 #pragma message ("Disabling unit tests")
\r
204 #warning "Disabling unit tests"
\r
206 // Disable unit tests
\r
207 #define ENABLE_TESTS 0
\r
209 // Enable unit tests
\r
210 #define ENABLE_TESTS 1
\r
214 #pragma comment(lib, "Irrlicht.lib")
\r
215 #pragma comment(lib, "jthread.lib")
\r
216 #pragma comment(lib, "zlibwapi.lib")
\r
217 // This would get rid of the console window
\r
218 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
222 #define WIN32_LEAN_AND_MEAN
\r
223 #include <windows.h>
\r
224 #define sleep_ms(x) Sleep(x)
\r
226 #include <unistd.h>
\r
227 #define sleep_ms(x) usleep(x*1000)
\r
230 #include <iostream>
\r
233 #include <jmutexautolock.h>
\r
234 #include <locale.h>
\r
235 #include "common_irrlicht.h"
\r
238 #include "player.h"
\r
241 #include "environment.h"
\r
242 #include "server.h"
\r
243 #include "client.h"
\r
244 #include "serialization.h"
\r
245 #include "constants.h"
\r
246 #include "strfnd.h"
\r
247 #include "porting.h"
\r
248 #include "guiPauseMenu.h"
\r
250 IrrlichtDevice *g_device = NULL;
\r
252 /*const char *g_content_filenames[MATERIALS_COUNT] =
\r
254 "../data/stone.png",
\r
255 "../data/grass.png",
\r
256 "../data/water.png",
\r
257 "../data/torch_on_floor.png",
\r
258 "../data/tree.png",
\r
259 "../data/leaves.png",
\r
260 "../data/grass_footsteps.png",
\r
261 "../data/mese.png",
\r
263 "../data/water.png", // CONTENT_OCEAN
\r
267 video::SMaterial g_materials[MATERIALS_COUNT];*/
\r
270 TextureCache g_texturecache;
\r
273 // All range-related stuff below is locked behind this
\r
274 JMutex g_range_mutex;
\r
276 // Blocks are viewed in this range from the player
\r
277 s16 g_viewing_range_nodes = 60;
\r
278 //s16 g_viewing_range_nodes = 0;
\r
280 // This is updated by the client's fetchBlocks routine
\r
281 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
283 // If true, the preceding value has no meaning and all blocks
\r
284 // already existing in memory are drawn
\r
285 bool g_viewing_range_all = false;
\r
287 // This is the freetime ratio imposed by the dynamic viewing
\r
288 // range changing code.
\r
289 // It is controlled by the main loop to the smallest value that
\r
290 // inhibits glitches (dtime jitter) in the main loop.
\r
291 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
296 These are loaded from the config file.
\r
299 Settings g_settings;
\r
301 extern void set_default_settings();
\r
307 //u16 g_selected_material = 0;
\r
308 u16 g_selected_item = 0;
\r
310 bool g_esc_pressed = false;
\r
312 std::wstring g_text_buffer;
\r
313 bool g_text_buffer_accepted = false;
\r
315 // When true, the mouse and keyboard are grabbed
\r
316 bool g_game_focused = true;
\r
323 std::ostream *dout_con_ptr = &dummyout;
\r
324 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
325 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
326 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
327 //std::ostream *dout_con_ptr = &dstream;
\r
328 //std::ostream *derr_con_ptr = &dstream;
\r
331 std::ostream *dout_server_ptr = &dstream;
\r
332 std::ostream *derr_server_ptr = &dstream;
\r
335 std::ostream *dout_client_ptr = &dstream;
\r
336 std::ostream *derr_client_ptr = &dstream;
\r
343 JMutex g_timestamp_mutex;
\r
344 //std::string g_timestamp;
\r
346 std::string getTimestamp()
\r
348 if(g_timestamp_mutex.IsInitialized()==false)
\r
350 JMutexAutoLock lock(g_timestamp_mutex);
\r
351 //return g_timestamp;
\r
352 time_t t = time(NULL);
\r
353 struct tm *tm = localtime(&t);
\r
355 strftime(cs, 20, "%H:%M:%S", tm);
\r
359 class MyEventReceiver : public IEventReceiver
\r
362 // This is the one method that we have to implement
\r
363 virtual bool OnEvent(const SEvent& event)
\r
365 // Remember whether each key is down or up
\r
366 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
368 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
370 if(event.KeyInput.PressedDown)
\r
372 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
373 if(g_game_focused == false)
\r
375 s16 key = event.KeyInput.Key;
\r
376 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
378 g_text_buffer_accepted = true;
\r
380 else if(key == irr::KEY_BACK)
\r
382 if(g_text_buffer.size() > 0)
\r
383 g_text_buffer = g_text_buffer.substr
\r
384 (0, g_text_buffer.size()-1);
\r
388 wchar_t wc = event.KeyInput.Char;
\r
390 g_text_buffer += wc;
\r
394 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
396 //TODO: Not used anymore?
\r
397 if(g_game_focused == true)
\r
399 dstream<<DTIME<<"ESC pressed"<<std::endl;
\r
400 g_esc_pressed = true;
\r
404 // Material selection
\r
405 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
407 if(g_game_focused == true)
\r
409 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
412 g_selected_item = 0;
\r
413 dstream<<DTIME<<"Selected item: "
\r
414 <<g_selected_item<<std::endl;
\r
418 // Viewing range selection
\r
419 if(event.KeyInput.Key == irr::KEY_KEY_R
\r
422 JMutexAutoLock lock(g_range_mutex);
\r
423 if(g_viewing_range_all)
\r
425 g_viewing_range_all = false;
\r
426 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
430 g_viewing_range_all = true;
\r
431 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
435 // Print debug stacks
\r
436 if(event.KeyInput.Key == irr::KEY_KEY_P
\r
439 dstream<<"-----------------------------------------"
\r
441 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
442 dstream<<"-----------------------------------------"
\r
444 debug_stacks_print();
\r
449 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
451 left_active = event.MouseInput.isLeftPressed();
\r
452 middle_active = event.MouseInput.isMiddlePressed();
\r
453 right_active = event.MouseInput.isRightPressed();
\r
455 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
457 leftclicked = true;
\r
459 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
461 rightclicked = true;
\r
463 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
465 leftreleased = true;
\r
467 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
469 rightreleased = true;
\r
471 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
473 /*dstream<<"event.MouseInput.Wheel="
\r
474 <<event.MouseInput.Wheel<<std::endl;*/
\r
475 if(event.MouseInput.Wheel < 0)
\r
477 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
480 g_selected_item = 0;
\r
482 else if(event.MouseInput.Wheel > 0)
\r
484 if(g_selected_item > 0)
\r
487 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
495 // This is used to check whether a key is being held down
\r
496 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
498 return keyIsDown[keyCode];
\r
503 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
504 keyIsDown[i] = false;
\r
505 leftclicked = false;
\r
506 rightclicked = false;
\r
507 leftreleased = false;
\r
508 rightreleased = false;
\r
510 left_active = false;
\r
511 middle_active = false;
\r
512 right_active = false;
\r
518 bool rightreleased;
\r
521 bool middle_active;
\r
525 // We use this array to store the current state of each key
\r
526 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
537 virtual ~InputHandler()
\r
541 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
543 virtual v2s32 getMousePos() = 0;
\r
544 virtual void setMousePos(s32 x, s32 y) = 0;
\r
546 virtual bool getLeftState() = 0;
\r
547 virtual bool getRightState() = 0;
\r
549 virtual bool getLeftClicked() = 0;
\r
550 virtual bool getRightClicked() = 0;
\r
551 virtual void resetLeftClicked() = 0;
\r
552 virtual void resetRightClicked() = 0;
\r
554 virtual bool getLeftReleased() = 0;
\r
555 virtual bool getRightReleased() = 0;
\r
556 virtual void resetLeftReleased() = 0;
\r
557 virtual void resetRightReleased() = 0;
\r
559 virtual void step(float dtime) {};
\r
561 virtual void clear() {};
\r
564 InputHandler *g_input = NULL;
\r
569 g_game_focused = true;
\r
574 g_game_focused = false;
\r
577 class RealInputHandler : public InputHandler
\r
580 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
582 m_receiver(receiver)
\r
585 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
587 return m_receiver->IsKeyDown(keyCode);
\r
589 virtual v2s32 getMousePos()
\r
591 return m_device->getCursorControl()->getPosition();
\r
593 virtual void setMousePos(s32 x, s32 y)
\r
595 m_device->getCursorControl()->setPosition(x, y);
\r
598 virtual bool getLeftState()
\r
600 return m_receiver->left_active;
\r
602 virtual bool getRightState()
\r
604 return m_receiver->right_active;
\r
607 virtual bool getLeftClicked()
\r
609 if(g_game_focused == false)
\r
611 return m_receiver->leftclicked;
\r
613 virtual bool getRightClicked()
\r
615 if(g_game_focused == false)
\r
617 return m_receiver->rightclicked;
\r
619 virtual void resetLeftClicked()
\r
621 m_receiver->leftclicked = false;
\r
623 virtual void resetRightClicked()
\r
625 m_receiver->rightclicked = false;
\r
628 virtual bool getLeftReleased()
\r
630 if(g_game_focused == false)
\r
632 return m_receiver->leftreleased;
\r
634 virtual bool getRightReleased()
\r
636 if(g_game_focused == false)
\r
638 return m_receiver->rightreleased;
\r
640 virtual void resetLeftReleased()
\r
642 m_receiver->leftreleased = false;
\r
644 virtual void resetRightReleased()
\r
646 m_receiver->rightreleased = false;
\r
651 resetRightClicked();
\r
652 resetLeftClicked();
\r
655 IrrlichtDevice *m_device;
\r
656 MyEventReceiver *m_receiver;
\r
659 class RandomInputHandler : public InputHandler
\r
662 RandomInputHandler()
\r
664 leftclicked = false;
\r
665 rightclicked = false;
\r
666 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
667 keydown[i] = false;
\r
669 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
671 return keydown[keyCode];
\r
673 virtual v2s32 getMousePos()
\r
677 virtual void setMousePos(s32 x, s32 y)
\r
679 mousepos = v2s32(x,y);
\r
682 virtual bool getLeftState()
\r
686 virtual bool getRightState()
\r
691 virtual bool getLeftClicked()
\r
693 return leftclicked;
\r
695 virtual bool getRightClicked()
\r
697 return rightclicked;
\r
699 virtual void resetLeftClicked()
\r
701 leftclicked = false;
\r
703 virtual void resetRightClicked()
\r
705 rightclicked = false;
\r
708 virtual bool getLeftReleased()
\r
712 virtual bool getRightReleased()
\r
716 virtual void resetLeftReleased()
\r
719 virtual void resetRightReleased()
\r
723 virtual void step(float dtime)
\r
726 static float counter1 = 0;
\r
730 counter1 = 0.1*Rand(1,10);
\r
731 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
732 g_selected_material++;
\r
734 g_selected_material = 0;*/
\r
735 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
738 g_selected_item = 0;
\r
742 static float counter1 = 0;
\r
746 counter1 = 0.1*Rand(1, 40);
\r
747 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
751 static float counter1 = 0;
\r
755 counter1 = 0.1*Rand(1, 40);
\r
756 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
760 static float counter1 = 0;
\r
764 counter1 = 0.1*Rand(1, 40);
\r
765 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
769 static float counter1 = 0;
\r
773 counter1 = 0.1*Rand(1, 40);
\r
774 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
778 static float counter1 = 0;
\r
782 counter1 = 0.1*Rand(1, 20);
\r
783 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
787 static float counter1 = 0;
\r
791 counter1 = 0.1*Rand(1, 30);
\r
792 leftclicked = true;
\r
796 static float counter1 = 0;
\r
800 counter1 = 0.1*Rand(1, 20);
\r
801 rightclicked = true;
\r
804 mousepos += mousespeed;
\r
807 s32 Rand(s32 min, s32 max)
\r
809 return (rand()%(max-min+1))+min;
\r
812 bool keydown[KEY_KEY_CODES_COUNT];
\r
819 void updateViewingRange(f32 frametime, Client *client)
\r
821 // Range_all messes up frametime_avg
\r
822 if(g_viewing_range_all == true)
\r
825 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
827 // Initialize to the target value
\r
828 static float frametime_avg = 1.0/wanted_fps;
\r
829 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
830 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
832 static f32 counter = 0;
\r
834 counter -= frametime;
\r
837 //counter = 1.0; //seconds
\r
838 counter = 0.5; //seconds
\r
840 //float freetime_ratio = 0.2;
\r
841 //float freetime_ratio = 0.4;
\r
842 float freetime_ratio = FREETIME_RATIO;
\r
844 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
846 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
848 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
849 + frametime_avg / frametime_wanted / 2.0;*/
\r
851 //float fraction = frametime_avg / frametime_wanted;
\r
853 static bool fraction_is_good = false;
\r
855 float fraction_good_threshold = 0.1;
\r
856 //float fraction_bad_threshold = 0.25;
\r
857 float fraction_bad_threshold = 0.1;
\r
858 float fraction_limit;
\r
859 // Use high limit if fraction is good AND the fraction would
\r
860 // lower the range. We want to keep the range fairly high.
\r
861 if(fraction_is_good && fraction > 1.0)
\r
862 fraction_limit = fraction_bad_threshold;
\r
864 fraction_limit = fraction_good_threshold;
\r
866 if(fabs(fraction - 1.0) < fraction_limit)
\r
868 fraction_is_good = true;
\r
873 fraction_is_good = false;
\r
876 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
877 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
878 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
879 <<" faction = "<<fraction<<std::endl;*/
\r
881 JMutexAutoLock lock(g_range_mutex);
\r
883 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
884 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
886 s16 n = (float)g_viewing_range_nodes / fraction;
\r
887 if(n < viewing_range_nodes_min)
\r
888 n = viewing_range_nodes_min;
\r
889 if(n > viewing_range_nodes_max)
\r
890 n = viewing_range_nodes_max;
\r
892 bool can_change = true;
\r
894 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
895 can_change = false;
\r
898 g_viewing_range_nodes = n;
\r
900 /*dstream<<"g_viewing_range_nodes = "
\r
901 <<g_viewing_range_nodes<<std::endl;*/
\r
904 class GUIQuickInventory : public IEventReceiver
\r
908 gui::IGUIEnvironment* env,
\r
909 gui::IGUIElement* parent,
\r
912 Inventory *inventory):
\r
913 m_itemcount(itemcount),
\r
914 m_inventory(inventory)
\r
916 core::rect<s32> imgsize(0,0,48,48);
\r
917 core::rect<s32> textsize(0,0,48,16);
\r
918 v2s32 spacing(0, 64);
\r
919 for(s32 i=0; i<m_itemcount; i++)
\r
921 m_images.push_back(env->addImage(
\r
922 imgsize + pos + spacing*i
\r
924 m_images[i]->setScaleImage(true);
\r
925 m_texts.push_back(env->addStaticText(
\r
927 textsize + pos + spacing*i,
\r
930 m_texts[i]->setBackgroundColor(
\r
931 video::SColor(128,0,0,0));
\r
932 m_texts[i]->setTextAlignment(
\r
934 gui::EGUIA_UPPERLEFT);
\r
938 virtual bool OnEvent(const SEvent& event)
\r
943 void setSelection(s32 i)
\r
952 start = m_selection - m_itemcount / 2;
\r
954 for(s32 i=0; i<m_itemcount; i++)
\r
958 if(j > (s32)m_inventory->getSize() - 1)
\r
959 j -= m_inventory->getSize();
\r
961 j += m_inventory->getSize();
\r
963 InventoryItem *item = m_inventory->getItem(j);
\r
967 m_images[i]->setImage(NULL);
\r
970 if(m_selection == j)
\r
971 swprintf(t, 10, L"<-");
\r
973 swprintf(t, 10, L"");
\r
974 m_texts[i]->setText(t);
\r
976 // The next ifs will segfault with a NULL pointer
\r
981 m_images[i]->setImage(item->getImage());
\r
984 if(m_selection == j)
\r
985 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
987 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
988 m_texts[i]->setText(t);
\r
994 core::array<gui::IGUIStaticText*> m_texts;
\r
995 core::array<gui::IGUIImage*> m_images;
\r
996 Inventory *m_inventory;
\r
1000 int main(int argc, char *argv[])
\r
1003 Low-level initialization
\r
1006 bool disable_stderr = false;
\r
1008 disable_stderr = true;
\r
1011 // Initialize debug streams
\r
1012 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1013 // Initialize debug stacks
\r
1014 debug_stacks_init();
\r
1016 DSTACK(__FUNCTION_NAME);
\r
1022 Parse command line
\r
1025 // List all allowed options
\r
1026 core::map<std::string, ValueSpec> allowed_options;
\r
1027 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1028 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1029 "Run server directly"));
\r
1030 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1031 "Load configuration from specified file"));
\r
1032 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1033 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1034 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1035 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1036 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1038 Settings cmd_args;
\r
1040 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1042 if(ret == false || cmd_args.getFlag("help"))
\r
1044 dstream<<"Allowed options:"<<std::endl;
\r
1045 for(core::map<std::string, ValueSpec>::Iterator
\r
1046 i = allowed_options.getIterator();
\r
1047 i.atEnd() == false; i++)
\r
1049 dstream<<" --"<<i.getNode()->getKey();
\r
1050 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1055 dstream<<" <value>";
\r
1057 dstream<<std::endl;
\r
1059 if(i.getNode()->getValue().help != NULL)
\r
1061 dstream<<" "<<i.getNode()->getValue().help
\r
1066 return cmd_args.getFlag("help") ? 0 : 1;
\r
1071 Basic initialization
\r
1074 // Initialize default settings
\r
1075 set_default_settings();
\r
1077 // Print startup message
\r
1078 dstream<<DTIME<<"minetest-c55"
\r
1079 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1080 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1083 // Set locale. This is for forcing '.' as the decimal point.
\r
1084 std::locale::global(std::locale("C"));
\r
1085 // This enables printing all characters in bitmap font
\r
1086 setlocale(LC_CTYPE, "en_US");
\r
1088 // Initialize sockets
\r
1090 atexit(sockets_cleanup);
\r
1092 // Initialize timestamp mutex
\r
1093 g_timestamp_mutex.Init();
\r
1103 // Path of configuration file in use
\r
1104 std::string configpath = "";
\r
1106 if(cmd_args.exists("config"))
\r
1108 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1111 dstream<<"Could not read configuration from \""
\r
1112 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1115 configpath = cmd_args.get("config");
\r
1119 const char *filenames[2] =
\r
1121 "../minetest.conf",
\r
1122 "../../minetest.conf"
\r
1125 for(u32 i=0; i<2; i++)
\r
1127 bool r = g_settings.readConfigFile(filenames[i]);
\r
1130 configpath = filenames[i];
\r
1136 // Initialize random seed
\r
1142 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1143 || cmd_args.getFlag("enable-unittests") == true)
\r
1149 Global range mutex
\r
1151 g_range_mutex.Init();
\r
1152 assert(g_range_mutex.IsInitialized());
\r
1154 // Read map parameters from settings
\r
1156 HMParams hm_params;
\r
1157 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1158 hm_params.randmax = g_settings.get("height_randmax");
\r
1159 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1160 hm_params.base = g_settings.get("height_base");
\r
1162 MapParams map_params;
\r
1163 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1164 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1170 std::cout<<std::endl<<std::endl;
\r
1173 <<" .__ __ __ "<<std::endl
\r
1174 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1175 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1176 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1177 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1178 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1180 <<"Now with more waterish water!"
\r
1183 std::cout<<std::endl;
\r
1184 char templine[100];
\r
1188 if(cmd_args.exists("port"))
\r
1190 port = cmd_args.getU16("port");
\r
1194 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1195 std::cout<<"-> "<<port<<std::endl;
\r
1198 if(cmd_args.getFlag("server"))
\r
1200 DSTACK("Dedicated server branch");
\r
1202 std::cout<<std::endl;
\r
1203 std::cout<<"========================"<<std::endl;
\r
1204 std::cout<<"Running dedicated server"<<std::endl;
\r
1205 std::cout<<"========================"<<std::endl;
\r
1206 std::cout<<std::endl;
\r
1208 Server server("../map", hm_params, map_params);
\r
1209 server.start(port);
\r
1213 // This is kind of a hack but can be done like this
\r
1214 // because server.step() is very light
\r
1216 server.step(0.030);
\r
1218 static int counter = 0;
\r
1224 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1225 core::list<PlayerInfo>::Iterator i;
\r
1226 static u32 sum_old = 0;
\r
1227 u32 sum = PIChecksum(list);
\r
1228 if(sum != sum_old)
\r
1230 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1231 for(i=list.begin(); i!=list.end(); i++)
\r
1233 i->PrintLine(&std::cout);
\r
1243 bool hosting = false;
\r
1244 char connect_name[100] = "";
\r
1246 if(cmd_args.exists("address"))
\r
1248 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1250 else if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1252 std::cout<<g_settings.get("address")<<std::endl;
\r
1253 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1257 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1258 std::cin.getline(connect_name, 100);
\r
1261 if(connect_name[0] == 0){
\r
1262 snprintf(connect_name, 100, "127.0.0.1");
\r
1267 std::cout<<"> Hosting game"<<std::endl;
\r
1269 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1271 char playername[PLAYERNAME_SIZE] = "";
\r
1272 if(g_settings.get("name") != "")
\r
1274 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1278 std::cout<<"Name of player: ";
\r
1279 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1281 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1284 Resolution selection
\r
1289 bool fullscreen = false;
\r
1291 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1293 screenW = atoi(g_settings.get("screenW").c_str());
\r
1294 screenH = atoi(g_settings.get("screenH").c_str());
\r
1298 u16 resolutions[][3] = {
\r
1299 //W, H, fullscreen
\r
1310 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1312 for(u16 i=0; i<res_count; i++)
\r
1314 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1315 <<resolutions[i][1];
\r
1316 if(resolutions[i][2])
\r
1317 std::cout<<" fullscreen"<<std::endl;
\r
1319 std::cout<<" windowed"<<std::endl;
\r
1321 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1322 std::cin.getline(templine, 100);
\r
1325 if(templine[0] == 0)
\r
1328 r0 = atoi(templine);
\r
1330 if(r0 > res_count || r0 == 0)
\r
1336 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1337 <<resolutions[i][1];
\r
1338 if(resolutions[i][2])
\r
1339 std::cout<<" fullscreen"<<std::endl;
\r
1341 std::cout<<" windowed"<<std::endl;
\r
1344 screenW = resolutions[r0-1][0];
\r
1345 screenH = resolutions[r0-1][1];
\r
1346 fullscreen = resolutions[r0-1][2];
\r
1351 MyEventReceiver receiver;
\r
1353 video::E_DRIVER_TYPE driverType;
\r
1356 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1357 driverType = video::EDT_OPENGL;
\r
1359 driverType = video::EDT_OPENGL;
\r
1362 // create device and exit if creation failed
\r
1364 IrrlichtDevice *device;
\r
1365 device = createDevice(driverType,
\r
1366 core::dimension2d<u32>(screenW, screenH),
\r
1367 16, fullscreen, false, false, &receiver);
\r
1369 /*device = createDevice(driverType,
\r
1370 core::dimension2d<u32>(screenW, screenH),
\r
1371 16, fullscreen, false, true, &receiver);*/
\r
1374 return 1; // could not create selected driver.
\r
1376 g_device = device;
\r
1378 device->setResizable(true);
\r
1380 bool random_input = g_settings.getBool("random_input")
\r
1381 || cmd_args.getFlag("random-input");
\r
1383 g_input = new RandomInputHandler();
\r
1385 g_input = new RealInputHandler(device, &receiver);
\r
1388 Continue initialization
\r
1391 video::IVideoDriver* driver = device->getVideoDriver();
\r
1392 // These make the textures not to show at all
\r
1393 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1394 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1396 //driver->setMinHardwareBufferVertexCount(1);
\r
1398 scene::ISceneManager* smgr = device->getSceneManager();
\r
1401 guiPauseMenu pauseMenu(device, &receiver);
\r
1403 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1404 gui::IGUISkin* skin = guienv->getSkin();
\r
1405 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1407 skin->setFont(font);
\r
1408 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1409 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1410 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1411 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1412 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1413 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1415 const wchar_t *text = L"Loading and connecting...";
\r
1416 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1417 core::dimension2d<u32> textd = font->getDimension(text);
\r
1418 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1419 // Have to add a bit to disable the text from word wrapping
\r
1420 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1421 core::vector2d<s32> textsize(300, textd.Height);
\r
1422 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1424 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1425 text, textrect, false, false);
\r
1426 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1428 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1429 guienv->drawAll();
\r
1430 driver->endScene();
\r
1433 Preload some random textures that are used in threads
\r
1436 g_texturecache.set("torch", driver->getTexture("../data/torch.png"));
\r
1437 g_texturecache.set("torch_on_floor", driver->getTexture("../data/torch_on_floor.png"));
\r
1438 g_texturecache.set("torch_on_ceiling", driver->getTexture("../data/torch_on_ceiling.png"));
\r
1441 Load tile textures
\r
1443 for(s32 i=0; i<TILES_COUNT; i++)
\r
1445 if(g_tile_texture_names[i] == NULL)
\r
1447 std::string name = g_tile_texture_names[i];
\r
1448 std::string filename;
\r
1449 filename += "../data/";
\r
1451 filename += ".png";
\r
1452 g_texturecache.set(name, driver->getTexture(filename.c_str()));
\r
1455 tile_materials_preload(g_texturecache);
\r
1458 Make a scope here for the client so that it gets removed
\r
1459 before the irrlicht device
\r
1463 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1468 SharedPtr<Server> server;
\r
1470 server = new Server("../map", hm_params, map_params);
\r
1471 server->start(port);
\r
1478 Client client(device, playername,
\r
1480 g_viewing_range_nodes,
\r
1481 g_viewing_range_all);
\r
1483 Address connect_address(0,0,0,0, port);
\r
1485 connect_address.Resolve(connect_name);
\r
1487 catch(ResolveError &e)
\r
1489 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1493 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1494 client.connect(connect_address);
\r
1497 while(client.connectedAndInitialized() == false)
\r
1500 if(server != NULL){
\r
1501 server->step(0.1);
\r
1506 catch(con::PeerNotFoundException &e)
\r
1508 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1515 /*scene::ISceneNode* skybox;
\r
1516 skybox = smgr->addSkyBoxSceneNode(
\r
1517 driver->getTexture("../data/skybox2.png"),
\r
1518 driver->getTexture("../data/skybox3.png"),
\r
1519 driver->getTexture("../data/skybox1.png"),
\r
1520 driver->getTexture("../data/skybox1.png"),
\r
1521 driver->getTexture("../data/skybox1.png"),
\r
1522 driver->getTexture("../data/skybox1.png"));*/
\r
1525 Create the camera node
\r
1528 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1529 0, // Camera parent
\r
1530 v3f(BS*100, BS*2, BS*100), // Look from
\r
1531 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1535 if(camera == NULL)
\r
1538 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1540 camera->setFOV(FOV_ANGLE);
\r
1542 // Just so big a value that everything rendered is visible
\r
1543 camera->setFarValue(100000*BS);
\r
1545 f32 camera_yaw = 0; // "right/left"
\r
1546 f32 camera_pitch = 0; // "up/down"
\r
1552 gui_loadingtext->remove();
\r
1554 pauseMenu.setVisible(true);
\r
1557 Add some gui stuff
\r
1560 // First line of debug text
\r
1561 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1563 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1565 // Second line of debug text
\r
1566 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1568 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1571 // At the middle of the screen
\r
1572 // Object infos are shown in this
\r
1573 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1575 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1578 // This is a copy of the inventory that the client's environment has
\r
1579 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1581 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1582 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1585 Some statistics are collected in these
\r
1588 u32 beginscenetime = 0;
\r
1589 u32 scenetime = 0;
\r
1590 u32 endscenetime = 0;
\r
1598 virtual void sendText(std::string text) = 0;
\r
1601 struct TextDestSign : public TextDest
\r
1603 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1605 m_blockpos = blockpos;
\r
1607 m_client = client;
\r
1609 void sendText(std::string text)
\r
1611 dstream<<"Changing text of a sign object: "
\r
1612 <<text<<std::endl;
\r
1613 m_client->sendSignText(m_blockpos, m_id, text);
\r
1621 TextDest *textbuf_dest = NULL;
\r
1623 //gui::IGUIWindow* input_window = NULL;
\r
1624 gui::IGUIStaticText* input_guitext = NULL;
\r
1635 bool first_loop_after_window_activation = true;
\r
1637 // Time is in milliseconds
\r
1638 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1639 // NOTE: Have to call run() between calls of this to update the timer
\r
1640 u32 lasttime = device->getTimer()->getTime();
\r
1642 while(device->run())
\r
1645 Random calculations
\r
1647 v2u32 screensize = driver->getScreenSize();
\r
1648 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1650 // Hilight boxes collected during the loop and displayed
\r
1651 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1654 std::wstring infotext;
\r
1656 //TimeTaker //timer1("//timer1", device);
\r
1658 // Time of frame without fps limit
\r
1662 // not using getRealTime is necessary for wine
\r
1663 u32 time = device->getTimer()->getTime();
\r
1664 if(time > lasttime)
\r
1665 busytime_u32 = time - lasttime;
\r
1668 busytime = busytime_u32 / 1000.0;
\r
1671 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1673 // Absolutelu necessary for wine!
\r
1680 //updateViewingRange(dtime, &client);
\r
1681 updateViewingRange(busytime, &client);
\r
1688 float fps_max = g_settings.getFloat("fps_max");
\r
1689 u32 frametime_min = 1000./fps_max;
\r
1691 if(busytime_u32 < frametime_min)
\r
1693 u32 sleeptime = frametime_min - busytime_u32;
\r
1694 device->sleep(sleeptime);
\r
1698 // Absolutelu necessary for wine!
\r
1702 Time difference calculation
\r
1704 f32 dtime; // in seconds
\r
1706 u32 time = device->getTimer()->getTime();
\r
1707 if(time > lasttime)
\r
1708 dtime = (time - lasttime) / 1000.0;
\r
1714 Time average and jitter calculation
\r
1717 static f32 dtime_avg1 = 0.0;
\r
1718 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1719 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1721 static f32 dtime_jitter1_max_sample = 0.0;
\r
1722 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1724 static f32 jitter1_max = 0.0;
\r
1725 static f32 counter = 0.0;
\r
1726 if(dtime_jitter1 > jitter1_max)
\r
1727 jitter1_max = dtime_jitter1;
\r
1732 dtime_jitter1_max_sample = jitter1_max;
\r
1733 dtime_jitter1_max_fraction
\r
1734 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1735 jitter1_max = 0.0;
\r
1738 Control freetime ratio
\r
1740 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1742 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1743 g_freetime_ratio += 0.01;
\r
1747 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1748 g_freetime_ratio -= 0.01;
\r
1754 Busytime average and jitter calculation
\r
1757 static f32 busytime_avg1 = 0.0;
\r
1758 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1759 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1761 static f32 busytime_jitter1_max_sample = 0.0;
\r
1762 static f32 busytime_jitter1_min_sample = 0.0;
\r
1764 static f32 jitter1_max = 0.0;
\r
1765 static f32 jitter1_min = 0.0;
\r
1766 static f32 counter = 0.0;
\r
1767 if(busytime_jitter1 > jitter1_max)
\r
1768 jitter1_max = busytime_jitter1;
\r
1769 if(busytime_jitter1 < jitter1_min)
\r
1770 jitter1_min = busytime_jitter1;
\r
1772 if(counter > 0.0){
\r
1774 busytime_jitter1_max_sample = jitter1_max;
\r
1775 busytime_jitter1_min_sample = jitter1_min;
\r
1776 jitter1_max = 0.0;
\r
1777 jitter1_min = 0.0;
\r
1782 Debug info for client
\r
1785 static float counter = 0.0;
\r
1790 client.printDebugInfo(std::cout);
\r
1795 Input handler step()
\r
1797 g_input->step(dtime);
\r
1802 /*if(g_esc_pressed)
\r
1808 Player speed control
\r
1811 if(g_game_focused)
\r
1818 bool a_superspeed,
\r
1821 PlayerControl control(
\r
1822 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1823 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1824 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1825 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1826 g_input->isKeyDown(irr::KEY_SPACE),
\r
1827 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1831 client.setPlayerControl(control);
\r
1835 // Set every key to inactive
\r
1836 PlayerControl control;
\r
1837 client.setPlayerControl(control);
\r
1842 Process environment
\r
1846 //TimeTaker timer("client.step(dtime)", device);
\r
1847 client.step(dtime);
\r
1848 //client.step(dtime_avg1);
\r
1851 if(server != NULL)
\r
1853 //TimeTaker timer("server->step(dtime)", device);
\r
1854 server->step(dtime);
\r
1857 v3f player_position = client.getPlayerPosition();
\r
1859 //TimeTaker //timer2("//timer2", device);
\r
1862 Mouse and camera control
\r
1865 if((device->isWindowActive() && g_game_focused && !pauseMenu.isVisible())
\r
1869 device->getCursorControl()->setVisible(false);
\r
1871 if(first_loop_after_window_activation){
\r
1872 //std::cout<<"window active, first loop"<<std::endl;
\r
1873 first_loop_after_window_activation = false;
\r
1876 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1877 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1878 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1879 camera_yaw -= dx*0.2;
\r
1880 camera_pitch += dy*0.2;
\r
1881 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1882 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1884 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1887 device->getCursorControl()->setVisible(true);
\r
1889 //std::cout<<"window inactive"<<std::endl;
\r
1890 first_loop_after_window_activation = true;
\r
1893 camera_yaw = wrapDegrees(camera_yaw);
\r
1894 camera_pitch = wrapDegrees(camera_pitch);
\r
1896 v3f camera_direction = v3f(0,0,1);
\r
1897 camera_direction.rotateYZBy(camera_pitch);
\r
1898 camera_direction.rotateXZBy(camera_yaw);
\r
1900 v3f camera_position =
\r
1901 player_position + v3f(0, BS+BS/2, 0);
\r
1903 camera->setPosition(camera_position);
\r
1904 // *100.0 helps in large map coordinates
\r
1905 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1907 if(FIELD_OF_VIEW_TEST){
\r
1908 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1909 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1912 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1913 //TimeTaker timer("client.updateCamera", device);
\r
1914 client.updateCamera(camera_position, camera_direction);
\r
1918 //TimeTaker //timer3("//timer3", device);
\r
1921 Calculate what block is the crosshair pointing to
\r
1924 //u32 t1 = device->getTimer()->getRealTime();
\r
1926 //f32 d = 4; // max. distance
\r
1927 f32 d = 4; // max. distance
\r
1928 core::line3d<f32> shootline(camera_position,
\r
1929 camera_position + camera_direction * BS * (d+1));
\r
1931 MapBlockObject *selected_object = client.getSelectedObject
\r
1932 (d*BS, camera_position, shootline);
\r
1934 if(selected_object != NULL)
\r
1936 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1938 core::aabbox3d<f32> box_on_map
\r
1939 = selected_object->getSelectionBoxOnMap();
\r
1941 hilightboxes.push_back(box_on_map);
\r
1943 infotext = narrow_to_wide(selected_object->infoText());
\r
1945 if(g_input->getLeftClicked())
\r
1947 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1948 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1949 selected_object->getId(), g_selected_item);
\r
1951 else if(g_input->getRightClicked())
\r
1953 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1955 Check if we want to modify the object ourselves
\r
1957 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1959 dstream<<"Sign object right-clicked"<<std::endl;
\r
1963 input_guitext = guienv->addStaticText(L"",
\r
1964 core::rect<s32>(150,100,350,120),
\r
1966 false, // wordwrap?
\r
1969 input_guitext->setDrawBackground(true);
\r
1973 g_text_buffer = L"ASD LOL 8)";
\r
1974 g_text_buffer_accepted = true;
\r
1978 g_text_buffer = L"";
\r
1979 g_text_buffer_accepted = false;
\r
1982 textbuf_dest = new TextDestSign(
\r
1983 selected_object->getBlock()->getPos(),
\r
1984 selected_object->getId(),
\r
1988 Otherwise pass the event to the server as-is
\r
1992 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1993 selected_object->getId(), g_selected_item);
\r
1997 else // selected_object == NULL
\r
2001 Find out which node we are pointing at
\r
2004 bool nodefound = false;
\r
2006 v3s16 neighbourpos;
\r
2007 core::aabbox3d<f32> nodefacebox;
\r
2008 f32 mindistance = BS * 1001;
\r
2010 v3s16 pos_i = floatToInt(player_position);
\r
2012 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2016 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2017 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2018 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2019 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2020 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2021 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2023 for(s16 y = ystart; y <= yend; y++)
\r
2024 for(s16 z = zstart; z <= zend; z++)
\r
2025 for(s16 x = xstart; x <= xend; x++)
\r
2030 n = client.getNode(v3s16(x,y,z));
\r
2031 if(content_pointable(n.d) == false)
\r
2034 catch(InvalidPositionException &e)
\r
2040 v3f npf = intToFloat(np);
\r
2045 v3s16(0,0,1), // back
\r
2046 v3s16(0,1,0), // top
\r
2047 v3s16(1,0,0), // right
\r
2048 v3s16(0,0,-1), // front
\r
2049 v3s16(0,-1,0), // bottom
\r
2050 v3s16(-1,0,0), // left
\r
2056 if(n.d == CONTENT_TORCH)
\r
2058 v3s16 dir = unpackDir(n.dir);
\r
2059 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2060 dir_f *= BS/2 - BS/6 - BS/20;
\r
2061 v3f cpf = npf + dir_f;
\r
2062 f32 distance = (cpf - camera_position).getLength();
\r
2064 core::aabbox3d<f32> box;
\r
2067 if(dir == v3s16(0,-1,0))
\r
2069 box = core::aabbox3d<f32>(
\r
2070 npf - v3f(BS/6, BS/2, BS/6),
\r
2071 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2075 else if(dir == v3s16(0,1,0))
\r
2077 box = core::aabbox3d<f32>(
\r
2078 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2079 npf + v3f(BS/6, BS/2, BS/6)
\r
2085 box = core::aabbox3d<f32>(
\r
2086 cpf - v3f(BS/6, BS/3, BS/6),
\r
2087 cpf + v3f(BS/6, BS/3, BS/6)
\r
2091 if(distance < mindistance)
\r
2093 if(box.intersectsWithLine(shootline))
\r
2097 neighbourpos = np;
\r
2098 mindistance = distance;
\r
2099 nodefacebox = box;
\r
2108 for(u16 i=0; i<6; i++)
\r
2110 v3f dir_f = v3f(dirs[i].X,
\r
2111 dirs[i].Y, dirs[i].Z);
\r
2112 v3f centerpoint = npf + dir_f * BS/2;
\r
2114 (centerpoint - camera_position).getLength();
\r
2116 if(distance < mindistance)
\r
2118 core::CMatrix4<f32> m;
\r
2119 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2121 // This is the back face
\r
2122 v3f corners[2] = {
\r
2123 v3f(BS/2, BS/2, BS/2),
\r
2124 v3f(-BS/2, -BS/2, BS/2+d)
\r
2127 for(u16 j=0; j<2; j++)
\r
2129 m.rotateVect(corners[j]);
\r
2130 corners[j] += npf;
\r
2133 core::aabbox3d<f32> facebox(corners[0]);
\r
2134 facebox.addInternalPoint(corners[1]);
\r
2136 if(facebox.intersectsWithLine(shootline))
\r
2140 neighbourpos = np + dirs[i];
\r
2141 mindistance = distance;
\r
2142 nodefacebox = facebox;
\r
2144 } // if distance < mindistance
\r
2146 } // regular block
\r
2149 /*static v3s16 oldnodepos;
\r
2150 static bool oldnodefound = false;*/
\r
2154 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
2155 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2156 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
2158 static v3s16 nodepos_old(-1,-1,-1);
\r
2159 if(nodepos != nodepos_old){
\r
2160 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2161 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2164 hilightboxes.push_back(nodefacebox);
\r
2166 //if(g_input->getLeftClicked())
\r
2167 if(g_input->getLeftClicked() ||
\r
2168 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2170 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2171 client.pressGround(0, nodepos, neighbourpos, g_selected_item);
\r
2173 if(g_input->getRightClicked())
\r
2174 /*if(g_input->getRightClicked() ||
\r
2175 (g_input->getRightState() && nodepos != nodepos_old))*/
\r
2177 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2178 client.pressGround(1, nodepos, neighbourpos, g_selected_item);
\r
2181 nodepos_old = nodepos;
\r
2184 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
2185 //positiontextgui->setText(L"");
\r
2188 /*oldnodefound = nodefound;
\r
2189 oldnodepos = nodepos;*/
\r
2191 } // selected_object == NULL
\r
2193 g_input->resetLeftClicked();
\r
2194 g_input->resetRightClicked();
\r
2196 if(g_input->getLeftReleased())
\r
2198 std::cout<<DTIME<<"Left released"<<std::endl;
\r
2199 client.stopDigging();
\r
2201 if(g_input->getRightReleased())
\r
2203 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2207 g_input->resetLeftReleased();
\r
2208 g_input->resetRightReleased();
\r
2211 Calculate stuff for drawing
\r
2214 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2216 // Background color is choosen based on whether the player is
\r
2217 // much beyond the initial ground level
\r
2218 /*video::SColor bgcolor;
\r
2219 v3s16 p0 = Map::floatToInt(player_position);
\r
2220 // Does this make short random delays?
\r
2221 // NOTE: no need for this, sky doesn't show underground with
\r
2223 bool is_underground = client.isNodeUnderground(p0);
\r
2224 //bool is_underground = false;
\r
2225 if(is_underground == false)
\r
2226 bgcolor = video::SColor(255,90,140,200);
\r
2228 bgcolor = video::SColor(255,0,0,0);*/
\r
2230 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2231 //video::SColor bgcolor = skycolor;
\r
2233 //s32 daynight_i = client.getDayNightIndex();
\r
2234 //video::SColor bgcolor = skycolor[daynight_i];
\r
2236 u32 daynight_ratio = client.getDayNightRatio();
\r
2237 video::SColor bgcolor = video::SColor(
\r
2239 skycolor.getRed() * daynight_ratio / 1000,
\r
2240 skycolor.getGreen() * daynight_ratio / 1000,
\r
2241 skycolor.getBlue() * daynight_ratio / 1000);
\r
2247 if(g_settings.getBool("enable_fog") == true)
\r
2249 f32 range = g_viewing_range_nodes * BS;
\r
2250 if(g_viewing_range_all)
\r
2251 range = 100000*BS;
\r
2255 video::EFT_FOG_LINEAR,
\r
2259 false, // pixel fog
\r
2260 false // range fog
\r
2266 Update gui stuff (0ms)
\r
2269 //TimeTaker guiupdatetimer("Gui updating", device);
\r
2272 wchar_t temptext[150];
\r
2274 static float drawtime_avg = 0;
\r
2275 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2276 static float beginscenetime_avg = 0;
\r
2277 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2278 static float scenetime_avg = 0;
\r
2279 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2280 static float endscenetime_avg = 0;
\r
2281 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2283 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2285 L", R: range_all=%i"
\r
2287 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2289 g_viewing_range_all,
\r
2291 beginscenetime_avg,
\r
2296 guitext->setText(temptext);
\r
2300 wchar_t temptext[150];
\r
2301 swprintf(temptext, 150,
\r
2302 L"(% .1f, % .1f, % .1f)"
\r
2303 L" (% .3f < btime_jitter < % .3f"
\r
2304 L", dtime_jitter = % .1f %%)",
\r
2305 player_position.X/BS,
\r
2306 player_position.Y/BS,
\r
2307 player_position.Z/BS,
\r
2308 busytime_jitter1_min_sample,
\r
2309 busytime_jitter1_max_sample,
\r
2310 dtime_jitter1_max_fraction * 100.0
\r
2313 guitext2->setText(temptext);
\r
2317 /*wchar_t temptext[100];
\r
2318 swprintf(temptext, 100,
\r
2319 SWPRINTF_CHARSTRING,
\r
2320 infotext.substr(0,99).c_str()
\r
2323 guitext_info->setText(temptext);*/
\r
2325 guitext_info->setText(infotext.c_str());
\r
2332 static u16 old_selected_item = 65535;
\r
2333 if(client.getLocalInventoryUpdated()
\r
2334 || g_selected_item != old_selected_item)
\r
2336 old_selected_item = g_selected_item;
\r
2337 //std::cout<<"Updating local inventory"<<std::endl;
\r
2338 client.getLocalInventory(local_inventory);
\r
2339 quick_inventory->setSelection(g_selected_item);
\r
2340 quick_inventory->update();
\r
2343 if(input_guitext != NULL)
\r
2345 /*wchar_t temptext[100];
\r
2346 swprintf(temptext, 100,
\r
2347 SWPRINTF_CHARSTRING,
\r
2348 g_text_buffer.substr(0,99).c_str()
\r
2350 input_guitext->setText(g_text_buffer.c_str());
\r
2356 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2358 input_guitext->remove();
\r
2359 input_guitext = NULL;
\r
2361 if(textbuf_dest != NULL)
\r
2363 std::string text = wide_to_narrow(g_text_buffer);
\r
2364 dstream<<"Sending text: "<<text<<std::endl;
\r
2365 textbuf_dest->sendText(text);
\r
2366 delete textbuf_dest;
\r
2367 textbuf_dest = NULL;
\r
2373 //guiupdatetimer.stop();
\r
2379 TimeTaker drawtimer("Drawing", device);
\r
2383 TimeTaker timer("beginScene", device);
\r
2384 driver->beginScene(true, true, bgcolor);
\r
2385 //driver->beginScene(false, true, bgcolor);
\r
2386 beginscenetime = timer.stop(true);
\r
2391 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2394 TimeTaker timer("smgr", device);
\r
2396 scenetime = timer.stop(true);
\r
2400 //TimeTaker timer9("auxiliary drawings", device);
\r
2403 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2404 displaycenter + core::vector2d<s32>(10,0),
\r
2405 video::SColor(255,255,255,255));
\r
2406 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2407 displaycenter + core::vector2d<s32>(0,10),
\r
2408 video::SColor(255,255,255,255));
\r
2411 //TimeTaker //timer10("//timer10", device);
\r
2413 video::SMaterial m;
\r
2415 m.Lighting = false;
\r
2416 driver->setMaterial(m);
\r
2418 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2420 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2421 i != hilightboxes.end(); i++)
\r
2423 /*std::cout<<"hilightbox min="
\r
2424 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2426 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2428 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2434 //TimeTaker //timer11("//timer11", device);
\r
2440 guienv->drawAll();
\r
2444 TimeTaker timer("endScene", device);
\r
2445 driver->endScene();
\r
2446 endscenetime = timer.stop(true);
\r
2449 drawtime = drawtimer.stop(true);
\r
2455 static s16 lastFPS = 0;
\r
2456 //u16 fps = driver->getFPS();
\r
2457 u16 fps = (1.0/dtime_avg1);
\r
2459 if (lastFPS != fps)
\r
2461 core::stringw str = L"Minetest [";
\r
2462 str += driver->getName();
\r
2466 device->setWindowCaption(str.c_str());
\r
2472 device->yield();*/
\r
2475 delete quick_inventory;
\r
2477 } // client is deleted at this point
\r
2482 In the end, delete the Irrlicht device.
\r
2487 Update configuration file
\r
2489 /*if(configpath != "")
\r
2491 g_settings.updateConfigFile(configpath.c_str());
\r
2495 catch(con::PeerNotFoundException &e)
\r
2497 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2499 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2501 This is what has to be done in every thread to get suitable debug info
\r
2503 catch(std::exception &e)
\r
2505 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2506 <<e.what()<<std::endl;
\r
2511 debugstreams_deinit();
\r