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
170 TODO: Check if the usage of Client::isFetchingBlocks() in
\r
171 updateViewingRange() actually does something
\r
174 ======================================================================
\r
176 TODO: Convert the text input system to use a modal menu... or something
\r
178 ======================================================================
\r
183 Setting this to 1 enables a special camera mode that forces
\r
184 the renderers to think that the camera statically points from
\r
185 the starting place to a static direction.
\r
187 This allows one to move around with the player and see what
\r
188 is actually drawn behind solid things and behind the player.
\r
190 #define FIELD_OF_VIEW_TEST 0
\r
192 #ifdef UNITTEST_DISABLE
\r
194 #pragma message ("Disabling unit tests")
\r
196 #warning "Disabling unit tests"
\r
198 // Disable unit tests
\r
199 #define ENABLE_TESTS 0
\r
201 // Enable unit tests
\r
202 #define ENABLE_TESTS 1
\r
206 #pragma comment(lib, "Irrlicht.lib")
\r
207 #pragma comment(lib, "jthread.lib")
\r
208 #pragma comment(lib, "zlibwapi.lib")
\r
209 // This would get rid of the console window
\r
210 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
213 #include <iostream>
\r
215 #include <jmutexautolock.h>
\r
216 #include <locale.h>
\r
217 #include "common_irrlicht.h"
\r
220 #include "player.h"
\r
223 #include "environment.h"
\r
224 #include "server.h"
\r
225 #include "client.h"
\r
226 #include "serialization.h"
\r
227 #include "constants.h"
\r
228 #include "strfnd.h"
\r
229 #include "porting.h"
\r
230 #include "irrlichtwrapper.h"
\r
231 #include "gettime.h"
\r
232 #include "porting.h"
\r
233 #include "guiPauseMenu.h"
\r
234 #include "guiInventoryMenu.h"
\r
235 #include "guiTextInputMenu.h"
\r
237 IrrlichtWrapper *g_irrlicht;
\r
239 // All range-related stuff below is locked behind this
\r
240 JMutex g_range_mutex;
\r
242 // Blocks are viewed in this range from the player
\r
243 s16 g_viewing_range_nodes = 60;
\r
244 //s16 g_viewing_range_nodes = 0;
\r
246 // This is updated by the client's fetchBlocks routine
\r
247 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
249 // If true, the preceding value has no meaning and all blocks
\r
250 // already existing in memory are drawn
\r
251 bool g_viewing_range_all = false;
\r
253 // This is the freetime ratio imposed by the dynamic viewing
\r
254 // range changing code.
\r
255 // It is controlled by the main loop to the smallest value that
\r
256 // inhibits glitches (dtime jitter) in the main loop.
\r
257 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
261 These are loaded from the config file.
\r
264 Settings g_settings;
\r
266 extern void set_default_settings();
\r
272 //u16 g_selected_material = 0;
\r
273 u16 g_selected_item = 0;
\r
275 IrrlichtDevice *g_device = NULL;
\r
276 Client *g_client = NULL;
\r
281 gui::IGUIEnvironment* guienv = NULL;
\r
282 gui::IGUIStaticText *guiroot = NULL;
\r
283 int g_active_menu_count = 0;
\r
285 bool noMenuActive()
\r
287 return (g_active_menu_count == 0);
\r
290 // Inventory actions from the menu are buffered here before sending
\r
291 Queue<InventoryAction*> inventory_action_queue;
\r
292 // This is a copy of the inventory that the client's environment has
\r
293 Inventory local_inventory;
\r
300 std::ostream *dout_con_ptr = &dummyout;
\r
301 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
302 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
303 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
304 //std::ostream *dout_con_ptr = &dstream;
\r
305 //std::ostream *derr_con_ptr = &dstream;
\r
308 std::ostream *dout_server_ptr = &dstream;
\r
309 std::ostream *derr_server_ptr = &dstream;
\r
312 std::ostream *dout_client_ptr = &dstream;
\r
313 std::ostream *derr_client_ptr = &dstream;
\r
316 gettime.h implementation
\r
322 Use irrlicht because it is more precise than porting.h's
\r
325 if(g_irrlicht == NULL)
\r
327 return g_irrlicht->getTime();
\r
334 struct TextDestSign : public TextDest
\r
336 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
338 m_blockpos = blockpos;
\r
342 void gotText(std::wstring text)
\r
344 std::string ntext = wide_to_narrow(text);
\r
345 dstream<<"Changing text of a sign object: "
\r
346 <<ntext<<std::endl;
\r
347 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
355 struct TextDestChat : public TextDest
\r
357 TextDestChat(Client *client)
\r
361 void gotText(std::wstring text)
\r
363 m_client->sendChatMessage(text);
\r
364 m_client->addChatMessage(text);
\r
370 class MyEventReceiver : public IEventReceiver
\r
373 // This is the one method that we have to implement
\r
374 virtual bool OnEvent(const SEvent& event)
\r
377 React to nothing here if a menu is active
\r
379 if(noMenuActive() == false)
\r
385 // Remember whether each key is down or up
\r
386 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
388 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
390 if(event.KeyInput.PressedDown)
\r
392 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
398 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
400 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
402 dstream<<DTIME<<"MyEventReceiver: "
\r
403 <<"Launching pause menu"<<std::endl;
\r
404 // It will delete itself by itself
\r
405 (new GUIPauseMenu(guienv, guiroot, -1, g_device,
\r
406 &g_active_menu_count))->drop();
\r
409 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
411 dstream<<DTIME<<"MyEventReceiver: "
\r
412 <<"Launching inventory"<<std::endl;
\r
413 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
414 &local_inventory, &inventory_action_queue,
\r
415 &g_active_menu_count))->drop();
\r
418 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
420 TextDest *dest = new TextDestChat(g_client);
\r
422 (new GUITextInputMenu(guienv, guiroot, -1,
\r
423 &g_active_menu_count, dest,
\r
428 // Material selection
\r
429 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
431 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
434 g_selected_item = 0;
\r
435 dstream<<DTIME<<"Selected item: "
\r
436 <<g_selected_item<<std::endl;
\r
439 // Viewing range selection
\r
440 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
442 JMutexAutoLock lock(g_range_mutex);
\r
443 if(g_viewing_range_all)
\r
445 g_viewing_range_all = false;
\r
446 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
450 g_viewing_range_all = true;
\r
451 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
455 // Print debug stacks
\r
456 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
458 dstream<<"-----------------------------------------"
\r
460 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
461 dstream<<"-----------------------------------------"
\r
463 debug_stacks_print();
\r
468 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
470 if(noMenuActive() == false)
\r
472 left_active = false;
\r
473 middle_active = false;
\r
474 right_active = false;
\r
478 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
479 left_active = event.MouseInput.isLeftPressed();
\r
480 middle_active = event.MouseInput.isMiddlePressed();
\r
481 right_active = event.MouseInput.isRightPressed();
\r
483 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
485 leftclicked = true;
\r
487 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
489 rightclicked = true;
\r
491 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
493 leftreleased = true;
\r
495 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
497 rightreleased = true;
\r
499 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
501 /*dstream<<"event.MouseInput.Wheel="
\r
502 <<event.MouseInput.Wheel<<std::endl;*/
\r
503 if(event.MouseInput.Wheel < 0)
\r
505 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
508 g_selected_item = 0;
\r
510 else if(event.MouseInput.Wheel > 0)
\r
512 if(g_selected_item > 0)
\r
515 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
524 // This is used to check whether a key is being held down
\r
525 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
527 return keyIsDown[keyCode];
\r
532 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
533 keyIsDown[i] = false;
\r
535 leftclicked = false;
\r
536 rightclicked = false;
\r
537 leftreleased = false;
\r
538 rightreleased = false;
\r
540 left_active = false;
\r
541 middle_active = false;
\r
542 right_active = false;
\r
553 bool rightreleased;
\r
556 bool middle_active;
\r
560 // We use this array to store the current state of each key
\r
561 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
564 IrrlichtDevice *m_device;
\r
573 virtual ~InputHandler()
\r
577 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
579 virtual v2s32 getMousePos() = 0;
\r
580 virtual void setMousePos(s32 x, s32 y) = 0;
\r
582 virtual bool getLeftState() = 0;
\r
583 virtual bool getRightState() = 0;
\r
585 virtual bool getLeftClicked() = 0;
\r
586 virtual bool getRightClicked() = 0;
\r
587 virtual void resetLeftClicked() = 0;
\r
588 virtual void resetRightClicked() = 0;
\r
590 virtual bool getLeftReleased() = 0;
\r
591 virtual bool getRightReleased() = 0;
\r
592 virtual void resetLeftReleased() = 0;
\r
593 virtual void resetRightReleased() = 0;
\r
595 virtual void step(float dtime) {};
\r
597 virtual void clear() {};
\r
600 InputHandler *g_input = NULL;
\r
602 class RealInputHandler : public InputHandler
\r
605 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
607 m_receiver(receiver)
\r
610 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
612 return m_receiver->IsKeyDown(keyCode);
\r
614 virtual v2s32 getMousePos()
\r
616 return m_device->getCursorControl()->getPosition();
\r
618 virtual void setMousePos(s32 x, s32 y)
\r
620 m_device->getCursorControl()->setPosition(x, y);
\r
623 virtual bool getLeftState()
\r
625 return m_receiver->left_active;
\r
627 virtual bool getRightState()
\r
629 return m_receiver->right_active;
\r
632 virtual bool getLeftClicked()
\r
634 return m_receiver->leftclicked;
\r
636 virtual bool getRightClicked()
\r
638 return m_receiver->rightclicked;
\r
640 virtual void resetLeftClicked()
\r
642 m_receiver->leftclicked = false;
\r
644 virtual void resetRightClicked()
\r
646 m_receiver->rightclicked = false;
\r
649 virtual bool getLeftReleased()
\r
651 return m_receiver->leftreleased;
\r
653 virtual bool getRightReleased()
\r
655 return m_receiver->rightreleased;
\r
657 virtual void resetLeftReleased()
\r
659 m_receiver->leftreleased = false;
\r
661 virtual void resetRightReleased()
\r
663 m_receiver->rightreleased = false;
\r
668 resetRightClicked();
\r
669 resetLeftClicked();
\r
672 IrrlichtDevice *m_device;
\r
673 MyEventReceiver *m_receiver;
\r
676 class RandomInputHandler : public InputHandler
\r
679 RandomInputHandler()
\r
681 leftclicked = false;
\r
682 rightclicked = false;
\r
683 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
684 keydown[i] = false;
\r
686 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
688 return keydown[keyCode];
\r
690 virtual v2s32 getMousePos()
\r
694 virtual void setMousePos(s32 x, s32 y)
\r
696 mousepos = v2s32(x,y);
\r
699 virtual bool getLeftState()
\r
703 virtual bool getRightState()
\r
708 virtual bool getLeftClicked()
\r
710 return leftclicked;
\r
712 virtual bool getRightClicked()
\r
714 return rightclicked;
\r
716 virtual void resetLeftClicked()
\r
718 leftclicked = false;
\r
720 virtual void resetRightClicked()
\r
722 rightclicked = false;
\r
725 virtual bool getLeftReleased()
\r
729 virtual bool getRightReleased()
\r
733 virtual void resetLeftReleased()
\r
736 virtual void resetRightReleased()
\r
740 virtual void step(float dtime)
\r
743 static float counter1 = 0;
\r
747 counter1 = 0.1*Rand(1,10);
\r
748 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
749 g_selected_material++;
\r
751 g_selected_material = 0;*/
\r
752 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
755 g_selected_item = 0;
\r
759 static float counter1 = 0;
\r
763 counter1 = 0.1*Rand(1, 40);
\r
764 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
768 static float counter1 = 0;
\r
772 counter1 = 0.1*Rand(1, 40);
\r
773 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
777 static float counter1 = 0;
\r
781 counter1 = 0.1*Rand(1, 40);
\r
782 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
786 static float counter1 = 0;
\r
790 counter1 = 0.1*Rand(1, 40);
\r
791 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
795 static float counter1 = 0;
\r
799 counter1 = 0.1*Rand(1, 20);
\r
800 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
804 static float counter1 = 0;
\r
808 counter1 = 0.1*Rand(1, 30);
\r
809 leftclicked = true;
\r
813 static float counter1 = 0;
\r
817 counter1 = 0.1*Rand(1, 20);
\r
818 rightclicked = true;
\r
821 mousepos += mousespeed;
\r
824 s32 Rand(s32 min, s32 max)
\r
826 return (rand()%(max-min+1))+min;
\r
829 bool keydown[KEY_KEY_CODES_COUNT];
\r
836 void updateViewingRange(f32 frametime, Client *client)
\r
838 // Range_all messes up frametime_avg
\r
839 if(g_viewing_range_all == true)
\r
842 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
844 // Initialize to the target value
\r
845 static float frametime_avg = 1.0/wanted_fps;
\r
846 //frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
847 frametime_avg = frametime_avg * 0.7 + frametime * 0.3;
\r
849 static f32 counter = 0;
\r
851 counter -= frametime;
\r
854 //counter = 1.0; //seconds
\r
855 counter = 0.5; //seconds
\r
857 //float freetime_ratio = 0.2;
\r
858 //float freetime_ratio = 0.4;
\r
859 float freetime_ratio = FREETIME_RATIO;
\r
861 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
863 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
865 /*float fraction = sqrt(frametime_avg / frametime_wanted) / 2.0
\r
866 + frametime_avg / frametime_wanted / 2.0;*/
\r
868 //float fraction = frametime_avg / frametime_wanted;
\r
870 static bool fraction_is_good = false;
\r
872 //float fraction_good_threshold = 0.1;
\r
873 //float fraction_bad_threshold = 0.25;
\r
874 float fraction_good_threshold = 0.075;
\r
875 float fraction_bad_threshold = 0.125;
\r
876 float fraction_limit;
\r
877 // Use high limit if fraction is good AND the fraction would
\r
878 // lower the range. We want to keep the range fairly high.
\r
879 if(fraction_is_good && fraction > 1.0)
\r
880 fraction_limit = fraction_bad_threshold;
\r
882 fraction_limit = fraction_good_threshold;
\r
884 if(fabs(fraction - 1.0) < fraction_limit)
\r
886 fraction_is_good = true;
\r
891 fraction_is_good = false;
\r
894 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
895 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
896 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
897 <<" faction = "<<fraction<<std::endl;*/
\r
899 JMutexAutoLock lock(g_range_mutex);
\r
901 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
902 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
904 s16 n = (float)g_viewing_range_nodes / fraction;
\r
905 if(n < viewing_range_nodes_min)
\r
906 n = viewing_range_nodes_min;
\r
907 if(n > viewing_range_nodes_max)
\r
908 n = viewing_range_nodes_max;
\r
910 bool can_change = true;
\r
912 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
913 can_change = false;
\r
916 g_viewing_range_nodes = n;
\r
918 /*dstream<<"g_viewing_range_nodes = "
\r
919 <<g_viewing_range_nodes<<std::endl;*/
\r
922 class GUIQuickInventory : public IEventReceiver
\r
926 gui::IGUIEnvironment* env,
\r
927 gui::IGUIElement* parent,
\r
930 Inventory *inventory):
\r
931 m_itemcount(itemcount),
\r
932 m_inventory(inventory)
\r
934 core::rect<s32> imgsize(0,0,48,48);
\r
935 core::rect<s32> textsize(0,0,48,16);
\r
936 v2s32 spacing(0, 64);
\r
937 for(s32 i=0; i<m_itemcount; i++)
\r
939 m_images.push_back(env->addImage(
\r
940 imgsize + pos + spacing*i
\r
942 m_images[i]->setScaleImage(true);
\r
943 m_texts.push_back(env->addStaticText(
\r
945 textsize + pos + spacing*i,
\r
948 m_texts[i]->setBackgroundColor(
\r
949 video::SColor(128,0,0,0));
\r
950 m_texts[i]->setTextAlignment(
\r
952 gui::EGUIA_UPPERLEFT);
\r
956 virtual bool OnEvent(const SEvent& event)
\r
961 void setSelection(s32 i)
\r
970 start = m_selection - m_itemcount / 2;
\r
972 InventoryList *mainlist = m_inventory->getList("main");
\r
974 for(s32 i=0; i<m_itemcount; i++)
\r
978 if(j > (s32)mainlist->getSize() - 1)
\r
979 j -= mainlist->getSize();
\r
981 j += mainlist->getSize();
\r
983 InventoryItem *item = mainlist->getItem(j);
\r
987 m_images[i]->setImage(NULL);
\r
990 if(m_selection == j)
\r
991 swprintf(t, 10, L"<-");
\r
993 swprintf(t, 10, L"");
\r
994 m_texts[i]->setText(t);
\r
996 // The next ifs will segfault with a NULL pointer
\r
1001 m_images[i]->setImage(item->getImage());
\r
1004 if(m_selection == j)
\r
1005 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1007 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1008 m_texts[i]->setText(t);
\r
1014 core::array<gui::IGUIStaticText*> m_texts;
\r
1015 core::array<gui::IGUIImage*> m_images;
\r
1016 Inventory *m_inventory;
\r
1020 int main(int argc, char *argv[])
\r
1023 Low-level initialization
\r
1026 bool disable_stderr = false;
\r
1028 disable_stderr = true;
\r
1031 // Initialize debug streams
\r
1032 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1033 // Initialize debug stacks
\r
1034 debug_stacks_init();
\r
1036 DSTACK(__FUNCTION_NAME);
\r
1042 Parse command line
\r
1045 // List all allowed options
\r
1046 core::map<std::string, ValueSpec> allowed_options;
\r
1047 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1048 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1049 "Run server directly"));
\r
1050 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1051 "Load configuration from specified file"));
\r
1052 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1053 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1054 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1055 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1056 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1058 Settings cmd_args;
\r
1060 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1062 if(ret == false || cmd_args.getFlag("help"))
\r
1064 dstream<<"Allowed options:"<<std::endl;
\r
1065 for(core::map<std::string, ValueSpec>::Iterator
\r
1066 i = allowed_options.getIterator();
\r
1067 i.atEnd() == false; i++)
\r
1069 dstream<<" --"<<i.getNode()->getKey();
\r
1070 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1075 dstream<<" <value>";
\r
1077 dstream<<std::endl;
\r
1079 if(i.getNode()->getValue().help != NULL)
\r
1081 dstream<<" "<<i.getNode()->getValue().help
\r
1086 return cmd_args.getFlag("help") ? 0 : 1;
\r
1091 Basic initialization
\r
1094 // Initialize default settings
\r
1095 set_default_settings();
\r
1097 // Print startup message
\r
1098 dstream<<DTIME<<"minetest-c55"
\r
1099 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1100 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
1103 // Set locale. This is for forcing '.' as the decimal point.
\r
1104 std::locale::global(std::locale("C"));
\r
1105 // This enables printing all characters in bitmap font
\r
1106 setlocale(LC_CTYPE, "en_US");
\r
1108 // Initialize sockets
\r
1110 atexit(sockets_cleanup);
\r
1120 // Path of configuration file in use
\r
1121 std::string configpath = "";
\r
1123 if(cmd_args.exists("config"))
\r
1125 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1128 dstream<<"Could not read configuration from \""
\r
1129 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1132 configpath = cmd_args.get("config");
\r
1136 const char *filenames[2] =
\r
1138 "../minetest.conf",
\r
1139 "../../minetest.conf"
\r
1142 for(u32 i=0; i<2; i++)
\r
1144 bool r = g_settings.readConfigFile(filenames[i]);
\r
1147 configpath = filenames[i];
\r
1153 // Initialize random seed
\r
1159 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1160 || cmd_args.getFlag("enable-unittests") == true)
\r
1166 Global range mutex
\r
1168 g_range_mutex.Init();
\r
1169 assert(g_range_mutex.IsInitialized());
\r
1171 // Read map parameters from settings
\r
1173 HMParams hm_params;
\r
1174 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1175 hm_params.randmax = g_settings.get("height_randmax");
\r
1176 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1177 hm_params.base = g_settings.get("height_base");
\r
1179 MapParams map_params;
\r
1180 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1181 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1187 std::cout<<std::endl<<std::endl;
\r
1190 <<" .__ __ __ "<<std::endl
\r
1191 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1192 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1193 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1194 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1195 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1198 std::cout<<std::endl;
\r
1199 //char templine[100];
\r
1203 if(cmd_args.exists("port"))
\r
1205 port = cmd_args.getU16("port");
\r
1209 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1210 std::cout<<"-> "<<port<<std::endl;
\r
1213 if(cmd_args.getFlag("server"))
\r
1215 DSTACK("Dedicated server branch");
\r
1217 std::cout<<std::endl;
\r
1218 std::cout<<"========================"<<std::endl;
\r
1219 std::cout<<"Running dedicated server"<<std::endl;
\r
1220 std::cout<<"========================"<<std::endl;
\r
1221 std::cout<<std::endl;
\r
1223 Server server("../map", hm_params, map_params);
\r
1224 server.start(port);
\r
1228 // This is kind of a hack but can be done like this
\r
1229 // because server.step() is very light
\r
1231 server.step(0.030);
\r
1233 static int counter = 0;
\r
1239 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1240 core::list<PlayerInfo>::Iterator i;
\r
1241 static u32 sum_old = 0;
\r
1242 u32 sum = PIChecksum(list);
\r
1243 if(sum != sum_old)
\r
1245 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1246 for(i=list.begin(); i!=list.end(); i++)
\r
1248 i->PrintLine(&std::cout);
\r
1258 bool hosting = false;
\r
1259 char connect_name[100] = "";
\r
1261 if(cmd_args.exists("address"))
\r
1263 snprintf(connect_name, 100, "%s", cmd_args.get("address").c_str());
\r
1265 else if(is_yes(g_settings.get("host_game")) == false)
\r
1267 if(g_settings.get("address") != "")
\r
1269 std::cout<<g_settings.get("address")<<std::endl;
\r
1270 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1274 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1275 std::cin.getline(connect_name, 100);
\r
1279 if(connect_name[0] == 0){
\r
1280 snprintf(connect_name, 100, "127.0.0.1");
\r
1285 std::cout<<"> Hosting game"<<std::endl;
\r
1287 std::cout<<"> Connecting to "<<connect_name<<std::endl;
\r
1289 char playername[PLAYERNAME_SIZE] = "";
\r
1290 if(g_settings.get("name") != "")
\r
1292 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1296 std::cout<<"Name of player: ";
\r
1297 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1299 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1302 Resolution selection
\r
1305 bool fullscreen = false;
\r
1306 u16 screenW = atoi(g_settings.get("screenW").c_str());
\r
1307 u16 screenH = atoi(g_settings.get("screenH").c_str());
\r
1311 MyEventReceiver receiver;
\r
1313 video::E_DRIVER_TYPE driverType;
\r
1316 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1317 driverType = video::EDT_OPENGL;
\r
1319 driverType = video::EDT_OPENGL;
\r
1322 // create device and exit if creation failed
\r
1324 IrrlichtDevice *device;
\r
1325 device = createDevice(driverType,
\r
1326 core::dimension2d<u32>(screenW, screenH),
\r
1327 16, fullscreen, false, false, &receiver);
\r
1330 return 1; // could not create selected driver.
\r
1332 g_device = device;
\r
1333 g_irrlicht = new IrrlichtWrapper(device);
\r
1335 //g_device = device;
\r
1337 device->setResizable(true);
\r
1339 bool random_input = g_settings.getBool("random_input")
\r
1340 || cmd_args.getFlag("random-input");
\r
1342 g_input = new RandomInputHandler();
\r
1344 g_input = new RealInputHandler(device, &receiver);
\r
1347 Continue initialization
\r
1350 video::IVideoDriver* driver = device->getVideoDriver();
\r
1353 This changes the minimum allowed number of vertices in a VBO
\r
1355 //driver->setMinHardwareBufferVertexCount(1);
\r
1357 scene::ISceneManager* smgr = device->getSceneManager();
\r
1359 guienv = device->getGUIEnvironment();
\r
1360 gui::IGUISkin* skin = guienv->getSkin();
\r
1361 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1363 skin->setFont(font);
\r
1365 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1366 dstream<<"text_height="<<text_height<<std::endl;
\r
1368 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1369 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1370 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1371 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1372 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1373 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1375 const wchar_t *text = L"Loading and connecting...";
\r
1376 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1377 core::vector2d<s32> textsize(300, text_height);
\r
1378 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1380 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1381 text, textrect, false, false);
\r
1382 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1384 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1385 guienv->drawAll();
\r
1386 driver->endScene();
\r
1389 Preload some textures
\r
1392 tile_materials_preload(g_irrlicht);
\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 g_client = &client;
\r
1422 Address connect_address(0,0,0,0, port);
\r
1424 connect_address.Resolve(connect_name);
\r
1426 catch(ResolveError &e)
\r
1428 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1432 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1433 client.connect(connect_address);
\r
1436 while(client.connectedAndInitialized() == false)
\r
1439 if(server != NULL){
\r
1440 server->step(0.1);
\r
1445 catch(con::PeerNotFoundException &e)
\r
1447 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1454 /*scene::ISceneNode* skybox;
\r
1455 skybox = smgr->addSkyBoxSceneNode(
\r
1456 driver->getTexture("../data/skybox2.png"),
\r
1457 driver->getTexture("../data/skybox3.png"),
\r
1458 driver->getTexture("../data/skybox1.png"),
\r
1459 driver->getTexture("../data/skybox1.png"),
\r
1460 driver->getTexture("../data/skybox1.png"),
\r
1461 driver->getTexture("../data/skybox1.png"));*/
\r
1464 Create the camera node
\r
1467 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1468 0, // Camera parent
\r
1469 v3f(BS*100, BS*2, BS*100), // Look from
\r
1470 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1474 if(camera == NULL)
\r
1477 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1479 camera->setFOV(FOV_ANGLE);
\r
1481 // Just so big a value that everything rendered is visible
\r
1482 camera->setFarValue(100000*BS);
\r
1484 f32 camera_yaw = 0; // "right/left"
\r
1485 f32 camera_pitch = 0; // "up/down"
\r
1491 gui_loadingtext->remove();
\r
1494 Add some gui stuff
\r
1497 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1498 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1501 We need some kind of a root node to be able to add
\r
1502 custom elements directly on the screen.
\r
1503 Otherwise they won't be automatically drawn.
\r
1505 guiroot = guienv->addStaticText(L"",
\r
1506 core::rect<s32>(0, 0, 10000, 10000));
\r
1508 // Test the text input system
\r
1509 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_active_menu_count,
\r
1512 // First line of debug text
\r
1513 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1515 core::rect<s32>(5, 5, 795, 5+textsize.Y),
\r
1517 // Second line of debug text
\r
1518 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1520 core::rect<s32>(5, 5+(textsize.Y+5)*1, 795, (5+textsize.Y)*2),
\r
1523 // At the middle of the screen
\r
1524 // Object infos are shown in this
\r
1525 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1527 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1531 gui::IGUIStaticText *chat_guitext = guienv->addStaticText(
\r
1532 L"Chat here\nOther line\nOther line\nOther line\nOther line",
\r
1533 core::rect<s32>(70, 60, 795, 150),
\r
1535 core::list<std::wstring> chat_lines;
\r
1536 //chat_lines.push_back(L"Minetest-c55 up and running!");
\r
1539 Some statistics are collected in these
\r
1542 u32 beginscenetime = 0;
\r
1543 u32 scenetime = 0;
\r
1544 u32 endscenetime = 0;
\r
1550 bool first_loop_after_window_activation = true;
\r
1552 // Time is in milliseconds
\r
1553 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1554 // NOTE: Have to call run() between calls of this to update the timer
\r
1555 u32 lasttime = device->getTimer()->getTime();
\r
1557 while(device->run())
\r
1560 Run global IrrlichtWrapper's main thread processing stuff
\r
1562 g_irrlicht->Run();
\r
1565 Random calculations
\r
1567 v2u32 screensize = driver->getScreenSize();
\r
1568 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1570 // Hilight boxes collected during the loop and displayed
\r
1571 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1574 std::wstring infotext;
\r
1576 //TimeTaker //timer1("//timer1");
\r
1578 // Time of frame without fps limit
\r
1582 // not using getRealTime is necessary for wine
\r
1583 u32 time = device->getTimer()->getTime();
\r
1584 if(time > lasttime)
\r
1585 busytime_u32 = time - lasttime;
\r
1588 busytime = busytime_u32 / 1000.0;
\r
1591 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1593 // Absolutelu necessary for wine!
\r
1600 updateViewingRange(busytime, &client);
\r
1607 float fps_max = g_settings.getFloat("fps_max");
\r
1608 u32 frametime_min = 1000./fps_max;
\r
1610 if(busytime_u32 < frametime_min)
\r
1612 u32 sleeptime = frametime_min - busytime_u32;
\r
1613 device->sleep(sleeptime);
\r
1617 // Absolutelu necessary for wine!
\r
1621 Time difference calculation
\r
1623 f32 dtime; // in seconds
\r
1625 u32 time = device->getTimer()->getTime();
\r
1626 if(time > lasttime)
\r
1627 dtime = (time - lasttime) / 1000.0;
\r
1633 Time average and jitter calculation
\r
1636 static f32 dtime_avg1 = 0.0;
\r
1637 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1638 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1640 static f32 dtime_jitter1_max_sample = 0.0;
\r
1641 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1643 static f32 jitter1_max = 0.0;
\r
1644 static f32 counter = 0.0;
\r
1645 if(dtime_jitter1 > jitter1_max)
\r
1646 jitter1_max = dtime_jitter1;
\r
1651 dtime_jitter1_max_sample = jitter1_max;
\r
1652 dtime_jitter1_max_fraction
\r
1653 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1654 jitter1_max = 0.0;
\r
1657 Control freetime ratio
\r
1659 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1661 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1662 g_freetime_ratio += 0.01;
\r
1666 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1667 g_freetime_ratio -= 0.01;
\r
1673 Busytime average and jitter calculation
\r
1676 static f32 busytime_avg1 = 0.0;
\r
1677 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1678 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1680 static f32 busytime_jitter1_max_sample = 0.0;
\r
1681 static f32 busytime_jitter1_min_sample = 0.0;
\r
1683 static f32 jitter1_max = 0.0;
\r
1684 static f32 jitter1_min = 0.0;
\r
1685 static f32 counter = 0.0;
\r
1686 if(busytime_jitter1 > jitter1_max)
\r
1687 jitter1_max = busytime_jitter1;
\r
1688 if(busytime_jitter1 < jitter1_min)
\r
1689 jitter1_min = busytime_jitter1;
\r
1691 if(counter > 0.0){
\r
1693 busytime_jitter1_max_sample = jitter1_max;
\r
1694 busytime_jitter1_min_sample = jitter1_min;
\r
1695 jitter1_max = 0.0;
\r
1696 jitter1_min = 0.0;
\r
1701 Debug info for client
\r
1704 static float counter = 0.0;
\r
1709 client.printDebugInfo(std::cout);
\r
1714 Input handler step()
\r
1716 g_input->step(dtime);
\r
1719 Player speed control
\r
1728 bool a_superspeed,
\r
1731 PlayerControl control(
\r
1732 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1733 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1734 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1735 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1736 g_input->isKeyDown(irr::KEY_SPACE),
\r
1737 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1741 client.setPlayerControl(control);
\r
1745 Process environment
\r
1749 //TimeTaker timer("client.step(dtime)");
\r
1750 client.step(dtime);
\r
1751 //client.step(dtime_avg1);
\r
1754 if(server != NULL)
\r
1756 //TimeTaker timer("server->step(dtime)");
\r
1757 server->step(dtime);
\r
1760 v3f player_position = client.getPlayerPosition();
\r
1762 //TimeTaker //timer2("//timer2");
\r
1765 Mouse and camera control
\r
1768 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
1771 device->getCursorControl()->setVisible(false);
\r
1773 if(first_loop_after_window_activation){
\r
1774 //std::cout<<"window active, first loop"<<std::endl;
\r
1775 first_loop_after_window_activation = false;
\r
1778 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
1779 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
1780 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1781 camera_yaw -= dx*0.2;
\r
1782 camera_pitch += dy*0.2;
\r
1783 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1784 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1786 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
1789 device->getCursorControl()->setVisible(true);
\r
1791 //std::cout<<"window inactive"<<std::endl;
\r
1792 first_loop_after_window_activation = true;
\r
1795 camera_yaw = wrapDegrees(camera_yaw);
\r
1796 camera_pitch = wrapDegrees(camera_pitch);
\r
1798 v3f camera_direction = v3f(0,0,1);
\r
1799 camera_direction.rotateYZBy(camera_pitch);
\r
1800 camera_direction.rotateXZBy(camera_yaw);
\r
1802 v3f camera_position =
\r
1803 player_position + v3f(0, BS+BS/2, 0);
\r
1805 camera->setPosition(camera_position);
\r
1806 // *100.0 helps in large map coordinates
\r
1807 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1809 if(FIELD_OF_VIEW_TEST){
\r
1810 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1811 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1814 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1815 //TimeTaker timer("client.updateCamera");
\r
1816 client.updateCamera(camera_position, camera_direction);
\r
1820 //TimeTaker //timer3("//timer3");
\r
1823 Calculate what block is the crosshair pointing to
\r
1826 //u32 t1 = device->getTimer()->getRealTime();
\r
1828 //f32 d = 4; // max. distance
\r
1829 f32 d = 4; // max. distance
\r
1830 core::line3d<f32> shootline(camera_position,
\r
1831 camera_position + camera_direction * BS * (d+1));
\r
1833 MapBlockObject *selected_object = client.getSelectedObject
\r
1834 (d*BS, camera_position, shootline);
\r
1836 if(selected_object != NULL)
\r
1838 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1840 core::aabbox3d<f32> box_on_map
\r
1841 = selected_object->getSelectionBoxOnMap();
\r
1843 hilightboxes.push_back(box_on_map);
\r
1845 infotext = narrow_to_wide(selected_object->infoText());
\r
1847 if(g_input->getLeftClicked())
\r
1849 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1850 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1851 selected_object->getId(), g_selected_item);
\r
1853 else if(g_input->getRightClicked())
\r
1855 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1857 Check if we want to modify the object ourselves
\r
1859 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1861 dstream<<"Sign object right-clicked"<<std::endl;
\r
1863 if(random_input == false)
\r
1865 // Get a new text for it
\r
1867 TextDest *dest = new TextDestSign(
\r
1868 selected_object->getBlock()->getPos(),
\r
1869 selected_object->getId(),
\r
1872 SignObject *sign_object = (SignObject*)selected_object;
\r
1874 std::wstring wtext =
\r
1875 narrow_to_wide(sign_object->getText());
\r
1877 (new GUITextInputMenu(guienv, guiroot, -1,
\r
1878 &g_active_menu_count, dest,
\r
1883 Otherwise pass the event to the server as-is
\r
1887 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1888 selected_object->getId(), g_selected_item);
\r
1892 else // selected_object == NULL
\r
1896 Find out which node we are pointing at
\r
1899 bool nodefound = false;
\r
1901 v3s16 neighbourpos;
\r
1902 core::aabbox3d<f32> nodefacebox;
\r
1903 f32 mindistance = BS * 1001;
\r
1905 v3s16 pos_i = floatToInt(player_position);
\r
1907 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1911 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1912 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1913 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1914 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1915 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1916 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1918 for(s16 y = ystart; y <= yend; y++)
\r
1919 for(s16 z = zstart; z <= zend; z++)
\r
1920 for(s16 x = xstart; x <= xend; x++)
\r
1925 n = client.getNode(v3s16(x,y,z));
\r
1926 if(content_pointable(n.d) == false)
\r
1929 catch(InvalidPositionException &e)
\r
1935 v3f npf = intToFloat(np);
\r
1940 v3s16(0,0,1), // back
\r
1941 v3s16(0,1,0), // top
\r
1942 v3s16(1,0,0), // right
\r
1943 v3s16(0,0,-1), // front
\r
1944 v3s16(0,-1,0), // bottom
\r
1945 v3s16(-1,0,0), // left
\r
1951 if(n.d == CONTENT_TORCH)
\r
1953 v3s16 dir = unpackDir(n.dir);
\r
1954 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
1955 dir_f *= BS/2 - BS/6 - BS/20;
\r
1956 v3f cpf = npf + dir_f;
\r
1957 f32 distance = (cpf - camera_position).getLength();
\r
1959 core::aabbox3d<f32> box;
\r
1962 if(dir == v3s16(0,-1,0))
\r
1964 box = core::aabbox3d<f32>(
\r
1965 npf - v3f(BS/6, BS/2, BS/6),
\r
1966 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
1970 else if(dir == v3s16(0,1,0))
\r
1972 box = core::aabbox3d<f32>(
\r
1973 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
1974 npf + v3f(BS/6, BS/2, BS/6)
\r
1980 box = core::aabbox3d<f32>(
\r
1981 cpf - v3f(BS/6, BS/3, BS/6),
\r
1982 cpf + v3f(BS/6, BS/3, BS/6)
\r
1986 if(distance < mindistance)
\r
1988 if(box.intersectsWithLine(shootline))
\r
1992 neighbourpos = np;
\r
1993 mindistance = distance;
\r
1994 nodefacebox = box;
\r
2003 for(u16 i=0; i<6; i++)
\r
2005 v3f dir_f = v3f(dirs[i].X,
\r
2006 dirs[i].Y, dirs[i].Z);
\r
2007 v3f centerpoint = npf + dir_f * BS/2;
\r
2009 (centerpoint - camera_position).getLength();
\r
2011 if(distance < mindistance)
\r
2013 core::CMatrix4<f32> m;
\r
2014 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2016 // This is the back face
\r
2017 v3f corners[2] = {
\r
2018 v3f(BS/2, BS/2, BS/2),
\r
2019 v3f(-BS/2, -BS/2, BS/2+d)
\r
2022 for(u16 j=0; j<2; j++)
\r
2024 m.rotateVect(corners[j]);
\r
2025 corners[j] += npf;
\r
2028 core::aabbox3d<f32> facebox(corners[0]);
\r
2029 facebox.addInternalPoint(corners[1]);
\r
2031 if(facebox.intersectsWithLine(shootline))
\r
2035 neighbourpos = np + dirs[i];
\r
2036 mindistance = distance;
\r
2037 nodefacebox = facebox;
\r
2039 } // if distance < mindistance
\r
2041 } // regular block
\r
2046 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2048 static float dig_time = 0.0;
\r
2049 static u16 dig_index = 0;
\r
2051 if(nodepos != nodepos_old)
\r
2053 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2054 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2056 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2058 client.clearTempMod(nodepos_old);
\r
2063 hilightboxes.push_back(nodefacebox);
\r
2065 if(g_input->getLeftReleased())
\r
2067 client.clearTempMod(nodepos);
\r
2070 if(g_input->getLeftClicked() ||
\r
2071 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2073 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2074 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2076 if(g_input->getLeftClicked())
\r
2078 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2080 if(g_input->getLeftState())
\r
2082 MapNode n = client.getNode(nodepos);
\r
2084 // TODO: Get this from some table that is sent by server
\r
2085 float dig_time_complete = 0.5;
\r
2086 if(n.d == CONTENT_STONE)
\r
2087 dig_time_complete = 1.5;
\r
2089 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2090 * dig_time/dig_time_complete);
\r
2092 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2094 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2095 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2099 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2100 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2101 client.clearTempMod(nodepos);
\r
2102 client.removeNode(nodepos);
\r
2105 dig_time += dtime;
\r
2108 if(g_input->getRightClicked())
\r
2110 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2111 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2114 nodepos_old = nodepos;
\r
2119 } // selected_object == NULL
\r
2121 g_input->resetLeftClicked();
\r
2122 g_input->resetRightClicked();
\r
2124 if(g_input->getLeftReleased())
\r
2126 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2128 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2130 if(g_input->getRightReleased())
\r
2132 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2136 g_input->resetLeftReleased();
\r
2137 g_input->resetRightReleased();
\r
2140 Calculate stuff for drawing
\r
2143 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2145 u32 daynight_ratio = client.getDayNightRatio();
\r
2146 video::SColor bgcolor = video::SColor(
\r
2148 skycolor.getRed() * daynight_ratio / 1000,
\r
2149 skycolor.getGreen() * daynight_ratio / 1000,
\r
2150 skycolor.getBlue() * daynight_ratio / 1000);
\r
2156 if(g_settings.getBool("enable_fog") == true)
\r
2158 f32 range = g_viewing_range_nodes * BS;
\r
2159 if(g_viewing_range_all)
\r
2160 range = 100000*BS;
\r
2164 video::EFT_FOG_LINEAR,
\r
2168 false, // pixel fog
\r
2169 false // range fog
\r
2175 Update gui stuff (0ms)
\r
2178 //TimeTaker guiupdatetimer("Gui updating");
\r
2181 wchar_t temptext[150];
\r
2183 static float drawtime_avg = 0;
\r
2184 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
2185 static float beginscenetime_avg = 0;
\r
2186 beginscenetime_avg = beginscenetime_avg * 0.98 + (float)beginscenetime*0.02;
\r
2187 static float scenetime_avg = 0;
\r
2188 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
2189 static float endscenetime_avg = 0;
\r
2190 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
2192 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2194 L", R: range_all=%i"
\r
2196 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2198 g_viewing_range_all,
\r
2200 beginscenetime_avg,
\r
2205 guitext->setText(temptext);
\r
2209 wchar_t temptext[150];
\r
2210 swprintf(temptext, 150,
\r
2211 L"(% .1f, % .1f, % .1f)"
\r
2212 L" (% .3f < btime_jitter < % .3f"
\r
2213 L", dtime_jitter = % .1f %%)",
\r
2214 player_position.X/BS,
\r
2215 player_position.Y/BS,
\r
2216 player_position.Z/BS,
\r
2217 busytime_jitter1_min_sample,
\r
2218 busytime_jitter1_max_sample,
\r
2219 dtime_jitter1_max_fraction * 100.0
\r
2222 guitext2->setText(temptext);
\r
2226 guitext_info->setText(infotext.c_str());
\r
2230 Get chat messages from client
\r
2234 std::wstring message;
\r
2235 while(client.getChatMessage(message))
\r
2237 chat_lines.push_back(message);
\r
2238 if(chat_lines.size() > 5)
\r
2240 core::list<std::wstring>::Iterator
\r
2241 i = chat_lines.begin();
\r
2242 chat_lines.erase(i);
\r
2245 // Append them to form the whole static text and throw
\r
2246 // it to the gui element
\r
2247 std::wstring whole;
\r
2248 for(core::list<std::wstring>::Iterator
\r
2249 i = chat_lines.begin();
\r
2250 i != chat_lines.end(); i++)
\r
2252 whole += (*i) + L'\n';
\r
2254 chat_guitext->setText(whole.c_str());
\r
2255 // Update gui element size and position
\r
2256 core::rect<s32> rect(
\r
2258 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2259 screensize.X - 10,
\r
2262 chat_guitext->setRelativePosition(rect);
\r
2269 static u16 old_selected_item = 65535;
\r
2270 if(client.getLocalInventoryUpdated()
\r
2271 || g_selected_item != old_selected_item)
\r
2273 old_selected_item = g_selected_item;
\r
2274 //std::cout<<"Updating local inventory"<<std::endl;
\r
2275 client.getLocalInventory(local_inventory);
\r
2276 quick_inventory->setSelection(g_selected_item);
\r
2277 quick_inventory->update();
\r
2281 Send actions returned by the inventory menu
\r
2283 while(inventory_action_queue.size() != 0)
\r
2285 InventoryAction *a = inventory_action_queue.pop_front();
\r
2287 client.sendInventoryAction(a);
\r
2296 TimeTaker drawtimer("Drawing");
\r
2300 TimeTaker timer("beginScene");
\r
2301 driver->beginScene(true, true, bgcolor);
\r
2302 //driver->beginScene(false, true, bgcolor);
\r
2303 beginscenetime = timer.stop(true);
\r
2308 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2311 TimeTaker timer("smgr");
\r
2313 scenetime = timer.stop(true);
\r
2317 //TimeTaker timer9("auxiliary drawings");
\r
2321 //TimeTaker //timer10("//timer10");
\r
2323 video::SMaterial m;
\r
2325 m.Lighting = false;
\r
2326 driver->setMaterial(m);
\r
2328 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2330 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2331 i != hilightboxes.end(); i++)
\r
2333 /*std::cout<<"hilightbox min="
\r
2334 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2336 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2338 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2344 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2345 displaycenter + core::vector2d<s32>(10,0),
\r
2346 video::SColor(255,255,255,255));
\r
2347 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2348 displaycenter + core::vector2d<s32>(0,10),
\r
2349 video::SColor(255,255,255,255));
\r
2354 //TimeTaker //timer11("//timer11");
\r
2360 guienv->drawAll();
\r
2364 TimeTaker timer("endScene");
\r
2365 driver->endScene();
\r
2366 endscenetime = timer.stop(true);
\r
2369 drawtime = drawtimer.stop(true);
\r
2375 static s16 lastFPS = 0;
\r
2376 //u16 fps = driver->getFPS();
\r
2377 u16 fps = (1.0/dtime_avg1);
\r
2379 if (lastFPS != fps)
\r
2381 core::stringw str = L"Minetest [";
\r
2382 str += driver->getName();
\r
2386 device->setWindowCaption(str.c_str());
\r
2392 device->yield();*/
\r
2395 delete quick_inventory;
\r
2397 } // client is deleted at this point
\r
2402 In the end, delete the Irrlicht device.
\r
2407 Update configuration file
\r
2409 /*if(configpath != "")
\r
2411 g_settings.updateConfigFile(configpath.c_str());
\r
2415 catch(con::PeerNotFoundException &e)
\r
2417 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2419 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2421 This is what has to be done in every thread to get suitable debug info
\r
2423 catch(std::exception &e)
\r
2425 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2426 <<e.what()<<std::endl;
\r
2431 debugstreams_deinit();
\r