1- // Copyright (C) 2005 - 2024 Settlers Freaks (sf-team at siedler25.org)
1+ // Copyright (C) 2005 - 2026 Settlers Freaks (sf-team at siedler25.org)
22//
33// SPDX-License-Identifier: GPL-2.0-or-later
44
77#include " QuickStartGame.h"
88#include " RTTR_Version.h"
99#include " RttrConfig.h"
10+ #include " addons/Addon.h"
11+ #include " addons/AddonBool.h"
12+ #include " addons/AddonList.h"
13+ #include " addons/const_addons.h"
1014#include " ai/random.h"
1115#include " files.h"
1216#include " random/Random.h"
17+ #include " s25util/StringConversion.h"
1318#include " s25util/System.h"
1419
1520#include < boost/filesystem.hpp>
1621#include < boost/nowide/args.hpp>
1722#include < boost/nowide/filesystem.hpp>
1823#include < boost/nowide/iostream.hpp>
1924#include < boost/program_options.hpp>
25+ #include < boost/property_tree/ini_parser.hpp>
26+ #include < iomanip>
2027#if BOOST_VERSION >= 109000
2128# include < optional>
2229using std::optional;
@@ -29,13 +36,47 @@ namespace bnw = boost::nowide;
2936namespace bfs = boost::filesystem;
3037namespace po = boost::program_options;
3138
39+ static void loadAddonsFromIni (GlobalGameSettings& ggs, const bfs::path& iniPath)
40+ {
41+ if (!bfs::exists (iniPath))
42+ throw std::runtime_error (" Settings file not found: " + iniPath.string ());
43+
44+ boost::property_tree::ptree tree;
45+ boost::property_tree::read_ini (iniPath.string (), tree);
46+
47+ const auto addons = tree.get_child_optional (" addons" );
48+ if (!addons)
49+ {
50+ bnw::cout << " Note: no [addons] section in " << iniPath << " , using defaults.\n " ;
51+ return ;
52+ }
53+
54+ unsigned loaded = 0 ;
55+ for (const auto & entry : *addons)
56+ {
57+ try
58+ {
59+ const auto id = static_cast <AddonId>(s25util::fromStringClassic<unsigned >(entry.first ));
60+ const auto v = entry.second .get_value <unsigned >();
61+ ggs.setSelection (id, v);
62+ ++loaded;
63+ } catch (const std::exception&)
64+ {
65+ // Unknown or invalid entry - skip silently
66+ }
67+ }
68+ bnw::cout << " Loaded " << loaded << " addon settings from " << iniPath << ' \n ' ;
69+ }
70+
3271int main (int argc, char ** argv)
3372{
3473 bnw::nowide_filesystem ();
3574 bnw::args _ (argc, argv);
3675
3776 optional<std::string> replay_path;
3877 optional<std::string> savegame_path;
78+ optional<std::string> lua_path;
79+ optional<std::string> settings_path;
3980 unsigned random_init = static_cast <unsigned >(std::chrono::high_resolution_clock::now ().time_since_epoch ().count ());
4081 unsigned random_ai_init = random_init;
4182
@@ -44,20 +85,30 @@ int main(int argc, char** argv)
4485 desc.add_options ()
4586 (" help,h" , " Show help" )
4687 (" map,m" , po::value<std::string>()->required ()," Map to load" )
47- (" ai" , po::value<std::vector<std::string>>()->required ()," AI player(s) to add" )
48- (" objective" , po::value<std::string>()->default_value (" domination" )," domination(default)|conquer" )
88+ (" ai" , po::value<std::vector<std::string>>()->required ()," AI player(s) to add (aijh | dummy)" )
89+ (" objective" , po::value<std::string>()->default_value (" domination" )," domination(default) | conquer" )
90+ (" wares" , po::value<std::string>()->default_value (" normal" )," Starting wares: vlow | low | normal (default) | alot" )
91+ (" settings" , po::value (&settings_path)," INI file with an [addons] section to configure addon settings (optional)" )
4992 (" replay" , po::value (&replay_path)," Filename to write replay to (optional)" )
5093 (" save" , po::value (&savegame_path)," Filename to write savegame to (optional)" )
94+ (" lua" , po::value (&lua_path)," Lua script to execute during the game (optional)" )
5195 (" random_init" , po::value (&random_init)," Seed value for the random number generator (optional)" )
5296 (" random_ai_init" , po::value (&random_ai_init)," Seed value for the AI random number generator (optional)" )
5397 (" maxGF" , po::value<unsigned >()->default_value (std::numeric_limits<unsigned >::max ())," Maximum number of game frames to run (optional)" )
5498 (" version" , " Show version information and exit" )
5599 ;
56100 // clang-format on
57101
102+ const auto printHelp = [&](std::ostream& os) {
103+ os << desc
104+ << " \n Note: path arguments support the <RTTR_USERDATA> placeholder "
105+ " (game data folder: SAVES, REPLAYS, MAPS, PRESETS)."
106+ << std::endl;
107+ };
108+
58109 if (argc == 1 )
59110 {
60- bnw::cerr << desc << std::endl ;
111+ printHelp ( bnw::cerr) ;
61112 return 1 ;
62113 }
63114
@@ -68,7 +119,7 @@ int main(int argc, char** argv)
68119
69120 if (options.count (" help" ))
70121 {
71- bnw::cout << desc << std::endl ;
122+ printHelp ( bnw::cout) ;
72123 return 0 ;
73124 }
74125 if (options.count (" version" ))
@@ -83,7 +134,7 @@ int main(int argc, char** argv)
83134 } catch (const std::exception& e)
84135 {
85136 bnw::cerr << " Error: " << e.what () << std::endl;
86- bnw::cerr << desc << std::endl ;
137+ printHelp ( bnw::cerr) ;
87138 return 1 ;
88139 }
89140
@@ -116,15 +167,54 @@ int main(int argc, char** argv)
116167 return 1 ;
117168 }
118169
119- ggs.objective = GameObjective::TotalDomination;
120- HeadlessGame game (ggs, mapPath, ais);
170+ const auto wares = options[" wares" ].as <std::string>();
171+ if (wares == " vlow" )
172+ ggs.startWares = StartWares::VLow;
173+ else if (wares == " low" )
174+ ggs.startWares = StartWares::Low;
175+ else if (wares == " normal" )
176+ ggs.startWares = StartWares::Normal;
177+ else if (wares == " alot" )
178+ ggs.startWares = StartWares::ALot;
179+ else
180+ {
181+ bnw::cerr << " Unknown wares value: " << wares << std::endl;
182+ return 1 ;
183+ }
184+
185+ if (settings_path)
186+ {
187+ loadAddonsFromIni (ggs, RTTRCONFIG .ExpandPath (*settings_path));
188+
189+ bnw::cout << " settings: " << RTTRCONFIG .ExpandPath (*settings_path) << std::endl;
190+ bnw::cout << " addon selections (non-default only):" << std::endl;
191+ for (unsigned i = 0 ; i < ggs.getNumAddons (); ++i)
192+ {
193+ unsigned status = 0 ;
194+ const Addon* addon = ggs.getAddon (i, status);
195+ if (addon && status != addon->getDefaultStatus ())
196+ {
197+ bnw::cout << " [0x" << std::hex << std::setw (8 ) << std::setfill (' 0' )
198+ << static_cast <unsigned >(addon->getId ()) << std::dec << " ] " << addon->getName () << " = " ;
199+ if (const auto * listAddon = dynamic_cast <const AddonList*>(addon))
200+ bnw::cout << listAddon->getOptionName (status);
201+ else if (dynamic_cast <const AddonBool*>(addon))
202+ bnw::cout << (status ? " True" : " False" );
203+ else
204+ bnw::cout << status;
205+ bnw::cout << std::endl;
206+ }
207+ }
208+ }
209+
210+ HeadlessGame game (ggs, mapPath, ais, lua_path ? RTTRCONFIG .ExpandPath (*lua_path) : bfs::path{});
121211 if (replay_path)
122- game.RecordReplay (*replay_path, random_init);
212+ game.RecordReplay (RTTRCONFIG . ExpandPath ( *replay_path) , random_init);
123213
124214 game.Run (options[" maxGF" ].as <unsigned >());
125215 game.Close ();
126216 if (savegame_path)
127- game.SaveGame (*savegame_path);
217+ game.SaveGame (RTTRCONFIG . ExpandPath ( *savegame_path) );
128218 } catch (const std::exception& e)
129219 {
130220 bnw::cerr << e.what () << std::endl;
0 commit comments