| 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 | |
| 38 | const 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 | |
| 62 | C4ActionDef::C4ActionDef() : Procedure{DFA_NONE} {} |
| 63 | |
| 64 | void C4ActionDef::Default() |
| 65 | { |
| 66 | *this = {}; |
| 67 | } |
| 68 | |
| 69 | void 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 | |
| 117 | C4DefCore::C4DefCore() |
| 118 | { |
| 119 | Default(); |
| 120 | } |
| 121 | |
| 122 | void 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 | |
| 206 | bool 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 | |
| 246 | bool 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 | |
| 251 | void 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 | |
| 465 | C4Def::C4Def() |
| 466 | { |
| 467 | Graphics.pDef = this; |
| 468 | Default(); |
| 469 | } |
| 470 | |
| 471 | void 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 | |
| 496 | C4Def::~C4Def() |
| 497 | { |
| 498 | Clear(); |
| 499 | } |
| 500 | |
| 501 | void 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 | |
| 521 | bool 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 | |
| 746 | bool 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 | |
| 773 | void 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 | |
| 801 | bool 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 | |
| 813 | void 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 | |
| 839 | int32_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 | |
| 860 | C4PhysicalInfo *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 | |
| 877 | void C4Def::ClearFairCrewPhysicals() |
| 878 | { |
| 879 | // invalidate physicals so the next call to GetFairCrewPhysicals will |
| 880 | // reacreate them |
| 881 | delete pFairCrewPhysical; pFairCrewPhysical = nullptr; |
| 882 | } |
| 883 | |
| 884 | void 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 | |
| 893 | C4DefList::C4DefList() |
| 894 | { |
| 895 | Clear(); |
| 896 | } |
| 897 | |
| 898 | C4DefList::~C4DefList() |
| 899 | { |
| 900 | Clear(); |
| 901 | } |
| 902 | |
| 903 | int32_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 | |
| 987 | int32_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 | |
| 1059 | bool 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 | |
| 1094 | bool 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 | |
| 1104 | void 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 | |
| 1115 | void C4DefList::Clear() |
| 1116 | { |
| 1117 | Defs.clear(); |
| 1118 | LoadFailure = false; |
| 1119 | Sorted = false; |
| 1120 | } |
| 1121 | |
| 1122 | C4Def *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 | |
| 1132 | int32_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 | |
| 1141 | C4Def *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 | |
| 1158 | C4Def *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 | |
| 1175 | int32_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 | |
| 1190 | int32_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 | |
| 1212 | int32_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 | |
| 1220 | void 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 | |
| 1226 | bool 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 | |
| 1250 | bool 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 | |
| 1266 | C4ValueArray *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 | |
| 1278 | int32_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 | |
| 1295 | C4ID 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 | |
| 1322 | void 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 | |
| 1358 | void 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 | |
| 1366 | void 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 | |
| 1374 | void 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 | |
| 1382 | bool 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 | |
| 1394 | void 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 | |
| 1409 | void C4DefList::Synchronize() |
| 1410 | { |
| 1411 | for (const auto &it : Defs) |
| 1412 | it->Synchronize(); |
| 1413 | } |
| 1414 | |
| 1415 | void C4DefList::ResetIncludeDependencies() |
| 1416 | { |
| 1417 | for (const auto &it : Defs) |
| 1418 | it->ResetIncludeDependencies(); |
| 1419 | } |
| 1420 | |
| 1421 | std::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 | |