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
32C4RankSystem::C4RankSystem()
33{
34 Default();
35}
36
37int 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
96bool 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
184StdStrBuf 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
226int 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
232int C4RankSystem::RankByExperience(int iExp)
233{
234 int iRank = 0;
235 while (Experience(iRank: iRank + 1) <= iExp) ++iRank;
236 return iRank;
237}
238
239void 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
250void 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
261bool 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