1 /*
2 C-Dogs SDL
3 A port of the legendary (and fun) action/arcade cdogs.
4 Copyright (c) 2015-2016, 2018, 2020-2021 Cong Xu
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9
10 Redistributions of source code must retain the above copyright notice, this
11 list of conditions and the following disclaimer.
12 Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 POSSIBILITY OF SUCH DAMAGE.
27 */
28 #include "pickup.h"
29
30 #include "ammo.h"
31 #include "game_events.h"
32 #include "json_utils.h"
33 #include "log.h"
34 #include "mission.h"
35 #include "powerup.h"
36
37 PickupClasses gPickupClasses;
38
StrPickupType(const char * s)39 PickupType StrPickupType(const char *s)
40 {
41 S2T(PICKUP_JEWEL, "Score");
42 S2T(PICKUP_HEALTH, "Health");
43 S2T(PICKUP_AMMO, "Ammo");
44 S2T(PICKUP_KEYCARD, "Key");
45 S2T(PICKUP_GUN, "Gun");
46 S2T(PICKUP_SHOW_MAP, "ShowMap");
47 return PICKUP_NONE;
48 }
PickupTypeStr(const PickupType pt)49 const char *PickupTypeStr(const PickupType pt)
50 {
51 switch (pt)
52 {
53 T2S(PICKUP_JEWEL, "Score");
54 T2S(PICKUP_HEALTH, "Health");
55 T2S(PICKUP_AMMO, "Ammo");
56 T2S(PICKUP_KEYCARD, "Key");
57 T2S(PICKUP_GUN, "Gun");
58 T2S(PICKUP_SHOW_MAP, "ShowMap");
59 default:
60 return "";
61 }
62 }
63
StrPickupClass(const char * s)64 PickupClass *StrPickupClass(const char *s)
65 {
66 if (s == NULL || strlen(s) == 0)
67 {
68 return NULL;
69 }
70 CA_FOREACH(PickupClass, c, gPickupClasses.CustomClasses)
71 if (strcmp(s, c->Name) == 0)
72 {
73 return c;
74 }
75 CA_FOREACH_END()
76 CA_FOREACH(PickupClass, c, gPickupClasses.Classes)
77 if (strcmp(s, c->Name) == 0)
78 {
79 return c;
80 }
81 CA_FOREACH_END()
82 CA_FOREACH(PickupClass, c, gPickupClasses.KeyClasses)
83 if (strcmp(s, c->Name) == 0)
84 {
85 return c;
86 }
87 CA_FOREACH_END()
88 return NULL;
89 }
IntPickupClass(const int i)90 PickupClass *IntPickupClass(const int i)
91 {
92 static const char *pickupItems[] = {
93 "folder", "disk1", "disk2", "disk3", "blueprint", "cd",
94 "sack", "holo", "bottle", "radio", "pci_card", "paper"};
95 #define PICKUPS_COUNT (sizeof(pickupItems) / sizeof(const char *))
96 if (i < 0 || i >= (int)PICKUPS_COUNT)
97 {
98 return NULL;
99 }
100 return StrPickupClass(pickupItems[i]);
101 }
IntExitStyle(const int i)102 const char *IntExitStyle(const int i)
103 {
104 static const char *exitStyles[] = {"hazard", "plate"};
105 return exitStyles[abs(i) % 2];
106 }
107 #define KEYSTYLE_COUNT 4
IntKeyStyle(const int style)108 const char *IntKeyStyle(const int style)
109 {
110 static const char *keyStyles[] = {"office", "dungeon", "plain", "cube"};
111 return keyStyles[abs(style) % KEYSTYLE_COUNT];
112 }
IntKeyPickupClass(const int style,const int i)113 PickupClass *IntKeyPickupClass(const int style, const int i)
114 {
115 return KeyPickupClass(IntKeyStyle(style), i);
116 }
117 // Define the key colours
118 static const char *keyColors[] = {"yellow", "green", "blue", "red"};
119 // TODO: support more colours
KeyPickupClass(const char * style,const int i)120 PickupClass *KeyPickupClass(const char *style, const int i)
121 {
122 static char buf[256];
123 sprintf(buf, "keys/%s/%s", style, keyColors[abs(i) % KEY_COUNT]);
124 CA_FOREACH(PickupClass, c, gPickupClasses.KeyClasses)
125 if (strcmp(buf, c->Name) == 0)
126 {
127 return c;
128 }
129 CA_FOREACH_END()
130 CASSERT(false, "cannot parse key class");
131 return NULL;
132 }
PickupClassGetById(PickupClasses * classes,const int id)133 PickupClass *PickupClassGetById(PickupClasses *classes, const int id)
134 {
135 if (id < (int)classes->Classes.size)
136 {
137 return CArrayGet(&classes->Classes, id);
138 }
139 else
140 {
141 return CArrayGet(
142 &classes->CustomClasses, id - (int)classes->Classes.size);
143 }
144 }
StrPickupClassId(const char * s)145 int StrPickupClassId(const char *s)
146 {
147 if (s == NULL || strlen(s) == 0)
148 {
149 return 0;
150 }
151 CA_FOREACH(const PickupClass, c, gPickupClasses.CustomClasses)
152 if (strcmp(s, c->Name) == 0)
153 {
154 return _ca_index + (int)gPickupClasses.Classes.size;
155 }
156 CA_FOREACH_END()
157 CA_FOREACH(const PickupClass, c, gPickupClasses.Classes)
158 if (strcmp(s, c->Name) == 0)
159 {
160 return _ca_index;
161 }
162 CA_FOREACH_END()
163 CASSERT(false, "cannot parse pickup class name");
164 return 0;
165 }
166
167 #define VERSION 2
168
PickupClassesInit(PickupClasses * classes,const char * filename,const AmmoClasses * ammo,const WeaponClasses * guns)169 void PickupClassesInit(
170 PickupClasses *classes, const char *filename, const AmmoClasses *ammo,
171 const WeaponClasses *guns)
172 {
173 CArrayInit(&classes->Classes, sizeof(PickupClass));
174 CArrayInit(&classes->CustomClasses, sizeof(PickupClass));
175 CArrayInit(&classes->KeyClasses, sizeof(PickupClass));
176
177 char buf[CDOGS_PATH_MAX];
178 GetDataFilePath(buf, filename);
179 FILE *f = fopen(buf, "r");
180 json_t *root = NULL;
181 if (f == NULL)
182 {
183 LOG(LM_MAIN, LL_ERROR, "Error: cannot load pickups file %s", buf);
184 goto bail;
185 }
186 enum json_error e = json_stream_parse(f, &root);
187 if (e != JSON_OK)
188 {
189 LOG(LM_MAIN, LL_ERROR, "Error parsing pickups file %s", buf);
190 goto bail;
191 }
192 PickupClassesLoadJSON(&classes->Classes, root);
193 PickupClassesLoadAmmo(&classes->Classes, &ammo->Ammo);
194 PickupClassesLoadGuns(&classes->Classes, &guns->Guns);
195 PickupClassesLoadKeys(&classes->KeyClasses);
196
197 bail:
198 if (f != NULL)
199 {
200 fclose(f);
201 }
202 json_free_value(&root);
203 }
204 static bool TryLoadPickupclass(
205 PickupClass *c, json_t *node, const int version);
PickupClassesLoadJSON(CArray * classes,json_t * root)206 void PickupClassesLoadJSON(CArray *classes, json_t *root)
207 {
208 int version = -1;
209 LoadInt(&version, root, "Version");
210 if (version > VERSION || version <= 0)
211 {
212 CASSERT(false, "cannot read pickups file version");
213 return;
214 }
215
216 json_t *pickupsNode = json_find_first_label(root, "Pickups")->child;
217 for (json_t *child = pickupsNode->child; child; child = child->next)
218 {
219 PickupClass c;
220 if (TryLoadPickupclass(&c, child, version))
221 {
222 CArrayPushBack(classes, &c);
223 }
224 }
225 }
TryLoadPickupclass(PickupClass * c,json_t * node,const int version)226 static bool TryLoadPickupclass(PickupClass *c, json_t *node, const int version)
227 {
228 memset(c, 0, sizeof *c);
229 char *tmp;
230
231 JSON_UTILS_LOAD_ENUM(c->Type, node, "Type", StrPickupType);
232 LoadStr(&c->Sound, node, "Sound");
233 switch (c->Type)
234 {
235 case PICKUP_JEWEL:
236 // Set default score
237 c->u.Score = PICKUP_SCORE;
238 LoadInt(&c->u.Score, node, "Score");
239 if (c->Sound == NULL)
240 {
241 CSTRDUP(c->Sound, "pickup");
242 }
243 break;
244 case PICKUP_HEALTH:
245 // Set default heal amount
246 c->u.Health = HEALTH_PICKUP_HEAL_AMOUNT;
247 LoadInt(&c->u.Health, node, "Health");
248 if (c->Sound == NULL)
249 {
250 CSTRDUP(c->Sound, "health");
251 }
252 break;
253 case PICKUP_AMMO: {
254 tmp = GetString(node, "Ammo");
255 c->u.Ammo.Id = StrAmmoId(tmp);
256 CFREE(tmp);
257 const Ammo *ammo = AmmoGetById(&gAmmo, c->u.Ammo.Id);
258 // Set default ammo amount
259 int amount = ammo->Amount;
260 LoadInt(&amount, node, "Amount");
261 c->u.Ammo.Amount = amount;
262 if (c->Sound == NULL && ammo->Sound)
263 {
264 CSTRDUP(c->Sound, ammo->Sound);
265 }
266 break;
267 }
268 case PICKUP_KEYCARD:
269 // Do nothing; keys now loaded directly from graphics files
270 return false;
271 case PICKUP_GUN:
272 CASSERT(false, "unimplemented");
273 break;
274 case PICKUP_SHOW_MAP:
275 if (c->Sound == NULL)
276 {
277 CSTRDUP(c->Sound, "show_map");
278 }
279 break;
280 default:
281 CASSERT(false, "Unknown pickup type");
282 break;
283 }
284 c->Name = GetString(node, "Name");
285 json_t *picNode = json_find_first_label(node, "Pic")->child;
286 if (version < 2)
287 {
288 CPicLoadNormal(&c->Pic, picNode);
289 }
290 else
291 {
292 CPicLoadJSON(&c->Pic, picNode);
293 }
294 return true;
295 }
296
297 // TODO: move ammo pickups to pickups file; remove "ammo_"
PickupClassesLoadAmmo(CArray * classes,const CArray * ammoClasses)298 void PickupClassesLoadAmmo(CArray *classes, const CArray *ammoClasses)
299 {
300 CA_FOREACH(const Ammo, a, *ammoClasses)
301 PickupClass c;
302 memset(&c, 0, sizeof c);
303 char buf[256];
304 sprintf(buf, "ammo_%s", a->Name);
305 CSTRDUP(c.Name, buf);
306 CPicCopyPic(&c.Pic, &a->Pic);
307 c.Type = PICKUP_AMMO;
308 c.u.Ammo.Id = StrAmmoId(a->Name);
309 c.u.Ammo.Amount = a->Amount;
310 if (a->Sound)
311 {
312 CSTRDUP(c.Sound, a->Sound);
313 }
314 CArrayPushBack(classes, &c);
315 CA_FOREACH_END()
316 }
317
PickupClassesLoadGuns(CArray * classes,const CArray * gunClasses)318 void PickupClassesLoadGuns(CArray *classes, const CArray *gunClasses)
319 {
320 CA_FOREACH(const WeaponClass, wc, *gunClasses)
321 PickupClass c;
322 memset(&c, 0, sizeof c);
323 char buf[256];
324 sprintf(buf, "gun_%s", wc->name);
325 CSTRDUP(c.Name, buf);
326 CPicInitNormal(&c.Pic, wc->Icon);
327 c.Type = PICKUP_GUN;
328 c.u.GunId = WeaponClassId(wc);
329 CArrayPushBack(classes, &c);
330 CA_FOREACH_END()
331 }
332
PickupClassesLoadKeys(CArray * classes)333 void PickupClassesLoadKeys(CArray *classes)
334 {
335 CA_FOREACH(const char *, keyStyleName, gPicManager.keyStyleNames)
336 for (int i = 0; i < KEY_COUNT; i++)
337 {
338 char buf[CDOGS_FILENAME_MAX];
339 sprintf(buf, "keys/%s/%s", *keyStyleName, keyColors[i]);
340 if (StrPickupClass(buf) != NULL)
341 {
342 continue;
343 }
344 PickupClass c;
345 memset(&c, 0, sizeof c);
346 CSTRDUP(c.Name, buf);
347 CPicInitNormalFromName(&c.Pic, c.Name);
348 c.Type = PICKUP_KEYCARD;
349 c.u.Keys = StrKeycard(keyColors[i]);
350 CArrayPushBack(classes, &c);
351 }
352 CA_FOREACH_END()
353 }
354
PickupClassesClear(CArray * classes)355 void PickupClassesClear(CArray *classes)
356 {
357 CA_FOREACH(PickupClass, c, *classes)
358 CFREE(c->Name);
359 CFREE(c->Sound);
360 CA_FOREACH_END()
361 CArrayClear(classes);
362 }
PickupClassesTerminate(PickupClasses * classes)363 void PickupClassesTerminate(PickupClasses *classes)
364 {
365 PickupClassesClear(&classes->Classes);
366 CArrayTerminate(&classes->Classes);
367 PickupClassesClear(&classes->CustomClasses);
368 CArrayTerminate(&classes->CustomClasses);
369 PickupClassesClear(&classes->KeyClasses);
370 CArrayTerminate(&classes->KeyClasses);
371 }
372
PickupClassesCount(const PickupClasses * classes)373 int PickupClassesCount(const PickupClasses *classes)
374 {
375 return (int)classes->Classes.size + (int)classes->CustomClasses.size;
376 }
377
PickupClassesGetScoreIdx(const PickupClass * p)378 int PickupClassesGetScoreIdx(const PickupClass *p)
379 {
380 if (p == NULL)
381 {
382 return 0;
383 }
384 int idx = -1;
385 CA_FOREACH(const PickupClass, c, gPickupClasses.Classes)
386 if (c->Type == PICKUP_JEWEL)
387 {
388 idx++;
389 if (c == p)
390 {
391 return idx;
392 }
393 }
394 CA_FOREACH_END()
395 CA_FOREACH(PickupClass, c, gPickupClasses.CustomClasses)
396 if (c->Type == PICKUP_JEWEL)
397 {
398 idx++;
399 if (c == p)
400 {
401 return idx;
402 }
403 }
404 CA_FOREACH_END()
405 return 0;
406 }
PickupClassesGetScoreCount(const PickupClasses * classes)407 int PickupClassesGetScoreCount(const PickupClasses *classes)
408 {
409 int count = 0;
410 CA_FOREACH(const PickupClass, c, classes->Classes)
411 if (c->Type == PICKUP_JEWEL)
412 {
413 count++;
414 }
415 CA_FOREACH_END()
416 CA_FOREACH(const PickupClass, c, classes->CustomClasses)
417 if (c->Type == PICKUP_JEWEL)
418 {
419 count++;
420 }
421 CA_FOREACH_END()
422 return count;
423 }
IntScorePickupClass(const int i)424 PickupClass *IntScorePickupClass(const int i)
425 {
426 int idx = -1;
427 CA_FOREACH(PickupClass, c, gPickupClasses.Classes)
428 if (c->Type == PICKUP_JEWEL)
429 {
430 idx++;
431 if (idx == i)
432 {
433 return c;
434 }
435 }
436 CA_FOREACH_END()
437 CA_FOREACH(PickupClass, c, gPickupClasses.CustomClasses)
438 if (c->Type == PICKUP_JEWEL)
439 {
440 idx++;
441 if (idx == i)
442 {
443 return c;
444 }
445 }
446 CA_FOREACH_END()
447 return NULL;
448 }
449