Index: binaries/data/mods/public/autostart/autostart.js =================================================================== --- binaries/data/mods/public/autostart/autostart.js +++ binaries/data/mods/public/autostart/autostart.js @@ -1,14 +1,24 @@ class AutoStart { - constructor(initData) + constructor(cmdLineArgs) { + this.playerAssignments = { + "local": { + "player": +cmdLineArgs?.['autostart-player'] ?? 1, + "name": "anonymous", + }, + }; this.settings = new GameSettings().init(); - this.settings.fromInitAttributes(initData.attribs); - this.playerAssignments = initData.playerAssignments; + // Enable cheats in SP + this.settings.cheats.setEnabled(true); - this.settings.launchGame(this.playerAssignments, initData.storeReplay); + parseCmdLineArgs(this.settings, cmdLineArgs); + if ('autostart-nonvisual' in cmdLineArgs) + this.settings.triggerScripts.customScripts.add("scripts/NonVisualTrigger.js"); + + this.settings.launchGame(this.playerAssignments, !cmdLineArgs['autostart-disable-replay']); this.onLaunch(); } Index: binaries/data/mods/public/autostart/autostart_client.js =================================================================== --- binaries/data/mods/public/autostart/autostart_client.js +++ binaries/data/mods/public/autostart/autostart_client.js @@ -1,12 +1,15 @@ class AutoStartClient { - constructor(initData) + constructor(cmdLineArgs) { this.playerAssignments = {}; try { - Engine.StartNetworkJoin(initData.playerName, initData.ip, initData.port, initData.storeReplay); + const playerName = cmdLineArgs['autostart-playername'] || "anonymous"; + const ip = cmdLineArgs['autostart-client'] ?? "127.0.0.1"; + const port = cmdLineArgs['autostart-port'] ?? 5073; + Engine.StartNetworkJoin(playerName, ip, port, !cmdLineArgs['autostart-disable-replay']); } catch (e) { Index: binaries/data/mods/public/autostart/autostart_host.js =================================================================== --- binaries/data/mods/public/autostart/autostart_host.js +++ binaries/data/mods/public/autostart/autostart_host.js @@ -1,18 +1,20 @@ class AutoStartHost { - constructor(initData) + constructor(cmdLineArgs) { this.launched = false; - this.maxPlayers = initData.maxPlayers; - this.storeReplay = initData.storeReplay; this.playerAssignments = {}; - this.initAttribs = initData.attribs; + this.maxPlayers = cmdLineArgs['autostart-host-players'] ?? 2; + this.cmdLineArgs = cmdLineArgs; try { + const playerName = cmdLineArgs['autostart-playername'] || "anonymous"; + const port = cmdLineArgs['autostart-port'] ?? 5073; + // Stun and password not implemented for autostart. - Engine.StartNetworkHost(initData.playerName, initData.port, false, "", initData.storeReplay); + Engine.StartNetworkHost(playerName, port, false, "", !cmdLineArgs['autostart-disable-replay']); } catch (e) { @@ -74,11 +76,16 @@ this.launched = true; this.settings = new GameSettings().init(); - this.settings.fromInitAttributes(this.initAttribs); + + parseCmdLineArgs(this.settings, this.cmdLineArgs); + + if ('autostart-nonvisual' in this.cmdLineArgs) + this.settings.triggerScripts.customScripts.add("scripts/NonVisualTrigger.js"); + this.settings.playerCount.setNb(Object.keys(this.playerAssignments).length); this.settings.launchGame(this.playerAssignments, this.storeReplay); Engine.SwitchGuiPage("page_loading.xml", { - "attribs": this.initAttribs, + "attribs": this.settings.finalizedAttributes, "playerAssignments": this.playerAssignments }); } Index: binaries/data/mods/public/autostart/cmd_line_args.js =================================================================== --- /dev/null +++ binaries/data/mods/public/autostart/cmd_line_args.js @@ -0,0 +1,133 @@ +/** + * Command line options for autostart + * (keep synchronized with binaries/system/readme.txt): + * TODO: potentially the remaining C++ options could be handled in JS too. + * + * -autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME; + * TYPEDIR is skirmishes, scenarios, or random + * -autostart-biome=BIOME sets BIOME for a random map + * -autostart-seed=SEED sets randomization seed value (default 0, use -1 for 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 + * (default 3, 0: sandbox, 5: very hard) + * -autostart-aiseed=AISEED sets the seed used for the AI random + * generator (default 0, use -1 for random) + * -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV (skirmish and random maps only). + * Use random for a random civ. + * -autostart-team=PLAYER:TEAM sets the team for PLAYER (e.g. 2:2). + * -autostart-ceasefire=NUM sets a ceasefire duration NUM + * (default 0 minutes) + * -autostart-victory=SCRIPTNAME sets the victory conditions with SCRIPTNAME + * located in simulation/data/settings/victory_conditions/ + * (default conquest). When the first given SCRIPTNAME is + * "endless", no victory conditions will apply. + * -autostart-wonderduration=NUM sets the victory duration NUM for wonder victory condition + * (default 10 minutes) + * -autostart-relicduration=NUM sets the victory duration NUM for relic victory condition + * (default 10 minutes) + * -autostart-reliccount=NUM sets the number of relics for relic victory condition + * (default 2 relics) + * + * -autostart-nonvisual (partly handled in C++) disable any graphics and sounds + * -autostart-disable-replay disable saving of replays (handled in autostart*.js files) + * -autostart-player=NUMBER sets the playerID in non-networked games (default 1, use -1 for observer) + * + * Multiplayer (handled in specific autostart*.js files): + * -autostart-playername=NAME sets local player NAME (default 'anonymous') + * -autostart-host (handled in C++) sets multiplayer host mode + * -autostart-host-players=NUMBER sets NUMBER of human players for multiplayer + * game (default 2) + * -autostart-port=NUMBER sets port NUMBER for multiplayer game + * -autostart-client=IP (handled in C++) sets multiplayer client to join host at + * given IP address + * + * Random maps only: + * -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" -autostart-host -autostart-host-players=2 -autostart-playername="Bob" + * "Alice" joins the match as player 2: + * -autostart-client=127.0.0.1 -autostart-playername="Alice" + * The players use the developer overlay to control players. + * + * 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 + * + * 3) Observe the PetraBot on a triggerscript map: + * -autostart="random/jebel_barkal" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=1:petra -autostart-ai=2:petra -autostart-player=-1 + */ +function parseCmdLineArgs(settings, cmdLineArgs) +{ + const mapType = cmdLineArgs['autostart'].substring(0, cmdLineArgs['autostart'].indexOf('/')); + settings.map.setType({ + "scenarios": "scenario", + "random": "random", + "skirmishes": "skirmish", + }[mapType]); + settings.map.selectMap("maps/" + cmdLineArgs['autostart']); + settings.mapSize.setSize(cmdLineArgs['autostart-size'] || 192); + settings.biome.setBiome(cmdLineArgs['autostart-biome'] || "random"); + + settings.playerCount.setNb(cmdLineArgs['autostart-players'] || 2); + + const getPlayer = (key, i) => { + if (!cmdLineArgs['autostart-' + key]) + return; + var value = cmdLineArgs['autostart-' + key]; + if (!Array.isArray(value)) + value = [value]; + // TODO: support more than 8 players + return value.find(x => x[0] == i)?.substring(2); + } + + for (let i = 1; i <= settings.playerCount.nbPlayers; ++i) + { + const civ = getPlayer("civ", i) + if (civ) + settings.playerCiv.setValue(i - 1, civ); + + const team = getPlayer("team", i) + if (team) + settings.playerTeam.setValue(i - 1, team); + + const ai = getPlayer("ai", i) + if (ai) + settings.playerAI.set(i - 1, { + "bot": ai, + "difficulty": getPlayer("aidiff") || 3, + "behavior": getPlayer("aibehavior") || "balanced", + }); + } + + settings.cheats.setEnabled(true); + + // Seeds default to random so we only need to set specific values. + if (cmdLineArgs?.['autostart-seed'] != -1) + settings.seeds.seed = cmdLineArgs?.['autostart-seed'] || 0; + + if (cmdLineArgs?.['autostart-aiseed'] != -1) + settings.seeds.AIseed = cmdLineArgs?.['autostart-aiseed'] || 0; + + if (cmdLineArgs['autostart-ceasefire']) + settings.seeds.ceaserfire.setValue(+cmdLineArgs['autostart-ceasefire']); + + if ('autostart-nonvisual' in cmdLineArgs) + settings.triggerScripts.customScripts.add("scripts/NonVisualTrigger.js"); + + const victoryConditions = cmdLineArgs["autostart-victory"]; + if (Array.isArray(victoryConditions)) + for (const cond of victoryConditions) + settings.victoryConditions.setEnabled(cond, true); + else if (victoryConditions && victoryConditions !== "endless") + settings.victoryConditions.setEnabled(victoryConditions, true); + + + settings.wonder.setDuration(cmdLineArgs['autostart-wonderduration'] || 10); + settings.relic.setDuration(cmdLineArgs['autostart-relicduration'] || 10); + settings.relic.setCount(cmdLineArgs['autostart-reliccount'] || 2); + + return settings; +} Index: binaries/data/mods/public/autostart/entrypoint.js =================================================================== --- binaries/data/mods/public/autostart/entrypoint.js +++ binaries/data/mods/public/autostart/entrypoint.js @@ -22,16 +22,16 @@ var autostartInstance; -function autostartClient(initData) +function autostartClient(cmdLineArgs) { - autostartInstance = new AutoStartClient(initData); + autostartInstance = new AutoStartClient(cmdLineArgs); } /** * This path depends on files currently stored under gui/, which should be moved. * The best place would probably be a new 'engine' mod, independent from the 'mod' mod and the public mod. */ -function autostartHost(initData, networked = false) +function autostartHost(cmdLineArgs, networked = false) { Engine.LoadScript("gui/common/color.js"); Engine.LoadScript("gui/common/functions_utility.js"); @@ -44,9 +44,9 @@ Engine.LoadScript("gamesettings/attributes/"); if (networked) - autostartInstance = new AutoStartHost(initData); + autostartInstance = new AutoStartHost(cmdLineArgs); else - autostartInstance = new AutoStart(initData); + autostartInstance = new AutoStart(cmdLineArgs); } /** Index: source/ps/GameSetup/CmdLineArgs.h =================================================================== --- source/ps/GameSetup/CmdLineArgs.h +++ source/ps/GameSetup/CmdLineArgs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -25,6 +25,13 @@ #include #include +class ScriptRequest; + +namespace Script { +template +void ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const T& val); +} + class CmdLineArgs { public: @@ -70,6 +77,9 @@ std::vector GetArgsWithoutName() const; private: + template + friend void Script::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const T& val); + typedef std::vector > ArgsT; ArgsT m_Args; OsPath m_Arg0; Index: source/ps/GameSetup/CmdLineArgs.cpp =================================================================== --- source/ps/GameSetup/CmdLineArgs.cpp +++ source/ps/GameSetup/CmdLineArgs.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2022 Wildfire Games. +/* Copyright (C) 2023 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -19,10 +19,13 @@ #include "CmdLineArgs.h" #include "lib/sysdep/sysdep.h" +#include "scriptinterface/Object.h" +#include "scriptinterface/ScriptConversions.h" #include #include #include +#include CmdLineArgs g_CmdLineArgs; @@ -113,3 +116,31 @@ { return m_ArgsWithoutName; } + +template<> void Script::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const CmdLineArgs& val) +{ + if (!Script::CreateObject(rq, ret)) + return; + + std::unordered_map> args; + for (const std::pair& arg : val.m_Args) + args.emplace(arg.first, std::vector{}).first->second.emplace_back(arg.second); + + JS::RootedValue argVal(rq.cx); + for (const decltype(args)::value_type& arg : args) + { + if (arg.second.size() == 1) + { + if (arg.second[0].empty()) + argVal = JS::UndefinedHandleValue; + else + Script::ToJSVal(rq, &argVal, arg.second[0]); + Script::SetProperty(rq, ret, arg.first.c_str(), argVal); + } + else + { + Script::ToJSVal(rq, &argVal, arg.second); + Script::SetProperty(rq, ret, arg.first.c_str(), argVal); + } + } +} Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -794,76 +794,22 @@ return templateRoot; } -/* - * Command line options for autostart - * (keep synchronized with binaries/system/readme.txt): - * - * -autostart="TYPEDIR/MAPNAME" enables autostart and sets MAPNAME; - * TYPEDIR is skirmishes, scenarios, or random - * -autostart-biome=BIOME sets BIOME for a random map - * -autostart-seed=SEED sets randomization seed value (default 0, use -1 for 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 - * (default 3, 0: sandbox, 5: very hard) - * -autostart-aiseed=AISEED sets the seed used for the AI random - * generator (default 0, use -1 for random) - * -autostart-player=NUMBER sets the playerID in non-networked games (default 1, use -1 for observer) - * -autostart-civ=PLAYER:CIV sets PLAYER's civilisation to CIV (skirmish and random maps only). - * Use random for a random civ. - * -autostart-team=PLAYER:TEAM sets the team for PLAYER (e.g. 2:2). - * -autostart-ceasefire=NUM sets a ceasefire duration NUM - * (default 0 minutes) - * -autostart-nonvisual disable any graphics and sounds - * -autostart-victory=SCRIPTNAME sets the victory conditions with SCRIPTNAME - * located in simulation/data/settings/victory_conditions/ - * (default conquest). When the first given SCRIPTNAME is - * "endless", no victory conditions will apply. - * -autostart-wonderduration=NUM sets the victory duration NUM for wonder victory condition - * (default 10 minutes) - * -autostart-relicduration=NUM sets the victory duration NUM for relic victory condition - * (default 10 minutes) - * -autostart-reliccount=NUM sets the number of relics for relic victory condition - * (default 2 relics) - * -autostart-disable-replay disable saving of replays - * - * 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-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" -autostart-host -autostart-host-players=2 -autostart-playername="Bob" - * "Alice" joins the match as player 2: - * -autostart-client=127.0.0.1 -autostart-playername="Alice" - * The players use the developer overlay to control players. - * - * 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 - * - * 3) Observe the PetraBot on a triggerscript map: - * -autostart="random/jebel_barkal" -autostart-seed=-1 -autostart-players=2 -autostart-civ=1:athen -autostart-civ=2:brit -autostart-ai=1:petra -autostart-ai=2:petra -autostart-player=-1 +/** + * Autostart arguments are parsed in javascript for convenience and moddability. + * This C++ part only handles the following arguments: + * -autostart=MAP to enable regular autostart / host mode autostart + * -autostart-client=IP to enable 'MP client' autostart. + * -autostart-host to enable 'MP host' autostart. + * -autostart-nonvisual to start in non-visual mode. + * TODO: it might be nice to move these to JS too. */ bool Autostart(const CmdLineArgs& args) { if (!args.Has("autostart-client") && !args.Has("autostart")) return false; - // Get optional playername. - CStrW userName = L"anonymous"; - if (args.Has("autostart-playername")) - userName = args.Get("autostart-playername").FromUTF8(); - // Create some scriptinterface to store the js values for the settings. ScriptInterface scriptInterface("Engine", "Game Setup", g_ScriptContext); - ScriptRequest rq(scriptInterface); // We use the javascript gameSettings to handle options, but that requires running JS. @@ -889,301 +835,19 @@ JSI_VFS::RegisterScriptFunctions_ReadWriteAnywhere(rq); JSI_Network::RegisterScriptFunctions(rq); - JS::RootedValue sessionInitData(rq.cx); + JS::RootedValue cmdLineArgs(rq.cx); + Script::ToJSVal(rq, &cmdLineArgs, args); - if (args.Has("autostart-client")) + if (args.Has("autostart-client") || args.Has("autostart-host")) { - CStr ip = args.Get("autostart-client"); - if (ip.empty()) - ip = "127.0.0.1"; - - Script::CreateObject( - rq, - &sessionInitData, - "playerName", userName, - "ip", ip, - "port", PS_DEFAULT_PORT, - "storeReplay", !args.Has("autostart-disable-replay")); - - JS::RootedValue global(rq.cx, rq.globalValue()); - if (!ScriptFunction::CallVoid(rq, global, "autostartClient", sessionInitData, true)) - return false; - - bool shouldQuit = false; - while (!shouldQuit) - { - g_NetClient->Poll(); - ScriptFunction::Call(rq, global, "onTick", shouldQuit); - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } - - if (args.Has("autostart-nonvisual")) - { - LDR_NonprogressiveLoad(); - g_Game->ReallyStartGame(); - } - return true; - } - - CStr autoStartName = args.Get("autostart"); - - if (autoStartName.empty()) - return false; - - JS::RootedValue attrs(rq.cx); - JS::RootedValue settings(rq.cx); - JS::RootedValue playerData(rq.cx); - - Script::CreateObject(rq, &attrs); - Script::CreateObject(rq, &settings); - Script::CreateArray(rq, &playerData); - - // 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 - // - Prevents mixing up of scenarios and skirmish maps to some degree - Path mapPath = Path(autoStartName); - std::wstring mapDirectory = mapPath.Parent().Filename().string(); - std::string mapType; - - if (mapDirectory == L"random") - { - // Get optional map size argument (default 192) - uint mapSize = 192; - if (args.Has("autostart-size")) - { - CStr size = args.Get("autostart-size"); - mapSize = size.ToUInt(); - } - - Script::SetProperty(rq, settings, "Size", mapSize); // Random map size (in patches) - - if (args.Has("autostart-biome")) - { - CStr biome = args.Get("autostart-biome"); - Script::SetProperty(rq, settings, "Biome", biome); - } - - // Get optional number of players (default 2) - size_t numPlayers = 2; - if (args.Has("autostart-players")) - { - CStr num = args.Get("autostart-players"); - numPlayers = num.ToUInt(); - } - - // Set up player data - for (size_t i = 0; i < numPlayers; ++i) - { - JS::RootedValue player(rq.cx); - - // We could load player_defaults.json here, but that would complicate the logic - // even more and autostart is only intended for developers anyway - Script::CreateObject(rq, &player, "Civ", "athen"); - - Script::SetPropertyInt(rq, playerData, i, player); - } - mapType = "random"; - } - else if (mapDirectory == L"scenarios") - mapType = "scenario"; - else if (mapDirectory == L"skirmishes") - mapType = "skirmish"; - else - { - LOGERROR("Autostart: Unrecognized map type '%s'", utf8_from_wstring(mapDirectory)); - throw PSERROR_Game_World_MapLoadFailed("Unrecognized map type.\nConsult readme.txt for the currently supported types."); - } - - Script::SetProperty(rq, attrs, "mapType", mapType); - Script::SetProperty(rq, attrs, "map", "maps/" + autoStartName); - Script::SetProperty(rq, settings, "mapType", mapType); - Script::SetProperty(rq, settings, "CheatsEnabled", true); - - // The seed is used for both random map generation and simulation - u32 seed = 0; - if (args.Has("autostart-seed")) - { - CStr seedArg = args.Get("autostart-seed"); - if (seedArg == "-1") - seed = rand(); - else - seed = seedArg.ToULong(); - } - Script::SetProperty(rq, settings, "Seed", seed); - - // Set seed for AIs - u32 aiseed = 0; - if (args.Has("autostart-aiseed")) - { - CStr seedArg = args.Get("autostart-aiseed"); - if (seedArg == "-1") - aiseed = rand(); - else - aiseed = seedArg.ToULong(); - } - Script::SetProperty(rq, settings, "AISeed", aiseed); - - // Set player data for AIs - // attrs.settings = { PlayerData: [ { AI: ... }, ... ] } - // or = { PlayerData: [ null, { AI: ... }, ... ] } when gaia set - int offset = 1; - JS::RootedValue player(rq.cx); - if (Script::GetPropertyInt(rq, playerData, 0, &player) && player.isNull()) - offset = 0; - - // Set teams - if (args.Has("autostart-team")) - { - std::vector civArgs = args.GetMultiple("autostart-team"); - 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 currentPlayer(rq.cx); - if (!Script::GetPropertyInt(rq, playerData, playerID-offset, ¤tPlayer) || currentPlayer.isUndefined()) - Script::CreateObject(rq, ¤tPlayer); - - int teamID = civArgs[i].AfterFirst(":").ToInt() - 1; - Script::SetProperty(rq, currentPlayer, "Team", teamID); - Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer); - } - } - - int ceasefire = 0; - if (args.Has("autostart-ceasefire")) - ceasefire = args.Get("autostart-ceasefire").ToInt(); - Script::SetProperty(rq, settings, "Ceasefire", ceasefire); - - if (args.Has("autostart-ai")) - { - 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 currentPlayer(rq.cx); - if (!Script::GetPropertyInt(rq, playerData, playerID-offset, ¤tPlayer) || currentPlayer.isUndefined()) - Script::CreateObject(rq, ¤tPlayer); - - Script::SetProperty(rq, currentPlayer, "AI", aiArgs[i].AfterFirst(":")); - Script::SetProperty(rq, currentPlayer, "AIDiff", 3); - Script::SetProperty(rq, currentPlayer, "AIBehavior", "balanced"); - Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer); - } - } - // Set AI difficulty - if (args.Has("autostart-aidiff")) - { - 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 currentPlayer(rq.cx); - if (!Script::GetPropertyInt(rq, playerData, playerID-offset, ¤tPlayer) || currentPlayer.isUndefined()) - Script::CreateObject(rq, ¤tPlayer); - - Script::SetProperty(rq, currentPlayer, "AIDiff", civArgs[i].AfterFirst(":").ToInt()); - Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer); - } - } - // Set player data for Civs - if (args.Has("autostart-civ")) - { - if (mapDirectory != L"scenarios") - { - std::vector civArgs = args.GetMultiple("autostart-civ"); - 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 currentPlayer(rq.cx); - if (!Script::GetPropertyInt(rq, playerData, playerID-offset, ¤tPlayer) || currentPlayer.isUndefined()) - Script::CreateObject(rq, ¤tPlayer); - - Script::SetProperty(rq, currentPlayer, "Civ", civArgs[i].AfterFirst(":")); - Script::SetPropertyInt(rq, playerData, playerID-offset, currentPlayer); - } - } - else - LOGWARNING("Autostart: Option 'autostart-civ' is invalid for scenarios"); - } - - // Add additional scripts to the TriggerScripts property - std::vector triggerScriptsVector; - JS::RootedValue triggerScripts(rq.cx); - - if (Script::HasProperty(rq, settings, "TriggerScripts")) - { - Script::GetProperty(rq, settings, "TriggerScripts", &triggerScripts); - Script::FromJSVal(rq, triggerScripts, triggerScriptsVector); - } - - if (!CRenderer::IsInitialised()) - { - CStr nonVisualScript = "scripts/NonVisualTrigger.js"; - triggerScriptsVector.push_back(nonVisualScript.FromUTF8()); - } - - Script::ToJSVal(rq, &triggerScripts, triggerScriptsVector); - Script::SetProperty(rq, settings, "TriggerScripts", triggerScripts); - - std::vector victoryConditions(1, "conquest"); - if (args.Has("autostart-victory")) - victoryConditions = args.GetMultiple("autostart-victory"); - - if (victoryConditions.size() == 1 && victoryConditions[0] == "endless") - victoryConditions.clear(); - - Script::SetProperty(rq, settings, "VictoryConditions", victoryConditions); - - int wonderDuration = 10; - if (args.Has("autostart-wonderduration")) - wonderDuration = args.Get("autostart-wonderduration").ToInt(); - Script::SetProperty(rq, settings, "WonderDuration", wonderDuration); - - int relicDuration = 10; - if (args.Has("autostart-relicduration")) - relicDuration = args.Get("autostart-relicduration").ToInt(); - Script::SetProperty(rq, settings, "RelicDuration", relicDuration); - - int relicCount = 2; - if (args.Has("autostart-reliccount")) - relicCount = args.Get("autostart-reliccount").ToInt(); - Script::SetProperty(rq, settings, "RelicCount", relicCount); - - // Add player data to map settings. - Script::SetProperty(rq, settings, "PlayerData", playerData); - - // Add map settings to game attributes. - Script::SetProperty(rq, attrs, "settings", settings); - - if (args.Has("autostart-host")) - { - int maxPlayers = 2; - if (args.Has("autostart-host-players")) - maxPlayers = args.Get("autostart-host-players").ToUInt(); - - Script::CreateObject( - rq, - &sessionInitData, - "attribs", attrs, - "playerName", userName, - "port", PS_DEFAULT_PORT, - "maxPlayers", maxPlayers, - "storeReplay", !args.Has("autostart-disable-replay")); + // Pass the default port if undefined, to avoid duplicating it in JS. + if (!Script::HasProperty(rq, cmdLineArgs, "autostart-port")) + Script::SetProperty(rq, cmdLineArgs, "autostart-port", PS_DEFAULT_PORT); JS::RootedValue global(rq.cx, rq.globalValue()); - if (!ScriptFunction::CallVoid(rq, global, "autostartHost", sessionInitData, true)) + if (!ScriptFunction::CallVoid(rq, global, args.Has("autostart-client") ? "autostartClient" : "autostartHost", cmdLineArgs, true)) return false; - // In MP host mode, we need to wait until clients have loaded. bool shouldQuit = false; while (!shouldQuit) { @@ -1194,26 +858,8 @@ } else { - JS::RootedValue localPlayer(rq.cx); - Script::CreateObject( - rq, - &localPlayer, - "player", args.Has("autostart-player") ? args.Get("autostart-player").ToInt() : 1, - "name", userName); - - JS::RootedValue playerAssignments(rq.cx); - Script::CreateObject(rq, &playerAssignments); - Script::SetProperty(rq, playerAssignments, "local", localPlayer); - - Script::CreateObject( - rq, - &sessionInitData, - "attribs", attrs, - "playerAssignments", playerAssignments, - "storeReplay", !args.Has("autostart-disable-replay")); - JS::RootedValue global(rq.cx, rq.globalValue()); - if (!ScriptFunction::CallVoid(rq, global, "autostartHost", sessionInitData, false)) + if (!ScriptFunction::CallVoid(rq, global, "autostartHost", cmdLineArgs, false)) return false; }