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
354 ======================================================================
\r
359 Setting this to 1 enables a special camera mode that forces
\r
360 the renderers to think that the camera statically points from
\r
361 the starting place to a static direction.
\r
363 This allows one to move around with the player and see what
\r
364 is actually drawn behind solid things and behind the player.
\r
366 #define FIELD_OF_VIEW_TEST 0
\r
370 #pragma message ("Disabling unit tests")
\r
372 #warning "Disabling unit tests"
\r
374 // Disable unit tests
\r
375 #define ENABLE_TESTS 0
\r
377 // Enable unit tests
\r
378 #define ENABLE_TESTS 1
\r
382 #pragma comment(lib, "Irrlicht.lib")
\r
383 //#pragma comment(lib, "jthread.lib")
\r
384 #pragma comment(lib, "zlibwapi.lib")
\r
385 #pragma comment(lib, "Shell32.lib")
\r
386 // This would get rid of the console window
\r
387 //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")
\r
390 #include <iostream>
\r
392 #include <jmutexautolock.h>
\r
393 #include <locale.h>
\r
394 #include "common_irrlicht.h"
\r
397 #include "player.h"
\r
400 #include "environment.h"
\r
401 #include "server.h"
\r
402 #include "client.h"
\r
403 #include "serialization.h"
\r
404 #include "constants.h"
\r
405 #include "strfnd.h"
\r
406 #include "porting.h"
\r
407 #include "irrlichtwrapper.h"
\r
408 #include "gettime.h"
\r
409 #include "porting.h"
\r
410 #include "guiPauseMenu.h"
\r
411 #include "guiInventoryMenu.h"
\r
412 #include "guiTextInputMenu.h"
\r
413 #include "materials.h"
\r
414 #include "guiMessageMenu.h"
\r
415 #include "filesys.h"
\r
416 #include "config.h"
\r
417 #include "guiMainMenu.h"
\r
418 #include "mineral.h"
\r
420 IrrlichtWrapper *g_irrlicht;
\r
422 MapDrawControl draw_control;
\r
426 These are loaded from the config file.
\r
429 Settings g_settings;
\r
431 extern void set_default_settings();
\r
437 IrrlichtDevice *g_device = NULL;
\r
438 Client *g_client = NULL;
\r
444 gui::IGUIEnvironment* guienv = NULL;
\r
445 gui::IGUIStaticText *guiroot = NULL;
\r
447 class MainMenuManager : public IMenuManager
\r
450 virtual void createdMenu(GUIModalMenu *menu)
\r
452 for(core::list<GUIModalMenu*>::Iterator
\r
453 i = m_stack.begin();
\r
454 i != m_stack.end(); i++)
\r
456 assert(*i != menu);
\r
459 if(m_stack.size() != 0)
\r
460 (*m_stack.getLast())->setVisible(false);
\r
461 m_stack.push_back(menu);
\r
464 virtual void deletingMenu(GUIModalMenu *menu)
\r
466 // Remove all entries if there are duplicates
\r
467 bool removed_entry;
\r
469 removed_entry = false;
\r
470 for(core::list<GUIModalMenu*>::Iterator
\r
471 i = m_stack.begin();
\r
472 i != m_stack.end(); i++)
\r
477 removed_entry = true;
\r
481 }while(removed_entry);
\r
483 /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
\r
484 assert(*i == menu);
\r
485 m_stack.erase(i);*/
\r
487 if(m_stack.size() != 0)
\r
488 (*m_stack.getLast())->setVisible(true);
\r
493 return m_stack.size();
\r
496 core::list<GUIModalMenu*> m_stack;
\r
499 MainMenuManager g_menumgr;
\r
501 bool noMenuActive()
\r
503 return (g_menumgr.menuCount() == 0);
\r
506 bool g_disconnect_requested = false;
\r
508 class MainGameCallback : public IGameCallback
\r
511 virtual void exitToOS()
\r
513 g_device->closeDevice();
\r
516 virtual void disconnect()
\r
518 g_disconnect_requested = true;
\r
522 MainGameCallback g_gamecallback;
\r
524 // Inventory actions from the menu are buffered here before sending
\r
525 Queue<InventoryAction*> inventory_action_queue;
\r
526 // This is a copy of the inventory that the client's environment has
\r
527 Inventory local_inventory;
\r
529 u16 g_selected_item = 0;
\r
536 std::ostream *dout_con_ptr = &dummyout;
\r
537 std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
538 //std::ostream *dout_con_ptr = &dstream_no_stderr;
\r
539 //std::ostream *derr_con_ptr = &dstream_no_stderr;
\r
540 //std::ostream *dout_con_ptr = &dstream;
\r
541 //std::ostream *derr_con_ptr = &dstream;
\r
544 std::ostream *dout_server_ptr = &dstream;
\r
545 std::ostream *derr_server_ptr = &dstream;
\r
548 std::ostream *dout_client_ptr = &dstream;
\r
549 std::ostream *derr_client_ptr = &dstream;
\r
552 gettime.h implementation
\r
558 Use irrlicht because it is more precise than porting.h's
\r
561 if(g_irrlicht == NULL)
\r
563 return g_irrlicht->getTime();
\r
570 struct TextDestSign : public TextDest
\r
572 TextDestSign(v3s16 blockpos, s16 id, Client *client)
\r
574 m_blockpos = blockpos;
\r
578 void gotText(std::wstring text)
\r
580 std::string ntext = wide_to_narrow(text);
\r
581 dstream<<"Changing text of a sign object: "
\r
582 <<ntext<<std::endl;
\r
583 m_client->sendSignText(m_blockpos, m_id, ntext);
\r
591 struct TextDestChat : public TextDest
\r
593 TextDestChat(Client *client)
\r
597 void gotText(std::wstring text)
\r
599 m_client->sendChatMessage(text);
\r
600 m_client->addChatMessage(text);
\r
606 class MyEventReceiver : public IEventReceiver
\r
609 // This is the one method that we have to implement
\r
610 virtual bool OnEvent(const SEvent& event)
\r
613 React to nothing here if a menu is active
\r
615 if(noMenuActive() == false)
\r
621 // Remember whether each key is down or up
\r
622 if(event.EventType == irr::EET_KEY_INPUT_EVENT)
\r
624 keyIsDown[event.KeyInput.Key] = event.KeyInput.PressedDown;
\r
626 if(event.KeyInput.PressedDown)
\r
628 //dstream<<"Pressed key: "<<(char)event.KeyInput.Key<<std::endl;
\r
634 if(guienv != NULL && guiroot != NULL && g_device != NULL)
\r
636 if(event.KeyInput.Key == irr::KEY_ESCAPE)
\r
638 dstream<<DTIME<<"MyEventReceiver: "
\r
639 <<"Launching pause menu"<<std::endl;
\r
640 // It will delete itself by itself
\r
641 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
642 &g_menumgr))->drop();
\r
645 if(event.KeyInput.Key == irr::KEY_KEY_I)
\r
647 dstream<<DTIME<<"MyEventReceiver: "
\r
648 <<"Launching inventory"<<std::endl;
\r
649 (new GUIInventoryMenu(guienv, guiroot, -1,
\r
650 &local_inventory, &inventory_action_queue,
\r
651 &g_menumgr))->drop();
\r
654 if(event.KeyInput.Key == irr::KEY_KEY_T)
\r
656 TextDest *dest = new TextDestChat(g_client);
\r
658 (new GUITextInputMenu(guienv, guiroot, -1,
\r
664 // Material selection
\r
665 if(event.KeyInput.Key == irr::KEY_KEY_F)
\r
667 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
670 g_selected_item = 0;
\r
671 dstream<<DTIME<<"Selected item: "
\r
672 <<g_selected_item<<std::endl;
\r
675 // Viewing range selection
\r
676 if(event.KeyInput.Key == irr::KEY_KEY_R)
\r
678 if(draw_control.range_all)
\r
680 draw_control.range_all = false;
\r
681 dstream<<DTIME<<"Disabled full viewing range"<<std::endl;
\r
685 draw_control.range_all = true;
\r
686 dstream<<DTIME<<"Enabled full viewing range"<<std::endl;
\r
690 // Print debug stacks
\r
691 if(event.KeyInput.Key == irr::KEY_KEY_P)
\r
693 dstream<<"-----------------------------------------"
\r
695 dstream<<DTIME<<"Printing debug stacks:"<<std::endl;
\r
696 dstream<<"-----------------------------------------"
\r
698 debug_stacks_print();
\r
703 if(event.EventType == irr::EET_MOUSE_INPUT_EVENT)
\r
705 if(noMenuActive() == false)
\r
707 left_active = false;
\r
708 middle_active = false;
\r
709 right_active = false;
\r
713 //dstream<<"MyEventReceiver: mouse input"<<std::endl;
\r
714 left_active = event.MouseInput.isLeftPressed();
\r
715 middle_active = event.MouseInput.isMiddlePressed();
\r
716 right_active = event.MouseInput.isRightPressed();
\r
718 if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
\r
720 leftclicked = true;
\r
722 if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
\r
724 rightclicked = true;
\r
726 if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
\r
728 leftreleased = true;
\r
730 if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
\r
732 rightreleased = true;
\r
734 if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
\r
736 /*dstream<<"event.MouseInput.Wheel="
\r
737 <<event.MouseInput.Wheel<<std::endl;*/
\r
738 if(event.MouseInput.Wheel < 0)
\r
740 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
743 g_selected_item = 0;
\r
745 else if(event.MouseInput.Wheel > 0)
\r
747 if(g_selected_item > 0)
\r
750 g_selected_item = PLAYER_INVENTORY_SIZE-1;
\r
759 // This is used to check whether a key is being held down
\r
760 virtual bool IsKeyDown(EKEY_CODE keyCode) const
\r
762 return keyIsDown[keyCode];
\r
767 for (u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
768 keyIsDown[i] = false;
\r
770 leftclicked = false;
\r
771 rightclicked = false;
\r
772 leftreleased = false;
\r
773 rightreleased = false;
\r
775 left_active = false;
\r
776 middle_active = false;
\r
777 right_active = false;
\r
788 bool rightreleased;
\r
791 bool middle_active;
\r
795 // We use this array to store the current state of each key
\r
796 bool keyIsDown[KEY_KEY_CODES_COUNT];
\r
799 IrrlichtDevice *m_device;
\r
808 virtual ~InputHandler()
\r
812 virtual bool isKeyDown(EKEY_CODE keyCode) = 0;
\r
814 virtual v2s32 getMousePos() = 0;
\r
815 virtual void setMousePos(s32 x, s32 y) = 0;
\r
817 virtual bool getLeftState() = 0;
\r
818 virtual bool getRightState() = 0;
\r
820 virtual bool getLeftClicked() = 0;
\r
821 virtual bool getRightClicked() = 0;
\r
822 virtual void resetLeftClicked() = 0;
\r
823 virtual void resetRightClicked() = 0;
\r
825 virtual bool getLeftReleased() = 0;
\r
826 virtual bool getRightReleased() = 0;
\r
827 virtual void resetLeftReleased() = 0;
\r
828 virtual void resetRightReleased() = 0;
\r
830 virtual void step(float dtime) {};
\r
832 virtual void clear() {};
\r
835 InputHandler *g_input = NULL;
\r
837 class RealInputHandler : public InputHandler
\r
840 RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver):
\r
842 m_receiver(receiver)
\r
845 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
847 return m_receiver->IsKeyDown(keyCode);
\r
849 virtual v2s32 getMousePos()
\r
851 return m_device->getCursorControl()->getPosition();
\r
853 virtual void setMousePos(s32 x, s32 y)
\r
855 m_device->getCursorControl()->setPosition(x, y);
\r
858 virtual bool getLeftState()
\r
860 return m_receiver->left_active;
\r
862 virtual bool getRightState()
\r
864 return m_receiver->right_active;
\r
867 virtual bool getLeftClicked()
\r
869 return m_receiver->leftclicked;
\r
871 virtual bool getRightClicked()
\r
873 return m_receiver->rightclicked;
\r
875 virtual void resetLeftClicked()
\r
877 m_receiver->leftclicked = false;
\r
879 virtual void resetRightClicked()
\r
881 m_receiver->rightclicked = false;
\r
884 virtual bool getLeftReleased()
\r
886 return m_receiver->leftreleased;
\r
888 virtual bool getRightReleased()
\r
890 return m_receiver->rightreleased;
\r
892 virtual void resetLeftReleased()
\r
894 m_receiver->leftreleased = false;
\r
896 virtual void resetRightReleased()
\r
898 m_receiver->rightreleased = false;
\r
903 resetRightClicked();
\r
904 resetLeftClicked();
\r
907 IrrlichtDevice *m_device;
\r
908 MyEventReceiver *m_receiver;
\r
911 class RandomInputHandler : public InputHandler
\r
914 RandomInputHandler()
\r
916 leftclicked = false;
\r
917 rightclicked = false;
\r
918 for(u32 i=0; i<KEY_KEY_CODES_COUNT; ++i)
\r
919 keydown[i] = false;
\r
921 virtual bool isKeyDown(EKEY_CODE keyCode)
\r
923 return keydown[keyCode];
\r
925 virtual v2s32 getMousePos()
\r
929 virtual void setMousePos(s32 x, s32 y)
\r
931 mousepos = v2s32(x,y);
\r
934 virtual bool getLeftState()
\r
938 virtual bool getRightState()
\r
943 virtual bool getLeftClicked()
\r
945 return leftclicked;
\r
947 virtual bool getRightClicked()
\r
949 return rightclicked;
\r
951 virtual void resetLeftClicked()
\r
953 leftclicked = false;
\r
955 virtual void resetRightClicked()
\r
957 rightclicked = false;
\r
960 virtual bool getLeftReleased()
\r
964 virtual bool getRightReleased()
\r
968 virtual void resetLeftReleased()
\r
971 virtual void resetRightReleased()
\r
975 virtual void step(float dtime)
\r
978 static float counter1 = 0;
\r
982 counter1 = 0.1*Rand(1,10);
\r
983 /*if(g_selected_material < USEFUL_CONTENT_COUNT-1)
\r
984 g_selected_material++;
\r
986 g_selected_material = 0;*/
\r
987 if(g_selected_item < PLAYER_INVENTORY_SIZE-1)
\r
990 g_selected_item = 0;
\r
994 static float counter1 = 0;
\r
998 counter1 = 0.1*Rand(1, 40);
\r
999 keydown[irr::KEY_SPACE] = !keydown[irr::KEY_SPACE];
\r
1003 static float counter1 = 0;
\r
1004 counter1 -= dtime;
\r
1005 if(counter1 < 0.0)
\r
1007 counter1 = 0.1*Rand(1, 40);
\r
1008 keydown[irr::KEY_KEY_2] = !keydown[irr::KEY_KEY_2];
\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_KEY_W] = !keydown[irr::KEY_KEY_W];
\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_A] = !keydown[irr::KEY_KEY_A];
\r
1030 static float counter1 = 0;
\r
1031 counter1 -= dtime;
\r
1032 if(counter1 < 0.0)
\r
1034 counter1 = 0.1*Rand(1, 20);
\r
1035 mousespeed = v2s32(Rand(-20,20), Rand(-15,20));
\r
1039 static float counter1 = 0;
\r
1040 counter1 -= dtime;
\r
1041 if(counter1 < 0.0)
\r
1043 counter1 = 0.1*Rand(1, 30);
\r
1044 leftclicked = true;
\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 rightclicked = true;
\r
1056 mousepos += mousespeed;
\r
1059 s32 Rand(s32 min, s32 max)
\r
1061 return (myrand()%(max-min+1))+min;
\r
1064 bool keydown[KEY_KEY_CODES_COUNT];
\r
1068 bool rightclicked;
\r
1071 void updateViewingRange(f32 frametime_in, Client *client)
\r
1073 if(draw_control.range_all == true)
\r
1076 static f32 added_frametime = 0;
\r
1077 static s16 added_frames = 0;
\r
1079 added_frametime += frametime_in;
\r
1080 added_frames += 1;
\r
1082 // Actually this counter kind of sucks because frametime is busytime
\r
1083 static f32 counter = 0;
\r
1084 counter -= frametime_in;
\r
1090 /*dstream<<__FUNCTION_NAME
\r
1091 <<": Collected "<<added_frames<<" frames, total of "
\r
1092 <<added_frametime<<"s."<<std::endl;*/
\r
1094 /*dstream<<"draw_control.blocks_drawn="
\r
1095 <<draw_control.blocks_drawn
\r
1096 <<", draw_control.blocks_would_have_drawn="
\r
1097 <<draw_control.blocks_would_have_drawn
\r
1100 float range_min = g_settings.getS16("viewing_range_nodes_min");
\r
1101 float range_max = g_settings.getS16("viewing_range_nodes_max");
\r
1103 draw_control.wanted_min_range = range_min;
\r
1104 draw_control.wanted_max_blocks = (1.2*draw_control.blocks_drawn)+1;
\r
1106 float block_draw_ratio = 1.0;
\r
1107 if(draw_control.blocks_would_have_drawn != 0)
\r
1109 block_draw_ratio = (float)draw_control.blocks_drawn
\r
1110 / (float)draw_control.blocks_would_have_drawn;
\r
1113 // Calculate the average frametime in the case that all wanted
\r
1114 // blocks had been drawn
\r
1115 f32 frametime = added_frametime / added_frames / block_draw_ratio;
\r
1117 added_frametime = 0.0;
\r
1120 float wanted_fps = g_settings.getFloat("wanted_fps");
\r
1121 float wanted_frametime = 1.0 / wanted_fps;
\r
1123 f32 wanted_frametime_change = wanted_frametime - frametime;
\r
1124 //dstream<<"wanted_frametime_change="<<wanted_frametime_change<<std::endl;
\r
1126 // If needed frametime change is very small, just return
\r
1127 if(fabs(wanted_frametime_change) < wanted_frametime*0.2)
\r
1129 //dstream<<"ignoring small wanted_frametime_change"<<std::endl;
\r
1133 float range = draw_control.wanted_range;
\r
1134 float new_range = range;
\r
1136 static s16 range_old = 0;
\r
1137 static f32 frametime_old = 0;
\r
1139 float d_range = range - range_old;
\r
1140 f32 d_frametime = frametime - frametime_old;
\r
1141 // A sane default of 30ms per 50 nodes of range
\r
1142 static f32 time_per_range = 30. / 50;
\r
1145 time_per_range = d_frametime / d_range;
\r
1148 // The minimum allowed calculated frametime-range derivative:
\r
1149 // Practically this sets the maximum speed of changing the range.
\r
1150 // The lower this value, the higher the maximum changing speed.
\r
1151 // A low value here results in wobbly range (0.001)
\r
1152 // A high value here results in slow changing range (0.0025)
\r
1153 // SUGG: This could be dynamically adjusted so that when
\r
1154 // the camera is turning, this is lower
\r
1155 //float min_time_per_range = 0.0015;
\r
1156 float min_time_per_range = 0.0010;
\r
1157 //float min_time_per_range = 0.05 / range;
\r
1158 if(time_per_range < min_time_per_range)
\r
1160 time_per_range = min_time_per_range;
\r
1161 //dstream<<"time_per_range="<<time_per_range<<" (min)"<<std::endl;
\r
1165 //dstream<<"time_per_range="<<time_per_range<<std::endl;
\r
1168 f32 wanted_range_change = wanted_frametime_change / time_per_range;
\r
1169 // Dampen the change a bit to kill oscillations
\r
1170 //wanted_range_change *= 0.9;
\r
1171 //wanted_range_change *= 0.75;
\r
1172 wanted_range_change *= 0.5;
\r
1173 //dstream<<"wanted_range_change="<<wanted_range_change<<std::endl;
\r
1175 // If needed range change is very small, just return
\r
1176 if(fabs(wanted_range_change) < 0.001)
\r
1178 //dstream<<"ignoring small wanted_range_change"<<std::endl;
\r
1182 new_range += wanted_range_change;
\r
1183 //dstream<<"new_range="<<new_range/*<<std::endl*/;
\r
1185 //float new_range_unclamped = new_range;
\r
1186 if(new_range < range_min)
\r
1187 new_range = range_min;
\r
1188 if(new_range > range_max)
\r
1189 new_range = range_max;
\r
1191 /*if(new_range != new_range_unclamped)
\r
1192 dstream<<", clamped to "<<new_range<<std::endl;
\r
1194 dstream<<std::endl;*/
\r
1196 draw_control.wanted_range = new_range;
\r
1198 range_old = new_range;
\r
1199 frametime_old = frametime;
\r
1202 class GUIQuickInventory : public IEventReceiver
\r
1205 GUIQuickInventory(
\r
1206 gui::IGUIEnvironment* env,
\r
1207 gui::IGUIElement* parent,
\r
1210 Inventory *inventory):
\r
1211 m_itemcount(itemcount),
\r
1212 m_inventory(inventory)
\r
1214 core::rect<s32> imgsize(0,0,48,48);
\r
1215 core::rect<s32> textsize(0,0,48,16);
\r
1216 v2s32 spacing(0, 64);
\r
1217 for(s32 i=0; i<m_itemcount; i++)
\r
1219 m_images.push_back(env->addImage(
\r
1220 imgsize + pos + spacing*i
\r
1222 m_images[i]->setScaleImage(true);
\r
1223 m_texts.push_back(env->addStaticText(
\r
1225 textsize + pos + spacing*i,
\r
1228 m_texts[i]->setBackgroundColor(
\r
1229 video::SColor(128,0,0,0));
\r
1230 m_texts[i]->setTextAlignment(
\r
1231 gui::EGUIA_CENTER,
\r
1232 gui::EGUIA_UPPERLEFT);
\r
1236 ~GUIQuickInventory()
\r
1238 for(u32 i=0; i<m_texts.size(); i++)
\r
1240 m_texts[i]->remove();
\r
1242 for(u32 i=0; i<m_images.size(); i++)
\r
1244 m_images[i]->remove();
\r
1248 virtual bool OnEvent(const SEvent& event)
\r
1253 void setSelection(s32 i)
\r
1262 start = m_selection - m_itemcount / 2;
\r
1264 InventoryList *mainlist = m_inventory->getList("main");
\r
1266 for(s32 i=0; i<m_itemcount; i++)
\r
1268 s32 j = i + start;
\r
1270 if(j > (s32)mainlist->getSize() - 1)
\r
1271 j -= mainlist->getSize();
\r
1273 j += mainlist->getSize();
\r
1275 InventoryItem *item = mainlist->getItem(j);
\r
1279 m_images[i]->setImage(NULL);
\r
1282 if(m_selection == j)
\r
1283 swprintf(t, 10, L"<-");
\r
1285 swprintf(t, 10, L"");
\r
1286 m_texts[i]->setText(t);
\r
1288 // The next ifs will segfault with a NULL pointer
\r
1293 m_images[i]->setImage(item->getImage());
\r
1296 if(m_selection == j)
\r
1297 swprintf(t, 10, SWPRINTF_CHARSTRING L" <-", item->getText().c_str());
\r
1299 swprintf(t, 10, SWPRINTF_CHARSTRING, item->getText().c_str());
\r
1300 m_texts[i]->setText(t);
\r
1306 core::array<gui::IGUIStaticText*> m_texts;
\r
1307 core::array<gui::IGUIImage*> m_images;
\r
1308 Inventory *m_inventory;
\r
1319 ChatLine(const std::wstring &a_text):
\r
1325 std::wstring text;
\r
1328 // These are defined global so that they're not optimized too much.
\r
1329 // Can't change them to volatile.
\r
1334 std::string tempstring;
\r
1335 std::string tempstring2;
\r
1340 dstream<<"The following test should take around 20ms."<<std::endl;
\r
1341 TimeTaker timer("Testing std::string speed");
\r
1342 const u32 jj = 10000;
\r
1343 for(u32 j=0; j<jj; j++)
\r
1347 const u32 ii = 10;
\r
1348 for(u32 i=0; i<ii; i++){
\r
1349 tempstring2 += "asd";
\r
1351 for(u32 i=0; i<ii+1; i++){
\r
1352 tempstring += "asd";
\r
1353 if(tempstring == tempstring2)
\r
1359 dstream<<"All of the following tests should take around 100ms each."
\r
1363 TimeTaker timer("Testing floating-point conversion speed");
\r
1365 for(u32 i=0; i<4000000; i++){
\r
1372 TimeTaker timer("Testing floating-point vector speed");
\r
1374 tempv3f1 = v3f(1,2,3);
\r
1375 tempv3f2 = v3f(4,5,6);
\r
1376 for(u32 i=0; i<10000000; i++){
\r
1377 tempf += tempv3f1.dotProduct(tempv3f2);
\r
1378 tempv3f2 += v3f(7,8,9);
\r
1383 TimeTaker timer("Testing core::map speed");
\r
1385 core::map<v2s16, f32> map1;
\r
1388 for(s16 y=0; y<ii; y++){
\r
1389 for(s16 x=0; x<ii; x++){
\r
1390 map1.insert(v2s16(x,y), tempf);
\r
1394 for(s16 y=ii-1; y>=0; y--){
\r
1395 for(s16 x=0; x<ii; x++){
\r
1396 tempf = map1[v2s16(x,y)];
\r
1402 dstream<<"Around 5000/ms should do well here."<<std::endl;
\r
1403 TimeTaker timer("Testing mutex speed");
\r
1416 // Do at least 10ms
\r
1417 while(timer.getTime() < 10);
\r
1419 u32 dtime = timer.stop();
\r
1420 u32 per_ms = n / dtime;
\r
1421 std::cout<<"Done. "<<dtime<<"ms, "
\r
1422 <<per_ms<<"/ms"<<std::endl;
\r
1426 int main(int argc, char *argv[])
\r
1429 Parse command line
\r
1432 // List all allowed options
\r
1433 core::map<std::string, ValueSpec> allowed_options;
\r
1434 allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
\r
1435 allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
\r
1436 "Run server directly"));
\r
1437 allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
\r
1438 "Load configuration from specified file"));
\r
1439 allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
\r
1440 allowed_options.insert("address", ValueSpec(VALUETYPE_STRING));
\r
1441 allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG));
\r
1442 allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1443 allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG));
\r
1444 allowed_options.insert("map-dir", ValueSpec(VALUETYPE_STRING));
\r
1446 allowed_options.insert("dstream-on-stderr", ValueSpec(VALUETYPE_FLAG));
\r
1448 allowed_options.insert("speedtests", ValueSpec(VALUETYPE_FLAG));
\r
1450 Settings cmd_args;
\r
1452 bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
\r
1454 if(ret == false || cmd_args.getFlag("help"))
\r
1456 dstream<<"Allowed options:"<<std::endl;
\r
1457 for(core::map<std::string, ValueSpec>::Iterator
\r
1458 i = allowed_options.getIterator();
\r
1459 i.atEnd() == false; i++)
\r
1461 dstream<<" --"<<i.getNode()->getKey();
\r
1462 if(i.getNode()->getValue().type == VALUETYPE_FLAG)
\r
1467 dstream<<" <value>";
\r
1469 dstream<<std::endl;
\r
1471 if(i.getNode()->getValue().help != NULL)
\r
1473 dstream<<" "<<i.getNode()->getValue().help
\r
1478 return cmd_args.getFlag("help") ? 0 : 1;
\r
1482 Low-level initialization
\r
1485 bool disable_stderr = false;
\r
1487 if(cmd_args.getFlag("dstream-on-stderr") == false)
\r
1488 disable_stderr = true;
\r
1491 // Initialize debug streams
\r
1492 debugstreams_init(disable_stderr, DEBUGFILE);
\r
1493 // Initialize debug stacks
\r
1494 debug_stacks_init();
\r
1496 DSTACK(__FUNCTION_NAME);
\r
1498 porting::initializePaths();
\r
1499 // Create user data directory
\r
1500 fs::CreateDir(porting::path_userdata);
\r
1502 // C-style stuff initialization
\r
1503 initializeMaterialProperties();
\r
1506 BEGIN_DEBUG_EXCEPTION_HANDLER
\r
1508 // Print startup message
\r
1509 dstream<<DTIME<<"minetest-c55"
\r
1510 " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
\r
1511 <<", "<<BUILD_INFO
\r
1515 Basic initialization
\r
1518 // Initialize default settings
\r
1519 set_default_settings();
\r
1521 // Set locale. This is for forcing '.' as the decimal point.
\r
1522 std::locale::global(std::locale("C"));
\r
1523 // This enables printing all characters in bitmap font
\r
1524 setlocale(LC_CTYPE, "en_US");
\r
1526 // Initialize sockets
\r
1528 atexit(sockets_cleanup);
\r
1538 // Path of configuration file in use
\r
1539 std::string configpath = "";
\r
1541 if(cmd_args.exists("config"))
\r
1543 bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
\r
1546 dstream<<"Could not read configuration from \""
\r
1547 <<cmd_args.get("config")<<"\""<<std::endl;
\r
1550 configpath = cmd_args.get("config");
\r
1554 core::array<std::string> filenames;
\r
1555 filenames.push_back(porting::path_userdata + "/minetest.conf");
\r
1556 #ifdef RUN_IN_PLACE
\r
1557 filenames.push_back(porting::path_userdata + "/../minetest.conf");
\r
1560 for(u32 i=0; i<filenames.size(); i++)
\r
1562 bool r = g_settings.readConfigFile(filenames[i].c_str());
\r
1565 configpath = filenames[i];
\r
1571 // Initialize random seed
\r
1576 Pre-initialize some stuff with a dummy irrlicht wrapper.
\r
1578 These are needed for unit tests at least.
\r
1581 IIrrlichtWrapper irrlicht_dummy;
\r
1583 init_mapnode(&irrlicht_dummy);
\r
1588 if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false)
\r
1589 || cmd_args.getFlag("enable-unittests") == true)
\r
1594 // Read map parameters from settings
\r
1596 HMParams hm_params;
\r
1597 /*hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
\r
1598 hm_params.randmax = g_settings.get("height_randmax");
\r
1599 hm_params.randfactor = g_settings.get("height_randfactor");
\r
1600 hm_params.base = g_settings.get("height_base");*/
\r
1602 MapParams map_params;
\r
1603 map_params.plants_amount = g_settings.getFloat("plants_amount");
\r
1604 map_params.ravines_amount = g_settings.getFloat("ravines_amount");
\r
1612 if(cmd_args.exists("port"))
\r
1613 port = cmd_args.getU16("port");
\r
1614 else if(cmd_args.exists("port"))
\r
1615 port = g_settings.getU16("port");
\r
1618 std::string map_dir = porting::path_userdata+"/map";
\r
1619 if(cmd_args.exists("map-dir"))
\r
1620 map_dir = cmd_args.get("map-dir");
\r
1621 else if(g_settings.exists("map-dir"))
\r
1622 map_dir = g_settings.get("map-dir");
\r
1624 // Run dedicated server if asked to
\r
1625 if(cmd_args.getFlag("server"))
\r
1627 DSTACK("Dedicated server branch");
\r
1630 Server server(map_dir.c_str(), hm_params, map_params);
\r
1631 server.start(port);
\r
1634 dedicated_server_loop(server);
\r
1643 // Address to connect to
\r
1644 std::string address = "";
\r
1646 if(cmd_args.exists("address"))
\r
1648 address = cmd_args.get("address");
\r
1652 address = g_settings.get("address");
\r
1655 std::string playername = g_settings.get("name");
\r
1658 Resolution selection
\r
1661 bool fullscreen = false;
\r
1662 u16 screenW = g_settings.getU16("screenW");
\r
1663 u16 screenH = g_settings.getU16("screenH");
\r
1667 MyEventReceiver receiver;
\r
1669 video::E_DRIVER_TYPE driverType;
\r
1672 //driverType = video::EDT_DIRECT3D9;
\r
1673 driverType = video::EDT_OPENGL;
\r
1675 driverType = video::EDT_OPENGL;
\r
1676 //driverType = video::EDT_BURNINGSVIDEO;
\r
1679 // create device and exit if creation failed
\r
1681 IrrlichtDevice *device;
\r
1682 device = createDevice(driverType,
\r
1683 core::dimension2d<u32>(screenW, screenH),
\r
1684 16, fullscreen, false, false, &receiver);
\r
1687 return 1; // could not create selected driver.
\r
1689 g_device = device;
\r
1690 g_irrlicht = new IrrlichtWrapper(device);
\r
1693 Speed tests (done after irrlicht is loaded to get timer)
\r
1695 if(cmd_args.getFlag("speedtests"))
\r
1697 dstream<<"Running speed tests"<<std::endl;
\r
1702 device->setResizable(true);
\r
1704 bool random_input = g_settings.getBool("random_input")
\r
1705 || cmd_args.getFlag("random-input");
\r
1707 g_input = new RandomInputHandler();
\r
1709 g_input = new RealInputHandler(device, &receiver);
\r
1712 Continue initialization
\r
1715 video::IVideoDriver* driver = device->getVideoDriver();
\r
1718 This changes the minimum allowed number of vertices in a VBO
\r
1720 //driver->setMinHardwareBufferVertexCount(50);
\r
1722 scene::ISceneManager* smgr = device->getSceneManager();
\r
1724 guienv = device->getGUIEnvironment();
\r
1725 gui::IGUISkin* skin = guienv->getSkin();
\r
1726 gui::IGUIFont* font = guienv->getFont(porting::getDataPath("fontlucida.png").c_str());
\r
1728 skin->setFont(font);
\r
1730 dstream<<"WARNING: Font file was not found."
\r
1731 " Using default font."<<std::endl;
\r
1732 // If font was not found, this will get us one
\r
1733 font = skin->getFont();
\r
1736 u32 text_height = font->getDimension(L"Hello, world!").Height;
\r
1737 dstream<<"text_height="<<text_height<<std::endl;
\r
1739 //skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0));
\r
1740 skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255));
\r
1741 //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0));
\r
1742 //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0));
\r
1743 skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0));
\r
1744 skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0));
\r
1747 Preload some textures and stuff
\r
1750 init_content_inventory_texture_paths();
\r
1751 init_mapnode(g_irrlicht);
\r
1752 init_mineral(g_irrlicht);
\r
1759 We need some kind of a root node to be able to add
\r
1760 custom gui elements directly on the screen.
\r
1761 Otherwise they won't be automatically drawn.
\r
1763 guiroot = guienv->addStaticText(L"",
\r
1764 core::rect<s32>(0, 0, 10000, 10000));
\r
1766 // First line of debug text
\r
1767 gui::IGUIStaticText *guitext = guienv->addStaticText(
\r
1769 core::rect<s32>(5, 5, 795, 5+text_height),
\r
1771 // Second line of debug text
\r
1772 gui::IGUIStaticText *guitext2 = guienv->addStaticText(
\r
1774 core::rect<s32>(5, 5+(text_height+5)*1, 795, (5+text_height)*2),
\r
1777 // At the middle of the screen
\r
1778 // Object infos are shown in this
\r
1779 gui::IGUIStaticText *guitext_info = guienv->addStaticText(
\r
1781 core::rect<s32>(100, 70, 100+400, 70+(text_height+5)),
\r
1785 gui::IGUIStaticText *guitext_chat = guienv->addStaticText(
\r
1787 core::rect<s32>(0,0,0,0),
\r
1789 guitext_chat->setBackgroundColor(video::SColor(96,0,0,0));
\r
1790 core::list<ChatLine> chat_lines;
\r
1793 If an error occurs, this is set to something and the
\r
1794 menu-game loop is restarted. It is then displayed before
\r
1797 std::wstring error_message = L"";
\r
1802 while(g_device->run())
\r
1805 // This is used for catching disconnects
\r
1810 Out-of-game menu loop.
\r
1812 Loop quits when menu returns proper parameters.
\r
1816 // Cursor can be non-visible when coming from the game
\r
1817 device->getCursorControl()->setVisible(true);
\r
1818 // Some stuff are left to scene manager when coming from the game
\r
1819 // (map at least?)
\r
1821 // Reset or hide the debug gui texts
\r
1822 guitext->setText(L"Minetest-c55");
\r
1823 guitext2->setVisible(false);
\r
1824 guitext_info->setVisible(false);
\r
1825 guitext_chat->setVisible(false);
\r
1827 // Initialize menu data
\r
1828 MainMenuData menudata;
\r
1829 menudata.address = narrow_to_wide(address);
\r
1830 menudata.name = narrow_to_wide(playername);
\r
1831 menudata.port = narrow_to_wide(itos(port));
\r
1832 menudata.creative_mode = g_settings.getBool("creative_mode");
\r
1834 GUIMainMenu *menu =
\r
1835 new GUIMainMenu(guienv, guiroot, -1,
\r
1836 &g_menumgr, &menudata, &g_gamecallback);
\r
1837 menu->allowFocusRemoval(true);
\r
1839 if(error_message != L"")
\r
1841 GUIMessageMenu *menu2 =
\r
1842 new GUIMessageMenu(guienv, guiroot, -1,
\r
1843 &g_menumgr, error_message.c_str());
\r
1845 error_message = L"";
\r
1848 video::IVideoDriver* driver = g_device->getVideoDriver();
\r
1850 dstream<<"Created main menu"<<std::endl;
\r
1852 while(g_device->run())
\r
1854 // Run global IrrlichtWrapper's main thread processing stuff
\r
1855 g_irrlicht->Run();
\r
1857 if(menu->getStatus() == true)
\r
1860 //driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1861 driver->beginScene(true, true, video::SColor(255,128,128,128));
\r
1862 guienv->drawAll();
\r
1863 driver->endScene();
\r
1866 // Break out of menu-game loop to shut down cleanly
\r
1867 if(g_device->run() == false)
\r
1870 dstream<<"Dropping main menu"<<std::endl;
\r
1874 // Delete map if requested
\r
1875 if(menudata.delete_map)
\r
1877 bool r = fs::RecursiveDeleteContent(map_dir);
\r
1879 error_message = L"Delete failed";
\r
1883 playername = wide_to_narrow(menudata.name);
\r
1884 address = wide_to_narrow(menudata.address);
\r
1885 port = stoi(wide_to_narrow(menudata.port));
\r
1886 g_settings.set("creative_mode", itos(menudata.creative_mode));
\r
1888 // Check for valid parameters, restart menu if invalid.
\r
1889 if(playername == "")
\r
1891 error_message = L"Name required.";
\r
1896 g_settings.set("name", playername);
\r
1897 g_settings.set("address", address);
\r
1898 g_settings.set("port", itos(port));
\r
1899 // Update configuration file
\r
1900 if(configpath != "")
\r
1901 g_settings.updateConfigFile(configpath.c_str());
\r
1903 // Continue to game
\r
1907 // Break out of menu-game loop to shut down cleanly
\r
1908 if(g_device->run() == false)
\r
1912 Make a scope here so that the client and the server and other
\r
1913 stuff gets removed when disconnected or the irrlicht device
\r
1918 // This is set to true at the end of the scope
\r
1919 g_irrlicht->Shutdown(false);
\r
1922 Draw "Loading" screen
\r
1924 const wchar_t *text = L"Loading and connecting...";
\r
1925 core::vector2d<s32> center(screenW/2, screenH/2);
\r
1926 core::vector2d<s32> textsize(300, text_height);
\r
1927 core::rect<s32> textrect(center - textsize/2, center + textsize/2);
\r
1929 gui::IGUIStaticText *gui_loadingtext = guienv->addStaticText(
\r
1930 text, textrect, false, false);
\r
1931 gui_loadingtext->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
\r
1933 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1934 guienv->drawAll();
\r
1935 driver->endScene();
\r
1937 std::cout<<DTIME<<"Creating server and client"<<std::endl;
\r
1941 SharedPtr will delete it when it goes out of scope.
\r
1943 SharedPtr<Server> server;
\r
1944 if(address == ""){
\r
1945 server = new Server(map_dir, hm_params, map_params);
\r
1946 server->start(port);
\r
1953 Client client(device, playername.c_str(), draw_control);
\r
1955 g_client = &client;
\r
1957 Address connect_address(0,0,0,0, port);
\r
1960 connect_address.Resolve("localhost");
\r
1962 connect_address.Resolve(address.c_str());
\r
1964 catch(ResolveError &e)
\r
1966 std::cout<<DTIME<<"Couldn't resolve address"<<std::endl;
\r
1968 error_message = L"Couldn't resolve address";
\r
1969 gui_loadingtext->remove();
\r
1973 std::cout<<DTIME<<"Connecting to server..."<<std::endl;
\r
1974 client.connect(connect_address);
\r
1977 while(client.connectedAndInitialized() == false)
\r
1980 driver->beginScene(true, true, video::SColor(255,0,0,0));
\r
1981 guienv->drawAll();
\r
1982 driver->endScene();
\r
1984 // Update client and server
\r
1988 if(server != NULL)
\r
1989 server->step(0.1);
\r
1995 catch(con::PeerNotFoundException &e)
\r
1997 std::cout<<DTIME<<"Timed out."<<std::endl;
\r
1999 error_message = L"Connection timed out.";
\r
2000 gui_loadingtext->remove();
\r
2007 /*scene::ISceneNode* skybox;
\r
2008 skybox = smgr->addSkyBoxSceneNode(
\r
2009 driver->getTexture(porting::getDataPath("skybox2.png").c_str()),
\r
2010 driver->getTexture(porting::getDataPath("skybox3.png").c_str()),
\r
2011 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2012 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2013 driver->getTexture(porting::getDataPath("skybox1.png").c_str()),
\r
2014 driver->getTexture(porting::getDataPath("skybox1.png").c_str()));*/
\r
2017 Create the camera node
\r
2020 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
\r
2021 0, // Camera parent
\r
2022 v3f(BS*100, BS*2, BS*100), // Look from
\r
2023 v3f(BS*100+1, BS*2, BS*100), // Look to
\r
2027 if(camera == NULL)
\r
2030 video::SColor skycolor = video::SColor(255,90,140,200);
\r
2032 camera->setFOV(FOV_ANGLE);
\r
2034 // Just so big a value that everything rendered is visible
\r
2035 camera->setFarValue(100000*BS);
\r
2037 f32 camera_yaw = 0; // "right/left"
\r
2038 f32 camera_pitch = 0; // "up/down"
\r
2044 gui_loadingtext->remove();
\r
2047 Add some gui stuff
\r
2050 GUIQuickInventory *quick_inventory = new GUIQuickInventory
\r
2051 (guienv, NULL, v2s32(10, 70), 5, &local_inventory);
\r
2053 // Test the text input system
\r
2054 /*(new GUITextInputMenu(guienv, guiroot, -1, &g_menumgr,
\r
2056 /*GUIMessageMenu *menu =
\r
2057 new GUIMessageMenu(guienv, guiroot, -1,
\r
2062 // Launch pause menu
\r
2063 (new GUIPauseMenu(guienv, guiroot, -1, &g_gamecallback,
\r
2064 &g_menumgr))->drop();
\r
2067 guitext2->setVisible(true);
\r
2068 guitext_info->setVisible(true);
\r
2069 guitext_chat->setVisible(true);
\r
2072 Some statistics are collected in these
\r
2075 u32 beginscenetime = 0;
\r
2076 u32 scenetime = 0;
\r
2077 u32 endscenetime = 0;
\r
2080 //throw con::PeerNotFoundException("lol");
\r
2086 bool first_loop_after_window_activation = true;
\r
2088 // Time is in milliseconds
\r
2089 // NOTE: getRealTime() causes strange problems in wine (imprecision?)
\r
2090 // NOTE: So we have to use getTime() and call run()s between them
\r
2091 u32 lasttime = device->getTimer()->getTime();
\r
2093 while(device->run())
\r
2095 if(g_disconnect_requested)
\r
2097 g_disconnect_requested = false;
\r
2102 Run global IrrlichtWrapper's main thread processing stuff
\r
2104 g_irrlicht->Run();
\r
2107 Random calculations
\r
2109 v2u32 screensize = driver->getScreenSize();
\r
2110 core::vector2d<s32> displaycenter(screensize.X/2,screensize.Y/2);
\r
2112 // Hilight boxes collected during the loop and displayed
\r
2113 core::list< core::aabbox3d<f32> > hilightboxes;
\r
2116 std::wstring infotext;
\r
2118 //TimeTaker //timer1("//timer1");
\r
2120 // Time of frame without fps limit
\r
2124 // not using getRealTime is necessary for wine
\r
2125 u32 time = device->getTimer()->getTime();
\r
2126 if(time > lasttime)
\r
2127 busytime_u32 = time - lasttime;
\r
2130 busytime = busytime_u32 / 1000.0;
\r
2133 //std::cout<<"busytime_u32="<<busytime_u32<<std::endl;
\r
2135 // Absolutelu necessary for wine!
\r
2142 updateViewingRange(busytime, &client);
\r
2149 float fps_max = g_settings.getFloat("fps_max");
\r
2150 u32 frametime_min = 1000./fps_max;
\r
2152 if(busytime_u32 < frametime_min)
\r
2154 u32 sleeptime = frametime_min - busytime_u32;
\r
2155 device->sleep(sleeptime);
\r
2159 // Absolutelu necessary for wine!
\r
2163 Time difference calculation
\r
2165 f32 dtime; // in seconds
\r
2167 u32 time = device->getTimer()->getTime();
\r
2168 if(time > lasttime)
\r
2169 dtime = (time - lasttime) / 1000.0;
\r
2175 Time average and jitter calculation
\r
2178 static f32 dtime_avg1 = 0.0;
\r
2179 dtime_avg1 = dtime_avg1 * 0.98 + dtime * 0.02;
\r
2180 f32 dtime_jitter1 = dtime - dtime_avg1;
\r
2182 static f32 dtime_jitter1_max_sample = 0.0;
\r
2183 static f32 dtime_jitter1_max_fraction = 0.0;
\r
2185 static f32 jitter1_max = 0.0;
\r
2186 static f32 counter = 0.0;
\r
2187 if(dtime_jitter1 > jitter1_max)
\r
2188 jitter1_max = dtime_jitter1;
\r
2193 dtime_jitter1_max_sample = jitter1_max;
\r
2194 dtime_jitter1_max_fraction
\r
2195 = dtime_jitter1_max_sample / (dtime_avg1+0.001);
\r
2196 jitter1_max = 0.0;
\r
2201 Busytime average and jitter calculation
\r
2204 static f32 busytime_avg1 = 0.0;
\r
2205 busytime_avg1 = busytime_avg1 * 0.98 + busytime * 0.02;
\r
2206 f32 busytime_jitter1 = busytime - busytime_avg1;
\r
2208 static f32 busytime_jitter1_max_sample = 0.0;
\r
2209 static f32 busytime_jitter1_min_sample = 0.0;
\r
2211 static f32 jitter1_max = 0.0;
\r
2212 static f32 jitter1_min = 0.0;
\r
2213 static f32 counter = 0.0;
\r
2214 if(busytime_jitter1 > jitter1_max)
\r
2215 jitter1_max = busytime_jitter1;
\r
2216 if(busytime_jitter1 < jitter1_min)
\r
2217 jitter1_min = busytime_jitter1;
\r
2219 if(counter > 0.0){
\r
2221 busytime_jitter1_max_sample = jitter1_max;
\r
2222 busytime_jitter1_min_sample = jitter1_min;
\r
2223 jitter1_max = 0.0;
\r
2224 jitter1_min = 0.0;
\r
2229 Debug info for client
\r
2232 static float counter = 0.0;
\r
2237 client.printDebugInfo(std::cout);
\r
2242 Input handler step()
\r
2244 g_input->step(dtime);
\r
2247 Player speed control
\r
2256 bool a_superspeed,
\r
2259 PlayerControl control(
\r
2260 g_input->isKeyDown(irr::KEY_KEY_W),
\r
2261 g_input->isKeyDown(irr::KEY_KEY_S),
\r
2262 g_input->isKeyDown(irr::KEY_KEY_A),
\r
2263 g_input->isKeyDown(irr::KEY_KEY_D),
\r
2264 g_input->isKeyDown(irr::KEY_SPACE),
\r
2265 g_input->isKeyDown(irr::KEY_KEY_2),
\r
2269 client.setPlayerControl(control);
\r
2273 Process environment
\r
2277 //TimeTaker timer("client.step(dtime)");
\r
2278 client.step(dtime);
\r
2279 //client.step(dtime_avg1);
\r
2282 if(server != NULL)
\r
2284 //TimeTaker timer("server->step(dtime)");
\r
2285 server->step(dtime);
\r
2288 v3f player_position = client.getPlayerPosition();
\r
2290 //TimeTaker //timer2("//timer2");
\r
2293 Mouse and camera control
\r
2296 if((device->isWindowActive() && noMenuActive()) || random_input)
\r
2299 device->getCursorControl()->setVisible(false);
\r
2301 if(first_loop_after_window_activation){
\r
2302 //std::cout<<"window active, first loop"<<std::endl;
\r
2303 first_loop_after_window_activation = false;
\r
2306 s32 dx = g_input->getMousePos().X - displaycenter.X;
\r
2307 s32 dy = g_input->getMousePos().Y - displaycenter.Y;
\r
2308 //std::cout<<"window active, pos difference "<<dx<<","<<dy<<std::endl;
\r
2309 camera_yaw -= dx*0.2;
\r
2310 camera_pitch += dy*0.2;
\r
2311 if(camera_pitch < -89.5) camera_pitch = -89.5;
\r
2312 if(camera_pitch > 89.5) camera_pitch = 89.5;
\r
2314 g_input->setMousePos(displaycenter.X, displaycenter.Y);
\r
2317 device->getCursorControl()->setVisible(true);
\r
2319 //std::cout<<"window inactive"<<std::endl;
\r
2320 first_loop_after_window_activation = true;
\r
2323 camera_yaw = wrapDegrees(camera_yaw);
\r
2324 camera_pitch = wrapDegrees(camera_pitch);
\r
2326 v3f camera_direction = v3f(0,0,1);
\r
2327 camera_direction.rotateYZBy(camera_pitch);
\r
2328 camera_direction.rotateXZBy(camera_yaw);
\r
2330 // This is at the height of the eyes of the current figure
\r
2331 //v3f camera_position = player_position + v3f(0, BS+BS/2, 0);
\r
2332 // This is more like in minecraft
\r
2333 v3f camera_position = player_position + v3f(0, BS+BS*0.625, 0);
\r
2335 camera->setPosition(camera_position);
\r
2336 // *100.0 helps in large map coordinates
\r
2337 camera->setTarget(camera_position + camera_direction * 100.0);
\r
2339 if(FIELD_OF_VIEW_TEST){
\r
2340 //client.m_env.getMap().updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
2341 client.updateCamera(v3f(0,0,0), v3f(0,0,1));
\r
2344 //client.m_env.getMap().updateCamera(camera_position, camera_direction);
\r
2345 //TimeTaker timer("client.updateCamera");
\r
2346 client.updateCamera(camera_position, camera_direction);
\r
2350 //TimeTaker //timer3("//timer3");
\r
2353 Calculate what block is the crosshair pointing to
\r
2356 //u32 t1 = device->getTimer()->getRealTime();
\r
2358 //f32 d = 4; // max. distance
\r
2359 f32 d = 4; // max. distance
\r
2360 core::line3d<f32> shootline(camera_position,
\r
2361 camera_position + camera_direction * BS * (d+1));
\r
2363 MapBlockObject *selected_object = client.getSelectedObject
\r
2364 (d*BS, camera_position, shootline);
\r
2367 If it's pointing to a MapBlockObject
\r
2370 if(selected_object != NULL)
\r
2372 //dstream<<"Client returned selected_object != NULL"<<std::endl;
\r
2374 core::aabbox3d<f32> box_on_map
\r
2375 = selected_object->getSelectionBoxOnMap();
\r
2377 hilightboxes.push_back(box_on_map);
\r
2379 infotext = narrow_to_wide(selected_object->infoText());
\r
2381 if(g_input->getLeftClicked())
\r
2383 std::cout<<DTIME<<"Left-clicked object"<<std::endl;
\r
2384 client.clickObject(0, selected_object->getBlock()->getPos(),
\r
2385 selected_object->getId(), g_selected_item);
\r
2387 else if(g_input->getRightClicked())
\r
2389 std::cout<<DTIME<<"Right-clicked object"<<std::endl;
\r
2391 Check if we want to modify the object ourselves
\r
2393 if(selected_object->getTypeId() == MAPBLOCKOBJECT_TYPE_SIGN)
\r
2395 dstream<<"Sign object right-clicked"<<std::endl;
\r
2397 if(random_input == false)
\r
2399 // Get a new text for it
\r
2401 TextDest *dest = new TextDestSign(
\r
2402 selected_object->getBlock()->getPos(),
\r
2403 selected_object->getId(),
\r
2406 SignObject *sign_object = (SignObject*)selected_object;
\r
2408 std::wstring wtext =
\r
2409 narrow_to_wide(sign_object->getText());
\r
2411 (new GUITextInputMenu(guienv, guiroot, -1,
\r
2417 Otherwise pass the event to the server as-is
\r
2421 client.clickObject(1, selected_object->getBlock()->getPos(),
\r
2422 selected_object->getId(), g_selected_item);
\r
2426 else // selected_object == NULL
\r
2430 Find out which node we are pointing at
\r
2433 bool nodefound = false;
\r
2435 v3s16 neighbourpos;
\r
2436 core::aabbox3d<f32> nodehilightbox;
\r
2437 f32 mindistance = BS * 1001;
\r
2439 v3s16 pos_i = floatToInt(player_position);
\r
2441 /*std::cout<<"pos_i=("<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z<<")"
\r
2445 s16 ystart = pos_i.Y + 0 - (camera_direction.Y<0 ? a : 1);
\r
2446 s16 zstart = pos_i.Z - (camera_direction.Z<0 ? a : 1);
\r
2447 s16 xstart = pos_i.X - (camera_direction.X<0 ? a : 1);
\r
2448 s16 yend = pos_i.Y + 1 + (camera_direction.Y>0 ? a : 1);
\r
2449 s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
\r
2450 s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
\r
2452 for(s16 y = ystart; y <= yend; y++)
\r
2453 for(s16 z = zstart; z <= zend; z++)
\r
2454 for(s16 x = xstart; x <= xend; x++)
\r
2459 n = client.getNode(v3s16(x,y,z));
\r
2460 if(content_pointable(n.d) == false)
\r
2463 catch(InvalidPositionException &e)
\r
2469 v3f npf = intToFloat(np);
\r
2474 v3s16(0,0,1), // back
\r
2475 v3s16(0,1,0), // top
\r
2476 v3s16(1,0,0), // right
\r
2477 v3s16(0,0,-1), // front
\r
2478 v3s16(0,-1,0), // bottom
\r
2479 v3s16(-1,0,0), // left
\r
2485 if(n.d == CONTENT_TORCH)
\r
2487 v3s16 dir = unpackDir(n.dir);
\r
2488 v3f dir_f = v3f(dir.X, dir.Y, dir.Z);
\r
2489 dir_f *= BS/2 - BS/6 - BS/20;
\r
2490 v3f cpf = npf + dir_f;
\r
2491 f32 distance = (cpf - camera_position).getLength();
\r
2493 core::aabbox3d<f32> box;
\r
2496 if(dir == v3s16(0,-1,0))
\r
2498 box = core::aabbox3d<f32>(
\r
2499 npf - v3f(BS/6, BS/2, BS/6),
\r
2500 npf + v3f(BS/6, -BS/2+BS/3*2, BS/6)
\r
2504 else if(dir == v3s16(0,1,0))
\r
2506 box = core::aabbox3d<f32>(
\r
2507 npf - v3f(BS/6, -BS/2+BS/3*2, BS/6),
\r
2508 npf + v3f(BS/6, BS/2, BS/6)
\r
2514 box = core::aabbox3d<f32>(
\r
2515 cpf - v3f(BS/6, BS/3, BS/6),
\r
2516 cpf + v3f(BS/6, BS/3, BS/6)
\r
2520 if(distance < mindistance)
\r
2522 if(box.intersectsWithLine(shootline))
\r
2526 neighbourpos = np;
\r
2527 mindistance = distance;
\r
2528 nodehilightbox = box;
\r
2537 for(u16 i=0; i<6; i++)
\r
2539 v3f dir_f = v3f(dirs[i].X,
\r
2540 dirs[i].Y, dirs[i].Z);
\r
2541 v3f centerpoint = npf + dir_f * BS/2;
\r
2543 (centerpoint - camera_position).getLength();
\r
2545 if(distance < mindistance)
\r
2547 core::CMatrix4<f32> m;
\r
2548 m.buildRotateFromTo(v3f(0,0,1), dir_f);
\r
2550 // This is the back face
\r
2551 v3f corners[2] = {
\r
2552 v3f(BS/2, BS/2, BS/2),
\r
2553 v3f(-BS/2, -BS/2, BS/2+d)
\r
2556 for(u16 j=0; j<2; j++)
\r
2558 m.rotateVect(corners[j]);
\r
2559 corners[j] += npf;
\r
2562 core::aabbox3d<f32> facebox(corners[0]);
\r
2563 facebox.addInternalPoint(corners[1]);
\r
2565 if(facebox.intersectsWithLine(shootline))
\r
2569 neighbourpos = np + dirs[i];
\r
2570 mindistance = distance;
\r
2572 //nodehilightbox = facebox;
\r
2574 const float d = 0.502;
\r
2575 core::aabbox3d<f32> nodebox
\r
2576 (-BS*d, -BS*d, -BS*d, BS*d, BS*d, BS*d);
\r
2577 v3f nodepos_f = intToFloat(nodepos);
\r
2578 nodebox.MinEdge += nodepos_f;
\r
2579 nodebox.MaxEdge += nodepos_f;
\r
2580 nodehilightbox = nodebox;
\r
2582 } // if distance < mindistance
\r
2584 } // regular block
\r
2587 static float nodig_delay_counter = 0.0;
\r
2591 static v3s16 nodepos_old(-32768,-32768,-32768);
\r
2593 static float dig_time = 0.0;
\r
2594 static u16 dig_index = 0;
\r
2596 // Visualize selection
\r
2598 hilightboxes.push_back(nodehilightbox);
\r
2602 if(g_input->getLeftReleased())
\r
2604 client.clearTempMod(nodepos);
\r
2608 if(nodig_delay_counter > 0.0)
\r
2610 nodig_delay_counter -= dtime;
\r
2614 if(nodepos != nodepos_old)
\r
2616 std::cout<<DTIME<<"Pointing at ("<<nodepos.X<<","
\r
2617 <<nodepos.Y<<","<<nodepos.Z<<")"<<std::endl;
\r
2619 if(nodepos_old != v3s16(-32768,-32768,-32768))
\r
2621 client.clearTempMod(nodepos_old);
\r
2626 if(g_input->getLeftClicked() ||
\r
2627 (g_input->getLeftState() && nodepos != nodepos_old))
\r
2629 dstream<<DTIME<<"Started digging"<<std::endl;
\r
2630 client.groundAction(0, nodepos, neighbourpos, g_selected_item);
\r
2632 if(g_input->getLeftClicked())
\r
2634 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, 0));
\r
2636 if(g_input->getLeftState())
\r
2638 MapNode n = client.getNode(nodepos);
\r
2640 // Get tool name. Default is "" = bare hands
\r
2641 std::string toolname = "";
\r
2642 InventoryList *mlist = local_inventory.getList("main");
\r
2645 InventoryItem *item = mlist->getItem(g_selected_item);
\r
2646 if(item && (std::string)item->getName() == "ToolItem")
\r
2648 ToolItem *titem = (ToolItem*)item;
\r
2649 toolname = titem->getToolName();
\r
2653 // Get digging properties for material and tool
\r
2654 u8 material = n.d;
\r
2655 DiggingProperties prop =
\r
2656 getDiggingProperties(material, toolname);
\r
2658 float dig_time_complete = 0.0;
\r
2660 if(prop.diggable == false)
\r
2662 /*dstream<<"Material "<<(int)material
\r
2663 <<" not diggable with \""
\r
2664 <<toolname<<"\""<<std::endl;*/
\r
2665 // I guess nobody will wait for this long
\r
2666 dig_time_complete = 10000000.0;
\r
2670 dig_time_complete = prop.time;
\r
2673 if(dig_time_complete >= 0.001)
\r
2675 dig_index = (u16)((float)CRACK_ANIMATION_LENGTH
\r
2676 * dig_time/dig_time_complete);
\r
2678 // This is for torches
\r
2681 dig_index = CRACK_ANIMATION_LENGTH;
\r
2684 if(dig_index < CRACK_ANIMATION_LENGTH)
\r
2686 //TimeTaker timer("client.setTempMod");
\r
2687 //dstream<<"dig_index="<<dig_index<<std::endl;
\r
2688 client.setTempMod(nodepos, NodeMod(NODEMOD_CRACK, dig_index));
\r
2692 dstream<<DTIME<<"Digging completed"<<std::endl;
\r
2693 client.groundAction(3, nodepos, neighbourpos, g_selected_item);
\r
2694 client.clearTempMod(nodepos);
\r
2695 client.removeNode(nodepos);
\r
2699 nodig_delay_counter = dig_time_complete
\r
2700 / (float)CRACK_ANIMATION_LENGTH;
\r
2702 // We don't want a corresponding delay to
\r
2703 // very time consuming nodes
\r
2704 if(nodig_delay_counter > 0.5)
\r
2706 nodig_delay_counter = 0.5;
\r
2708 // We want a slight delay to very little
\r
2709 // time consuming nodes
\r
2710 float mindelay = 0.15;
\r
2711 if(nodig_delay_counter < mindelay)
\r
2713 nodig_delay_counter = mindelay;
\r
2717 dig_time += dtime;
\r
2721 if(g_input->getRightClicked())
\r
2723 std::cout<<DTIME<<"Ground right-clicked"<<std::endl;
\r
2724 client.groundAction(1, nodepos, neighbourpos, g_selected_item);
\r
2727 nodepos_old = nodepos;
\r
2732 } // selected_object == NULL
\r
2734 g_input->resetLeftClicked();
\r
2735 g_input->resetRightClicked();
\r
2737 if(g_input->getLeftReleased())
\r
2739 std::cout<<DTIME<<"Left button released (stopped digging)"
\r
2741 client.groundAction(2, v3s16(0,0,0), v3s16(0,0,0), 0);
\r
2743 if(g_input->getRightReleased())
\r
2745 //std::cout<<DTIME<<"Right released"<<std::endl;
\r
2749 g_input->resetLeftReleased();
\r
2750 g_input->resetRightReleased();
\r
2753 Calculate stuff for drawing
\r
2756 camera->setAspectRatio((f32)screensize.X / (f32)screensize.Y);
\r
2758 u32 daynight_ratio = client.getDayNightRatio();
\r
2759 /*video::SColor bgcolor = video::SColor(
\r
2761 skycolor.getRed() * daynight_ratio / 1000,
\r
2762 skycolor.getGreen() * daynight_ratio / 1000,
\r
2763 skycolor.getBlue() * daynight_ratio / 1000);*/
\r
2765 u8 l = decode_light((daynight_ratio * LIGHT_SUN) / 1000);
\r
2766 video::SColor bgcolor = video::SColor(
\r
2768 skycolor.getRed() * l / 255,
\r
2769 skycolor.getGreen() * l / 255,
\r
2770 skycolor.getBlue() * l / 255);
\r
2776 if(g_settings.getBool("enable_fog") == true)
\r
2778 f32 range = draw_control.wanted_range * BS;
\r
2779 if(draw_control.range_all)
\r
2780 range = 100000*BS;
\r
2784 video::EFT_FOG_LINEAR,
\r
2788 false, // pixel fog
\r
2789 false // range fog
\r
2795 Update gui stuff (0ms)
\r
2798 //TimeTaker guiupdatetimer("Gui updating");
\r
2801 wchar_t temptext[150];
\r
2803 static float drawtime_avg = 0;
\r
2804 drawtime_avg = drawtime_avg * 0.95 + (float)drawtime*0.05;
\r
2805 static float beginscenetime_avg = 0;
\r
2806 beginscenetime_avg = beginscenetime_avg * 0.95 + (float)beginscenetime*0.05;
\r
2807 static float scenetime_avg = 0;
\r
2808 scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
\r
2809 static float endscenetime_avg = 0;
\r
2810 endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;
\r
2812 swprintf(temptext, 150, L"Minetest-c55 ("
\r
2814 L", R: range_all=%i"
\r
2816 L" drawtime=%.0f, beginscenetime=%.0f, scenetime=%.0f, endscenetime=%.0f",
\r
2818 draw_control.range_all,
\r
2820 beginscenetime_avg,
\r
2825 guitext->setText(temptext);
\r
2829 wchar_t temptext[150];
\r
2830 swprintf(temptext, 150,
\r
2831 L"(% .1f, % .1f, % .1f)"
\r
2832 L" (% .3f < btime_jitter < % .3f"
\r
2833 L", dtime_jitter = % .1f %%"
\r
2834 L", v_range = %.1f)",
\r
2835 player_position.X/BS,
\r
2836 player_position.Y/BS,
\r
2837 player_position.Z/BS,
\r
2838 busytime_jitter1_min_sample,
\r
2839 busytime_jitter1_max_sample,
\r
2840 dtime_jitter1_max_fraction * 100.0,
\r
2841 draw_control.wanted_range
\r
2844 guitext2->setText(temptext);
\r
2848 guitext_info->setText(infotext.c_str());
\r
2852 Get chat messages from client
\r
2855 // Get new messages
\r
2856 std::wstring message;
\r
2857 while(client.getChatMessage(message))
\r
2859 chat_lines.push_back(ChatLine(message));
\r
2860 /*if(chat_lines.size() > 6)
\r
2862 core::list<ChatLine>::Iterator
\r
2863 i = chat_lines.begin();
\r
2864 chat_lines.erase(i);
\r
2867 // Append them to form the whole static text and throw
\r
2868 // it to the gui element
\r
2869 std::wstring whole;
\r
2870 // This will correspond to the line number counted from
\r
2871 // top to bottom, from size-1 to 0
\r
2872 s16 line_number = chat_lines.size();
\r
2873 // Count of messages to be removed from the top
\r
2874 u16 to_be_removed_count = 0;
\r
2875 for(core::list<ChatLine>::Iterator
\r
2876 i = chat_lines.begin();
\r
2877 i != chat_lines.end(); i++)
\r
2879 // After this, line number is valid for this loop
\r
2882 (*i).age += dtime;
\r
2884 This results in a maximum age of 60*6 to the
\r
2885 lowermost line and a maximum of 6 lines
\r
2887 float allowed_age = (6-line_number) * 60.0;
\r
2889 if((*i).age > allowed_age)
\r
2891 to_be_removed_count++;
\r
2894 whole += (*i).text + L'\n';
\r
2896 for(u16 i=0; i<to_be_removed_count; i++)
\r
2898 core::list<ChatLine>::Iterator
\r
2899 it = chat_lines.begin();
\r
2900 chat_lines.erase(it);
\r
2902 guitext_chat->setText(whole.c_str());
\r
2903 // Update gui element size and position
\r
2904 core::rect<s32> rect(
\r
2906 screensize.Y - 10 - text_height*chat_lines.size(),
\r
2907 screensize.X - 10,
\r
2910 guitext_chat->setRelativePosition(rect);
\r
2912 if(chat_lines.size() == 0)
\r
2913 guitext_chat->setVisible(false);
\r
2915 guitext_chat->setVisible(true);
\r
2922 static u16 old_selected_item = 65535;
\r
2923 if(client.getLocalInventoryUpdated()
\r
2924 || g_selected_item != old_selected_item)
\r
2926 old_selected_item = g_selected_item;
\r
2927 //std::cout<<"Updating local inventory"<<std::endl;
\r
2928 client.getLocalInventory(local_inventory);
\r
2929 quick_inventory->setSelection(g_selected_item);
\r
2930 quick_inventory->update();
\r
2934 Send actions returned by the inventory menu
\r
2936 while(inventory_action_queue.size() != 0)
\r
2938 InventoryAction *a = inventory_action_queue.pop_front();
\r
2940 client.sendInventoryAction(a);
\r
2949 TimeTaker drawtimer("Drawing");
\r
2953 TimeTaker timer("beginScene");
\r
2954 driver->beginScene(true, true, bgcolor);
\r
2955 //driver->beginScene(false, true, bgcolor);
\r
2956 beginscenetime = timer.stop(true);
\r
2961 //std::cout<<DTIME<<"smgr->drawAll()"<<std::endl;
\r
2964 TimeTaker timer("smgr");
\r
2966 scenetime = timer.stop(true);
\r
2970 //TimeTaker timer9("auxiliary drawings");
\r
2974 //TimeTaker //timer10("//timer10");
\r
2976 video::SMaterial m;
\r
2977 //m.Thickness = 10;
\r
2979 m.Lighting = false;
\r
2980 driver->setMaterial(m);
\r
2982 driver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
\r
2984 for(core::list< core::aabbox3d<f32> >::Iterator i=hilightboxes.begin();
\r
2985 i != hilightboxes.end(); i++)
\r
2987 /*std::cout<<"hilightbox min="
\r
2988 <<"("<<i->MinEdge.X<<","<<i->MinEdge.Y<<","<<i->MinEdge.Z<<")"
\r
2990 <<"("<<i->MaxEdge.X<<","<<i->MaxEdge.Y<<","<<i->MaxEdge.Z<<")"
\r
2992 driver->draw3DBox(*i, video::SColor(255,0,0,0));
\r
2998 driver->draw2DLine(displaycenter - core::vector2d<s32>(10,0),
\r
2999 displaycenter + core::vector2d<s32>(10,0),
\r
3000 video::SColor(255,255,255,255));
\r
3001 driver->draw2DLine(displaycenter - core::vector2d<s32>(0,10),
\r
3002 displaycenter + core::vector2d<s32>(0,10),
\r
3003 video::SColor(255,255,255,255));
\r
3008 //TimeTaker //timer11("//timer11");
\r
3014 guienv->drawAll();
\r
3018 TimeTaker timer("endScene");
\r
3019 driver->endScene();
\r
3020 endscenetime = timer.stop(true);
\r
3023 drawtime = drawtimer.stop(true);
\r
3029 static s16 lastFPS = 0;
\r
3030 //u16 fps = driver->getFPS();
\r
3031 u16 fps = (1.0/dtime_avg1);
\r
3033 if (lastFPS != fps)
\r
3035 core::stringw str = L"Minetest [";
\r
3036 str += driver->getName();
\r
3040 device->setWindowCaption(str.c_str());
\r
3046 device->yield();*/
\r
3049 delete quick_inventory;
\r
3052 Disable texture fetches and other stuff that is queued
\r
3053 to be processed by the main loop.
\r
3055 This has to be done before client goes out of scope.
\r
3057 g_irrlicht->Shutdown(true);
\r
3059 } // client and server are deleted at this point
\r
3062 catch(con::PeerNotFoundException &e)
\r
3064 dstream<<DTIME<<"Connection timed out."<<std::endl;
\r
3065 error_message = L"Connection timed out.";
\r
3068 } // Menu-game loop
\r
3073 In the end, delete the Irrlicht device.
\r
3078 Update configuration file
\r
3080 /*if(configpath != "")
\r
3082 g_settings.updateConfigFile(configpath.c_str());
\r
3085 END_DEBUG_EXCEPTION_HANDLER
\r
3087 debugstreams_deinit();
\r