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/* At static list of C4IDs */
18
19#include <C4Include.h>
20#include <C4IDList.h>
21
22#include <C4Def.h>
23#include <C4Application.h>
24#include <C4Game.h>
25
26#include <algorithm>
27
28void C4IDList::Clear()
29{
30 content.clear();
31}
32
33bool C4IDList::IsClear() const
34{
35 return content.empty();
36}
37
38C4ID C4IDList::GetID(size_t index, int32_t *ipCount) const
39{
40 if (!Inside<int32_t>(ival: index, lbound: 0, rbound: GetNumberOfIDs() - 1)) return C4ID_None;
41 const auto [id, count] = content[index];
42 // get chunk to query
43 if (ipCount) *ipCount = count;
44 return id;
45}
46
47int32_t C4IDList::GetCount(size_t index) const
48{
49 if (!Inside<int32_t>(ival: index, lbound: 0, rbound: GetNumberOfIDs() - 1)) return 0;
50 return content[index].count;
51}
52
53bool C4IDList::SetCount(size_t index, int32_t count)
54{
55 if (!Inside<int32_t>(ival: index, lbound: 0, rbound: GetNumberOfIDs() - 1)) return false;
56 content[index].count = count;
57 return true;
58}
59
60static auto findId(C4ID id, std::vector<C4IDList::Entry> &content)
61{
62 return std::find_if(first: content.begin(), last: content.end(), pred: [id](const C4IDList::Entry &entry)
63 {
64 return entry.id == id;
65 });
66}
67
68static auto findId(C4ID id, const std::vector<C4IDList::Entry> &content)
69{
70 return std::find_if(first: content.cbegin(), last: content.cend(), pred: [id](const C4IDList::Entry &entry)
71 {
72 return entry.id == id;
73 });
74}
75
76int32_t C4IDList::GetIDCount(C4ID id, int32_t zeroDefVal) const
77{
78 const auto it = findId(id, content);
79 if (it == content.cend()) return 0;
80
81 if (it->count == 0) return zeroDefVal;
82 return it->count;
83}
84
85bool C4IDList::SetIDCount(C4ID id, int32_t count, bool addNewID)
86{
87 const auto it = findId(id, content);
88 if (it == content.cend())
89 {
90 if (addNewID)
91 {
92 content.emplace_back(args&: id, args&: count);
93 return true;
94 }
95 else
96 {
97 return false;
98 }
99 }
100
101 it->count = count;
102 return true;
103}
104
105int32_t C4IDList::GetNumberOfIDs() const
106{
107 return content.size();
108}
109
110int32_t C4IDList::GetIndex(C4ID id) const
111{
112 int32_t i = 0;
113 for (const auto &it : content)
114 {
115 if (it.id == id) return i;
116 ++i;
117 }
118 return -1;
119}
120
121void C4IDList::IncreaseIDCount(C4ID id, bool addNewID, int32_t increaseBy, bool removeEmpty)
122{
123 const auto it = findId(id, content);
124 if (it == content.cend())
125 {
126 if (addNewID)
127 {
128 content.emplace_back(args&: id, args&: increaseBy);
129 }
130 }
131 else
132 {
133 it->count += increaseBy;
134 if (removeEmpty && it->count == 0)
135 {
136 content.erase(position: it);
137 }
138 }
139}
140
141// Access by category-sorted index
142
143C4ID C4IDList::GetID(C4DefList &defs, int32_t category, int32_t index, int32_t *ipCount) const
144{
145 int32_t cindex = -1;
146 for (const auto &it : content)
147 {
148 const auto def = defs.ID2Def(id: it.id);
149 if (!def) continue;
150
151 if (category == C4D_All || def->Category & category)
152 {
153 ++cindex;
154 if (cindex == index)
155 {
156 if (ipCount) *ipCount = it.count;
157 return it.id;
158 }
159 }
160 }
161
162 return C4ID_None;
163}
164
165int32_t C4IDList::GetNumberOfIDs(C4DefList &defs, int32_t category) const
166{
167 return std::count_if(first: content.cbegin(), last: content.cend(), pred: [&defs, category](const Entry &entry)
168 {
169 if (const auto def = defs.ID2Def(id: entry.id))
170 return category == C4D_All || def->Category & category;
171 return false;
172 });
173}
174
175// Removes all empty id gaps from the list.
176
177void C4IDList::ConsolidateValids(C4DefList &defs, int32_t category)
178{
179 content.erase(first: std::remove_if(first: content.begin(), last: content.end(), pred: [&defs, category](const auto &entry)
180 {
181 const auto def = defs.ID2Def(id: entry.id);
182 return !def || (category && !(def->Category & category));
183 }), last: content.end());
184}
185
186void C4IDList::SortByValue(C4DefList &defs)
187{
188 std::stable_sort(first: content.begin(), last: content.end(), comp: [&defs](const Entry &a, const Entry &b)
189 {
190 const auto defA = defs.ID2Def(id: a.id);
191 const auto defB = defs.ID2Def(id: b.id);
192 // FIXME: Should call GetValue here
193 return defA && defB && defA->Value < defB->Value;
194 });
195}
196
197void C4IDList::Load(C4DefList &defs, int32_t category)
198{
199 Clear();
200 // add all IDs of def list
201 for (std::size_t i{0}; const auto def = defs.GetDef(index: i, category); ++i)
202 {
203 content.emplace_back(args&: def->id, args: 0);
204 }
205}
206
207void C4IDList::Draw(C4Facet &cgo, int32_t selection,
208 C4DefList &defs, uint32_t category,
209 bool counts, int32_t align) const
210{
211#ifdef C4ENGINE
212 int32_t sections = cgo.GetSectionCount();
213 int32_t idnum = GetNumberOfIDs(defs, category);
214 int32_t firstid = BoundBy<int32_t>(bval: selection - sections / 2, lbound: 0, rbound: std::max<int32_t>(a: idnum - sections, b: 0));
215 int32_t idcount;
216 C4ID id;
217 C4Facet cgo2;
218 std::array<char, C4Strings::NumberOfCharactersForDigits<std::int32_t> + 1 + 1> buf;
219 for (int32_t cnt = 0; (cnt < sections) && (id = GetID(defs, category, index: firstid + cnt, ipCount: &idcount)); ++cnt)
220 {
221 cgo2 = cgo.TruncateSection(iAlign: align);
222 defs.Draw(id, cgo&: cgo2, fSelected: (firstid + cnt == selection), iColor: 0);
223 char *const ptr{std::to_chars(first: buf.data(), last: buf.data() + buf.size() - 2, value: idcount).ptr};
224 ptr[0] = 'x';
225 ptr[1] = '\0';
226 if (counts) Application.DDraw->TextOut(szText: buf.data(), rFont&: Game.GraphicsResource.FontRegular, fZoom: 1.0, sfcDest: cgo2.Surface, iTx: cgo2.X + cgo2.Wdt - 1, iTy: cgo2.Y + cgo2.Hgt - 1 - Game.GraphicsResource.FontRegular.GetLineHeight(), dwFCol: CStdDDraw::DEFAULT_MESSAGE_COLOR, byForm: ARight);
227 }
228#endif
229}
230
231bool C4IDList::DeleteItem(size_t index)
232{
233 if (!Inside<int32_t>(ival: index, lbound: 0, rbound: GetNumberOfIDs() - 1)) return false;
234
235 content.erase(position: std::next(x: content.cbegin(), n: index));
236 return true;
237}
238
239void C4IDList::Entry::CompileFunc(StdCompiler *compiler, bool withValues)
240{
241 compiler->Value(rStruct: mkC4IDAdapt(rValue&: id));
242
243 if (compiler->isCompiler() && !LooksLikeID(id))
244 {
245 // this breaks the loop in StdSTLContainerAdapt::CompileFunc
246 compiler->excNotFound(message: "Invalid ID");
247 }
248
249 if (withValues)
250 {
251 if (compiler->Separator(eSep: StdCompiler::SEP_SET))
252 compiler->Value(rStruct: mkDefaultAdapt(rValue&: count, rDefault: 0));
253 }
254}
255
256void C4IDList::CompileFunc(StdCompiler *pComp, bool withValues)
257{
258 auto stlAdapter = mkSTLContainerAdapt(rTarget&: content, eSep: StdCompiler::SEP_SEP2);
259 pComp->Value(rStruct: mkParAdapt(rObj&: stlAdapter, rPar: withValues));
260}
261
262bool C4IDList::operator==(const C4IDList &other) const
263{
264 return content == other.content;
265}
266