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
94 - This is also needed for item container chests
\r
96 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
98 SUGG: A version number to blocks, which increments when the block is
\r
99 modified (node add/remove, water update, lighting update)
\r
100 - This can then be used to make sure the most recent version of
\r
101 a block has been sent to client
\r
103 SUGG: Make the amount of blocks sending to client and the total
\r
104 amount of blocks dynamically limited. Transferring blocks is the
\r
105 main network eater of this system, so it is the one that has
\r
106 to be throttled so that RTTs stay low.
\r
108 TODO: Untie client network operations from framerate
\r
109 - Needs some input queues or something
\r
110 - Not really necessary?
\r
112 TODO: Combine MapBlock's face caches to so big pieces that VBO
\r
114 - That is >500 vertices
\r
116 TODO: Better dungeons
\r
119 TODO: Startup and configuration menu
\r
121 TODO: There are some lighting-related todos and fixmes in
\r
122 ServerMap::emergeBlock
\r
124 TODO: Proper handling of spawning place (try to find something that
\r
125 is not in the middle of an ocean (some land to stand on at
\r
126 least) and save it in map config.
\r
128 TODO: Players to only be hidden when the client quits.
\r
129 TODO: - Players to be saved on disk, with inventory
\r
130 TODO: Players to be saved as text in map/players/<name>
\r
131 TODO: Player inventory to be saved on disk
\r
133 TODO: Make fetching sector's blocks more efficient when rendering
\r
134 sectors that have very large amounts of blocks (on client)
\r
136 TODO: Make the video backend selectable
\r
138 Block object server side:
\r
139 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
140 - For all blocks in the buffer, objects are stepped(). This
\r
141 means they are active.
\r
142 - TODO: A global active buffer is needed for the server
\r
143 - TODO: A timestamp to blocks
\r
144 - TODO: All blocks going in and out of the buffer are recorded.
\r
145 - TODO: For outgoing blocks, timestamp is written.
\r
146 - TODO: For incoming blocks, time difference is calculated and
\r
147 objects are stepped according to it.
\r
149 TODO: Copy the text of the last picked sign to inventory in creative
\r
152 TODO: Get rid of GotSplitPacketException
\r
154 TODO: Check what goes wrong with caching map to disk (Kray)
\r
156 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
158 TODO: TOSERVER_LEAVE
\r
160 TODO: Better handling of objects and mobs
\r
162 - There has to be some way to do it with less spaghetti code
\r
163 - Make separate classes for client and server
\r
164 - Client should not discriminate between blocks, server should
\r
165 - Make other players utilize the same framework
\r
167 TODO: Draw big amounts of torches better (that is, throw them in the
\r
168 same meshbuffer (can the meshcollector class be used?))
\r
171 ======================================================================
\r
173 TODO: Convert the text input system to use a modal menu... or something
\r
175 ======================================================================
\r
180 Setting this to 1 enables a special camera mode that forces
\r
181 the renderers to think that the camera statically points from
\r
182 the starting place to a static direction.
\r
184 This allows one to move around with the player and see what
\r
185 is actually drawn behind solid things and behind the player.
\r
187 #define FIELD_OF_VIEW_TEST 0
\r
189 #ifdef UNITTEST_DISABLE
\r
191 #pragma message ("Disabling unit tests")
\r
193 #warning "Disabling unit tests"
\r
195 // Disable unit tests
\r
196 #define ENABLE_TESTS 0
\r
198 // Enable unit tests
\r
199 #define ENABLE_TESTS 1
\r
203 #pragma comment(lib, "Irrlicht.lib")
\r
204 #pragma comment(lib, "jthread.lib")
\r
205 #pragma comment(lib, "zlibwapi.lib")
\r
206 // This would get rid of the console window
\r
207 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
210 #include <iostream>
\r
212 #include <jmutexautolock.h>
\r
213 #include <locale.h>
\r
214 #include "common_irrlicht.h"
\r
217 #include "player.h"
\r
220 #include "environment.h"
\r
221 #include "server.h"
\r
222 #include "client.h"
\r
223 #include "serialization.h"
\r
224 #include "constants.h"
\r
225 #include "strfnd.h"
\r
226 #include "porting.h"
\r
227 #include "guiPauseMenu.h"
\r
228 #include "irrlichtwrapper.h"
\r
229 #include "gettime.h"
\r
230 #include "porting.h"
\r
231 #include "guiInventoryMenu.h"
\r
233 IrrlichtWrapper *g_irrlicht;
\r
235 // All range-related stuff below is locked behind this
\r
236 JMutex g_range_mutex;
\r
238 // Blocks are viewed in this range from the player
\r
239 s16 g_viewing_range_nodes = 60;
\r
240 //s16 g_viewing_range_nodes = 0;
\r
242 // This is updated by the client's fetchBlocks routine
\r
243 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
245 // If true, the preceding value has no meaning and all blocks
\r
246 // already existing in memory are drawn
\r
247 bool g_viewing_range_all = false;
\r
249 // This is the freetime ratio imposed by the dynamic viewing
\r
250 // range changing code.
\r
251 // It is controlled by the main loop to the smallest value that
\r
252 // inhibits glitches (dtime jitter) in the main loop.
\r
253 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
257 These are loaded from the config file.
\r
260 Settings g_settings;
\r
262 extern void set_default_settings();
\r
268 //u16 g_selected_material = 0;
\r
269 u16 g_selected_item = 0;
\r
274 gui::IGUIEnvironment* guienv = NULL;
\r
275 GUIPauseMenu *pauseMenu = NULL;
\r
276 GUIInventoryMenu *inventoryMenu = NULL;
\r
278 bool noMenuActive()
\r
280 if(pauseMenu && pauseMenu->isVisible())
\r
282 if(inventoryMenu && inventoryMenu->isVisible())
\r
287 std::wstring g_text_buffer;
\r
288 bool g_text_buffer_accepted = false;
\r
290 // When true, the mouse and keyboard are grabbed
\r
291 bool g_game_focused = true;
\r
298 std::ostream *dout_con_ptr = &dummyout;
\r
299 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
300 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
301 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
302 //std::ostream *dout_con_ptr = &dstream;
\r
303 //std::ostream *derr_con_ptr = &dstream;
\r
306 std::ostream *dout_server_ptr = &dstream;
\r
307 std::ostream *derr_server_ptr = &dstream;
\r
310 std::ostream *dout_client_ptr = &dstream;
\r
311 std::ostream *derr_client_ptr = &dstream;
\r
314 gettime.h implementation
\r
320 Use irrlicht because it is more precise than porting.h's
\r
323 if(g_irrlicht == NULL)
\r
325 return g_irrlicht->getTime();
\r
328 class MyEventReceiver : public IEventReceiver
\r
331 // This is the one method that we have to implement
\r
332 virtual bool OnEvent(const SEvent& event)
\r
334 // Remember whether each key is down or up
\r
335 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
337 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
339 if(event.KeyInput.PressedDown)
\r
341 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
342 if(g_game_focused == false)
\r
344 s16 key = event.KeyInput.Key;
\r
345 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
347 g_text_buffer_accepted = true;
\r
349 else if(key == irr::KEY_BACK)
\r
351 if(g_text_buffer.size() > 0)
\r
352 g_text_buffer = g_text_buffer.substr
\r
353 (0, g_text_buffer.size()-1);
\r
357 wchar_t wc = event.KeyInput.Char;
\r
359 g_text_buffer += wc;
\r
363 if(pauseMenu != NULL)
\r
365 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
367 if(g_game_focused == true && noMenuActive())
\r
369 dstream<<DTIME<<"MyEventReceiver: "
\r
370 <<"Launching pause menu"<<std::endl;
\r
371 pauseMenu->launch();
\r
377 if(inventoryMenu != NULL)
\r
379 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
381 if(g_game_focused == true && noMenuActive())
\r
383 dstream<<DTIME<<"MyEventReceiver: "
\r
384 <<"Launching inventory"<<std::endl;
\r
385 inventoryMenu->launch();
\r
391 // Material selection
\r
392 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
394 if(g_game_focused == true)
\r
396 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
399 g_selected_item = 0;
\r
400 dstream<<DTIME<<"Selected item: "
\r
401 <<g_selected_item<<std::endl;
\r
405 // Viewing range selection
\r
406 if(event.KeyInput.Key == irr::KEY_KEY_R
\r
409 JMutexAutoLock lock(g_range_mutex);
\r
410 if(g_viewing_range_all)
\r
412 g_viewing_range_all = false;
\r
413 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
417 g_viewing_range_all = true;
\r
418 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
422 // Print debug stacks
\r
423 if(event.KeyInput.Key == irr::KEY_KEY_P
\r
426 dstream<<"-----------------------------------------"
\r
428 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
429 dstream<<"-----------------------------------------"
\r
431 debug_stacks_print();
\r
436 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
438 if(noMenuActive() == false)
\r
440 left_active = false;
\r
441 middle_active = false;
\r
442 right_active = false;
\r
446 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
447 left_active = event.MouseInput.isLeftPressed();
\r
448 middle_active = event.MouseInput.isMiddlePressed();
\r
449 right_active = event.MouseInput.isRightPressed();
\r
451 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
453 leftclicked = true;
\r
455 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
457 rightclicked = true;
\r
459 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
461 leftreleased = true;
\r
463 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
465 rightreleased = true;
\r
467 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
469 /*dstream<<"event.MouseInput.Wheel="
\r
470 <<event.MouseInput.Wheel<<std::endl;*/
\r
471 if(event.MouseInput.Wheel < 0)
\r
473 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
476 g_selected_item = 0;
\r
478 else if(event.MouseInput.Wheel > 0)
\r
480 if(g_selected_item > 0)
\r
483 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
492 // This is used to check whether a key is being held down
\r
493 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
495 return keyIsDown[keyCode];
\r
500 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
501 keyIsDown[i] = false;
\r
502 leftclicked = false;
\r
503 rightclicked = false;
\r
504 leftreleased = false;
\r
505 rightreleased = false;
\r
507 left_active = false;
\r
508 middle_active = false;
\r
509 right_active = false;
\r
515 bool rightreleased;
\r
518 bool middle_active;
\r
522 // We use this array to store the current state of each key
\r
523 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
534 virtual ~InputHandler()
\r
538 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
540 virtual v2s32 getMousePos() = 0;
\r
541 virtual void setMousePos(s32 x, s32 y) = 0;
\r
543 virtual bool getLeftState() = 0;
\r
544 virtual bool getRightState() = 0;
\r
546 virtual bool getLeftClicked() = 0;
\r
547 virtual bool getRightClicked() = 0;
\r
548 virtual void resetLeftClicked() = 0;
\r
549 virtual void resetRightClicked() = 0;
\r
551 virtual bool getLeftReleased() = 0;
\r
552 virtual bool getRightReleased() = 0;
\r
553 virtual void resetLeftReleased() = 0;
\r
554 virtual void resetRightReleased() = 0;
\r
556 virtual void step(float dtime) {};
\r
558 virtual void clear() {};
\r
561 InputHandler *g_input = NULL;
\r
566 g_game_focused = true;
\r
571 g_game_focused = false;
\r
574 class RealInputHandler : public InputHandler
\r
577 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
579 m_receiver(receiver)
\r
582 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
584 return m_receiver->IsKeyDown(keyCode);
\r
586 virtual v2s32 getMousePos()
\r
588 return m_device->getCursorControl()->getPosition();
\r
590 virtual void setMousePos(s32 x, s32 y)
\r
592 m_device->getCursorControl()->setPosition(x, y);
\r
595 virtual bool getLeftState()
\r
597 return m_receiver->left_active;
\r
599 virtual bool getRightState()
\r
601 return m_receiver->right_active;
\r
604 virtual bool getLeftClicked()
\r
606 if(g_game_focused == false)
\r
608 return m_receiver->leftclicked;
\r
610 virtual bool getRightClicked()
\r
612 if(g_game_focused == false)
\r
614 return m_receiver->rightclicked;
\r
616 virtual void resetLeftClicked()
\r
618 m_receiver->leftclicked = false;
\r
620 virtual void resetRightClicked()
\r
622 m_receiver->rightclicked = false;
\r
625 virtual bool getLeftReleased()
\r
627 if(g_game_focused == false)
\r
629 return m_receiver->leftreleased;
\r
631 virtual bool getRightReleased()
\r
633 if(g_game_focused == false)
\r
635 return m_receiver->rightreleased;
\r
637 virtual void resetLeftReleased()
\r
639 m_receiver->leftreleased = false;
\r
641 virtual void resetRightReleased()
\r
643 m_receiver->rightreleased = false;
\r
648 resetRightClicked();
\r
649 resetLeftClicked();
\r
652 IrrlichtDevice *m_device;
\r
653 MyEventReceiver *m_receiver;
\r
656 class RandomInputHandler : public InputHandler
\r
659 RandomInputHandler()
\r
661 leftclicked = false;
\r
662 rightclicked = false;
\r
663 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
664 keydown[i] = false;
\r
666 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
668 return keydown[keyCode];
\r
670 virtual v2s32 getMousePos()
\r
674 virtual void setMousePos(s32 x, s32 y)
\r
676 mousepos = v2s32(x,y);
\r
679 virtual bool getLeftState()
\r
683 virtual bool getRightState()
\r
688 virtual bool getLeftClicked()
\r
690 return leftclicked;
\r
692 virtual bool getRightClicked()
\r
694 return rightclicked;
\r
696 virtual void resetLeftClicked()
\r
698 leftclicked = false;
\r
700 virtual void resetRightClicked()
\r
702 rightclicked = false;
\r
705 virtual bool getLeftReleased()
\r
709 virtual bool getRightReleased()
\r
713 virtual void resetLeftReleased()
\r
716 virtual void resetRightReleased()
\r
720 virtual void step(float dtime)
\r
723 static float counter1 = 0;
\r
727 counter1 = 0.1*Rand(1,10);
\r
728 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
729 g_selected_material++;
\r
731 g_selected_material = 0;*/
\r
732 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
735 g_selected_item = 0;
\r
739 static float counter1 = 0;
\r
743 counter1 = 0.1*Rand(1, 40);
\r
744 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
748 static float counter1 = 0;
\r
752 counter1 = 0.1*Rand(1, 40);
\r
753 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
757 static float counter1 = 0;
\r
761 counter1 = 0.1*Rand(1, 40);
\r
762 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
766 static float counter1 = 0;
\r
770 counter1 = 0.1*Rand(1, 40);
\r
771 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
775 static float counter1 = 0;
\r
779 counter1 = 0.1*Rand(1, 20);
\r
780 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
784 static float counter1 = 0;
\r
788 counter1 = 0.1*Rand(1, 30);
\r
789 leftclicked = true;
\r
793 static float counter1 = 0;
\r
797 counter1 = 0.1*Rand(1, 20);
\r
798 rightclicked = true;
\r
801 mousepos += mousespeed;
\r
804 s32 Rand(s32 min, s32 max)
\r
806 return (rand()%(max-min+1))+min;
\r
809 bool keydown[KEY_KEY_CODES_COUNT];
\r
816 void updateViewingRange(f32 frametime, Client *client)
\r
818 // Range_all messes up frametime_avg
\r
819 if(g_viewing_range_all == true)
\r
822 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
824 // Initialize to the target value
\r
825 static float frametime_avg = 1.0/wanted_fps;
\r
826 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
827 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
829 static f32 counter = 0;
\r
831 counter -= frametime;
\r
834 //counter = 1.0; //seconds
\r
835 counter = 0.5; //seconds
\r
837 //float freetime_ratio = 0.2;
\r
838 //float freetime_ratio = 0.4;
\r
839 float freetime_ratio = FREETIME_RATIO;
\r
841 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
843 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
845 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
846 + frametime_avg / frametime_wanted / 2.0;*/
\r
848 //float fraction = frametime_avg / frametime_wanted;
\r
850 static bool fraction_is_good = false;
\r
852 //float fraction_good_threshold = 0.1;
\r
853 //float fraction_bad_threshold = 0.25;
\r
854 float fraction_good_threshold = 0.075;
\r
855 float fraction_bad_threshold = 0.125;
\r
856 float fraction_limit;
\r
857 // Use high limit if fraction is good AND the fraction would
\r
858 // lower the range. We want to keep the range fairly high.
\r
859 if(fraction_is_good && fraction > 1.0)
\r
860 fraction_limit = fraction_bad_threshold;
\r
862 fraction_limit = fraction_good_threshold;
\r
864 if(fabs(fraction - 1.0) < fraction_limit)
\r
866 fraction_is_good = true;
\r
871 fraction_is_good = false;
\r
874 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
875 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
876 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
877 <<" faction = "<<fraction<<std::endl;*/
\r
879 JMutexAutoLock lock(g_range_mutex);
\r
881 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
882 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
884 s16 n = (float)g_viewing_range_nodes / fraction;
\r
885 if(n < viewing_range_nodes_min)
\r
886 n = viewing_range_nodes_min;
\r
887 if(n > viewing_range_nodes_max)
\r
888 n = viewing_range_nodes_max;
\r
890 bool can_change = true;
\r
892 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
893 can_change = false;
\r
896 g_viewing_range_nodes = n;
\r
898 /*dstream<<"g_viewing_range_nodes = "
\r
899 <<g_viewing_range_nodes<<std::endl;*/
\r
902 class GUIQuickInventory : public IEventReceiver
\r
906 gui::IGUIEnvironment* env,
\r
907 gui::IGUIElement* parent,
\r
910 Inventory *inventory):
\r
911 m_itemcount(itemcount),
\r
912 m_inventory(inventory)
\r
914 core::rect<s32> imgsize(0,0,48,48);
\r
915 core::rect<s32> textsize(0,0,48,16);
\r
916 v2s32 spacing(0, 64);
\r
917 for(s32 i=0; i<m_itemcount; i++)
\r
919 m_images.push_back(env->addImage(
\r
920 imgsize + pos + spacing*i
\r
922 m_images[i]->setScaleImage(true);
\r
923 m_texts.push_back(env->addStaticText(
\r
925 textsize + pos + spacing*i,
\r
928 m_texts[i]->setBackgroundColor(
\r
929 video::SColor(128,0,0,0));
\r
930 m_texts[i]->setTextAlignment(
\r
932 gui::EGUIA_UPPERLEFT);
\r
936 virtual bool OnEvent(const SEvent& event)
\r
941 void setSelection(s32 i)
\r
950 start = m_selection - m_itemcount / 2;
\r
952 InventoryList *mainlist = m_inventory->getList("main");
\r
954 for(s32 i=0; i<m_itemcount; i++)
\r
958 if(j > (s32)mainlist->getSize() - 1)
\r
959 j -= mainlist->getSize();
\r
961 j += mainlist->getSize();
\r
963 InventoryItem *item = mainlist->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
1100 // Path of configuration file in use
\r
1101 std::string configpath = "";
\r
1103 if(cmd_args.exists("config"))
\r
1105 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1108 dstream<<"Could not read configuration from \""
\r
1109 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1112 configpath = cmd_args.get("config");
\r
1116 const char *filenames[2] =
\r
1118 "../minetest.conf",
\r
1119 "../../minetest.conf"
\r
1122 for(u32 i=0; i<2; i++)
\r
1124 bool r = g_settings.readConfigFile(filenames[i]);
\r
1127 configpath = filenames[i];
\r
1133 // Initialize random seed
\r
1139 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1140 || cmd_args.getFlag("enable-unittests") == true)
\r
1146 Global range mutex
\r
1148 g_range_mutex.Init();
\r
1149 assert(g_range_mutex.IsInitialized());
\r
1151 // Read map parameters from settings
\r
1153 HMParams hm_params;
\r
1154 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1155 hm_params.randmax = g_settings.get("height_randmax");
\r
1156 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1157 hm_params.base = g_settings.get("height_base");
\r
1159 MapParams map_params;
\r
1160 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1161 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1167 std::cout<<std::endl<<std::endl;
\r
1170 <<" .__ __ __ "<<std::endl
\r
1171 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1172 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1173 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1174 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1175 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1178 std::cout<<std::endl;
\r
1179 //char templine[100];
\r
1183 if(cmd_args.exists("port"))
\r
1185 port = cmd_args.getU16("port");
\r
1189 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1190 std::cout<<"-> "<<port<<std::endl;
\r
1193 if(cmd_args.getFlag("server"))
\r
1195 DSTACK("Dedicated server branch");
\r
1197 std::cout<<std::endl;
\r
1198 std::cout<<"========================"<<std::endl;
\r
1199 std::cout<<"Running dedicated server"<<std::endl;
\r
1200 std::cout<<"========================"<<std::endl;
\r
1201 std::cout<<std::endl;
\r
1203 Server server("../map", hm_params, map_params);
\r
1204 server.start(port);
\r
1208 // This is kind of a hack but can be done like this
\r
1209 // because server.step() is very light
\r
1211 server.step(0.030);
\r
1213 static int counter = 0;
\r
1219 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1220 core::list<PlayerInfo>::Iterator i;
\r
1221 static u32 sum_old = 0;
\r
1222 u32 sum = PIChecksum(list);
\r
1223 if(sum != sum_old)
\r
1225 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1226 for(i=list.begin(); i!=list.end(); i++)
\r
1228 i->PrintLine(&std::cout);
\r
1238 bool hosting = false;
\r
1239 char connect_name[100] = "";
\r
1241 if(cmd_args.exists("address"))
\r
1243 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1245 else if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1247 std::cout<<g_settings.get("address")<<std::endl;
\r
1248 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1252 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1253 std::cin.getline(connect_name, 100);
\r
1256 if(connect_name[0] == 0){
\r
1257 snprintf(connect_name, 100, "127.0.0.1");
\r
1262 std::cout<<"> Hosting game"<<std::endl;
\r
1264 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1266 char playername[PLAYERNAME_SIZE] = "";
\r
1267 if(g_settings.get("name") != "")
\r
1269 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1273 std::cout<<"Name of player: ";
\r
1274 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1276 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1279 Resolution selection
\r
1282 bool fullscreen = false;
\r
1283 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1284 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1288 MyEventReceiver receiver;
\r
1290 video::E_DRIVER_TYPE driverType;
\r
1293 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1294 driverType = video::EDT_OPENGL;
\r
1296 driverType = video::EDT_OPENGL;
\r
1299 // create device and exit if creation failed
\r
1301 IrrlichtDevice *device;
\r
1302 device = createDevice(driverType,
\r
1303 core::dimension2d<u32>(screenW, screenH),
\r
1304 16, fullscreen, false, false, &receiver);
\r
1307 return 1; // could not create selected driver.
\r
1309 g_irrlicht = new IrrlichtWrapper(device);
\r
1311 //g_device = device;
\r
1313 device->setResizable(true);
\r
1315 bool random_input = g_settings.getBool("random_input")
\r
1316 || cmd_args.getFlag("random-input");
\r
1318 g_input = new RandomInputHandler();
\r
1320 g_input = new RealInputHandler(device, &receiver);
\r
1323 Continue initialization
\r
1326 video::IVideoDriver* driver = device->getVideoDriver();
\r
1329 This changes the minimum allowed number of vertices in a VBO
\r
1331 //driver->setMinHardwareBufferVertexCount(1);
\r
1333 scene::ISceneManager* smgr = device->getSceneManager();
\r
1335 guienv = device->getGUIEnvironment();
\r
1336 gui::IGUISkin* skin = guienv->getSkin();
\r
1337 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1339 skin->setFont(font);
\r
1340 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1341 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1342 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1343 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1344 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1345 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1347 const wchar_t *text = L"Loading and connecting...";
\r
1348 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1349 core::dimension2d<u32> textd = font->getDimension(text);
\r
1350 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1351 // Have to add a bit to disable the text from word wrapping
\r
1352 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1353 core::vector2d<s32> textsize(300, textd.Height);
\r
1354 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1356 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1357 text, textrect, false, false);
\r
1358 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1360 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1361 guienv->drawAll();
\r
1362 driver->endScene();
\r
1365 Preload some random textures that are used in threads
\r
1368 g_texturecache.set("torch", driver->getTexture("../data/torch.png"));
\r
1369 g_texturecache.set("torch_on_floor", driver->getTexture("../data/torch_on_floor.png"));
\r
1370 g_texturecache.set("torch_on_ceiling", driver->getTexture("../data/torch_on_ceiling.png"));
\r
1371 g_texturecache.set("crack", driver->getTexture("../data/crack.png"));
\r
1374 Load tile textures
\r
1376 for(s32 i=0; i<TILES_COUNT; i++)
\r
1378 if(g_tile_texture_names[i] == NULL)
\r
1380 std::string name = g_tile_texture_names[i];
\r
1381 std::string filename;
\r
1382 filename += "../data/";
\r
1384 filename += ".png";
\r
1385 g_texturecache.set(name, driver->getTexture(filename.c_str()));
\r
1390 //tile_materials_preload(g_texturecache);
\r
1391 tile_materials_preload(g_irrlicht);
\r
1392 //tile_materials_init();
\r
1395 Make a scope here for the client so that it gets removed
\r
1396 before the irrlicht device
\r
1400 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1405 SharedPtr<Server> server;
\r
1407 server = new Server("../map", hm_params, map_params);
\r
1408 server->start(port);
\r
1415 Client client(device, playername,
\r
1417 g_viewing_range_nodes,
\r
1418 g_viewing_range_all);
\r
1420 Address connect_address(0,0,0,0, port);
\r
1422 connect_address.Resolve(connect_name);
\r
1424 catch(ResolveError &e)
\r
1426 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1430 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1431 client.connect(connect_address);
\r
1434 while(client.connectedAndInitialized() == false)
\r
1437 if(server != NULL){
\r
1438 server->step(0.1);
\r
1443 catch(con::PeerNotFoundException &e)
\r
1445 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1452 /*scene::ISceneNode* skybox;
\r
1453 skybox = smgr->addSkyBoxSceneNode(
\r
1454 driver->getTexture("../data/skybox2.png"),
\r
1455 driver->getTexture("../data/skybox3.png"),
\r
1456 driver->getTexture("../data/skybox1.png"),
\r
1457 driver->getTexture("../data/skybox1.png"),
\r
1458 driver->getTexture("../data/skybox1.png"),
\r
1459 driver->getTexture("../data/skybox1.png"));*/
\r
1462 Create the camera node
\r
1465 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1466 0, // Camera parent
\r
1467 v3f(BS*100, BS*2, BS*100), // Look from
\r
1468 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1472 if(camera == NULL)
\r
1475 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1477 camera->setFOV(FOV_ANGLE);
\r
1479 // Just so big a value that everything rendered is visible
\r
1480 camera->setFarValue(100000*BS);
\r
1482 f32 camera_yaw = 0; // "right/left"
\r
1483 f32 camera_pitch = 0; // "up/down"
\r
1489 gui_loadingtext->remove();
\r
1492 Add some gui stuff
\r
1495 // This is a copy of the inventory that the client's environment has
\r
1496 Inventory local_inventory;
\r
1498 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1499 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1502 We need some kind of a root node to be able to add
\r
1503 custom elements directly on the screen.
\r
1504 Otherwise they won't be automatically drawn.
\r
1506 gui::IGUIStaticText *root = guienv->addStaticText(L"",
\r
1507 core::rect<s32>(0, 0, 10000, 10000));
\r
1510 pauseMenu = new GUIPauseMenu(guienv, root, -1, device);
\r
1513 inventoryMenu = new GUIInventoryMenu(guienv, root, -1, &local_inventory);
\r
1515 pauseMenu->launch();
\r
1516 //inventoryMenu->launch();
\r
1518 // First line of debug text
\r
1519 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1521 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1523 // Second line of debug text
\r
1524 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1526 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1529 // At the middle of the screen
\r
1530 // Object infos are shown in this
\r
1531 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1533 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1537 Some statistics are collected in these
\r
1540 u32 beginscenetime = 0;
\r
1541 u32 scenetime = 0;
\r
1542 u32 endscenetime = 0;
\r
1550 virtual void sendText(std::string text) = 0;
\r
1553 struct TextDestSign : public TextDest
\r
1555 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1557 m_blockpos = blockpos;
\r
1559 m_client = client;
\r
1561 void sendText(std::string text)
\r
1563 dstream<<"Changing text of a sign object: "
\r
1564 <<text<<std::endl;
\r
1565 m_client->sendSignText(m_blockpos, m_id, text);
\r
1573 TextDest *textbuf_dest = NULL;
\r
1575 //gui::IGUIWindow* input_window = NULL;
\r
1576 gui::IGUIStaticText* input_guitext = NULL;
\r
1582 bool first_loop_after_window_activation = true;
\r
1584 // Time is in milliseconds
\r
1585 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1586 // NOTE: Have to call run() between calls of this to update the timer
\r
1587 u32 lasttime = device->getTimer()->getTime();
\r
1589 while(device->run())
\r
1592 Run global IrrlichtWrapper's main thread processing stuff
\r
1594 g_irrlicht->Run();
\r
1597 Random calculations
\r
1599 v2u32 screensize = driver->getScreenSize();
\r
1600 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1602 pauseMenu->resizeGui();
\r
1603 inventoryMenu->resizeGui();
\r
1605 // Hilight boxes collected during the loop and displayed
\r
1606 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1609 std::wstring infotext;
\r
1611 //TimeTaker //timer1("//timer1");
\r
1613 // Time of frame without fps limit
\r
1617 // not using getRealTime is necessary for wine
\r
1618 u32 time = device->getTimer()->getTime();
\r
1619 if(time > lasttime)
\r
1620 busytime_u32 = time - lasttime;
\r
1623 busytime = busytime_u32 / 1000.0;
\r
1626 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1628 // Absolutelu necessary for wine!
\r
1635 //updateViewingRange(dtime, &client);
\r
1636 updateViewingRange(busytime, &client);
\r
1643 float fps_max = g_settings.getFloat("fps_max");
\r
1644 u32 frametime_min = 1000./fps_max;
\r
1646 if(busytime_u32 < frametime_min)
\r
1648 u32 sleeptime = frametime_min - busytime_u32;
\r
1649 device->sleep(sleeptime);
\r
1653 // Absolutelu necessary for wine!
\r
1657 Time difference calculation
\r
1659 f32 dtime; // in seconds
\r
1661 u32 time = device->getTimer()->getTime();
\r
1662 if(time > lasttime)
\r
1663 dtime = (time - lasttime) / 1000.0;
\r
1669 Time average and jitter calculation
\r
1672 static f32 dtime_avg1 = 0.0;
\r
1673 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1674 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1676 static f32 dtime_jitter1_max_sample = 0.0;
\r
1677 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1679 static f32 jitter1_max = 0.0;
\r
1680 static f32 counter = 0.0;
\r
1681 if(dtime_jitter1 > jitter1_max)
\r
1682 jitter1_max = dtime_jitter1;
\r
1687 dtime_jitter1_max_sample = jitter1_max;
\r
1688 dtime_jitter1_max_fraction
\r
1689 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1690 jitter1_max = 0.0;
\r
1693 Control freetime ratio
\r
1695 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1697 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1698 g_freetime_ratio += 0.01;
\r
1702 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1703 g_freetime_ratio -= 0.01;
\r
1709 Busytime average and jitter calculation
\r
1712 static f32 busytime_avg1 = 0.0;
\r
1713 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1714 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1716 static f32 busytime_jitter1_max_sample = 0.0;
\r
1717 static f32 busytime_jitter1_min_sample = 0.0;
\r
1719 static f32 jitter1_max = 0.0;
\r
1720 static f32 jitter1_min = 0.0;
\r
1721 static f32 counter = 0.0;
\r
1722 if(busytime_jitter1 > jitter1_max)
\r
1723 jitter1_max = busytime_jitter1;
\r
1724 if(busytime_jitter1 < jitter1_min)
\r
1725 jitter1_min = busytime_jitter1;
\r
1727 if(counter > 0.0){
\r
1729 busytime_jitter1_max_sample = jitter1_max;
\r
1730 busytime_jitter1_min_sample = jitter1_min;
\r
1731 jitter1_max = 0.0;
\r
1732 jitter1_min = 0.0;
\r
1737 Debug info for client
\r
1740 static float counter = 0.0;
\r
1745 client.printDebugInfo(std::cout);
\r
1750 Input handler step()
\r
1752 g_input->step(dtime);
\r
1757 /*if(g_esc_pressed)
\r
1763 inventoryMenu->setVisible(true);
\r
1764 g_i_pressed = false;
\r
1768 Player speed control
\r
1771 if(g_game_focused)
\r
1778 bool a_superspeed,
\r
1781 PlayerControl control(
\r
1782 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1783 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1784 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1785 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1786 g_input->isKeyDown(irr::KEY_SPACE),
\r
1787 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1791 client.setPlayerControl(control);
\r
1795 // Set every key to inactive
\r
1796 PlayerControl control;
\r
1797 client.setPlayerControl(control);
\r
1802 Process environment
\r
1806 //TimeTaker timer("client.step(dtime)");
\r
1807 client.step(dtime);
\r
1808 //client.step(dtime_avg1);
\r
1811 if(server != NULL)
\r
1813 //TimeTaker timer("server->step(dtime)");
\r
1814 server->step(dtime);
\r
1817 v3f player_position = client.getPlayerPosition();
\r
1819 //TimeTaker //timer2("//timer2");
\r
1822 Mouse and camera control
\r
1825 if((device->isWindowActive()
\r
1832 device->getCursorControl()->setVisible(false);
\r
1834 if(first_loop_after_window_activation){
\r
1835 //std::cout<<"window active, first loop"<<std::endl;
\r
1836 first_loop_after_window_activation = false;
\r
1839 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1840 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1841 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1842 camera_yaw -= dx*0.2;
\r
1843 camera_pitch += dy*0.2;
\r
1844 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1845 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1847 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1850 device->getCursorControl()->setVisible(true);
\r
1852 //std::cout<<"window inactive"<<std::endl;
\r
1853 first_loop_after_window_activation = true;
\r
1856 camera_yaw = wrapDegrees(camera_yaw);
\r
1857 camera_pitch = wrapDegrees(camera_pitch);
\r
1859 v3f camera_direction = v3f(0,0,1);
\r
1860 camera_direction.rotateYZBy(camera_pitch);
\r
1861 camera_direction.rotateXZBy(camera_yaw);
\r
1863 v3f camera_position =
\r
1864 player_position + v3f(0, BS+BS/2, 0);
\r
1866 camera->setPosition(camera_position);
\r
1867 // *100.0 helps in large map coordinates
\r
1868 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1870 if(FIELD_OF_VIEW_TEST){
\r
1871 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1872 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1875 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1876 //TimeTaker timer("client.updateCamera");
\r
1877 client.updateCamera(camera_position, camera_direction);
\r
1881 //TimeTaker //timer3("//timer3");
\r
1884 Calculate what block is the crosshair pointing to
\r
1887 //u32 t1 = device->getTimer()->getRealTime();
\r
1889 //f32 d = 4; // max. distance
\r
1890 f32 d = 4; // max. distance
\r
1891 core::line3d<f32> shootline(camera_position,
\r
1892 camera_position + camera_direction * BS * (d+1));
\r
1894 MapBlockObject *selected_object = client.getSelectedObject
\r
1895 (d*BS, camera_position, shootline);
\r
1897 if(selected_object != NULL)
\r
1899 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1901 core::aabbox3d<f32> box_on_map
\r
1902 = selected_object->getSelectionBoxOnMap();
\r
1904 hilightboxes.push_back(box_on_map);
\r
1906 infotext = narrow_to_wide(selected_object->infoText());
\r
1908 if(g_input->getLeftClicked())
\r
1910 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1911 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1912 selected_object->getId(), g_selected_item);
\r
1914 else if(g_input->getRightClicked())
\r
1916 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1918 Check if we want to modify the object ourselves
\r
1920 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1922 dstream<<"Sign object right-clicked"<<std::endl;
\r
1926 input_guitext = guienv->addStaticText(L"",
\r
1927 core::rect<s32>(150,100,350,120),
\r
1929 false, // wordwrap?
\r
1932 input_guitext->setDrawBackground(true);
\r
1936 g_text_buffer = L"ASD LOL 8)";
\r
1937 g_text_buffer_accepted = true;
\r
1941 g_text_buffer = L"";
\r
1942 g_text_buffer_accepted = false;
\r
1945 textbuf_dest = new TextDestSign(
\r
1946 selected_object->getBlock()->getPos(),
\r
1947 selected_object->getId(),
\r
1951 Otherwise pass the event to the server as-is
\r
1955 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1956 selected_object->getId(), g_selected_item);
\r
1960 else // selected_object == NULL
\r
1964 Find out which node we are pointing at
\r
1967 bool nodefound = false;
\r
1969 v3s16 neighbourpos;
\r
1970 core::aabbox3d<f32> nodefacebox;
\r
1971 f32 mindistance = BS * 1001;
\r
1973 v3s16 pos_i = floatToInt(player_position);
\r
1975 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1979 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1980 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1981 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1982 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1983 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1984 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1986 for(s16 y = ystart; y <= yend; y++)
\r
1987 for(s16 z = zstart; z <= zend; z++)
\r
1988 for(s16 x = xstart; x <= xend; x++)
\r
1993 n = client.getNode(v3s16(x,y,z));
\r
1994 if(content_pointable(n.d) == false)
\r
1997 catch(InvalidPositionException &e)
\r
2003 v3f npf = intToFloat(np);
\r
2008 v3s16(0,0,1), // back
\r
2009 v3s16(0,1,0), // top
\r
2010 v3s16(1,0,0), // right
\r
2011 v3s16(0,0,-1), // front
\r
2012 v3s16(0,-1,0), // bottom
\r
2013 v3s16(-1,0,0), // left
\r
2019 if(n.d == CONTENT_TORCH)
\r
2021 v3s16 dir = unpackDir(n.dir);
\r
2022 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2023 dir_f *= BS/2 - BS/6 - BS/20;
\r
2024 v3f cpf = npf + dir_f;
\r
2025 f32 distance = (cpf - camera_position).getLength();
\r
2027 core::aabbox3d<f32> box;
\r
2030 if(dir == v3s16(0,-1,0))
\r
2032 box = core::aabbox3d<f32>(
\r
2033 npf - v3f(BS/6, BS/2, BS/6),
\r
2034 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2038 else if(dir == v3s16(0,1,0))
\r
2040 box = core::aabbox3d<f32>(
\r
2041 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2042 npf + v3f(BS/6, BS/2, BS/6)
\r
2048 box = core::aabbox3d<f32>(
\r
2049 cpf - v3f(BS/6, BS/3, BS/6),
\r
2050 cpf + v3f(BS/6, BS/3, BS/6)
\r
2054 if(distance < mindistance)
\r
2056 if(box.intersectsWithLine(shootline))
\r
2060 neighbourpos = np;
\r
2061 mindistance = distance;
\r
2062 nodefacebox = box;
\r
2071 for(u16 i=0; i<6; i++)
\r
2073 v3f dir_f = v3f(dirs[i].X,
\r
2074 dirs[i].Y, dirs[i].Z);
\r
2075 v3f centerpoint = npf + dir_f * BS/2;
\r
2077 (centerpoint - camera_position).getLength();
\r
2079 if(distance < mindistance)
\r
2081 core::CMatrix4<f32> m;
\r
2082 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2084 // This is the back face
\r
2085 v3f corners[2] = {
\r
2086 v3f(BS/2, BS/2, BS/2),
\r
2087 v3f(-BS/2, -BS/2, BS/2+d)
\r
2090 for(u16 j=0; j<2; j++)
\r
2092 m.rotateVect(corners[j]);
\r
2093 corners[j] += npf;
\r
2096 core::aabbox3d<f32> facebox(corners[0]);
\r
2097 facebox.addInternalPoint(corners[1]);
\r
2099 if(facebox.intersectsWithLine(shootline))
\r
2103 neighbourpos = np + dirs[i];
\r
2104 mindistance = distance;
\r
2105 nodefacebox = facebox;
\r
2107 } // if distance < mindistance
\r
2109 } // regular block
\r
2114 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2116 static float dig_time = 0.0;
\r
2117 static u16 dig_index = 0;
\r
2119 if(nodepos != nodepos_old)
\r
2121 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2122 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2124 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2126 client.clearTempMod(nodepos_old);
\r
2131 hilightboxes.push_back(nodefacebox);
\r
2133 if(g_input->getLeftReleased())
\r
2135 client.clearTempMod(nodepos);
\r
2138 if(g_input->getLeftClicked() ||
\r
2139 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2141 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
2142 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2144 if(g_input->getLeftClicked())
\r
2146 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2148 if(g_input->getLeftState())
\r
2150 float dig_time_complete = 0.5;
\r
2151 MapNode n = client.getNode(nodepos);
\r
2152 if(n.d == CONTENT_STONE)
\r
2153 dig_time_complete = 1.5;
\r
2155 float dig_time_complete0 = dig_time_complete+client.getAvgRtt()*2;
\r
2156 if(dig_time_complete0 < 0.0)
\r
2157 dig_time_complete0 = 0.0;
\r
2159 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2160 * dig_time/dig_time_complete0);
\r
2162 if(dig_time > 0.125 && dig_index < CRACK_ANIMATION_LENGTH)
\r
2164 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2165 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2168 dig_time += dtime;
\r
2171 if(g_input->getRightClicked())
\r
2173 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2174 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2177 nodepos_old = nodepos;
\r
2182 } // selected_object == NULL
\r
2184 g_input->resetLeftClicked();
\r
2185 g_input->resetRightClicked();
\r
2187 if(g_input->getLeftReleased())
\r
2189 std::cout<<DTIME<<"Left released"<<std::endl;
\r
2190 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2192 if(g_input->getRightReleased())
\r
2194 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2198 g_input->resetLeftReleased();
\r
2199 g_input->resetRightReleased();
\r
2202 Calculate stuff for drawing
\r
2205 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2207 u32 daynight_ratio = client.getDayNightRatio();
\r
2208 video::SColor bgcolor = video::SColor(
\r
2210 skycolor.getRed() * daynight_ratio / 1000,
\r
2211 skycolor.getGreen() * daynight_ratio / 1000,
\r
2212 skycolor.getBlue() * daynight_ratio / 1000);
\r
2218 if(g_settings.getBool("enable_fog") == true)
\r
2220 f32 range = g_viewing_range_nodes * BS;
\r
2221 if(g_viewing_range_all)
\r
2222 range = 100000*BS;
\r
2226 video::EFT_FOG_LINEAR,
\r
2230 false, // pixel fog
\r
2231 false // range fog
\r
2237 Update gui stuff (0ms)
\r
2240 //TimeTaker guiupdatetimer("Gui updating");
\r
2243 wchar_t temptext[150];
\r
2245 static float drawtime_avg = 0;
\r
2246 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2247 static float beginscenetime_avg = 0;
\r
2248 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2249 static float scenetime_avg = 0;
\r
2250 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2251 static float endscenetime_avg = 0;
\r
2252 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2254 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2256 L", R: range_all=%i"
\r
2258 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2260 g_viewing_range_all,
\r
2262 beginscenetime_avg,
\r
2267 guitext->setText(temptext);
\r
2271 wchar_t temptext[150];
\r
2272 swprintf(temptext, 150,
\r
2273 L"(% .1f, % .1f, % .1f)"
\r
2274 L" (% .3f < btime_jitter < % .3f"
\r
2275 L", dtime_jitter = % .1f %%)",
\r
2276 player_position.X/BS,
\r
2277 player_position.Y/BS,
\r
2278 player_position.Z/BS,
\r
2279 busytime_jitter1_min_sample,
\r
2280 busytime_jitter1_max_sample,
\r
2281 dtime_jitter1_max_fraction * 100.0
\r
2284 guitext2->setText(temptext);
\r
2288 /*wchar_t temptext[100];
\r
2289 swprintf(temptext, 100,
\r
2290 SWPRINTF_CHARSTRING,
\r
2291 infotext.substr(0,99).c_str()
\r
2294 guitext_info->setText(temptext);*/
\r
2296 guitext_info->setText(infotext.c_str());
\r
2303 static u16 old_selected_item = 65535;
\r
2304 if(client.getLocalInventoryUpdated()
\r
2305 || g_selected_item != old_selected_item)
\r
2307 old_selected_item = g_selected_item;
\r
2308 //std::cout<<"Updating local inventory"<<std::endl;
\r
2309 client.getLocalInventory(local_inventory);
\r
2310 quick_inventory->setSelection(g_selected_item);
\r
2311 quick_inventory->update();
\r
2315 Send actions returned by the inventory menu
\r
2317 while(InventoryAction *a = inventoryMenu->getNextAction())
\r
2319 client.sendInventoryAction(a);
\r
2324 if(input_guitext != NULL)
\r
2326 /*wchar_t temptext[100];
\r
2327 swprintf(temptext, 100,
\r
2328 SWPRINTF_CHARSTRING,
\r
2329 g_text_buffer.substr(0,99).c_str()
\r
2331 input_guitext->setText(g_text_buffer.c_str());
\r
2337 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2339 input_guitext->remove();
\r
2340 input_guitext = NULL;
\r
2342 if(textbuf_dest != NULL)
\r
2344 std::string text = wide_to_narrow(g_text_buffer);
\r
2345 dstream<<"Sending text: "<<text<<std::endl;
\r
2346 textbuf_dest->sendText(text);
\r
2347 delete textbuf_dest;
\r
2348 textbuf_dest = NULL;
\r
2354 //guiupdatetimer.stop();
\r
2360 TimeTaker drawtimer("Drawing");
\r
2364 TimeTaker timer("beginScene");
\r
2365 driver->beginScene(true, true, bgcolor);
\r
2366 //driver->beginScene(false, true, bgcolor);
\r
2367 beginscenetime = timer.stop(true);
\r
2372 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2375 TimeTaker timer("smgr");
\r
2377 scenetime = timer.stop(true);
\r
2381 //TimeTaker timer9("auxiliary drawings");
\r
2384 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2385 displaycenter + core::vector2d<s32>(10,0),
\r
2386 video::SColor(255,255,255,255));
\r
2387 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2388 displaycenter + core::vector2d<s32>(0,10),
\r
2389 video::SColor(255,255,255,255));
\r
2392 //TimeTaker //timer10("//timer10");
\r
2394 video::SMaterial m;
\r
2396 m.Lighting = false;
\r
2397 driver->setMaterial(m);
\r
2399 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2401 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2402 i != hilightboxes.end(); i++)
\r
2404 /*std::cout<<"hilightbox min="
\r
2405 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2407 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2409 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2415 //TimeTaker //timer11("//timer11");
\r
2421 guienv->drawAll();
\r
2425 TimeTaker timer("endScene");
\r
2426 driver->endScene();
\r
2427 endscenetime = timer.stop(true);
\r
2430 drawtime = drawtimer.stop(true);
\r
2436 static s16 lastFPS = 0;
\r
2437 //u16 fps = driver->getFPS();
\r
2438 u16 fps = (1.0/dtime_avg1);
\r
2440 if (lastFPS != fps)
\r
2442 core::stringw str = L"Minetest [";
\r
2443 str += driver->getName();
\r
2447 device->setWindowCaption(str.c_str());
\r
2453 device->yield();*/
\r
2456 delete quick_inventory;
\r
2458 } // client is deleted at this point
\r
2463 In the end, delete the Irrlicht device.
\r
2468 Update configuration file
\r
2470 /*if(configpath != "")
\r
2472 g_settings.updateConfigFile(configpath.c_str());
\r
2476 catch(con::PeerNotFoundException &e)
\r
2478 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2480 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2482 This is what has to be done in every thread to get suitable debug info
\r
2484 catch(std::exception &e)
\r
2486 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2487 <<e.what()<<std::endl;
\r
2492 debugstreams_deinit();
\r