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
26 - It is not a memory leak but some kind of a buffer.
\r
28 NOTE: iostream.imbue(std::locale("C")) is very slow
\r
29 NOTE: Global locale is now set at initialization
\r
31 SUGG: Fix address to be ipv6 compatible
\r
33 NOTE: When a new sector is generated, it may change the ground level
\r
34 of it's and it's neighbors border that two blocks that are
\r
35 above and below each other and that are generated before and
\r
36 after the sector heightmap generation (order doesn't matter),
\r
37 can have a small gap between each other at the border.
\r
38 SUGG: Use same technique for sector heightmaps as what we're
\r
39 using for UnlimitedHeightmap? (getting all neighbors
\r
42 SUGG: Transfer more blocks in a single packet
\r
43 SUGG: A blockdata combiner class, to which blocks are added and at
\r
44 destruction it sends all the stuff in as few packets as possible.
\r
46 SUGG: If player is on ground, mainly fetch ground-level blocks
\r
47 SUGG: Fetch stuff mainly from the viewing direction
\r
49 SUGG: Expose Connection's seqnums and ACKs to server and client.
\r
50 - This enables saving many packets and making a faster connection
\r
51 - This also enables server to check if client has received the
\r
52 most recent block sent, for example.
\r
53 SUGG: Add a sane bandwidth throttling system to Connection
\r
55 SUGG: More fine-grained control of client's dumping of blocks from
\r
57 - ...What does this mean in the first place?
\r
59 SUGG: A map editing mode (similar to dedicated server mode)
\r
61 SUGG: Add a time value to the param of footstepped grass and check it
\r
62 against a global timer when a block is accessed, to make old
\r
65 SUGG: Make a copy of close-range environment on client for showing
\r
66 on screen, with minimal mutexes to slow down the main loop
\r
68 SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize
\r
69 it by sending more stuff in a single packet.
\r
70 - Add a packet queue to RemoteClient, from which packets will be
\r
71 combined with object data packets
\r
72 - This is not exactly trivial: the object data packets are
\r
73 sometimes very big by themselves
\r
75 SUGG: Split MapBlockObject serialization to to-client and to-disk
\r
76 - This will allow saving ages of rats on disk but not sending
\r
79 SUGG: MovingObject::move and Player::move are basically the same.
\r
82 SUGG: Implement a "Fast check queue" (a queue with a map for checking
\r
83 if something is already in it)
\r
84 - Use it in active block queue in water flowing
\r
86 SUGG: Precalculate lighting translation table at runtime (at startup)
\r
87 - This is not doable because it is currently hand-made and not
\r
88 based on some mathematical function.
\r
90 SUGG: A version number to blocks, which increments when the block is
\r
91 modified (node add/remove, water update, lighting update)
\r
92 - This can then be used to make sure the most recent version of
\r
93 a block has been sent to client
\r
95 SUGG: Make the amount of blocks sending to client and the total
\r
96 amount of blocks dynamically limited. Transferring blocks is the
\r
97 main network eater of this system, so it is the one that has
\r
98 to be throttled so that RTTs stay low.
\r
100 SUGG: Meshes of blocks could be split into 6 meshes facing into
\r
101 different directions and then only those drawn that need to be
\r
102 - Also an 1-dimensional tile map would be nice probably
\r
107 - Aim for something like controlling a single dwarf in Dwarf Fortress
\r
109 - The player could go faster by a crafting a boat, or riding an animal
\r
111 - Random NPC traders. what else?
\r
116 Build system / running:
\r
117 -----------------------
\r
119 NOTE: The following fixme is not apparently valid, and it does work.
\r
120 FIXME: Graphical mode seems to segfault with Irrlicht 1.7.1 on 64-bit
\r
122 - http://pastebin.no/32bo
\r
123 - Might be just a bad build, too
\r
124 - Doesn't affect Irrlicht 1.7.2 or 32-bit 1.7.1. (Arch/Debian)
\r
125 - A similar error occurs when getTexture is called from a thread
\r
126 when the texture has not been already loaded from disk:
\r
127 http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?p=68830
\r
129 FIXME: Some network errors on Windows that cause local game to not work
\r
130 - See siggjen's emails.
\r
132 Networking and serialization:
\r
133 -----------------------------
\r
135 TODO: Get rid of GotSplitPacketException
\r
140 TODO: Add gui option to remove map
\r
142 TODO: Configuration menu, at least for keys
\r
147 TODO: Optimize day/night mesh updating somehow
\r
148 - create copies of all textures for all lighting values and only
\r
149 change texture for material?
\r
150 - Umm... the collecting of the faces is the slow part
\r
151 -> what about just changing the color values of the existing
\r
152 meshbuffers? It should go quite fast.
\r
153 - This is not easy; There'd need to be a buffer somewhere
\r
154 that would contain the night and day lighting values.
\r
155 - Actually if FastFaces would be stored, they could
\r
158 FEATURE: Combine MapBlock's face caches to so big pieces that VBO
\r
160 - That is >500 vertices
\r
161 - This is not easy; all the MapBlocks close to the player would
\r
162 still need to be drawn separately and combining the blocks
\r
163 would have to happen in a background thread
\r
165 TODO: Make fetching sector's blocks more efficient when rendering
\r
166 sectors that have very large amounts of blocks (on client)
\r
167 - Is this necessary at all?
\r
169 TODO: Flowing water animation
\r
171 FIXME(FIXED): The new texture stuff is slow on wine
\r
172 - A basic grassy ground block takes 20-40ms
\r
173 - A bit more complicated block can take 270ms
\r
174 - On linux, a similar one doesn't take long at all (14ms)
\r
175 - It is NOT a bad std::string implementation of MSVC.
\r
176 - Can take up to 200ms? Is it when loading textures or always?
\r
177 - Updating excess amount of meshes when making footprints is too
\r
178 slow. It has to be fixed.
\r
179 -> implement Map::updateNodeMeshes()
\r
181 * Optimize TileSpec to only contain a reference number that
\r
182 is fast to compare, which refers to a cached string, or
\r
183 * Make TextureSpec for using instead of strings
\r
185 FIXME(FIXED): A lock condition is possible:
\r
186 1) MapBlock::updateMesh() is called from client asynchronously:
\r
187 - AsyncProcessData() -> Map::updateMeshes()
\r
188 2) Asynchronous locks m_temp_mods_mutex
\r
189 3) MapBlock::updateMesh() is called from client synchronously:
\r
190 - Client::step() -> Environment::step()
\r
191 4) Synchronous starts waiting for m_temp_mods_mutex
\r
192 5) Asynchronous calls getTexture, which starts waiting for main thread
\r
197 TODO: Make the video backend selectable
\r
202 TODO: Untie client network operations from framerate
\r
203 - Needs some input queues or something
\r
204 - Not really necessary?
\r
206 TODO: Make morning and evening shorter
\r
208 TODO: Don't update all meshes always on single node changes, but
\r
209 check which ones should be updated
\r
210 - implement Map::updateNodeMeshes()
\r
215 TODO: When player dies, throw items on map
\r
217 TODO: Make an option to the server to disable building and digging near
\r
218 the starting position
\r
220 TODO: Save players with inventories to disk
\r
221 TODO: Players to be saved as text in map/players/<name>
\r
223 TODO: Copy the text of the last picked sign to inventory in creative
\r
226 TODO: Check what goes wrong with caching map to disk (Kray)
\r
229 TODO: When server sees that client is removing an inexistent block or
\r
230 adding a block to an existent position, resend the MapBlock.
\r
235 TODO: Better handling of objects and mobs
\r
237 - There has to be some way to do it with less messy code
\r
238 - Make separate classes for client and server
\r
239 - Client should not discriminate between blocks, server should
\r
240 - Make other players utilize the same framework
\r
241 - This is also needed for objects that don't get sent to client
\r
242 but are used for triggers etc
\r
244 TODO: There has to be some better way to handle static objects than to
\r
245 send them all the time. This affects signs and item objects.
\r
246 SUGG: Signs could be done in the same way as torches. For this, blocks
\r
247 need an additional metadata field for the texts
\r
248 - This is also needed for item container chests
\r
250 Block object server side:
\r
251 - A "near blocks" buffer, in which some nearby blocks are stored.
\r
252 - For all blocks in the buffer, objects are stepped(). This
\r
253 means they are active.
\r
254 - TODO: A global active buffer is needed for the server
\r
255 - TODO: A timestamp to blocks
\r
256 - TODO: All blocks going in and out of the buffer are recorded.
\r
257 - TODO: For outgoing blocks, timestamp is written.
\r
258 - TODO: For incoming blocks, time difference is calculated and
\r
259 objects are stepped according to it.
\r
264 NOTE: There are some lighting-related todos and fixmes in
\r
265 ServerMap::emergeBlock. And there always will be. 8)
\r
267 TODO: Mineral and ground material properties
\r
268 - This way mineral ground toughness can be calculated with just
\r
269 some formula, as well as tool strengths
\r
271 TODO: Change AttributeList to split the area into smaller sections so
\r
272 that searching won't be as heavy.
\r
274 TODO: Remove HMParams
\r
276 TODO: Flowing water to actually contain flow direction information
\r
278 TODO: Remove duplicate lighting implementation from Map (leave
\r
279 VoxelManipulator, which is faster)
\r
281 FEATURE: Map generator version 2
\r
282 - Create surface areas based on central points; a given point's
\r
283 area type is given by the nearest central point
\r
284 - Separate points for heightmap, caves, plants and minerals?
\r
285 - Flat land, mountains, forest, jungle
\r
287 - There could be a certain height (to which mountains only reach)
\r
288 where some minerals are found
\r
289 - Create a system that allows a huge amount of different "map
\r
290 generator modules/filters"
\r
292 FEATURE: The map could be generated procedually:
\r
293 - This would need the map to be generated in larger pieces
\r
294 - How large? How do they connect to each other?
\r
295 - It has to be split vertically also
\r
296 - Lighting would not have to be necessarily calculated until
\r
297 the blocks are actually needed - it would be quite fast
\r
298 - Something like 64*64*16 MapBlocks?
\r
299 - No, MapSectors. And as much as it is efficient to do,
\r
300 64x64 might be too much.
\r
301 - FIXME: This is currently halfway done and the generator is
\r
303 * Make the stone level with a heightmap
\r
304 * Carve out stuff in the stone
\r
305 * Dump dirt all around, and simulate it falling off steep
\r
307 * Erosion simulation at map generation time
\r
308 - Simulate water flows, which would carve out dirt fast and
\r
309 then turn stone into gravel and sand and relocate it.
\r
310 - How about relocating minerals, too? Coal and gold in
\r
311 downstream sand and gravel would be kind of cool
\r
312 - This would need a better way of handling minerals, mainly
\r
313 to have mineral content as a separate field. the first
\r
314 parameter field is free for this.
\r
315 - Simulate rock falling from cliffs when water has removed
\r
316 enough solid rock from the bottom
\r
318 Doing now (most important at the top):
\r
319 --------------------------------------
\r
323 === Stuff to do before release
\r
324 * Save map seed to a metafile (with version information)
\r
325 - map/meta.txt, which should contain only plain text, something like this:
\r
326 seed = O7+BZT9Vk/iVYiBlZ2dsb6zemp4xdGVysJqYmNt2X+MQ+Kg1
\r
330 - Compressed bunch of data... um, actually no.
\r
331 - Make a directory for every chunk instead, which contains
\r
333 * Save chunk metadata on disk
\r
334 * Make server find the spawning place from the real map data, not from
\r
336 - But the changing borders of chunk have to be avoided, because
\r
337 there is time to generate only one chunk.
\r
338 * Make the generator to run in background and not blocking block
\r
339 placement and transfer
\r
340 * only_from_disk might not work anymore - check and fix it.
\r
342 === Stuff to do after release
\r
343 * Add some kind of erosion and other stuff that now is possible
\r
344 * Make client to fetch stuff asynchronously
\r
345 - Needs method SyncProcessData
\r
346 * Fix the problem with the server constantly saving one or a few
\r
347 blocks? List the first saved block, maybe it explains.
\r
348 - It is probably caused by oscillating water
\r
349 * Water doesn't start flowing after map generation like it should
\r
350 - Are there still problems?
\r
351 * Better water generation (spread it to underwater caverns but don't
\r
352 fill dungeons that don't touch outside air)
\r
353 * When generating a chunk and the neighboring chunk doesn't have mud
\r
354 and stuff yet and the ground is fairly flat, the mud will flow to
\r
355 the other chunk making nasty straight walls when the other chunk
\r
356 is generated. Fix it.
\r
357 * Make a small history check to transformLiquids to detect and log
\r
358 continuous oscillations, in such detail that they can be fixed.
\r
360 ======================================================================
\r
365 Setting this to 1 enables a special camera mode that forces
\r
366 the renderers to think that the camera statically points from
\r
367 the starting place to a static direction.
\r
369 This allows one to move around with the player and see what
\r
370 is actually drawn behind solid things and behind the player.
\r
372 #define FIELD_OF_VIEW_TEST 0
\r
376 #pragma message ("Disabling unit tests")
\r
378 #warning "Disabling unit tests"
\r
380 // Disable unit tests
\r
381 #define ENABLE_TESTS 0
\r
383 // Enable unit tests
\r
384 #define ENABLE_TESTS 1
\r
388 #pragma comment(lib, "Irrlicht.lib")
\r
389 //#pragma comment(lib, "jthread.lib")
\r
390 #pragma comment(lib, "zlibwapi.lib")
\r
391 #pragma comment(lib, "Shell32.lib")
\r
392 // This would get rid of the console window
\r
393 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
396 #include <iostream>
\r
398 #include <jmutexautolock.h>
\r
399 #include <locale.h>
\r
400 #include "common_irrlicht.h"
\r
403 #include "player.h"
\r
406 #include "environment.h"
\r
407 #include "server.h"
\r
408 #include "client.h"
\r
409 #include "serialization.h"
\r
410 #include "constants.h"
\r
411 #include "strfnd.h"
\r
412 #include "porting.h"
\r
413 #include "irrlichtwrapper.h"
\r
414 #include "gettime.h"
\r
415 #include "porting.h"
\r
416 #include "guiPauseMenu.h"
\r
417 #include "guiInventoryMenu.h"
\r
418 #include "guiTextInputMenu.h"
\r
419 #include "materials.h"
\r
420 #include "guiMessageMenu.h"
\r
421 #include "filesys.h"
\r
422 #include "config.h"
\r
423 #include "guiMainMenu.h"
\r
424 #include "mineral.h"
\r
426 IrrlichtWrapper *g_irrlicht;
\r
428 MapDrawControl draw_control;
\r
432 These are loaded from the config file.
\r
435 Settings g_settings;
\r
437 extern void set_default_settings();
\r
443 IrrlichtDevice *g_device = NULL;
\r
444 Client *g_client = NULL;
\r
450 gui::IGUIEnvironment* guienv = NULL;
\r
451 gui::IGUIStaticText *guiroot = NULL;
\r
453 class MainMenuManager : public IMenuManager
\r
456 virtual void createdMenu(GUIModalMenu *menu)
\r
458 for(core::list<GUIModalMenu*>::Iterator
\r
459 i = m_stack.begin();
\r
460 i != m_stack.end(); i++)
\r
462 assert(*i != menu);
\r
465 if(m_stack.size() != 0)
\r
466 (*m_stack.getLast())->setVisible(false);
\r
467 m_stack.push_back(menu);
\r
470 virtual void deletingMenu(GUIModalMenu *menu)
\r
472 // Remove all entries if there are duplicates
\r
473 bool removed_entry;
\r
475 removed_entry = false;
\r
476 for(core::list<GUIModalMenu*>::Iterator
\r
477 i = m_stack.begin();
\r
478 i != m_stack.end(); i++)
\r
483 removed_entry = true;
\r
487 }while(removed_entry);
\r
489 /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
\r
490 assert(*i == menu);
\r
491 m_stack.erase(i);*/
\r
493 if(m_stack.size() != 0)
\r
494 (*m_stack.getLast())->setVisible(true);
\r
499 return m_stack.size();
\r
502 core::list<GUIModalMenu*> m_stack;
\r
505 MainMenuManager g_menumgr;
\r
507 bool noMenuActive()
\r
509 return (g_menumgr.menuCount() == 0);
\r
512 bool g_disconnect_requested = false;
\r
514 class MainGameCallback : public IGameCallback
\r
517 virtual void exitToOS()
\r
519 g_device->closeDevice();
\r
522 virtual void disconnect()
\r
524 g_disconnect_requested = true;
\r
528 MainGameCallback g_gamecallback;
\r
530 // Inventory actions from the menu are buffered here before sending
\r
531 Queue<InventoryAction*> inventory_action_queue;
\r
532 // This is a copy of the inventory that the client's environment has
\r
533 Inventory local_inventory;
\r
535 u16 g_selected_item = 0;
\r
542 std::ostream *dout_con_ptr = &dummyout;
\r
543 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
544 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
545 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
546 //std::ostream *dout_con_ptr = &dstream;
\r
547 //std::ostream *derr_con_ptr = &dstream;
\r
550 std::ostream *dout_server_ptr = &dstream;
\r
551 std::ostream *derr_server_ptr = &dstream;
\r
554 std::ostream *dout_client_ptr = &dstream;
\r
555 std::ostream *derr_client_ptr = &dstream;
\r
558 gettime.h implementation
\r
564 Use irrlicht because it is more precise than porting.h's
\r
567 if(g_irrlicht == NULL)
\r
569 return g_irrlicht->getTime();
\r
576 struct TextDestSign : public TextDest
\r
578 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
580 m_blockpos = blockpos;
\r
584 void gotText(std::wstring text)
\r
586 std::string ntext = wide_to_narrow(text);
\r
587 dstream<<"Changing text of a sign object: "
\r
588 <<ntext<<std::endl;
\r
589 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
597 struct TextDestChat : public TextDest
\r
599 TextDestChat(Client *client)
\r
603 void gotText(std::wstring text)
\r
605 m_client->sendChatMessage(text);
\r
606 m_client->addChatMessage(text);
\r
612 class MyEventReceiver : public IEventReceiver
\r
615 // This is the one method that we have to implement
\r
616 virtual bool OnEvent(const SEvent& event)
\r
619 React to nothing here if a menu is active
\r
621 if(noMenuActive() == false)
\r
627 // Remember whether each key is down or up
\r
628 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
630 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
632 if(event.KeyInput.PressedDown)
\r
634 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
640 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
642 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
644 dstream<<DTIME<<"MyEventReceiver: "
\r
645 <<"Launching pause menu"<<std::endl;
\r
646 // It will delete itself by itself
\r
647 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
648 &g_menumgr))->drop();
\r
651 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
653 dstream<<DTIME<<"MyEventReceiver: "
\r
654 <<"Launching inventory"<<std::endl;
\r
655 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
656 &local_inventory, &inventory_action_queue,
\r
657 &g_menumgr))->drop();
\r
660 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
662 TextDest *dest = new TextDestChat(g_client);
\r
664 (new GUITextInputMenu(guienv, guiroot, -1,
\r
670 // Material selection
\r
671 /*if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
673 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
676 g_selected_item = 0;
\r
677 dstream<<DTIME<<"Selected item: "
\r
678 <<g_selected_item<<std::endl;
\r
681 if(event.KeyInput.Key >= irr::KEY_KEY_0
\r
682 && event.KeyInput.Key <= irr::KEY_KEY_9)
\r
684 u16 s1 = event.KeyInput.Key - irr::KEY_KEY_0;
\r
685 if(event.KeyInput.Key == irr::KEY_KEY_0)
\r
687 if(s1 < PLAYER_INVENTORY_SIZE)
\r
688 g_selected_item = s1-1;
\r
689 dstream<<DTIME<<"Selected item: "
\r
690 <<g_selected_item<<std::endl;
\r
693 // Viewing range selection
\r
694 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
696 if(draw_control.range_all)
\r
698 draw_control.range_all = false;
\r
699 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
703 draw_control.range_all = true;
\r
704 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
708 // Print debug stacks
\r
709 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
711 dstream<<"-----------------------------------------"
\r
713 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
714 dstream<<"-----------------------------------------"
\r
716 debug_stacks_print();
\r
721 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
723 if(noMenuActive() == false)
\r
725 left_active = false;
\r
726 middle_active = false;
\r
727 right_active = false;
\r
731 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
732 left_active = event.MouseInput.isLeftPressed();
\r
733 middle_active = event.MouseInput.isMiddlePressed();
\r
734 right_active = event.MouseInput.isRightPressed();
\r
736 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
738 leftclicked = true;
\r
740 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
742 rightclicked = true;
\r
744 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
746 leftreleased = true;
\r
748 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
750 rightreleased = true;
\r
752 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
754 /*dstream<<"event.MouseInput.Wheel="
\r
755 <<event.MouseInput.Wheel<<std::endl;*/
\r
756 if(event.MouseInput.Wheel < 0)
\r
758 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
761 g_selected_item = 0;
\r
763 else if(event.MouseInput.Wheel > 0)
\r
765 if(g_selected_item > 0)
\r
768 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
777 // This is used to check whether a key is being held down
\r
778 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
780 return keyIsDown[keyCode];
\r
785 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
786 keyIsDown[i] = false;
\r
788 leftclicked = false;
\r
789 rightclicked = false;
\r
790 leftreleased = false;
\r
791 rightreleased = false;
\r
793 left_active = false;
\r
794 middle_active = false;
\r
795 right_active = false;
\r
806 bool rightreleased;
\r
809 bool middle_active;
\r
813 // We use this array to store the current state of each key
\r
814 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
817 IrrlichtDevice *m_device;
\r
826 virtual ~InputHandler()
\r
830 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
832 virtual v2s32 getMousePos() = 0;
\r
833 virtual void setMousePos(s32 x, s32 y) = 0;
\r
835 virtual bool getLeftState() = 0;
\r
836 virtual bool getRightState() = 0;
\r
838 virtual bool getLeftClicked() = 0;
\r
839 virtual bool getRightClicked() = 0;
\r
840 virtual void resetLeftClicked() = 0;
\r
841 virtual void resetRightClicked() = 0;
\r
843 virtual bool getLeftReleased() = 0;
\r
844 virtual bool getRightReleased() = 0;
\r
845 virtual void resetLeftReleased() = 0;
\r
846 virtual void resetRightReleased() = 0;
\r
848 virtual void step(float dtime) {};
\r
850 virtual void clear() {};
\r
853 InputHandler *g_input = NULL;
\r
855 class RealInputHandler : public InputHandler
\r
858 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
860 m_receiver(receiver)
\r
863 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
865 return m_receiver->IsKeyDown(keyCode);
\r
867 virtual v2s32 getMousePos()
\r
869 return m_device->getCursorControl()->getPosition();
\r
871 virtual void setMousePos(s32 x, s32 y)
\r
873 m_device->getCursorControl()->setPosition(x, y);
\r
876 virtual bool getLeftState()
\r
878 return m_receiver->left_active;
\r
880 virtual bool getRightState()
\r
882 return m_receiver->right_active;
\r
885 virtual bool getLeftClicked()
\r
887 return m_receiver->leftclicked;
\r
889 virtual bool getRightClicked()
\r
891 return m_receiver->rightclicked;
\r
893 virtual void resetLeftClicked()
\r
895 m_receiver->leftclicked = false;
\r
897 virtual void resetRightClicked()
\r
899 m_receiver->rightclicked = false;
\r
902 virtual bool getLeftReleased()
\r
904 return m_receiver->leftreleased;
\r
906 virtual bool getRightReleased()
\r
908 return m_receiver->rightreleased;
\r
910 virtual void resetLeftReleased()
\r
912 m_receiver->leftreleased = false;
\r
914 virtual void resetRightReleased()
\r
916 m_receiver->rightreleased = false;
\r
921 resetRightClicked();
\r
922 resetLeftClicked();
\r
925 IrrlichtDevice *m_device;
\r
926 MyEventReceiver *m_receiver;
\r
929 class RandomInputHandler : public InputHandler
\r
932 RandomInputHandler()
\r
934 leftclicked = false;
\r
935 rightclicked = false;
\r
936 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
937 keydown[i] = false;
\r
939 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
941 return keydown[keyCode];
\r
943 virtual v2s32 getMousePos()
\r
947 virtual void setMousePos(s32 x, s32 y)
\r
949 mousepos = v2s32(x,y);
\r
952 virtual bool getLeftState()
\r
956 virtual bool getRightState()
\r
961 virtual bool getLeftClicked()
\r
963 return leftclicked;
\r
965 virtual bool getRightClicked()
\r
967 return rightclicked;
\r
969 virtual void resetLeftClicked()
\r
971 leftclicked = false;
\r
973 virtual void resetRightClicked()
\r
975 rightclicked = false;
\r
978 virtual bool getLeftReleased()
\r
982 virtual bool getRightReleased()
\r
986 virtual void resetLeftReleased()
\r
989 virtual void resetRightReleased()
\r
993 virtual void step(float dtime)
\r
996 static float counter1 = 0;
\r
1000 counter1 = 0.1*Rand(1,10);
\r
1001 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
1002 g_selected_material++;
\r
1004 g_selected_material = 0;*/
\r
1005 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
1006 g_selected_item++;
\r
1008 g_selected_item = 0;
\r
1012 static float counter1 = 0;
\r
1013 counter1 -= dtime;
\r
1014 if(counter1 < 0.0)
\r
1016 counter1 = 0.1*Rand(1, 40);
\r
1017 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
1021 static float counter1 = 0;
\r
1022 counter1 -= dtime;
\r
1023 if(counter1 < 0.0)
\r
1025 counter1 = 0.1*Rand(1, 40);
\r
1026 keydown[irr::KEY_KEY_E] = !keydown[irr::KEY_KEY_E];
\r
1030 static float counter1 = 0;
\r
1031 counter1 -= dtime;
\r
1032 if(counter1 < 0.0)
\r
1034 counter1 = 0.1*Rand(1, 40);
\r
1035 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
1039 static float counter1 = 0;
\r
1040 counter1 -= dtime;
\r
1041 if(counter1 < 0.0)
\r
1043 counter1 = 0.1*Rand(1, 40);
\r
1044 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
1048 static float counter1 = 0;
\r
1049 counter1 -= dtime;
\r
1050 if(counter1 < 0.0)
\r
1052 counter1 = 0.1*Rand(1, 20);
\r
1053 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
1057 static float counter1 = 0;
\r
1058 counter1 -= dtime;
\r
1059 if(counter1 < 0.0)
\r
1061 counter1 = 0.1*Rand(1, 30);
\r
1062 leftclicked = true;
\r
1066 static float counter1 = 0;
\r
1067 counter1 -= dtime;
\r
1068 if(counter1 < 0.0)
\r
1070 counter1 = 0.1*Rand(1, 20);
\r
1071 rightclicked = true;
\r
1074 mousepos += mousespeed;
\r
1077 s32 Rand(s32 min, s32 max)
\r
1079 return (myrand()%(max-min+1))+min;
\r
1082 bool keydown[KEY_KEY_CODES_COUNT];
\r
1086 bool rightclicked;
\r
1089 void updateViewingRange(f32 frametime_in, Client *client)
\r
1091 if(draw_control.range_all == true)
\r
1094 static f32 added_frametime = 0;
\r
1095 static s16 added_frames = 0;
\r
1097 added_frametime += frametime_in;
\r
1098 added_frames += 1;
\r
1100 // Actually this counter kind of sucks because frametime is busytime
\r
1101 static f32 counter = 0;
\r
1102 counter -= frametime_in;
\r
1108 /*dstream<<__FUNCTION_NAME
\r
1109 <<": Collected "<<added_frames<<" frames, total of "
\r
1110 <<added_frametime<<"s."<<std::endl;*/
\r
1112 /*dstream<<"draw_control.blocks_drawn="
\r
1113 <<draw_control.blocks_drawn
\r
1114 <<", draw_control.blocks_would_have_drawn="
\r
1115 <<draw_control.blocks_would_have_drawn
\r
1118 float range_min = g_settings.getS16("viewing_range_nodes_min");
\r
1119 float range_max = g_settings.getS16("viewing_range_nodes_max");
\r
1121 draw_control.wanted_min_range = range_min;
\r
1122 draw_control.wanted_max_blocks = (1.2*draw_control.blocks_drawn)+1;
\r
1124 float block_draw_ratio = 1.0;
\r
1125 if(draw_control.blocks_would_have_drawn != 0)
\r
1127 block_draw_ratio = (float)draw_control.blocks_drawn
\r
1128 / (float)draw_control.blocks_would_have_drawn;
\r
1131 // Calculate the average frametime in the case that all wanted
\r
1132 // blocks had been drawn
\r
1133 f32 frametime = added_frametime / added_frames / block_draw_ratio;
\r
1135 added_frametime = 0.0;
\r
1138 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
1139 float wanted_frametime = 1.0 / wanted_fps;
\r
1141 f32 wanted_frametime_change = wanted_frametime - frametime;
\r
1142 //dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
\r
1144 // If needed frametime change is very small, just return
\r
1145 if(fabs(wanted_frametime_change) < wanted_frametime*0.2)
\r
1147 //dstream<<"ignoring small wanted_frametime_change"<<std::endl;
\r
1151 float range = draw_control.wanted_range;
\r
1152 float new_range = range;
\r
1154 static s16 range_old = 0;
\r
1155 static f32 frametime_old = 0;
\r
1157 float d_range = range - range_old;
\r
1158 f32 d_frametime = frametime - frametime_old;
\r
1159 // A sane default of 30ms per 50 nodes of range
\r
1160 static f32 time_per_range = 30. / 50;
\r
1163 time_per_range = d_frametime / d_range;
\r
1166 // The minimum allowed calculated frametime-range derivative:
\r
1167 // Practically this sets the maximum speed of changing the range.
\r
1168 // The lower this value, the higher the maximum changing speed.
\r
1169 // A low value here results in wobbly range (0.001)
\r
1170 // A high value here results in slow changing range (0.0025)
\r
1171 // SUGG: This could be dynamically adjusted so that when
\r
1172 // the camera is turning, this is lower
\r
1173 //float min_time_per_range = 0.0015;
\r
1174 float min_time_per_range = 0.0010;
\r
1175 //float min_time_per_range = 0.05 / range;
\r
1176 if(time_per_range < min_time_per_range)
\r
1178 time_per_range = min_time_per_range;
\r
1179 //dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
\r
1183 //dstream<<"time_per_range="<<time_per_range<<std::endl;
\r
1186 f32 wanted_range_change = wanted_frametime_change / time_per_range;
\r
1187 // Dampen the change a bit to kill oscillations
\r
1188 //wanted_range_change *= 0.9;
\r
1189 //wanted_range_change *= 0.75;
\r
1190 wanted_range_change *= 0.5;
\r
1191 //dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
\r
1193 // If needed range change is very small, just return
\r
1194 if(fabs(wanted_range_change) < 0.001)
\r
1196 //dstream<<"ignoring small wanted_range_change"<<std::endl;
\r
1200 new_range += wanted_range_change;
\r
1201 //dstream<<"new_range="<<new_range/*<<std::endl*/;
\r
1203 //float new_range_unclamped = new_range;
\r
1204 if(new_range < range_min)
\r
1205 new_range = range_min;
\r
1206 if(new_range > range_max)
\r
1207 new_range = range_max;
\r
1209 /*if(new_range != new_range_unclamped)
\r
1210 dstream<<", clamped to "<<new_range<<std::endl;
\r
1212 dstream<<std::endl;*/
\r
1214 draw_control.wanted_range = new_range;
\r
1216 range_old = new_range;
\r
1217 frametime_old = frametime;
\r
1220 class GUIQuickInventory : public IEventReceiver
\r
1223 GUIQuickInventory(
\r
1224 gui::IGUIEnvironment* env,
\r
1225 gui::IGUIElement* parent,
\r
1228 Inventory *inventory):
\r
1229 m_itemcount(itemcount),
\r
1230 m_inventory(inventory)
\r
1232 core::rect<s32> imgsize(0,0,48,48);
\r
1233 core::rect<s32> textsize(0,0,48,16);
\r
1234 v2s32 spacing(0, 64);
\r
1235 for(s32 i=0; i<m_itemcount; i++)
\r
1237 m_images.push_back(env->addImage(
\r
1238 imgsize + pos + spacing*i
\r
1240 m_images[i]->setScaleImage(true);
\r
1241 m_texts.push_back(env->addStaticText(
\r
1243 textsize + pos + spacing*i,
\r
1246 m_texts[i]->setBackgroundColor(
\r
1247 video::SColor(128,0,0,0));
\r
1248 m_texts[i]->setTextAlignment(
\r
1249 gui::EGUIA_CENTER,
\r
1250 gui::EGUIA_UPPERLEFT);
\r
1254 ~GUIQuickInventory()
\r
1256 for(u32 i=0; i<m_texts.size(); i++)
\r
1258 m_texts[i]->remove();
\r
1260 for(u32 i=0; i<m_images.size(); i++)
\r
1262 m_images[i]->remove();
\r
1266 virtual bool OnEvent(const SEvent& event)
\r
1271 void setSelection(s32 i)
\r
1280 start = m_selection - m_itemcount / 2;
\r
1282 InventoryList *mainlist = m_inventory->getList("main");
\r
1284 for(s32 i=0; i<m_itemcount; i++)
\r
1286 s32 j = i + start;
\r
1288 if(j > (s32)mainlist->getSize() - 1)
\r
1289 j -= mainlist->getSize();
\r
1291 j += mainlist->getSize();
\r
1293 InventoryItem *item = mainlist->getItem(j);
\r
1297 m_images[i]->setImage(NULL);
\r
1300 if(m_selection == j)
\r
1301 swprintf(t, 10, L"<-");
\r
1303 swprintf(t, 10, L"");
\r
1304 m_texts[i]->setText(t);
\r
1306 // The next ifs will segfault with a NULL pointer
\r
1311 m_images[i]->setImage(item->getImage());
\r
1314 if(m_selection == j)
\r
1315 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1317 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1318 m_texts[i]->setText(t);
\r
1324 core::array<gui::IGUIStaticText*> m_texts;
\r
1325 core::array<gui::IGUIImage*> m_images;
\r
1326 Inventory *m_inventory;
\r
1337 ChatLine(const std::wstring &a_text):
\r
1343 std::wstring text;
\r
1346 // These are defined global so that they're not optimized too much.
\r
1347 // Can't change them to volatile.
\r
1352 std::string tempstring;
\r
1353 std::string tempstring2;
\r
1358 dstream<<"The following test should take around 20ms."<<std::endl;
\r
1359 TimeTaker timer("Testing std::string speed");
\r
1360 const u32 jj = 10000;
\r
1361 for(u32 j=0; j<jj; j++)
\r
1365 const u32 ii = 10;
\r
1366 for(u32 i=0; i<ii; i++){
\r
1367 tempstring2 += "asd";
\r
1369 for(u32 i=0; i<ii+1; i++){
\r
1370 tempstring += "asd";
\r
1371 if(tempstring == tempstring2)
\r
1377 dstream<<"All of the following tests should take around 100ms each."
\r
1381 TimeTaker timer("Testing floating-point conversion speed");
\r
1383 for(u32 i=0; i<4000000; i++){
\r
1390 TimeTaker timer("Testing floating-point vector speed");
\r
1392 tempv3f1 = v3f(1,2,3);
\r
1393 tempv3f2 = v3f(4,5,6);
\r
1394 for(u32 i=0; i<10000000; i++){
\r
1395 tempf += tempv3f1.dotProduct(tempv3f2);
\r
1396 tempv3f2 += v3f(7,8,9);
\r
1401 TimeTaker timer("Testing core::map speed");
\r
1403 core::map<v2s16, f32> map1;
\r
1406 for(s16 y=0; y<ii; y++){
\r
1407 for(s16 x=0; x<ii; x++){
\r
1408 map1.insert(v2s16(x,y), tempf);
\r
1412 for(s16 y=ii-1; y>=0; y--){
\r
1413 for(s16 x=0; x<ii; x++){
\r
1414 tempf = map1[v2s16(x,y)];
\r
1420 dstream<<"Around 5000/ms should do well here."<<std::endl;
\r
1421 TimeTaker timer("Testing mutex speed");
\r
1434 // Do at least 10ms
\r
1435 while(timer.getTime() < 10);
\r
1437 u32 dtime = timer.stop();
\r
1438 u32 per_ms = n / dtime;
\r
1439 std::cout<<"Done. "<<dtime<<"ms, "
\r
1440 <<per_ms<<"/ms"<<std::endl;
\r
1444 int main(int argc, char *argv[])
\r
1447 Parse command line
\r
1450 // List all allowed options
\r
1451 core::map<std::string, ValueSpec> allowed_options;
\r
1452 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1453 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1454 "Run server directly"));
\r
1455 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1456 "Load configuration from specified file"));
\r
1457 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1458 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1459 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1460 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1461 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1462 allowed_options.insert("map-dir", ValueSpec(VALUETYPE_STRING));
\r
1464 allowed_options.insert("dstream-on-stderr", ValueSpec(VALUETYPE_FLAG));
\r
1466 allowed_options.insert("speedtests", ValueSpec(VALUETYPE_FLAG));
\r
1468 Settings cmd_args;
\r
1470 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1472 if(ret == false || cmd_args.getFlag("help"))
\r
1474 dstream<<"Allowed options:"<<std::endl;
\r
1475 for(core::map<std::string, ValueSpec>::Iterator
\r
1476 i = allowed_options.getIterator();
\r
1477 i.atEnd() == false; i++)
\r
1479 dstream<<" --"<<i.getNode()->getKey();
\r
1480 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1485 dstream<<" <value>";
\r
1487 dstream<<std::endl;
\r
1489 if(i.getNode()->getValue().help != NULL)
\r
1491 dstream<<" "<<i.getNode()->getValue().help
\r
1496 return cmd_args.getFlag("help") ? 0 : 1;
\r
1500 Low-level initialization
\r
1503 bool disable_stderr = false;
\r
1505 if(cmd_args.getFlag("dstream-on-stderr") == false)
\r
1506 disable_stderr = true;
\r
1509 // Initialize debug streams
\r
1510 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1511 // Initialize debug stacks
\r
1512 debug_stacks_init();
\r
1514 DSTACK(__FUNCTION_NAME);
\r
1516 porting::initializePaths();
\r
1517 // Create user data directory
\r
1518 fs::CreateDir(porting::path_userdata);
\r
1520 // C-style stuff initialization
\r
1521 initializeMaterialProperties();
\r
1524 BEGIN_DEBUG_EXCEPTION_HANDLER
\r
1526 // Print startup message
\r
1527 dstream<<DTIME<<"minetest-c55"
\r
1528 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1529 <<", "<<BUILD_INFO
\r
1533 Basic initialization
\r
1536 // Initialize default settings
\r
1537 set_default_settings();
\r
1539 // Set locale. This is for forcing '.' as the decimal point.
\r
1540 std::locale::global(std::locale("C"));
\r
1541 // This enables printing all characters in bitmap font
\r
1542 setlocale(LC_CTYPE, "en_US");
\r
1544 // Initialize sockets
\r
1546 atexit(sockets_cleanup);
\r
1556 // Path of configuration file in use
\r
1557 std::string configpath = "";
\r
1559 if(cmd_args.exists("config"))
\r
1561 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1564 dstream<<"Could not read configuration from \""
\r
1565 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1568 configpath = cmd_args.get("config");
\r
1572 core::array<std::string> filenames;
\r
1573 filenames.push_back(porting::path_userdata + "/minetest.conf");
\r
1574 #ifdef RUN_IN_PLACE
\r
1575 filenames.push_back(porting::path_userdata + "/../minetest.conf");
\r
1578 for(u32 i=0; i<filenames.size(); i++)
\r
1580 bool r = g_settings.readConfigFile(filenames[i].c_str());
\r
1583 configpath = filenames[i];
\r
1589 // Initialize random seed
\r
1594 Pre-initialize some stuff with a dummy irrlicht wrapper.
\r
1596 These are needed for unit tests at least.
\r
1599 IIrrlichtWrapper irrlicht_dummy;
\r
1601 init_mapnode(&irrlicht_dummy);
\r
1606 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1607 || cmd_args.getFlag("enable-unittests") == true)
\r
1618 if(cmd_args.exists("port"))
\r
1619 port = cmd_args.getU16("port");
\r
1620 else if(cmd_args.exists("port"))
\r
1621 port = g_settings.getU16("port");
\r
1624 std::string map_dir = porting::path_userdata+"/map";
\r
1625 if(cmd_args.exists("map-dir"))
\r
1626 map_dir = cmd_args.get("map-dir");
\r
1627 else if(g_settings.exists("map-dir"))
\r
1628 map_dir = g_settings.get("map-dir");
\r
1630 // Run dedicated server if asked to
\r
1631 if(cmd_args.getFlag("server"))
\r
1633 DSTACK("Dedicated server branch");
\r
1636 Server server(map_dir.c_str());
\r
1637 server.start(port);
\r
1640 dedicated_server_loop(server);
\r
1649 // Address to connect to
\r
1650 std::string address = "";
\r
1652 if(cmd_args.exists("address"))
\r
1654 address = cmd_args.get("address");
\r
1658 address = g_settings.get("address");
\r
1661 std::string playername = g_settings.get("name");
\r
1664 Resolution selection
\r
1667 bool fullscreen = false;
\r
1668 u16 screenW = g_settings.getU16("screenW");
\r
1669 u16 screenH = g_settings.getU16("screenH");
\r
1673 MyEventReceiver receiver;
\r
1675 video::E_DRIVER_TYPE driverType;
\r
1678 //driverType = video::EDT_DIRECT3D9;
\r
1679 driverType = video::EDT_OPENGL;
\r
1681 driverType = video::EDT_OPENGL;
\r
1682 //driverType = video::EDT_BURNINGSVIDEO; // Best software renderer
\r
1685 // create device and exit if creation failed
\r
1687 IrrlichtDevice *device;
\r
1688 device = createDevice(driverType,
\r
1689 core::dimension2d<u32>(screenW, screenH),
\r
1690 16, fullscreen, false, false, &receiver);
\r
1693 return 1; // could not create selected driver.
\r
1695 g_device = device;
\r
1696 g_irrlicht = new IrrlichtWrapper(device);
\r
1699 Speed tests (done after irrlicht is loaded to get timer)
\r
1701 if(cmd_args.getFlag("speedtests"))
\r
1703 dstream<<"Running speed tests"<<std::endl;
\r
1708 device->setResizable(true);
\r
1710 bool random_input = g_settings.getBool("random_input")
\r
1711 || cmd_args.getFlag("random-input");
\r
1713 g_input = new RandomInputHandler();
\r
1715 g_input = new RealInputHandler(device, &receiver);
\r
1718 Continue initialization
\r
1721 video::IVideoDriver* driver = device->getVideoDriver();
\r
1724 This changes the minimum allowed number of vertices in a VBO
\r
1726 //driver->setMinHardwareBufferVertexCount(50);
\r
1728 scene::ISceneManager* smgr = device->getSceneManager();
\r
1730 guienv = device->getGUIEnvironment();
\r
1731 gui::IGUISkin* skin = guienv->getSkin();
\r
1732 gui::IGUIFont* font = guienv->getFont(porting::getDataPath("fontlucida.png").c_str());
\r
1734 skin->setFont(font);
\r
1736 dstream<<"WARNING: Font file was not found."
\r
1737 " Using default font."<<std::endl;
\r
1738 // If font was not found, this will get us one
\r
1739 font = skin->getFont();
\r
1742 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1743 dstream<<"text_height="<<text_height<<std::endl;
\r
1745 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1746 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1747 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1748 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1749 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1750 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1753 Preload some textures and stuff
\r
1756 init_content_inventory_texture_paths();
\r
1757 init_mapnode(g_irrlicht);
\r
1758 init_mineral(g_irrlicht);
\r
1765 We need some kind of a root node to be able to add
\r
1766 custom gui elements directly on the screen.
\r
1767 Otherwise they won't be automatically drawn.
\r
1769 guiroot = guienv->addStaticText(L"",
\r
1770 core::rect<s32>(0, 0, 10000, 10000));
\r
1772 // First line of debug text
\r
1773 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1775 core::rect<s32>(5, 5, 795, 5+text_height),
\r
1777 // Second line of debug text
\r
1778 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1780 core::rect<s32>(5, 5+(text_height+5)*1, 795, (5+text_height)*2),
\r
1783 // At the middle of the screen
\r
1784 // Object infos are shown in this
\r
1785 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1787 core::rect<s32>(100, 70, 100+400, 70+(text_height+5)),
\r
1791 gui::IGUIStaticText *guitext_chat = guienv->addStaticText(
\r
1793 core::rect<s32>(0,0,0,0),
\r
1795 guitext_chat->setBackgroundColor(video::SColor(96,0,0,0));
\r
1796 core::list<ChatLine> chat_lines;
\r
1799 If an error occurs, this is set to something and the
\r
1800 menu-game loop is restarted. It is then displayed before
\r
1803 std::wstring error_message = L"";
\r
1808 while(g_device->run())
\r
1811 // This is used for catching disconnects
\r
1816 Out-of-game menu loop.
\r
1818 Loop quits when menu returns proper parameters.
\r
1822 // Cursor can be non-visible when coming from the game
\r
1823 device->getCursorControl()->setVisible(true);
\r
1824 // Some stuff are left to scene manager when coming from the game
\r
1825 // (map at least?)
\r
1827 // Reset or hide the debug gui texts
\r
1828 guitext->setText(L"Minetest-c55");
\r
1829 guitext2->setVisible(false);
\r
1830 guitext_info->setVisible(false);
\r
1831 guitext_chat->setVisible(false);
\r
1833 // Initialize menu data
\r
1834 MainMenuData menudata;
\r
1835 menudata.address = narrow_to_wide(address);
\r
1836 menudata.name = narrow_to_wide(playername);
\r
1837 menudata.port = narrow_to_wide(itos(port));
\r
1838 menudata.creative_mode = g_settings.getBool("creative_mode");
\r
1840 GUIMainMenu *menu =
\r
1841 new GUIMainMenu(guienv, guiroot, -1,
\r
1842 &g_menumgr, &menudata, &g_gamecallback);
\r
1843 menu->allowFocusRemoval(true);
\r
1845 if(error_message != L"")
\r
1847 GUIMessageMenu *menu2 =
\r
1848 new GUIMessageMenu(guienv, guiroot, -1,
\r
1849 &g_menumgr, error_message.c_str());
\r
1851 error_message = L"";
\r
1854 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
1856 dstream<<"Created main menu"<<std::endl;
\r
1858 while(g_device->run())
\r
1860 // Run global IrrlichtWrapper's main thread processing stuff
\r
1861 g_irrlicht->Run();
\r
1863 if(menu->getStatus() == true)
\r
1866 //driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1867 driver->beginScene(true, true, video::SColor(255,128,128,128));
\r
1868 guienv->drawAll();
\r
1869 driver->endScene();
\r
1872 // Break out of menu-game loop to shut down cleanly
\r
1873 if(g_device->run() == false)
\r
1876 dstream<<"Dropping main menu"<<std::endl;
\r
1880 // Delete map if requested
\r
1881 if(menudata.delete_map)
\r
1883 bool r = fs::RecursiveDeleteContent(map_dir);
\r
1885 error_message = L"Delete failed";
\r
1889 playername = wide_to_narrow(menudata.name);
\r
1890 address = wide_to_narrow(menudata.address);
\r
1891 port = stoi(wide_to_narrow(menudata.port));
\r
1892 g_settings.set("creative_mode", itos(menudata.creative_mode));
\r
1894 // Check for valid parameters, restart menu if invalid.
\r
1895 if(playername == "")
\r
1897 error_message = L"Name required.";
\r
1902 g_settings.set("name", playername);
\r
1903 g_settings.set("address", address);
\r
1904 g_settings.set("port", itos(port));
\r
1905 // Update configuration file
\r
1906 if(configpath != "")
\r
1907 g_settings.updateConfigFile(configpath.c_str());
\r
1909 // Continue to game
\r
1913 // Break out of menu-game loop to shut down cleanly
\r
1914 if(g_device->run() == false)
\r
1918 Make a scope here so that the client and the server and other
\r
1919 stuff gets removed when disconnected or the irrlicht device
\r
1924 // This is set to true at the end of the scope
\r
1925 g_irrlicht->Shutdown(false);
\r
1928 Draw "Loading" screen
\r
1930 const wchar_t *text = L"Loading and connecting...";
\r
1931 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1932 core::vector2d<s32> textsize(300, text_height);
\r
1933 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1935 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1936 text, textrect, false, false);
\r
1937 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1939 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1940 guienv->drawAll();
\r
1941 driver->endScene();
\r
1943 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1947 SharedPtr will delete it when it goes out of scope.
\r
1949 SharedPtr<Server> server;
\r
1950 if(address == ""){
\r
1951 server = new Server(map_dir);
\r
1952 server->start(port);
\r
1959 Client client(device, playername.c_str(), draw_control);
\r
1961 g_client = &client;
\r
1963 Address connect_address(0,0,0,0, port);
\r
1966 connect_address.Resolve("localhost");
\r
1968 connect_address.Resolve(address.c_str());
\r
1970 catch(ResolveError &e)
\r
1972 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1974 error_message = L"Couldn't resolve address";
\r
1975 gui_loadingtext->remove();
\r
1979 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1980 client.connect(connect_address);
\r
1983 while(client.connectedAndInitialized() == false)
\r
1986 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1987 guienv->drawAll();
\r
1988 driver->endScene();
\r
1990 // Update client and server
\r
1994 if(server != NULL)
\r
1995 server->step(0.1);
\r
2001 catch(con::PeerNotFoundException &e)
\r
2003 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
2005 error_message = L"Connection timed out.";
\r
2006 gui_loadingtext->remove();
\r
2013 /*scene::ISceneNode* skybox;
\r
2014 skybox = smgr->addSkyBoxSceneNode(
\r
2015 driver->getTexture(porting::getDataPath("skybox2.png").c_str()),
\r
2016 driver->getTexture(porting::getDataPath("skybox3.png").c_str()),
\r
2017 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2018 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2019 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2020 driver->getTexture(porting::getDataPath("skybox1.png").c_str()));*/
\r
2023 Create the camera node
\r
2026 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
2027 0, // Camera parent
\r
2028 v3f(BS*100, BS*2, BS*100), // Look from
\r
2029 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
2033 if(camera == NULL)
\r
2036 video::SColor skycolor = video::SColor(255,90,140,200);
\r
2038 camera->setFOV(FOV_ANGLE);
\r
2040 // Just so big a value that everything rendered is visible
\r
2041 camera->setFarValue(100000*BS);
\r
2043 f32 camera_yaw = 0; // "right/left"
\r
2044 f32 camera_pitch = 0; // "up/down"
\r
2050 gui_loadingtext->remove();
\r
2053 Add some gui stuff
\r
2056 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
2057 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
2059 // Test the text input system
\r
2060 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_menumgr,
\r
2062 /*GUIMessageMenu *menu =
\r
2063 new GUIMessageMenu(guienv, guiroot, -1,
\r
2068 // Launch pause menu
\r
2069 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
2070 &g_menumgr))->drop();
\r
2073 guitext2->setVisible(true);
\r
2074 guitext_info->setVisible(true);
\r
2075 guitext_chat->setVisible(true);
\r
2078 Some statistics are collected in these
\r
2081 u32 beginscenetime = 0;
\r
2082 u32 scenetime = 0;
\r
2083 u32 endscenetime = 0;
\r
2086 //throw con::PeerNotFoundException("lol");
\r
2092 bool first_loop_after_window_activation = true;
\r
2094 // Time is in milliseconds
\r
2095 // NOTE: getRealTime() causes strange problems in wine (imprecision?)
\r
2096 // NOTE: So we have to use getTime() and call run()s between them
\r
2097 u32 lasttime = device->getTimer()->getTime();
\r
2099 while(device->run())
\r
2101 if(g_disconnect_requested)
\r
2103 g_disconnect_requested = false;
\r
2108 Run global IrrlichtWrapper's main thread processing stuff
\r
2110 g_irrlicht->Run();
\r
2113 Random calculations
\r
2115 v2u32 screensize = driver->getScreenSize();
\r
2116 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
2118 // Hilight boxes collected during the loop and displayed
\r
2119 core::list< core::aabbox3d<f32> > hilightboxes;
\r
2122 std::wstring infotext;
\r
2124 //TimeTaker //timer1("//timer1");
\r
2126 // Time of frame without fps limit
\r
2130 // not using getRealTime is necessary for wine
\r
2131 u32 time = device->getTimer()->getTime();
\r
2132 if(time > lasttime)
\r
2133 busytime_u32 = time - lasttime;
\r
2136 busytime = busytime_u32 / 1000.0;
\r
2139 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
2141 // Absolutelu necessary for wine!
\r
2148 updateViewingRange(busytime, &client);
\r
2155 float fps_max = g_settings.getFloat("fps_max");
\r
2156 u32 frametime_min = 1000./fps_max;
\r
2158 if(busytime_u32 < frametime_min)
\r
2160 u32 sleeptime = frametime_min - busytime_u32;
\r
2161 device->sleep(sleeptime);
\r
2165 // Absolutelu necessary for wine!
\r
2169 Time difference calculation
\r
2171 f32 dtime; // in seconds
\r
2173 u32 time = device->getTimer()->getTime();
\r
2174 if(time > lasttime)
\r
2175 dtime = (time - lasttime) / 1000.0;
\r
2181 Time average and jitter calculation
\r
2184 static f32 dtime_avg1 = 0.0;
\r
2185 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
2186 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
2188 static f32 dtime_jitter1_max_sample = 0.0;
\r
2189 static f32 dtime_jitter1_max_fraction = 0.0;
\r
2191 static f32 jitter1_max = 0.0;
\r
2192 static f32 counter = 0.0;
\r
2193 if(dtime_jitter1 > jitter1_max)
\r
2194 jitter1_max = dtime_jitter1;
\r
2199 dtime_jitter1_max_sample = jitter1_max;
\r
2200 dtime_jitter1_max_fraction
\r
2201 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
2202 jitter1_max = 0.0;
\r
2207 Busytime average and jitter calculation
\r
2210 static f32 busytime_avg1 = 0.0;
\r
2211 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
2212 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
2214 static f32 busytime_jitter1_max_sample = 0.0;
\r
2215 static f32 busytime_jitter1_min_sample = 0.0;
\r
2217 static f32 jitter1_max = 0.0;
\r
2218 static f32 jitter1_min = 0.0;
\r
2219 static f32 counter = 0.0;
\r
2220 if(busytime_jitter1 > jitter1_max)
\r
2221 jitter1_max = busytime_jitter1;
\r
2222 if(busytime_jitter1 < jitter1_min)
\r
2223 jitter1_min = busytime_jitter1;
\r
2225 if(counter > 0.0){
\r
2227 busytime_jitter1_max_sample = jitter1_max;
\r
2228 busytime_jitter1_min_sample = jitter1_min;
\r
2229 jitter1_max = 0.0;
\r
2230 jitter1_min = 0.0;
\r
2235 Debug info for client
\r
2238 static float counter = 0.0;
\r
2243 client.printDebugInfo(std::cout);
\r
2248 Input handler step()
\r
2250 g_input->step(dtime);
\r
2253 Player speed control
\r
2262 bool a_superspeed,
\r
2265 PlayerControl control(
\r
2266 g_input->isKeyDown(irr::KEY_KEY_W),
\r
2267 g_input->isKeyDown(irr::KEY_KEY_S),
\r
2268 g_input->isKeyDown(irr::KEY_KEY_A),
\r
2269 g_input->isKeyDown(irr::KEY_KEY_D),
\r
2270 g_input->isKeyDown(irr::KEY_SPACE),
\r
2271 g_input->isKeyDown(irr::KEY_KEY_E),
\r
2275 client.setPlayerControl(control);
\r
2279 Process environment
\r
2283 //TimeTaker timer("client.step(dtime)");
\r
2284 client.step(dtime);
\r
2285 //client.step(dtime_avg1);
\r
2288 if(server != NULL)
\r
2290 //TimeTaker timer("server->step(dtime)");
\r
2291 server->step(dtime);
\r
2294 v3f player_position = client.getPlayerPosition();
\r
2296 //TimeTaker //timer2("//timer2");
\r
2299 Mouse and camera control
\r
2302 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
2305 device->getCursorControl()->setVisible(false);
\r
2307 if(first_loop_after_window_activation){
\r
2308 //std::cout<<"window active, first loop"<<std::endl;
\r
2309 first_loop_after_window_activation = false;
\r
2312 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
2313 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
2314 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
2315 camera_yaw -= dx*0.2;
\r
2316 camera_pitch += dy*0.2;
\r
2317 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
2318 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
2320 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
2323 device->getCursorControl()->setVisible(true);
\r
2325 //std::cout<<"window inactive"<<std::endl;
\r
2326 first_loop_after_window_activation = true;
\r
2329 camera_yaw = wrapDegrees(camera_yaw);
\r
2330 camera_pitch = wrapDegrees(camera_pitch);
\r
2332 v3f camera_direction = v3f(0,0,1);
\r
2333 camera_direction.rotateYZBy(camera_pitch);
\r
2334 camera_direction.rotateXZBy(camera_yaw);
\r
2336 // This is at the height of the eyes of the current figure
\r
2337 //v3f camera_position = player_position + v3f(0, BS+BS/2, 0);
\r
2338 // This is more like in minecraft
\r
2339 v3f camera_position = player_position + v3f(0, BS+BS*0.625, 0);
\r
2341 camera->setPosition(camera_position);
\r
2342 // *100.0 helps in large map coordinates
\r
2343 camera->setTarget(camera_position + camera_direction * 100.0);
\r
2345 if(FIELD_OF_VIEW_TEST){
\r
2346 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
2347 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
2350 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
2351 //TimeTaker timer("client.updateCamera");
\r
2352 client.updateCamera(camera_position, camera_direction);
\r
2356 //TimeTaker //timer3("//timer3");
\r
2359 Calculate what block is the crosshair pointing to
\r
2362 //u32 t1 = device->getTimer()->getRealTime();
\r
2364 //f32 d = 4; // max. distance
\r
2365 f32 d = 4; // max. distance
\r
2366 core::line3d<f32> shootline(camera_position,
\r
2367 camera_position + camera_direction * BS * (d+1));
\r
2369 MapBlockObject *selected_object = client.getSelectedObject
\r
2370 (d*BS, camera_position, shootline);
\r
2373 If it's pointing to a MapBlockObject
\r
2376 if(selected_object != NULL)
\r
2378 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
2380 core::aabbox3d<f32> box_on_map
\r
2381 = selected_object->getSelectionBoxOnMap();
\r
2383 hilightboxes.push_back(box_on_map);
\r
2385 infotext = narrow_to_wide(selected_object->infoText());
\r
2387 if(g_input->getLeftClicked())
\r
2389 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
2390 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
2391 selected_object->getId(), g_selected_item);
\r
2393 else if(g_input->getRightClicked())
\r
2395 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
2397 Check if we want to modify the object ourselves
\r
2399 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
2401 dstream<<"Sign object right-clicked"<<std::endl;
\r
2403 if(random_input == false)
\r
2405 // Get a new text for it
\r
2407 TextDest *dest = new TextDestSign(
\r
2408 selected_object->getBlock()->getPos(),
\r
2409 selected_object->getId(),
\r
2412 SignObject *sign_object = (SignObject*)selected_object;
\r
2414 std::wstring wtext =
\r
2415 narrow_to_wide(sign_object->getText());
\r
2417 (new GUITextInputMenu(guienv, guiroot, -1,
\r
2423 Otherwise pass the event to the server as-is
\r
2427 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
2428 selected_object->getId(), g_selected_item);
\r
2432 else // selected_object == NULL
\r
2436 Find out which node we are pointing at
\r
2439 bool nodefound = false;
\r
2441 v3s16 neighbourpos;
\r
2442 core::aabbox3d<f32> nodehilightbox;
\r
2443 f32 mindistance = BS * 1001;
\r
2445 v3s16 pos_i = floatToInt(player_position);
\r
2447 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2451 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2452 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2453 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2454 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2455 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2456 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2458 for(s16 y = ystart; y <= yend; y++)
\r
2459 for(s16 z = zstart; z <= zend; z++)
\r
2460 for(s16 x = xstart; x <= xend; x++)
\r
2465 n = client.getNode(v3s16(x,y,z));
\r
2466 if(content_pointable(n.d) == false)
\r
2469 catch(InvalidPositionException &e)
\r
2475 v3f npf = intToFloat(np);
\r
2480 v3s16(0,0,1), // back
\r
2481 v3s16(0,1,0), // top
\r
2482 v3s16(1,0,0), // right
\r
2483 v3s16(0,0,-1), // front
\r
2484 v3s16(0,-1,0), // bottom
\r
2485 v3s16(-1,0,0), // left
\r
2491 if(n.d == CONTENT_TORCH)
\r
2493 v3s16 dir = unpackDir(n.dir);
\r
2494 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2495 dir_f *= BS/2 - BS/6 - BS/20;
\r
2496 v3f cpf = npf + dir_f;
\r
2497 f32 distance = (cpf - camera_position).getLength();
\r
2499 core::aabbox3d<f32> box;
\r
2502 if(dir == v3s16(0,-1,0))
\r
2504 box = core::aabbox3d<f32>(
\r
2505 npf - v3f(BS/6, BS/2, BS/6),
\r
2506 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2510 else if(dir == v3s16(0,1,0))
\r
2512 box = core::aabbox3d<f32>(
\r
2513 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2514 npf + v3f(BS/6, BS/2, BS/6)
\r
2520 box = core::aabbox3d<f32>(
\r
2521 cpf - v3f(BS/6, BS/3, BS/6),
\r
2522 cpf + v3f(BS/6, BS/3, BS/6)
\r
2526 if(distance < mindistance)
\r
2528 if(box.intersectsWithLine(shootline))
\r
2532 neighbourpos = np;
\r
2533 mindistance = distance;
\r
2534 nodehilightbox = box;
\r
2543 for(u16 i=0; i<6; i++)
\r
2545 v3f dir_f = v3f(dirs[i].X,
\r
2546 dirs[i].Y, dirs[i].Z);
\r
2547 v3f centerpoint = npf + dir_f * BS/2;
\r
2549 (centerpoint - camera_position).getLength();
\r
2551 if(distance < mindistance)
\r
2553 core::CMatrix4<f32> m;
\r
2554 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2556 // This is the back face
\r
2557 v3f corners[2] = {
\r
2558 v3f(BS/2, BS/2, BS/2),
\r
2559 v3f(-BS/2, -BS/2, BS/2+d)
\r
2562 for(u16 j=0; j<2; j++)
\r
2564 m.rotateVect(corners[j]);
\r
2565 corners[j] += npf;
\r
2568 core::aabbox3d<f32> facebox(corners[0]);
\r
2569 facebox.addInternalPoint(corners[1]);
\r
2571 if(facebox.intersectsWithLine(shootline))
\r
2575 neighbourpos = np + dirs[i];
\r
2576 mindistance = distance;
\r
2578 //nodehilightbox = facebox;
\r
2580 const float d = 0.502;
\r
2581 core::aabbox3d<f32> nodebox
\r
2582 (-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
\r
2583 v3f nodepos_f = intToFloat(nodepos);
\r
2584 nodebox.MinEdge += nodepos_f;
\r
2585 nodebox.MaxEdge += nodepos_f;
\r
2586 nodehilightbox = nodebox;
\r
2588 } // if distance < mindistance
\r
2590 } // regular block
\r
2593 static float nodig_delay_counter = 0.0;
\r
2597 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2599 static float dig_time = 0.0;
\r
2600 static u16 dig_index = 0;
\r
2602 // Visualize selection
\r
2604 hilightboxes.push_back(nodehilightbox);
\r
2608 if(g_input->getLeftReleased())
\r
2610 client.clearTempMod(nodepos);
\r
2614 if(nodig_delay_counter > 0.0)
\r
2616 nodig_delay_counter -= dtime;
\r
2620 if(nodepos != nodepos_old)
\r
2622 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2623 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2625 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2627 client.clearTempMod(nodepos_old);
\r
2632 if(g_input->getLeftClicked() ||
\r
2633 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2635 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2636 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2638 if(g_input->getLeftClicked())
\r
2640 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2642 if(g_input->getLeftState())
\r
2644 MapNode n = client.getNode(nodepos);
\r
2646 // Get tool name. Default is "" = bare hands
\r
2647 std::string toolname = "";
\r
2648 InventoryList *mlist = local_inventory.getList("main");
\r
2651 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2652 if(item && (std::string)item->getName() == "ToolItem")
\r
2654 ToolItem *titem = (ToolItem*)item;
\r
2655 toolname = titem->getToolName();
\r
2659 // Get digging properties for material and tool
\r
2660 u8 material = n.d;
\r
2661 DiggingProperties prop =
\r
2662 getDiggingProperties(material, toolname);
\r
2664 float dig_time_complete = 0.0;
\r
2666 if(prop.diggable == false)
\r
2668 /*dstream<<"Material "<<(int)material
\r
2669 <<" not diggable with \""
\r
2670 <<toolname<<"\""<<std::endl;*/
\r
2671 // I guess nobody will wait for this long
\r
2672 dig_time_complete = 10000000.0;
\r
2676 dig_time_complete = prop.time;
\r
2679 if(dig_time_complete >= 0.001)
\r
2681 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2682 * dig_time/dig_time_complete);
\r
2684 // This is for torches
\r
2687 dig_index = CRACK_ANIMATION_LENGTH;
\r
2690 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2692 //TimeTaker timer("client.setTempMod");
\r
2693 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2694 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2698 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2699 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2700 client.clearTempMod(nodepos);
\r
2701 client.removeNode(nodepos);
\r
2705 nodig_delay_counter = dig_time_complete
\r
2706 / (float)CRACK_ANIMATION_LENGTH;
\r
2708 // We don't want a corresponding delay to
\r
2709 // very time consuming nodes
\r
2710 if(nodig_delay_counter > 0.5)
\r
2712 nodig_delay_counter = 0.5;
\r
2714 // We want a slight delay to very little
\r
2715 // time consuming nodes
\r
2716 float mindelay = 0.15;
\r
2717 if(nodig_delay_counter < mindelay)
\r
2719 nodig_delay_counter = mindelay;
\r
2723 dig_time += dtime;
\r
2727 if(g_input->getRightClicked())
\r
2729 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2730 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2733 nodepos_old = nodepos;
\r
2738 } // selected_object == NULL
\r
2740 g_input->resetLeftClicked();
\r
2741 g_input->resetRightClicked();
\r
2743 if(g_input->getLeftReleased())
\r
2745 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2747 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2749 if(g_input->getRightReleased())
\r
2751 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2755 g_input->resetLeftReleased();
\r
2756 g_input->resetRightReleased();
\r
2759 Calculate stuff for drawing
\r
2762 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2764 u32 daynight_ratio = client.getDayNightRatio();
\r
2765 /*video::SColor bgcolor = video::SColor(
\r
2767 skycolor.getRed() * daynight_ratio / 1000,
\r
2768 skycolor.getGreen() * daynight_ratio / 1000,
\r
2769 skycolor.getBlue() * daynight_ratio / 1000);*/
\r
2771 u8 l = decode_light((daynight_ratio * LIGHT_SUN) / 1000);
\r
2772 video::SColor bgcolor = video::SColor(
\r
2774 skycolor.getRed() * l / 255,
\r
2775 skycolor.getGreen() * l / 255,
\r
2776 skycolor.getBlue() * l / 255);
\r
2782 if(g_settings.getBool("enable_fog") == true)
\r
2784 //f32 range = draw_control.wanted_range * BS + MAP_BLOCKSIZE/2*BS;
\r
2785 f32 range = draw_control.wanted_range * BS + MAP_BLOCKSIZE/3*BS;
\r
2786 if(draw_control.range_all)
\r
2787 range = 100000*BS;
\r
2791 video::EFT_FOG_LINEAR,
\r
2795 false, // pixel fog
\r
2796 false // range fog
\r
2802 Update gui stuff (0ms)
\r
2805 //TimeTaker guiupdatetimer("Gui updating");
\r
2808 wchar_t temptext[150];
\r
2810 static float drawtime_avg = 0;
\r
2811 drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
\r
2812 static float beginscenetime_avg = 0;
\r
2813 beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
\r
2814 static float scenetime_avg = 0;
\r
2815 scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
\r
2816 static float endscenetime_avg = 0;
\r
2817 endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;
\r
2819 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2821 L", R: range_all=%i"
\r
2823 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2825 draw_control.range_all,
\r
2827 beginscenetime_avg,
\r
2832 guitext->setText(temptext);
\r
2836 wchar_t temptext[150];
\r
2837 swprintf(temptext, 150,
\r
2838 L"(% .1f, % .1f, % .1f)"
\r
2839 L" (% .3f < btime_jitter < % .3f"
\r
2840 L", dtime_jitter = % .1f %%"
\r
2841 L", v_range = %.1f)",
\r
2842 player_position.X/BS,
\r
2843 player_position.Y/BS,
\r
2844 player_position.Z/BS,
\r
2845 busytime_jitter1_min_sample,
\r
2846 busytime_jitter1_max_sample,
\r
2847 dtime_jitter1_max_fraction * 100.0,
\r
2848 draw_control.wanted_range
\r
2851 guitext2->setText(temptext);
\r
2855 guitext_info->setText(infotext.c_str());
\r
2859 Get chat messages from client
\r
2862 // Get new messages
\r
2863 std::wstring message;
\r
2864 while(client.getChatMessage(message))
\r
2866 chat_lines.push_back(ChatLine(message));
\r
2867 /*if(chat_lines.size() > 6)
\r
2869 core::list<ChatLine>::Iterator
\r
2870 i = chat_lines.begin();
\r
2871 chat_lines.erase(i);
\r
2874 // Append them to form the whole static text and throw
\r
2875 // it to the gui element
\r
2876 std::wstring whole;
\r
2877 // This will correspond to the line number counted from
\r
2878 // top to bottom, from size-1 to 0
\r
2879 s16 line_number = chat_lines.size();
\r
2880 // Count of messages to be removed from the top
\r
2881 u16 to_be_removed_count = 0;
\r
2882 for(core::list<ChatLine>::Iterator
\r
2883 i = chat_lines.begin();
\r
2884 i != chat_lines.end(); i++)
\r
2886 // After this, line number is valid for this loop
\r
2889 (*i).age += dtime;
\r
2891 This results in a maximum age of 60*6 to the
\r
2892 lowermost line and a maximum of 6 lines
\r
2894 float allowed_age = (6-line_number) * 60.0;
\r
2896 if((*i).age > allowed_age)
\r
2898 to_be_removed_count++;
\r
2901 whole += (*i).text + L'\n';
\r
2903 for(u16 i=0; i<to_be_removed_count; i++)
\r
2905 core::list<ChatLine>::Iterator
\r
2906 it = chat_lines.begin();
\r
2907 chat_lines.erase(it);
\r
2909 guitext_chat->setText(whole.c_str());
\r
2910 // Update gui element size and position
\r
2911 core::rect<s32> rect(
\r
2913 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2914 screensize.X - 10,
\r
2917 guitext_chat->setRelativePosition(rect);
\r
2919 if(chat_lines.size() == 0)
\r
2920 guitext_chat->setVisible(false);
\r
2922 guitext_chat->setVisible(true);
\r
2929 static u16 old_selected_item = 65535;
\r
2930 if(client.getLocalInventoryUpdated()
\r
2931 || g_selected_item != old_selected_item)
\r
2933 old_selected_item = g_selected_item;
\r
2934 //std::cout<<"Updating local inventory"<<std::endl;
\r
2935 client.getLocalInventory(local_inventory);
\r
2936 quick_inventory->setSelection(g_selected_item);
\r
2937 quick_inventory->update();
\r
2941 Send actions returned by the inventory menu
\r
2943 while(inventory_action_queue.size() != 0)
\r
2945 InventoryAction *a = inventory_action_queue.pop_front();
\r
2947 client.sendInventoryAction(a);
\r
2956 TimeTaker drawtimer("Drawing");
\r
2960 TimeTaker timer("beginScene");
\r
2961 driver->beginScene(true, true, bgcolor);
\r
2962 //driver->beginScene(false, true, bgcolor);
\r
2963 beginscenetime = timer.stop(true);
\r
2968 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2971 TimeTaker timer("smgr");
\r
2973 scenetime = timer.stop(true);
\r
2977 //TimeTaker timer9("auxiliary drawings");
\r
2981 //TimeTaker //timer10("//timer10");
\r
2983 video::SMaterial m;
\r
2984 //m.Thickness = 10;
\r
2986 m.Lighting = false;
\r
2987 driver->setMaterial(m);
\r
2989 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2991 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2992 i != hilightboxes.end(); i++)
\r
2994 /*std::cout<<"hilightbox min="
\r
2995 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2997 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2999 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
3005 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
3006 displaycenter + core::vector2d<s32>(10,0),
\r
3007 video::SColor(255,255,255,255));
\r
3008 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
3009 displaycenter + core::vector2d<s32>(0,10),
\r
3010 video::SColor(255,255,255,255));
\r
3015 //TimeTaker //timer11("//timer11");
\r
3021 guienv->drawAll();
\r
3025 TimeTaker timer("endScene");
\r
3026 driver->endScene();
\r
3027 endscenetime = timer.stop(true);
\r
3030 drawtime = drawtimer.stop(true);
\r
3036 static s16 lastFPS = 0;
\r
3037 //u16 fps = driver->getFPS();
\r
3038 u16 fps = (1.0/dtime_avg1);
\r
3040 if (lastFPS != fps)
\r
3042 core::stringw str = L"Minetest [";
\r
3043 str += driver->getName();
\r
3047 device->setWindowCaption(str.c_str());
\r
3053 device->yield();*/
\r
3056 delete quick_inventory;
\r
3059 Disable texture fetches and other stuff that is queued
\r
3060 to be processed by the main loop.
\r
3062 This has to be done before client goes out of scope.
\r
3064 g_irrlicht->Shutdown(true);
\r
3066 } // client and server are deleted at this point
\r
3069 catch(con::PeerNotFoundException &e)
\r
3071 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
3072 error_message = L"Connection timed out.";
\r
3075 } // Menu-game loop
\r
3080 In the end, delete the Irrlicht device.
\r
3085 Update configuration file
\r
3087 /*if(configpath != "")
\r
3089 g_settings.updateConfigFile(configpath.c_str());
\r
3092 END_DEBUG_EXCEPTION_HANDLER
\r
3094 debugstreams_deinit();
\r