| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design) |
| 5 | * Copyright (c) 2017-2020, 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 | /* Rank list for players or crew members */ |
| 18 | |
| 19 | #include <C4Include.h> |
| 20 | #include <C4RankSystem.h> |
| 21 | |
| 22 | #include <C4Log.h> |
| 23 | #include <C4Group.h> |
| 24 | #include <C4ComponentHost.h> |
| 25 | #include <C4FacetEx.h> |
| 26 | #include <C4Game.h> |
| 27 | |
| 28 | #ifdef _WIN32 |
| 29 | #include "StdRegistry.h" |
| 30 | #endif |
| 31 | |
| 32 | C4RankSystem::C4RankSystem() |
| 33 | { |
| 34 | Default(); |
| 35 | } |
| 36 | |
| 37 | int C4RankSystem::Init(const char *szRegister, |
| 38 | const char *szDefRanks, |
| 39 | int iRankBase) |
| 40 | { |
| 41 | // Init |
| 42 | SCopy(szSource: szRegister, sTarget: Register, iMaxL: 256); |
| 43 | RankBase = iRankBase; |
| 44 | |
| 45 | // Check registry for present rank names and set defaults |
| 46 | #ifdef _WIN32 |
| 47 | int crank = 0; |
| 48 | char rankname[C4MaxName + 1]; |
| 49 | std::string keyname; |
| 50 | bool Checking = true; |
| 51 | while (Checking) |
| 52 | { |
| 53 | keyname = std::format("Rank{:03}" , crank + 1); |
| 54 | if (GetRegistryString(Register, keyname.c_str(), rankname, C4MaxName + 1)) |
| 55 | { |
| 56 | // Rank present |
| 57 | crank++; |
| 58 | } |
| 59 | else |
| 60 | { |
| 61 | // Rank not defined, check for default |
| 62 | if (SCopySegment(szDefRanks, crank, rankname, '|', C4MaxName) |
| 63 | && SetRegistryString(Register, keyname.c_str(), rankname)) |
| 64 | crank++; |
| 65 | else |
| 66 | Checking = false; |
| 67 | } |
| 68 | } |
| 69 | return crank; |
| 70 | #else |
| 71 | // clear any loaded rank names |
| 72 | Clear(); |
| 73 | if (!szDefRanks) return 0; |
| 74 | // make a copy |
| 75 | szRankNames = new char[strlen(s: szDefRanks) + 1]; |
| 76 | strcpy(dest: szRankNames, src: szDefRanks); |
| 77 | // split into substrings by replacing the | with zeros |
| 78 | for (char *p = szRankNames; *p; ++p) if (*p == '|') |
| 79 | { |
| 80 | *p = 0; |
| 81 | ++iRankNum; |
| 82 | } |
| 83 | ++iRankNum; // The last rank is already terminated by zero |
| 84 | // build a list of substrings |
| 85 | pszRankNames = new char *[iRankNum]; |
| 86 | char *p = szRankNames; |
| 87 | for (int i = 0; i < iRankNum; ++i) |
| 88 | { |
| 89 | pszRankNames[i] = p; |
| 90 | p += strlen(s: p) + 1; |
| 91 | } |
| 92 | return iRankNum; |
| 93 | #endif |
| 94 | } |
| 95 | |
| 96 | bool C4RankSystem::Load(C4Group &hGroup, const char *szFilenames, int DefRankBase, const char *szLanguage) |
| 97 | { |
| 98 | // clear any loaded rank names |
| 99 | Clear(); |
| 100 | assert(szFilenames); assert(szLanguage); |
| 101 | // load new |
| 102 | C4ComponentHost Ranks; |
| 103 | if (!Ranks.LoadEx(szName: "Ranks" , hGroup, szFilename: szFilenames, szLanguage)) return false; |
| 104 | size_t iSize = Ranks.GetDataSize(); |
| 105 | if (!iSize) return false; |
| 106 | szRankNames = new char[iSize + 1]; |
| 107 | memcpy(dest: szRankNames, src: Ranks.GetData(), n: iSize * sizeof(char)); |
| 108 | szRankNames[iSize] = 0; |
| 109 | Ranks.Close(); |
| 110 | // replace line breaks by zero-chars |
| 111 | unsigned int i = 0; |
| 112 | for (; i < iSize; ++i) |
| 113 | if (szRankNames[i] == 0x0A || szRankNames[i] == 0x0D) szRankNames[i] = 0; |
| 114 | // count names |
| 115 | char *pRank0 = szRankNames, *pPos = szRankNames; |
| 116 | for (i = 0; i < iSize; ++i, ++pPos) |
| 117 | if (!*pPos) |
| 118 | { |
| 119 | // zero-character found: content? |
| 120 | if (pPos - pRank0 > 0) |
| 121 | { |
| 122 | // rank extension? |
| 123 | if (*pRank0 == '*') |
| 124 | ++iRankExtNum; |
| 125 | // no comment? |
| 126 | else if (*pRank0 != '#') |
| 127 | // no setting? |
| 128 | if (SCharPos(cTarget: '=', szInStr: pRank0) < 0) |
| 129 | // count as name! |
| 130 | ++iRankNum; |
| 131 | } |
| 132 | // advance pos |
| 133 | pRank0 = pPos + 1; |
| 134 | } |
| 135 | // safety |
| 136 | if (!iRankNum) { Clear(); return false; } |
| 137 | // set default rank base |
| 138 | RankBase = DefRankBase; |
| 139 | // alloc lists |
| 140 | pszRankNames = new char *[iRankNum]; |
| 141 | if (iRankExtNum) pszRankExtensions = new char *[iRankExtNum]; |
| 142 | // fill list with names |
| 143 | // count names |
| 144 | pRank0 = szRankNames; pPos = szRankNames; |
| 145 | char **pszCurrRank = pszRankNames; |
| 146 | char **pszCurrRankExt = pszRankExtensions; |
| 147 | for (i = 0; i < iSize; ++i, ++pPos) |
| 148 | if (!*pPos) |
| 149 | { |
| 150 | // zero-character found: content? |
| 151 | if (pPos - pRank0 > 0) |
| 152 | // extension? |
| 153 | if (*pRank0 == '*') |
| 154 | { |
| 155 | *pszCurrRankExt++ = pRank0 + 1; |
| 156 | } |
| 157 | // no comment? |
| 158 | else if (*pRank0 != '#') |
| 159 | { |
| 160 | // check if it's a setting |
| 161 | int iEqPos = SCharPos(cTarget: '=', szInStr: pRank0); |
| 162 | if (iEqPos >= 0) |
| 163 | { |
| 164 | // get name and value of setting |
| 165 | pRank0[iEqPos] = 0; char *szValue = pRank0 + iEqPos + 1; |
| 166 | if (SEqual(szStr1: pRank0, szStr2: "Base" )) |
| 167 | // get rankbase |
| 168 | // note that invalid numbers may cause desyncs here...not very likely though :) |
| 169 | sscanf(s: szValue, format: "%d" , &RankBase); |
| 170 | } |
| 171 | else |
| 172 | // yeeehaa! it's a name! store it, store it! |
| 173 | *pszCurrRank++ = pRank0; |
| 174 | } |
| 175 | // advance pos |
| 176 | pRank0 = pPos + 1; |
| 177 | } |
| 178 | // check rankbase |
| 179 | if (!RankBase) RankBase = 1000; |
| 180 | // ranks read, success |
| 181 | return true; |
| 182 | } |
| 183 | |
| 184 | StdStrBuf C4RankSystem::GetRankName(int iRank, bool fReturnLastIfOver) |
| 185 | { |
| 186 | if (iRank < 0) return StdStrBuf(); |
| 187 | // if a new-style ranklist is loaded, seek there |
| 188 | if (pszRankNames) |
| 189 | { |
| 190 | if (iRankNum <= 0) return StdStrBuf(); |
| 191 | // overflow check |
| 192 | if (iRank >= iRankNum * (iRankExtNum + 1)) |
| 193 | { |
| 194 | // rank undefined: Fallback to last rank |
| 195 | if (!fReturnLastIfOver) return StdStrBuf(); |
| 196 | iRank = iRankNum * (iRankExtNum + 1) - 1; |
| 197 | } |
| 198 | StdStrBuf sResult; |
| 199 | if (iRank >= iRankNum) |
| 200 | { |
| 201 | // extended rank composed of two parts |
| 202 | int iExtension = iRank / iRankNum - 1; |
| 203 | iRank = iRank % iRankNum; |
| 204 | sResult.Copy(pnData: fmt::sprintf(fmt: pszRankExtensions[iExtension], args: pszRankNames[iRank]).c_str()); |
| 205 | } |
| 206 | else |
| 207 | { |
| 208 | // simple rank |
| 209 | sResult.Ref(pnData: pszRankNames[iRank]); |
| 210 | } |
| 211 | return sResult; |
| 212 | } |
| 213 | #ifdef _WIN32 |
| 214 | // old-style registry fallback |
| 215 | while (iRank >= 0) |
| 216 | { |
| 217 | if (GetRegistryString(Register, std::format("Rank{:03}" , iRank + 1).c_str(), RankName, C4MaxName + 1)) |
| 218 | return StdStrBuf(RankName); |
| 219 | if (!fReturnLastIfOver) return StdStrBuf(); |
| 220 | --iRank; |
| 221 | } |
| 222 | #endif |
| 223 | return StdStrBuf(); |
| 224 | } |
| 225 | |
| 226 | int C4RankSystem::Experience(int iRank) |
| 227 | { |
| 228 | if (iRank < 0) return 0; |
| 229 | return static_cast<int>(pow(x: double(iRank), y: 1.5) * RankBase); |
| 230 | } |
| 231 | |
| 232 | int C4RankSystem::RankByExperience(int iExp) |
| 233 | { |
| 234 | int iRank = 0; |
| 235 | while (Experience(iRank: iRank + 1) <= iExp) ++iRank; |
| 236 | return iRank; |
| 237 | } |
| 238 | |
| 239 | void C4RankSystem::Clear() |
| 240 | { |
| 241 | // clear any loaded rank names |
| 242 | delete[] pszRankNames; pszRankNames = nullptr; |
| 243 | delete[] pszRankExtensions; pszRankExtensions = nullptr; |
| 244 | delete[] szRankNames; szRankNames = nullptr; |
| 245 | // reset number of ranks |
| 246 | iRankNum = 0; |
| 247 | iRankExtNum = 0; |
| 248 | } |
| 249 | |
| 250 | void C4RankSystem::Default() |
| 251 | { |
| 252 | Register[0] = 0; |
| 253 | RankName[0] = 0; |
| 254 | RankBase = 1000; |
| 255 | pszRankNames = nullptr; |
| 256 | szRankNames = nullptr; |
| 257 | pszRankExtensions = nullptr; |
| 258 | iRankExtNum = 0; |
| 259 | } |
| 260 | |
| 261 | bool C4RankSystem::DrawRankSymbol(C4FacetExSurface *fctSymbol, int32_t iRank, C4FacetEx *pfctRankSymbols, int32_t iRankSymbolCount, bool fOwnSurface, int32_t iXOff, C4Facet *cgoDrawDirect) |
| 262 | { |
| 263 | // safety |
| 264 | if (iRank < 0) iRank = 0; |
| 265 | // symbol by rank |
| 266 | int32_t iMaxRankSym, Q; |
| 267 | if (pfctRankSymbols->GetPhaseNum(rX&: iMaxRankSym, rY&: Q)) |
| 268 | { |
| 269 | if (!iMaxRankSym) iMaxRankSym = 1; |
| 270 | int32_t iBaseRank = iRank % iRankSymbolCount; |
| 271 | if (iRank / iRankSymbolCount) |
| 272 | { |
| 273 | // extended rank: draw |
| 274 | // extension star defaults to captain star; but use extended symbols if they are in the gfx |
| 275 | C4Facet fctExtended = static_cast<const C4Facet &>(Game.GraphicsResource.fctCaptain); |
| 276 | if (iMaxRankSym > iRankSymbolCount) |
| 277 | { |
| 278 | int32_t iExtended = iRank / iRankSymbolCount - 1 + iRankSymbolCount; |
| 279 | if (iExtended >= iMaxRankSym) |
| 280 | { |
| 281 | // max rank exceeded |
| 282 | iExtended = iMaxRankSym - 1; |
| 283 | iBaseRank = iRankSymbolCount - 1; |
| 284 | } |
| 285 | fctExtended = static_cast<const C4Facet &>(pfctRankSymbols->GetPhase(iPhaseX: iExtended)); |
| 286 | } |
| 287 | int32_t iSize = pfctRankSymbols->Wdt; |
| 288 | if (!cgoDrawDirect) |
| 289 | { |
| 290 | fctSymbol->Create(iWdt: iSize, iHgt: iSize); |
| 291 | pfctRankSymbols->DrawX(sfcTarget: fctSymbol->Surface, iX: 0, iY: 0, iWdt: iSize, iHgt: iSize, iPhaseX: iBaseRank); |
| 292 | fctExtended.DrawX(sfcTarget: fctSymbol->Surface, iX: 0, iY: 0, iWdt: iSize * 2 / 3, iHgt: iSize * 2 / 3); |
| 293 | } |
| 294 | else |
| 295 | { |
| 296 | pfctRankSymbols->Draw(sfcTarget: cgoDrawDirect->Surface, iX: cgoDrawDirect->X + iXOff, iY: cgoDrawDirect->Y, iPhaseX: iBaseRank); |
| 297 | fctExtended.Draw(sfcTarget: cgoDrawDirect->Surface, iX: cgoDrawDirect->X + iXOff - 4, iY: cgoDrawDirect->Y - 3); |
| 298 | } |
| 299 | } |
| 300 | else |
| 301 | { |
| 302 | // regular rank: copy facet |
| 303 | if (cgoDrawDirect) |
| 304 | { |
| 305 | pfctRankSymbols->Draw(sfcTarget: cgoDrawDirect->Surface, iX: cgoDrawDirect->X + iXOff, iY: cgoDrawDirect->Y, iPhaseX: iBaseRank); |
| 306 | } |
| 307 | else if (fOwnSurface) |
| 308 | { |
| 309 | int32_t iSize = pfctRankSymbols->Wdt; |
| 310 | fctSymbol->Create(iWdt: iSize, iHgt: iSize); |
| 311 | pfctRankSymbols->DrawX(sfcTarget: fctSymbol->Surface, iX: 0, iY: 0, iWdt: iSize, iHgt: iSize, iPhaseX: iBaseRank); |
| 312 | } |
| 313 | else |
| 314 | { |
| 315 | fctSymbol->Set(pfctRankSymbols->GetPhase(iPhaseX: iBaseRank)); |
| 316 | } |
| 317 | } |
| 318 | return true; |
| 319 | } |
| 320 | return false; |
| 321 | } |
| 322 | |