1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 1998-2000, Matthes Bender
5 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18 /* Core component of a scenario file */
19
20 #include "C4Include.h"
21 #include "landscape/C4Scenario.h"
22
23 #include "c4group/C4Components.h"
24 #include "c4group/C4Group.h"
25 #include "lib/C4InputValidation.h"
26 #include "lib/C4Random.h"
27 #include "lib/StdColors.h"
28
29 //==================================== C4SVal ==============================================
30
C4SVal(int32_t std,int32_t rnd,int32_t min,int32_t max)31 C4SVal::C4SVal(int32_t std, int32_t rnd, int32_t min, int32_t max)
32 : Std(std), Rnd(rnd), Min(min), Max(max)
33 {
34 }
35
Set(int32_t std,int32_t rnd,int32_t min,int32_t max)36 void C4SVal::Set(int32_t std, int32_t rnd, int32_t min, int32_t max)
37 {
38 Std=std; Rnd=rnd; Min=min; Max=max;
39 }
40
SetConstant(int32_t val)41 void C4SVal::SetConstant(int32_t val)
42 {
43 // Set to constant value and ensure limits allow it
44 Std = val;
45 Rnd = 0;
46 Min = std::min<int32_t>(Min, val);
47 Max = std::max<int32_t>(Max, val);
48 }
49
Evaluate()50 int32_t C4SVal::Evaluate()
51 {
52 return Clamp<int32_t>(Std+Random(2*Rnd+1)-Rnd,Min,Max);
53 }
54
Default()55 void C4SVal::Default()
56 {
57 Set();
58 }
59
CompileFunc(StdCompiler * pComp)60 void C4SVal::CompileFunc(StdCompiler *pComp)
61 {
62 pComp->Value(mkDefaultAdapt(Std, 0));
63 if (!pComp->Separator()) return;
64 pComp->Value(mkDefaultAdapt(Rnd, 0));
65 if (!pComp->Separator()) return;
66 pComp->Value(mkDefaultAdapt(Min, 0));
67 if (!pComp->Separator()) return;
68 pComp->Value(mkDefaultAdapt(Max, 100));
69 }
70
71 //================================ C4Scenario ==========================================
72
C4Scenario()73 C4Scenario::C4Scenario()
74 {
75 Default();
76 }
77
Default()78 void C4Scenario::Default()
79 {
80 int32_t cnt;
81 Head.Default();
82 Definitions.Default();
83 Game.Default();
84 for (cnt=0; cnt<C4S_MaxPlayer; cnt++) PlrStart[cnt].Default();
85 Landscape.Default();
86 Animals.Default();
87 Weather.Default();
88 Game.Realism.Default();
89 Environment.Default();
90 }
91
Load(C4Group & hGroup,bool fLoadSection,bool suppress_errors)92 bool C4Scenario::Load(C4Group &hGroup, bool fLoadSection, bool suppress_errors)
93 {
94 StdStrBuf Buf;
95 if (!hGroup.LoadEntryString(C4CFN_ScenarioCore,&Buf)) return false;
96 if (!fLoadSection) Default();
97 if (suppress_errors)
98 {
99 if (!CompileFromBuf_Log<StdCompilerINIRead>(mkParAdapt(*this, fLoadSection), Buf, C4CFN_ScenarioCore))
100 {
101 return false;
102 }
103 }
104 else
105 {
106 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(mkParAdapt(*this, fLoadSection), Buf, C4CFN_ScenarioCore))
107 {
108 return false;
109 }
110 }
111 return true;
112 }
113
Save(C4Group & hGroup,bool fSaveSection)114 bool C4Scenario::Save(C4Group &hGroup, bool fSaveSection)
115 {
116 StdStrBuf Buf;
117 try
118 {
119 Buf.Take(DecompileToBuf<StdCompilerINIWrite>(mkParAdapt(*this, fSaveSection)));
120 }
121 catch (StdCompiler::Exception *)
122 { return false; }
123 if (!hGroup.Add(C4CFN_ScenarioCore,Buf,false,true))
124 { return false; }
125 return true;
126 }
127
CompileFunc(StdCompiler * pComp,bool fSection)128 void C4Scenario::CompileFunc(StdCompiler *pComp, bool fSection)
129 {
130 pComp->Value(mkNamingAdapt(mkParAdapt(Head, fSection), "Head"));
131 if (!fSection) pComp->Value(mkNamingAdapt(Definitions, "Definitions"));
132 pComp->Value(mkNamingAdapt(mkParAdapt(Game, fSection), "Game"));
133 for (int32_t i = 0; i < C4S_MaxPlayer; i++)
134 pComp->Value(mkNamingAdapt(PlrStart[i], FormatString("Player%d", i+1).getData()));
135 pComp->Value(mkNamingAdapt(Landscape, "Landscape"));
136 pComp->Value(mkNamingAdapt(Animals, "Animals"));
137 pComp->Value(mkNamingAdapt(Weather, "Weather"));
138 pComp->Value(mkNamingAdapt(Environment, "Environment"));
139 }
140
GetMinPlayer()141 int32_t C4Scenario::GetMinPlayer()
142 {
143 // MinPlayer is specified.
144 if (Head.MinPlayer != 0)
145 return Head.MinPlayer;
146 // Otherwise/unknown: need at least one.
147 return 1;
148 }
149
Default()150 void C4SDefinitions::Default()
151 {
152 LocalOnly=AllowUserChange=false;
153 ZeroMem(Definition,sizeof (Definition));
154 SkipDefs.Default();
155 }
156
157 const int32_t C4S_MaxPlayerDefault = 12;
158
Default()159 void C4SHead::Default()
160 {
161 Origin.Clear();
162 Icon=18;
163 Title.clear();
164 Loader.clear();
165 Font.clear();
166 Engine.clear();
167 MissionAccess.clear();
168 Secret = false;
169 C4XVer[0] = C4XVer[1] = 0;
170 Difficulty = RandomSeed = 0;
171 SaveGame = Replay = NoInitialize = false;
172 Film = 0;
173 NetworkGame = NetworkRuntimeJoin = false;
174
175 MaxPlayer=MaxPlayerLeague=C4S_MaxPlayerDefault;
176 MinPlayer=0; // auto-determine by mode
177 Title = "Default Title";
178 }
179
CompileFunc(StdCompiler * pComp,bool fSection)180 void C4SHead::CompileFunc(StdCompiler *pComp, bool fSection)
181 {
182 if (!fSection)
183 {
184 pComp->Value(mkNamingAdapt(Icon, "Icon", 18));
185 pComp->Value(mkNamingAdapt(mkStringAdaptA(Title), "Title", "Default Title"));
186 pComp->Value(mkNamingAdapt(mkStringAdaptA(Loader), "Loader", ""));
187 pComp->Value(mkNamingAdapt(mkStringAdaptA(Font), "Font", ""));
188 pComp->Value(mkNamingAdapt(mkArrayAdaptDM(C4XVer,0), "Version" ));
189 pComp->Value(mkNamingAdapt(Difficulty, "Difficulty", 0));
190 pComp->Value(mkNamingAdapt(MaxPlayer, "MaxPlayer", C4S_MaxPlayerDefault));
191 pComp->Value(mkNamingAdapt(MaxPlayerLeague, "MaxPlayerLeague", MaxPlayer));
192 pComp->Value(mkNamingAdapt(MinPlayer, "MinPlayer", 0));
193 pComp->Value(mkNamingAdapt(SaveGame, "SaveGame", false));
194 pComp->Value(mkNamingAdapt(Replay, "Replay", false));
195 pComp->Value(mkNamingAdapt(Film, "Film", 0));
196 }
197 pComp->Value(mkNamingAdapt(NoInitialize, "NoInitialize", false));
198 pComp->Value(mkNamingAdapt(RandomSeed, "RandomSeed", 0));
199 if (!fSection)
200 {
201 pComp->Value(mkNamingAdapt(mkStringAdaptA(Engine), "Engine", ""));
202 pComp->Value(mkNamingAdapt(mkStringAdaptA(MissionAccess),"MissionAccess", ""));
203 pComp->Value(mkNamingAdapt(Secret, "Secret", false));
204 pComp->Value(mkNamingAdapt(NetworkGame, "NetworkGame", false));
205 pComp->Value(mkNamingAdapt(NetworkRuntimeJoin, "NetworkRuntimeJoin", false));
206 pComp->Value(mkNamingAdapt(mkStrValAdapt(mkParAdapt(Origin, StdCompiler::RCT_All), C4InVal::VAL_SubPathFilename), "Origin", StdCopyStrBuf()));
207 // windows needs backslashes in Origin; other systems use forward slashes
208 if (pComp->isDeserializer())
209 {
210 Origin.ReplaceChar(AltDirectorySeparator, DirectorySeparator);
211 }
212 }
213 }
214
Default()215 void C4SGame::Default()
216 {
217 Goals.Clear();
218 Rules.Clear();
219 FoWEnabled = true;
220 EvaluateOnAbort = false;
221 }
222
CompileFunc(StdCompiler * pComp,bool fSection)223 void C4SGame::CompileFunc(StdCompiler *pComp, bool fSection)
224 {
225 if (!fSection)
226 {
227 pComp->Value(mkNamingAdapt(Realism.ValueOverloads, "ValueOverloads", C4IDList()));
228 }
229 pComp->Value(mkNamingAdapt(mkRuntimeValueAdapt(Realism.LandscapePushPull), "LandscapePushPull", false));
230 pComp->Value(mkNamingAdapt(mkRuntimeValueAdapt(Realism.LandscapeInsertThrust), "LandscapeInsertThrust",true));
231
232 pComp->Value(mkNamingAdapt(mkParAdapt(Mode, StdCompiler::RCT_IdtfAllowEmpty), "Mode", StdCopyStrBuf()));
233 pComp->Value(mkNamingAdapt(Goals, "Goals", C4IDList()));
234 pComp->Value(mkNamingAdapt(Rules, "Rules", C4IDList()));
235 pComp->Value(mkNamingAdapt(FoWEnabled, "FoWEnabled", true));
236 pComp->Value(mkNamingAdapt(EvaluateOnAbort, "EvaluateOnAbort", false));
237 }
238
Default()239 void C4SPlrStart::Default()
240 {
241 Wealth.Set(0,0,0,250);
242 Position[0]=Position[1]=-1;
243 EnforcePosition=0;
244 ReadyCrew.Default();
245 ReadyBase.Default();
246 ReadyVehic.Default();
247 ReadyMaterial.Default();
248 BuildKnowledge.Default();
249 BaseMaterial.Default();
250 BaseProduction.Default();
251 }
252
EquipmentEqual(C4SPlrStart & rhs)253 bool C4SPlrStart::EquipmentEqual(C4SPlrStart &rhs)
254 {
255 return *this == rhs;
256 }
257
operator ==(const C4SPlrStart & rhs)258 bool C4SPlrStart::operator==(const C4SPlrStart& rhs)
259 {
260 return (Wealth == rhs.Wealth)
261 && (ReadyCrew == rhs.ReadyCrew)
262 && (ReadyBase == rhs.ReadyBase)
263 && (ReadyVehic == rhs.ReadyVehic)
264 && (ReadyMaterial == rhs.ReadyMaterial)
265 && (BuildKnowledge == rhs.BuildKnowledge)
266 && (BaseMaterial == rhs.BaseMaterial)
267 && (BaseProduction == rhs.BaseProduction);
268 }
269
CompileFunc(StdCompiler * pComp)270 void C4SPlrStart::CompileFunc(StdCompiler *pComp)
271 {
272 C4IDList crewDefault;
273 crewDefault.SetIDCount(C4ID::Clonk,1,true);
274 pComp->Value(mkNamingAdapt(Wealth, "Wealth", C4SVal(0, 0, 0,250), true));
275 pComp->Value(mkNamingAdapt(mkArrayAdaptDM(Position,-1), "Position" ));
276 pComp->Value(mkNamingAdapt(EnforcePosition, "EnforcePosition", 0));
277 pComp->Value(mkNamingAdapt(ReadyCrew, "Crew", crewDefault));
278 pComp->Value(mkNamingAdapt(ReadyBase, "Buildings", C4IDList()));
279 pComp->Value(mkNamingAdapt(ReadyVehic, "Vehicles", C4IDList()));
280 pComp->Value(mkNamingAdapt(ReadyMaterial, "Material", C4IDList()));
281 pComp->Value(mkNamingAdapt(BuildKnowledge, "Knowledge", C4IDList()));
282 pComp->Value(mkNamingAdapt(BaseMaterial, "BaseMaterial", C4IDList()));
283 pComp->Value(mkNamingAdapt(BaseProduction, "BaseProduction", C4IDList()));
284 }
285
Default()286 void C4SLandscape::Default()
287 {
288 BottomOpen=0; TopOpen=1;
289 LeftOpen=0; RightOpen=0;
290 AutoScanSideOpen=1;
291 SkyDef[0]=0;
292 for (int & cnt : SkyDefFade) cnt=0;
293 VegLevel.Set(50,30,0,100);
294 Vegetation.Default();
295 InEarthLevel.Set(50,0,0,100);
296 InEarth.Default();
297 MapWdt.Set(100,0,64,250);
298 MapHgt.Set(50,0,40,250);
299 MapZoom.Set(8,0,1,15);
300 Amplitude.Set(0,0);
301 Phase.Set(50);
302 Period.Set(15);
303 Random.Set(0);
304 LiquidLevel.Default();
305 MapPlayerExtend=0;
306 Layers.Clear();
307 Material = "Earth";
308 Liquid = "Water";
309 ExactLandscape=false;
310 Gravity.Set(100,0,10,200);
311 NoScan=false;
312 KeepMapCreator=false;
313 SkyScrollMode=0;
314 MaterialZoom=4;
315 FlatChunkShapes=false;
316 Secret=false;
317 }
318
GetMapSize(int32_t & rWdt,int32_t & rHgt,int32_t iPlayerNum)319 void C4SLandscape::GetMapSize(int32_t &rWdt, int32_t &rHgt, int32_t iPlayerNum)
320 {
321 rWdt = MapWdt.Evaluate();
322 rHgt = MapHgt.Evaluate();
323 iPlayerNum = std::max<int32_t>( iPlayerNum, 1 );
324 if (MapPlayerExtend)
325 rWdt = std::min(rWdt * std::min(iPlayerNum, C4S_MaxMapPlayerExtend), MapWdt.Max);
326 }
327
CompileFunc(StdCompiler * pComp)328 void C4SLandscape::CompileFunc(StdCompiler *pComp)
329 {
330 pComp->Value(mkNamingAdapt(ExactLandscape, "ExactLandscape", false));
331 pComp->Value(mkNamingAdapt(Vegetation, "Vegetation", C4IDList()));
332 pComp->Value(mkNamingAdapt(VegLevel, "VegetationLevel", C4SVal(50,30,0,100), true));
333 pComp->Value(mkNamingAdapt(InEarth, "InEarth", C4IDList()));
334 pComp->Value(mkNamingAdapt(InEarthLevel, "InEarthLevel", C4SVal(50,0,0,100), true));
335 pComp->Value(mkNamingAdapt(mkStringAdaptA(SkyDef), "Sky", ""));
336 pComp->Value(mkNamingAdapt(mkArrayAdaptDM(SkyDefFade,0),"SkyFade" ));
337 pComp->Value(mkNamingAdapt(BottomOpen, "BottomOpen", 0));
338 pComp->Value(mkNamingAdapt(TopOpen, "TopOpen", 1));
339 pComp->Value(mkNamingAdapt(LeftOpen, "LeftOpen", 0));
340 pComp->Value(mkNamingAdapt(RightOpen, "RightOpen", 0));
341 pComp->Value(mkNamingAdapt(AutoScanSideOpen, "AutoScanSideOpen", 1));
342 pComp->Value(mkNamingAdapt(MapWdt, "MapWidth", C4SVal(100,0,64,250), true));
343 pComp->Value(mkNamingAdapt(MapHgt, "MapHeight", C4SVal(50,0,40,250), true));
344 pComp->Value(mkNamingAdapt(MapZoom, "MapZoom", C4SVal(8,0,1,15), true));
345 pComp->Value(mkNamingAdapt(Amplitude, "Amplitude", C4SVal(0)));
346 pComp->Value(mkNamingAdapt(Phase, "Phase", C4SVal(50)));
347 pComp->Value(mkNamingAdapt(Period, "Period", C4SVal(15)));
348 pComp->Value(mkNamingAdapt(Random, "Random", C4SVal(0)));
349 pComp->Value(mkNamingAdapt(mkStringAdaptA(Material),"Material", "Earth"));
350 pComp->Value(mkNamingAdapt(mkStringAdaptA(Liquid), "Liquid", "Water"));
351 pComp->Value(mkNamingAdapt(LiquidLevel, "LiquidLevel", C4SVal()));
352 pComp->Value(mkNamingAdapt(MapPlayerExtend, "MapPlayerExtend", 0));
353 pComp->Value(mkNamingAdapt(Layers, "Layers", C4NameList()));
354 pComp->Value(mkNamingAdapt(Gravity, "Gravity", C4SVal(100,0,10,200), true));
355 pComp->Value(mkNamingAdapt(NoScan, "NoScan", false));
356 pComp->Value(mkNamingAdapt(KeepMapCreator, "KeepMapCreator", false));
357 pComp->Value(mkNamingAdapt(SkyScrollMode, "SkyScrollMode", 0));
358 pComp->Value(mkNamingAdapt(MaterialZoom, "MaterialZoom", 4));
359 pComp->Value(mkNamingAdapt(FlatChunkShapes, "FlatChunkShapes", false));
360 pComp->Value(mkNamingAdapt(Secret, "Secret", false));
361 }
362
Default()363 void C4SWeather::Default()
364 {
365 Climate.Set(50,10);
366 StartSeason.Set(50,50);
367 YearSpeed.Set(50);
368 Wind.Set(0,70,-100,+100);
369 NoGamma=true;
370 }
371
CompileFunc(StdCompiler * pComp)372 void C4SWeather::CompileFunc(StdCompiler *pComp)
373 {
374 pComp->Value(mkNamingAdapt(Climate, "Climate", C4SVal(50,10), true));
375 pComp->Value(mkNamingAdapt(StartSeason, "StartSeason", C4SVal(50,50), true));
376 pComp->Value(mkNamingAdapt(YearSpeed, "YearSpeed", C4SVal(50)));
377 pComp->Value(mkNamingAdapt(Wind, "Wind", C4SVal(0,70,-100,+100), true));
378 pComp->Value(mkNamingAdapt(NoGamma, "NoGamma", true));
379 }
380
Default()381 void C4SAnimals::Default()
382 {
383 FreeLife.Clear();
384 EarthNest.Clear();
385 }
386
CompileFunc(StdCompiler * pComp)387 void C4SAnimals::CompileFunc(StdCompiler *pComp)
388 {
389 pComp->Value(mkNamingAdapt(FreeLife, "Animal", C4IDList()));
390 pComp->Value(mkNamingAdapt(EarthNest, "Nest", C4IDList()));
391 }
392
Default()393 void C4SEnvironment::Default()
394 {
395 Objects.Clear();
396 }
397
CompileFunc(StdCompiler * pComp)398 void C4SEnvironment::CompileFunc(StdCompiler *pComp)
399 {
400 pComp->Value(mkNamingAdapt(Objects, "Objects", C4IDList()));
401 }
402
Default()403 void C4SRealism::Default()
404 {
405 LandscapePushPull=false;
406 LandscapeInsertThrust=false;
407 ValueOverloads.Default();
408 }
409
Clear()410 void C4Scenario::Clear()
411 {
412
413 }
414
SetExactLandscape()415 void C4Scenario::SetExactLandscape()
416 {
417 if (Landscape.ExactLandscape) return;
418 // Set landscape
419 Landscape.ExactLandscape = true;
420 }
421
GetModules(StdStrBuf * psOutModules) const422 bool C4SDefinitions::GetModules(StdStrBuf *psOutModules) const
423 {
424 // Local only
425 if (LocalOnly) { psOutModules->Copy(""); return true; }
426 // Scan for any valid entries
427 bool fSpecified = false;
428 int32_t cnt=0;
429 for (; cnt<C4S_MaxDefinitions; cnt++)
430 if (Definition[cnt][0])
431 fSpecified = true;
432 // No valid entries
433 if (!fSpecified) return false;
434 // Compose entry list
435 psOutModules->Copy("");
436 for (cnt=0; cnt<C4S_MaxDefinitions; cnt++)
437 if (Definition[cnt][0])
438 {
439 if (psOutModules->getLength()) psOutModules->AppendChar(';');
440 psOutModules->Append(Definition[cnt]);
441 }
442 // Done
443 return true;
444 }
445
GetModulesAsList() const446 std::list<const char *> C4SDefinitions::GetModulesAsList() const
447 {
448 // get definitions as string pointers into this structure
449 std::list<const char *> result;
450 if (!LocalOnly)
451 {
452 for (const char *def : Definition)
453 {
454 if (*def)
455 {
456 result.push_back(def);
457 }
458 }
459 }
460 return result;
461 }
462
463
SetModules(const char * szList,const char * szRelativeToPath,const char * szRelativeToPath2)464 void C4SDefinitions::SetModules(const char *szList, const char *szRelativeToPath, const char *szRelativeToPath2)
465 {
466 int32_t cnt;
467
468 // Empty list: local only
469 if (!SModuleCount(szList))
470 {
471 LocalOnly=true;
472 for (cnt=0; cnt<C4S_MaxDefinitions; cnt++) Definition[cnt][0]=0;
473 return;
474 }
475
476 // Set list
477 LocalOnly=false;
478 for (cnt=0; cnt<C4S_MaxDefinitions; cnt++)
479 {
480 SGetModule(szList,cnt,Definition[cnt],_MAX_PATH);
481 // Make relative path
482 if (szRelativeToPath && *szRelativeToPath)
483 {
484 if (GetRelativePathS(Definition[cnt],szRelativeToPath) != Definition[cnt])
485 {
486 SCopy(GetRelativePathS(Definition[cnt],szRelativeToPath),Definition[cnt]);
487 continue;
488 }
489 }
490 if (szRelativeToPath2 && *szRelativeToPath2)
491 {
492 if (GetRelativePathS(Definition[cnt],szRelativeToPath2) != Definition[cnt])
493 {
494 SCopy(GetRelativePathS(Definition[cnt],szRelativeToPath2),Definition[cnt]);
495 continue;
496 }
497 }
498 }
499
500 }
501
CompileFunc(StdCompiler * pComp)502 void C4SDefinitions::CompileFunc(StdCompiler *pComp)
503 {
504 pComp->Value(mkNamingAdapt(LocalOnly, "LocalOnly", false));
505 pComp->Value(mkNamingAdapt(AllowUserChange, "AllowUserChange", false));
506 pComp->Value(mkNamingAdapt(mkStringAdaptMA(Definition[0]), "Definition1", "Objects.ocd"));
507 for (int32_t i = 1; i < C4S_MaxDefinitions; i++)
508 pComp->Value(mkNamingAdapt(mkStringAdaptMA(Definition[i]), FormatString("Definition%i", i+1).getData(), ""));
509 pComp->Value(mkNamingAdapt(SkipDefs, "SkipDefs", C4IDList()));
510 }
511
IsMelee()512 bool C4SGame::IsMelee()
513 {
514 // Check for game modes known to be melees
515 // Also allow it in parkours by default because that works fine
516 if (Mode == "Melee" || Mode == "Parkour") return true;
517 // Game mode not present or unknown? Check for old MELE goal which was still used by some scenarios.
518 if (Goals.GetIDCount(C4ID::Melee, 1)) return true;
519 // Nothing looks like melee here
520 return false;
521 }
522