diff --git a/binaries/system/readme.txt b/binaries/system/readme.txt index d6234632ac..cd8b6e0862 100644 --- a/binaries/system/readme.txt +++ b/binaries/system/readme.txt @@ -7,20 +7,25 @@ Basic gameplay: -quickstart load faster (disables audio and some system info logging) Autostart: --autostart=TYPEDIR/MAPNAME enables autostart, selects map in subdirectory of maps/ --autostart-ai=PLAYER:AI adds named AI to the given PLAYER (e.g. 2:testbot) --autostart-aidiff=PLAYER:level changes the difficulty setting for AI PLAYER (0: easy, 3: very hard) +-autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME; TYPEDIR is skirmishes, scenarios, or random +-autostart-ai=PLAYER:AI sets the AI for PLAYER (e.g. 2:petra) +-autostart-aidiff=PLAYER:DIFF sets the DIFFiculty of PLAYER's AI (0: easy, 3: very hard) +-autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV (skirmish and random maps only) Multiplayer: --autostart-playername=NAME multiplayer local player NAME (default 'anonymous') --autostart-host multiplayer host mode --autostart-players=NUMBER multiplayer host: NUMBER of client players (default 2) --autostart-client multiplayer client mode --autostart-ip=IP multiplayer client: connect to this host IP -Random maps only: --autostart-random=SEED random map with SEED value (default 0, use -1 for random) --autostart-size=TILES random map SIZE in tiles (default 192) --autostart-players=NUMBER NUMBER of players on random map --autostart-civ=PLAYER:CIV changes the civilisation of player PLAYER to CIV (default athen) +-autostart-playername=NAME sets local player NAME (default 'anonymous') +-autostart-host sets multiplayer host mode +-autostart-host-players=NUMBER sets NUMBER of human players for multiplayer game (default 2) +-autostart-client=IP sets multiplayer client to join host at given IP address + Random maps only: +-autostart-seed=SEED sets random map SEED value (default 0, use -1 for random) +-autostart-size=TILES sets random map size in TILES (default 192) +-autostart-players=NUMBER sets NUMBER of players on random map (default 2) + +Examples: +1) "Bob" will host a 2 player game on the Arcadia map: + -autostart="scenarios/Arcadia 02" -autostart-host -autostart-host-players=2 -autostart-playername="Bob" +2) Load Alpine Lakes random map with random seed, 2 players (Athens and Britons), and player 2 is PetraBot: + -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra Configuration: -conf=KEY:VALUE set a config value diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index c35e22bb56..2a08670213 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -1126,8 +1126,8 @@ CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath) if (INFO::OK != loadResult) { - LOGERROR(L"Unable to load map file - maybe path typo?"); - throw PSERROR_Game_World_MapLoadFailed("Unable to load map file - maybe path typo?"); + LOGERROR(L"LoadSettingsOfScenarioMap: Unable to load map file '%ls'", mapPath.string().c_str()); + throw PSERROR_Game_World_MapLoadFailed("Unable to load map file, check the path for typos."); } XMBElement mapElement = mapFile.GetRoot(); @@ -1152,38 +1152,41 @@ CStr8 LoadSettingsOfScenarioMap(const VfsPath &mapPath) return mapElement.GetText(); } +/* + * Command line options for autostart (keep synchronized with readme.txt): + * + * -autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME; TYPEDIR is skirmishes, scenarios, or random + * -autostart-ai=PLAYER:AI sets the AI for PLAYER (e.g. 2:petra) + * -autostart-aidiff=PLAYER:DIFF sets the DIFFiculty of PLAYER's AI (0: easy, 3: very hard) + * -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV (skirmish and random maps only) + * Multiplayer: + * -autostart-playername=NAME sets local player NAME (default 'anonymous') + * -autostart-host sets multiplayer host mode + * -autostart-host-players=NUMBER sets NUMBER of human players for multiplayer game (default 2) + * -autostart-client=IP sets multiplayer client to join host at given IP address + * Random maps only: + * -autostart-seed=SEED sets random map SEED value (default 0, use -1 for random) + * -autostart-size=TILES sets random map size in TILES (default 192) + * -autostart-players=NUMBER sets NUMBER of players on random map (default 2) + * + * Examples: + * 1) "Bob" will host a 2 player game on the Arcadia map: + * -autostart="scenarios/Arcadia 02" -autostart-host -autostart-host-players=2 -autostart-playername="Bob" + * 2) Load Alpine Lakes random map with random seed, 2 players (Athens and Britons), and player 2 is PetraBot: + * -autostart="random/alpine_lakes" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=2:petra +*/ bool Autostart(const CmdLineArgs& args) { - /* - * Handle various command-line options, for quick testing of various features: - * -autostart=name -- map name for scenario, or rms name for random map - * -autostart-ai=1:dummybot -- adds the dummybot AI to player 1 - * -autostart-playername=name -- multiplayer player name - * -autostart-host -- multiplayer host mode - * -autostart-players=2 -- number of players - * -autostart-client -- multiplayer client mode - * -autostart-ip=127.0.0.1 -- multiplayer connect to 127.0.0.1 - * -autostart-random=104 -- random map, optional seed value = 104 (default is 0, random is -1) - * -autostart-size=192 -- random map size in tiles = 192 (default is 192) - * -autostart-civ=1:hele -- set player #1 civ to "hele" - * - * Examples: - * -autostart=Acropolis -autostart-host -autostart-players=2 -- Host game on Acropolis map, 2 players - * -autostart=latium -autostart-random=-1 -- Start single player game on latium random map, random rng seed - */ - CStr autoStartName = args.Get("autostart"); #if OS_ANDROID // HACK: currently the most convenient way to test maps on Android; // should find a better solution - autoStartName = "Oasis"; + autoStartName = "scenarios/Arcadia 02"; #endif if (autoStartName.empty()) - { return false; - } g_Game = new CGame(); @@ -1201,8 +1204,7 @@ bool Autostart(const CmdLineArgs& args) // The directory in front of the actual map name indicates which type // of map is being loaded. Drawback of this approach is the association // of map types and folders is hard-coded, but benefits are: - // - No need to pass the map type via command line separately (as was - // done by -autostart-random) + // - No need to pass the map type via command line separately // - Prevents mixing up of scenarios and skirmish maps to some degree Path mapPath = Path(autoStartName); std::wstring mapDirectory = mapPath.Parent().Filename().string(); @@ -1212,18 +1214,15 @@ bool Autostart(const CmdLineArgs& args) { // Default seed is 0 u32 seed = 0; - CStr seedArg = args.Get("autostart-random"); + CStr seedArg = args.Get("autostart-seed"); if (!seedArg.empty()) { - if (seedArg.compare("-1") == 0) - { // Random seed value + // Random seed value + if (seedArg != "-1") seed = rand(); - } else - { seed = seedArg.ToULong(); - } } // Random map definition will be loaded from JSON file, so we need to parse it @@ -1240,7 +1239,7 @@ bool Autostart(const CmdLineArgs& args) else { // Problem with JSON file - LOGERROR(L"Error reading random map script '%ls'", scriptPath.c_str()); + LOGERROR(L"Autostart: Error reading random map script '%ls'", scriptPath.c_str()); throw PSERROR_Game_World_MapLoadFailed("Error reading random map script.\nCheck application log for details."); } @@ -1270,48 +1269,36 @@ bool Autostart(const CmdLineArgs& args) scriptInterface.Eval("({})", &player); // We could load player_defaults.json here, but that would complicate the logic - // even more and autostart is only intended for developers anyway + // even more and autostart is only intended for developers anyway scriptInterface.SetProperty(player, "Civ", std::string("athen")); scriptInterface.SetPropertyInt(playerData, i, player); } mapType = "random"; } - else if (mapDirectory == L"scenarios") + else if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes") { // Initialize general settings from the map data so some values // (e.g. name of map) are always present, even when autostart is - // less-than-completely configured - // (Omitting this may cause the loading screen to display "Loading (undefined)", - // for example...) - CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml"); - scriptInterface.ParseJSON(mapSettingsJSON, &settings); - mapType = "scenario"; - } - else if (mapDirectory == L"skirmishes") - { - // In skirmish mode, the player initialization data is taken from the - // game-setup settings (see CGame::RegisterInit(...)). If some player - // is not initialized (PlayerData[] holding fewer/more entries than - // defined in map), there's a crash. - // To prevent this, we mimic the behavior of the game setup screen by - // retrieving the map settings from the actual map xml... + // partially configured CStr8 mapSettingsJSON = LoadSettingsOfScenarioMap("maps/" + autoStartName + ".xml"); scriptInterface.ParseJSON(mapSettingsJSON, &settings); - // ...and initialize the playerData array being edited by - // autostart-civ et.al. with the real map data, so sensible values - // are always present: + // Initialize the playerData array being modified by autostart + // with the real map data, so sensible values are present: scriptInterface.GetProperty(settings, "PlayerData", &playerData); - mapType = "skirmish"; + + if (mapDirectory == L"scenarios") + mapType = "scenario"; + else + mapType = "skirmish"; } - if (mapType.empty()) + else { - LOGERROR(L"Unrecognized map type '%ls' detected", mapType.c_str()); - throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult GameSetup.cpp for the currently supported types."); + LOGERROR(L"Autostart: Unrecognized map type '%ls'", mapDirectory.c_str()); + throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types."); } scriptInterface.SetProperty(attrs, "mapType", mapType); scriptInterface.SetProperty(attrs, "map", std::string("maps/" + autoStartName)); - scriptInterface.SetProperty(settings, "mapType", mapType); // Set player data for AIs @@ -1321,16 +1308,22 @@ bool Autostart(const CmdLineArgs& args) std::vector aiArgs = args.GetMultiple("autostart-ai"); for (size_t i = 0; i < aiArgs.size(); ++i) { + int playerID = aiArgs[i].BeforeFirst(":").ToInt(); + // Instead of overwriting existing player data, modify the array JS::RootedValue player(cx); - if (!scriptInterface.GetPropertyInt(playerData, i, &player) || player.isUndefined()) + if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined()) { + if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes") + { + // playerID is certainly bigger than this map player number + LOGWARNING(L"Autostart: Invalid player %d in autostart-ai option", playerID); + continue; + } scriptInterface.Eval("({})", &player); } - int playerID = aiArgs[i].BeforeFirst(":").ToInt(); CStr name = aiArgs[i].AfterFirst(":"); - scriptInterface.SetProperty(player, "AI", std::string(name)); scriptInterface.SetProperty(player, "AIDiff", 2); scriptInterface.SetPropertyInt(playerData, playerID-1, player); @@ -1342,16 +1335,22 @@ bool Autostart(const CmdLineArgs& args) std::vector civArgs = args.GetMultiple("autostart-aidiff"); for (size_t i = 0; i < civArgs.size(); ++i) { + int playerID = civArgs[i].BeforeFirst(":").ToInt(); + // Instead of overwriting existing player data, modify the array JS::RootedValue player(cx); - if (!scriptInterface.GetPropertyInt(playerData, i, &player) || player.isUndefined()) + if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined()) { + if (mapDirectory == L"scenarios" || mapDirectory == L"skirmishes") + { + // playerID is certainly bigger than this map player number + LOGWARNING(L"Autostart: Invalid player %d in autostart-aidiff option", playerID); + continue; + } scriptInterface.Eval("({})", &player); } - - int playerID = civArgs[i].BeforeFirst(":").ToInt(); - int difficulty = civArgs[i].AfterFirst(":").ToInt(); - + + int difficulty = civArgs[i].AfterFirst(":").ToInt(); scriptInterface.SetProperty(player, "AIDiff", difficulty); scriptInterface.SetPropertyInt(playerData, playerID-1, player); } @@ -1359,22 +1358,33 @@ bool Autostart(const CmdLineArgs& args) // Set player data for Civs if (args.Has("autostart-civ")) { - std::vector civArgs = args.GetMultiple("autostart-civ"); - for (size_t i = 0; i < civArgs.size(); ++i) + if (mapDirectory != L"scenarios") { - // Instead of overwriting existing player data, modify the array - JS::RootedValue player(cx); - if (!scriptInterface.GetPropertyInt(playerData, i, &player) || player.isUndefined()) + std::vector civArgs = args.GetMultiple("autostart-civ"); + for (size_t i = 0; i < civArgs.size(); ++i) { - scriptInterface.Eval("({})", &player); + int playerID = civArgs[i].BeforeFirst(":").ToInt(); + + // Instead of overwriting existing player data, modify the array + JS::RootedValue player(cx); + if (!scriptInterface.GetPropertyInt(playerData, playerID-1, &player) || player.isUndefined()) + { + if (mapDirectory == L"skirmishes") + { + // playerID is certainly bigger than this map player number + LOGWARNING(L"Autostart: Invalid player %d in autostart-civ option", playerID); + continue; + } + scriptInterface.Eval("({})", &player); + } + + CStr name = civArgs[i].AfterFirst(":"); + scriptInterface.SetProperty(player, "Civ", std::string(name)); + scriptInterface.SetPropertyInt(playerData, playerID-1, player); } - - int playerID = civArgs[i].BeforeFirst(":").ToInt(); - CStr name = civArgs[i].AfterFirst(":"); - - scriptInterface.SetProperty(player, "Civ", std::string(name)); - scriptInterface.SetPropertyInt(playerData, playerID-1, player); } + else + LOGWARNING(L"Autostart: Option 'autostart-civ' is invalid for scenarios"); } // Add player data to map settings @@ -1399,10 +1409,8 @@ bool Autostart(const CmdLineArgs& args) InitPs(true, L"page_loading.xml", &scriptInterface, mpInitData); size_t maxPlayers = 2; - if (args.Has("autostart-players")) - { - maxPlayers = args.Get("autostart-players").ToUInt(); - } + if (args.Has("autostart-host-players")) + maxPlayers = args.Get("autostart-host-players").ToUInt(); g_NetServer = new CNetServer(maxPlayers); @@ -1422,11 +1430,9 @@ bool Autostart(const CmdLineArgs& args) g_NetClient = new CNetClient(g_Game); g_NetClient->SetUserName(userName); - CStr ip = "127.0.0.1"; - if (args.Has("autostart-ip")) - { - ip = args.Get("autostart-ip"); - } + CStr ip = args.Get("autostart-client"); + if (ip == "") + ip = "127.0.0.1"; bool ok = g_NetClient->SetupConnection(ip); ENSURE(ok);