| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design) |
| 5 | * Copyright (c) 2017-2021, 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 | /* Structures for object and player info components */ |
| 18 | |
| 19 | #include <C4InfoCore.h> |
| 20 | |
| 21 | #include <C4Random.h> |
| 22 | #include <C4RankSystem.h> |
| 23 | #include "StdMarkup.h" |
| 24 | #include <C4Group.h> |
| 25 | #include <C4Components.h> |
| 26 | #include <C4Wrappers.h> |
| 27 | |
| 28 | #include <C4Random.h> |
| 29 | |
| 30 | // Name File Handling |
| 31 | |
| 32 | char GetANameBuffer[C4MaxName + 1]; |
| 33 | |
| 34 | const char *GetAName(const char *szNameFile) |
| 35 | { |
| 36 | // always eat the Random-value, so having or not having a Names.txt makes no difference |
| 37 | int iName = Random(iRange: 1000); |
| 38 | |
| 39 | FILE *hNamefile; |
| 40 | |
| 41 | if (!szNameFile) return "Clonk" ; |
| 42 | if (!(hNamefile = fopen(filename: szNameFile, modes: "r" ))) return "Clonk" ; |
| 43 | |
| 44 | for (int iCnt = 0; iCnt < iName; iCnt++) |
| 45 | AdvanceFileLine(fhnd: hNamefile); |
| 46 | GetANameBuffer[0] = 0; int iLoops = 0; |
| 47 | do |
| 48 | { |
| 49 | if (!ReadFileLine(fhnd: hNamefile, tobuf: GetANameBuffer, maxlen: C4MaxName)) |
| 50 | { |
| 51 | rewind(stream: hNamefile); iLoops++; |
| 52 | } |
| 53 | } while ((iLoops < 2) && (!GetANameBuffer[0] || (GetANameBuffer[0] == '#') || (GetANameBuffer[0] == ' '))); |
| 54 | fclose(stream: hNamefile); |
| 55 | if (iLoops >= 2) return "Clonk" ; |
| 56 | return GetANameBuffer; |
| 57 | } |
| 58 | |
| 59 | // Player Info |
| 60 | |
| 61 | C4PlayerInfoCore::C4PlayerInfoCore() |
| 62 | { |
| 63 | Default(); |
| 64 | } |
| 65 | |
| 66 | void C4PlayerInfoCore::Default(C4RankSystem *pRanks) |
| 67 | { |
| 68 | Rank = 0; |
| 69 | SCopy(szSource: "Neuling" , sTarget: PrefName); |
| 70 | Comment[0] = 0; |
| 71 | if (pRanks) SCopy(szSource: pRanks->GetRankName(iRank: Rank, fReturnLastIfOver: false).getData(), sTarget: RankName); |
| 72 | else SCopy(szSource: "Rang" , sTarget: RankName); |
| 73 | Score = 0; |
| 74 | Rounds = 0; |
| 75 | RoundsWon = 0; |
| 76 | RoundsLost = 0; |
| 77 | TotalPlayingTime = 0; |
| 78 | PrefColor = 0; |
| 79 | PrefColorDw = 0xff; |
| 80 | PrefColor2Dw = 0; |
| 81 | PrefControl = C4P_Control_Keyboard1; |
| 82 | PrefPosition = 0; |
| 83 | PrefMouse = 1; |
| 84 | PrefControlStyle = 0; |
| 85 | PrefAutoContextMenu = 0; |
| 86 | ExtraData.Reset(); |
| 87 | LastRound.Default(); |
| 88 | } |
| 89 | |
| 90 | uint32_t C4PlayerInfoCore::GetPrefColorValue(int32_t iPrefColor) |
| 91 | { |
| 92 | uint32_t valRGB[12] = |
| 93 | { |
| 94 | 0x0000E8, 0xF40000, 0x00C800, 0xFCF41C, |
| 95 | 0xC48444, 0x784830, 0xA04400, 0xF08050, |
| 96 | 0x848484, 0xFFFFFF, 0x0094F8, 0xBC00C0 |
| 97 | }; |
| 98 | if (Inside<int32_t>(ival: iPrefColor, lbound: 0, rbound: 11)) |
| 99 | return valRGB[iPrefColor]; |
| 100 | return 0xAAAAAA; |
| 101 | } |
| 102 | |
| 103 | bool C4PlayerInfoCore::Load(C4Group &hGroup) |
| 104 | { |
| 105 | // New version |
| 106 | StdStrBuf Source; |
| 107 | if (hGroup.LoadEntryString(C4CFN_PlayerInfoCore, Buf&: Source)) |
| 108 | { |
| 109 | // Compile |
| 110 | StdStrBuf GrpName = hGroup.GetFullName(); GrpName.Append(DirSep C4CFN_PlayerInfoCore); |
| 111 | if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct&: *this, SrcBuf: Source, szName: GrpName.getData())) |
| 112 | return false; |
| 113 | // Pref for AutoContextMenus is still undecided: default by player's control style |
| 114 | if (PrefAutoContextMenu == -1) |
| 115 | PrefAutoContextMenu = PrefControlStyle; |
| 116 | // Determine true color from indexed pref color |
| 117 | if (!PrefColorDw) |
| 118 | PrefColorDw = GetPrefColorValue(iPrefColor: PrefColor); |
| 119 | // Validate colors |
| 120 | PrefColorDw &= 0xffffff; |
| 121 | PrefColor2Dw &= 0xffffff; |
| 122 | // Validate name |
| 123 | CMarkup::StripMarkup(szText: PrefName); |
| 124 | // Success |
| 125 | return true; |
| 126 | } |
| 127 | |
| 128 | // Old version no longer supported - sorry |
| 129 | return false; |
| 130 | } |
| 131 | |
| 132 | bool C4PlayerInfoCore::Save(C4Group &hGroup) |
| 133 | { |
| 134 | std::string source; |
| 135 | |
| 136 | StdStrBuf name{hGroup.GetFullName()}; |
| 137 | name.Append(DirSep C4CFN_PlayerInfoCore); |
| 138 | |
| 139 | if (!DecompileToBuf_Log<StdCompilerINIWrite>(TargetStruct&: *this, pOut: &source, szName: name.getData())) |
| 140 | return false; |
| 141 | StdStrBuf buf{source.c_str(), source.size()}; |
| 142 | if (!hGroup.Add(C4CFN_PlayerInfoCore, pBuffer&: buf, fChild: false, fHoldBuffer: true)) |
| 143 | return false; |
| 144 | hGroup.Delete(szFiles: "C4Player.c4b" ); |
| 145 | return true; |
| 146 | } |
| 147 | |
| 148 | void C4PlayerInfoCore::CompileFunc(StdCompiler *pComp) |
| 149 | { |
| 150 | { |
| 151 | const auto name = pComp->Name(szName: "Player" ); |
| 152 | pComp->Value(rStruct: mkNamingAdapt(toC4CStr(PrefName), szName: "Name" , rDefault: "Neuling" )); |
| 153 | pComp->Value(rStruct: mkNamingAdapt(toC4CStr(Comment), szName: "Comment" , rDefault: "" )); |
| 154 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Rank, szName: "Rank" , rDefault: 0)); |
| 155 | pComp->Value(rStruct: mkNamingAdapt(toC4CStr(RankName), szName: "RankName" , rDefault: LoadResStr(id: C4ResStrTableKey::IDS_MSG_RANK))); |
| 156 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Score, szName: "Score" , rDefault: 0)); |
| 157 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Rounds, szName: "Rounds" , rDefault: 0)); |
| 158 | pComp->Value(rStruct: mkNamingAdapt(rValue&: RoundsWon, szName: "RoundsWon" , rDefault: 0)); |
| 159 | pComp->Value(rStruct: mkNamingAdapt(rValue&: RoundsLost, szName: "RoundsLost" , rDefault: 0)); |
| 160 | pComp->Value(rStruct: mkNamingAdapt(rValue&: TotalPlayingTime, szName: "TotalPlayingTime" , rDefault: 0)); |
| 161 | pComp->Value(rStruct: mkNamingAdapt(rValue&: ExtraData, szName: "ExtraData" , rDefault: C4ValueMapData())); |
| 162 | } |
| 163 | |
| 164 | { |
| 165 | const auto name = pComp->Name(szName: "Preferences" ); |
| 166 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefColor, szName: "Color" , rDefault: 0)); |
| 167 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefColorDw, szName: "ColorDw" , rDefault: 0xffu)); |
| 168 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefColor2Dw, szName: "AlternateColorDw" , rDefault: 0u)); |
| 169 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefControl, szName: "Control" , rDefault: C4P_Control_Keyboard2)); |
| 170 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefControlStyle, szName: "AutoStopControl" , rDefault: 0)); |
| 171 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefAutoContextMenu, szName: "AutoContextMenu" , rDefault: -1)); // compiling default is -1 (if this is detected, AutoContextMenus will be defaulted by control style) |
| 172 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefPosition, szName: "Position" , rDefault: 0)); |
| 173 | pComp->Value(rStruct: mkNamingAdapt(rValue&: PrefMouse, szName: "Mouse" , rDefault: 1)); |
| 174 | } |
| 175 | |
| 176 | pComp->Value(rStruct: mkNamingAdapt(rValue&: LastRound, szName: "LastRound" )); |
| 177 | } |
| 178 | |
| 179 | // Physical Info |
| 180 | |
| 181 | struct C4PhysInfoNameMap_t { const char *szName; C4PhysicalInfo::Offset off; } C4PhysInfoNameMap[] = |
| 182 | { |
| 183 | { .szName: "Energy" , .off: &C4PhysicalInfo::Energy }, |
| 184 | { .szName: "Breath" , .off: &C4PhysicalInfo::Breath }, |
| 185 | { .szName: "Walk" , .off: &C4PhysicalInfo::Walk }, |
| 186 | { .szName: "Jump" , .off: &C4PhysicalInfo::Jump }, |
| 187 | { .szName: "Scale" , .off: &C4PhysicalInfo::Scale }, |
| 188 | { .szName: "Hangle" , .off: &C4PhysicalInfo::Hangle }, |
| 189 | { .szName: "Dig" , .off: &C4PhysicalInfo::Dig }, |
| 190 | { .szName: "Swim" , .off: &C4PhysicalInfo::Swim }, |
| 191 | { .szName: "Throw" , .off: &C4PhysicalInfo::Throw }, |
| 192 | { .szName: "Push" , .off: &C4PhysicalInfo::Push }, |
| 193 | { .szName: "Fight" , .off: &C4PhysicalInfo::Fight }, |
| 194 | { .szName: "Magic" , .off: &C4PhysicalInfo::Magic }, |
| 195 | { .szName: "Float" , .off: &C4PhysicalInfo::Float }, |
| 196 | { .szName: "CanScale" , .off: &C4PhysicalInfo::CanScale }, |
| 197 | { .szName: "CanHangle" , .off: &C4PhysicalInfo::CanHangle }, |
| 198 | { .szName: "CanDig" , .off: &C4PhysicalInfo::CanDig }, |
| 199 | { .szName: "CanConstruct" , .off: &C4PhysicalInfo::CanConstruct }, |
| 200 | { .szName: "CanChop" , .off: &C4PhysicalInfo::CanChop }, |
| 201 | { .szName: "CanFly" , .off: &C4PhysicalInfo::CanFly }, |
| 202 | { .szName: "CorrosionResist" , .off: &C4PhysicalInfo::CorrosionResist }, |
| 203 | { .szName: "BreatheWater" , .off: &C4PhysicalInfo::BreatheWater }, |
| 204 | { .szName: nullptr, .off: nullptr } |
| 205 | }; |
| 206 | |
| 207 | void C4PhysicalInfo::PromotionUpdate(int32_t iRank, bool fUpdateTrainablePhysicals, C4Def *pTrainDef) |
| 208 | { |
| 209 | if (iRank >= 0) { CanDig = 1; CanChop = 1; CanConstruct = 1; } |
| 210 | if (iRank >= 0) { CanScale = 1; } |
| 211 | if (iRank >= 0) { CanHangle = 1; } |
| 212 | Energy = std::max<int32_t>(a: Energy, b: (50 + 5 * BoundBy<int32_t>(bval: iRank, lbound: 0, rbound: 10)) * C4MaxPhysical / 100); |
| 213 | if (fUpdateTrainablePhysicals && pTrainDef) |
| 214 | { |
| 215 | // do standard training: Expect everything to be trained fully at rank 20 |
| 216 | int32_t iTrainRank = BoundBy<int32_t>(bval: iRank, lbound: 0, rbound: 20); |
| 217 | Scale = pTrainDef->Physical.Scale + (C4MaxPhysical - pTrainDef->Physical.Scale) * iTrainRank / 20; |
| 218 | Hangle = pTrainDef->Physical.Hangle + (C4MaxPhysical - pTrainDef->Physical.Hangle) * iTrainRank / 20; |
| 219 | Swim = pTrainDef->Physical.Swim + (C4MaxPhysical - pTrainDef->Physical.Swim) * iTrainRank / 20; |
| 220 | Fight = pTrainDef->Physical.Fight + (C4MaxPhysical - pTrainDef->Physical.Fight) * iTrainRank / 20; |
| 221 | // do script updates for any physicals as required (this will train stuff like magic) |
| 222 | const char *szPhysName; C4PhysicalInfo::Offset PhysOff; |
| 223 | for (int32_t iPhysIdx = 0; szPhysName = GetNameByIndex(iIdx: iPhysIdx, pmpiOut: &PhysOff); ++iPhysIdx) |
| 224 | { |
| 225 | C4Value PhysVal(this->*PhysOff, C4V_Int); |
| 226 | if (pTrainDef->Script.Call(PSF_GetFairCrewPhysical, pPars: {C4VString(strString: szPhysName), C4VInt(iVal: iRank), C4VRef(pVal: &PhysVal)})) |
| 227 | { |
| 228 | this->*PhysOff = PhysVal.getInt(); |
| 229 | } |
| 230 | } |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | C4PhysicalInfo::C4PhysicalInfo() |
| 235 | { |
| 236 | Default(); |
| 237 | } |
| 238 | |
| 239 | void C4PhysicalInfo::Default() |
| 240 | { |
| 241 | std::memset(s: this, c: 0, n: sizeof(C4PhysicalInfo)); |
| 242 | } |
| 243 | |
| 244 | bool C4PhysicalInfo::GetOffsetByName(const char *szPhysicalName, Offset *pmpiOut) |
| 245 | { |
| 246 | // query map |
| 247 | for (C4PhysInfoNameMap_t *entry = C4PhysInfoNameMap; entry->szName; ++entry) |
| 248 | if (SEqual(szStr1: entry->szName, szStr2: szPhysicalName)) |
| 249 | { |
| 250 | *pmpiOut = entry->off; |
| 251 | return true; |
| 252 | } |
| 253 | return false; |
| 254 | } |
| 255 | |
| 256 | const char *C4PhysicalInfo::GetNameByOffset(Offset mpiOff) |
| 257 | { |
| 258 | // query map |
| 259 | for (C4PhysInfoNameMap_t *entry = C4PhysInfoNameMap; entry->szName; ++entry) |
| 260 | if (entry->off == mpiOff) |
| 261 | return entry->szName; |
| 262 | return nullptr; |
| 263 | } |
| 264 | |
| 265 | const char *C4PhysicalInfo::GetNameByIndex(int32_t iIdx, Offset *pmpiOut) |
| 266 | { |
| 267 | // query map |
| 268 | if (!Inside<int32_t>(ival: iIdx, lbound: 0, rbound: sizeof(C4PhysInfoNameMap) / sizeof(C4PhysInfoNameMap_t))) return nullptr; |
| 269 | if (pmpiOut) *pmpiOut = C4PhysInfoNameMap[iIdx].off; |
| 270 | return C4PhysInfoNameMap[iIdx].szName; |
| 271 | } |
| 272 | |
| 273 | void C4PhysicalInfo::CompileFunc(StdCompiler *pComp) |
| 274 | { |
| 275 | for (C4PhysInfoNameMap_t *entry = C4PhysInfoNameMap; entry->szName; ++entry) |
| 276 | pComp->Value(rStruct: mkNamingAdapt(rValue&: (this->*(entry->off)), szName: entry->szName, rDefault: 0)); |
| 277 | } |
| 278 | |
| 279 | void C4PhysicalInfo::TrainValue(int32_t *piVal, int32_t iTrainBy, int32_t iMaxTrain) |
| 280 | { |
| 281 | // only do training if value was nonzero before (e.g., Magic for revaluated Clonks) |
| 282 | if (*piVal) |
| 283 | // do train value: Do not increase above maximum, but never decrease either |
| 284 | *piVal = (std::max)(a: (std::min)(a: *piVal + iTrainBy, b: iMaxTrain), b: *piVal); |
| 285 | } |
| 286 | |
| 287 | void C4PhysicalInfo::Train(Offset mpiOffset, int32_t iTrainBy, int32_t iMaxTrain) |
| 288 | { |
| 289 | // train own value |
| 290 | TrainValue(piVal: &(this->*mpiOffset), iTrainBy, iMaxTrain); |
| 291 | } |
| 292 | |
| 293 | bool C4PhysicalInfo::operator==(const C4PhysicalInfo &cmp) const |
| 294 | { |
| 295 | // all fields must be equal |
| 296 | for (C4PhysInfoNameMap_t *entry = C4PhysInfoNameMap; entry->szName; ++entry) |
| 297 | if (this->*(entry->off) != cmp.*(entry->off)) |
| 298 | return false; |
| 299 | return true; |
| 300 | } |
| 301 | |
| 302 | void C4TempPhysicalInfo::CompileFunc(StdCompiler *pComp) |
| 303 | { |
| 304 | C4PhysicalInfo::CompileFunc(pComp); |
| 305 | |
| 306 | pComp->Value(rStruct: mkNamingAdapt(rValue: mkSTLContainerAdapt(rTarget&: Changes), szName: "Changes" , rDefault: std::vector<C4PhysicalChange>())); |
| 307 | } |
| 308 | |
| 309 | void C4TempPhysicalInfo::Train(Offset mpiOffset, int32_t iTrainBy, int32_t iMaxTrain) |
| 310 | { |
| 311 | // train own value |
| 312 | C4PhysicalInfo::Train(mpiOffset, iTrainBy, iMaxTrain); |
| 313 | // train all temp values |
| 314 | for (std::vector<C4PhysicalChange>::iterator i = Changes.begin(); i != Changes.end(); ++i) |
| 315 | if (i->mpiOffset == mpiOffset) |
| 316 | TrainValue(piVal: &(i->PrevVal), iTrainBy, iMaxTrain); |
| 317 | } |
| 318 | |
| 319 | bool C4TempPhysicalInfo::HasChanges(C4PhysicalInfo *pRefPhysical) |
| 320 | { |
| 321 | // always return true if there are temp changes |
| 322 | if (!Changes.empty()) return true; |
| 323 | // also return true if any value deviates from the reference |
| 324 | if (pRefPhysical) |
| 325 | { |
| 326 | if (!(*pRefPhysical == *this)) return true; |
| 327 | } |
| 328 | |
| 329 | // no change known |
| 330 | return false; |
| 331 | } |
| 332 | |
| 333 | void C4TempPhysicalInfo::RegisterChange(C4PhysicalInfo::Offset mpiOffset) |
| 334 | { |
| 335 | // append physical change to list |
| 336 | Changes.push_back(x: C4PhysicalChange(this->*mpiOffset, mpiOffset)); |
| 337 | } |
| 338 | |
| 339 | bool C4TempPhysicalInfo::ResetPhysical(C4PhysicalInfo::Offset mpiOffset) |
| 340 | { |
| 341 | // search last matching physical check (should always be last if well scripted) |
| 342 | for (std::vector<C4PhysicalChange>::reverse_iterator i = Changes.rbegin(); i != Changes.rend(); ++i) |
| 343 | if ((*i).mpiOffset == mpiOffset) |
| 344 | { |
| 345 | this->*mpiOffset = (*i).PrevVal; |
| 346 | Changes.erase(position: (i + 1).base()); |
| 347 | return true; |
| 348 | } |
| 349 | |
| 350 | return false; |
| 351 | } |
| 352 | |
| 353 | void C4PhysicalChange::CompileFunc(StdCompiler *pComp) |
| 354 | { |
| 355 | // name=oldval |
| 356 | char phyn[C4MaxName + 1]; |
| 357 | const char *szPhyn = C4PhysicalInfo::GetNameByOffset(mpiOff: mpiOffset); |
| 358 | if (szPhyn) SCopy(szSource: szPhyn, sTarget: phyn, iMaxL: C4MaxName); else *phyn = '\0'; |
| 359 | pComp->Value(rStruct: mkStringAdapt(szString: phyn, iMaxLength: C4MaxName, eRawType: StdCompiler::RCT_Idtf)); |
| 360 | if (!C4PhysicalInfo::GetOffsetByName(szPhysicalName: phyn, pmpiOut: &mpiOffset)) pComp->excNotFound(message: "Physical change name \"{}\" not found." , args: phyn); |
| 361 | pComp->Separator(eSep: StdCompiler::SEP_SET); |
| 362 | pComp->Value(rInt&: PrevVal); |
| 363 | } |
| 364 | |
| 365 | // Object Info |
| 366 | |
| 367 | C4ObjectInfoCore::C4ObjectInfoCore() |
| 368 | { |
| 369 | Default(); |
| 370 | } |
| 371 | |
| 372 | void C4ObjectInfoCore::Default(C4ID n_id, |
| 373 | C4DefList *pDefs, |
| 374 | const char *cpNames) |
| 375 | { |
| 376 | // Def |
| 377 | C4Def *pDef = nullptr; |
| 378 | if (pDefs) pDef = pDefs->ID2Def(id: n_id); |
| 379 | |
| 380 | // Defaults |
| 381 | id = n_id; |
| 382 | Participation = 1; |
| 383 | Rank = 0; |
| 384 | Experience = 0; |
| 385 | Rounds = 0; |
| 386 | DeathCount = 0; |
| 387 | Birthday = 0; |
| 388 | TotalPlayingTime = 0; |
| 389 | SCopy(szSource: "Clonk" , sTarget: Name, iMaxL: C4MaxName); |
| 390 | SCopy(szSource: "Clonk" , sTarget: TypeName, iMaxL: C4MaxName); |
| 391 | sRankName.Copy(pnData: "Clonk" ); |
| 392 | sNextRankName.Clear(); |
| 393 | NextRankExp = 0; |
| 394 | DeathMessage[0] = '\0'; |
| 395 | *PortraitFile = 0; |
| 396 | Age = 0; |
| 397 | ExtraData.Reset(); |
| 398 | |
| 399 | // Type |
| 400 | if (pDef) SCopy(szSource: pDef->GetName(), sTarget: TypeName, iMaxL: C4MaxName); |
| 401 | |
| 402 | // Name |
| 403 | if (cpNames) |
| 404 | { |
| 405 | // Name file reference |
| 406 | if (SSearchNoCase(szString: cpNames, C4CFN_Names)) |
| 407 | SCopy(szSource: GetAName(szNameFile: cpNames), sTarget: Name, iMaxL: C4MaxName); |
| 408 | // Name list |
| 409 | else |
| 410 | { |
| 411 | SCopySegment(fstr: cpNames, segn: Random(iRange: SCharCount(cTarget: 0x0A, szInStr: cpNames)), tstr: Name, sepa: 0x0A, iMaxL: C4MaxName + 1); |
| 412 | SClearFrontBack(szString: Name); |
| 413 | SReplaceChar(str: Name, fc: 0x0D, tc: 0x00); |
| 414 | } |
| 415 | if (!Name[0]) SCopy(szSource: "Clonk" , sTarget: Name, iMaxL: C4MaxName); |
| 416 | } |
| 417 | |
| 418 | if (pDefs) UpdateCustomRanks(pDefs); |
| 419 | |
| 420 | // Physical |
| 421 | Physical.Default(); |
| 422 | if (pDef) Physical = pDef->Physical; |
| 423 | Physical.PromotionUpdate(iRank: Rank); |
| 424 | |
| 425 | // Old format |
| 426 | } |
| 427 | |
| 428 | void C4ObjectInfoCore::Promote(int32_t iRank, C4RankSystem &rRanks, bool fForceRankName) |
| 429 | { |
| 430 | Rank = iRank; |
| 431 | Physical.PromotionUpdate(iRank: Rank); |
| 432 | // copy new rank name if defined only, or forced to use highest defined rank for too high info ranks |
| 433 | StdStrBuf sNewRank(rRanks.GetRankName(iRank: Rank, fReturnLastIfOver: fForceRankName)); |
| 434 | if (sNewRank) sRankName.Copy(Buf2: sNewRank); |
| 435 | } |
| 436 | |
| 437 | void C4ObjectInfoCore::UpdateCustomRanks(C4DefList *pDefs) |
| 438 | { |
| 439 | assert(pDefs); |
| 440 | C4Def *pDef = pDefs->ID2Def(id); |
| 441 | if (!pDef) return; |
| 442 | if (pDef->pRankNames) |
| 443 | { |
| 444 | StdStrBuf sRank(pDef->pRankNames->GetRankName(iRank: Rank, fReturnLastIfOver: false)); |
| 445 | if (sRank) sRankName.Copy(Buf2: sRank); |
| 446 | // next rank data |
| 447 | StdStrBuf (pDef->pRankNames->GetRankName(iRank: Rank + 1, fReturnLastIfOver: false)); |
| 448 | if (sNextRank) |
| 449 | { |
| 450 | sNextRankName.Copy(Buf2: sNextRank); |
| 451 | NextRankExp = pDef->pRankNames->Experience(iRank: Rank + 1); |
| 452 | } |
| 453 | else |
| 454 | { |
| 455 | // no more promotion possible by custom rank system |
| 456 | sNextRankName.Clear(); |
| 457 | NextRankExp = C4RankSystem::EXP_NoPromotion; |
| 458 | } |
| 459 | } |
| 460 | else |
| 461 | { |
| 462 | // definition does not have custom rank names |
| 463 | sNextRankName.Clear(); |
| 464 | NextRankExp = 0; |
| 465 | } |
| 466 | } |
| 467 | |
| 468 | bool C4ObjectInfoCore::(C4RankSystem &rDefaultRanks, int32_t *, StdStrBuf *) |
| 469 | { |
| 470 | int32_t ; |
| 471 | // custom rank assigned? |
| 472 | if (NextRankExp) |
| 473 | { |
| 474 | iNextRankExp = NextRankExp; |
| 475 | if (psNextRankName) psNextRankName->Copy(Buf2: sNextRankName); |
| 476 | } |
| 477 | else |
| 478 | { |
| 479 | // no custom rank: Get from default set |
| 480 | StdStrBuf sRank(rDefaultRanks.GetRankName(iRank: Rank + 1, fReturnLastIfOver: false)); |
| 481 | if (sRank) |
| 482 | { |
| 483 | iNextRankExp = rDefaultRanks.Experience(iRank: Rank + 1); |
| 484 | if (psNextRankName) psNextRankName->Copy(Buf2: sRank); |
| 485 | } |
| 486 | else |
| 487 | // no more promotion |
| 488 | iNextRankExp = C4RankSystem::EXP_NoPromotion; |
| 489 | } |
| 490 | // return result |
| 491 | if (piNextRankExp) *piNextRankExp = iNextRankExp; |
| 492 | // return value is whether additional promotion is possible |
| 493 | return iNextRankExp != C4RankSystem::EXP_NoPromotion; |
| 494 | } |
| 495 | |
| 496 | bool C4ObjectInfoCore::Load(C4Group &hGroup) |
| 497 | { |
| 498 | StdStrBuf Source; |
| 499 | return hGroup.LoadEntryString(C4CFN_ObjectInfoCore, Buf&: Source) && |
| 500 | Compile(szSource: Source.getData()); |
| 501 | } |
| 502 | |
| 503 | bool C4ObjectInfoCore::Save(C4Group &hGroup, C4DefList *pDefs) |
| 504 | { |
| 505 | // rank overload by def: Update any NextRank-stuff |
| 506 | if (pDefs) UpdateCustomRanks(pDefs); |
| 507 | |
| 508 | std::string buf; |
| 509 | try |
| 510 | { |
| 511 | buf = DecompileToBuf<StdCompilerINIWrite>(SrcStruct: mkNamingAdapt(rValue&: *this, szName: "ObjectInfo" )); |
| 512 | } |
| 513 | catch (const StdCompiler::Exception &) |
| 514 | { |
| 515 | return false; |
| 516 | } |
| 517 | |
| 518 | StdStrBuf copy{buf.c_str(), buf.size()}; |
| 519 | if (!hGroup.Add(C4CFN_ObjectInfoCore, pBuffer&: copy, fChild: false, fHoldBuffer: true)) |
| 520 | { |
| 521 | return false; |
| 522 | } |
| 523 | return true; |
| 524 | } |
| 525 | |
| 526 | void C4ObjectInfoCore::CompileFunc(StdCompiler *pComp) |
| 527 | { |
| 528 | pComp->Value(rStruct: mkNamingAdapt(rValue: mkC4IDAdapt(rValue&: id), szName: "id" , rDefault: C4ID_None)); |
| 529 | pComp->Value(rStruct: mkNamingAdapt(toC4CStr(Name), szName: "Name" , rDefault: "Clonk" )); |
| 530 | pComp->Value(rStruct: mkNamingAdapt(toC4CStr(DeathMessage), szName: "DeathMessage" , rDefault: "" )); |
| 531 | pComp->Value(rStruct: mkNamingAdapt(toC4CStr(PortraitFile), szName: "PortraitFile" , rDefault: "" )); |
| 532 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Rank, szName: "Rank" , rDefault: 0)); |
| 533 | pComp->Value(rStruct: mkNamingAdapt(rValue&: sRankName, szName: "RankName" , rDefault: "Clonk" )); |
| 534 | pComp->Value(rStruct: mkNamingAdapt(rValue&: sNextRankName, szName: "NextRankName" , rDefault: "" )); |
| 535 | pComp->Value(rStruct: mkNamingAdapt(toC4CStr(TypeName), szName: "TypeName" , rDefault: "Clonk" )); |
| 536 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Participation, szName: "Participation" , rDefault: 1)); |
| 537 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Experience, szName: "Experience" , rDefault: 0)); |
| 538 | pComp->Value(rStruct: mkNamingAdapt(rValue&: NextRankExp, szName: "NextRankExp" , rDefault: 0)); |
| 539 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Rounds, szName: "Rounds" , rDefault: 0)); |
| 540 | pComp->Value(rStruct: mkNamingAdapt(rValue&: DeathCount, szName: "DeathCount" , rDefault: 0)); |
| 541 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Birthday, szName: "Birthday" , rDefault: 0)); |
| 542 | pComp->Value(rStruct: mkNamingAdapt(rValue&: TotalPlayingTime, szName: "TotalPlayingTime" , rDefault: 0)); |
| 543 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Age, szName: "Age" , rDefault: 0)); |
| 544 | pComp->Value(rStruct: mkNamingAdapt(rValue&: ExtraData, szName: "ExtraData" , rDefault: C4ValueMapData())); |
| 545 | |
| 546 | pComp->FollowName(szName: "Physical" ); |
| 547 | pComp->Value(rStruct&: Physical); |
| 548 | } |
| 549 | |
| 550 | bool C4ObjectInfoCore::Compile(const char *szSource) |
| 551 | { |
| 552 | bool ret = CompileFromBuf_LogWarn<StdCompilerINIRead>( |
| 553 | TargetStruct: mkNamingAdapt(rValue&: *this, szName: "ObjectInfo" ), |
| 554 | SrcBuf: StdStrBuf::MakeRef(str: szSource), |
| 555 | szName: "ObjectInfo" ); |
| 556 | // Do a promotion update to set physicals right |
| 557 | Physical.PromotionUpdate(iRank: Rank); |
| 558 | // DeathMessages are not allowed to stay forever |
| 559 | if ('@' == DeathMessage[0]) DeathMessage[0] = ' '; |
| 560 | return ret; |
| 561 | } |
| 562 | |
| 563 | // Round Info |
| 564 | |
| 565 | void C4RoundResult::Default() |
| 566 | { |
| 567 | *this = {}; |
| 568 | } |
| 569 | |
| 570 | void C4RoundResult::CompileFunc(StdCompiler *pComp) |
| 571 | { |
| 572 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Title, szName: "Title" , rDefault: "" )); |
| 573 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Date, szName: "Date" , rDefault: 0u)); |
| 574 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Duration, szName: "Duration" , rDefault: 0)); |
| 575 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Won, szName: "Won" , rDefault: 0)); |
| 576 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Score, szName: "Score" , rDefault: 0)); |
| 577 | pComp->Value(rStruct: mkNamingAdapt(rValue&: FinalScore, szName: "FinalScore" , rDefault: 0)); |
| 578 | pComp->Value(rStruct: mkNamingAdapt(rValue&: TotalScore, szName: "TotalScore" , rDefault: 0)); |
| 579 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Bonus, szName: "Bonus" , rDefault: 0)); |
| 580 | pComp->Value(rStruct: mkNamingAdapt(rValue&: Level, szName: "Level" , rDefault: 0)); |
| 581 | } |
| 582 | |