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/* Dynamic list for crew member info */
18
19#include <C4Include.h>
20#include <C4ObjectInfoList.h>
21
22#include <C4ObjectInfo.h>
23#include <C4Components.h>
24#include <C4Game.h>
25#include <C4RankSystem.h>
26#include <C4Config.h>
27#include <C4Wrappers.h>
28
29C4ObjectInfoList::C4ObjectInfoList()
30{
31 Default();
32}
33
34C4ObjectInfoList::~C4ObjectInfoList()
35{
36 Clear();
37}
38
39void C4ObjectInfoList::Default()
40{
41 First = nullptr;
42 iNumCreated = 0;
43}
44
45void C4ObjectInfoList::Clear()
46{
47 C4ObjectInfo *next;
48 while (First)
49 {
50 next = First->Next;
51 delete First;
52 First = next;
53 }
54}
55
56int32_t C4ObjectInfoList::Load(C4Group &hGroup, bool fLoadPortraits)
57{
58 int32_t infn = 0;
59 char entryname[256 + 1];
60
61 // Search all c4i files
62 hGroup.ResetSearch();
63 while (hGroup.FindNextEntry(C4CFN_ObjectInfoFiles, sFileName: entryname))
64 {
65 auto info = std::make_unique<C4ObjectInfo>();
66 if (info->Load(hMother&: hGroup, szEntryname: entryname, fLoadPortrait: fLoadPortraits))
67 {
68 Add(pInfo: info.release());
69 ++infn;
70 }
71 }
72
73 // Search subfolders
74 hGroup.ResetSearch();
75 while (hGroup.FindNextEntry(szWildCard: "*", sFileName: entryname))
76 {
77 C4Group ItemGroup;
78 if (ItemGroup.OpenAsChild(pMother: &hGroup, szEntryName: entryname))
79 Load(hGroup&: ItemGroup, fLoadPortraits);
80 }
81
82 return infn;
83}
84
85bool C4ObjectInfoList::Add(C4ObjectInfo *pInfo)
86{
87 if (!pInfo) return false;
88 pInfo->Next = First;
89 First = pInfo;
90 return true;
91}
92
93void C4ObjectInfoList::MakeValidName(char *sName)
94{
95 char tstr[_MAX_PATH];
96 int32_t iname, namelen = SLen(sptr: sName);
97 for (iname = 2; NameExists(szName: sName); iname++)
98 {
99 *std::to_chars(first: tstr, last: tstr + std::size(tstr) - 1, value: iname).ptr = '\0';
100 SCopy(szSource: tstr, sTarget: sName + std::min<int32_t>(a: namelen, b: C4MaxName - SLen(sptr: tstr)));
101 }
102}
103
104bool C4ObjectInfoList::NameExists(const char *szName)
105{
106 C4ObjectInfo *cinf;
107 for (cinf = First; cinf; cinf = cinf->Next)
108 if (SEqualNoCase(szStr1: szName, szStr2: cinf->Name))
109 return true;
110 return false;
111}
112
113C4ObjectInfo *C4ObjectInfoList::GetIdle(C4ID c_id, C4DefList &rDefs)
114{
115 C4Def *pDef;
116 C4ObjectInfo *pInfo;
117 C4ObjectInfo *pHiExp = nullptr;
118
119 // Search list
120 for (pInfo = First; pInfo; pInfo = pInfo->Next)
121 // Valid only
122 if (pDef = rDefs.ID2Def(id: pInfo->id))
123 // Use standard crew or matching id
124 if ((!c_id && !pDef->NativeCrew) || (pInfo->id == c_id))
125 // Participating and not in action
126 if (pInfo->Participation) if (!pInfo->InAction)
127 // Not dead
128 if (!pInfo->HasDied)
129 // Highest experience
130 if (!pHiExp || (pInfo->Experience > pHiExp->Experience))
131 // Set this
132 pHiExp = pInfo;
133
134 // Found
135 if (pHiExp)
136 {
137 pHiExp->Recruit();
138 return pHiExp;
139 }
140
141 return nullptr;
142}
143
144C4ObjectInfo *C4ObjectInfoList::New(C4ID n_id, C4DefList *pDefs, const char *cpNames)
145{
146 // Create new info object
147 auto info = std::make_unique<C4ObjectInfo>();
148
149 // Default type clonk if none specified
150 if (n_id == C4ID_None) n_id = C4ID_Clonk;
151
152 // Check type valid and def available
153 C4Def *def{nullptr};
154 if (!pDefs || !(def = pDefs->ID2Def(id: n_id)))
155 {
156 return nullptr;
157 }
158
159 // Override name source by definition
160 if (def->pClonkNames)
161 {
162 cpNames = def->pClonkNames->GetData();
163 }
164
165 // Default by type
166 static_cast<C4ObjectInfoCore *>(info.get())->Default(n_id, pDefs, cpNames);
167
168 // Set birthday
169 info->Birthday = static_cast<int32_t>(time(timer: nullptr));
170
171 // Make valid names
172 MakeValidName(sName: info->Name);
173
174 // Add new portrait (permanently w/o copying file)
175 if (Config.Graphics.AddNewCrewPortraits)
176 {
177 info->SetRandomPortrait(idSourceDef: 0, fAssignPermanently: true, fCopyFile: false);
178 }
179
180 // Add
181 Add(pInfo: info.get());
182 ++iNumCreated;
183
184 return info.release();
185}
186
187void C4ObjectInfoList::Evaluate()
188{
189 C4ObjectInfo *cinf;
190 for (cinf = First; cinf; cinf = cinf->Next)
191 cinf->Evaluate();
192}
193
194bool C4ObjectInfoList::Save(C4Group &hGroup, bool fSavegame, bool fStoreTiny, C4DefList *pDefs)
195{
196 // Save in opposite order (for identical crew order on load)
197 C4ObjectInfo *pInfo;
198 for (pInfo = GetLast(); pInfo; pInfo = GetPrevious(pInfo))
199 {
200 // don't safe TemporaryCrew in regular player files
201 if (!fSavegame)
202 {
203 C4Def *pDef = C4Id2Def(id: pInfo->id);
204 if (pDef) if (pDef->TemporaryCrew) continue;
205 }
206 // save
207 if (!pInfo->Save(hGroup, fStoreTiny, pDefs))
208 return false;
209 }
210 return true;
211}
212
213C4ObjectInfo *C4ObjectInfoList::GetIdle(const char *szByName)
214{
215 C4ObjectInfo *pInfo;
216 // Find matching name, participating, alive and not in action
217 for (pInfo = First; pInfo; pInfo = pInfo->Next)
218 if (SEqualNoCase(szStr1: pInfo->Name, szStr2: szByName))
219 if (pInfo->Participation) if (!pInfo->InAction)
220 if (!pInfo->HasDied)
221 {
222 pInfo->Recruit();
223 return pInfo;
224 }
225 return nullptr;
226}
227
228void C4ObjectInfoList::DetachFromObjects()
229{
230 C4ObjectInfo *cinf;
231 for (cinf = First; cinf; cinf = cinf->Next)
232 Game.Objects.ClearInfo(pInfo: cinf);
233}
234
235C4ObjectInfo *C4ObjectInfoList::GetLast()
236{
237 C4ObjectInfo *cInfo = First;
238 while (cInfo && cInfo->Next) cInfo = cInfo->Next;
239 return cInfo;
240}
241
242C4ObjectInfo *C4ObjectInfoList::GetPrevious(C4ObjectInfo *pInfo)
243{
244 for (C4ObjectInfo *cInfo = First; cInfo; cInfo = cInfo->Next)
245 if (cInfo->Next == pInfo)
246 return cInfo;
247 return nullptr;
248}
249
250bool C4ObjectInfoList::IsElement(C4ObjectInfo *pInfo)
251{
252 for (C4ObjectInfo *cInfo = First; cInfo; cInfo = cInfo->Next)
253 if (cInfo == pInfo) return true;
254 return false;
255}
256
257void C4ObjectInfoList::Strip(C4DefList &rDefs)
258{
259 C4ObjectInfo *pInfo, *pPrev;
260 // Search list
261 for (pInfo = First, pPrev = nullptr; pInfo;)
262 {
263 // Invalid?
264 if (!rDefs.ID2Def(id: pInfo->id))
265 {
266 C4ObjectInfo *pDelete = pInfo;
267 if (pPrev) pPrev->Next = pInfo->Next; else First = pInfo->Next;
268 pInfo = pInfo->Next;
269 delete pDelete;
270 }
271 else
272 {
273 pPrev = pInfo;
274 pInfo = pInfo->Next;
275 }
276 }
277}
278