1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Core component of a scenario file */
18
19#include <C4Include.h>
20#include <C4Scenario.h>
21#include <C4InputValidation.h>
22
23#include <C4Random.h>
24#include <C4Group.h>
25#include <C4Game.h>
26#include <C4Wrappers.h>
27
28#include <iterator>
29
30int32_t C4SHead::MainForcedAutoContextMenu{-1};
31int32_t C4SHead::MainForcedControlStyle{-1};
32
33// C4SVal
34
35C4SVal::C4SVal(int32_t std, int32_t rnd, int32_t min, int32_t max)
36 : Std(std), Rnd(rnd), Min(min), Max(max) {}
37
38void C4SVal::Set(int32_t std, int32_t rnd, int32_t min, int32_t max)
39{
40 Std = std; Rnd = rnd; Min = min; Max = max;
41}
42
43int32_t C4SVal::Evaluate()
44{
45 return BoundBy(bval: Std + Random(iRange: 2 * Rnd + 1) - Rnd, lbound: Min, rbound: Max);
46}
47
48void C4SVal::Default()
49{
50 Set();
51}
52
53void C4SVal::CompileFunc(StdCompiler *pComp)
54{
55 pComp->Value(rStruct: mkDefaultAdapt(rValue&: Std, rDefault: 0));
56 if (!pComp->Separator()) return;
57 pComp->Value(rStruct: mkDefaultAdapt(rValue&: Rnd, rDefault: 0));
58 if (!pComp->Separator()) return;
59 pComp->Value(rStruct: mkDefaultAdapt(rValue&: Min, rDefault: 0));
60 if (!pComp->Separator()) return;
61 pComp->Value(rStruct: mkDefaultAdapt(rValue&: Max, rDefault: 100));
62}
63
64// C4Scenario
65
66C4Scenario::C4Scenario()
67{
68 Default();
69}
70
71void C4Scenario::Default()
72{
73 int32_t cnt;
74 Head.Default();
75 Definitions.Default();
76 Game.Default();
77 for (cnt = 0; cnt < C4S_MaxPlayer; cnt++) PlrStart[cnt].Default();
78 Landscape.Default();
79 Animals.Default();
80 Weather.Default();
81 Disasters.Default();
82 Game.Realism.Default();
83 Environment.Default();
84}
85
86bool C4Scenario::Load(C4Group &hGroup, bool fLoadSection)
87{
88 char *pSource;
89 // Load
90 if (!hGroup.LoadEntry(C4CFN_ScenarioCore, lpbpBuf: &pSource, ipSize: nullptr, iAppendZeros: 1)) return false;
91 // Compile
92 if (!Compile(szSource: pSource, fLoadSection)) { delete[] pSource; return false; }
93 delete[] pSource;
94 // Convert
95 Game.ConvertGoals(rRealism&: Game.Realism);
96 // Success
97 return true;
98}
99
100bool C4Scenario::Save(C4Group &hGroup, bool fSaveSection)
101{
102 std::string buf;
103 try
104 {
105 buf = DecompileToBuf<StdCompilerINIWrite>(SrcStruct: mkParAdapt(rObj&: *this, rPar: fSaveSection));
106 }
107 catch (const StdCompiler::Exception &)
108 {
109 return false;
110 }
111
112 StdStrBuf copy{buf.c_str(), buf.size()};
113 if (!hGroup.Add(C4CFN_ScenarioCore, pBuffer&: copy, fChild: false, fHoldBuffer: true))
114 {
115 return false;
116 }
117 return true;
118}
119
120void C4Scenario::CompileFunc(StdCompiler *pComp, bool fSection)
121{
122 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Head, rPar: fSection), szName: "Head"));
123 if (!fSection) pComp->Value(rStruct: mkNamingAdapt(rValue&: Definitions, szName: "Definitions"));
124 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Game, rPar: fSection), szName: "Game"));
125 for (int32_t i = 0; i < C4S_MaxPlayer; i++)
126 pComp->Value(rStruct: mkNamingAdapt(rValue&: PlrStart[i], szName: std::format(fmt: "Player{}", args: i + 1).c_str()));
127
128 const bool newScenario{!Head.C4XVer[0] || CompareVersion(iVer1: Head.C4XVer[0], iVer2: Head.C4XVer[1], iVer3: Head.C4XVer[2], iVer4: Head.C4XVer[3], iVerBuild: Head.C4XVer[4], iRVer1: 4, iRVer2: 6, iRVer3: 5, iRVer4: 0, iRVerBuild: 0) >= 0};
129 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Landscape, rPar: newScenario), szName: "Landscape"));
130 pComp->Value(rStruct: mkNamingAdapt(rValue&: Animals, szName: "Animals"));
131 pComp->Value(rStruct: mkNamingAdapt(rValue&: Weather, szName: "Weather"));
132 pComp->Value(rStruct: mkNamingAdapt(rValue&: Disasters, szName: "Disasters"));
133 pComp->Value(rStruct: mkNamingAdapt(rValue&: Environment, szName: "Environment"));
134}
135
136int32_t C4Scenario::GetMinPlayer()
137{
138 // MinPlayer is specified.
139 if (Head.MinPlayer != 0)
140 return Head.MinPlayer;
141 // Melee? Need at least two.
142 if (Game.IsMelee())
143 return 2;
144 // Otherwise/unknown: need at least one.
145 return 1;
146}
147
148void C4SDefinitions::Default()
149{
150 LocalOnly = AllowUserChange = false;
151 Definitions.clear();
152 SkipDefs.Clear();
153}
154
155const int32_t C4S_MaxPlayerDefault = 12;
156
157C4SHead::C4SHead() : MaxPlayer{C4S_MaxPlayerDefault}, MaxPlayerLeague{C4S_MaxPlayerDefault} {}
158
159void C4SHead::Default()
160{
161 *this = {};
162}
163
164void C4SHead::CompileFunc(StdCompiler *pComp, bool fSection)
165{
166 if (!fSection)
167 {
168 pComp->Value(rStruct: mkNamingAdapt(rValue&: Icon, szName: "Icon", rDefault: 18));
169 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(Title), szName: "Title", rDefault: "Default Title"));
170 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(Loader), szName: "Loader", rDefault: ""));
171 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(Font), szName: "Font", rDefault: ""));
172 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: C4XVer, default_: 0), szName: "Version"));
173 pComp->Value(rStruct: mkNamingAdapt(rValue&: Difficulty, szName: "Difficulty", rDefault: 0));
174 // Ignore EnableUnregisteredAccess
175 int32_t EnableUnregisteredAccess = false;
176 pComp->Value(rStruct: mkNamingAdapt(rValue&: EnableUnregisteredAccess, szName: "Access", rDefault: 0));
177 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxPlayer, szName: "MaxPlayer", rDefault: C4S_MaxPlayerDefault));
178 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxPlayerLeague, szName: "MaxPlayerLeague", rDefault: MaxPlayer));
179 pComp->Value(rStruct: mkNamingAdapt(rValue&: MinPlayer, szName: "MinPlayer", rDefault: 0));
180 pComp->Value(rStruct: mkNamingAdapt(rValue&: SaveGame, szName: "SaveGame", rDefault: 0));
181 pComp->Value(rStruct: mkNamingAdapt(rValue&: Replay, szName: "Replay", rDefault: 0));
182 pComp->Value(rStruct: mkNamingAdapt(rValue&: Film, szName: "Film", rDefault: 0));
183 pComp->Value(rStruct: mkNamingAdapt(rValue&: DisableMouse, szName: "DisableMouse", rDefault: 0));
184 }
185 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoInitialize, szName: "NoInitialize", rDefault: 0));
186 pComp->Value(rStruct: mkNamingAdapt(rValue&: RandomSeed, szName: "RandomSeed", rDefault: 0));
187 pComp->Value(rStruct: mkNamingAdapt(rValue&: ForcedAutoContextMenu, szName: "ForcedAutoContextMenu", rDefault: fSection ? MainForcedAutoContextMenu : -1));
188 pComp->Value(rStruct: mkNamingAdapt(rValue&: ForcedControlStyle, szName: "ForcedAutoStopControl", rDefault: fSection ? MainForcedControlStyle : -1));
189 if (!fSection)
190 {
191 MainForcedAutoContextMenu = ForcedAutoContextMenu;
192 MainForcedControlStyle = ForcedControlStyle;
193 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(Engine), szName: "Engine", rDefault: ""));
194 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(MissionAccess), szName: "MissionAccess", rDefault: ""));
195 pComp->Value(rStruct: mkNamingAdapt(rValue&: NetworkGame, szName: "NetworkGame", rDefault: false));
196 pComp->Value(rStruct: mkNamingAdapt(rValue&: NetworkRuntimeJoin, szName: "NetworkRuntimeJoin", rDefault: false));
197 pComp->Value(rStruct: mkNamingAdapt(rValue&: ForcedGfxMode, szName: "ForcedGfxMode", rDefault: 0));
198 pComp->Value(rStruct: mkNamingAdapt(rValue&: ForcedFairCrew, szName: "ForcedNoCrew", rDefault: 0));
199 pComp->Value(rStruct: mkNamingAdapt(rValue&: FairCrewStrength, szName: "DefCrewStrength", rDefault: 0));
200 pComp->Value(rStruct: mkNamingAdapt(rValue: mkStrValAdapt(rValue: mkParAdapt(rObj&: Origin, rPar: StdCompiler::RCT_All), eValType: C4InVal::VAL_SubPathFilename), szName: "Origin", rDefault: StdStrBuf()));
201 // windows needs backslashes in Origin; other systems use forward slashes
202 if (pComp->isCompiler()) Origin.ReplaceChar(AltDirectorySeparator, DirectorySeparator);
203 }
204}
205
206void C4SGame::Default()
207{
208 Elimination = C4S_EliminateCrew;
209 ValueGain = 0;
210 CreateObjects.Clear();
211 ClearObjects.Clear();
212 ClearMaterial.Clear();
213 Mode = C4S_Cooperative;
214 CooperativeGoal = C4S_NoGoal;
215 EnableRemoveFlag = false;
216 Goals.Clear();
217 Rules.Clear();
218 FoWColor = 0;
219}
220
221void C4SGame::CompileFunc(StdCompiler *pComp, bool fSection)
222{
223 pComp->Value(rStruct: mkNamingAdapt(rValue&: Mode, szName: "Mode", rDefault: C4S_Cooperative));
224 pComp->Value(rStruct: mkNamingAdapt(rValue&: Elimination, szName: "Elimination", rDefault: C4S_EliminateCrew));
225 pComp->Value(rStruct: mkNamingAdapt(rValue&: CooperativeGoal, szName: "CooperativeGoal", rDefault: C4S_NoGoal));
226 pComp->Value(rStruct: mkNamingAdapt(rValue&: CreateObjects, szName: "CreateObjects", rDefault: C4IDList()));
227 pComp->Value(rStruct: mkNamingAdapt(rValue&: ClearObjects, szName: "ClearObjects", rDefault: C4IDList()));
228 pComp->Value(rStruct: mkNamingAdapt(rValue&: ClearMaterial, szName: "ClearMaterials", rDefault: C4NameList()));
229 pComp->Value(rStruct: mkNamingAdapt(rValue&: ValueGain, szName: "ValueGain", rDefault: 0));
230 pComp->Value(rStruct: mkNamingAdapt(rValue&: EnableRemoveFlag, szName: "EnableRemoveFlag", rDefault: false));
231 pComp->Value(rStruct: mkNamingAdapt(rValue&: Realism.ConstructionNeedsMaterial, szName: "StructNeedMaterial", rDefault: false));
232 pComp->Value(rStruct: mkNamingAdapt(rValue&: Realism.StructuresNeedEnergy, szName: "StructNeedEnergy", rDefault: true));
233 if (!fSection)
234 {
235 pComp->Value(rStruct: mkNamingAdapt(rValue&: Realism.ValueOverloads, szName: "ValueOverloads", rDefault: C4IDList()));
236 }
237 pComp->Value(rStruct: mkNamingAdapt(rValue: mkRuntimeValueAdapt(rValue&: Realism.LandscapePushPull), szName: "LandscapePushPull", rDefault: 0));
238 pComp->Value(rStruct: mkNamingAdapt(rValue: mkRuntimeValueAdapt(rValue&: Realism.LandscapeInsertThrust), szName: "LandscapeInsertThrust", rDefault: 1));
239
240 const StdBitfieldEntry<int32_t> BaseFunctionalities[] =
241 {
242 { .Name: "BASEFUNC_AutoSellContents", .Val: BASEFUNC_AutoSellContents },
243 { .Name: "BASEFUNC_RegenerateEnergy", .Val: BASEFUNC_RegenerateEnergy },
244 { .Name: "BASEFUNC_Buy", .Val: BASEFUNC_Buy },
245 { .Name: "BASEFUNC_Sell", .Val: BASEFUNC_Sell },
246 { .Name: "BASEFUNC_RejectEntrance", .Val: BASEFUNC_RejectEntrance },
247 { .Name: "BASEFUNC_Extinguish", .Val: BASEFUNC_Extinguish },
248 { .Name: "BASEFUNC_Default", .Val: BASEFUNC_Default },
249 { .Name: nullptr, .Val: 0 }
250 };
251
252 pComp->Value(rStruct: mkNamingAdapt(rValue: mkRuntimeValueAdapt(rValue: mkBitfieldAdapt<int32_t>(rVal&: Realism.BaseFunctionality, pNames: BaseFunctionalities)), szName: "BaseFunctionality", rDefault: BASEFUNC_Default));
253 pComp->Value(rStruct: mkNamingAdapt(rValue: mkRuntimeValueAdapt(rValue&: Realism.BaseRegenerateEnergyPrice), szName: "BaseRegenerateEnergyPrice", rDefault: BASE_RegenerateEnergyPrice));
254 pComp->Value(rStruct: mkNamingAdapt(rValue&: Goals, szName: "Goals", rDefault: C4IDList()));
255 pComp->Value(rStruct: mkNamingAdapt(rValue&: Rules, szName: "Rules", rDefault: C4IDList()));
256 pComp->Value(rStruct: mkNamingAdapt(rValue&: FoWColor, szName: "FoWColor", rDefault: 0u));
257}
258
259void C4SPlrStart::Default()
260{
261 NativeCrew = C4ID_None;
262 Crew.Set(std: 1, rnd: 0, min: 1, max: 10);
263 Wealth.Set(std: 0, rnd: 0, min: 0, max: 250);
264 Position[0] = Position[1] = -1;
265 EnforcePosition = 0;
266 ReadyCrew.Clear();
267 ReadyBase.Clear();
268 ReadyVehic.Clear();
269 ReadyMaterial.Clear();
270 BuildKnowledge.Clear();
271 HomeBaseMaterial.Clear();
272 HomeBaseProduction.Clear();
273 Magic.Clear();
274}
275
276void C4SPlrStart::CompileFunc(StdCompiler *pComp)
277{
278 pComp->Value(rStruct: mkNamingAdapt(rValue: mkC4IDAdapt(rValue&: NativeCrew), szName: "StandardCrew", rDefault: C4ID_None));
279 pComp->Value(rStruct: mkNamingAdapt(rValue&: Crew, szName: "Clonks", rDefault: C4SVal(1, 0, 1, 10), fPrefillDefault: true));
280 pComp->Value(rStruct: mkNamingAdapt(rValue&: Wealth, szName: "Wealth", rDefault: C4SVal(0, 0, 0, 250), fPrefillDefault: true));
281 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: Position, default_: -1), szName: "Position"));
282 pComp->Value(rStruct: mkNamingAdapt(rValue&: EnforcePosition, szName: "EnforcePosition", rDefault: 0));
283 pComp->Value(rStruct: mkNamingAdapt(rValue&: ReadyCrew, szName: "Crew", rDefault: C4IDList()));
284 pComp->Value(rStruct: mkNamingAdapt(rValue&: ReadyBase, szName: "Buildings", rDefault: C4IDList()));
285 pComp->Value(rStruct: mkNamingAdapt(rValue&: ReadyVehic, szName: "Vehicles", rDefault: C4IDList()));
286 pComp->Value(rStruct: mkNamingAdapt(rValue&: ReadyMaterial, szName: "Material", rDefault: C4IDList()));
287 pComp->Value(rStruct: mkNamingAdapt(rValue&: BuildKnowledge, szName: "Knowledge", rDefault: C4IDList()));
288 pComp->Value(rStruct: mkNamingAdapt(rValue&: HomeBaseMaterial, szName: "HomeBaseMaterial", rDefault: C4IDList()));
289 pComp->Value(rStruct: mkNamingAdapt(rValue&: HomeBaseProduction, szName: "HomeBaseProduction", rDefault: C4IDList()));
290 pComp->Value(rStruct: mkNamingAdapt(rValue&: Magic, szName: "Magic", rDefault: C4IDList()));
291}
292
293void C4SLandscape::Default()
294{
295 BottomOpen = false; TopOpen = true;
296 LeftOpen = 0; RightOpen = 0;
297 AutoScanSideOpen = true;
298 SkyDef[0] = 0;
299 NoSky = false;
300 for (int32_t cnt = 0; cnt < 6; cnt++) SkyDefFade[cnt] = 0;
301 VegLevel.Set(std: 50, rnd: 30, min: 0, max: 100);
302 Vegetation.Clear();
303 InEarthLevel.Set(std: 50, rnd: 0, min: 0, max: 100);
304 InEarth.Clear();
305 MapWdt.Set(std: 100, rnd: 0, min: 64, max: 250);
306 MapHgt.Set(std: 50, rnd: 0, min: 40, max: 250);
307 MapZoom.Set(std: 10, rnd: 0, min: 5, max: 15);
308 Amplitude.Set(std: 0, rnd: 0);
309 Phase.Set(std: 50);
310 Period.Set(std: 15);
311 Random.Set(std: 0);
312 LiquidLevel.Default();
313 MapPlayerExtend = false;
314 Layers.Clear();
315 SCopy(szSource: "Earth", sTarget: Material, iMaxL: C4M_MaxName);
316 SCopy(szSource: "Water", sTarget: Liquid, iMaxL: C4M_MaxName);
317 ExactLandscape = false;
318 Gravity.Set(std: 100, rnd: 0, min: 10, max: 200);
319 NoScan = false;
320 KeepMapCreator = false;
321 SkyScrollMode = 0;
322 NewStyleLandscape = 0;
323 FoWRes = CClrModAddMap::iDefResolutionX;
324 ShadeMaterials = true;
325}
326
327void C4SLandscape::GetMapSize(int32_t &rWdt, int32_t &rHgt, int32_t iPlayerNum)
328{
329 rWdt = MapWdt.Evaluate();
330 rHgt = MapHgt.Evaluate();
331 iPlayerNum = std::max<int32_t>(a: iPlayerNum, b: 1);
332 if (MapPlayerExtend)
333 rWdt = (std::min)(a: rWdt * (std::min)(a: iPlayerNum, b: C4S_MaxMapPlayerExtend), b: MapWdt.Max);
334}
335
336void C4SLandscape::CompileFunc(StdCompiler *pComp, bool newScenario)
337{
338 pComp->Value(rStruct: mkNamingAdapt(rValue&: ExactLandscape, szName: "ExactLandscape", rDefault: false));
339 pComp->Value(rStruct: mkNamingAdapt(rValue&: Vegetation, szName: "Vegetation", rDefault: C4IDList()));
340 pComp->Value(rStruct: mkNamingAdapt(rValue&: VegLevel, szName: "VegetationLevel", rDefault: C4SVal(50, 30, 0, 100), fPrefillDefault: true));
341 pComp->Value(rStruct: mkNamingAdapt(rValue&: InEarth, szName: "InEarth", rDefault: C4IDList()));
342 pComp->Value(rStruct: mkNamingAdapt(rValue&: InEarthLevel, szName: "InEarthLevel", rDefault: C4SVal(50, 0, 0, 100), fPrefillDefault: true));
343 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(SkyDef), szName: "Sky", rDefault: ""));
344 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: SkyDefFade, default_: 0), szName: "SkyFade"));
345 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoSky, szName: "NoSky", rDefault: false));
346 pComp->Value(rStruct: mkNamingAdapt(rValue&: BottomOpen, szName: "BottomOpen", rDefault: false));
347 pComp->Value(rStruct: mkNamingAdapt(rValue&: TopOpen, szName: "TopOpen", rDefault: true));
348 pComp->Value(rStruct: mkNamingAdapt(rValue&: LeftOpen, szName: "LeftOpen", rDefault: 0));
349 pComp->Value(rStruct: mkNamingAdapt(rValue&: RightOpen, szName: "RightOpen", rDefault: 0));
350 pComp->Value(rStruct: mkNamingAdapt(rValue&: AutoScanSideOpen, szName: "AutoScanSideOpen", rDefault: true));
351 pComp->Value(rStruct: mkNamingAdapt(rValue&: MapWdt, szName: "MapWidth", rDefault: C4SVal(100, 0, 64, 250), fPrefillDefault: true));
352 pComp->Value(rStruct: mkNamingAdapt(rValue&: MapHgt, szName: "MapHeight", rDefault: C4SVal(50, 0, 40, 250), fPrefillDefault: true));
353 pComp->Value(rStruct: mkNamingAdapt(rValue&: MapZoom, szName: "MapZoom", rDefault: C4SVal(10, 0, 5, 15), fPrefillDefault: true));
354 pComp->Value(rStruct: mkNamingAdapt(rValue&: Amplitude, szName: "Amplitude", rDefault: C4SVal(0)));
355 pComp->Value(rStruct: mkNamingAdapt(rValue&: Phase, szName: "Phase", rDefault: C4SVal(50)));
356 pComp->Value(rStruct: mkNamingAdapt(rValue&: Period, szName: "Period", rDefault: C4SVal(15)));
357 pComp->Value(rStruct: mkNamingAdapt(rValue&: Random, szName: "Random", rDefault: C4SVal(0)));
358 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(Material), szName: "Material", rDefault: "Earth"));
359 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(Liquid), szName: "Liquid", rDefault: "Water"));
360 pComp->Value(rStruct: mkNamingAdapt(rValue&: LiquidLevel, szName: "LiquidLevel", rDefault: C4SVal()));
361 pComp->Value(rStruct: mkNamingAdapt(rValue&: MapPlayerExtend, szName: "MapPlayerExtend", rDefault: false));
362 pComp->Value(rStruct: mkNamingAdapt(rValue&: Layers, szName: "Layers", rDefault: C4NameList()));
363 pComp->Value(rStruct: mkNamingAdapt(rValue&: Gravity, szName: "Gravity", rDefault: C4SVal(100, 0, 10, 200), fPrefillDefault: true));
364 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoScan, szName: "NoScan", rDefault: false));
365 pComp->Value(rStruct: mkNamingAdapt(rValue&: KeepMapCreator, szName: "KeepMapCreator", rDefault: false));
366 pComp->Value(rStruct: mkNamingAdapt(rValue&: SkyScrollMode, szName: "SkyScrollMode", rDefault: 0));
367 pComp->Value(rStruct: mkNamingAdapt(rValue&: NewStyleLandscape, szName: "NewStyleLandscape", rDefault: 0));
368 pComp->Value(rStruct: mkNamingAdapt(rValue&: FoWRes, szName: "FoWRes", rDefault: static_cast<int32_t>(CClrModAddMap::iDefResolutionX)));
369 pComp->Value(rStruct: mkNamingAdapt(rValue&: ShadeMaterials, szName: "ShadeMaterials", rDefault: newScenario));
370}
371
372void C4SWeather::Default()
373{
374 Climate.Set(std: 50, rnd: 10);
375 StartSeason.Set(std: 50, rnd: 50);
376 YearSpeed.Set(std: 50);
377 Rain.Default(); Lightning.Default(); Wind.Set(std: 0, rnd: 70, min: -100, max: +100);
378 SCopy(szSource: "Water", sTarget: Precipitation, iMaxL: C4M_MaxName);
379 NoGamma = true;
380}
381
382void C4SWeather::CompileFunc(StdCompiler *pComp)
383{
384 pComp->Value(rStruct: mkNamingAdapt(rValue&: Climate, szName: "Climate", rDefault: C4SVal(50, 10), fPrefillDefault: true));
385 pComp->Value(rStruct: mkNamingAdapt(rValue&: StartSeason, szName: "StartSeason", rDefault: C4SVal(50, 50), fPrefillDefault: true));
386 pComp->Value(rStruct: mkNamingAdapt(rValue&: YearSpeed, szName: "YearSpeed", rDefault: C4SVal(50)));
387 pComp->Value(rStruct: mkNamingAdapt(rValue&: Rain, szName: "Rain", rDefault: C4SVal()));
388 pComp->Value(rStruct: mkNamingAdapt(rValue&: Wind, szName: "Wind", rDefault: C4SVal(0, 70, -100, +100), fPrefillDefault: true));
389 pComp->Value(rStruct: mkNamingAdapt(rValue&: Lightning, szName: "Lightning", rDefault: C4SVal()));
390 pComp->Value(rStruct: mkNamingAdapt(mkStringAdaptMA(Precipitation), szName: "Precipitation", rDefault: "Water"));
391 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoGamma, szName: "NoGamma", rDefault: true));
392}
393
394void C4SAnimals::Default()
395{
396 FreeLife.Clear();
397 EarthNest.Clear();
398}
399
400void C4SAnimals::CompileFunc(StdCompiler *pComp)
401{
402 pComp->Value(rStruct: mkNamingAdapt(rValue&: FreeLife, szName: "Animal", rDefault: C4IDList()));
403 pComp->Value(rStruct: mkNamingAdapt(rValue&: EarthNest, szName: "Nest", rDefault: C4IDList()));
404}
405
406void C4SEnvironment::Default()
407{
408 Objects.Clear();
409}
410
411void C4SEnvironment::CompileFunc(StdCompiler *pComp)
412{
413 pComp->Value(rStruct: mkNamingAdapt(rValue&: Objects, szName: "Objects", rDefault: C4IDList()));
414}
415
416void C4SRealism::Default()
417{
418 ConstructionNeedsMaterial = false;
419 StructuresNeedEnergy = true;
420 LandscapePushPull = 0;
421 LandscapeInsertThrust = 0;
422 ValueOverloads.Clear();
423 BaseFunctionality = BASEFUNC_Default;
424 BaseRegenerateEnergyPrice = BASE_RegenerateEnergyPrice;
425}
426
427void C4SDisasters::Default()
428{
429 Volcano.Default();
430 Earthquake.Default();
431 Meteorite.Default();
432}
433
434void C4SDisasters::CompileFunc(StdCompiler *pComp)
435{
436 pComp->Value(rStruct: mkNamingAdapt(rValue&: Meteorite, szName: "Meteorite", rDefault: C4SVal()));
437 pComp->Value(rStruct: mkNamingAdapt(rValue&: Volcano, szName: "Volcano", rDefault: C4SVal()));
438 pComp->Value(rStruct: mkNamingAdapt(rValue&: Earthquake, szName: "Earthquake", rDefault: C4SVal()));
439}
440
441bool C4Scenario::Compile(const char *szSource, bool fLoadSection)
442{
443 if (!fLoadSection) Default();
444 return CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkParAdapt(rObj&: *this, rPar: fLoadSection), SrcBuf: StdStrBuf::MakeRef(str: szSource), C4CFN_ScenarioCore);
445}
446
447void C4Scenario::Clear() {}
448
449void C4Scenario::SetExactLandscape()
450{
451 if (Landscape.ExactLandscape) return;
452 // Set landscape
453 Landscape.ExactLandscape = true;
454}
455
456std::vector<std::string> C4SDefinitions::GetModules() const
457{
458 return LocalOnly ? std::vector<std::string>() : Definitions;
459}
460
461void C4SDefinitions::SetModules(const std::vector<std::string> &modules, const std::string &relativeToPath, const std::string &relativeToPath2)
462{
463 Definitions.clear();
464 std::transform(first: modules.begin(), last: modules.end(), result: std::inserter(x&: Definitions, i: Definitions.begin()), unary_op: [relativeToPath, relativeToPath2](std::string def)
465 {
466 if (relativeToPath.size() && SEqualNoCase(szStr1: def.c_str(), szStr2: relativeToPath.c_str(), iLen: static_cast<int32_t>(relativeToPath.length())))
467 {
468 def = def.substr(pos: relativeToPath.length());
469 }
470 if (relativeToPath2.size() && SEqualNoCase(szStr1: def.c_str(), szStr2: relativeToPath2.c_str(), iLen: static_cast<int32_t>(relativeToPath2.length())))
471 {
472 def = def.substr(pos: relativeToPath2.length());
473 }
474 return def;
475 });
476
477 LocalOnly = Definitions.empty();
478}
479
480void C4SDefinitions::CompileFunc(StdCompiler *pComp)
481{
482 pComp->Value(rStruct: mkNamingAdapt(rValue&: LocalOnly, szName: "LocalOnly", rDefault: false));
483 pComp->Value(rStruct: mkNamingAdapt(rValue&: AllowUserChange, szName: "AllowUserChange", rDefault: false));
484 pComp->Value(rStruct: mkNamingAdapt(rValue: mkSTLContainerAdapt(rTarget&: Definitions), szName: "Definitions", rDefault: decltype(Definitions)()));
485
486 if (Definitions.empty() && pComp->isCompiler())
487 {
488 for (size_t i = 0; i < C4S_MaxDefinitions; ++i)
489 {
490 std::string def;
491 pComp->Value(rStruct: mkNamingAdapt(rValue: mkStringAdaptA(string&: def), szName: std::format(fmt: "Definition{}", args: i + 1).c_str(), rDefault: ""));
492
493 if (!def.empty())
494 {
495 Definitions.push_back(x: def);
496 }
497 }
498 }
499
500 pComp->Value(rStruct: mkNamingAdapt(rValue&: SkipDefs, szName: "SkipDefs", rDefault: C4IDList()));
501}
502
503void C4SGame::ConvertGoals(C4SRealism &rRealism)
504{
505 // Convert mode to goals
506 switch (Mode)
507 {
508 case C4S_Melee: Goals.SetIDCount(id: C4Id(str: "MELE"), count: 1, addNewID: true); ClearOldGoals(); break;
509 case C4S_MeleeTeamwork: Goals.SetIDCount(id: C4Id(str: "MELE"), count: 1, addNewID: true); ClearOldGoals(); break;
510 }
511 Mode = 0;
512
513 // Convert goals (master selection)
514 switch (CooperativeGoal)
515 {
516 case C4S_Goldmine: Goals.SetIDCount(id: C4Id(str: "GLDM"), count: 1, addNewID: true); ClearOldGoals(); break;
517 case C4S_Monsterkill: Goals.SetIDCount(id: C4Id(str: "MNTK"), count: 1, addNewID: true); ClearOldGoals(); break;
518 case C4S_ValueGain: Goals.SetIDCount(id: C4Id(str: "VALG"), count: (std::max)(a: ValueGain / 100, b: 1), addNewID: true); ClearOldGoals(); break;
519 }
520 CooperativeGoal = 0;
521 // CreateObjects,ClearObjects,ClearMaterials are still valid but invisible
522
523 // Convert realism to rules
524 if (rRealism.ConstructionNeedsMaterial) Rules.SetIDCount(id: C4Id(str: "CNMT"), count: 1, addNewID: true); rRealism.ConstructionNeedsMaterial = false;
525 if (rRealism.StructuresNeedEnergy) Rules.SetIDCount(id: C4Id(str: "ENRG"), count: 1, addNewID: true); rRealism.StructuresNeedEnergy = false;
526
527 // Convert rules
528 if (EnableRemoveFlag) Rules.SetIDCount(id: C4Id(str: "FGRV"), count: 1, addNewID: true); EnableRemoveFlag = false;
529
530 // Convert eliminiation to rules
531 switch (Elimination)
532 {
533 case C4S_KillTheCaptain: Rules.SetIDCount(id: C4Id(str: "KILC"), count: 1, addNewID: true); break;
534 case C4S_CaptureTheFlag: Rules.SetIDCount(id: C4Id(str: "CTFL"), count: 1, addNewID: true); break;
535 }
536 Elimination = 1; // unconvertible default crew elimination
537
538 // CaptureTheFlag requires FlagRemoveable
539 if (Rules.GetIDCount(id: C4Id(str: "CTFL"))) Rules.SetIDCount(id: C4Id(str: "FGRV"), count: 1, addNewID: true);
540}
541
542void C4SGame::ClearOldGoals()
543{
544 CreateObjects.Clear(); ClearObjects.Clear(); ClearMaterial.Clear();
545 ValueGain = 0;
546}
547
548bool C4SGame::IsMelee()
549{
550 return (Goals.GetIDCount(id: C4Id(str: "MELE")) || Goals.GetIDCount(id: C4Id(str: "MEL2")));
551}
552
553// scenario sections
554
555const char *C4ScenSect_Main = "main";
556
557C4ScenarioSection::C4ScenarioSection(char *szName)
558 : Name{(szName && !SEqualNoCase(szStr1: szName, szStr2: C4ScenSect_Main) && *szName)
559 ? szName : C4ScenSect_Main}
560{
561 // zero fields
562 fModified = false;
563 // link into main list
564 pNext = Game.pScenarioSections;
565 Game.pScenarioSections = this;
566}
567
568C4ScenarioSection::~C4ScenarioSection()
569{
570 // del following scenario sections
571 while (pNext)
572 {
573 C4ScenarioSection *pDel = pNext;
574 pNext = pNext->pNext;
575 pDel->pNext = nullptr;
576 delete pDel;
577 }
578 // del temp file
579 if (!TempFilename.empty()) EraseItem(szItemName: TempFilename.c_str());
580}
581
582bool C4ScenarioSection::ScenarioLoad(char *szFilename)
583{
584 // safety
585 if (!Filename.empty()) return false;
586 // store name
587 Filename = szFilename;
588 // extract if it's not an open folder
589 if (Game.ScenarioFile.IsPacked()) if (!EnsureTempStore(fExtractLandscape: true, fExtractObjects: true)) return false;
590 // donce, success
591 return true;
592}
593
594C4Group *C4ScenarioSection::GetGroupfile(C4Group &rGrp)
595{
596 // check temp filename
597 if (!TempFilename.empty()) if (rGrp.Open(szGroupName: TempFilename.c_str())) return &rGrp; else return nullptr;
598 // check filename within scenario
599 if (!Filename.empty()) if (rGrp.OpenAsChild(pMother: &Game.ScenarioFile, szEntryName: Filename.c_str())) return &rGrp; else return nullptr;
600 // unmodified main section: return main group
601 if (SEqualNoCase(szStr1: Name.c_str(), szStr2: C4ScenSect_Main)) return &Game.ScenarioFile;
602 // failure
603 return nullptr;
604}
605
606bool C4ScenarioSection::EnsureTempStore(bool fExtractLandscape, bool fExtractObjects)
607{
608 // if it's temp store already, don't do anything
609 if (!TempFilename.empty()) return true;
610 // make temp filename
611 StdStrBuf tmp{Config.AtTempPath(szFilename: !Filename.empty() ? Filename.c_str() : Name.c_str())};
612 MakeTempFilename(sFileName: &tmp);
613 // main section: extract section files from main scenario group (create group as open dir)
614 if (Filename.empty())
615 {
616 if (!MakeDirectory(pathname: tmp.getData(), nullptr)) return false;
617 C4Group hGroup;
618 if (!hGroup.Open(szGroupName: tmp.getData(), fCreate: true)) { EraseItem(szItemName: tmp.getData()); return false; }
619 // extract all desired section files
620 Game.ScenarioFile.ResetSearch();
621 char fn[_MAX_FNAME + 1]; *fn = 0;
622 while (Game.ScenarioFile.FindNextEntry(C4FLS_Section, sFileName: fn))
623 if (fExtractLandscape || !WildcardMatch(C4FLS_SectionLandscape, szFName2: fn))
624 if (fExtractObjects || !WildcardMatch(C4FLS_SectionObjects, szFName2: fn))
625 Game.ScenarioFile.ExtractEntry(szFilename: fn, szExtractTo: tmp.getData());
626 hGroup.Close();
627 }
628 else
629 {
630 // subsection: simply extract section from main group
631 if (!Game.ScenarioFile.ExtractEntry(szFilename: Filename.c_str(), szExtractTo: tmp.getData())) return false;
632 // delete undesired landscape/object files
633 if (!fExtractLandscape || !fExtractObjects)
634 {
635 C4Group hGroup;
636 if (hGroup.Open(szGroupName: Filename.c_str()))
637 {
638 if (!fExtractLandscape) hGroup.Delete(C4FLS_SectionLandscape);
639 if (!fExtractObjects) hGroup.Delete(C4FLS_SectionObjects);
640 }
641 }
642 }
643 // copy temp filename
644 TempFilename = tmp.getData();
645 // done, success
646 return true;
647}
648
649const char *C4ScenarioSection::GetName() const
650{
651 return Name.c_str();
652}
653
654const char *C4ScenarioSection::GetTempFilename() const
655{
656 return TempFilename.c_str();
657}
658