X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fmain.cpp;h=72daaef012a0fe1360e7e51384d7fce41823e703;hb=630f453da402c7b6f0af9ea32aa8b127ce2e4629;hp=83f532af1b2b94f5026300479b41b63efc041c5d;hpb=2f4a92d70192468096e35974f0725532aef837b1;p=minetest.git diff --git a/src/main.cpp b/src/main.cpp index 83f532af1..72daaef01 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,1699 +1,978 @@ /* -Minetest-c55 -Copyright (C) 2010-2011 celeron55, Perttu Ahola +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +GNU Lesser General Public License for more details. -You should have received a copy of the GNU General Public License along +You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -/* -=============================== NOTES ============================== -NOTE: Things starting with TODO are sometimes only suggestions. - -NOTE: iostream.imbue(std::locale("C")) is very slow -NOTE: Global locale is now set at initialization - -NOTE: If VBO (EHM_STATIC) is used, remember to explicitly free the - hardware buffer (it is not freed automatically) - -NOTE: A random to-do list saved here as documentation: -A list of "active blocks" in which stuff happens. (+=done) - + Add a never-resetted game timer to the server - + Add a timestamp value to blocks - + The simple rule: All blocks near some player are "active" - - Do stuff in real time in active blocks - + Handle objects - - Grow grass, delete leaves without a tree - - Spawn some mobs based on some rules - - Transform cobble to mossy cobble near water - - Run a custom script - - ...And all kinds of other dynamic stuff - + Keep track of when a block becomes active and becomes inactive - + When a block goes inactive: - + Store objects statically to block - + Store timer value as the timestamp - + When a block goes active: - + Create active objects out of static objects - - Simulate the results of what would have happened if it would have - been active for all the time - - Grow a lot of grass and so on - + Initially it is fine to send information about every active object - to every player. Eventually it should be modified to only send info - about the nearest ones. - + This was left to be done by the old system and it sends only the - nearest ones. - -Vim conversion regexpes for moving to extended content type storage: -%s/\(\.\|->\)d \([!=]=\)/\1getContent() \2/g -%s/content_features(\([^.]*\)\.d)/content_features(\1)/g -%s/\(\.\|->\)d = \([^;]*\);/\1setContent(\2);/g -%s/\(getNodeNoExNoEmerge([^)]*)\)\.d/\1.getContent()/g -%s/\(getNodeNoExNoEmerge(.*)\)\.d/\1.getContent()/g -%s/\.d;/.getContent();/g -%s/\(content_liquid\|content_flowing_liquid\|make_liquid_flowing\|content_pointable\)(\([^.]*\).d)/\1(\2.getContent())/g -Other things to note: -- node.d = node.param0 (only in raw serialization; use getContent() otherwise) -- node.param = node.param1 -- node.dir = node.param2 -- content_walkable(node.d) etc should be changed to - content_features(node).walkable etc -- Also check for lines that store the result of getContent to a 8-bit - variable and fix them (result of getContent() must be stored in - content_t, which is 16-bit) - -NOTE: Seeds in 1260:6c77e7dbfd29: -5721858502589302589: - Spawns you on a small sand island with a surface dungeon -2983455799928051958: - Enormous jungle + a surface dungeon at ~(250,0,0) - -Old, wild and random suggestions that probably won't be done: -------------------------------------------------------------- - -SUGG: If player is on ground, mainly fetch ground-level blocks - -SUGG: Expose Connection's seqnums and ACKs to server and client. - - This enables saving many packets and making a faster connection - - This also enables server to check if client has received the - most recent block sent, for example. -SUGG: Add a sane bandwidth throttling system to Connection - -SUGG: More fine-grained control of client's dumping of blocks from - memory - - ...What does this mean in the first place? - -SUGG: A map editing mode (similar to dedicated server mode) - -SUGG: Transfer more blocks in a single packet -SUGG: A blockdata combiner class, to which blocks are added and at - destruction it sends all the stuff in as few packets as possible. -SUGG: Make a PACKET_COMBINED which contains many subpackets. Utilize - it by sending more stuff in a single packet. - - Add a packet queue to RemoteClient, from which packets will be - combined with object data packets - - This is not exactly trivial: the object data packets are - sometimes very big by themselves - - This might not give much network performance gain though. - -SUGG: Precalculate lighting translation table at runtime (at startup) - - This is not doable because it is currently hand-made and not - based on some mathematical function. - - Note: This has been changing lately - -SUGG: A version number to blocks, which increments when the block is - modified (node add/remove, water update, lighting update) - - This can then be used to make sure the most recent version of - a block has been sent to client, for example - -SUGG: Make the amount of blocks sending to client and the total - amount of blocks dynamically limited. Transferring blocks is the - main network eater of this system, so it is the one that has - to be throttled so that RTTs stay low. - -SUGG: Meshes of blocks could be split into 6 meshes facing into - different directions and then only those drawn that need to be - -SUGG: Background music based on cellular automata? - http://www.earslap.com/projectslab/otomata - -SUGG: Simple light color information to air - -SUGG: Server-side objects could be moved based on nodes to enable very - lightweight operation and simple AI - - Not practical; client would still need to show smooth movement. - -SUGG: Make a system for pregenerating quick information for mapblocks, so - that the client can show them as cubes before they are actually sent - or even generated. - -SUGG: Erosion simulation at map generation time - - This might be plausible if larger areas of map were pregenerated - without lighting (which is slow) - - Simulate water flows, which would carve out dirt fast and - then turn stone into gravel and sand and relocate it. - - How about relocating minerals, too? Coal and gold in - downstream sand and gravel would be kind of cool - - This would need a better way of handling minerals, mainly - to have mineral content as a separate field. the first - parameter field is free for this. - - Simulate rock falling from cliffs when water has removed - enough solid rock from the bottom - -SUGG: For non-mapgen FarMesh: Add a per-sector database to store surface - stuff as simple flags/values - - Light? - - A building? - And at some point make the server send this data to the client too, - instead of referring to the noise functions - - Ground height - - Surface ground type - - Trees? - -Gaming ideas: -------------- - -- Aim for something like controlling a single dwarf in Dwarf Fortress -- The player could go faster by a crafting a boat, or riding an animal -- Random NPC traders. what else? - -Game content: -------------- - -- When furnace is destroyed, move items to player's inventory -- Add lots of stuff -- Glass blocks -- Growing grass, decaying leaves - - This can be done in the active blocks I guess. - - Lots of stuff can be done in the active blocks. - - Uh, is there an active block list somewhere? I think not. Add it. -- Breaking weak structures - - This can probably be accomplished in the same way as grass -- Player health points - - When player dies, throw items on map (needs better item-on-map - implementation) -- Cobble to get mossy if near water -- More slots in furnace source list, so that multiple ingredients - are possible. -- Keys to chests? - -- The Treasure Guard; a big monster with a hammer - - The hammer does great damage, shakes the ground and removes a block - - You can drop on top of it, and have some time to attack there - before he shakes you off - -- Maybe the difficulty could come from monsters getting tougher in - far-away places, and the player starting to need something from - there when time goes by. - - The player would have some of that stuff at the beginning, and - would need new supplies of it when it runs out - -- A bomb -- A spread-items-on-map routine for the bomb, and for dying players - -- Fighting: - - Proper sword swing simulation - - Player should get damage from colliding to a wall at high speed - -Documentation: --------------- - -Build system / running: ------------------------ - -Networking and serialization: ------------------------------ - -SUGG: Fix address to be ipv6 compatible +#ifdef _MSC_VER + #ifndef SERVER // Dedicated server isn't linked with Irrlicht + #pragma comment(lib, "Irrlicht.lib") + // This would get rid of the console window + //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") + #endif + #pragma comment(lib, "zlibwapi.lib") + #pragma comment(lib, "Shell32.lib") +#endif -User Interface: ---------------- +#include "irrlicht.h" // createDevice -Graphics: ---------- +#include "mainmenumanager.h" +#include "irrlichttypes_extrabloated.h" +#include "debug.h" +#include "unittest/test.h" +#include "server.h" +#include "filesys.h" +#include "version.h" +#include "guiMainMenu.h" +#include "game.h" +#include "defaultsettings.h" +#include "gettext.h" +#include "log.h" +#include "quicktune.h" +#include "httpfetch.h" +#include "guiEngine.h" +#include "map.h" +#include "player.h" +#include "mapsector.h" +#include "fontengine.h" +#include "gameparams.h" +#include "database.h" +#include "config.h" +#if USE_CURSES + #include "terminal_chat_console.h" +#endif +#ifndef SERVER +#include "client/clientlauncher.h" +#endif -SUGG: Combine MapBlock's face caches to so big pieces that VBO - can be used - - That is >500 vertices - - This is not easy; all the MapBlocks close to the player would - still need to be drawn separately and combining the blocks - would have to happen in a background thread +#ifdef HAVE_TOUCHSCREENGUI + #include "touchscreengui.h" +#endif -SUGG: Make fetching sector's blocks more efficient when rendering - sectors that have very large amounts of blocks (on client) - - Is this necessary at all? +#if !defined(SERVER) && \ + (IRRLICHT_VERSION_MAJOR == 1) && \ + (IRRLICHT_VERSION_MINOR == 8) && \ + (IRRLICHT_VERSION_REVISION == 2) + #error "Irrlicht 1.8.2 is known to be broken - please update Irrlicht to version >= 1.8.3" +#endif -SUGG: Draw cubes in inventory directly with 3D drawing commands, so that - animating them is easier. +#define DEBUGFILE "debug.txt" +#define DEFAULT_SERVER_PORT 30000 -SUGG: Option for enabling proper alpha channel for textures +typedef std::map OptionList; -TODO: Flowing water animation +/********************************************************************** + * Private functions + **********************************************************************/ -TODO: A setting for enabling bilinear filtering for textures +static bool get_cmdline_opts(int argc, char *argv[], Settings *cmd_args); +static void set_allowed_options(OptionList *allowed_options); -TODO: Better control of draw_control.wanted_max_blocks +static void print_help(const OptionList &allowed_options); +static void print_allowed_options(const OptionList &allowed_options); +static void print_version(); +static void print_worldspecs(const std::vector &worldspecs, + std::ostream &os); +static void print_modified_quicktune_values(); -TODO: Further investigate the use of GPU lighting in addition to the - current one +static void list_game_ids(); +static void list_worlds(); +static void setup_log_params(const Settings &cmd_args); +static bool create_userdata_path(); +static bool init_common(const Settings &cmd_args, int argc, char *argv[]); +static void startup_message(); +static bool read_config_file(const Settings &cmd_args); +static void init_log_streams(const Settings &cmd_args); -TODO: Artificial (night) light could be more yellow colored than sunlight. - - This is technically doable. - - Also the actual colors of the textures could be made less colorful - in the dark but it's a bit more difficult. +static bool game_configure(GameParams *game_params, const Settings &cmd_args); +static void game_configure_port(GameParams *game_params, const Settings &cmd_args); -SUGG: Somehow make the night less colorful +static bool game_configure_world(GameParams *game_params, const Settings &cmd_args); +static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_args); +static bool get_world_from_config(GameParams *game_params, const Settings &cmd_args); +static bool auto_select_world(GameParams *game_params); +static std::string get_clean_world_path(const std::string &path); -TODO: Occlusion culling - - At the same time, move some of the renderMap() block choosing code - to the same place as where the new culling happens. - - Shoot some rays per frame and when ready, make a new list of - blocks for usage of renderMap and give it a new pointer to it. +static bool game_configure_subgame(GameParams *game_params, const Settings &cmd_args); +static bool get_game_from_cmdline(GameParams *game_params, const Settings &cmd_args); +static bool determine_subgame(GameParams *game_params); -Configuration: --------------- +static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args); +static bool migrate_database(const GameParams &game_params, const Settings &cmd_args); -Client: -------- +/**********************************************************************/ -TODO: Untie client network operations from framerate - - Needs some input queues or something - - This won't give much performance boost because calculating block - meshes takes so long +/* + gettime.h implementation +*/ -SUGG: Make morning and evening transition more smooth and maybe shorter +#ifdef SERVER -TODO: Don't update all meshes always on single node changes, but - check which ones should be updated - - implement Map::updateNodeMeshes() and the usage of it - - It will give almost always a 4x boost in mesh update performance. +u32 getTimeMs() +{ + /* Use imprecise system calls directly (from porting.h) */ + return porting::getTime(PRECISION_MILLI); +} -- A weapon engine +u32 getTime(TimePrecision prec) +{ + return porting::getTime(prec); +} -- Tool/weapon visualization +#endif -FIXME: When disconnected to the menu, memory is not freed properly +FileLogOutput file_log_output; -TODO: Investigate how much the mesh generator thread gets used when - transferring map data +static OptionList allowed_options; -Server: -------- +int main(int argc, char *argv[]) +{ + int retval; -SUGG: Make an option to the server to disable building and digging near - the starting position + debug_set_exception_handler(); -FIXME: Server sometimes goes into some infinite PeerNotFoundException loop + g_logger.registerThread("Main"); + g_logger.addOutputMaxLevel(&stderr_output, LL_ACTION); -* Fix the problem with the server constantly saving one or a few - blocks? List the first saved block, maybe it explains. - - It is probably caused by oscillating water - - TODO: Investigate if this still happens (this is a very old one) -* Make a small history check to transformLiquids to detect and log - continuous oscillations, in such detail that they can be fixed. + Settings cmd_args; + bool cmd_args_ok = get_cmdline_opts(argc, argv, &cmd_args); + if (!cmd_args_ok + || cmd_args.getFlag("help") + || cmd_args.exists("nonopt1")) { + print_help(allowed_options); + return cmd_args_ok ? 0 : 1; + } -FIXME: The new optimized map sending doesn't sometimes send enough blocks - from big caves and such -FIXME: Block send distance configuration does not take effect for some reason + if (cmd_args.getFlag("version")) { + print_version(); + return 0; + } -Environment: ------------- + setup_log_params(cmd_args); -TODO: Add proper hooks to when adding and removing active blocks + porting::signal_handler_init(); -TODO: Finish the ActiveBlockModifier stuff and use it for something +#ifdef __ANDROID__ + porting::initAndroid(); + porting::initializePathsAndroid(); +#else + porting::initializePaths(); +#endif -Objects: --------- + if (!create_userdata_path()) { + errorstream << "Cannot create user data directory" << std::endl; + return 1; + } -TODO: Get rid of MapBlockObjects and use only ActiveObjects - - Skipping the MapBlockObject data is nasty - there is no "total - length" stored; have to make a SkipMBOs function which contains - enough of the current code to skip them properly. + // Initialize debug stacks + DSTACK(FUNCTION_NAME); -SUGG: MovingObject::move and Player::move are basically the same. - combine them. - - NOTE: This is a bit tricky because player has the sneaking ability - - NOTE: Player::move is more up-to-date. - - NOTE: There is a simple move implementation now in collision.{h,cpp} - - NOTE: MovingObject will be deleted (MapBlockObject) + // Debug handler + BEGIN_DEBUG_EXCEPTION_HANDLER -TODO: Add a long step function to objects that is called with the time - difference when block activates + // List gameids if requested + if (cmd_args.exists("gameid") && cmd_args.get("gameid") == "list") { + list_game_ids(); + return 0; + } -Map: ----- + // List worlds if requested + if (cmd_args.exists("world") && cmd_args.get("world") == "list") { + list_worlds(); + return 0; + } -TODO: Flowing water to actually contain flow direction information - - There is a space for this - it just has to be implemented. + if (!init_common(cmd_args, argc, argv)) + return 1; -TODO: Consider smoothening cave floors after generating them +#ifndef __ANDROID__ + // Run unit tests + if (cmd_args.getFlag("run-unittests")) { + return run_tests(); + } +#endif -TODO: Fix make_tree, make_* to use seed-position-consistent pseudorandom - - delta also + GameParams game_params; +#ifdef SERVER + game_params.is_dedicated_server = true; +#else + game_params.is_dedicated_server = cmd_args.getFlag("server"); +#endif -Misc. stuff: ------------- -TODO: Make sure server handles removing grass when a block is placed (etc) - - The client should not do it by itself - - NOTE: I think nobody does it currently... -TODO: Block cube placement around player's head -TODO: Protocol version field -TODO: Think about using same bits for material for fences and doors, for - example -TODO: Move mineral to param2, increment map serialization version, add - conversion + if (!game_configure(&game_params, cmd_args)) + return 1; -SUGG: Restart irrlicht completely when coming back to main menu from game. - - This gets rid of everything that is stored in irrlicht's caches. - - This might be needed for texture pack selection in menu + sanity_check(!game_params.world_path.empty()); -TODO: Merge bahamada's audio stuff (clean patch available) + infostream << "Using commanded world path [" + << game_params.world_path << "]" << std::endl; -Making it more portable: ------------------------- - -Stuff to do before release: ---------------------------- + //Run dedicated server if asked to or no other option + g_settings->set("server_dedicated", + game_params.is_dedicated_server ? "true" : "false"); -Fixes to the current release: ------------------------------ + if (game_params.is_dedicated_server) + return run_dedicated_server(game_params, cmd_args) ? 0 : 1; -Stuff to do after release: ---------------------------- +#ifndef SERVER + ClientLauncher launcher; + retval = launcher.run(game_params, cmd_args) ? 0 : 1; +#else + retval = 0; +#endif -Doing currently: ----------------- + // Update configuration file + if (g_settings_path != "") + g_settings->updateConfigFile(g_settings_path.c_str()); -====================================================================== + print_modified_quicktune_values(); -*/ + // Stop httpfetch thread (if started) + httpfetch_cleanup(); -#ifdef NDEBUG - /*#ifdef _WIN32 - #pragma message ("Disabling unit tests") - #else - #warning "Disabling unit tests" - #endif*/ - // Disable unit tests - #define ENABLE_TESTS 0 -#else - // Enable unit tests - #define ENABLE_TESTS 1 -#endif + END_DEBUG_EXCEPTION_HANDLER -#ifdef _MSC_VER - #pragma comment(lib, "Irrlicht.lib") - //#pragma comment(lib, "jthread.lib") - #pragma comment(lib, "zlibwapi.lib") - #pragma comment(lib, "Shell32.lib") - // This would get rid of the console window - //#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup") -#endif + return retval; +} -#include "irrlicht.h" // createDevice -#include "main.h" -#include "mainmenumanager.h" -#include -#include -#include -#include "common_irrlicht.h" -#include "debug.h" -#include "test.h" -#include "server.h" -#include "constants.h" -#include "porting.h" -#include "gettime.h" -#include "guiMessageMenu.h" -#include "filesys.h" -#include "config.h" -#include "guiMainMenu.h" -#include "mineral.h" -#include "materials.h" -#include "game.h" -#include "keycode.h" -#include "tile.h" -#include "defaultsettings.h" -#include "gettext.h" -#include "settings.h" -#include "profiler.h" -#include "log.h" -#include "mods.h" +/***************************************************************************** + * Startup / Init + *****************************************************************************/ -/* - Settings. - These are loaded from the config file. -*/ -Settings main_settings; -Settings *g_settings = &main_settings; -// Global profiler -Profiler main_profiler; -Profiler *g_profiler = &main_profiler; +static bool get_cmdline_opts(int argc, char *argv[], Settings *cmd_args) +{ + set_allowed_options(&allowed_options); -/* - Random stuff -*/ + return cmd_args->parseCommandLine(argc, argv, allowed_options); +} -/* - mainmenumanager.h -*/ +static void set_allowed_options(OptionList *allowed_options) +{ + allowed_options->clear(); + + allowed_options->insert(std::make_pair("help", ValueSpec(VALUETYPE_FLAG, + _("Show allowed options")))); + allowed_options->insert(std::make_pair("version", ValueSpec(VALUETYPE_FLAG, + _("Show version information")))); + allowed_options->insert(std::make_pair("config", ValueSpec(VALUETYPE_STRING, + _("Load configuration from specified file")))); + allowed_options->insert(std::make_pair("port", ValueSpec(VALUETYPE_STRING, + _("Set network port (UDP)")))); + allowed_options->insert(std::make_pair("run-unittests", ValueSpec(VALUETYPE_FLAG, + _("Run the unit tests and exit")))); + allowed_options->insert(std::make_pair("map-dir", ValueSpec(VALUETYPE_STRING, + _("Same as --world (deprecated)")))); + allowed_options->insert(std::make_pair("world", ValueSpec(VALUETYPE_STRING, + _("Set world path (implies local game) ('list' lists all)")))); + allowed_options->insert(std::make_pair("worldname", ValueSpec(VALUETYPE_STRING, + _("Set world by name (implies local game)")))); + allowed_options->insert(std::make_pair("quiet", ValueSpec(VALUETYPE_FLAG, + _("Print to console errors only")))); + allowed_options->insert(std::make_pair("info", ValueSpec(VALUETYPE_FLAG, + _("Print more information to console")))); + allowed_options->insert(std::make_pair("verbose", ValueSpec(VALUETYPE_FLAG, + _("Print even more information to console")))); + allowed_options->insert(std::make_pair("trace", ValueSpec(VALUETYPE_FLAG, + _("Print enormous amounts of information to log and console")))); + allowed_options->insert(std::make_pair("logfile", ValueSpec(VALUETYPE_STRING, + _("Set logfile path ('' = no logging)")))); + allowed_options->insert(std::make_pair("gameid", ValueSpec(VALUETYPE_STRING, + _("Set gameid (\"--gameid list\" prints available ones)")))); + allowed_options->insert(std::make_pair("migrate", ValueSpec(VALUETYPE_STRING, + _("Migrate from current map backend to another (Only works when using minetestserver or with --server)")))); + allowed_options->insert(std::make_pair("terminal", ValueSpec(VALUETYPE_FLAG, + _("Feature an interactive terminal (Only works when using minetestserver or with --server)")))); +#ifndef SERVER + allowed_options->insert(std::make_pair("videomodes", ValueSpec(VALUETYPE_FLAG, + _("Show available video modes")))); + allowed_options->insert(std::make_pair("speedtests", ValueSpec(VALUETYPE_FLAG, + _("Run speed tests")))); + allowed_options->insert(std::make_pair("address", ValueSpec(VALUETYPE_STRING, + _("Address to connect to. ('' = local game)")))); + allowed_options->insert(std::make_pair("random-input", ValueSpec(VALUETYPE_FLAG, + _("Enable random user input, for testing")))); + allowed_options->insert(std::make_pair("server", ValueSpec(VALUETYPE_FLAG, + _("Run dedicated server")))); + allowed_options->insert(std::make_pair("name", ValueSpec(VALUETYPE_STRING, + _("Set player name")))); + allowed_options->insert(std::make_pair("password", ValueSpec(VALUETYPE_STRING, + _("Set password")))); + allowed_options->insert(std::make_pair("go", ValueSpec(VALUETYPE_FLAG, + _("Disable main menu")))); +#endif -gui::IGUIEnvironment* guienv = NULL; -gui::IGUIStaticText *guiroot = NULL; -MainMenuManager g_menumgr; +} -bool noMenuActive() +static void print_help(const OptionList &allowed_options) { - return (g_menumgr.menuCount() == 0); + std::cout << _("Allowed options:") << std::endl; + print_allowed_options(allowed_options); } -// Passed to menus to allow disconnecting and exiting -MainGameCallback *g_gamecallback = NULL; +static void print_allowed_options(const OptionList &allowed_options) +{ + for (OptionList::const_iterator i = allowed_options.begin(); + i != allowed_options.end(); ++i) { + std::ostringstream os1(std::ios::binary); + os1 << " --" << i->first; + if (i->second.type != VALUETYPE_FLAG) + os1 << _(" "); -/* - Debug streams -*/ + std::cout << padStringRight(os1.str(), 24); -// Connection -std::ostream *dout_con_ptr = &dummyout; -std::ostream *derr_con_ptr = &verbosestream; -//std::ostream *dout_con_ptr = &infostream; -//std::ostream *derr_con_ptr = &errorstream; + if (i->second.help != NULL) + std::cout << i->second.help; -// Server -std::ostream *dout_server_ptr = &infostream; -std::ostream *derr_server_ptr = &errorstream; + std::cout << std::endl; + } +} -// Client -std::ostream *dout_client_ptr = &infostream; -std::ostream *derr_client_ptr = &errorstream; +static void print_version() +{ + std::cout << PROJECT_NAME_C " " << g_version_hash << std::endl; +#ifndef SERVER + std::cout << "Using Irrlicht " << IRRLICHT_SDK_VERSION << std::endl; +#endif + std::cout << "Build info: " << g_build_info << std::endl; +} -/* - gettime.h implementation -*/ +static void list_game_ids() +{ + std::set gameids = getAvailableGameIds(); + for (std::set::const_iterator i = gameids.begin(); + i != gameids.end(); ++i) + std::cout << (*i) < worldspecs = getAvailableWorlds(); + print_worldspecs(worldspecs, std::cout); +} -// A precise irrlicht one -class IrrlichtTimeGetter: public TimeGetter +static void print_worldspecs(const std::vector &worldspecs, + std::ostream &os) { -public: - IrrlichtTimeGetter(IrrlichtDevice *device): - m_device(device) - {} - u32 getTime() - { - if(m_device == NULL) - return 0; - return m_device->getTimer()->getRealTime(); + for (size_t i = 0; i < worldspecs.size(); i++) { + std::string name = worldspecs[i].name; + std::string path = worldspecs[i].path; + if (name.find(" ") != std::string::npos) + name = std::string("'") + name + "'"; + path = std::string("'") + path + "'"; + name = padStringRight(name, 14); + os << " " << name << " " << path << std::endl; } -private: - IrrlichtDevice *m_device; -}; -// Not so precise one which works without irrlicht -class SimpleTimeGetter: public TimeGetter +} + +static void print_modified_quicktune_values() { -public: - u32 getTime() - { - return porting::getTimeMs(); + bool header_printed = false; + std::vector names = getQuicktuneNames(); + + for (u32 i = 0; i < names.size(); i++) { + QuicktuneValue val = getQuicktuneValue(names[i]); + if (!val.modified) + continue; + if (!header_printed) { + dstream << "Modified quicktune values:" << std::endl; + header_printed = true; + } + dstream << names[i] << " = " << val.getString() << std::endl; } -}; - -// A pointer to a global instance of the time getter -// TODO: why? -TimeGetter *g_timegetter = NULL; +} -u32 getTimeMs() +static void setup_log_params(const Settings &cmd_args) { - if(g_timegetter == NULL) - return 0; - return g_timegetter->getTime(); -} + // Quiet mode, print errors only + if (cmd_args.getFlag("quiet")) { + g_logger.removeOutput(&stderr_output); + g_logger.addOutputMaxLevel(&stderr_output, LL_ERROR); + } -/* - Event handler for Irrlicht + // If trace is enabled, enable logging of certain things + if (cmd_args.getFlag("trace")) { + dstream << _("Enabling trace level debug output") << std::endl; + g_logger.setTraceEnabled(true); + dout_con_ptr = &verbosestream; // This is somewhat old + socket_enable_debug_output = true; // Sockets doesn't use log.h + } - NOTE: Everything possible should be moved out from here, - probably to InputHandler and the_game -*/ + // In certain cases, output info level on stderr + if (cmd_args.getFlag("info") || cmd_args.getFlag("verbose") || + cmd_args.getFlag("trace") || cmd_args.getFlag("speedtests")) + g_logger.addOutput(&stderr_output, LL_INFO); -class MyEventReceiver : public IEventReceiver + // In certain cases, output verbose level on stderr + if (cmd_args.getFlag("verbose") || cmd_args.getFlag("trace")) + g_logger.addOutput(&stderr_output, LL_VERBOSE); +} + +static bool create_userdata_path() { -public: - // This is the one method that we have to implement - virtual bool OnEvent(const SEvent& event) - { - /* - React to nothing here if a menu is active - */ - if(noMenuActive() == false) - { - return false; - } + bool success; - // Remember whether each key is down or up - if(event.EventType == irr::EET_KEY_INPUT_EVENT) - { - if(event.KeyInput.PressedDown) { - keyIsDown.set(event.KeyInput); - keyWasDown.set(event.KeyInput); - } else { - keyIsDown.unset(event.KeyInput); - } - } +#ifdef __ANDROID__ + if (!fs::PathExists(porting::path_user)) { + success = fs::CreateDir(porting::path_user); + } else { + success = true; + } + porting::copyAssets(); +#else + // Create user data directory + success = fs::CreateDir(porting::path_user); +#endif - if(event.EventType == irr::EET_MOUSE_INPUT_EVENT) - { - if(noMenuActive() == false) - { - left_active = false; - middle_active = false; - right_active = false; - } - else - { - left_active = event.MouseInput.isLeftPressed(); - middle_active = event.MouseInput.isMiddlePressed(); - right_active = event.MouseInput.isRightPressed(); - - if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) - { - leftclicked = true; - } - if(event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN) - { - rightclicked = true; - } - if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) - { - leftreleased = true; - } - if(event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP) - { - rightreleased = true; - } - if(event.MouseInput.Event == EMIE_MOUSE_WHEEL) - { - mouse_wheel += event.MouseInput.Wheel; - } - } - } + return success; +} +static bool init_common(const Settings &cmd_args, int argc, char *argv[]) +{ + startup_message(); + set_default_settings(g_settings); + + // Initialize sockets + sockets_init(); + atexit(sockets_cleanup); + + if (!read_config_file(cmd_args)) return false; - } - bool IsKeyDown(const KeyPress &keyCode) const - { - return keyIsDown[keyCode]; - } - - // Checks whether a key was down and resets the state - bool WasKeyDown(const KeyPress &keyCode) - { - bool b = keyWasDown[keyCode]; - if (b) - keyWasDown.unset(keyCode); - return b; - } + init_log_streams(cmd_args); - s32 getMouseWheel() - { - s32 a = mouse_wheel; - mouse_wheel = 0; - return a; - } + // Initialize random seed + srand(time(0)); + mysrand(time(0)); - void clearInput() - { - keyIsDown.clear(); - keyWasDown.clear(); + // Initialize HTTP fetcher + httpfetch_init(g_settings->getS32("curl_parallel_limit")); - leftclicked = false; - rightclicked = false; - leftreleased = false; - rightreleased = false; + init_gettext(porting::path_locale.c_str(), + g_settings->get("language"), argc, argv); - left_active = false; - middle_active = false; - right_active = false; + return true; +} - mouse_wheel = 0; - } +static void startup_message() +{ + infostream << PROJECT_NAME << " " << _("with") + << " SER_FMT_VER_HIGHEST_READ=" + << (int)SER_FMT_VER_HIGHEST_READ << ", " + << g_build_info << std::endl; +} - MyEventReceiver() - { - clearInput(); - } +static bool read_config_file(const Settings &cmd_args) +{ + // Path of configuration file in use + sanity_check(g_settings_path == ""); // Sanity check - bool leftclicked; - bool rightclicked; - bool leftreleased; - bool rightreleased; + if (cmd_args.exists("config")) { + bool r = g_settings->readConfigFile(cmd_args.get("config").c_str()); + if (!r) { + errorstream << "Could not read configuration from \"" + << cmd_args.get("config") << "\"" << std::endl; + return false; + } + g_settings_path = cmd_args.get("config"); + } else { + std::vector filenames; + filenames.push_back(porting::path_user + DIR_DELIM + "minetest.conf"); + // Legacy configuration file location + filenames.push_back(porting::path_user + + DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); - bool left_active; - bool middle_active; - bool right_active; +#if RUN_IN_PLACE + // Try also from a lower level (to aid having the same configuration + // for many RUN_IN_PLACE installs) + filenames.push_back(porting::path_user + + DIR_DELIM + ".." + DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); +#endif - s32 mouse_wheel; + for (size_t i = 0; i < filenames.size(); i++) { + bool r = g_settings->readConfigFile(filenames[i].c_str()); + if (r) { + g_settings_path = filenames[i]; + break; + } + } -private: - IrrlichtDevice *m_device; - - // The current state of keys - KeyList keyIsDown; - // Whether a key has been pressed or not - KeyList keyWasDown; -}; + // If no path found, use the first one (menu creates the file) + if (g_settings_path == "") + g_settings_path = filenames[0]; + } -/* - Separated input handler -*/ + return true; +} -class RealInputHandler : public InputHandler +static void init_log_streams(const Settings &cmd_args) { -public: - RealInputHandler(IrrlichtDevice *device, MyEventReceiver *receiver): - m_device(device), - m_receiver(receiver) - { - } - virtual bool isKeyDown(const KeyPress &keyCode) - { - return m_receiver->IsKeyDown(keyCode); - } - virtual bool wasKeyDown(const KeyPress &keyCode) - { - return m_receiver->WasKeyDown(keyCode); - } - virtual v2s32 getMousePos() - { - return m_device->getCursorControl()->getPosition(); - } - virtual void setMousePos(s32 x, s32 y) - { - m_device->getCursorControl()->setPosition(x, y); +#if RUN_IN_PLACE + std::string log_filename = DEBUGFILE; +#else + std::string log_filename = porting::path_user + DIR_DELIM + DEBUGFILE; +#endif + if (cmd_args.exists("logfile")) + log_filename = cmd_args.get("logfile"); + + g_logger.removeOutput(&file_log_output); + std::string conf_loglev = g_settings->get("debug_log_level"); + + // Old integer format + if (std::isdigit(conf_loglev[0])) { + warningstream << "Deprecated use of debug_log_level with an " + "integer value; please update your configuration." << std::endl; + static const char *lev_name[] = + {"", "error", "action", "info", "verbose"}; + int lev_i = atoi(conf_loglev.c_str()); + if (lev_i < 0 || lev_i >= (int)ARRLEN(lev_name)) { + warningstream << "Supplied invalid debug_log_level!" + " Assuming action level." << std::endl; + lev_i = 2; + } + conf_loglev = lev_name[lev_i]; } - virtual bool getLeftState() - { - return m_receiver->left_active; - } - virtual bool getRightState() - { - return m_receiver->right_active; - } - - virtual bool getLeftClicked() - { - return m_receiver->leftclicked; - } - virtual bool getRightClicked() - { - return m_receiver->rightclicked; - } - virtual void resetLeftClicked() - { - m_receiver->leftclicked = false; - } - virtual void resetRightClicked() - { - m_receiver->rightclicked = false; - } + if (log_filename.empty() || conf_loglev.empty()) // No logging + return; - virtual bool getLeftReleased() - { - return m_receiver->leftreleased; - } - virtual bool getRightReleased() - { - return m_receiver->rightreleased; - } - virtual void resetLeftReleased() - { - m_receiver->leftreleased = false; - } - virtual void resetRightReleased() - { - m_receiver->rightreleased = false; + LogLevel log_level = Logger::stringToLevel(conf_loglev); + if (log_level == LL_MAX) { + warningstream << "Supplied unrecognized debug_log_level; " + "using maximum." << std::endl; } - virtual s32 getMouseWheel() - { - return m_receiver->getMouseWheel(); - } + verbosestream << "log_filename = " << log_filename << std::endl; - void clear() - { - m_receiver->clearInput(); - } -private: - IrrlichtDevice *m_device; - MyEventReceiver *m_receiver; -}; + file_log_output.open(log_filename.c_str()); + g_logger.addOutputMaxLevel(&file_log_output, log_level); +} -class RandomInputHandler : public InputHandler +static bool game_configure(GameParams *game_params, const Settings &cmd_args) { -public: - RandomInputHandler() - { - leftdown = false; - rightdown = false; - leftclicked = false; - rightclicked = false; - leftreleased = false; - rightreleased = false; - keydown.clear(); - } - virtual bool isKeyDown(const KeyPress &keyCode) - { - return keydown[keyCode]; - } - virtual bool wasKeyDown(const KeyPress &keyCode) - { + game_configure_port(game_params, cmd_args); + + if (!game_configure_world(game_params, cmd_args)) { + errorstream << "No world path specified or found." << std::endl; return false; } - virtual v2s32 getMousePos() - { - return mousepos; - } - virtual void setMousePos(s32 x, s32 y) - { - mousepos = v2s32(x,y); - } - virtual bool getLeftState() - { - return leftdown; - } - virtual bool getRightState() - { - return rightdown; - } + game_configure_subgame(game_params, cmd_args); - virtual bool getLeftClicked() - { - return leftclicked; - } - virtual bool getRightClicked() - { - return rightclicked; - } - virtual void resetLeftClicked() - { - leftclicked = false; - } - virtual void resetRightClicked() - { - rightclicked = false; - } + return true; +} - virtual bool getLeftReleased() - { - return leftreleased; - } - virtual bool getRightReleased() - { - return rightreleased; - } - virtual void resetLeftReleased() - { - leftreleased = false; - } - virtual void resetRightReleased() - { - rightreleased = false; - } +static void game_configure_port(GameParams *game_params, const Settings &cmd_args) +{ + if (cmd_args.exists("port")) + game_params->socket_port = cmd_args.getU16("port"); + else + game_params->socket_port = g_settings->getU16("port"); - virtual s32 getMouseWheel() - { - return 0; - } + if (game_params->socket_port == 0) + game_params->socket_port = DEFAULT_SERVER_PORT; +} - virtual void step(float dtime) - { - { - static float counter1 = 0; - counter1 -= dtime; - if(counter1 < 0.0) - { - counter1 = 0.1*Rand(1, 40); - keydown.toggle(getKeySetting("keymap_jump")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if(counter1 < 0.0) - { - counter1 = 0.1*Rand(1, 40); - keydown.toggle(getKeySetting("keymap_special1")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if(counter1 < 0.0) - { - counter1 = 0.1*Rand(1, 40); - keydown.toggle(getKeySetting("keymap_forward")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if(counter1 < 0.0) - { - counter1 = 0.1*Rand(1, 40); - keydown.toggle(getKeySetting("keymap_left")); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if(counter1 < 0.0) - { - counter1 = 0.1*Rand(1, 20); - mousespeed = v2s32(Rand(-20,20), Rand(-15,20)); - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if(counter1 < 0.0) - { - counter1 = 0.1*Rand(1, 30); - leftdown = !leftdown; - if(leftdown) - leftclicked = true; - if(!leftdown) - leftreleased = true; - } - } - { - static float counter1 = 0; - counter1 -= dtime; - if(counter1 < 0.0) - { - counter1 = 0.1*Rand(1, 15); - rightdown = !rightdown; - if(rightdown) - rightclicked = true; - if(!rightdown) - rightreleased = true; - } - } - mousepos += mousespeed; - } +static bool game_configure_world(GameParams *game_params, const Settings &cmd_args) +{ + if (get_world_from_cmdline(game_params, cmd_args)) + return true; + if (get_world_from_config(game_params, cmd_args)) + return true; - s32 Rand(s32 min, s32 max) - { - return (myrand()%(max-min+1))+min; - } -private: - KeyList keydown; - v2s32 mousepos; - v2s32 mousespeed; - bool leftdown; - bool rightdown; - bool leftclicked; - bool rightclicked; - bool leftreleased; - bool rightreleased; -}; - -// These are defined global so that they're not optimized too much. -// Can't change them to volatile. -s16 temp16; -f32 tempf; -v3f tempv3f1; -v3f tempv3f2; -std::string tempstring; -std::string tempstring2; - -void SpeedTests() + return auto_select_world(game_params); +} + +static bool get_world_from_cmdline(GameParams *game_params, const Settings &cmd_args) { - { - dstream<<"The following test should take around 20ms."< worldspecs = getAvailableWorlds(); + bool found = false; + for (u32 i = 0; i < worldspecs.size(); i++) { + std::string name = worldspecs[i].name; + if (name == commanded_worldname) { + dstream << _("Using world specified by --worldname on the " + "command line") << std::endl; + commanded_world = worldspecs[i].path; + found = true; + break; } } - } - - dstream<<"All of the following tests should take around 100ms each." - < map1; - tempf = -324; - const s16 ii=300; - for(s16 y=0; y=0; y--){ - for(s16 x=0; xworld_path = get_clean_world_path(commanded_world); + return commanded_world != ""; } - { - dstream<<"Around 5000/ms should do well here."<world_path = get_clean_world_path(commanded_world); + return commanded_world != ""; } -void drawMenuBackground(video::IVideoDriver* driver) +static bool get_world_from_config(GameParams *game_params, const Settings &cmd_args) { - core::dimension2d screensize = driver->getScreenSize(); - - video::ITexture *bgtexture = - driver->getTexture(getTexturePath("menubg.png").c_str()); - if(bgtexture) - { - s32 texturesize = 128; - s32 tiled_y = screensize.Height / texturesize + 1; - s32 tiled_x = screensize.Width / texturesize + 1; - - for(s32 y=0; y rect(0,0,texturesize,texturesize); - rect += v2s32(x*texturesize, y*texturesize); - driver->draw2DImage(bgtexture, rect, - core::rect(core::position2d(0,0), - core::dimension2di(bgtexture->getSize())), - NULL, NULL, true); - } - } - - video::ITexture *logotexture = - driver->getTexture(getTexturePath("menulogo.png").c_str()); - if(logotexture) - { - v2s32 logosize(logotexture->getOriginalSize().Width, - logotexture->getOriginalSize().Height); - logosize *= 4; - - video::SColor bgcolor(255,50,50,50); - core::rect bgrect(0, screensize.Height-logosize.Y-20, - screensize.Width, screensize.Height); - driver->draw2DRectangle(bgcolor, bgrect, NULL); - - core::rect rect(0,0,logosize.X,logosize.Y); - rect += v2s32(screensize.Width/2,screensize.Height-10-logosize.Y); - rect -= v2s32(logosize.X/2, 0); - driver->draw2DImage(logotexture, rect, - core::rect(core::position2d(0,0), - core::dimension2di(logotexture->getSize())), - NULL, NULL, true); - } + // World directory + std::string commanded_world = ""; + + if (g_settings->exists("map-dir")) + commanded_world = g_settings->get("map-dir"); + + game_params->world_path = get_clean_world_path(commanded_world); + + return commanded_world != ""; } -class StderrLogOutput: public ILogOutput +static bool auto_select_world(GameParams *game_params) { -public: - /* line: Full line with timestamp, level and thread */ - void printLog(const std::string &line) - { - std::cerr< worldspecs = getAvailableWorlds(); + std::string world_path; + + // If there is only a single world, use it + if (worldspecs.size() == 1) { + world_path = worldspecs[0].path; + dstream <<_("Automatically selecting world at") << " [" + << world_path << "]" << std::endl; + // If there are multiple worlds, list them + } else if (worldspecs.size() > 1 && game_params->is_dedicated_server) { + std::cerr << _("Multiple worlds are available.") << std::endl; + std::cerr << _("Please select one using --worldname " + " or --world ") << std::endl; + print_worldspecs(worldspecs, std::cerr); + return false; + // If there are no worlds, automatically create a new one + } else { + // This is the ultimate default world path + world_path = porting::path_user + DIR_DELIM + "worlds" + + DIR_DELIM + "world"; + infostream << "Creating default world at [" + << world_path << "]" << std::endl; + } + + assert(world_path != ""); // Post-condition + game_params->world_path = world_path; + return true; +} -class DstreamNoStderrLogOutput: public ILogOutput +static std::string get_clean_world_path(const std::string &path) { -public: - /* line: Full line with timestamp, level and thread */ - void printLog(const std::string &line) - { - dstream_no_stderr< worldmt.size() + && path.substr(path.size() - worldmt.size()) == worldmt) { + dstream << _("Supplied world.mt file - stripping it off.") << std::endl; + clean_path = path.substr(0, path.size() - worldmt.size()); + } else { + clean_path = path; } -} main_dstream_no_stderr_log_out; + return path; +} -int main(int argc, char *argv[]) + +static bool game_configure_subgame(GameParams *game_params, const Settings &cmd_args) { - /* - Initialization - */ - - log_add_output_maxlev(&main_stderr_log_out, LMT_ACTION); - log_add_output_all_levs(&main_dstream_no_stderr_log_out); - - log_register_thread("main"); - - // Set locale. This is for forcing '.' as the decimal point. - std::locale::global(std::locale("C")); - // This enables printing all characters in bitmap font - setlocale(LC_CTYPE, "en_US"); - - /* - Parse command line - */ - - // List all allowed options - core::map allowed_options; - allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG)); - allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG, - "Run server directly")); - allowed_options.insert("config", ValueSpec(VALUETYPE_STRING, - "Load configuration from specified file")); - allowed_options.insert("port", ValueSpec(VALUETYPE_STRING)); - allowed_options.insert("address", ValueSpec(VALUETYPE_STRING)); - allowed_options.insert("random-input", ValueSpec(VALUETYPE_FLAG)); - allowed_options.insert("disable-unittests", ValueSpec(VALUETYPE_FLAG)); - allowed_options.insert("enable-unittests", ValueSpec(VALUETYPE_FLAG)); - allowed_options.insert("map-dir", ValueSpec(VALUETYPE_STRING)); -#ifdef _WIN32 - allowed_options.insert("dstream-on-stderr", ValueSpec(VALUETYPE_FLAG)); -#endif - allowed_options.insert("speedtests", ValueSpec(VALUETYPE_FLAG)); - allowed_options.insert("info-on-stderr", ValueSpec(VALUETYPE_FLAG)); + bool success; - Settings cmd_args; - - bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options); - - if(ret == false || cmd_args.getFlag("help")) - { - dstream<<"Allowed options:"<::Iterator - i = allowed_options.getIterator(); - i.atEnd() == false; i++) - { - dstream<<" --"<getKey(); - if(i.getNode()->getValue().type == VALUETYPE_FLAG) - { - } - else - { - dstream<<" "; - } - dstream<getValue().help != NULL) - { - dstream<<" "<getValue().help - <game_spec = commanded_gamespec; + return true; + } - return cmd_args.getFlag("help") ? 0 : 1; + return false; +} + +static bool determine_subgame(GameParams *game_params) +{ + SubgameSpec gamespec; + + assert(game_params->world_path != ""); // Pre-condition + + verbosestream << _("Determining gameid/gamespec") << std::endl; + // If world doesn't exist + if (game_params->world_path != "" + && !getWorldExists(game_params->world_path)) { + // Try to take gamespec from command line + if (game_params->game_spec.isValid()) { + gamespec = game_params->game_spec; + infostream << "Using commanded gameid [" << gamespec.id << "]" << std::endl; + } else { // Otherwise we will be using "minetest" + gamespec = findSubgame(g_settings->get("default_game")); + infostream << "Using default gameid [" << gamespec.id << "]" << std::endl; + if (!gamespec.isValid()) { + errorstream << "Subgame specified in default_game [" + << g_settings->get("default_game") + << "] is invalid." << std::endl; + return false; + } + } + } else { // World exists + std::string world_gameid = getWorldGameId(game_params->world_path, false); + // If commanded to use a gameid, do so + if (game_params->game_spec.isValid()) { + gamespec = game_params->game_spec; + if (game_params->game_spec.id != world_gameid) { + warningstream << "Using commanded gameid [" + << gamespec.id << "]" << " instead of world gameid [" + << world_gameid << "]" << std::endl; + } + } else { + // If world contains an embedded game, use it; + // Otherwise find world from local system. + gamespec = findWorldSubgame(game_params->world_path); + infostream << "Using world gameid [" << gamespec.id << "]" << std::endl; + } } - - /* - Low-level initialization - */ - - bool disable_stderr = false; -#ifdef _WIN32 - if(cmd_args.getFlag("dstream-on-stderr") == false) - disable_stderr = true; -#endif - - if(cmd_args.getFlag("info-on-stderr")) - log_add_output(&main_stderr_log_out, LMT_INFO); - porting::signal_handler_init(); - bool &kill = *porting::signal_handler_killstatus(); - - // Initialize porting::path_data and porting::path_userdata - porting::initializePaths(); + if (!gamespec.isValid()) { + errorstream << "Subgame [" << gamespec.id << "] could not be found." + << std::endl; + return false; + } - // Create user data directory - fs::CreateDir(porting::path_userdata); + game_params->game_spec = gamespec; + return true; +} - init_gettext((porting::path_data+DIR_DELIM+".."+DIR_DELIM+"locale").c_str()); - - // Initialize debug streams -#ifdef RUN_IN_PLACE - std::string debugfile = DEBUGFILE; -#else - std::string debugfile = porting::path_userdata+DIR_DELIM+DEBUGFILE; -#endif - debugstreams_init(disable_stderr, debugfile.c_str()); - // Initialize debug stacks - debug_stacks_init(); - DSTACK(__FUNCTION_NAME); +/***************************************************************************** + * Dedicated server + *****************************************************************************/ +static bool run_dedicated_server(const GameParams &game_params, const Settings &cmd_args) +{ + DSTACK("Dedicated server branch"); + + verbosestream << _("Using world path") << " [" + << game_params.world_path << "]" << std::endl; + verbosestream << _("Using gameid") << " [" + << game_params.game_spec.id << "]" << std::endl; + + // Bind address + std::string bind_str = g_settings->get("bind_address"); + Address bind_addr(0, 0, 0, 0, game_params.socket_port); + + if (g_settings->getBool("ipv6_server")) { + bind_addr.setAddress((IPv6AddressBytes*) NULL); + } + try { + bind_addr.Resolve(bind_str.c_str()); + } catch (ResolveError &e) { + infostream << "Resolving bind address \"" << bind_str + << "\" failed: " << e.what() + << " -- Listening on all addresses." << std::endl; + } + if (bind_addr.isIPv6() && !g_settings->getBool("enable_ipv6")) { + errorstream << "Unable to listen on " + << bind_addr.serializeString() + << L" because IPv6 is disabled" << std::endl; + return false; + } - // Init material properties table - //initializeMaterialProperties(); + // Database migration + if (cmd_args.exists("migrate")) + return migrate_database(game_params, cmd_args); - // Debug handler - BEGIN_DEBUG_EXCEPTION_HANDLER + if (cmd_args.exists("terminal")) { +#if USE_CURSES + bool name_ok = true; + std::string admin_nick = g_settings->get("name"); - // Print startup message - actionstream<readConfigFile(cmd_args.get("config").c_str()); - if(r == false) - { - errorstream<<"Could not read configuration from \"" - < filenames; - filenames.push_back(porting::path_userdata + - DIR_DELIM + "minetest.conf"); -#ifdef RUN_IN_PLACE - filenames.push_back(porting::path_userdata + - DIR_DELIM + ".." + DIR_DELIM + "minetest.conf"); -#endif + name_ok = name_ok && !admin_nick.empty(); + name_ok = name_ok && string_allowed(admin_nick, PLAYERNAME_ALLOWED_CHARS); - for(u32 i=0; ireadConfigFile(filenames[i].c_str()); - if(r) - { - configpath = filenames[i]; - break; + if (!name_ok) { + if (admin_nick.empty()) { + errorstream << "No name given for admin. " + << "Please check your minetest.conf that it " + << "contains a 'name = ' to your main admin account." + << std::endl; + } else { + errorstream << "Name for admin '" + << admin_nick << "' is not valid. " + << "Please check that it only contains allowed characters. " + << "Valid characters are: " << PLAYERNAME_ALLOWED_CHARS_USER_EXPL + << std::endl; } + return false; } - - // If no path found, use the first one (menu creates the file) - if(configpath == "") - configpath = filenames[0]; - } + ChatInterface iface; + bool &kill = *porting::signal_handler_killstatus(); - // Initialize random seed - srand(time(0)); - mysrand(time(0)); + try { + // Create server + Server server(game_params.world_path, + game_params.game_spec, false, bind_addr.isIPv6(), &iface); - /* - Pre-initialize some stuff with a dummy irrlicht wrapper. + g_term_console.setup(&iface, &kill, admin_nick); - These are needed for unit tests at least. - */ - - // Must be called before texturesource is created - // (for texture atlas making) - init_mineral(); + g_term_console.start(); - /* - Run unit tests - */ + server.start(bind_addr); + // Run server + dedicated_server_loop(server, kill); + } catch (const ModError &e) { + g_term_console.stopAndWaitforThread(); + errorstream << "ModError: " << e.what() << std::endl; + return false; + } catch (const ServerError &e) { + g_term_console.stopAndWaitforThread(); + errorstream << "ServerError: " << e.what() << std::endl; + return false; + } - if((ENABLE_TESTS && cmd_args.getFlag("disable-unittests") == false) - || cmd_args.getFlag("enable-unittests") == true) - { - run_tests(); - } - - /*for(s16 y=-100; y<100; y++) - for(s16 x=-100; x<100; x++) - { - std::cout<exists("port")) - port = g_settings->getU16("port"); - if(port == 0) - port = 30000; - - // Map directory - std::string map_dir = porting::path_userdata+DIR_DELIM+"world"; - if(cmd_args.exists("map-dir")) - map_dir = cmd_args.get("map-dir"); - else if(g_settings->exists("map-dir")) - map_dir = g_settings->get("map-dir"); - - // Run dedicated server if asked to - if(cmd_args.getFlag("server")) - { - DSTACK("Dedicated server branch"); - - // Create time getter - g_timegetter = new SimpleTimeGetter(); - - // Create server - Server server(map_dir.c_str(), configpath); - server.start(port); - - // Run server - dedicated_server_loop(server, kill); + // Tell the console to stop, and wait for it to finish, + // only then leave context and free iface + g_term_console.stop(); + g_term_console.wait(); - return 0; + g_term_console.clearKillStatus(); + } else { +#else + errorstream << "Cmd arg --terminal passed, but " + << "compiled without ncurses. Ignoring." << std::endl; + } { +#endif + try { + // Create server + Server server(game_params.world_path, game_params.game_spec, false, + bind_addr.isIPv6()); + server.start(bind_addr); + + // Run server + bool &kill = *porting::signal_handler_killstatus(); + dedicated_server_loop(server, kill); + + } catch (const ModError &e) { + errorstream << "ModError: " << e.what() << std::endl; + return false; + } catch (const ServerError &e) { + errorstream << "ServerError: " << e.what() << std::endl; + return false; + } } + return true; +} - /* - More parameters - */ - - // Address to connect to - std::string address = ""; - - if(cmd_args.exists("address")) - { - address = cmd_args.get("address"); +static bool migrate_database(const GameParams &game_params, const Settings &cmd_args) +{ + std::string migrate_to = cmd_args.get("migrate"); + Settings world_mt; + std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt"; + if (!world_mt.readConfigFile(world_mt_path.c_str())) { + errorstream << "Cannot read world.mt!" << std::endl; + return false; } - else - { - address = g_settings->get("address"); + if (!world_mt.exists("backend")) { + errorstream << "Please specify your current backend in world.mt:" + << std::endl + << " backend = {sqlite3|leveldb|redis|dummy}" + << std::endl; + return false; } - - std::string playername = g_settings->get("name"); - - /* - Device initialization - */ - - // Resolution selection - - bool fullscreen = false; - u16 screenW = g_settings->getU16("screenW"); - u16 screenH = g_settings->getU16("screenH"); - - // Determine driver - - video::E_DRIVER_TYPE driverType; - - std::string driverstring = g_settings->get("video_driver"); - - if(driverstring == "null") - driverType = video::EDT_NULL; - else if(driverstring == "software") - driverType = video::EDT_SOFTWARE; - else if(driverstring == "burningsvideo") - driverType = video::EDT_BURNINGSVIDEO; - else if(driverstring == "direct3d8") - driverType = video::EDT_DIRECT3D8; - else if(driverstring == "direct3d9") - driverType = video::EDT_DIRECT3D9; - else if(driverstring == "opengl") - driverType = video::EDT_OPENGL; - else - { - errorstream<<"WARNING: Invalid video_driver specified; defaulting " - "to opengl"<(screenW, screenH), - 16, fullscreen, false, false, &receiver); - - if (device == 0) - return 1; // could not create selected driver. - - /* - Continue initialization - */ - - video::IVideoDriver* driver = device->getVideoDriver(); - - // Disable mipmaps (because some of them look ugly) - driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false); - - /* - This changes the minimum allowed number of vertices in a VBO. - Default is 500. - */ - //driver->setMinHardwareBufferVertexCount(50); - - // Set the window caption - device->setWindowCaption(L"Minetest [Main Menu]"); - - // Create time getter - g_timegetter = new IrrlichtTimeGetter(device); - - // Create game callback for menus - g_gamecallback = new MainGameCallback(device); - - /* - Speed tests (done after irrlicht is loaded to get timer) - */ - if(cmd_args.getFlag("speedtests")) - { - dstream<<"Running speed tests"<setResizable(true); - - bool random_input = g_settings->getBool("random_input") - || cmd_args.getFlag("random-input"); - InputHandler *input = NULL; - if(random_input) - input = new RandomInputHandler(); - else - input = new RealInputHandler(device, &receiver); - - scene::ISceneManager* smgr = device->getSceneManager(); - - guienv = device->getGUIEnvironment(); - gui::IGUISkin* skin = guienv->getSkin(); - gui::IGUIFont* font = guienv->getFont(getTexturePath("fontlucida.png").c_str()); - if(font) - skin->setFont(font); - else - errorstream<<"WARNING: Font file was not found." - " Using default font."<getFont(); - assert(font); - - u32 text_height = font->getDimension(L"Hello, world!").Height; - infostream<<"text_height="<setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,0,0,0)); - skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255,255,255,255)); - //skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(0,0,0,0)); - //skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(0,0,0,0)); - skin->setColor(gui::EGDC_3D_HIGH_LIGHT, video::SColor(255,0,0,0)); - skin->setColor(gui::EGDC_3D_SHADOW, video::SColor(255,0,0,0)); - - /* - GUI stuff - */ - - /* - If an error occurs, this is set to something and the - menu-game loop is restarted. It is then displayed before - the menu. - */ - std::wstring error_message = L""; - - // The password entered during the menu screen, - std::string password; - - /* - Menu-game loop - */ - while(device->run() && kill == false) - { - - // This is used for catching disconnects - try - { - - /* - Clear everything from the GUIEnvironment - */ - guienv->clear(); - - /* - We need some kind of a root node to be able to add - custom gui elements directly on the screen. - Otherwise they won't be automatically drawn. - */ - guiroot = guienv->addStaticText(L"", - core::rect(0, 0, 10000, 10000)); - - /* - Out-of-game menu loop. - - Loop quits when menu returns proper parameters. - */ - while(kill == false) - { - // Cursor can be non-visible when coming from the game - device->getCursorControl()->setVisible(true); - // Some stuff are left to scene manager when coming from the game - // (map at least?) - smgr->clear(); - // Reset or hide the debug gui texts - /*guitext->setText(L"Minetest-c55"); - guitext2->setVisible(false); - guitext_info->setVisible(false); - guitext_chat->setVisible(false);*/ - - // Initialize menu data - MainMenuData menudata; - menudata.address = narrow_to_wide(address); - menudata.name = narrow_to_wide(playername); - menudata.port = narrow_to_wide(itos(port)); - menudata.fancy_trees = g_settings->getBool("new_style_leaves"); - menudata.smooth_lighting = g_settings->getBool("smooth_lighting"); - menudata.clouds_3d = g_settings->getBool("enable_3d_clouds"); - menudata.opaque_water = g_settings->getBool("opaque_water"); - menudata.creative_mode = g_settings->getBool("creative_mode"); - menudata.enable_damage = g_settings->getBool("enable_damage"); - - GUIMainMenu *menu = - new GUIMainMenu(guienv, guiroot, -1, - &g_menumgr, &menudata, g_gamecallback); - menu->allowFocusRemoval(true); - - if(error_message != L"") - { - errorstream<<"error_message = " - <drop(); - error_message = L""; - } - - video::IVideoDriver* driver = device->getVideoDriver(); - - infostream<<"Created main menu"<run() && kill == false) - { - if(menu->getStatus() == true) - break; - - //driver->beginScene(true, true, video::SColor(255,0,0,0)); - driver->beginScene(true, true, video::SColor(255,128,128,128)); - - drawMenuBackground(driver); - - guienv->drawAll(); - - driver->endScene(); - - // On some computers framerate doesn't seem to be - // automatically limited - sleep_ms(25); - } - - // Break out of menu-game loop to shut down cleanly - if(device->run() == false || kill == true) - break; - - infostream<<"Dropping main menu"<drop(); - - // Delete map if requested - if(menudata.delete_map) - { - bool r = fs::RecursiveDeleteContent(map_dir); - if(r == false) - error_message = L"Delete failed"; - continue; - } - - playername = wide_to_narrow(menudata.name); - - password = translatePassword(playername, menudata.password); - - //infostream<<"Main: password hash: '"<set("new_style_leaves", itos(menudata.fancy_trees)); - g_settings->set("smooth_lighting", itos(menudata.smooth_lighting)); - g_settings->set("enable_3d_clouds", itos(menudata.clouds_3d)); - g_settings->set("opaque_water", itos(menudata.opaque_water)); - g_settings->set("creative_mode", itos(menudata.creative_mode)); - g_settings->set("enable_damage", itos(menudata.enable_damage)); - - // NOTE: These are now checked server side; no need to do it - // here, so let's not do it here. - /*// Check for valid parameters, restart menu if invalid. - if(playername == "") - { - error_message = L"Name required."; - continue; - } - // Check that name has only valid chars - if(string_allowed(playername, PLAYERNAME_ALLOWED_CHARS)==false) - { - error_message = L"Characters allowed: " - +narrow_to_wide(PLAYERNAME_ALLOWED_CHARS); - continue; - }*/ - - // Save settings - g_settings->set("name", playername); - g_settings->set("address", address); - g_settings->set("port", itos(port)); - // Update configuration file - if(configpath != "") - g_settings->updateConfigFile(configpath.c_str()); - - // Continue to game - break; - } - - // Break out of menu-game loop to shut down cleanly - if(device->run() == false) - break; - - /* - Run game - */ - the_game( - kill, - random_input, - input, - device, - font, - map_dir, - playername, - password, - address, - port, - error_message, - configpath - ); - - } //try - catch(con::PeerNotFoundException &e) - { - errorstream<<"Connection error (timed out?)"< blocks; + old_db->listAllLoadableBlocks(blocks); + new_db->beginSave(); + for (std::vector::const_iterator it = blocks.begin(); it != blocks.end(); ++it) { + if (kill) return false; + + const std::string &data = old_db->loadBlock(*it); + if (!data.empty()) { + new_db->saveBlock(*it, data); + } else { + errorstream << "Failed to load block " << PP(*it) << ", skipping it." << std::endl; } -#ifdef NDEBUG - catch(std::exception &e) - { - std::string narrow_message = "Some exception, what()=\""; - narrow_message += e.what(); - narrow_message += "\""; - errorstream<= 1) { + std::cerr << " Migrated " << count << " blocks, " + << (100.0 * count / blocks.size()) << "% completed.\r"; + new_db->endSave(); + new_db->beginSave(); + last_update_time = time(NULL); } -#endif + } + std::cerr << std::endl; + new_db->endSave(); + delete old_db; + delete new_db; - } // Menu-game loop - - delete input; - - /* - In the end, delete the Irrlicht device. - */ - device->drop(); - - END_DEBUG_EXCEPTION_HANDLER(errorstream) - - debugstreams_deinit(); - - return 0; -} + actionstream << "Successfully migrated " << count << " blocks" << std::endl; + world_mt.set("backend", migrate_to); + if (!world_mt.updateConfigFile(world_mt_path.c_str())) + errorstream << "Failed to update world.mt!" << std::endl; + else + actionstream << "world.mt updated" << std::endl; -//END + return true; +}