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/* Object definition */
18
19#include <C4Include.h>
20#include <C4Def.h>
21#include <C4Version.h>
22#include <C4GameVersion.h>
23#include <C4FileMonitor.h>
24
25#include <C4SurfaceFile.h>
26#include <C4Log.h>
27#include <C4Components.h>
28#include <C4Config.h>
29#include <C4ValueList.h>
30#include <C4Wrappers.h>
31#include <C4Object.h>
32#include "C4Network2Res.h"
33
34#include <algorithm>
35
36// Default Action Procedures
37
38const char *ProcedureName[C4D_MaxDFA] =
39{
40 "WALK",
41 "FLIGHT",
42 "KNEEL",
43 "SCALE",
44 "HANGLE",
45 "DIG",
46 "SWIM",
47 "THROW",
48 "BRIDGE",
49 "BUILD",
50 "PUSH",
51 "CHOP",
52 "LIFT",
53 "FLOAT",
54 "ATTACH",
55 "FIGHT",
56 "CONNECT",
57 "PULL"
58};
59
60// C4ActionDef
61
62C4ActionDef::C4ActionDef() : Procedure{DFA_NONE} {}
63
64void C4ActionDef::Default()
65{
66 *this = {};
67}
68
69void C4ActionDef::CompileFunc(StdCompiler *pComp)
70{
71 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(Name), szName: "Name", rDefault: ""));
72 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(ProcedureName), szName: "Procedure", rDefault: ""));
73 pComp->Value(rStruct: mkNamingAdapt(rValue&: Directions, szName: "Directions", rDefault: 1));
74 pComp->Value(rStruct: mkNamingAdapt(rValue&: FlipDir, szName: "FlipDir", rDefault: 0));
75 pComp->Value(rStruct: mkNamingAdapt(rValue&: Length, szName: "Length", rDefault: 1));
76
77 StdBitfieldEntry<int32_t> CNATs[] =
78 {
79 { .Name: "CNAT_None", .Val: CNAT_None },
80 { .Name: "CNAT_Left", .Val: CNAT_Left },
81 { .Name: "CNAT_Right", .Val: CNAT_Right },
82 { .Name: "CNAT_Top", .Val: CNAT_Top },
83 { .Name: "CNAT_Bottom", .Val: CNAT_Bottom },
84 { .Name: "CNAT_Center", .Val: CNAT_Center },
85 { .Name: "CNAT_MultiAttach", .Val: CNAT_MultiAttach },
86 { .Name: "CNAT_NoCollision", .Val: CNAT_NoCollision },
87
88 { .Name: nullptr, .Val: 0 }
89 };
90
91 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt(rVal&: Attach, pNames: CNATs),
92 szName: "Attach", rDefault: 0));
93
94 pComp->Value(rStruct: mkNamingAdapt(rValue&: Delay, szName: "Delay", rDefault: 0));
95 pComp->Value(rStruct: mkNamingAdapt(rValue&: Facet, szName: "Facet", rDefault: TargetRect0));
96 pComp->Value(rStruct: mkNamingAdapt(rValue&: FacetBase, szName: "FacetBase", rDefault: 0));
97 pComp->Value(rStruct: mkNamingAdapt(rValue&: FacetTopFace, szName: "FacetTopFace", rDefault: 0));
98 pComp->Value(rStruct: mkNamingAdapt(rValue&: FacetTargetStretch, szName: "FacetTargetStretch", rDefault: 0));
99 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(NextActionName), szName: "NextAction", rDefault: ""));
100 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoOtherAction, szName: "NoOtherAction", rDefault: 0));
101 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(SStartCall), szName: "StartCall", rDefault: ""));
102 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(SEndCall), szName: "EndCall", rDefault: ""));
103 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(SAbortCall), szName: "AbortCall", rDefault: ""));
104 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(SPhaseCall), szName: "PhaseCall", rDefault: ""));
105 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(Sound), szName: "Sound", rDefault: ""));
106 pComp->Value(rStruct: mkNamingAdapt(rValue&: Disabled, szName: "ObjectDisabled", rDefault: 0));
107 pComp->Value(rStruct: mkNamingAdapt(rValue&: DigFree, szName: "DigFree", rDefault: 0));
108 pComp->Value(rStruct: mkNamingAdapt(rValue&: EnergyUsage, szName: "EnergyUsage", rDefault: 0));
109 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(InLiquidAction), szName: "InLiquidAction", rDefault: ""));
110 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(TurnAction), szName: "TurnAction", rDefault: ""));
111 pComp->Value(rStruct: mkNamingAdapt(rValue&: Reverse, szName: "Reverse", rDefault: 0));
112 pComp->Value(rStruct: mkNamingAdapt(rValue&: Step, szName: "Step", rDefault: 1));
113}
114
115// C4DefCore
116
117C4DefCore::C4DefCore()
118{
119 Default();
120}
121
122void C4DefCore::Default()
123{
124 std::fill(first: rC4XVer, last: std::end(arr&: rC4XVer), value: 0);
125 RequireDef.Clear();
126 Name.Ref(pnData: "Undefined");
127 Physical.Default();
128 Shape.Default();
129 Entrance.Default();
130 Collection.Default();
131 PictureRect.Default();
132 SolidMask.Default();
133 TopFace.Default();
134 Component.Clear();
135 BurnTurnTo = C4ID_None;
136 BuildTurnTo = C4ID_None;
137 STimerCall[0] = 0;
138 Timer = 35;
139 ColorByMaterial[0] = 0;
140 GrowthType = 0;
141 Basement = 0;
142 CanBeBase = 0;
143 CrewMember = 0;
144 NativeCrew = 0;
145 Mass = 0;
146 Value = 0;
147 Exclusive = 0;
148 Category = 0;
149 Growth = 0;
150 Rebuyable = 0;
151 ContactIncinerate = 0;
152 BlastIncinerate = 0;
153 Constructable = 0;
154 Grab = 0;
155 Carryable = 0;
156 Rotateable = 0;
157 RotatedEntrance = 0;
158 Chopable = 0;
159 Float = 0;
160 ColorByOwner = 0;
161 NoHorizontalMove = 0;
162 BorderBound = 0;
163 LiftTop = 0;
164 CollectionLimit = 0;
165 GrabPutGet = 0;
166 ContainBlast = 0;
167 UprightAttach = 0;
168 ContactFunctionCalls = 0;
169 MaxUserSelect = 0;
170 Line = 0;
171 LineConnect = 0;
172 LineIntersect = 0;
173 NoBurnDecay = 0;
174 IncompleteActivity = 0;
175 Placement = 0;
176 Prey = 0;
177 Edible = 0;
178 AttractLightning = 0;
179 Oversize = 0;
180 Fragile = 0;
181 NoPushEnter = 0;
182 Explosive = 0;
183 Projectile = 0;
184 DragImagePicture = 0;
185 VehicleControl = 0;
186 Pathfinder = 0;
187 NoComponentMass = 0;
188 MoveToRange = 0;
189 NoStabilize = 0;
190 ClosedContainer = 0;
191 SilentCommands = 0;
192 NoBurnDamage = 0;
193 TemporaryCrew = 0;
194 SmokeRate = 100;
195 BlitMode = C4D_Blit_Normal;
196 NoBreath = 0;
197 ConSizeOff = 0;
198 NoSell = NoGet = 0;
199 NoFight = 0;
200 RotatedSolidmasks = 0;
201 NeededGfxMode = 0;
202 NoTransferZones = 0;
203 Scale = 100;
204}
205
206bool C4DefCore::Load(C4Group &hGroup)
207{
208 StdStrBuf Source;
209 if (hGroup.LoadEntryString(C4CFN_DefCore, Buf&: Source))
210 {
211 StdStrBuf Name = hGroup.GetFullName();
212 Name.AppendChar(DirectorySeparator);
213 Name.Append(pnData: "DefCore.txt");
214 if (!Compile(szSource: Source.getData(), szName: Name.getData()))
215 return false;
216 Source.Clear();
217
218 // Adjust category: C4D_CrewMember by CrewMember flag
219 if (CrewMember) Category |= C4D_CrewMember;
220
221 // Adjust picture rect
222 if ((PictureRect.Wdt == 0) || (PictureRect.Hgt == 0))
223 PictureRect.Set(iX: 0, iY: 0, iWdt: Shape.Wdt, iHgt: Shape.Hgt);
224
225 // Check category
226 if (!(Category & C4D_SortLimit))
227 {
228 // special: Allow this for spells
229 if (~Category & C4D_Magic)
230 DebugLog(level: spdlog::level::warn, fmt: "Def {} ({}) at {} has invalid category!", args: GetName(), args: C4IdText(id), args: hGroup.GetFullName().getData());
231 // assign a default category here
232 Category = (Category & ~C4D_SortLimit) | 1;
233 }
234 // Check mass
235 if (Mass < 0)
236 {
237 DebugLog(level: spdlog::level::warn, fmt: "Def {} ({}) at {} has invalid mass!", args: GetName(), args: C4IdText(id), args: hGroup.GetFullName().getData());
238 Mass = 0;
239 }
240
241 return true;
242 }
243 return false;
244}
245
246bool C4DefCore::Compile(const char *szSource, const char *szName)
247{
248 return CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: *this, szName: "DefCore"), SrcBuf: StdStrBuf::MakeRef(str: szSource), szName);
249}
250
251void C4DefCore::CompileFunc(StdCompiler *pComp)
252{
253 pComp->Value(rStruct: mkNamingAdapt(rValue: mkC4IDAdapt(rValue&: id), szName: "id", rDefault: C4ID_None));
254 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: rC4XVer, default_: 0), szName: "Version"));
255 pComp->Value(rStruct: mkNamingAdapt(toC4CStrBuf(Name), szName: "Name", rDefault: "Undefined"));
256 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: RequireDef, rPar: false), szName: "RequireDef", rDefault: C4IDList()));
257
258 const StdBitfieldEntry<int32_t> Categories[] =
259 {
260 { .Name: "C4D_StaticBack", .Val: C4D_StaticBack },
261 { .Name: "C4D_Structure", .Val: C4D_Structure },
262 { .Name: "C4D_Vehicle", .Val: C4D_Vehicle },
263 { .Name: "C4D_Living", .Val: C4D_Living },
264 { .Name: "C4D_Object", .Val: C4D_Object },
265
266 { .Name: "C4D_Goal", .Val: C4D_Goal },
267 { .Name: "C4D_Environment", .Val: C4D_Environment },
268 { .Name: "C4D_SelectBuilding", .Val: C4D_SelectBuilding },
269 { .Name: "C4D_SelectVehicle", .Val: C4D_SelectVehicle },
270 { .Name: "C4D_SelectMaterial", .Val: C4D_SelectMaterial },
271 { .Name: "C4D_SelectKnowledge", .Val: C4D_SelectKnowledge },
272 { .Name: "C4D_SelectHomebase", .Val: C4D_SelectHomebase },
273 { .Name: "C4D_SelectAnimal", .Val: C4D_SelectAnimal },
274 { .Name: "C4D_SelectNest", .Val: C4D_SelectNest },
275 { .Name: "C4D_SelectInEarth", .Val: C4D_SelectInEarth },
276 { .Name: "C4D_SelectVegetation", .Val: C4D_SelectVegetation },
277
278 { .Name: "C4D_TradeLiving", .Val: C4D_TradeLiving },
279 { .Name: "C4D_Magic", .Val: C4D_Magic },
280 { .Name: "C4D_CrewMember", .Val: C4D_CrewMember },
281
282 { .Name: "C4D_Rule", .Val: C4D_Rule },
283
284 { .Name: "C4D_Background", .Val: C4D_Background },
285 { .Name: "C4D_Parallax", .Val: C4D_Parallax },
286 { .Name: "C4D_MouseSelect", .Val: C4D_MouseSelect },
287 { .Name: "C4D_Foreground", .Val: C4D_Foreground },
288 { .Name: "C4D_MouseIgnore", .Val: C4D_MouseIgnore },
289 { .Name: "C4D_IgnoreFoW", .Val: C4D_IgnoreFoW },
290
291 { .Name: nullptr, .Val: 0 }
292 };
293
294 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt<int32_t>(rVal&: Category, pNames: Categories),
295 szName: "Category", rDefault: 0));
296
297 pComp->Value(rStruct: mkNamingAdapt(rValue&: MaxUserSelect, szName: "MaxUserSelect", rDefault: 0));
298 pComp->Value(rStruct: mkNamingAdapt(rValue&: Timer, szName: "Timer", rDefault: 35));
299 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(STimerCall), szName: "TimerCall", rDefault: ""));
300 pComp->Value(rStruct: mkNamingAdapt(rValue&: ContactFunctionCalls, szName: "ContactCalls", rDefault: 0));
301 pComp->Value(rStruct: mkParAdapt(rObj&: Shape, rPar: false));
302 pComp->Value(rStruct: mkNamingAdapt(rValue&: Value, szName: "Value", rDefault: 0));
303 pComp->Value(rStruct: mkNamingAdapt(rValue&: Mass, szName: "Mass", rDefault: 0));
304 pComp->Value(rStruct: mkNamingAdapt(rValue&: Component, szName: "Components", rDefault: C4IDList()));
305 pComp->Value(rStruct: mkNamingAdapt(rValue&: SolidMask, szName: "SolidMask", rDefault: TargetRect0));
306 pComp->Value(rStruct: mkNamingAdapt(rValue&: TopFace, szName: "TopFace", rDefault: TargetRect0));
307 pComp->Value(rStruct: mkNamingAdapt(rValue&: PictureRect, szName: "Picture", rDefault: Rect0));
308 pComp->Value(rStruct: mkNamingAdapt(rValue: StdNullAdapt(), szName: "PictureFE"));
309 pComp->Value(rStruct: mkNamingAdapt(rValue&: Entrance, szName: "Entrance", rDefault: Rect0));
310 pComp->Value(rStruct: mkNamingAdapt(rValue&: Collection, szName: "Collection", rDefault: Rect0));
311 pComp->Value(rStruct: mkNamingAdapt(rValue&: CollectionLimit, szName: "CollectionLimit", rDefault: 0));
312 pComp->Value(rStruct: mkNamingAdapt(rValue&: Placement, szName: "Placement", rDefault: 0));
313 pComp->Value(rStruct: mkNamingAdapt(rValue&: Exclusive, szName: "Exclusive", rDefault: 0));
314 pComp->Value(rStruct: mkNamingAdapt(rValue&: ContactIncinerate, szName: "ContactIncinerate", rDefault: 0));
315 pComp->Value(rStruct: mkNamingAdapt(rValue&: BlastIncinerate, szName: "BlastIncinerate", rDefault: 0));
316 pComp->Value(rStruct: mkNamingAdapt(rValue: mkC4IDAdapt(rValue&: BurnTurnTo), szName: "BurnTo", rDefault: C4ID_None));
317 pComp->Value(rStruct: mkNamingAdapt(rValue&: CanBeBase, szName: "Base", rDefault: 0));
318
319 const StdBitfieldEntry<int32_t> LineTypes[] =
320 {
321 { .Name: "C4D_LinePower", .Val: C4D_Line_Power },
322 { .Name: "C4D_LineSource", .Val: C4D_Line_Source },
323 { .Name: "C4D_LineDrain", .Val: C4D_Line_Drain },
324 { .Name: "C4D_LineLightning", .Val: C4D_Line_Lightning },
325 { .Name: "C4D_LineVolcano", .Val: C4D_Line_Volcano },
326 { .Name: "C4D_LineRope", .Val: C4D_Line_Rope },
327 { .Name: "C4D_LineColored", .Val: C4D_Line_Colored },
328 { .Name: "C4D_LineVertex", .Val: C4D_Line_Vertex },
329
330 { .Name: nullptr, .Val: 0 }
331 };
332
333 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt(rVal&: Line, pNames: LineTypes), szName: "Line", rDefault: 0));
334
335 const StdBitfieldEntry<int32_t> LineConnectTypes[] =
336 {
337 { .Name: "C4D_PowerInput", .Val: C4D_Power_Input },
338 { .Name: "C4D_PowerOutput", .Val: C4D_Power_Output },
339 { .Name: "C4D_LiquidInput", .Val: C4D_Liquid_Input },
340 { .Name: "C4D_LiquidOutput", .Val: C4D_Liquid_Output },
341 { .Name: "C4D_PowerGenerator", .Val: C4D_Power_Generator },
342 { .Name: "C4D_PowerConsumer", .Val: C4D_Power_Consumer },
343 { .Name: "C4D_LiquidPump", .Val: C4D_Liquid_Pump },
344 { .Name: "C4D_ConnectRope", .Val: C4D_Connect_Rope },
345 { .Name: "C4D_EnergyHolder", .Val: C4D_EnergyHolder },
346
347 { .Name: nullptr, .Val: 0 }
348 };
349
350 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt(rVal&: LineConnect, pNames: LineConnectTypes),
351 szName: "LineConnect", rDefault: 0));
352
353 pComp->Value(rStruct: mkNamingAdapt(rValue&: LineIntersect, szName: "LineIntersect", rDefault: 0));
354 pComp->Value(rStruct: mkNamingAdapt(rValue&: Prey, szName: "Prey", rDefault: 0));
355 pComp->Value(rStruct: mkNamingAdapt(rValue&: Edible, szName: "Edible", rDefault: 0));
356 pComp->Value(rStruct: mkNamingAdapt(rValue&: CrewMember, szName: "CrewMember", rDefault: 0));
357 pComp->Value(rStruct: mkNamingAdapt(rValue&: NativeCrew, szName: "NoStandardCrew", rDefault: 0));
358 pComp->Value(rStruct: mkNamingAdapt(rValue&: Growth, szName: "Growth", rDefault: 0));
359 pComp->Value(rStruct: mkNamingAdapt(rValue&: Rebuyable, szName: "Rebuy", rDefault: 0));
360 pComp->Value(rStruct: mkNamingAdapt(rValue&: Constructable, szName: "Construction", rDefault: 0));
361 pComp->Value(rStruct: mkNamingAdapt(rValue: mkC4IDAdapt(rValue&: BuildTurnTo), szName: "ConstructTo", rDefault: 0));
362 pComp->Value(rStruct: mkNamingAdapt(rValue&: Grab, szName: "Grab", rDefault: 0));
363
364 const StdBitfieldEntry<int32_t> GrabPutGetTypes[] =
365 {
366 { .Name: "C4D_GrabGet", .Val: C4D_Grab_Get },
367 { .Name: "C4D_GrabPut", .Val: C4D_Grab_Put },
368
369 { .Name: nullptr, .Val: 0 }
370 };
371
372 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt(rVal&: GrabPutGet, pNames: GrabPutGetTypes),
373 szName: "GrabPutGet", rDefault: 0));
374
375 pComp->Value(rStruct: mkNamingAdapt(rValue&: Carryable, szName: "Collectible", rDefault: 0));
376 pComp->Value(rStruct: mkNamingAdapt(rValue&: Rotateable, szName: "Rotate", rDefault: 0));
377 pComp->Value(rStruct: mkNamingAdapt(rValue&: RotatedEntrance, szName: "RotatedEntrance", rDefault: 0));
378 pComp->Value(rStruct: mkNamingAdapt(rValue&: Chopable, szName: "Chop", rDefault: 0));
379 pComp->Value(rStruct: mkNamingAdapt(rValue&: Float, szName: "Float", rDefault: 0));
380 pComp->Value(rStruct: mkNamingAdapt(rValue&: ContainBlast, szName: "ContainBlast", rDefault: 0));
381 pComp->Value(rStruct: mkNamingAdapt(rValue&: ColorByOwner, szName: "ColorByOwner", rDefault: 0));
382 pComp->Value(rStruct: mkNamingAdapt(toC4CStr(ColorByMaterial), szName: "ColorByMaterial", rDefault: ""));
383 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoHorizontalMove, szName: "HorizontalFix", rDefault: 0));
384 pComp->Value(rStruct: mkNamingAdapt(rValue&: BorderBound, szName: "BorderBound", rDefault: 0));
385 pComp->Value(rStruct: mkNamingAdapt(rValue&: LiftTop, szName: "LiftTop", rDefault: 0));
386 pComp->Value(rStruct: mkNamingAdapt(rValue&: UprightAttach, szName: "UprightAttach", rDefault: 0));
387 pComp->Value(rStruct: mkNamingAdapt(rValue&: GrowthType, szName: "StretchGrowth", rDefault: 0));
388 pComp->Value(rStruct: mkNamingAdapt(rValue&: Basement, szName: "Basement", rDefault: 0));
389 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoBurnDecay, szName: "NoBurnDecay", rDefault: 0));
390 pComp->Value(rStruct: mkNamingAdapt(rValue&: IncompleteActivity, szName: "IncompleteActivity", rDefault: 0));
391 pComp->Value(rStruct: mkNamingAdapt(rValue&: AttractLightning, szName: "AttractLightning", rDefault: 0));
392 pComp->Value(rStruct: mkNamingAdapt(rValue&: Oversize, szName: "Oversize", rDefault: 0));
393 pComp->Value(rStruct: mkNamingAdapt(rValue&: Fragile, szName: "Fragile", rDefault: 0));
394 pComp->Value(rStruct: mkNamingAdapt(rValue&: Explosive, szName: "Explosive", rDefault: 0));
395 pComp->Value(rStruct: mkNamingAdapt(rValue&: Projectile, szName: "Projectile", rDefault: 0));
396 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoPushEnter, szName: "NoPushEnter", rDefault: 0));
397 pComp->Value(rStruct: mkNamingAdapt(rValue&: DragImagePicture, szName: "DragImagePicture", rDefault: 0));
398 pComp->Value(rStruct: mkNamingAdapt(rValue&: VehicleControl, szName: "VehicleControl", rDefault: 0));
399 pComp->Value(rStruct: mkNamingAdapt(rValue&: Pathfinder, szName: "Pathfinder", rDefault: 0));
400 pComp->Value(rStruct: mkNamingAdapt(rValue&: MoveToRange, szName: "MoveToRange", rDefault: 0));
401 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoComponentMass, szName: "NoComponentMass", rDefault: 0));
402 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoStabilize, szName: "NoStabilize", rDefault: 0));
403 pComp->Value(rStruct: mkNamingAdapt(rValue&: ClosedContainer, szName: "ClosedContainer", rDefault: 0));
404 pComp->Value(rStruct: mkNamingAdapt(rValue&: SilentCommands, szName: "SilentCommands", rDefault: 0));
405 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoBurnDamage, szName: "NoBurnDamage", rDefault: 0));
406 pComp->Value(rStruct: mkNamingAdapt(rValue&: TemporaryCrew, szName: "TemporaryCrew", rDefault: 0));
407 pComp->Value(rStruct: mkNamingAdapt(rValue&: SmokeRate, szName: "SmokeRate", rDefault: 100));
408 pComp->Value(rStruct: mkNamingAdapt(rValue&: BlitMode, szName: "BlitMode", C4D_Blit_Normal));
409 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoBreath, szName: "NoBreath", rDefault: 0));
410 pComp->Value(rStruct: mkNamingAdapt(rValue&: ConSizeOff, szName: "ConSizeOff", rDefault: 0));
411 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoSell, szName: "NoSell", rDefault: 0));
412 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoGet, szName: "NoGet", rDefault: 0));
413 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoFight, szName: "NoFight", rDefault: 0));
414 pComp->Value(rStruct: mkNamingAdapt(rValue&: RotatedSolidmasks, szName: "RotatedSolidmasks", rDefault: 0));
415 pComp->Value(rStruct: mkNamingAdapt(rValue&: NoTransferZones, szName: "NoTransferZones", rDefault: 0));
416 pComp->Value(rStruct: mkNamingAdapt(rValue&: AutoContextMenu, szName: "AutoContextMenu", rDefault: 0));
417 pComp->Value(rStruct: mkNamingAdapt(rValue&: NeededGfxMode, szName: "NeededGfxMode", rDefault: 0));
418
419 const StdBitfieldEntry<int32_t> AllowPictureStackModes[] =
420 {
421 { .Name: "APS_Color", .Val: APS_Color },
422 { .Name: "APS_Graphics", .Val: APS_Graphics },
423 { .Name: "APS_Name", .Val: APS_Name },
424 { .Name: "APS_Overlay", .Val: APS_Overlay },
425 { .Name: nullptr, .Val: 0 }
426 };
427
428 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt<int32_t>(rVal&: AllowPictureStack, pNames: AllowPictureStackModes),
429 szName: "AllowPictureStack", rDefault: 0));
430
431 const StdBitfieldEntry<int32_t> HideBarValues[] =
432 {
433 { .Name: "Energy", .Val: HB_Energy },
434 { .Name: "MagicEnergy", .Val: HB_MagicEnergy },
435 { .Name: "Breath", .Val: HB_Breath },
436 { .Name: "All", .Val: HB_All },
437 { .Name: nullptr, .Val: 0 }
438 };
439
440 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt<int32_t>(rVal&: HideHUDBars, pNames: HideBarValues), szName: "HideHUDBars", rDefault: 0));
441
442 const StdBitfieldEntry<int32_t> HideHUDValues[] =
443 {
444 { .Name: "Portrait", .Val: HH_Portrait },
445 { .Name: "Captain", .Val: HH_Captain },
446 { .Name: "Name", .Val: HH_Name },
447 { .Name: "Rank", .Val: HH_Rank },
448 { .Name: "RankImage", .Val: HH_RankImage },
449 { .Name: "Inventory", .Val: HH_Inventory },
450 { .Name: "All", .Val: HH_All },
451 { .Name: nullptr, .Val: 0 }
452 };
453
454 pComp->Value(rStruct: mkNamingAdapt(rValue: mkBitfieldAdapt<int32_t>(rVal&: HideHUDElements, pNames: HideHUDValues), szName: "HideHUDElements", rDefault: 0));
455
456 pComp->Value(rStruct: mkNamingAdapt(rValue&: Scale, szName: "Scale", rDefault: 100));
457 pComp->Value(rStruct: mkNamingAdapt(rValue&: BaseAutoSell, szName: "BaseAutoSell", rDefault: id == C4ID_Gold)); // do not brick third-party GOLD objects which don't have that flag
458
459 pComp->FollowName(szName: "Physical");
460 pComp->Value(rStruct&: Physical);
461}
462
463// C4Def
464
465C4Def::C4Def()
466{
467 Graphics.pDef = this;
468 Default();
469}
470
471void C4Def::Default()
472{
473 C4DefCore::Default();
474
475 ActNum = 0;
476 ActMap = nullptr;
477 Maker[0] = 0;
478 Filename[0] = 0;
479 Creation = 0;
480 Count = 0;
481 TimerCall = nullptr;
482 MainFace.Set(nsfc: nullptr, nx: 0, ny: 0, nwdt: 0, nhgt: 0);
483 Script.Default();
484 StringTable.Default();
485 pClonkNames = nullptr;
486 pRankNames = nullptr;
487 pRankSymbols = nullptr;
488 fClonkNamesOwned = fRankNamesOwned = fRankSymbolsOwned = false;
489 iNumRankSymbols = 1;
490 PortraitCount = 0;
491 Portraits = nullptr;
492 pFairCrewPhysical = nullptr;
493 Scale = 1.0f;
494}
495
496C4Def::~C4Def()
497{
498 Clear();
499}
500
501void C4Def::Clear()
502{
503 Graphics.Clear();
504
505 Script.Clear();
506 StringTable.Clear();
507 if (fClonkNamesOwned) delete pClonkNames; pClonkNames = nullptr;
508 if (fRankNamesOwned) delete pRankNames; pRankNames = nullptr;
509 if (fRankSymbolsOwned) delete pRankSymbols; pRankSymbols = nullptr;
510 delete pFairCrewPhysical; pFairCrewPhysical = nullptr;
511 fClonkNamesOwned = fRankNamesOwned = fRankSymbolsOwned = false;
512
513 PortraitCount = 0;
514 Portraits = nullptr;
515 Scale = 1.0f;
516
517 delete[] ActMap; ActMap = nullptr;
518 Desc.Clear();
519}
520
521bool C4Def::Load(C4Group &hGroup,
522 uint32_t dwLoadWhat,
523 const char *szLanguage,
524 C4SoundSystem *pSoundSystem)
525{
526 bool fSuccess = true;
527 const bool addFileMonitoring{!hGroup.IsPacked() && !SEqual(szStr1: hGroup.GetFullName().getData(), szStr2: Filename)};
528
529 // Store filename, maker, creation
530 SCopy(szSource: hGroup.GetFullName().getData(), sTarget: Filename);
531 SCopy(szSource: hGroup.GetMaker(), sTarget: Maker, iMaxL: C4MaxName);
532 Creation = hGroup.GetCreation();
533
534 // Verbose log filename
535 if (Config.Graphics.VerboseObjectLoading >= 3)
536 LogNTr(level: spdlog::level::info, message: hGroup.GetFullName().getData());
537
538 if (addFileMonitoring)
539 {
540 Game.AddDirectoryForMonitoring(directory: Filename);
541 }
542
543 // particle def?
544 if (hGroup.AccessEntry(C4CFN_ParticleCore))
545 {
546 // def loading not successful; abort after reading sounds
547 fSuccess = false;
548 // create new particle def
549 C4ParticleDef *pParticleDef = new C4ParticleDef();
550 // load it
551 if (!pParticleDef->Load(rGrp&: hGroup))
552 {
553 // not successful :( - destroy it again
554 delete pParticleDef;
555 }
556 // done
557 }
558
559 // Read DefCore
560 if (fSuccess) fSuccess = C4DefCore::Load(hGroup);
561 // check id
562 if (fSuccess)
563 {
564 if (!LooksLikeID(id))
565 {
566 // wie geth ID?????ßßßß
567 if (!Name[0]) Name = GetFilename(path: hGroup.GetName());
568 Log(id: C4ResStrTableKey::IDS_ERR_INVALIDID, args: Name.getData());
569
570 fSuccess = false;
571 }
572
573 if (CompareVersion(iVer1: rC4XVer[0], iVer2: rC4XVer[1], iVer3: rC4XVer[2], iVer4: rC4XVer[3], iVerBuild: rC4XVer[4], iRVer1: 4, iRVer2: 0, iRVer3: 0, iRVer4: 0, iRVerBuild: 0) == -1)
574 {
575 DebugLog(level: spdlog::level::warn, message: LoadResStr(id: C4ResStrTableKey::IDS_PRC_DEFSINVVERSION, args: fSuccess ? std::string_view{std::format(fmt: "{} ({})", args: Name.getData(), args: C4IdText(id))} : Name.getData()));
576 // assume Clonk Rage 4.9.10.7
577 rC4XVer[0] = 4;
578 rC4XVer[1] = 9;
579 rC4XVer[2] = 10;
580 rC4XVer[3] = 7;
581 rC4XVer[4] = 0;
582 }
583 }
584
585 // skip def: don't even read sounds!
586 if (fSuccess && Game.C4S.Definitions.SkipDefs.GetIDCount(id, zeroDefVal: 1)) return false;
587
588 // OldGfx is no longer supported
589 if (NeededGfxMode == C4DGFXMODE_OLDGFX) return false;
590
591 if (!fSuccess)
592 {
593 // Read sounds even if not a valid def (for pure c4d sound folders)
594 if (dwLoadWhat & C4D_Load_Sounds)
595 if (pSoundSystem)
596 pSoundSystem->LoadEffects(group&: hGroup);
597
598 return false;
599 }
600
601 // Read surface bitmap
602 if (dwLoadWhat & C4D_Load_Bitmap)
603 if (!Graphics.LoadAllGraphics(hGroup, fColorByOwner: !!ColorByOwner))
604 {
605 DebugLog(level: spdlog::level::err, fmt: "Error loading graphics of {} ({})", args: hGroup.GetFullName().getData(), args: C4IdText(id));
606 return false;
607 }
608
609 // Read portraits
610 if (dwLoadWhat & C4D_Load_Bitmap)
611 if (!LoadPortraits(hGroup))
612 {
613 DebugLog(level: spdlog::level::err, fmt: "Error loading portrait graphics of {} ({})", args: hGroup.GetFullName().getData(), args: C4IdText(id));
614 return false;
615 }
616
617 // Read ActMap
618 if (dwLoadWhat & C4D_Load_ActMap)
619 if (!LoadActMap(hGroup))
620 {
621 DebugLog(level: spdlog::level::err, fmt: "Error loading ActMap of {} ({})", args: hGroup.GetFullName().getData(), args: C4IdText(id));
622 return false;
623 }
624
625 // Read script
626 if (dwLoadWhat & C4D_Load_Script)
627 {
628 // reg script to engine
629 Script.Reg2List(pEngine: &Game.ScriptEngine, pOwner: &Game.ScriptEngine);
630 // Load script - loads string table as well, because that must be done after script load
631 // for downwards compatibility with packing order
632 Script.Load(szName: "Script", hGroup, C4CFN_Script, szLanguage, pDef: this, pLocalTable: &StringTable, fLoadTable: true);
633 }
634
635 // Read name
636 C4ComponentHost DefNames;
637 if (DefNames.LoadEx(szName: "Names", hGroup, C4CFN_DefNames, szLanguage))
638 DefNames.GetLanguageString(szLanguage, rTarget&: Name);
639 DefNames.Close();
640
641 // read clonknames
642 if (dwLoadWhat & C4D_Load_ClonkNames)
643 {
644 // clear any previous
645 delete pClonkNames; pClonkNames = nullptr;
646 if (hGroup.FindEntry(C4CFN_ClonkNameFiles))
647 {
648 // create new
649 pClonkNames = new C4ComponentHost();
650 if (!pClonkNames->LoadEx(szName: LoadResStr(id: C4ResStrTableKey::IDS_CNS_NAMES), hGroup, C4CFN_ClonkNames, szLanguage))
651 {
652 delete pClonkNames; pClonkNames = nullptr;
653 }
654 else
655 fClonkNamesOwned = true;
656 }
657 }
658
659 // read clonkranks
660 if (dwLoadWhat & C4D_Load_RankNames)
661 {
662 // clear any previous
663 delete pRankNames; pRankNames = nullptr;
664 if (hGroup.FindEntry(C4CFN_RankNameFiles))
665 {
666 // create new
667 pRankNames = new C4RankSystem();
668 // load from group
669 if (!pRankNames->Load(hGroup, C4CFN_RankNames, DefRankBase: 1000, szLanguage))
670 {
671 delete pRankNames; pRankNames = nullptr;
672 }
673 else
674 fRankNamesOwned = true;
675 }
676 }
677
678 // read rankfaces
679 if (dwLoadWhat & C4D_Load_RankFaces)
680 {
681 // clear any previous
682 delete pRankSymbols; pRankSymbols = nullptr;
683 // load new: try png first
684 if (hGroup.AccessEntry(C4CFN_RankFacesPNG))
685 {
686 pRankSymbols = new C4FacetExSurface();
687 if (!pRankSymbols->GetFace().ReadPNG(hGroup)) { delete pRankSymbols; pRankSymbols = nullptr; }
688 }
689 else if (hGroup.AccessEntry(C4CFN_RankFaces))
690 {
691 pRankSymbols = new C4FacetExSurface();
692 if (!pRankSymbols->GetFace().Read(hGroup)) { delete pRankSymbols; pRankSymbols = nullptr; }
693 }
694 // set size
695 if (pRankSymbols)
696 {
697 pRankSymbols->Set(nsfc: &pRankSymbols->GetFace(), nx: 0, ny: 0, nwdt: pRankSymbols->GetFace().Hgt, nhgt: pRankSymbols->GetFace().Hgt);
698 int32_t Q; pRankSymbols->GetPhaseNum(rX&: iNumRankSymbols, rY&: Q);
699 if (!iNumRankSymbols) { delete pRankSymbols; pRankSymbols = nullptr; }
700 else
701 {
702 if (pRankNames)
703 {
704 // if extended rank names are defined, subtract those from the symbol count. The last symbols are used as overlay
705 iNumRankSymbols = std::max<int32_t>(a: 1, b: iNumRankSymbols - pRankNames->GetExtendedRankNum());
706 }
707 fRankSymbolsOwned = true;
708 }
709 }
710 }
711
712 // Read desc
713 if (dwLoadWhat & C4D_Load_Desc)
714 {
715 Desc.LoadEx(szName: "Desc", hGroup, C4CFN_DefDesc, szLanguage);
716 Desc.TrimSpaces();
717 }
718
719 // Read sounds
720 if (dwLoadWhat & C4D_Load_Sounds)
721 if (pSoundSystem)
722 pSoundSystem->LoadEffects(group&: hGroup);
723
724 // Post-load settings
725 Scale = C4DefCore::Scale / 100.0f;
726
727 if (Graphics.GetBitmap())
728 {
729 // check SolidMask
730 if (SolidMask.x < 0 || SolidMask.y < 0 || SolidMask.x + SolidMask.Wdt > Graphics.Bitmap->Wdt || SolidMask.y + SolidMask.Hgt > Graphics.Bitmap->Hgt) SolidMask.Default();
731 // Set MainFace (unassigned bitmap: will be set by GetMainFace())
732 MainFace.Set(nsfc: nullptr, nx: 0, ny: 0, nwdt: Shape.Wdt, nhgt: Shape.Hgt);
733 }
734
735 // validate TopFace
736 if (TopFace.x < 0 || TopFace.y < 0 || TopFace.x + TopFace.Wdt > Graphics.Bitmap->Wdt / Scale || TopFace.y + TopFace.Hgt > Graphics.Bitmap->Hgt / Scale)
737 {
738 TopFace.Default();
739 // warn in debug mode
740 DebugLog(level: spdlog::level::warn, fmt: "invalid TopFace in {}({})", args: Name.getData(), args: C4IdText(id));
741 }
742
743 return true;
744}
745
746bool C4Def::LoadActMap(C4Group &hGroup)
747{
748 // New format
749 StdStrBuf Data;
750 if (hGroup.LoadEntryString(C4CFN_DefActMap, Buf&: Data))
751 {
752 // Get action count (hacky), create buffer
753 int actnum;
754 if (!(actnum = SCharCount(cTarget: '[', szInStr: Data.getData()))
755 || !(ActMap = new C4ActionDef[actnum]))
756 return false;
757 // Compile
758 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(
759 TargetStruct: mkNamingAdapt(rValue: mkArrayAdaptS(array: ActMap, size: actnum), szName: "Action"),
760 SrcBuf: Data,
761 szName: (hGroup.GetFullName() + DirSep C4CFN_DefActMap).getData()))
762 return false;
763 ActNum = actnum;
764 // Process map
765 CrossMapActMap();
766 return true;
767 }
768
769 // No act map in group: okay
770 return true;
771}
772
773void C4Def::CrossMapActMap()
774{
775 int32_t cnt, cnt2;
776 for (cnt = 0; cnt < ActNum; cnt++)
777 {
778 // Map standard procedures
779 ActMap[cnt].Procedure = DFA_NONE;
780 for (cnt2 = 0; cnt2 < C4D_MaxDFA; cnt2++)
781 if (SEqual(szStr1: ActMap[cnt].ProcedureName, szStr2: ProcedureName[cnt2]))
782 ActMap[cnt].Procedure = cnt2;
783 // Map next action
784 if (ActMap[cnt].NextActionName[0])
785 {
786 if (SEqualNoCase(szStr1: ActMap[cnt].NextActionName, szStr2: "Hold"))
787 ActMap[cnt].NextAction = ActHold;
788 else
789 for (cnt2 = 0; cnt2 < ActNum; cnt2++)
790 if (SEqual(szStr1: ActMap[cnt].NextActionName, szStr2: ActMap[cnt2].Name))
791 ActMap[cnt].NextAction = cnt2;
792 }
793 // Check act calls
794 if (SEqualNoCase(szStr1: ActMap[cnt].SStartCall, szStr2: "None")) ActMap[cnt].SStartCall[0] = 0;
795 if (SEqualNoCase(szStr1: ActMap[cnt].SPhaseCall, szStr2: "None")) ActMap[cnt].SPhaseCall[0] = 0;
796 if (SEqualNoCase(szStr1: ActMap[cnt].SEndCall, szStr2: "None")) ActMap[cnt].SEndCall [0] = 0;
797 if (SEqualNoCase(szStr1: ActMap[cnt].SAbortCall, szStr2: "None")) ActMap[cnt].SAbortCall[0] = 0;
798 }
799}
800
801bool C4Def::ColorizeByMaterial(C4MaterialMap &rMats, uint8_t bGBM)
802{
803 if (ColorByMaterial[0])
804 {
805 int32_t mat = rMats.Get(szMaterial: ColorByMaterial);
806 if (mat == MNone) { LogNTr(level: spdlog::level::err, fmt: "C4Def::ColorizeByMaterial: mat {} not defined", args: +ColorByMaterial); return false; }
807 if (!Graphics.ColorizeByMaterial(iMat: mat, rMats, bGBM)) return false;
808 }
809 // success
810 return true;
811}
812
813void C4Def::Draw(C4Facet &cgo, bool fSelected, uint32_t iColor, C4Object *pObj, int32_t iPhaseX, int32_t iPhaseY)
814{
815 // default: def picture rect
816 C4Rect fctPicRect = PictureRect;
817 C4Facet fctPicture;
818
819 // if assigned: use object specific rect and graphics
820 if (pObj) if (pObj->PictureRect.Wdt) fctPicRect = pObj->PictureRect;
821
822 fctPicture.Set(nsfc: (pObj ? *pObj->GetGraphics() : Graphics).GetBitmap(dwClr: iColor), nx: fctPicRect.x, ny: fctPicRect.y, nwdt: fctPicRect.Wdt, nhgt: fctPicRect.Hgt);
823
824 if (fSelected)
825 Application.DDraw->DrawBox(sfcDest: cgo.Surface, iX1: cgo.X, iY1: cgo.Y, iX2: cgo.X + cgo.Wdt - 1, iY2: cgo.Y + cgo.Hgt - 1, byCol: CRed);
826
827 // specific object color?
828 if (pObj) pObj->PrepareDrawing();
829 fctPicture.Draw(cgo, fAspect: true, iPhaseX, iPhaseY, fTransparent: true, scale: Scale);
830 if (pObj) pObj->FinishedDrawing();
831
832 // draw overlays
833 if (pObj && pObj->pGfxOverlay)
834 for (C4GraphicsOverlay *pGfxOvrl = pObj->pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
835 if (pGfxOvrl->IsPicture())
836 pGfxOvrl->DrawPicture(cgo, pForObj: pObj);
837}
838
839int32_t C4Def::GetValue(C4Object *pInBase, int32_t iBuyPlayer)
840{
841 // CalcDefValue defined?
842 C4AulFunc *pCalcValueFn = Script.GetSFunc(PSF_CalcDefValue, AccNeeded: AA_PROTECTED);
843 int32_t iValue;
844 if (pCalcValueFn)
845 // then call it!
846 iValue = pCalcValueFn->Exec(pObj: nullptr, pPars: {C4VObj(pObj: pInBase), C4VInt(iVal: iBuyPlayer)}).getInt();
847 else
848 // otherwise, use default value
849 iValue = Value;
850 // do any adjustments based on where the item is bought
851 if (pInBase)
852 {
853 C4AulFunc *pFn;
854 if (pFn = pInBase->Def->Script.GetSFunc(PSF_CalcBuyValue, AccNeeded: AA_PROTECTED))
855 iValue = pFn->Exec(pObj: pInBase, pPars: {C4VID(idVal: id), C4VInt(iVal: iValue)}).getInt();
856 }
857 return iValue;
858}
859
860C4PhysicalInfo *C4Def::GetFairCrewPhysicals()
861{
862 // if fair crew physicals have been created, assume they are valid
863 if (!pFairCrewPhysical)
864 {
865 pFairCrewPhysical = new C4PhysicalInfo(Physical);
866 // determine the rank
867 int32_t iExpGain = Game.Parameters.FairCrewStrength;
868 C4RankSystem *pRankSys = &Game.Rank;
869 if (pRankNames) pRankSys = pRankNames;
870 int32_t iRank = pRankSys->RankByExperience(iExp: iExpGain);
871 // promote physicals for rank
872 pFairCrewPhysical->PromotionUpdate(iRank, fUpdateTrainablePhysicals: true, pTrainDef: this);
873 }
874 return pFairCrewPhysical;
875}
876
877void C4Def::ClearFairCrewPhysicals()
878{
879 // invalidate physicals so the next call to GetFairCrewPhysicals will
880 // reacreate them
881 delete pFairCrewPhysical; pFairCrewPhysical = nullptr;
882}
883
884void C4Def::Synchronize()
885{
886 // because recreation of fair crew physicals does a script call, which *might* do a call to e.g. Random
887 // fair crew physicals must be cleared and recalculated for everyone
888 ClearFairCrewPhysicals();
889}
890
891// C4DefList
892
893C4DefList::C4DefList()
894{
895 Clear();
896}
897
898C4DefList::~C4DefList()
899{
900 Clear();
901}
902
903int32_t C4DefList::Load(C4Group &hGroup, uint32_t dwLoadWhat,
904 const char *szLanguage,
905 C4SoundSystem *pSoundSystem,
906 bool fOverload,
907 bool fSearchMessage, int32_t iMinProgress, int32_t iMaxProgress, bool fLoadSysGroups)
908{
909 int32_t iResult = 0;
910 char szEntryname[_MAX_FNAME + 1];
911 C4Group hChild;
912 bool fPrimaryDef = false;
913 bool fThisSearchMessage = false;
914
915 // This search message
916 if (fSearchMessage)
917 if (SEqualNoCase(szStr1: GetExtension(fname: hGroup.GetName()), szStr2: "c4d")
918 || SEqualNoCase(szStr1: GetExtension(fname: hGroup.GetName()), szStr2: "c4s")
919 || SEqualNoCase(szStr1: GetExtension(fname: hGroup.GetName()), szStr2: "c4f"))
920 {
921 fThisSearchMessage = true;
922 fSearchMessage = false;
923 }
924
925 if (fThisSearchMessage) { LogNTr(fmt: "{}...", args: GetFilename(path: hGroup.GetName())); }
926
927 auto def = std::make_unique<C4Def>();
928 // Load primary definition
929 if (def->Load(hGroup, dwLoadWhat, szLanguage, pSoundSystem) && Add(ndef: def.get(), fOverload))
930 {
931 iResult++; fPrimaryDef = true;
932 def.release();
933 }
934 else
935 {
936 def.reset();
937 }
938
939 // Load sub definitions
940 int i = 0;
941 hGroup.ResetSearch();
942 while (hGroup.FindNextEntry(C4CFN_DefFiles, sFileName: szEntryname))
943 if (hChild.OpenAsChild(pMother: &hGroup, szEntryName: szEntryname))
944 {
945 // Hack: Assume that there are sixteen sub definitions to avoid unnecessary I/O
946 int iSubMinProgress = std::min<int32_t>(a: iMaxProgress, b: iMinProgress + ((iMaxProgress - iMinProgress) * i) / 16);
947 int iSubMaxProgress = std::min<int32_t>(a: iMaxProgress, b: iMinProgress + ((iMaxProgress - iMinProgress) * (i + 1)) / 16);
948 ++i;
949 iResult += Load(hGroup&: hChild, dwLoadWhat, szLanguage, pSoundSystem, fOverload, fSearchMessage, iMinProgress: iSubMinProgress, iMaxProgress: iSubMaxProgress);
950 hChild.Close();
951 }
952
953 // load additional system scripts for def groups only
954 C4Group SysGroup;
955 char fn[_MAX_FNAME + 1] = { 0 };
956 if (!fPrimaryDef && fLoadSysGroups) if (SysGroup.OpenAsChild(pMother: &hGroup, C4CFN_System))
957 {
958 C4LangStringTable SysGroupString;
959 SysGroupString.LoadEx(szName: "StringTbl", hGroup&: SysGroup, C4CFN_ScriptStringTbl, szLanguage: Config.General.LanguageEx);
960 // load all scripts in there
961 SysGroup.ResetSearch();
962 while (SysGroup.FindNextEntry(C4CFN_ScriptFiles, sFileName: fn, iSize: nullptr, fChild: nullptr, fStartAtFilename: !!fn[0]))
963 {
964 // host will be destroyed by script engine, so drop the references
965 C4ScriptHost *scr = new C4ScriptHost();
966 scr->Reg2List(pEngine: &Game.ScriptEngine, pOwner: &Game.ScriptEngine);
967 scr->Load(szName: nullptr, hGroup&: SysGroup, szFilename: fn, szLanguage: Config.General.LanguageEx, pDef: nullptr, pLocalTable: &SysGroupString);
968 }
969
970 // if it's a physical group: watch out for changes
971 if (!SysGroup.IsPacked())
972 {
973 Game.AddDirectoryForMonitoring(directory: SysGroup.GetFullName().getData());
974 }
975
976 SysGroup.Close();
977 }
978
979 if (fThisSearchMessage) { Log(id: C4ResStrTableKey::IDS_PRC_DEFSLOADED, args&: iResult); }
980
981 // progress (could go down one level of recursion...)
982 if (iMinProgress != iMaxProgress) Game.SetInitProgress(float(iMaxProgress));
983
984 return iResult;
985}
986
987int32_t C4DefList::Load(const char *szSearch,
988 uint32_t dwLoadWhat, const char *szLanguage,
989 C4SoundSystem *pSoundSystem,
990 bool fOverload, int32_t iMinProgress, int32_t iMaxProgress)
991{
992 int32_t iResult = 0;
993
994 // Empty
995 if (!szSearch[0]) return iResult;
996
997 // Segments
998 char szSegment[_MAX_PATH + 1]; int32_t iGroupCount;
999 if (iGroupCount = SCharCount(cTarget: ';', szInStr: szSearch))
1000 {
1001 ++iGroupCount; int32_t iPrg = iMaxProgress - iMinProgress;
1002 for (int32_t cseg = 0; SCopySegment(fstr: szSearch, segn: cseg, tstr: szSegment, sepa: ';', _MAX_PATH); cseg++)
1003 iResult += Load(szSearch: szSegment, dwLoadWhat, szLanguage, pSoundSystem, fOverload,
1004 iMinProgress: iMinProgress + iPrg * cseg / iGroupCount, iMaxProgress: iMinProgress + iPrg * (cseg + 1) / iGroupCount);
1005 return iResult;
1006 }
1007
1008 // Wildcard items
1009 if (SCharCount(cTarget: '*', szInStr: szSearch))
1010 {
1011#ifdef _WIN32
1012 struct _finddata_t fdt; intptr_t fdthnd;
1013 if ((fdthnd = _findfirst(szSearch, &fdt)) < 0) return false;
1014 do
1015 {
1016 iResult += Load(fdt.name, dwLoadWhat, szLanguage, pSoundSystem, fOverload);
1017 } while (_findnext(fdthnd, &fdt) == 0);
1018 _findclose(fdthnd);
1019 // progress
1020 if (iMinProgress != iMaxProgress) Game.SetInitProgress(float(iMaxProgress));
1021#else
1022 fputs(s: "FIXME: C4DefList::Load\n", stderr);
1023#endif
1024 return iResult;
1025 }
1026
1027 // File specified with creation (currently not used)
1028 char szCreation[25 + 1];
1029 int32_t iCreation = 0;
1030 if (SCopyEnclosed(szSource: szSearch, cOpen: '(', cClose: ')', sTarget: szCreation, iSize: 25))
1031 {
1032 // Scan creation
1033 SClearFrontBack(szString: szCreation);
1034 sscanf(s: szCreation, format: "%i", &iCreation);
1035 // Extract filename
1036 SCopyUntil(szSource: szSearch, sTarget: szSegment, cUntil: '(', _MAX_PATH);
1037 SClearFrontBack(szString: szSegment);
1038 szSearch = szSegment;
1039 }
1040
1041 // Load from specified file
1042 C4Group hGroup;
1043 if (!hGroup.Open(szGroupName: szSearch))
1044 {
1045 // Specified file not found (failure)
1046 LogFatal(id: C4ResStrTableKey::IDS_PRC_DEFNOTFOUND, args&: szSearch);
1047 LoadFailure = true;
1048 return iResult;
1049 }
1050 iResult += Load(hGroup, dwLoadWhat, szLanguage, pSoundSystem, fOverload, fSearchMessage: true, iMinProgress, iMaxProgress);
1051 hGroup.Close();
1052
1053 // progress (could go down one level of recursion...)
1054 if (iMinProgress != iMaxProgress) Game.SetInitProgress(float(iMaxProgress));
1055
1056 return iResult;
1057}
1058
1059bool C4DefList::Add(C4Def *pDef, bool fOverload)
1060{
1061 if (!pDef) return false;
1062
1063 // Check old def to overload
1064 const auto old = FindDefByID(id: pDef->id);
1065 const auto hasOld = (old != Defs.end());
1066 if (hasOld && !fOverload) return false;
1067
1068 // Log overloaded def
1069 if (Config.Graphics.VerboseObjectLoading >= 1)
1070 if (hasOld)
1071 {
1072 Log(id: C4ResStrTableKey::IDS_PRC_DEFOVERLOAD, args: pDef->GetName(), args: C4IdText(id: (*old)->id));
1073 if (Config.Graphics.VerboseObjectLoading >= 2)
1074 {
1075 LogNTr(fmt: " Old def at {}", args: +(*old)->Filename);
1076 LogNTr(fmt: " Overload by {}", args: +pDef->Filename);
1077 }
1078 }
1079
1080 if (hasOld)
1081 {
1082 // Replace old def
1083 old->reset(p: pDef);
1084 }
1085 else
1086 {
1087 // Add new def
1088 Defs.emplace_back(args&: pDef);
1089 }
1090
1091 return true;
1092}
1093
1094bool C4DefList::Remove(C4ID id)
1095{
1096 if (const auto it = FindDefByID(id); it != Defs.end())
1097 {
1098 Defs.erase(position: it);
1099 return true;
1100 }
1101 return false;
1102}
1103
1104void C4DefList::Remove(C4Def *def)
1105{
1106 if (const auto it = std::find_if(first: Defs.begin(), last: Defs.end(), pred: [def](const auto &check)
1107 {
1108 return check.get() == def;
1109 }); it != Defs.end())
1110 {
1111 Defs.erase(position: it);
1112 }
1113}
1114
1115void C4DefList::Clear()
1116{
1117 Defs.clear();
1118 LoadFailure = false;
1119 Sorted = false;
1120}
1121
1122C4Def *C4DefList::ID2Def(C4ID id)
1123{
1124 if (id == C4ID_None) return nullptr;
1125 if (const auto it = FindDefByID(id); it != Defs.end())
1126 {
1127 return it->get();
1128 }
1129 return nullptr;
1130}
1131
1132int32_t C4DefList::GetIndex(C4ID id)
1133{
1134 if (const auto it = FindDefByID(id); it != Defs.end())
1135 {
1136 return std::distance(first: Defs.begin(), last: it);
1137 }
1138 return -1;
1139}
1140
1141C4Def *C4DefList::GetDef(const std::size_t index, const std::uint32_t category)
1142{
1143 if (category == C4D_All)
1144 {
1145 if (index >= Defs.size()) return nullptr;
1146 return Defs[index].get();
1147 }
1148 else
1149 {
1150 std::size_t i{0};
1151 const auto it = std::find_if(first: Defs.begin(), last: Defs.end(), pred: [&](const auto &def) {
1152 return (def->Category & category) && i++ == index; });
1153 if (it == Defs.end()) return nullptr;
1154 return it->get();
1155 }
1156}
1157
1158C4Def *C4DefList::GetByPath(const char *szPath)
1159{
1160 if (const auto it = std::find_if(first: Defs.begin(), last: Defs.end(), pred: [szPath](const auto &def)
1161 {
1162 const auto defPath = Config.AtExeRelativePath(szFilename: def->Filename);
1163 if (defPath && SEqual2NoCase(szPath, defPath))
1164 {
1165 return !szPath[SLen(defPath)] || (szPath[SLen(defPath)] == '\\' && !strchr(szPath + SLen(defPath) + 1, '\\'));
1166 }
1167 return false;
1168 }); it != Defs.end())
1169 {
1170 return it->get();
1171 }
1172 return nullptr;
1173}
1174
1175int32_t C4DefList::CheckEngineVersion(int32_t ver1, int32_t ver2, int32_t ver3, int32_t ver4, int32_t ver5)
1176{
1177 int32_t rcount = 0;
1178 Defs.erase(first: std::remove_if(first: Defs.begin(), last: Defs.end(), pred: [ver1, ver2, ver3, ver4, ver5, &rcount](const auto &def)
1179 {
1180 if (CompareVersion(def->rC4XVer[0], def->rC4XVer[1], def->rC4XVer[2], def->rC4XVer[3], def->rC4XVer[4], ver1, ver2, ver3, ver4, ver5) > 0)
1181 {
1182 ++rcount;
1183 return true;
1184 }
1185 return false;
1186 }), last: Defs.end());
1187 return rcount;
1188}
1189
1190int32_t C4DefList::CheckRequireDef()
1191{
1192 int32_t rcount = 0, rcount2;
1193 do
1194 {
1195 rcount2 = rcount;
1196 Defs.erase(first: std::remove_if(first: Defs.begin(), last: Defs.end(), pred: [this, &rcount](const auto &def)
1197 {
1198 for (const auto &it : def->RequireDef)
1199 {
1200 if (GetIndex(id: it.id) < 0)
1201 {
1202 ++rcount;
1203 return true;
1204 }
1205 }
1206 return false;
1207 }), last: Defs.end());
1208 } while (rcount != rcount2);
1209 return rcount;
1210}
1211
1212int32_t C4DefList::ColorizeByMaterial(C4MaterialMap &rMats, uint8_t bGBM)
1213{
1214 return std::count_if(first: Defs.begin(), last: Defs.end(), pred: [bGBM, &rMats](const auto &def)
1215 {
1216 return def->ColorizeByMaterial(rMats, bGBM);
1217 });
1218}
1219
1220void C4DefList::Draw(C4ID id, C4Facet &cgo, bool fSelected, int32_t iColor)
1221{
1222 if (C4Def *def = ID2Def(id); def)
1223 def->Draw(cgo, fSelected, iColor);
1224}
1225
1226bool C4DefList::Reload(C4Def *pDef, uint32_t dwLoadWhat, const char *szLanguage, C4SoundSystem *pSoundSystem)
1227{
1228 // Safety
1229 if (!pDef) return false;
1230 // backup graphics names and pointers
1231 // GfxBackup-dtor will ensure that upon loading-failure all graphics are reset to default
1232 C4DefGraphicsPtrBackup GfxBackup(&pDef->Graphics);
1233 // Clear def
1234 pDef->Clear(); // Assume filename is being kept
1235 // Reload def
1236 C4Group hGroup;
1237 if (!hGroup.Open(szGroupName: pDef->Filename)) return false;
1238 if (!pDef->Load(hGroup, dwLoadWhat, szLanguage, pSoundSystem)) return false;
1239 hGroup.Close();
1240 // rebuild quick access table
1241 SortByID();
1242 // update script engine - this will also do include callbacks
1243 Game.ScriptEngine.ReLink(rDefs: this);
1244 // restore graphics
1245 GfxBackup.AssignUpdate(pNewGraphics: &pDef->Graphics);
1246 // Success
1247 return true;
1248}
1249
1250bool C4Def::LoadPortraits(C4Group &hGroup)
1251{
1252 // reset any previous portraits
1253 Portraits = nullptr; PortraitCount = 0;
1254 // search for portraits within def graphics
1255 for (C4DefGraphics *pGfx = &Graphics; pGfx; pGfx = pGfx->GetNext())
1256 if (pGfx->IsPortrait())
1257 {
1258 // assign first portrait
1259 if (!Portraits) Portraits = pGfx->IsPortrait();
1260 // count
1261 ++PortraitCount;
1262 }
1263 return true;
1264}
1265
1266C4ValueArray *C4Def::GetCustomComponents(C4Value *pvArrayHolder, C4Object *pBuilder, C4Object *pObjInstance)
1267{
1268 // return custom components array if script function is defined and returns an array
1269 if (Script.SFn_CustomComponents)
1270 {
1271 *pvArrayHolder = Script.SFn_CustomComponents->Exec(pObj: pObjInstance, pPars: {C4VObj(pObj: pBuilder)});
1272 return pvArrayHolder->getArray();
1273 }
1274
1275 return nullptr;
1276}
1277
1278int32_t C4Def::GetComponentCount(C4ID idComponent, C4Object *pBuilder)
1279{
1280 // script overload?
1281 C4Value vArrayHolder;
1282 C4ValueArray *pArray = GetCustomComponents(pvArrayHolder: &vArrayHolder, pBuilder);
1283 if (pArray)
1284 {
1285 int32_t iCount = 0;
1286 for (int32_t i = 0; i < pArray->GetSize(); ++i)
1287 if (pArray->GetItem(index: i).getC4ID() == idComponent)
1288 ++iCount;
1289 return iCount;
1290 }
1291 // no valid script overload: Assume definition components
1292 return Component.GetIDCount(id: idComponent);
1293}
1294
1295C4ID C4Def::GetIndexedComponent(int32_t idx, C4Object *pBuilder)
1296{
1297 // script overload?
1298 C4Value vArrayHolder;
1299 C4ValueArray *pArray = GetCustomComponents(pvArrayHolder: &vArrayHolder, pBuilder);
1300 if (pArray)
1301 {
1302 // assume that components are always returned ordered ([a, a, b], but not [a, b, a])
1303 if (!pArray->GetSize()) return 0;
1304 C4ID idLast = pArray->GetItem(index: 0).getC4ID();
1305 if (!idx) return idLast;
1306 for (int32_t i = 1; i < pArray->GetSize(); ++i)
1307 {
1308 C4ID idCurr = pArray->GetItem(index: i).getC4ID();
1309 if (idCurr != idLast)
1310 {
1311 if (!--idx) return (idCurr);
1312 idLast = idCurr;
1313 }
1314 }
1315 // index out of bounds
1316 return 0;
1317 }
1318 // no valid script overload: Assume definition components
1319 return Component.GetID(index: idx);
1320}
1321
1322void C4Def::GetComponents(C4IDList *pOutList, C4Object *pObjInstance, C4Object *pBuilder)
1323{
1324 assert(pOutList);
1325 assert(!pOutList->GetNumberOfIDs());
1326 // script overload?
1327 C4Value vArrayHolder;
1328 C4ValueArray *pArray = GetCustomComponents(pvArrayHolder: &vArrayHolder, pBuilder, pObjInstance);
1329 if (pArray)
1330 {
1331 // transform array into IDList
1332 // assume that components are always returned ordered ([a, a, b], but not [a, b, a])
1333 C4ID idLast = 0; int32_t iCount = 0;
1334 for (int32_t i = 0; i < pArray->GetSize(); ++i)
1335 {
1336 C4ID idCurr = pArray->GetItem(index: i).getC4ID();
1337 if (!idCurr) continue;
1338 if (i && idCurr != idLast)
1339 {
1340 pOutList->SetIDCount(id: idLast, count: iCount, addNewID: true);
1341 iCount = 0;
1342 }
1343 idLast = idCurr;
1344 ++iCount;
1345 }
1346 if (iCount) pOutList->SetIDCount(id: idLast, count: iCount, addNewID: true);
1347 }
1348 else
1349 {
1350 // no valid script overload: Assume object or definition components
1351 if (pObjInstance)
1352 *pOutList = pObjInstance->Component;
1353 else
1354 *pOutList = Component;
1355 }
1356}
1357
1358void C4Def::IncludeDefinition(C4Def *pIncludeDef)
1359{
1360 // inherited rank infos and clonk names, if this definition doesn't have its own
1361 if (!fClonkNamesOwned) pClonkNames = pIncludeDef->pClonkNames;
1362 if (!fRankNamesOwned) pRankNames = pIncludeDef->pRankNames;
1363 if (!fRankSymbolsOwned) { pRankSymbols = pIncludeDef->pRankSymbols; iNumRankSymbols = pIncludeDef->iNumRankSymbols; }
1364}
1365
1366void C4Def::ResetIncludeDependencies()
1367{
1368 // clear all pointers into foreign defs
1369 if (!fClonkNamesOwned) pClonkNames = nullptr;
1370 if (!fRankNamesOwned) pRankNames = nullptr;
1371 if (!fRankSymbolsOwned) { pRankSymbols = nullptr; iNumRankSymbols = 0; }
1372}
1373
1374void C4Def::Picture2Facet(C4FacetExSurface &cgo, uint32_t color, int32_t xPhase)
1375{
1376 const auto scaledRect = C4Rect{PictureRect.x + xPhase * PictureRect.Wdt, PictureRect.y, PictureRect.Wdt, PictureRect.Hgt}.Scaled(scale: Scale);
1377 cgo.Set(nsfc: Graphics.GetBitmap(dwClr: color), nx: scaledRect.x, ny: scaledRect.y, nwdt: scaledRect.Wdt, nhgt: scaledRect.Hgt);
1378}
1379
1380// C4DefList
1381
1382bool C4DefList::GetFontImage(const char *szImageTag, C4Facet &rOutImgFacet)
1383{
1384 // extended: images by game
1385 C4FacetExSurface fctOut;
1386 if (!Game.DrawTextSpecImage(fctTarget&: fctOut, szSpec: szImageTag)) return false;
1387 if (fctOut.Surface == &fctOut.GetFace()) return false; // cannot use facets that are drawn on the fly right now...
1388 rOutImgFacet.Set(nsfc: fctOut.Surface, nx: fctOut.X, ny: fctOut.Y, nwdt: fctOut.Wdt, nhgt: fctOut.Hgt);
1389
1390 // done, found
1391 return true;
1392}
1393
1394void C4DefList::SortByID()
1395{
1396 // ID sorting will prevent some possible sync losses due to definition loading in different order
1397 // (it's still possible to cause desyncs by global script function or constant overloads, overloads
1398 // within the same object pack and multiple appendtos with function overloads that depend on their
1399 // order.)
1400
1401 std::sort(first: Defs.begin(), last: Defs.end(), comp: [](const auto &a, const auto &b)
1402 {
1403 return a->id < b->id;
1404 });
1405
1406 Sorted = true;
1407}
1408
1409void C4DefList::Synchronize()
1410{
1411 for (const auto &it : Defs)
1412 it->Synchronize();
1413}
1414
1415void C4DefList::ResetIncludeDependencies()
1416{
1417 for (const auto &it : Defs)
1418 it->ResetIncludeDependencies();
1419}
1420
1421std::vector<std::unique_ptr<C4Def>>::iterator C4DefList::FindDefByID(C4ID id)
1422{
1423 if (Sorted)
1424 {
1425 const auto it = std::lower_bound(first: Defs.begin(), last: Defs.end(), val: id, comp: [](const auto &def, C4ID id)
1426 {
1427 return def->id < id;
1428 });
1429 if (it != Defs.end() && (*it)->id != id)
1430 {
1431 return Defs.end();
1432 }
1433 return it;
1434 }
1435
1436 return std::find_if(first: Defs.begin(), last: Defs.end(), pred: [id](const auto &def)
1437 {
1438 return def->id == id;
1439 });
1440}
1441