3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
\r
5 This program is free software; you can redistribute it and/or modify
\r
6 it under the terms of the GNU General Public License as published by
\r
7 the Free Software Foundation; either version 2 of the License, or
\r
8 (at your option) any later version.
\r
10 This program is distributed in the hope that it will be useful,
\r
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 GNU General Public License for more details.
\r
15 You should have received a copy of the GNU General Public License along
\r
16 with this program; if not, write to the Free Software Foundation, Inc.,
\r
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
21 =============================== NOTES ==============================
\r
22 NOTE: Things starting with TODO are sometimes only suggestions.
\r
24 NOTE: VBO cannot be turned on for fast-changing stuff because there
\r
25 is an apparanet memory leak in irrlicht when using it (not sure)
\r
27 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
28 NOTE: Global locale is now set at initialization
\r
30 SUGGESTION: add a second lighting value to the MS nibble of param of
\r
31 air to tell how bright the air node is when there is no sunlight.
\r
32 When day changes to night, these two values can be interpolated.
\r
34 TODO: Fix address to be ipv6 compatible
\r
36 TODO: ESC Pause mode in which the cursor is not kept at the center of window.
\r
37 TODO: Stop player if focus of window is taken away (go to pause mode)
\r
38 TODO: Optimize and fix makeFastFace or whatever it's called
\r
39 - Face calculation is the source of CPU usage on the client
\r
40 SUGGESTION: The client will calculate and send lighting changes and
\r
41 the server will randomly check some of them and kick the client out
\r
42 if it fails to calculate them right.
\r
43 - Actually, it could just start ignoring them and calculate them
\r
45 SUGGESTION: Combine MapBlock's face caches to so big pieces that VBO
\r
47 - That is >500 vertices
\r
49 TODO: Better dungeons
\r
50 TODO: There should be very slight natural caves also, starting from
\r
51 only a straightened-up cliff
\r
53 TODO: Changing of block with mouse wheel or something
\r
58 - One single map container with ids as keys
\r
61 TODO: - Keep track of the place of the mob in the last few hundreth's
\r
62 of a second - then, if a player hits it, take the value that is
\r
63 avg_rtt/2 before the moment the packet is received.
\r
66 SUGGESTION: Modify client to calculate single changes asynchronously
\r
68 TODO: Moving players more smoothly. Calculate moving animation
\r
69 in a way that doesn't make the player jump to the right place
\r
70 immediately when the server sends a new position
\r
72 TODO: There are some lighting-related todos and fixmes in
\r
73 ServerMap::emergeBlock
\r
75 FIXME: When a new sector is generated, it may change the ground level
\r
76 of it's and it's neighbors border that two blocks that are
\r
77 above and below each other and that are generated before and
\r
78 after the sector heightmap generation (order doesn't matter),
\r
79 can have a small gap between each other at the border.
\r
80 SUGGESTION: Use same technique for sector heightmaps as what we're
\r
81 using for UnlimitedHeightmap? (getting all neighbors
\r
84 SUGG: Set server to automatically find a good spawning place in some
\r
85 place where there is water and land.
\r
86 - Map to have a getWalkableNear(p)
\r
87 - Is this a good idea? It's part of the game to find a good place.
\r
89 TODO: Transfer more blocks in a single packet
\r
90 SUGG: A blockdata combiner class, to which blocks are added and at
\r
91 destruction it sends all the stuff in as few packets as possible.
\r
93 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
94 SUGG: Fetch stuff mainly from the viewing direction
\r
96 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
97 - This enables saving many packets and making a faster connection
\r
98 - This also enables server to check if client has received the
\r
99 most recent block sent, for example.
\r
100 TODO: Add a sane bandwidth throttling system to Connection
\r
102 SUGG: More fine-grained control of client's dumping of blocks from
\r
104 - ...What does this mean in the first place?
\r
106 TODO: Make the amount of blocks sending to client and the total
\r
107 amount of blocks dynamically limited. Transferring blocks is the
\r
108 main network eater of this system, so it is the one that has
\r
109 to be throttled so that RTTs stay low.
\r
111 TODO: Server to load starting inventory from disk
\r
113 TODO: PLayers to only be hidden when the client quits.
\r
114 TODO: - Players to be saved on disk, with inventory
\r
115 TODO: Players to be saved as text in map/players/<name>
\r
117 SUGG: A map editing mode (similar to dedicated server mode)
\r
119 TODO: Make fetching sector's blocks more efficient when rendering
\r
120 sectors that have very large amounts of blocks (on client)
\r
122 TODO: Make the video backend selectable
\r
124 Block object server side:
\r
125 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
126 - For all blocks in the buffer, objects are stepped(). This
\r
127 means they are active.
\r
128 - TODO: A global active buffer is needed for the server
\r
129 - TODO: All blocks going in and out of the buffer are recorded.
\r
130 - TODO: For outgoing blocks, a timestamp is written.
\r
131 - TODO: For incoming blocks, the time difference is calculated and
\r
132 objects are stepped according to it.
\r
133 TODO: A timestamp to blocks
\r
135 SUGG: Add a time value to the param of footstepped grass and check it
\r
136 against a global timer when a block is accessed, to make old
\r
139 TODO: Add config parameters for server's sending and generating distance
\r
141 TODO: Copy the text of the last picked sign to inventory in creative
\r
144 TODO: Untie client network operations from framerate
\r
145 - Needs some input queues or something
\r
147 SUGG: Make a copy of close-range environment on client for showing
\r
148 on screen, with minimal mutexes to slow down the main loop
\r
150 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
151 it by sending more stuff in a single packet.
\r
152 - Add a packet queue to RemoteClient, from which packets will be
\r
153 combined with object data packets
\r
154 - This is not exactly trivial: the object data packets are
\r
155 sometimes very big by themselves
\r
157 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
158 - This will allow saving ages of rats on disk but not sending
\r
161 TODO: Get rid of GotSplitPacketException
\r
163 SUGG: Implement lighting using VoxelManipulator
\r
164 - Would it be significantly faster?
\r
166 TODO: Check what goes wrong with caching map to disk (Kray)
\r
168 TODO: Remove LazyMeshUpdater. It is not used as supposed.
\r
170 FIXME: Rats somehow go underground sometimes (you can see it in water)
\r
171 - Does their position get saved to a border value or something?
\r
173 TODO: MovingObject::move and Player::move are basically the same.
\r
177 ======================================================================
\r
180 ======================================================================
\r
185 Setting this to 1 enables a special camera mode that forces
\r
186 the renderers to think that the camera statically points from
\r
187 the starting place to a static direction.
\r
189 This allows one to move around with the player and see what
\r
190 is actually drawn behind solid things and behind the player.
\r
192 #define FIELD_OF_VIEW_TEST 0
\r
194 #ifdef UNITTEST_DISABLE
\r
196 #pragma message ("Disabling unit tests")
\r
198 #warning "Disabling unit tests"
\r
200 // Disable unit tests
\r
201 #define ENABLE_TESTS 0
\r
203 // Enable unit tests
\r
204 #define ENABLE_TESTS 1
\r
208 #pragma comment(lib, "Irrlicht.lib")
\r
209 #pragma comment(lib, "jthread.lib")
\r
210 // This would get rid of the console window
\r
211 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
215 #define WIN32_LEAN_AND_MEAN
\r
216 #include <windows.h>
\r
217 #define sleep_ms(x) Sleep(x)
\r
219 #include <unistd.h>
\r
220 #define sleep_ms(x) usleep(x*1000)
\r
223 #include <iostream>
\r
226 #include <jmutexautolock.h>
\r
227 #include "common_irrlicht.h"
\r
230 #include "player.h"
\r
233 #include "environment.h"
\r
234 #include "server.h"
\r
235 #include "client.h"
\r
236 #include "serialization.h"
\r
237 #include "constants.h"
\r
238 #include "strfnd.h"
\r
239 #include "porting.h"
\r
240 #include <locale.h>
\r
242 IrrlichtDevice *g_device = NULL;
\r
244 const char *g_material_filenames[MATERIALS_COUNT] =
\r
246 "../data/stone.png",
\r
247 "../data/grass.png",
\r
248 "../data/water.png",
\r
249 "../data/light.png",
\r
250 "../data/tree.png",
\r
251 "../data/leaves.png",
\r
252 "../data/grass_footsteps.png",
\r
253 "../data/mese.png",
\r
257 video::SMaterial g_materials[MATERIALS_COUNT];
\r
258 //video::SMaterial g_mesh_materials[3];
\r
260 // All range-related stuff below is locked behind this
\r
261 JMutex g_range_mutex;
\r
263 // Blocks are viewed in this range from the player
\r
264 s16 g_viewing_range_nodes = 60;
\r
266 // This is updated by the client's fetchBlocks routine
\r
267 //s16 g_actual_viewing_range_nodes = VIEWING_RANGE_NODES_DEFAULT;
\r
269 // If true, the preceding value has no meaning and all blocks
\r
270 // already existing in memory are drawn
\r
271 bool g_viewing_range_all = false;
\r
273 // This is the freetime ratio imposed by the dynamic viewing
\r
274 // range changing code.
\r
275 // It is controlled by the main loop to the smallest value that
\r
276 // inhibits glitches (dtime jitter) in the main loop.
\r
277 //float g_freetime_ratio = FREETIME_RATIO_MAX;
\r
282 These are loaded from the config file.
\r
285 Settings g_settings;
\r
287 // Sets default settings
\r
288 void set_default_settings()
\r
290 g_settings.set("dedicated_server", "");
\r
293 g_settings.set("wanted_fps", "30");
\r
294 g_settings.set("fps_max", "60");
\r
295 g_settings.set("viewing_range_nodes_max", "300");
\r
296 g_settings.set("viewing_range_nodes_min", "20");
\r
297 g_settings.set("screenW", "");
\r
298 g_settings.set("screenH", "");
\r
299 g_settings.set("host_game", "");
\r
300 g_settings.set("port", "");
\r
301 g_settings.set("address", "");
\r
302 g_settings.set("name", "");
\r
303 g_settings.set("random_input", "false");
\r
304 g_settings.set("client_delete_unused_sectors_timeout", "1200");
\r
307 g_settings.set("creative_mode", "false");
\r
308 g_settings.set("heightmap_blocksize", "128");
\r
309 g_settings.set("height_randmax", "constant 70.0");
\r
310 g_settings.set("height_randfactor", "constant 0.6");
\r
311 g_settings.set("height_base", "linear 0 35 0");
\r
312 g_settings.set("plants_amount", "1.0");
\r
313 g_settings.set("ravines_amount", "1.0");
\r
314 g_settings.set("objectdata_interval", "0.2");
\r
315 g_settings.set("active_object_range", "2");
\r
316 g_settings.set("max_simultaneous_block_sends_per_client", "1");
\r
317 g_settings.set("max_simultaneous_block_sends_server_total", "4");
\r
324 //u16 g_selected_material = 0;
\r
325 u16 g_selected_item = 0;
\r
327 bool g_esc_pressed = false;
\r
329 std::wstring g_text_buffer;
\r
330 bool g_text_buffer_accepted = false;
\r
332 // When true, the mouse and keyboard are grabbed
\r
333 bool g_game_focused = true;
\r
340 std::ostream *dout_con_ptr = &dummyout;
\r
341 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
342 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
343 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
344 //std::ostream *dout_con_ptr = &dstream;
\r
345 //std::ostream *derr_con_ptr = &dstream;
\r
348 std::ostream *dout_server_ptr = &dstream;
\r
349 std::ostream *derr_server_ptr = &dstream;
\r
352 std::ostream *dout_client_ptr = &dstream;
\r
353 std::ostream *derr_client_ptr = &dstream;
\r
360 JMutex g_timestamp_mutex;
\r
361 //std::string g_timestamp;
\r
363 std::string getTimestamp()
\r
365 if(g_timestamp_mutex.IsInitialized()==false)
\r
367 JMutexAutoLock lock(g_timestamp_mutex);
\r
368 //return g_timestamp;
\r
369 time_t t = time(NULL);
\r
370 struct tm *tm = localtime(&t);
\r
372 strftime(cs, 20, "%H:%M:%S", tm);
\r
376 class MyEventReceiver : public IEventReceiver
\r
379 // This is the one method that we have to implement
\r
380 virtual bool OnEvent(const SEvent& event)
\r
382 // Remember whether each key is down or up
\r
383 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
385 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
387 if(event.KeyInput.PressedDown)
\r
389 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
390 if(g_game_focused == false)
\r
392 s16 key = event.KeyInput.Key;
\r
393 if(key == irr::KEY_RETURN || key == irr::KEY_ESCAPE)
\r
395 g_text_buffer_accepted = true;
\r
397 else if(key == irr::KEY_BACK)
\r
399 if(g_text_buffer.size() > 0)
\r
400 g_text_buffer = g_text_buffer.substr
\r
401 (0, g_text_buffer.size()-1);
\r
405 wchar_t wc = event.KeyInput.Char;
\r
407 g_text_buffer += wc;
\r
411 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
413 if(g_game_focused == true)
\r
415 dstream<<DTIME<<"ESC pressed"<<std::endl;
\r
416 g_esc_pressed = true;
\r
420 // Material selection
\r
421 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
423 if(g_game_focused == true)
\r
425 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
428 g_selected_item = 0;
\r
429 dstream<<DTIME<<"Selected item: "
\r
430 <<g_selected_item<<std::endl;
\r
434 // Viewing range selection
\r
435 if(event.KeyInput.Key == irr::KEY_KEY_R
\r
438 JMutexAutoLock lock(g_range_mutex);
\r
439 if(g_viewing_range_all)
\r
441 g_viewing_range_all = false;
\r
442 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
446 g_viewing_range_all = true;
\r
447 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
451 // Print debug stacks
\r
452 if(event.KeyInput.Key == irr::KEY_KEY_P
\r
455 dstream<<"-----------------------------------------"
\r
457 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
458 dstream<<"-----------------------------------------"
\r
460 debug_stacks_print();
\r
465 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
467 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
469 leftclicked = true;
\r
471 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
473 rightclicked = true;
\r
475 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
477 /*dstream<<"event.MouseInput.Wheel="
\r
478 <<event.MouseInput.Wheel<<std::endl;*/
\r
479 if(event.MouseInput.Wheel < 0)
\r
481 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
484 g_selected_item = 0;
\r
486 else if(event.MouseInput.Wheel > 0)
\r
488 if(g_selected_item > 0)
\r
491 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
499 // This is used to check whether a key is being held down
\r
500 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
502 return keyIsDown[keyCode];
\r
507 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
508 keyIsDown[i] = false;
\r
509 leftclicked = false;
\r
510 rightclicked = false;
\r
516 // We use this array to store the current state of each key
\r
517 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
528 virtual ~InputHandler()
\r
531 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
532 virtual v2s32 getMousePos() = 0;
\r
533 virtual void setMousePos(s32 x, s32 y) = 0;
\r
534 virtual bool getLeftClicked() = 0;
\r
535 virtual bool getRightClicked() = 0;
\r
536 virtual void resetLeftClicked() = 0;
\r
537 virtual void resetRightClicked() = 0;
\r
539 virtual void step(float dtime) {};
\r
541 virtual void clear() {};
\r
544 InputHandler *g_input = NULL;
\r
549 g_game_focused = true;
\r
554 g_game_focused = false;
\r
557 class RealInputHandler : public InputHandler
\r
560 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
562 m_receiver(receiver)
\r
565 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
567 return m_receiver->IsKeyDown(keyCode);
\r
569 virtual v2s32 getMousePos()
\r
571 return m_device->getCursorControl()->getPosition();
\r
573 virtual void setMousePos(s32 x, s32 y)
\r
575 m_device->getCursorControl()->setPosition(x, y);
\r
578 virtual bool getLeftClicked()
\r
580 if(g_game_focused == false)
\r
582 return m_receiver->leftclicked;
\r
584 virtual bool getRightClicked()
\r
586 if(g_game_focused == false)
\r
588 return m_receiver->rightclicked;
\r
590 virtual void resetLeftClicked()
\r
592 m_receiver->leftclicked = false;
\r
594 virtual void resetRightClicked()
\r
596 m_receiver->rightclicked = false;
\r
601 resetRightClicked();
\r
602 resetLeftClicked();
\r
605 IrrlichtDevice *m_device;
\r
606 MyEventReceiver *m_receiver;
\r
609 class RandomInputHandler : public InputHandler
\r
612 RandomInputHandler()
\r
614 leftclicked = false;
\r
615 rightclicked = false;
\r
616 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
617 keydown[i] = false;
\r
619 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
621 return keydown[keyCode];
\r
623 virtual v2s32 getMousePos()
\r
627 virtual void setMousePos(s32 x, s32 y)
\r
629 mousepos = v2s32(x,y);
\r
632 virtual bool getLeftClicked()
\r
634 return leftclicked;
\r
636 virtual bool getRightClicked()
\r
638 return rightclicked;
\r
640 virtual void resetLeftClicked()
\r
642 leftclicked = false;
\r
644 virtual void resetRightClicked()
\r
646 rightclicked = false;
\r
649 virtual void step(float dtime)
\r
652 static float counter1 = 0;
\r
656 counter1 = 0.1*Rand(1,10);
\r
657 /*if(g_selected_material < USEFUL_MATERIAL_COUNT-1)
\r
658 g_selected_material++;
\r
660 g_selected_material = 0;*/
\r
661 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
664 g_selected_item = 0;
\r
668 static float counter1 = 0;
\r
672 counter1 = 0.1*Rand(1, 40);
\r
673 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
677 static float counter1 = 0;
\r
681 counter1 = 0.1*Rand(1, 40);
\r
682 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
686 static float counter1 = 0;
\r
690 counter1 = 0.1*Rand(1, 40);
\r
691 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
695 static float counter1 = 0;
\r
699 counter1 = 0.1*Rand(1, 40);
\r
700 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
704 static float counter1 = 0;
\r
708 counter1 = 0.1*Rand(1, 20);
\r
709 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
713 static float counter1 = 0;
\r
717 counter1 = 0.1*Rand(1, 30);
\r
718 leftclicked = true;
\r
722 static float counter1 = 0;
\r
726 counter1 = 0.1*Rand(1, 20);
\r
727 rightclicked = true;
\r
730 mousepos += mousespeed;
\r
733 s32 Rand(s32 min, s32 max)
\r
735 return (rand()%(max-min+1))+min;
\r
738 bool keydown[KEY_KEY_CODES_COUNT];
\r
745 void updateViewingRange(f32 frametime, Client *client)
\r
747 // Range_all messes up frametime_avg
\r
748 if(g_viewing_range_all == true)
\r
751 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
753 // Initialize to the target value
\r
754 static float frametime_avg = 1.0/wanted_fps;
\r
755 frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
\r
757 static f32 counter = 0;
\r
759 counter -= frametime;
\r
762 //counter = 1.0; //seconds
\r
763 counter = 0.5; //seconds
\r
765 //float freetime_ratio = 0.2;
\r
766 //float freetime_ratio = 0.4;
\r
767 float freetime_ratio = FREETIME_RATIO;
\r
769 float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
\r
771 float fraction = sqrt(frametime_avg / frametime_wanted);
\r
773 static bool fraction_is_good = false;
\r
775 float fraction_good_threshold = 0.1;
\r
776 float fraction_bad_threshold = 0.25;
\r
777 float fraction_limit;
\r
778 // Use high limit if fraction is good AND the fraction would
\r
779 // lower the range. We want to keep the range fairly high.
\r
780 if(fraction_is_good && fraction > 1.0)
\r
781 fraction_limit = fraction_bad_threshold;
\r
783 fraction_limit = fraction_good_threshold;
\r
785 if(fabs(fraction - 1.0) < fraction_limit)
\r
787 fraction_is_good = true;
\r
792 fraction_is_good = false;
\r
795 //dstream<<"frametime_avg="<<frametime_avg<<std::endl;
\r
796 //dstream<<"frametime_wanted="<<frametime_wanted<<std::endl;
\r
797 /*dstream<<"fetching="<<client->isFetchingBlocks()
\r
798 <<" faction = "<<fraction<<std::endl;*/
\r
800 JMutexAutoLock lock(g_range_mutex);
\r
802 s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
\r
803 s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
\r
805 s16 n = (float)g_viewing_range_nodes / fraction;
\r
806 if(n < viewing_range_nodes_min)
\r
807 n = viewing_range_nodes_min;
\r
808 if(n > viewing_range_nodes_max)
\r
809 n = viewing_range_nodes_max;
\r
811 bool can_change = true;
\r
813 if(client->isFetchingBlocks() == true && n > g_viewing_range_nodes)
\r
814 can_change = false;
\r
817 g_viewing_range_nodes = n;
\r
819 /*dstream<<"g_viewing_range_nodes = "
\r
820 <<g_viewing_range_nodes<<std::endl;*/
\r
823 class GUIQuickInventory : public IEventReceiver
\r
827 gui::IGUIEnvironment* env,
\r
828 gui::IGUIElement* parent,
\r
831 Inventory *inventory):
\r
832 m_itemcount(itemcount),
\r
833 m_inventory(inventory)
\r
835 core::rect<s32> imgsize(0,0,48,48);
\r
836 core::rect<s32> textsize(0,0,48,16);
\r
837 v2s32 spacing(0, 64);
\r
838 for(s32 i=0; i<m_itemcount; i++)
\r
840 m_images.push_back(env->addImage(
\r
841 imgsize + pos + spacing*i
\r
843 m_images[i]->setScaleImage(true);
\r
844 m_texts.push_back(env->addStaticText(
\r
846 textsize + pos + spacing*i,
\r
849 m_texts[i]->setBackgroundColor(
\r
850 video::SColor(128,0,0,0));
\r
851 m_texts[i]->setTextAlignment(
\r
853 gui::EGUIA_UPPERLEFT);
\r
857 virtual bool OnEvent(const SEvent& event)
\r
862 void setSelection(s32 i)
\r
871 start = m_selection - m_itemcount / 2;
\r
873 for(s32 i=0; i<m_itemcount; i++)
\r
877 if(j > (s32)m_inventory->getSize() - 1)
\r
878 j -= m_inventory->getSize();
\r
880 j += m_inventory->getSize();
\r
882 InventoryItem *item = m_inventory->getItem(j);
\r
886 m_images[i]->setImage(NULL);
\r
889 if(m_selection == j)
\r
890 swprintf(t, 10, L"<-");
\r
892 swprintf(t, 10, L"");
\r
893 m_texts[i]->setText(t);
\r
895 // The next ifs will segfault with a NULL pointer
\r
900 m_images[i]->setImage(item->getImage());
\r
903 if(m_selection == j)
\r
904 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
906 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
907 m_texts[i]->setText(t);
\r
913 core::array<gui::IGUIStaticText*> m_texts;
\r
914 core::array<gui::IGUIImage*> m_images;
\r
915 Inventory *m_inventory;
\r
919 int main(int argc, char *argv[])
\r
922 Low-level initialization
\r
925 bool disable_stderr = false;
\r
927 disable_stderr = true;
\r
930 // Initialize debug streams
\r
931 debugstreams_init(disable_stderr, DEBUGFILE);
\r
932 // Initialize debug stacks
\r
933 debug_stacks_init();
\r
935 DSTACK(__FUNCTION_NAME);
\r
941 Basic initialization
\r
944 // Initialize default settings
\r
945 set_default_settings();
\r
947 // Print startup message
\r
948 dstream<<DTIME<<"minetest-c55"
\r
949 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
950 <<", ENABLE_TESTS="<<ENABLE_TESTS
\r
953 // Set locale. This is for forcing '.' as the decimal point.
\r
954 std::locale::global(std::locale("C"));
\r
955 // This enables printing all characters in bitmap font
\r
956 setlocale(LC_CTYPE, "en_US");
\r
958 // Initialize sockets
\r
960 atexit(sockets_cleanup);
\r
962 // Initialize timestamp mutex
\r
963 g_timestamp_mutex.Init();
\r
977 // Read config file
\r
981 g_settings.readConfigFile(argv[1]);
\r
985 const char *filenames[2] =
\r
987 "../minetest.conf",
\r
988 "../../minetest.conf"
\r
991 for(u32 i=0; i<2; i++)
\r
993 bool r = g_settings.readConfigFile(filenames[i]);
\r
999 // Initialize random seed
\r
1002 g_range_mutex.Init();
\r
1003 assert(g_range_mutex.IsInitialized());
\r
1005 // Read map parameters from settings
\r
1007 HMParams hm_params;
\r
1008 hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1009 hm_params.randmax = g_settings.get("height_randmax");
\r
1010 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1011 hm_params.base = g_settings.get("height_base");
\r
1013 MapParams map_params;
\r
1014 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1015 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1021 std::cout<<std::endl<<std::endl;
\r
1024 <<" .__ __ __ "<<std::endl
\r
1025 <<" _____ |__| ____ _____/ |_ ____ _______/ |_ "<<std::endl
\r
1026 <<" / \\| |/ \\_/ __ \\ __\\/ __ \\ / ___/\\ __\\"<<std::endl
\r
1027 <<"| Y Y \\ | | \\ ___/| | \\ ___/ \\___ \\ | | "<<std::endl
\r
1028 <<"|__|_| /__|___| /\\___ >__| \\___ >____ > |__| "<<std::endl
\r
1029 <<" \\/ \\/ \\/ \\/ \\/ "<<std::endl
\r
1031 <<"Now with more waterish water!"
\r
1034 std::cout<<std::endl;
\r
1035 char templine[100];
\r
1038 bool dedicated = g_settings.getBoolAsk
\r
1039 ("dedicated_server", "Dedicated server?", false);
\r
1040 std::cout<<"dedicated = "<<dedicated<<std::endl;
\r
1043 u16 port = g_settings.getU16Ask("port", "Port", 30000);
\r
1044 std::cout<<"-> "<<port<<std::endl;
\r
1048 DSTACK("Dedicated server branch");
\r
1050 std::cout<<std::endl;
\r
1051 std::cout<<"========================"<<std::endl;
\r
1052 std::cout<<"Running dedicated server"<<std::endl;
\r
1053 std::cout<<"========================"<<std::endl;
\r
1054 std::cout<<std::endl;
\r
1056 Server server("../map", hm_params, map_params);
\r
1057 server.start(port);
\r
1061 // This is kind of a hack but can be done like this
\r
1062 // because server.step() is very light
\r
1064 server.step(0.030);
\r
1066 static int counter = 0;
\r
1072 core::list<PlayerInfo> list = server.getPlayerInfo();
\r
1073 core::list<PlayerInfo>::Iterator i;
\r
1074 static u32 sum_old = 0;
\r
1075 u32 sum = PIChecksum(list);
\r
1076 if(sum != sum_old)
\r
1078 std::cout<<DTIME<<"Player info:"<<std::endl;
\r
1079 for(i=list.begin(); i!=list.end(); i++)
\r
1081 i->PrintLine(&std::cout);
\r
1091 bool hosting = false;
\r
1092 char connect_name[100] = "";
\r
1094 std::cout<<"Address to connect to [empty = host a game]: ";
\r
1095 if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
\r
1097 std::cout<<g_settings.get("address")<<std::endl;
\r
1098 snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
\r
1102 std::cin.getline(connect_name, 100);
\r
1105 if(connect_name[0] == 0){
\r
1106 snprintf(connect_name, 100, "127.0.0.1");
\r
1111 std::cout<<"-> hosting"<<std::endl;
\r
1113 std::cout<<"-> "<<connect_name<<std::endl;
\r
1115 char playername[PLAYERNAME_SIZE] = "";
\r
1116 if(g_settings.get("name") != "")
\r
1118 snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
\r
1122 std::cout<<"Name of player: ";
\r
1123 std::cin.getline(playername, PLAYERNAME_SIZE);
\r
1125 std::cout<<"-> \""<<playername<<"\""<<std::endl;
\r
1128 Resolution selection
\r
1133 bool fullscreen = false;
\r
1135 if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
\r
1137 screenW = atoi(g_settings.get("screenW").c_str());
\r
1138 screenH = atoi(g_settings.get("screenH").c_str());
\r
1142 u16 resolutions[][3] = {
\r
1143 //W, H, fullscreen
\r
1154 u16 res_count = sizeof(resolutions)/sizeof(resolutions[0]);
\r
1156 for(u16 i=0; i<res_count; i++)
\r
1158 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1159 <<resolutions[i][1];
\r
1160 if(resolutions[i][2])
\r
1161 std::cout<<" fullscreen"<<std::endl;
\r
1163 std::cout<<" windowed"<<std::endl;
\r
1165 std::cout<<"Select a window resolution number [empty = 2]: ";
\r
1166 std::cin.getline(templine, 100);
\r
1169 if(templine[0] == 0)
\r
1172 r0 = atoi(templine);
\r
1174 if(r0 > res_count || r0 == 0)
\r
1180 std::cout<<(i+1)<<": "<<resolutions[i][0]<<"x"
\r
1181 <<resolutions[i][1];
\r
1182 if(resolutions[i][2])
\r
1183 std::cout<<" fullscreen"<<std::endl;
\r
1185 std::cout<<" windowed"<<std::endl;
\r
1188 screenW = resolutions[r0-1][0];
\r
1189 screenH = resolutions[r0-1][1];
\r
1190 fullscreen = resolutions[r0-1][2];
\r
1195 MyEventReceiver receiver;
\r
1197 video::E_DRIVER_TYPE driverType;
\r
1200 //driverType = video::EDT_DIRECT3D9; // Doesn't seem to work
\r
1201 driverType = video::EDT_OPENGL;
\r
1203 driverType = video::EDT_OPENGL;
\r
1206 // create device and exit if creation failed
\r
1208 IrrlichtDevice *device;
\r
1209 device = createDevice(driverType,
\r
1210 core::dimension2d<u32>(screenW, screenH),
\r
1211 16, fullscreen, false, false, &receiver);
\r
1213 /*device = createDevice(driverType,
\r
1214 core::dimension2d<u32>(screenW, screenH),
\r
1215 16, fullscreen, false, true, &receiver);*/
\r
1218 return 1; // could not create selected driver.
\r
1220 g_device = device;
\r
1222 device->setResizable(true);
\r
1224 if(g_settings.getBool("random_input"))
\r
1225 g_input = new RandomInputHandler();
\r
1227 g_input = new RealInputHandler(device, &receiver);
\r
1230 Continue initialization
\r
1233 video::IVideoDriver* driver = device->getVideoDriver();
\r
1234 // These make the textures not to show at all
\r
1235 //driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
\r
1236 //driver->setTextureCreationFlag(video::ETCF_OPTIMIZED_FOR_SPEED );
\r
1238 //driver->setMinHardwareBufferVertexCount(1);
\r
1240 scene::ISceneManager* smgr = device->getSceneManager();
\r
1242 gui::IGUIEnvironment* guienv = device->getGUIEnvironment();
\r
1243 gui::IGUISkin* skin = guienv->getSkin();
\r
1244 gui::IGUIFont* font = guienv->getFont("../data/fontlucida.png");
\r
1246 skin->setFont(font);
\r
1247 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1248 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1249 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1250 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1251 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1252 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1254 const wchar_t *text = L"Loading and connecting...";
\r
1255 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1256 core::dimension2d<u32> textd = font->getDimension(text);
\r
1257 std::cout<<DTIME<<"Text w="<<textd.Width<<" h="<<textd.Height<<std::endl;
\r
1258 // Have to add a bit to disable the text from word wrapping
\r
1259 //core::vector2d<s32> textsize(textd.Width+4, textd.Height);
\r
1260 core::vector2d<s32> textsize(300, textd.Height);
\r
1261 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1263 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1264 text, textrect, false, false);
\r
1265 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1267 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1268 guienv->drawAll();
\r
1269 driver->endScene();
\r
1272 Initialize material array
\r
1275 //video::SMaterial g_materials[MATERIALS_COUNT];
\r
1276 for(u16 i=0; i<MATERIALS_COUNT; i++)
\r
1278 g_materials[i].Lighting = false;
\r
1279 g_materials[i].BackfaceCulling = false;
\r
1281 const char *filename = g_material_filenames[i];
\r
1282 if(filename != NULL){
\r
1283 video::ITexture *t = driver->getTexture(filename);
\r
1285 std::cout<<DTIME<<"Texture could not be loaded: \""
\r
1286 <<filename<<"\""<<std::endl;
\r
1289 g_materials[i].setTexture(0, driver->getTexture(filename));
\r
1291 //g_materials[i].setFlag(video::EMF_TEXTURE_WRAP, video::ETC_REPEAT);
\r
1292 g_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1293 //g_materials[i].setFlag(video::EMF_ANISOTROPIC_FILTER, false);
\r
1294 //g_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1295 if(i == MATERIAL_WATER)
\r
1297 g_materials[i].MaterialType = video::EMT_TRANSPARENT_VERTEX_ALPHA;
\r
1298 //g_materials[i].MaterialType = video::EMT_TRANSPARENT_ADD_COLOR;
\r
1302 /*g_mesh_materials[0].setTexture(0, driver->getTexture("../data/water.png"));
\r
1303 g_mesh_materials[1].setTexture(0, driver->getTexture("../data/grass.png"));
\r
1304 g_mesh_materials[2].setTexture(0, driver->getTexture("../data/stone.png"));
\r
1305 for(u32 i=0; i<3; i++)
\r
1307 g_mesh_materials[i].Lighting = false;
\r
1308 g_mesh_materials[i].BackfaceCulling = false;
\r
1309 g_mesh_materials[i].setFlag(video::EMF_BILINEAR_FILTER, false);
\r
1310 g_mesh_materials[i].setFlag(video::EMF_FOG_ENABLE, true);
\r
1313 // Make a scope here for the client so that it gets removed
\r
1314 // before the irrlicht device
\r
1317 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1322 SharedPtr<Server> server;
\r
1324 server = new Server("../map", hm_params, map_params);
\r
1325 server->start(port);
\r
1332 // TODO: Get rid of the g_materials parameter or it's globalness
\r
1333 Client client(device, g_materials,
\r
1334 g_settings.getFloat("client_delete_unused_sectors_timeout"),
\r
1337 Address connect_address(0,0,0,0, port);
\r
1339 connect_address.Resolve(connect_name);
\r
1341 catch(ResolveError &e)
\r
1343 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1347 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1348 client.connect(connect_address);
\r
1351 while(client.connectedAndInitialized() == false)
\r
1354 if(server != NULL){
\r
1355 server->step(0.1);
\r
1360 catch(con::PeerNotFoundException &e)
\r
1362 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1367 Create the camera node
\r
1370 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
1371 0, // Camera parent
\r
1372 v3f(BS*100, BS*2, BS*100), // Look from
\r
1373 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
1377 if(camera == NULL)
\r
1380 video::SColor skycolor = video::SColor(255,90,140,200);
\r
1382 camera->setFOV(FOV_ANGLE);
\r
1384 // Just so big a value that everything rendered is visible
\r
1385 camera->setFarValue(100000*BS);
\r
1387 /*//f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1388 f32 range = BS*HEIGHTMAP_RANGE_NODES*0.9;
\r
1390 camera->setFarValue(range);
\r
1394 video::EFT_FOG_LINEAR,
\r
1402 f32 camera_yaw = 0; // "right/left"
\r
1403 f32 camera_pitch = 0; // "up/down"
\r
1405 gui_loadingtext->remove();
\r
1408 Add some gui stuff
\r
1411 // First line of debug text
\r
1412 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1414 core::rect<s32>(5, 5, 5+600, 5+textsize.Y),
\r
1416 // Second line of debug text
\r
1417 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1419 core::rect<s32>(5, 5+(textsize.Y+5)*1, 5+600, (5+textsize.Y)*2),
\r
1422 // At the middle of the screen
\r
1423 // Object infos are shown in this
\r
1424 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1426 core::rect<s32>(100, 70, 100+400, 70+(textsize.Y+5)),
\r
1429 // This is a copy of the inventory that the client's environment has
\r
1430 Inventory local_inventory(PLAYER_INVENTORY_SIZE);
\r
1432 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
1433 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
1436 Some statistics are collected in these
\r
1439 u32 scenetime = 0;
\r
1440 u32 endscenetime = 0;
\r
1448 virtual void sendText(std::string text) = 0;
\r
1451 struct TextDestSign : public TextDest
\r
1453 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
1455 m_blockpos = blockpos;
\r
1457 m_client = client;
\r
1459 void sendText(std::string text)
\r
1461 dstream<<"Changing text of a sign object: "
\r
1462 <<text<<std::endl;
\r
1463 m_client->sendSignText(m_blockpos, m_id, text);
\r
1471 TextDest *textbuf_dest = NULL;
\r
1473 //gui::IGUIWindow* input_window = NULL;
\r
1474 gui::IGUIStaticText* input_guitext = NULL;
\r
1480 bool first_loop_after_window_activation = true;
\r
1482 // Time is in milliseconds
\r
1483 // NOTE: getRealTime() without run()s causes strange problems in wine
\r
1484 // NOTE: Have to call run() between calls of this to update the timer
\r
1485 u32 lasttime = device->getTimer()->getTime();
\r
1487 while(device->run())
\r
1489 // Hilight boxes collected during the loop and displayed
\r
1490 core::list< core::aabbox3d<f32> > hilightboxes;
\r
1493 std::wstring infotext;
\r
1495 //TimeTaker //timer1("//timer1", device);
\r
1497 // Time of frame without fps limit
\r
1501 // not using getRealTime is necessary for wine
\r
1502 u32 time = device->getTimer()->getTime();
\r
1503 if(time > lasttime)
\r
1504 busytime_u32 = time - lasttime;
\r
1507 busytime = busytime_u32 / 1000.0;
\r
1510 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
1512 // Absolutelu necessary for wine!
\r
1519 //updateViewingRange(dtime, &client);
\r
1520 updateViewingRange(busytime, &client);
\r
1527 float fps_max = g_settings.getFloat("fps_max");
\r
1528 u32 frametime_min = 1000./fps_max;
\r
1530 if(busytime_u32 < frametime_min)
\r
1532 u32 sleeptime = frametime_min - busytime_u32;
\r
1533 device->sleep(sleeptime);
\r
1537 // Absolutelu necessary for wine!
\r
1541 Time difference calculation
\r
1543 f32 dtime; // in seconds
\r
1545 u32 time = device->getTimer()->getTime();
\r
1546 if(time > lasttime)
\r
1547 dtime = (time - lasttime) / 1000.0;
\r
1553 Time average and jitter calculation
\r
1556 static f32 dtime_avg1 = 0.0;
\r
1557 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
1558 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
1560 static f32 dtime_jitter1_max_sample = 0.0;
\r
1561 static f32 dtime_jitter1_max_fraction = 0.0;
\r
1563 static f32 jitter1_max = 0.0;
\r
1564 static f32 counter = 0.0;
\r
1565 if(dtime_jitter1 > jitter1_max)
\r
1566 jitter1_max = dtime_jitter1;
\r
1571 dtime_jitter1_max_sample = jitter1_max;
\r
1572 dtime_jitter1_max_fraction
\r
1573 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
1574 jitter1_max = 0.0;
\r
1577 Control freetime ratio
\r
1579 /*if(dtime_jitter1_max_fraction > DTIME_JITTER_MAX_FRACTION)
\r
1581 if(g_freetime_ratio < FREETIME_RATIO_MAX)
\r
1582 g_freetime_ratio += 0.01;
\r
1586 if(g_freetime_ratio > FREETIME_RATIO_MIN)
\r
1587 g_freetime_ratio -= 0.01;
\r
1593 Busytime average and jitter calculation
\r
1596 static f32 busytime_avg1 = 0.0;
\r
1597 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
1598 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
1600 static f32 busytime_jitter1_max_sample = 0.0;
\r
1601 static f32 busytime_jitter1_min_sample = 0.0;
\r
1603 static f32 jitter1_max = 0.0;
\r
1604 static f32 jitter1_min = 0.0;
\r
1605 static f32 counter = 0.0;
\r
1606 if(busytime_jitter1 > jitter1_max)
\r
1607 jitter1_max = busytime_jitter1;
\r
1608 if(busytime_jitter1 < jitter1_min)
\r
1609 jitter1_min = busytime_jitter1;
\r
1611 if(counter > 0.0){
\r
1613 busytime_jitter1_max_sample = jitter1_max;
\r
1614 busytime_jitter1_min_sample = jitter1_min;
\r
1615 jitter1_max = 0.0;
\r
1616 jitter1_min = 0.0;
\r
1621 Debug info for client
\r
1624 static float counter = 0.0;
\r
1629 client.printDebugInfo(std::cout);
\r
1634 Input handler step()
\r
1636 g_input->step(dtime);
\r
1647 Player speed control
\r
1650 if(g_game_focused)
\r
1657 bool a_superspeed,
\r
1660 PlayerControl control(
\r
1661 g_input->isKeyDown(irr::KEY_KEY_W),
\r
1662 g_input->isKeyDown(irr::KEY_KEY_S),
\r
1663 g_input->isKeyDown(irr::KEY_KEY_A),
\r
1664 g_input->isKeyDown(irr::KEY_KEY_D),
\r
1665 g_input->isKeyDown(irr::KEY_SPACE),
\r
1666 g_input->isKeyDown(irr::KEY_KEY_2),
\r
1670 client.setPlayerControl(control);
\r
1674 // Set every key to inactive
\r
1675 PlayerControl control;
\r
1676 client.setPlayerControl(control);
\r
1681 Process environment
\r
1685 //TimeTaker timer("client.step(dtime)", device);
\r
1686 client.step(dtime);
\r
1687 //client.step(dtime_avg1);
\r
1690 if(server != NULL)
\r
1692 //TimeTaker timer("server->step(dtime)", device);
\r
1693 server->step(dtime);
\r
1696 v3f player_position = client.getPlayerPosition();
\r
1698 //TimeTaker //timer2("//timer2", device);
\r
1701 Mouse and camera control
\r
1704 if(device->isWindowActive() && g_game_focused)
\r
1706 device->getCursorControl()->setVisible(false);
\r
1708 if(first_loop_after_window_activation){
\r
1709 //std::cout<<"window active, first loop"<<std::endl;
\r
1710 first_loop_after_window_activation = false;
\r
1713 s32 dx = g_input->getMousePos().X - 320;
\r
1714 s32 dy = g_input->getMousePos().Y - 240;
\r
1715 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
1716 camera_yaw -= dx*0.2;
\r
1717 camera_pitch += dy*0.2;
\r
1718 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
1719 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
1721 g_input->setMousePos(320, 240);
\r
1724 device->getCursorControl()->setVisible(true);
\r
1726 //std::cout<<"window inactive"<<std::endl;
\r
1727 first_loop_after_window_activation = true;
\r
1730 camera_yaw = wrapDegrees(camera_yaw);
\r
1731 camera_pitch = wrapDegrees(camera_pitch);
\r
1733 v3f camera_direction = v3f(0,0,1);
\r
1734 camera_direction.rotateYZBy(camera_pitch);
\r
1735 camera_direction.rotateXZBy(camera_yaw);
\r
1737 v3f camera_position =
\r
1738 player_position + v3f(0, BS+BS/2, 0);
\r
1740 camera->setPosition(camera_position);
\r
1741 // *100.0 helps in large map coordinates
\r
1742 camera->setTarget(camera_position + camera_direction * 100.0);
\r
1744 if(FIELD_OF_VIEW_TEST){
\r
1745 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1746 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
1749 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
1750 //TimeTaker timer("client.updateCamera", device);
\r
1751 client.updateCamera(camera_position, camera_direction);
\r
1755 //TimeTaker //timer3("//timer3", device);
\r
1758 Calculate what block is the crosshair pointing to
\r
1761 //u32 t1 = device->getTimer()->getRealTime();
\r
1763 //f32 d = 4; // max. distance
\r
1764 f32 d = 4; // max. distance
\r
1765 core::line3d<f32> shootline(camera_position,
\r
1766 camera_position + camera_direction * BS * (d+1));
\r
1768 MapBlockObject *selected_object = client.getSelectedObject
\r
1769 (d*BS, camera_position, shootline);
\r
1771 if(selected_object != NULL)
\r
1773 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
1775 core::aabbox3d<f32> box_on_map
\r
1776 = selected_object->getSelectionBoxOnMap();
\r
1778 hilightboxes.push_back(box_on_map);
\r
1780 infotext = narrow_to_wide(selected_object->infoText());
\r
1782 if(g_input->getLeftClicked())
\r
1784 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
1785 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
1786 selected_object->getId(), g_selected_item);
\r
1788 else if(g_input->getRightClicked())
\r
1790 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
1792 Check if we want to modify the object ourselves
\r
1794 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
1796 dstream<<"Sign object right-clicked"<<std::endl;
\r
1800 input_guitext = guienv->addStaticText(L"",
\r
1801 core::rect<s32>(150,100,350,120),
\r
1803 false, // wordwrap?
\r
1806 input_guitext->setDrawBackground(true);
\r
1808 g_text_buffer = L"";
\r
1809 g_text_buffer_accepted = false;
\r
1810 textbuf_dest = new TextDestSign(
\r
1811 selected_object->getBlock()->getPos(),
\r
1812 selected_object->getId(),
\r
1816 Otherwise pass the event to the server as-is
\r
1820 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
1821 selected_object->getId(), g_selected_item);
\r
1825 else // selected_object == NULL
\r
1828 bool nodefound = false;
\r
1830 v3s16 neighbourpos;
\r
1831 core::aabbox3d<f32> nodefacebox;
\r
1832 f32 mindistance = BS * 1001;
\r
1834 v3s16 pos_i = floatToInt(player_position);
\r
1836 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
1840 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
1841 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
1842 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
1843 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
1844 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
1845 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
1847 for(s16 y = ystart; y <= yend; y++){
\r
1848 for(s16 z = zstart; z <= zend; z++){
\r
1849 for(s16 x = xstart; x <= xend; x++)
\r
1852 if(material_pointable(client.getNode(v3s16(x,y,z)).d) == false)
\r
1854 }catch(InvalidPositionException &e){
\r
1859 v3f npf = intToFloat(np);
\r
1863 v3s16 directions[6] = {
\r
1864 v3s16(0,0,1), // back
\r
1865 v3s16(0,1,0), // top
\r
1866 v3s16(1,0,0), // right
\r
1872 for(u16 i=0; i<6; i++){
\r
1874 v3f dir_f = v3f(directions[i].X,
\r
1875 directions[i].Y, directions[i].Z);
\r
1876 v3f centerpoint = npf + dir_f * BS/2;
\r
1878 (centerpoint - camera_position).getLength();
\r
1880 if(distance < mindistance){
\r
1881 //std::cout<<DTIME<<"for centerpoint=("<<centerpoint.X<<","<<centerpoint.Y<<","<<centerpoint.Z<<"): distance < mindistance"<<std::endl;
\r
1882 //std::cout<<DTIME<<"npf=("<<npf.X<<","<<npf.Y<<","<<npf.Z<<")"<<std::endl;
\r
1883 core::CMatrix4<f32> m;
\r
1884 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
1886 // This is the back face
\r
1887 v3f corners[2] = {
\r
1888 v3f(BS/2, BS/2, BS/2),
\r
1889 v3f(-BS/2, -BS/2, BS/2+d)
\r
1892 for(u16 j=0; j<2; j++){
\r
1893 m.rotateVect(corners[j]);
\r
1894 corners[j] += npf;
\r
1895 //std::cout<<DTIME<<"box corners["<<j<<"]: ("<<corners[j].X<<","<<corners[j].Y<<","<<corners[j].Z<<")"<<std::endl;
\r
1898 //core::aabbox3d<f32> facebox(corners[0],corners[1]);
\r
1899 core::aabbox3d<f32> facebox(corners[0]);
\r
1900 facebox.addInternalPoint(corners[1]);
\r
1902 if(facebox.intersectsWithLine(shootline)){
\r
1905 neighbourpos = np + directions[i];
\r
1906 mindistance = distance;
\r
1907 nodefacebox = facebox;
\r
1915 //std::cout<<DTIME<<"nodefound == true"<<std::endl;
\r
1916 //std::cout<<DTIME<<"nodepos=("<<nodepos.X<<","<<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
1917 //std::cout<<DTIME<<"neighbourpos=("<<neighbourpos.X<<","<<neighbourpos.Y<<","<<neighbourpos.Z<<")"<<std::endl;
\r
1919 static v3s16 nodepos_old(-1,-1,-1);
\r
1920 if(nodepos != nodepos_old){
\r
1921 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
1922 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
1923 nodepos_old = nodepos;
\r
1925 /*wchar_t positiontext[20];
\r
1926 swprintf(positiontext, 20, L"(%i,%i,%i)",
\r
1927 nodepos.X, nodepos.Y, nodepos.Z);
\r
1928 positiontextgui->setText(positiontext);*/
\r
1931 hilightboxes.push_back(nodefacebox);
\r
1933 if(g_input->getLeftClicked())
\r
1935 //std::cout<<DTIME<<"Removing node"<<std::endl;
\r
1936 //client.removeNode(nodepos);
\r
1937 std::cout<<DTIME<<"Ground left-clicked"<<std::endl;
\r
1938 client.clickGround(0, nodepos, neighbourpos, g_selected_item);
\r
1940 if(g_input->getRightClicked())
\r
1942 //std::cout<<DTIME<<"Placing node"<<std::endl;
\r
1943 //client.addNodeFromInventory(neighbourpos, g_selected_item);
\r
1944 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
1945 client.clickGround(1, nodepos, neighbourpos, g_selected_item);
\r
1949 //std::cout<<DTIME<<"nodefound == false"<<std::endl;
\r
1950 //positiontextgui->setText(L"");
\r
1953 } // selected_object == NULL
\r
1955 g_input->resetLeftClicked();
\r
1956 g_input->resetRightClicked();
\r
1959 Calculate stuff for drawing
\r
1962 v2u32 screensize = driver->getScreenSize();
\r
1963 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
1965 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
1968 Update gui stuff (0ms)
\r
1971 //TimeTaker guiupdatetimer("Gui updating", device);
\r
1974 wchar_t temptext[100];
\r
1976 static float drawtime_avg = 0;
\r
1977 drawtime_avg = drawtime_avg * 0.98 + (float)drawtime*0.02;
\r
1978 static float scenetime_avg = 0;
\r
1979 scenetime_avg = scenetime_avg * 0.98 + (float)scenetime*0.02;
\r
1980 static float endscenetime_avg = 0;
\r
1981 endscenetime_avg = endscenetime_avg * 0.98 + (float)endscenetime*0.02;
\r
1983 swprintf(temptext, 100, L"Minetest-c55 ("
\r
1985 L", R: range_all=%i"
\r
1987 L" drawtime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
1989 g_viewing_range_all,
\r
1995 guitext->setText(temptext);
\r
1999 wchar_t temptext[100];
\r
2000 /*swprintf(temptext, 100,
\r
2002 L"% .3f < btime_jitter < % .3f"
\r
2003 L", dtime_jitter = % .1f %%"
\r
2004 //L", ftime_ratio = % .3f"
\r
2006 busytime_jitter1_min_sample,
\r
2007 busytime_jitter1_max_sample,
\r
2008 dtime_jitter1_max_fraction * 100.0
\r
2009 //g_freetime_ratio
\r
2011 swprintf(temptext, 100,
\r
2012 L"(% .1f, % .1f, % .1f)"
\r
2013 L" (% .3f < btime_jitter < % .3f"
\r
2014 L", dtime_jitter = % .1f %%)",
\r
2015 player_position.X/BS,
\r
2016 player_position.Y/BS,
\r
2017 player_position.Z/BS,
\r
2018 busytime_jitter1_min_sample,
\r
2019 busytime_jitter1_max_sample,
\r
2020 dtime_jitter1_max_fraction * 100.0
\r
2023 guitext2->setText(temptext);
\r
2027 /*wchar_t temptext[100];
\r
2028 swprintf(temptext, 100,
\r
2029 SWPRINTF_CHARSTRING,
\r
2030 infotext.substr(0,99).c_str()
\r
2033 guitext_info->setText(temptext);*/
\r
2035 guitext_info->setText(infotext.c_str());
\r
2042 static u16 old_selected_item = 65535;
\r
2043 if(client.getLocalInventoryUpdated()
\r
2044 || g_selected_item != old_selected_item)
\r
2046 old_selected_item = g_selected_item;
\r
2047 //std::cout<<"Updating local inventory"<<std::endl;
\r
2048 client.getLocalInventory(local_inventory);
\r
2049 quick_inventory->setSelection(g_selected_item);
\r
2050 quick_inventory->update();
\r
2053 if(input_guitext != NULL)
\r
2055 /*wchar_t temptext[100];
\r
2056 swprintf(temptext, 100,
\r
2057 SWPRINTF_CHARSTRING,
\r
2058 g_text_buffer.substr(0,99).c_str()
\r
2060 input_guitext->setText(g_text_buffer.c_str());
\r
2066 if(input_guitext != NULL && g_text_buffer_accepted)
\r
2068 input_guitext->remove();
\r
2069 input_guitext = NULL;
\r
2071 if(textbuf_dest != NULL)
\r
2073 std::string text = wide_to_narrow(g_text_buffer);
\r
2074 dstream<<"Sending text: "<<text<<std::endl;
\r
2075 textbuf_dest->sendText(text);
\r
2076 delete textbuf_dest;
\r
2077 textbuf_dest = NULL;
\r
2083 //guiupdatetimer.stop();
\r
2089 TimeTaker drawtimer("Drawing", device);
\r
2092 Background color is choosen based on whether the player is
\r
2093 much beyond the initial ground level
\r
2095 /*video::SColor bgcolor;
\r
2096 v3s16 p0 = Map::floatToInt(player_position);
\r
2097 // Does this make short random delays?
\r
2098 // NOTE: no need for this, sky doesn't show underground with
\r
2100 bool is_underground = client.isNodeUnderground(p0);
\r
2101 //bool is_underground = false;
\r
2102 if(is_underground == false)
\r
2103 bgcolor = video::SColor(255,90,140,200);
\r
2105 bgcolor = video::SColor(255,0,0,0);*/
\r
2107 //video::SColor bgcolor = video::SColor(255,90,140,200);
\r
2108 video::SColor bgcolor = skycolor;
\r
2111 driver->beginScene(true, true, bgcolor);
\r
2115 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2118 TimeTaker timer("smgr", device);
\r
2120 scenetime = timer.stop(true);
\r
2124 //TimeTaker timer9("auxiliary drawings", device);
\r
2127 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2128 displaycenter + core::vector2d<s32>(10,0),
\r
2129 video::SColor(255,255,255,255));
\r
2130 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
2131 displaycenter + core::vector2d<s32>(0,10),
\r
2132 video::SColor(255,255,255,255));
\r
2135 //TimeTaker //timer10("//timer10", device);
\r
2137 video::SMaterial m;
\r
2139 m.Lighting = false;
\r
2140 driver->setMaterial(m);
\r
2142 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2144 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2145 i != hilightboxes.end(); i++)
\r
2147 /*std::cout<<"hilightbox min="
\r
2148 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2150 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2152 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2158 //TimeTaker //timer11("//timer11", device);
\r
2164 guienv->drawAll();
\r
2168 TimeTaker timer("endScene", device);
\r
2169 driver->endScene();
\r
2170 endscenetime = timer.stop(true);
\r
2173 drawtime = drawtimer.stop(true);
\r
2179 static s16 lastFPS = 0;
\r
2180 //u16 fps = driver->getFPS();
\r
2181 u16 fps = (1.0/dtime_avg1);
\r
2183 if (lastFPS != fps)
\r
2185 core::stringw str = L"Minetest [";
\r
2186 str += driver->getName();
\r
2190 device->setWindowCaption(str.c_str());
\r
2196 device->yield();*/
\r
2199 } // client is deleted at this point
\r
2204 In the end, delete the Irrlicht device.
\r
2209 catch(con::PeerNotFoundException &e)
\r
2211 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
2213 #if CATCH_UNHANDLED_EXCEPTIONS
\r
2215 This is what has to be done in every thread to get suitable debug info
\r
2217 catch(std::exception &e)
\r
2219 dstream<<std::endl<<DTIME<<"An unhandled exception occurred: "
\r
2220 <<e.what()<<std::endl;
\r
2225 debugstreams_deinit();
\r