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
323 * Remove all kinds of systems that are made redundant by the new map
\r
325 - Sector heightmaps? At least they should be made redundant.
\r
327 * Do something about AttributeDatabase/List being too slow
\r
328 * Save chunk metadata on disk
\r
329 * Change water side textures so that buggy water doesn't look bad
\r
330 * Make server find the spawning place from the real map data, not from
\r
332 * only_from_disk doesn't work that well anymore
\r
333 * Make the generator to run in background and not blocking block
\r
334 placement and transfer
\r
335 * Fix the strange mineral occurences
\r
336 * When the map is generated and a place is found for the player, the
\r
337 first chunk is actually still volatile and will have stuff still
\r
338 changed after spawning, which creates a lot of glitches.
\r
339 - This is partly fixed by now allowing only 2-sector deeep
\r
340 modification of volatile chunks. But it should still be fixed?
\r
341 - How about checking that the neighbors are fully generated too and
\r
342 generate them when the middle piece is needed
\r
343 - This is very slow
\r
344 - How about just enabling changed_blocks properly
\r
345 - This is probably a good idea
\r
346 - The server has to make sure the spawn point is not at the
\r
347 changing borders of a chunk
\r
348 * Add some kind of erosion and other stuff that now is possible
\r
349 * Make client to fetch stuff asynchronously
\r
350 - Needs method SyncProcessData
\r
351 * What is the problem with the server constantly saving one or a few
\r
352 blocks? List the first saved block, maybe it explains.
\r
353 * Water doesn't start flowing after map generation like it should
\r
354 * Better water generation
\r
356 ======================================================================
\r
361 Setting this to 1 enables a special camera mode that forces
\r
362 the renderers to think that the camera statically points from
\r
363 the starting place to a static direction.
\r
365 This allows one to move around with the player and see what
\r
366 is actually drawn behind solid things and behind the player.
\r
368 #define FIELD_OF_VIEW_TEST 0
\r
372 #pragma message ("Disabling unit tests")
\r
374 #warning "Disabling unit tests"
\r
376 // Disable unit tests
\r
377 #define ENABLE_TESTS 0
\r
379 // Enable unit tests
\r
380 #define ENABLE_TESTS 1
\r
384 #pragma comment(lib, "Irrlicht.lib")
\r
385 //#pragma comment(lib, "jthread.lib")
\r
386 #pragma comment(lib, "zlibwapi.lib")
\r
387 #pragma comment(lib, "Shell32.lib")
\r
388 // This would get rid of the console window
\r
389 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
392 #include <iostream>
\r
394 #include <jmutexautolock.h>
\r
395 #include <locale.h>
\r
396 #include "common_irrlicht.h"
\r
399 #include "player.h"
\r
402 #include "environment.h"
\r
403 #include "server.h"
\r
404 #include "client.h"
\r
405 #include "serialization.h"
\r
406 #include "constants.h"
\r
407 #include "strfnd.h"
\r
408 #include "porting.h"
\r
409 #include "irrlichtwrapper.h"
\r
410 #include "gettime.h"
\r
411 #include "porting.h"
\r
412 #include "guiPauseMenu.h"
\r
413 #include "guiInventoryMenu.h"
\r
414 #include "guiTextInputMenu.h"
\r
415 #include "materials.h"
\r
416 #include "guiMessageMenu.h"
\r
417 #include "filesys.h"
\r
418 #include "config.h"
\r
419 #include "guiMainMenu.h"
\r
420 #include "mineral.h"
\r
422 IrrlichtWrapper *g_irrlicht;
\r
424 MapDrawControl draw_control;
\r
428 These are loaded from the config file.
\r
431 Settings g_settings;
\r
433 extern void set_default_settings();
\r
439 IrrlichtDevice *g_device = NULL;
\r
440 Client *g_client = NULL;
\r
446 gui::IGUIEnvironment* guienv = NULL;
\r
447 gui::IGUIStaticText *guiroot = NULL;
\r
449 class MainMenuManager : public IMenuManager
\r
452 virtual void createdMenu(GUIModalMenu *menu)
\r
454 for(core::list<GUIModalMenu*>::Iterator
\r
455 i = m_stack.begin();
\r
456 i != m_stack.end(); i++)
\r
458 assert(*i != menu);
\r
461 if(m_stack.size() != 0)
\r
462 (*m_stack.getLast())->setVisible(false);
\r
463 m_stack.push_back(menu);
\r
466 virtual void deletingMenu(GUIModalMenu *menu)
\r
468 // Remove all entries if there are duplicates
\r
469 bool removed_entry;
\r
471 removed_entry = false;
\r
472 for(core::list<GUIModalMenu*>::Iterator
\r
473 i = m_stack.begin();
\r
474 i != m_stack.end(); i++)
\r
479 removed_entry = true;
\r
483 }while(removed_entry);
\r
485 /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
\r
486 assert(*i == menu);
\r
487 m_stack.erase(i);*/
\r
489 if(m_stack.size() != 0)
\r
490 (*m_stack.getLast())->setVisible(true);
\r
495 return m_stack.size();
\r
498 core::list<GUIModalMenu*> m_stack;
\r
501 MainMenuManager g_menumgr;
\r
503 bool noMenuActive()
\r
505 return (g_menumgr.menuCount() == 0);
\r
508 bool g_disconnect_requested = false;
\r
510 class MainGameCallback : public IGameCallback
\r
513 virtual void exitToOS()
\r
515 g_device->closeDevice();
\r
518 virtual void disconnect()
\r
520 g_disconnect_requested = true;
\r
524 MainGameCallback g_gamecallback;
\r
526 // Inventory actions from the menu are buffered here before sending
\r
527 Queue<InventoryAction*> inventory_action_queue;
\r
528 // This is a copy of the inventory that the client's environment has
\r
529 Inventory local_inventory;
\r
531 u16 g_selected_item = 0;
\r
538 std::ostream *dout_con_ptr = &dummyout;
\r
539 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
540 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
541 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
542 //std::ostream *dout_con_ptr = &dstream;
\r
543 //std::ostream *derr_con_ptr = &dstream;
\r
546 std::ostream *dout_server_ptr = &dstream;
\r
547 std::ostream *derr_server_ptr = &dstream;
\r
550 std::ostream *dout_client_ptr = &dstream;
\r
551 std::ostream *derr_client_ptr = &dstream;
\r
554 gettime.h implementation
\r
560 Use irrlicht because it is more precise than porting.h's
\r
563 if(g_irrlicht == NULL)
\r
565 return g_irrlicht->getTime();
\r
572 struct TextDestSign : public TextDest
\r
574 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
576 m_blockpos = blockpos;
\r
580 void gotText(std::wstring text)
\r
582 std::string ntext = wide_to_narrow(text);
\r
583 dstream<<"Changing text of a sign object: "
\r
584 <<ntext<<std::endl;
\r
585 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
593 struct TextDestChat : public TextDest
\r
595 TextDestChat(Client *client)
\r
599 void gotText(std::wstring text)
\r
601 m_client->sendChatMessage(text);
\r
602 m_client->addChatMessage(text);
\r
608 class MyEventReceiver : public IEventReceiver
\r
611 // This is the one method that we have to implement
\r
612 virtual bool OnEvent(const SEvent& event)
\r
615 React to nothing here if a menu is active
\r
617 if(noMenuActive() == false)
\r
623 // Remember whether each key is down or up
\r
624 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
626 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
628 if(event.KeyInput.PressedDown)
\r
630 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
636 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
638 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
640 dstream<<DTIME<<"MyEventReceiver: "
\r
641 <<"Launching pause menu"<<std::endl;
\r
642 // It will delete itself by itself
\r
643 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
644 &g_menumgr))->drop();
\r
647 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
649 dstream<<DTIME<<"MyEventReceiver: "
\r
650 <<"Launching inventory"<<std::endl;
\r
651 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
652 &local_inventory, &inventory_action_queue,
\r
653 &g_menumgr))->drop();
\r
656 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
658 TextDest *dest = new TextDestChat(g_client);
\r
660 (new GUITextInputMenu(guienv, guiroot, -1,
\r
666 // Material selection
\r
667 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
669 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
672 g_selected_item = 0;
\r
673 dstream<<DTIME<<"Selected item: "
\r
674 <<g_selected_item<<std::endl;
\r
677 // Viewing range selection
\r
678 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
680 if(draw_control.range_all)
\r
682 draw_control.range_all = false;
\r
683 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
687 draw_control.range_all = true;
\r
688 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
692 // Print debug stacks
\r
693 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
695 dstream<<"-----------------------------------------"
\r
697 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
698 dstream<<"-----------------------------------------"
\r
700 debug_stacks_print();
\r
705 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
707 if(noMenuActive() == false)
\r
709 left_active = false;
\r
710 middle_active = false;
\r
711 right_active = false;
\r
715 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
716 left_active = event.MouseInput.isLeftPressed();
\r
717 middle_active = event.MouseInput.isMiddlePressed();
\r
718 right_active = event.MouseInput.isRightPressed();
\r
720 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
722 leftclicked = true;
\r
724 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
726 rightclicked = true;
\r
728 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
730 leftreleased = true;
\r
732 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
734 rightreleased = true;
\r
736 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
738 /*dstream<<"event.MouseInput.Wheel="
\r
739 <<event.MouseInput.Wheel<<std::endl;*/
\r
740 if(event.MouseInput.Wheel < 0)
\r
742 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
745 g_selected_item = 0;
\r
747 else if(event.MouseInput.Wheel > 0)
\r
749 if(g_selected_item > 0)
\r
752 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
761 // This is used to check whether a key is being held down
\r
762 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
764 return keyIsDown[keyCode];
\r
769 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
770 keyIsDown[i] = false;
\r
772 leftclicked = false;
\r
773 rightclicked = false;
\r
774 leftreleased = false;
\r
775 rightreleased = false;
\r
777 left_active = false;
\r
778 middle_active = false;
\r
779 right_active = false;
\r
790 bool rightreleased;
\r
793 bool middle_active;
\r
797 // We use this array to store the current state of each key
\r
798 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
801 IrrlichtDevice *m_device;
\r
810 virtual ~InputHandler()
\r
814 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
816 virtual v2s32 getMousePos() = 0;
\r
817 virtual void setMousePos(s32 x, s32 y) = 0;
\r
819 virtual bool getLeftState() = 0;
\r
820 virtual bool getRightState() = 0;
\r
822 virtual bool getLeftClicked() = 0;
\r
823 virtual bool getRightClicked() = 0;
\r
824 virtual void resetLeftClicked() = 0;
\r
825 virtual void resetRightClicked() = 0;
\r
827 virtual bool getLeftReleased() = 0;
\r
828 virtual bool getRightReleased() = 0;
\r
829 virtual void resetLeftReleased() = 0;
\r
830 virtual void resetRightReleased() = 0;
\r
832 virtual void step(float dtime) {};
\r
834 virtual void clear() {};
\r
837 InputHandler *g_input = NULL;
\r
839 class RealInputHandler : public InputHandler
\r
842 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
844 m_receiver(receiver)
\r
847 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
849 return m_receiver->IsKeyDown(keyCode);
\r
851 virtual v2s32 getMousePos()
\r
853 return m_device->getCursorControl()->getPosition();
\r
855 virtual void setMousePos(s32 x, s32 y)
\r
857 m_device->getCursorControl()->setPosition(x, y);
\r
860 virtual bool getLeftState()
\r
862 return m_receiver->left_active;
\r
864 virtual bool getRightState()
\r
866 return m_receiver->right_active;
\r
869 virtual bool getLeftClicked()
\r
871 return m_receiver->leftclicked;
\r
873 virtual bool getRightClicked()
\r
875 return m_receiver->rightclicked;
\r
877 virtual void resetLeftClicked()
\r
879 m_receiver->leftclicked = false;
\r
881 virtual void resetRightClicked()
\r
883 m_receiver->rightclicked = false;
\r
886 virtual bool getLeftReleased()
\r
888 return m_receiver->leftreleased;
\r
890 virtual bool getRightReleased()
\r
892 return m_receiver->rightreleased;
\r
894 virtual void resetLeftReleased()
\r
896 m_receiver->leftreleased = false;
\r
898 virtual void resetRightReleased()
\r
900 m_receiver->rightreleased = false;
\r
905 resetRightClicked();
\r
906 resetLeftClicked();
\r
909 IrrlichtDevice *m_device;
\r
910 MyEventReceiver *m_receiver;
\r
913 class RandomInputHandler : public InputHandler
\r
916 RandomInputHandler()
\r
918 leftclicked = false;
\r
919 rightclicked = false;
\r
920 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
921 keydown[i] = false;
\r
923 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
925 return keydown[keyCode];
\r
927 virtual v2s32 getMousePos()
\r
931 virtual void setMousePos(s32 x, s32 y)
\r
933 mousepos = v2s32(x,y);
\r
936 virtual bool getLeftState()
\r
940 virtual bool getRightState()
\r
945 virtual bool getLeftClicked()
\r
947 return leftclicked;
\r
949 virtual bool getRightClicked()
\r
951 return rightclicked;
\r
953 virtual void resetLeftClicked()
\r
955 leftclicked = false;
\r
957 virtual void resetRightClicked()
\r
959 rightclicked = false;
\r
962 virtual bool getLeftReleased()
\r
966 virtual bool getRightReleased()
\r
970 virtual void resetLeftReleased()
\r
973 virtual void resetRightReleased()
\r
977 virtual void step(float dtime)
\r
980 static float counter1 = 0;
\r
984 counter1 = 0.1*Rand(1,10);
\r
985 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
986 g_selected_material++;
\r
988 g_selected_material = 0;*/
\r
989 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
992 g_selected_item = 0;
\r
996 static float counter1 = 0;
\r
1000 counter1 = 0.1*Rand(1, 40);
\r
1001 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
1005 static float counter1 = 0;
\r
1006 counter1 -= dtime;
\r
1007 if(counter1 < 0.0)
\r
1009 counter1 = 0.1*Rand(1, 40);
\r
1010 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\r
1014 static float counter1 = 0;
\r
1015 counter1 -= dtime;
\r
1016 if(counter1 < 0.0)
\r
1018 counter1 = 0.1*Rand(1, 40);
\r
1019 keydown[irr::KEY_KEY_W] = !keydown[irr::KEY_KEY_W];
\r
1023 static float counter1 = 0;
\r
1024 counter1 -= dtime;
\r
1025 if(counter1 < 0.0)
\r
1027 counter1 = 0.1*Rand(1, 40);
\r
1028 keydown[irr::KEY_KEY_A] = !keydown[irr::KEY_KEY_A];
\r
1032 static float counter1 = 0;
\r
1033 counter1 -= dtime;
\r
1034 if(counter1 < 0.0)
\r
1036 counter1 = 0.1*Rand(1, 20);
\r
1037 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
1041 static float counter1 = 0;
\r
1042 counter1 -= dtime;
\r
1043 if(counter1 < 0.0)
\r
1045 counter1 = 0.1*Rand(1, 30);
\r
1046 leftclicked = true;
\r
1050 static float counter1 = 0;
\r
1051 counter1 -= dtime;
\r
1052 if(counter1 < 0.0)
\r
1054 counter1 = 0.1*Rand(1, 20);
\r
1055 rightclicked = true;
\r
1058 mousepos += mousespeed;
\r
1061 s32 Rand(s32 min, s32 max)
\r
1063 return (myrand()%(max-min+1))+min;
\r
1066 bool keydown[KEY_KEY_CODES_COUNT];
\r
1070 bool rightclicked;
\r
1073 void updateViewingRange(f32 frametime_in, Client *client)
\r
1075 if(draw_control.range_all == true)
\r
1078 static f32 added_frametime = 0;
\r
1079 static s16 added_frames = 0;
\r
1081 added_frametime += frametime_in;
\r
1082 added_frames += 1;
\r
1084 // Actually this counter kind of sucks because frametime is busytime
\r
1085 static f32 counter = 0;
\r
1086 counter -= frametime_in;
\r
1092 /*dstream<<__FUNCTION_NAME
\r
1093 <<": Collected "<<added_frames<<" frames, total of "
\r
1094 <<added_frametime<<"s."<<std::endl;*/
\r
1096 /*dstream<<"draw_control.blocks_drawn="
\r
1097 <<draw_control.blocks_drawn
\r
1098 <<", draw_control.blocks_would_have_drawn="
\r
1099 <<draw_control.blocks_would_have_drawn
\r
1102 float range_min = g_settings.getS16("viewing_range_nodes_min");
\r
1103 float range_max = g_settings.getS16("viewing_range_nodes_max");
\r
1105 draw_control.wanted_min_range = range_min;
\r
1106 draw_control.wanted_max_blocks = (1.2*draw_control.blocks_drawn)+1;
\r
1108 float block_draw_ratio = 1.0;
\r
1109 if(draw_control.blocks_would_have_drawn != 0)
\r
1111 block_draw_ratio = (float)draw_control.blocks_drawn
\r
1112 / (float)draw_control.blocks_would_have_drawn;
\r
1115 // Calculate the average frametime in the case that all wanted
\r
1116 // blocks had been drawn
\r
1117 f32 frametime = added_frametime / added_frames / block_draw_ratio;
\r
1119 added_frametime = 0.0;
\r
1122 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
1123 float wanted_frametime = 1.0 / wanted_fps;
\r
1125 f32 wanted_frametime_change = wanted_frametime - frametime;
\r
1126 //dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
\r
1128 // If needed frametime change is very small, just return
\r
1129 if(fabs(wanted_frametime_change) < wanted_frametime*0.2)
\r
1131 //dstream<<"ignoring small wanted_frametime_change"<<std::endl;
\r
1135 float range = draw_control.wanted_range;
\r
1136 float new_range = range;
\r
1138 static s16 range_old = 0;
\r
1139 static f32 frametime_old = 0;
\r
1141 float d_range = range - range_old;
\r
1142 f32 d_frametime = frametime - frametime_old;
\r
1143 // A sane default of 30ms per 50 nodes of range
\r
1144 static f32 time_per_range = 30. / 50;
\r
1147 time_per_range = d_frametime / d_range;
\r
1150 // The minimum allowed calculated frametime-range derivative:
\r
1151 // Practically this sets the maximum speed of changing the range.
\r
1152 // The lower this value, the higher the maximum changing speed.
\r
1153 // A low value here results in wobbly range (0.001)
\r
1154 // A high value here results in slow changing range (0.0025)
\r
1155 // SUGG: This could be dynamically adjusted so that when
\r
1156 // the camera is turning, this is lower
\r
1157 //float min_time_per_range = 0.0015;
\r
1158 float min_time_per_range = 0.0010;
\r
1159 //float min_time_per_range = 0.05 / range;
\r
1160 if(time_per_range < min_time_per_range)
\r
1162 time_per_range = min_time_per_range;
\r
1163 //dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
\r
1167 //dstream<<"time_per_range="<<time_per_range<<std::endl;
\r
1170 f32 wanted_range_change = wanted_frametime_change / time_per_range;
\r
1171 // Dampen the change a bit to kill oscillations
\r
1172 //wanted_range_change *= 0.9;
\r
1173 //wanted_range_change *= 0.75;
\r
1174 wanted_range_change *= 0.5;
\r
1175 //dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
\r
1177 // If needed range change is very small, just return
\r
1178 if(fabs(wanted_range_change) < 0.001)
\r
1180 //dstream<<"ignoring small wanted_range_change"<<std::endl;
\r
1184 new_range += wanted_range_change;
\r
1185 //dstream<<"new_range="<<new_range/*<<std::endl*/;
\r
1187 //float new_range_unclamped = new_range;
\r
1188 if(new_range < range_min)
\r
1189 new_range = range_min;
\r
1190 if(new_range > range_max)
\r
1191 new_range = range_max;
\r
1193 /*if(new_range != new_range_unclamped)
\r
1194 dstream<<", clamped to "<<new_range<<std::endl;
\r
1196 dstream<<std::endl;*/
\r
1198 draw_control.wanted_range = new_range;
\r
1200 range_old = new_range;
\r
1201 frametime_old = frametime;
\r
1204 class GUIQuickInventory : public IEventReceiver
\r
1207 GUIQuickInventory(
\r
1208 gui::IGUIEnvironment* env,
\r
1209 gui::IGUIElement* parent,
\r
1212 Inventory *inventory):
\r
1213 m_itemcount(itemcount),
\r
1214 m_inventory(inventory)
\r
1216 core::rect<s32> imgsize(0,0,48,48);
\r
1217 core::rect<s32> textsize(0,0,48,16);
\r
1218 v2s32 spacing(0, 64);
\r
1219 for(s32 i=0; i<m_itemcount; i++)
\r
1221 m_images.push_back(env->addImage(
\r
1222 imgsize + pos + spacing*i
\r
1224 m_images[i]->setScaleImage(true);
\r
1225 m_texts.push_back(env->addStaticText(
\r
1227 textsize + pos + spacing*i,
\r
1230 m_texts[i]->setBackgroundColor(
\r
1231 video::SColor(128,0,0,0));
\r
1232 m_texts[i]->setTextAlignment(
\r
1233 gui::EGUIA_CENTER,
\r
1234 gui::EGUIA_UPPERLEFT);
\r
1238 ~GUIQuickInventory()
\r
1240 for(u32 i=0; i<m_texts.size(); i++)
\r
1242 m_texts[i]->remove();
\r
1244 for(u32 i=0; i<m_images.size(); i++)
\r
1246 m_images[i]->remove();
\r
1250 virtual bool OnEvent(const SEvent& event)
\r
1255 void setSelection(s32 i)
\r
1264 start = m_selection - m_itemcount / 2;
\r
1266 InventoryList *mainlist = m_inventory->getList("main");
\r
1268 for(s32 i=0; i<m_itemcount; i++)
\r
1270 s32 j = i + start;
\r
1272 if(j > (s32)mainlist->getSize() - 1)
\r
1273 j -= mainlist->getSize();
\r
1275 j += mainlist->getSize();
\r
1277 InventoryItem *item = mainlist->getItem(j);
\r
1281 m_images[i]->setImage(NULL);
\r
1284 if(m_selection == j)
\r
1285 swprintf(t, 10, L"<-");
\r
1287 swprintf(t, 10, L"");
\r
1288 m_texts[i]->setText(t);
\r
1290 // The next ifs will segfault with a NULL pointer
\r
1295 m_images[i]->setImage(item->getImage());
\r
1298 if(m_selection == j)
\r
1299 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1301 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1302 m_texts[i]->setText(t);
\r
1308 core::array<gui::IGUIStaticText*> m_texts;
\r
1309 core::array<gui::IGUIImage*> m_images;
\r
1310 Inventory *m_inventory;
\r
1321 ChatLine(const std::wstring &a_text):
\r
1327 std::wstring text;
\r
1330 // These are defined global so that they're not optimized too much.
\r
1331 // Can't change them to volatile.
\r
1336 std::string tempstring;
\r
1337 std::string tempstring2;
\r
1342 dstream<<"The following test should take around 20ms."<<std::endl;
\r
1343 TimeTaker timer("Testing std::string speed");
\r
1344 const u32 jj = 10000;
\r
1345 for(u32 j=0; j<jj; j++)
\r
1349 const u32 ii = 10;
\r
1350 for(u32 i=0; i<ii; i++){
\r
1351 tempstring2 += "asd";
\r
1353 for(u32 i=0; i<ii+1; i++){
\r
1354 tempstring += "asd";
\r
1355 if(tempstring == tempstring2)
\r
1361 dstream<<"All of the following tests should take around 100ms each."
\r
1365 TimeTaker timer("Testing floating-point conversion speed");
\r
1367 for(u32 i=0; i<4000000; i++){
\r
1374 TimeTaker timer("Testing floating-point vector speed");
\r
1376 tempv3f1 = v3f(1,2,3);
\r
1377 tempv3f2 = v3f(4,5,6);
\r
1378 for(u32 i=0; i<10000000; i++){
\r
1379 tempf += tempv3f1.dotProduct(tempv3f2);
\r
1380 tempv3f2 += v3f(7,8,9);
\r
1385 TimeTaker timer("Testing core::map speed");
\r
1387 core::map<v2s16, f32> map1;
\r
1390 for(s16 y=0; y<ii; y++){
\r
1391 for(s16 x=0; x<ii; x++){
\r
1392 map1.insert(v2s16(x,y), tempf);
\r
1396 for(s16 y=ii-1; y>=0; y--){
\r
1397 for(s16 x=0; x<ii; x++){
\r
1398 tempf = map1[v2s16(x,y)];
\r
1404 dstream<<"Around 5000/ms should do well here."<<std::endl;
\r
1405 TimeTaker timer("Testing mutex speed");
\r
1418 // Do at least 10ms
\r
1419 while(timer.getTime() < 10);
\r
1421 u32 dtime = timer.stop();
\r
1422 u32 per_ms = n / dtime;
\r
1423 std::cout<<"Done. "<<dtime<<"ms, "
\r
1424 <<per_ms<<"/ms"<<std::endl;
\r
1428 int main(int argc, char *argv[])
\r
1431 Parse command line
\r
1434 // List all allowed options
\r
1435 core::map<std::string, ValueSpec> allowed_options;
\r
1436 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1437 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1438 "Run server directly"));
\r
1439 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1440 "Load configuration from specified file"));
\r
1441 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1442 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1443 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1444 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1445 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1446 allowed_options.insert("map-dir", ValueSpec(VALUETYPE_STRING));
\r
1448 allowed_options.insert("dstream-on-stderr", ValueSpec(VALUETYPE_FLAG));
\r
1450 allowed_options.insert("speedtests", ValueSpec(VALUETYPE_FLAG));
\r
1452 Settings cmd_args;
\r
1454 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1456 if(ret == false || cmd_args.getFlag("help"))
\r
1458 dstream<<"Allowed options:"<<std::endl;
\r
1459 for(core::map<std::string, ValueSpec>::Iterator
\r
1460 i = allowed_options.getIterator();
\r
1461 i.atEnd() == false; i++)
\r
1463 dstream<<" --"<<i.getNode()->getKey();
\r
1464 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1469 dstream<<" <value>";
\r
1471 dstream<<std::endl;
\r
1473 if(i.getNode()->getValue().help != NULL)
\r
1475 dstream<<" "<<i.getNode()->getValue().help
\r
1480 return cmd_args.getFlag("help") ? 0 : 1;
\r
1484 Low-level initialization
\r
1487 bool disable_stderr = false;
\r
1489 if(cmd_args.getFlag("dstream-on-stderr") == false)
\r
1490 disable_stderr = true;
\r
1493 // Initialize debug streams
\r
1494 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1495 // Initialize debug stacks
\r
1496 debug_stacks_init();
\r
1498 DSTACK(__FUNCTION_NAME);
\r
1500 porting::initializePaths();
\r
1501 // Create user data directory
\r
1502 fs::CreateDir(porting::path_userdata);
\r
1504 // C-style stuff initialization
\r
1505 initializeMaterialProperties();
\r
1508 BEGIN_DEBUG_EXCEPTION_HANDLER
\r
1510 // Print startup message
\r
1511 dstream<<DTIME<<"minetest-c55"
\r
1512 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1513 <<", "<<BUILD_INFO
\r
1517 Basic initialization
\r
1520 // Initialize default settings
\r
1521 set_default_settings();
\r
1523 // Set locale. This is for forcing '.' as the decimal point.
\r
1524 std::locale::global(std::locale("C"));
\r
1525 // This enables printing all characters in bitmap font
\r
1526 setlocale(LC_CTYPE, "en_US");
\r
1528 // Initialize sockets
\r
1530 atexit(sockets_cleanup);
\r
1540 // Path of configuration file in use
\r
1541 std::string configpath = "";
\r
1543 if(cmd_args.exists("config"))
\r
1545 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1548 dstream<<"Could not read configuration from \""
\r
1549 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1552 configpath = cmd_args.get("config");
\r
1556 core::array<std::string> filenames;
\r
1557 filenames.push_back(porting::path_userdata + "/minetest.conf");
\r
1558 #ifdef RUN_IN_PLACE
\r
1559 filenames.push_back(porting::path_userdata + "/../minetest.conf");
\r
1562 for(u32 i=0; i<filenames.size(); i++)
\r
1564 bool r = g_settings.readConfigFile(filenames[i].c_str());
\r
1567 configpath = filenames[i];
\r
1573 // Initialize random seed
\r
1578 Pre-initialize some stuff with a dummy irrlicht wrapper.
\r
1580 These are needed for unit tests at least.
\r
1583 IIrrlichtWrapper irrlicht_dummy;
\r
1585 init_mapnode(&irrlicht_dummy);
\r
1590 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1591 || cmd_args.getFlag("enable-unittests") == true)
\r
1596 // Read map parameters from settings
\r
1598 HMParams hm_params;
\r
1599 /*hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1600 hm_params.randmax = g_settings.get("height_randmax");
\r
1601 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1602 hm_params.base = g_settings.get("height_base");*/
\r
1604 MapParams map_params;
\r
1605 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1606 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1614 if(cmd_args.exists("port"))
\r
1615 port = cmd_args.getU16("port");
\r
1616 else if(cmd_args.exists("port"))
\r
1617 port = g_settings.getU16("port");
\r
1620 std::string map_dir = porting::path_userdata+"/map";
\r
1621 if(cmd_args.exists("map-dir"))
\r
1622 map_dir = cmd_args.get("map-dir");
\r
1623 else if(g_settings.exists("map-dir"))
\r
1624 map_dir = g_settings.get("map-dir");
\r
1626 // Run dedicated server if asked to
\r
1627 if(cmd_args.getFlag("server"))
\r
1629 DSTACK("Dedicated server branch");
\r
1632 Server server(map_dir.c_str(), hm_params, map_params);
\r
1633 server.start(port);
\r
1636 dedicated_server_loop(server);
\r
1645 // Address to connect to
\r
1646 std::string address = "";
\r
1648 if(cmd_args.exists("address"))
\r
1650 address = cmd_args.get("address");
\r
1654 address = g_settings.get("address");
\r
1657 std::string playername = g_settings.get("name");
\r
1660 Resolution selection
\r
1663 bool fullscreen = false;
\r
1664 u16 screenW = g_settings.getU16("screenW");
\r
1665 u16 screenH = g_settings.getU16("screenH");
\r
1669 MyEventReceiver receiver;
\r
1671 video::E_DRIVER_TYPE driverType;
\r
1674 //driverType = video::EDT_DIRECT3D9;
\r
1675 driverType = video::EDT_OPENGL;
\r
1677 driverType = video::EDT_OPENGL;
\r
1678 //driverType = video::EDT_BURNINGSVIDEO;
\r
1681 // create device and exit if creation failed
\r
1683 IrrlichtDevice *device;
\r
1684 device = createDevice(driverType,
\r
1685 core::dimension2d<u32>(screenW, screenH),
\r
1686 16, fullscreen, false, false, &receiver);
\r
1689 return 1; // could not create selected driver.
\r
1691 g_device = device;
\r
1692 g_irrlicht = new IrrlichtWrapper(device);
\r
1695 Speed tests (done after irrlicht is loaded to get timer)
\r
1697 if(cmd_args.getFlag("speedtests"))
\r
1699 dstream<<"Running speed tests"<<std::endl;
\r
1704 device->setResizable(true);
\r
1706 bool random_input = g_settings.getBool("random_input")
\r
1707 || cmd_args.getFlag("random-input");
\r
1709 g_input = new RandomInputHandler();
\r
1711 g_input = new RealInputHandler(device, &receiver);
\r
1714 Continue initialization
\r
1717 video::IVideoDriver* driver = device->getVideoDriver();
\r
1720 This changes the minimum allowed number of vertices in a VBO
\r
1722 //driver->setMinHardwareBufferVertexCount(50);
\r
1724 scene::ISceneManager* smgr = device->getSceneManager();
\r
1726 guienv = device->getGUIEnvironment();
\r
1727 gui::IGUISkin* skin = guienv->getSkin();
\r
1728 gui::IGUIFont* font = guienv->getFont(porting::getDataPath("fontlucida.png").c_str());
\r
1730 skin->setFont(font);
\r
1732 dstream<<"WARNING: Font file was not found."
\r
1733 " Using default font."<<std::endl;
\r
1734 // If font was not found, this will get us one
\r
1735 font = skin->getFont();
\r
1738 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1739 dstream<<"text_height="<<text_height<<std::endl;
\r
1741 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1742 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1743 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1744 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1745 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1746 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1749 Preload some textures and stuff
\r
1752 init_content_inventory_texture_paths();
\r
1753 init_mapnode(g_irrlicht);
\r
1754 init_mineral(g_irrlicht);
\r
1761 We need some kind of a root node to be able to add
\r
1762 custom gui elements directly on the screen.
\r
1763 Otherwise they won't be automatically drawn.
\r
1765 guiroot = guienv->addStaticText(L"",
\r
1766 core::rect<s32>(0, 0, 10000, 10000));
\r
1768 // First line of debug text
\r
1769 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1771 core::rect<s32>(5, 5, 795, 5+text_height),
\r
1773 // Second line of debug text
\r
1774 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1776 core::rect<s32>(5, 5+(text_height+5)*1, 795, (5+text_height)*2),
\r
1779 // At the middle of the screen
\r
1780 // Object infos are shown in this
\r
1781 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1783 core::rect<s32>(100, 70, 100+400, 70+(text_height+5)),
\r
1787 gui::IGUIStaticText *guitext_chat = guienv->addStaticText(
\r
1789 core::rect<s32>(0,0,0,0),
\r
1791 guitext_chat->setBackgroundColor(video::SColor(96,0,0,0));
\r
1792 core::list<ChatLine> chat_lines;
\r
1795 If an error occurs, this is set to something and the
\r
1796 menu-game loop is restarted. It is then displayed before
\r
1799 std::wstring error_message = L"";
\r
1804 while(g_device->run())
\r
1807 // This is used for catching disconnects
\r
1812 Out-of-game menu loop.
\r
1814 Loop quits when menu returns proper parameters.
\r
1818 // Cursor can be non-visible when coming from the game
\r
1819 device->getCursorControl()->setVisible(true);
\r
1820 // Some stuff are left to scene manager when coming from the game
\r
1821 // (map at least?)
\r
1823 // Reset or hide the debug gui texts
\r
1824 guitext->setText(L"Minetest-c55");
\r
1825 guitext2->setVisible(false);
\r
1826 guitext_info->setVisible(false);
\r
1827 guitext_chat->setVisible(false);
\r
1829 // Initialize menu data
\r
1830 MainMenuData menudata;
\r
1831 menudata.address = narrow_to_wide(address);
\r
1832 menudata.name = narrow_to_wide(playername);
\r
1833 menudata.port = narrow_to_wide(itos(port));
\r
1834 menudata.creative_mode = g_settings.getBool("creative_mode");
\r
1836 GUIMainMenu *menu =
\r
1837 new GUIMainMenu(guienv, guiroot, -1,
\r
1838 &g_menumgr, &menudata, &g_gamecallback);
\r
1839 menu->allowFocusRemoval(true);
\r
1841 if(error_message != L"")
\r
1843 GUIMessageMenu *menu2 =
\r
1844 new GUIMessageMenu(guienv, guiroot, -1,
\r
1845 &g_menumgr, error_message.c_str());
\r
1847 error_message = L"";
\r
1850 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
1852 dstream<<"Created main menu"<<std::endl;
\r
1854 while(g_device->run())
\r
1856 // Run global IrrlichtWrapper's main thread processing stuff
\r
1857 g_irrlicht->Run();
\r
1859 if(menu->getStatus() == true)
\r
1862 //driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1863 driver->beginScene(true, true, video::SColor(255,128,128,128));
\r
1864 guienv->drawAll();
\r
1865 driver->endScene();
\r
1868 // Break out of menu-game loop to shut down cleanly
\r
1869 if(g_device->run() == false)
\r
1872 dstream<<"Dropping main menu"<<std::endl;
\r
1876 // Delete map if requested
\r
1877 if(menudata.delete_map)
\r
1879 bool r = fs::RecursiveDeleteContent(map_dir);
\r
1881 error_message = L"Delete failed";
\r
1885 playername = wide_to_narrow(menudata.name);
\r
1886 address = wide_to_narrow(menudata.address);
\r
1887 port = stoi(wide_to_narrow(menudata.port));
\r
1888 g_settings.set("creative_mode", itos(menudata.creative_mode));
\r
1890 // Check for valid parameters, restart menu if invalid.
\r
1891 if(playername == "")
\r
1893 error_message = L"Name required.";
\r
1898 g_settings.set("name", playername);
\r
1899 g_settings.set("address", address);
\r
1900 g_settings.set("port", itos(port));
\r
1901 // Update configuration file
\r
1902 if(configpath != "")
\r
1903 g_settings.updateConfigFile(configpath.c_str());
\r
1905 // Continue to game
\r
1909 // Break out of menu-game loop to shut down cleanly
\r
1910 if(g_device->run() == false)
\r
1914 Make a scope here so that the client and the server and other
\r
1915 stuff gets removed when disconnected or the irrlicht device
\r
1920 // This is set to true at the end of the scope
\r
1921 g_irrlicht->Shutdown(false);
\r
1924 Draw "Loading" screen
\r
1926 const wchar_t *text = L"Loading and connecting...";
\r
1927 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1928 core::vector2d<s32> textsize(300, text_height);
\r
1929 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1931 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1932 text, textrect, false, false);
\r
1933 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1935 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1936 guienv->drawAll();
\r
1937 driver->endScene();
\r
1939 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1943 SharedPtr will delete it when it goes out of scope.
\r
1945 SharedPtr<Server> server;
\r
1946 if(address == ""){
\r
1947 server = new Server(map_dir, hm_params, map_params);
\r
1948 server->start(port);
\r
1955 Client client(device, playername.c_str(), draw_control);
\r
1957 g_client = &client;
\r
1959 Address connect_address(0,0,0,0, port);
\r
1962 connect_address.Resolve("localhost");
\r
1964 connect_address.Resolve(address.c_str());
\r
1966 catch(ResolveError &e)
\r
1968 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1970 error_message = L"Couldn't resolve address";
\r
1971 gui_loadingtext->remove();
\r
1975 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1976 client.connect(connect_address);
\r
1979 while(client.connectedAndInitialized() == false)
\r
1982 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1983 guienv->drawAll();
\r
1984 driver->endScene();
\r
1986 // Update client and server
\r
1990 if(server != NULL)
\r
1991 server->step(0.1);
\r
1997 catch(con::PeerNotFoundException &e)
\r
1999 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
2001 error_message = L"Connection timed out.";
\r
2002 gui_loadingtext->remove();
\r
2009 /*scene::ISceneNode* skybox;
\r
2010 skybox = smgr->addSkyBoxSceneNode(
\r
2011 driver->getTexture(porting::getDataPath("skybox2.png").c_str()),
\r
2012 driver->getTexture(porting::getDataPath("skybox3.png").c_str()),
\r
2013 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2014 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2015 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2016 driver->getTexture(porting::getDataPath("skybox1.png").c_str()));*/
\r
2019 Create the camera node
\r
2022 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
2023 0, // Camera parent
\r
2024 v3f(BS*100, BS*2, BS*100), // Look from
\r
2025 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
2029 if(camera == NULL)
\r
2032 video::SColor skycolor = video::SColor(255,90,140,200);
\r
2034 camera->setFOV(FOV_ANGLE);
\r
2036 // Just so big a value that everything rendered is visible
\r
2037 camera->setFarValue(100000*BS);
\r
2039 f32 camera_yaw = 0; // "right/left"
\r
2040 f32 camera_pitch = 0; // "up/down"
\r
2046 gui_loadingtext->remove();
\r
2049 Add some gui stuff
\r
2052 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
2053 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
2055 // Test the text input system
\r
2056 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_menumgr,
\r
2058 /*GUIMessageMenu *menu =
\r
2059 new GUIMessageMenu(guienv, guiroot, -1,
\r
2064 // Launch pause menu
\r
2065 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
2066 &g_menumgr))->drop();
\r
2069 guitext2->setVisible(true);
\r
2070 guitext_info->setVisible(true);
\r
2071 guitext_chat->setVisible(true);
\r
2074 Some statistics are collected in these
\r
2077 u32 beginscenetime = 0;
\r
2078 u32 scenetime = 0;
\r
2079 u32 endscenetime = 0;
\r
2082 //throw con::PeerNotFoundException("lol");
\r
2088 bool first_loop_after_window_activation = true;
\r
2090 // Time is in milliseconds
\r
2091 // NOTE: getRealTime() causes strange problems in wine (imprecision?)
\r
2092 // NOTE: So we have to use getTime() and call run()s between them
\r
2093 u32 lasttime = device->getTimer()->getTime();
\r
2095 while(device->run())
\r
2097 if(g_disconnect_requested)
\r
2099 g_disconnect_requested = false;
\r
2104 Run global IrrlichtWrapper's main thread processing stuff
\r
2106 g_irrlicht->Run();
\r
2109 Random calculations
\r
2111 v2u32 screensize = driver->getScreenSize();
\r
2112 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
2114 // Hilight boxes collected during the loop and displayed
\r
2115 core::list< core::aabbox3d<f32> > hilightboxes;
\r
2118 std::wstring infotext;
\r
2120 //TimeTaker //timer1("//timer1");
\r
2122 // Time of frame without fps limit
\r
2126 // not using getRealTime is necessary for wine
\r
2127 u32 time = device->getTimer()->getTime();
\r
2128 if(time > lasttime)
\r
2129 busytime_u32 = time - lasttime;
\r
2132 busytime = busytime_u32 / 1000.0;
\r
2135 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
2137 // Absolutelu necessary for wine!
\r
2144 updateViewingRange(busytime, &client);
\r
2151 float fps_max = g_settings.getFloat("fps_max");
\r
2152 u32 frametime_min = 1000./fps_max;
\r
2154 if(busytime_u32 < frametime_min)
\r
2156 u32 sleeptime = frametime_min - busytime_u32;
\r
2157 device->sleep(sleeptime);
\r
2161 // Absolutelu necessary for wine!
\r
2165 Time difference calculation
\r
2167 f32 dtime; // in seconds
\r
2169 u32 time = device->getTimer()->getTime();
\r
2170 if(time > lasttime)
\r
2171 dtime = (time - lasttime) / 1000.0;
\r
2177 Time average and jitter calculation
\r
2180 static f32 dtime_avg1 = 0.0;
\r
2181 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
2182 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
2184 static f32 dtime_jitter1_max_sample = 0.0;
\r
2185 static f32 dtime_jitter1_max_fraction = 0.0;
\r
2187 static f32 jitter1_max = 0.0;
\r
2188 static f32 counter = 0.0;
\r
2189 if(dtime_jitter1 > jitter1_max)
\r
2190 jitter1_max = dtime_jitter1;
\r
2195 dtime_jitter1_max_sample = jitter1_max;
\r
2196 dtime_jitter1_max_fraction
\r
2197 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
2198 jitter1_max = 0.0;
\r
2203 Busytime average and jitter calculation
\r
2206 static f32 busytime_avg1 = 0.0;
\r
2207 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
2208 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
2210 static f32 busytime_jitter1_max_sample = 0.0;
\r
2211 static f32 busytime_jitter1_min_sample = 0.0;
\r
2213 static f32 jitter1_max = 0.0;
\r
2214 static f32 jitter1_min = 0.0;
\r
2215 static f32 counter = 0.0;
\r
2216 if(busytime_jitter1 > jitter1_max)
\r
2217 jitter1_max = busytime_jitter1;
\r
2218 if(busytime_jitter1 < jitter1_min)
\r
2219 jitter1_min = busytime_jitter1;
\r
2221 if(counter > 0.0){
\r
2223 busytime_jitter1_max_sample = jitter1_max;
\r
2224 busytime_jitter1_min_sample = jitter1_min;
\r
2225 jitter1_max = 0.0;
\r
2226 jitter1_min = 0.0;
\r
2231 Debug info for client
\r
2234 static float counter = 0.0;
\r
2239 client.printDebugInfo(std::cout);
\r
2244 Input handler step()
\r
2246 g_input->step(dtime);
\r
2249 Player speed control
\r
2258 bool a_superspeed,
\r
2261 PlayerControl control(
\r
2262 g_input->isKeyDown(irr::KEY_KEY_W),
\r
2263 g_input->isKeyDown(irr::KEY_KEY_S),
\r
2264 g_input->isKeyDown(irr::KEY_KEY_A),
\r
2265 g_input->isKeyDown(irr::KEY_KEY_D),
\r
2266 g_input->isKeyDown(irr::KEY_SPACE),
\r
2267 g_input->isKeyDown(irr::KEY_KEY_2),
\r
2271 client.setPlayerControl(control);
\r
2275 Process environment
\r
2279 //TimeTaker timer("client.step(dtime)");
\r
2280 client.step(dtime);
\r
2281 //client.step(dtime_avg1);
\r
2284 if(server != NULL)
\r
2286 //TimeTaker timer("server->step(dtime)");
\r
2287 server->step(dtime);
\r
2290 v3f player_position = client.getPlayerPosition();
\r
2292 //TimeTaker //timer2("//timer2");
\r
2295 Mouse and camera control
\r
2298 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
2301 device->getCursorControl()->setVisible(false);
\r
2303 if(first_loop_after_window_activation){
\r
2304 //std::cout<<"window active, first loop"<<std::endl;
\r
2305 first_loop_after_window_activation = false;
\r
2308 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
2309 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
2310 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
2311 camera_yaw -= dx*0.2;
\r
2312 camera_pitch += dy*0.2;
\r
2313 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
2314 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
2316 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
2319 device->getCursorControl()->setVisible(true);
\r
2321 //std::cout<<"window inactive"<<std::endl;
\r
2322 first_loop_after_window_activation = true;
\r
2325 camera_yaw = wrapDegrees(camera_yaw);
\r
2326 camera_pitch = wrapDegrees(camera_pitch);
\r
2328 v3f camera_direction = v3f(0,0,1);
\r
2329 camera_direction.rotateYZBy(camera_pitch);
\r
2330 camera_direction.rotateXZBy(camera_yaw);
\r
2332 // This is at the height of the eyes of the current figure
\r
2333 //v3f camera_position = player_position + v3f(0, BS+BS/2, 0);
\r
2334 // This is more like in minecraft
\r
2335 v3f camera_position = player_position + v3f(0, BS+BS*0.625, 0);
\r
2337 camera->setPosition(camera_position);
\r
2338 // *100.0 helps in large map coordinates
\r
2339 camera->setTarget(camera_position + camera_direction * 100.0);
\r
2341 if(FIELD_OF_VIEW_TEST){
\r
2342 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
2343 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
2346 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
2347 //TimeTaker timer("client.updateCamera");
\r
2348 client.updateCamera(camera_position, camera_direction);
\r
2352 //TimeTaker //timer3("//timer3");
\r
2355 Calculate what block is the crosshair pointing to
\r
2358 //u32 t1 = device->getTimer()->getRealTime();
\r
2360 //f32 d = 4; // max. distance
\r
2361 f32 d = 4; // max. distance
\r
2362 core::line3d<f32> shootline(camera_position,
\r
2363 camera_position + camera_direction * BS * (d+1));
\r
2365 MapBlockObject *selected_object = client.getSelectedObject
\r
2366 (d*BS, camera_position, shootline);
\r
2369 If it's pointing to a MapBlockObject
\r
2372 if(selected_object != NULL)
\r
2374 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
2376 core::aabbox3d<f32> box_on_map
\r
2377 = selected_object->getSelectionBoxOnMap();
\r
2379 hilightboxes.push_back(box_on_map);
\r
2381 infotext = narrow_to_wide(selected_object->infoText());
\r
2383 if(g_input->getLeftClicked())
\r
2385 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
2386 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
2387 selected_object->getId(), g_selected_item);
\r
2389 else if(g_input->getRightClicked())
\r
2391 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
2393 Check if we want to modify the object ourselves
\r
2395 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
2397 dstream<<"Sign object right-clicked"<<std::endl;
\r
2399 if(random_input == false)
\r
2401 // Get a new text for it
\r
2403 TextDest *dest = new TextDestSign(
\r
2404 selected_object->getBlock()->getPos(),
\r
2405 selected_object->getId(),
\r
2408 SignObject *sign_object = (SignObject*)selected_object;
\r
2410 std::wstring wtext =
\r
2411 narrow_to_wide(sign_object->getText());
\r
2413 (new GUITextInputMenu(guienv, guiroot, -1,
\r
2419 Otherwise pass the event to the server as-is
\r
2423 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
2424 selected_object->getId(), g_selected_item);
\r
2428 else // selected_object == NULL
\r
2432 Find out which node we are pointing at
\r
2435 bool nodefound = false;
\r
2437 v3s16 neighbourpos;
\r
2438 core::aabbox3d<f32> nodehilightbox;
\r
2439 f32 mindistance = BS * 1001;
\r
2441 v3s16 pos_i = floatToInt(player_position);
\r
2443 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2447 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2448 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2449 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2450 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2451 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2452 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2454 for(s16 y = ystart; y <= yend; y++)
\r
2455 for(s16 z = zstart; z <= zend; z++)
\r
2456 for(s16 x = xstart; x <= xend; x++)
\r
2461 n = client.getNode(v3s16(x,y,z));
\r
2462 if(content_pointable(n.d) == false)
\r
2465 catch(InvalidPositionException &e)
\r
2471 v3f npf = intToFloat(np);
\r
2476 v3s16(0,0,1), // back
\r
2477 v3s16(0,1,0), // top
\r
2478 v3s16(1,0,0), // right
\r
2479 v3s16(0,0,-1), // front
\r
2480 v3s16(0,-1,0), // bottom
\r
2481 v3s16(-1,0,0), // left
\r
2487 if(n.d == CONTENT_TORCH)
\r
2489 v3s16 dir = unpackDir(n.dir);
\r
2490 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2491 dir_f *= BS/2 - BS/6 - BS/20;
\r
2492 v3f cpf = npf + dir_f;
\r
2493 f32 distance = (cpf - camera_position).getLength();
\r
2495 core::aabbox3d<f32> box;
\r
2498 if(dir == v3s16(0,-1,0))
\r
2500 box = core::aabbox3d<f32>(
\r
2501 npf - v3f(BS/6, BS/2, BS/6),
\r
2502 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2506 else if(dir == v3s16(0,1,0))
\r
2508 box = core::aabbox3d<f32>(
\r
2509 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2510 npf + v3f(BS/6, BS/2, BS/6)
\r
2516 box = core::aabbox3d<f32>(
\r
2517 cpf - v3f(BS/6, BS/3, BS/6),
\r
2518 cpf + v3f(BS/6, BS/3, BS/6)
\r
2522 if(distance < mindistance)
\r
2524 if(box.intersectsWithLine(shootline))
\r
2528 neighbourpos = np;
\r
2529 mindistance = distance;
\r
2530 nodehilightbox = box;
\r
2539 for(u16 i=0; i<6; i++)
\r
2541 v3f dir_f = v3f(dirs[i].X,
\r
2542 dirs[i].Y, dirs[i].Z);
\r
2543 v3f centerpoint = npf + dir_f * BS/2;
\r
2545 (centerpoint - camera_position).getLength();
\r
2547 if(distance < mindistance)
\r
2549 core::CMatrix4<f32> m;
\r
2550 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2552 // This is the back face
\r
2553 v3f corners[2] = {
\r
2554 v3f(BS/2, BS/2, BS/2),
\r
2555 v3f(-BS/2, -BS/2, BS/2+d)
\r
2558 for(u16 j=0; j<2; j++)
\r
2560 m.rotateVect(corners[j]);
\r
2561 corners[j] += npf;
\r
2564 core::aabbox3d<f32> facebox(corners[0]);
\r
2565 facebox.addInternalPoint(corners[1]);
\r
2567 if(facebox.intersectsWithLine(shootline))
\r
2571 neighbourpos = np + dirs[i];
\r
2572 mindistance = distance;
\r
2574 //nodehilightbox = facebox;
\r
2576 const float d = 0.502;
\r
2577 core::aabbox3d<f32> nodebox
\r
2578 (-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
\r
2579 v3f nodepos_f = intToFloat(nodepos);
\r
2580 nodebox.MinEdge += nodepos_f;
\r
2581 nodebox.MaxEdge += nodepos_f;
\r
2582 nodehilightbox = nodebox;
\r
2584 } // if distance < mindistance
\r
2586 } // regular block
\r
2589 static float nodig_delay_counter = 0.0;
\r
2593 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2595 static float dig_time = 0.0;
\r
2596 static u16 dig_index = 0;
\r
2598 // Visualize selection
\r
2600 hilightboxes.push_back(nodehilightbox);
\r
2604 if(g_input->getLeftReleased())
\r
2606 client.clearTempMod(nodepos);
\r
2610 if(nodig_delay_counter > 0.0)
\r
2612 nodig_delay_counter -= dtime;
\r
2616 if(nodepos != nodepos_old)
\r
2618 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2619 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2621 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2623 client.clearTempMod(nodepos_old);
\r
2628 if(g_input->getLeftClicked() ||
\r
2629 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2631 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2632 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2634 if(g_input->getLeftClicked())
\r
2636 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2638 if(g_input->getLeftState())
\r
2640 MapNode n = client.getNode(nodepos);
\r
2642 // Get tool name. Default is "" = bare hands
\r
2643 std::string toolname = "";
\r
2644 InventoryList *mlist = local_inventory.getList("main");
\r
2647 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2648 if(item && (std::string)item->getName() == "ToolItem")
\r
2650 ToolItem *titem = (ToolItem*)item;
\r
2651 toolname = titem->getToolName();
\r
2655 // Get digging properties for material and tool
\r
2656 u8 material = n.d;
\r
2657 DiggingProperties prop =
\r
2658 getDiggingProperties(material, toolname);
\r
2660 float dig_time_complete = 0.0;
\r
2662 if(prop.diggable == false)
\r
2664 /*dstream<<"Material "<<(int)material
\r
2665 <<" not diggable with \""
\r
2666 <<toolname<<"\""<<std::endl;*/
\r
2667 // I guess nobody will wait for this long
\r
2668 dig_time_complete = 10000000.0;
\r
2672 dig_time_complete = prop.time;
\r
2675 if(dig_time_complete >= 0.001)
\r
2677 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2678 * dig_time/dig_time_complete);
\r
2680 // This is for torches
\r
2683 dig_index = CRACK_ANIMATION_LENGTH;
\r
2686 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2688 //TimeTaker timer("client.setTempMod");
\r
2689 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2690 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2694 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2695 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2696 client.clearTempMod(nodepos);
\r
2697 client.removeNode(nodepos);
\r
2701 nodig_delay_counter = dig_time_complete
\r
2702 / (float)CRACK_ANIMATION_LENGTH;
\r
2704 // We don't want a corresponding delay to
\r
2705 // very time consuming nodes
\r
2706 if(nodig_delay_counter > 0.5)
\r
2708 nodig_delay_counter = 0.5;
\r
2710 // We want a slight delay to very little
\r
2711 // time consuming nodes
\r
2712 float mindelay = 0.15;
\r
2713 if(nodig_delay_counter < mindelay)
\r
2715 nodig_delay_counter = mindelay;
\r
2719 dig_time += dtime;
\r
2723 if(g_input->getRightClicked())
\r
2725 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2726 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2729 nodepos_old = nodepos;
\r
2734 } // selected_object == NULL
\r
2736 g_input->resetLeftClicked();
\r
2737 g_input->resetRightClicked();
\r
2739 if(g_input->getLeftReleased())
\r
2741 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2743 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2745 if(g_input->getRightReleased())
\r
2747 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2751 g_input->resetLeftReleased();
\r
2752 g_input->resetRightReleased();
\r
2755 Calculate stuff for drawing
\r
2758 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2760 u32 daynight_ratio = client.getDayNightRatio();
\r
2761 /*video::SColor bgcolor = video::SColor(
\r
2763 skycolor.getRed() * daynight_ratio / 1000,
\r
2764 skycolor.getGreen() * daynight_ratio / 1000,
\r
2765 skycolor.getBlue() * daynight_ratio / 1000);*/
\r
2767 u8 l = decode_light((daynight_ratio * LIGHT_SUN) / 1000);
\r
2768 video::SColor bgcolor = video::SColor(
\r
2770 skycolor.getRed() * l / 255,
\r
2771 skycolor.getGreen() * l / 255,
\r
2772 skycolor.getBlue() * l / 255);
\r
2778 if(g_settings.getBool("enable_fog") == true)
\r
2780 //f32 range = draw_control.wanted_range * BS + MAP_BLOCKSIZE/2*BS;
\r
2781 f32 range = draw_control.wanted_range * BS + MAP_BLOCKSIZE/3*BS;
\r
2782 if(draw_control.range_all)
\r
2783 range = 100000*BS;
\r
2787 video::EFT_FOG_LINEAR,
\r
2791 false, // pixel fog
\r
2792 false // range fog
\r
2798 Update gui stuff (0ms)
\r
2801 //TimeTaker guiupdatetimer("Gui updating");
\r
2804 wchar_t temptext[150];
\r
2806 static float drawtime_avg = 0;
\r
2807 drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
\r
2808 static float beginscenetime_avg = 0;
\r
2809 beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
\r
2810 static float scenetime_avg = 0;
\r
2811 scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
\r
2812 static float endscenetime_avg = 0;
\r
2813 endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;
\r
2815 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2817 L", R: range_all=%i"
\r
2819 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2821 draw_control.range_all,
\r
2823 beginscenetime_avg,
\r
2828 guitext->setText(temptext);
\r
2832 wchar_t temptext[150];
\r
2833 swprintf(temptext, 150,
\r
2834 L"(% .1f, % .1f, % .1f)"
\r
2835 L" (% .3f < btime_jitter < % .3f"
\r
2836 L", dtime_jitter = % .1f %%"
\r
2837 L", v_range = %.1f)",
\r
2838 player_position.X/BS,
\r
2839 player_position.Y/BS,
\r
2840 player_position.Z/BS,
\r
2841 busytime_jitter1_min_sample,
\r
2842 busytime_jitter1_max_sample,
\r
2843 dtime_jitter1_max_fraction * 100.0,
\r
2844 draw_control.wanted_range
\r
2847 guitext2->setText(temptext);
\r
2851 guitext_info->setText(infotext.c_str());
\r
2855 Get chat messages from client
\r
2858 // Get new messages
\r
2859 std::wstring message;
\r
2860 while(client.getChatMessage(message))
\r
2862 chat_lines.push_back(ChatLine(message));
\r
2863 /*if(chat_lines.size() > 6)
\r
2865 core::list<ChatLine>::Iterator
\r
2866 i = chat_lines.begin();
\r
2867 chat_lines.erase(i);
\r
2870 // Append them to form the whole static text and throw
\r
2871 // it to the gui element
\r
2872 std::wstring whole;
\r
2873 // This will correspond to the line number counted from
\r
2874 // top to bottom, from size-1 to 0
\r
2875 s16 line_number = chat_lines.size();
\r
2876 // Count of messages to be removed from the top
\r
2877 u16 to_be_removed_count = 0;
\r
2878 for(core::list<ChatLine>::Iterator
\r
2879 i = chat_lines.begin();
\r
2880 i != chat_lines.end(); i++)
\r
2882 // After this, line number is valid for this loop
\r
2885 (*i).age += dtime;
\r
2887 This results in a maximum age of 60*6 to the
\r
2888 lowermost line and a maximum of 6 lines
\r
2890 float allowed_age = (6-line_number) * 60.0;
\r
2892 if((*i).age > allowed_age)
\r
2894 to_be_removed_count++;
\r
2897 whole += (*i).text + L'\n';
\r
2899 for(u16 i=0; i<to_be_removed_count; i++)
\r
2901 core::list<ChatLine>::Iterator
\r
2902 it = chat_lines.begin();
\r
2903 chat_lines.erase(it);
\r
2905 guitext_chat->setText(whole.c_str());
\r
2906 // Update gui element size and position
\r
2907 core::rect<s32> rect(
\r
2909 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2910 screensize.X - 10,
\r
2913 guitext_chat->setRelativePosition(rect);
\r
2915 if(chat_lines.size() == 0)
\r
2916 guitext_chat->setVisible(false);
\r
2918 guitext_chat->setVisible(true);
\r
2925 static u16 old_selected_item = 65535;
\r
2926 if(client.getLocalInventoryUpdated()
\r
2927 || g_selected_item != old_selected_item)
\r
2929 old_selected_item = g_selected_item;
\r
2930 //std::cout<<"Updating local inventory"<<std::endl;
\r
2931 client.getLocalInventory(local_inventory);
\r
2932 quick_inventory->setSelection(g_selected_item);
\r
2933 quick_inventory->update();
\r
2937 Send actions returned by the inventory menu
\r
2939 while(inventory_action_queue.size() != 0)
\r
2941 InventoryAction *a = inventory_action_queue.pop_front();
\r
2943 client.sendInventoryAction(a);
\r
2952 TimeTaker drawtimer("Drawing");
\r
2956 TimeTaker timer("beginScene");
\r
2957 driver->beginScene(true, true, bgcolor);
\r
2958 //driver->beginScene(false, true, bgcolor);
\r
2959 beginscenetime = timer.stop(true);
\r
2964 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2967 TimeTaker timer("smgr");
\r
2969 scenetime = timer.stop(true);
\r
2973 //TimeTaker timer9("auxiliary drawings");
\r
2977 //TimeTaker //timer10("//timer10");
\r
2979 video::SMaterial m;
\r
2980 //m.Thickness = 10;
\r
2982 m.Lighting = false;
\r
2983 driver->setMaterial(m);
\r
2985 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2987 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2988 i != hilightboxes.end(); i++)
\r
2990 /*std::cout<<"hilightbox min="
\r
2991 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2993 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2995 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
3001 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
3002 displaycenter + core::vector2d<s32>(10,0),
\r
3003 video::SColor(255,255,255,255));
\r
3004 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
3005 displaycenter + core::vector2d<s32>(0,10),
\r
3006 video::SColor(255,255,255,255));
\r
3011 //TimeTaker //timer11("//timer11");
\r
3017 guienv->drawAll();
\r
3021 TimeTaker timer("endScene");
\r
3022 driver->endScene();
\r
3023 endscenetime = timer.stop(true);
\r
3026 drawtime = drawtimer.stop(true);
\r
3032 static s16 lastFPS = 0;
\r
3033 //u16 fps = driver->getFPS();
\r
3034 u16 fps = (1.0/dtime_avg1);
\r
3036 if (lastFPS != fps)
\r
3038 core::stringw str = L"Minetest [";
\r
3039 str += driver->getName();
\r
3043 device->setWindowCaption(str.c_str());
\r
3049 device->yield();*/
\r
3052 delete quick_inventory;
\r
3055 Disable texture fetches and other stuff that is queued
\r
3056 to be processed by the main loop.
\r
3058 This has to be done before client goes out of scope.
\r
3060 g_irrlicht->Shutdown(true);
\r
3062 } // client and server are deleted at this point
\r
3065 catch(con::PeerNotFoundException &e)
\r
3067 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
3068 error_message = L"Connection timed out.";
\r
3071 } // Menu-game loop
\r
3076 In the end, delete the Irrlicht device.
\r
3081 Update configuration file
\r
3083 /*if(configpath != "")
\r
3085 g_settings.updateConfigFile(configpath.c_str());
\r
3088 END_DEBUG_EXCEPTION_HANDLER
\r
3090 debugstreams_deinit();
\r