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