| 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 | |
| 29 | C4ObjectInfoList::C4ObjectInfoList() |
| 30 | { |
| 31 | Default(); |
| 32 | } |
| 33 | |
| 34 | C4ObjectInfoList::~C4ObjectInfoList() |
| 35 | { |
| 36 | Clear(); |
| 37 | } |
| 38 | |
| 39 | void C4ObjectInfoList::Default() |
| 40 | { |
| 41 | First = nullptr; |
| 42 | iNumCreated = 0; |
| 43 | } |
| 44 | |
| 45 | void C4ObjectInfoList::Clear() |
| 46 | { |
| 47 | C4ObjectInfo *next; |
| 48 | while (First) |
| 49 | { |
| 50 | next = First->Next; |
| 51 | delete First; |
| 52 | First = next; |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | int32_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 | |
| 85 | bool C4ObjectInfoList::Add(C4ObjectInfo *pInfo) |
| 86 | { |
| 87 | if (!pInfo) return false; |
| 88 | pInfo->Next = First; |
| 89 | First = pInfo; |
| 90 | return true; |
| 91 | } |
| 92 | |
| 93 | void 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 | |
| 104 | bool 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 | |
| 113 | C4ObjectInfo *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 | |
| 144 | C4ObjectInfo *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 | |
| 187 | void C4ObjectInfoList::Evaluate() |
| 188 | { |
| 189 | C4ObjectInfo *cinf; |
| 190 | for (cinf = First; cinf; cinf = cinf->Next) |
| 191 | cinf->Evaluate(); |
| 192 | } |
| 193 | |
| 194 | bool 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 | |
| 213 | C4ObjectInfo *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 | |
| 228 | void C4ObjectInfoList::DetachFromObjects() |
| 229 | { |
| 230 | C4ObjectInfo *cinf; |
| 231 | for (cinf = First; cinf; cinf = cinf->Next) |
| 232 | Game.Objects.ClearInfo(pInfo: cinf); |
| 233 | } |
| 234 | |
| 235 | C4ObjectInfo *C4ObjectInfoList::GetLast() |
| 236 | { |
| 237 | C4ObjectInfo *cInfo = First; |
| 238 | while (cInfo && cInfo->Next) cInfo = cInfo->Next; |
| 239 | return cInfo; |
| 240 | } |
| 241 | |
| 242 | C4ObjectInfo *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 | |
| 250 | bool 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 | |
| 257 | void 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 | |