1 /*
2 C-Dogs SDL
3 A port of the legendary (and fun) action/arcade cdogs.
4
5 Copyright (c) 2016-2017, 2019-2021 Cong Xu
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10
11 Redistributions of source code must retain the above copyright notice, this
12 list of conditions and the following disclaimer.
13 Redistributions in binary form must reproduce the above copyright notice,
14 this list of conditions and the following disclaimer in the documentation
15 and/or other materials provided with the distribution.
16
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 POSSIBILITY OF SUCH DAMAGE.
28 */
29 #include "character_class.h"
30
31 #include "json_utils.h"
32 #include "log.h"
33
34 #define VERSION 2
35 #define FOOTSTEP_DISTANCE_PLUS 250
36
37 CharacterClasses gCharacterClasses;
38
39 // TODO: use map structure?
StrCharacterClass(const char * s)40 const CharacterClass *StrCharacterClass(const char *s)
41 {
42 CA_FOREACH(const CharacterClass, c, gCharacterClasses.CustomClasses)
43 if (strcmp(s, c->Name) == 0)
44 {
45 return c;
46 }
47 CA_FOREACH_END()
48 CA_FOREACH(const CharacterClass, c, gCharacterClasses.Classes)
49 if (strcmp(s, c->Name) == 0)
50 {
51 return c;
52 }
53 CA_FOREACH_END()
54 LOG(LM_MAIN, LL_ERROR, "Cannot find character name: %s", s);
55 return NULL;
56 }
57 static const char *characterNames[] = {
58 "Jones", "Ice", "Ogre", "Dragon", "WarBaby", "Bug-eye",
59 "Smith", "Ogre Boss", "Grunt", "Professor", "Snake", "Wolf",
60 "Bob", "Mad bug-eye", "Cyborg", "Robot", "Lady"};
IntCharacterFace(const int face)61 const char *IntCharacterFace(const int face)
62 {
63 return characterNames[face];
64 }
CharacterOldFaceToHair(const char * face,char ** newFace,char ** hair)65 void CharacterOldFaceToHair(const char *face, char **newFace, char **hair)
66 {
67 // Convert old faces to face + hair
68 if (strcmp(face, "Bob") == 0)
69 {
70 CSTRDUP(*newFace, "Jones");
71 CSTRDUP(*hair, "beard");
72 }
73 else if (strcmp(face, "Cyber Jones") == 0)
74 {
75 CSTRDUP(*newFace, "Cyborg");
76 CSTRDUP(*hair, "cyber_shades");
77 }
78 else if (strcmp(face, "Cyber Smith") == 0)
79 {
80 CSTRDUP(*newFace, "Cyborg");
81 CSTRDUP(*hair, "flattop");
82 }
83 else if (strcmp(face, "Cyber WarBaby") == 0)
84 {
85 CSTRDUP(*newFace, "Cyborg");
86 CSTRDUP(*hair, "beret");
87 }
88 else if (strcmp(face, "Cyborg") == 0)
89 {
90 CSTRDUP(*newFace, "Cyborg");
91 CSTRDUP(*hair, "cyborg");
92 }
93 else if (strcmp(face, "Dragon") == 0)
94 {
95 CSTRDUP(*newFace, "Jones");
96 CSTRDUP(*hair, "hogan");
97 }
98 else if (strcmp(face, "Evil Ogre") == 0)
99 {
100 CSTRDUP(*newFace, "Ogre");
101 CSTRDUP(*hair, "horns");
102 }
103 else if (strcmp(face, "Freeze") == 0)
104 {
105 CSTRDUP(*newFace, "Jones");
106 CSTRDUP(*hair, "ski_goggles");
107 }
108 else if (strcmp(face, "Goggles") == 0)
109 {
110 CSTRDUP(*newFace, "Jones");
111 CSTRDUP(*hair, "goggles");
112 }
113 else if (strcmp(face, "Grunt") == 0)
114 {
115 CSTRDUP(*newFace, "Jones");
116 CSTRDUP(*hair, "riot_helmet");
117 }
118 else if (strcmp(face, "Ice") == 0)
119 {
120 CSTRDUP(*newFace, "Jones");
121 CSTRDUP(*hair, "shades");
122 }
123 else if (strcmp(face, "Lady") == 0)
124 {
125 CSTRDUP(*newFace, "Lady");
126 CSTRDUP(*hair, "ponytail");
127 }
128 else if (strcmp(face, "Ogre Boss") == 0)
129 {
130 CSTRDUP(*newFace, "Ogre");
131 CSTRDUP(*hair, "mohawk");
132 }
133 else if (strcmp(face, "Professor") == 0)
134 {
135 CSTRDUP(*newFace, "Jones");
136 CSTRDUP(*hair, "professor");
137 }
138 else if (strcmp(face, "Smith") == 0)
139 {
140 CSTRDUP(*newFace, "Jones");
141 CSTRDUP(*hair, "flattop");
142 }
143 else if (strcmp(face, "Snake") == 0)
144 {
145 CSTRDUP(*newFace, "Jones");
146 CSTRDUP(*hair, "eye_patch");
147 }
148 else if (strcmp(face, "Sweeper") == 0)
149 {
150 CSTRDUP(*newFace, "Jones");
151 CSTRDUP(*hair, "helmet");
152 }
153 else if (strcmp(face, "WarBaby") == 0)
154 {
155 CSTRDUP(*newFace, "Jones");
156 CSTRDUP(*hair, "beret");
157 }
158 else if (strcmp(face, "Wolf") == 0)
159 {
160 CSTRDUP(*newFace, "Jones");
161 CSTRDUP(*hair, "dutch");
162 }
163 else
164 {
165 CSTRDUP(*newFace, face);
166 }
167 }
168
CharacterClassGetDeathSprites(const CharacterClass * c,const PicManager * pm)169 const NamedSprites *CharacterClassGetDeathSprites(const CharacterClass *c, const PicManager *pm)
170 {
171 char buf[256];
172 sprintf(buf, "chars/%s", c->DeathSprites);
173 return PicManagerGetSprites(pm, buf);
174 }
IndexCharacterClass(const int i)175 const CharacterClass *IndexCharacterClass(const int i)
176 {
177 CASSERT(
178 i >= 0 && i < (int)gCharacterClasses.Classes.size +
179 (int)gCharacterClasses.CustomClasses.size,
180 "Character class index out of bounds");
181 if (i < (int)gCharacterClasses.Classes.size)
182 {
183 return CArrayGet(&gCharacterClasses.Classes, i);
184 }
185 return CArrayGet(
186 &gCharacterClasses.CustomClasses, i - gCharacterClasses.Classes.size);
187 }
CharacterClassIndex(const CharacterClass * c)188 int CharacterClassIndex(const CharacterClass *c)
189 {
190 if (c == NULL)
191 {
192 return 0;
193 }
194 CA_FOREACH(const CharacterClass, cc, gCharacterClasses.Classes)
195 if (cc == c)
196 {
197 return _ca_index;
198 }
199 CA_FOREACH_END()
200 CA_FOREACH(const CharacterClass, cc, gCharacterClasses.CustomClasses)
201 if (cc == c)
202 {
203 return _ca_index + (int)gCharacterClasses.Classes.size;
204 }
205 CA_FOREACH_END()
206 CASSERT(false, "cannot find character class");
207 return -1;
208 }
209
CharacterClassGetSound(const CharacterClass * c,char * out,const char * sound)210 void CharacterClassGetSound(const CharacterClass *c, char *out, const char *sound)
211 {
212 sprintf(out, "chars/%s/%s", sound, c->Sounds);
213 }
214
CharacterClassesInitialize(CharacterClasses * c,const char * filename)215 void CharacterClassesInitialize(CharacterClasses *c, const char *filename)
216 {
217 memset(c, 0, sizeof *c);
218 CArrayInit(&c->Classes, sizeof(CharacterClass));
219 CArrayInit(&c->CustomClasses, sizeof(CharacterClass));
220
221 char buf[CDOGS_PATH_MAX];
222 GetDataFilePath(buf, filename);
223 FILE *f = fopen(buf, "r");
224 json_t *root = NULL;
225 if (f == NULL)
226 {
227 LOG(LM_MAIN, LL_ERROR, "cannot load characters file %s", buf);
228 goto bail;
229 }
230 enum json_error e = json_stream_parse(f, &root);
231 if (e != JSON_OK)
232 {
233 LOG(LM_MAIN, LL_ERROR, "error parsing characters file %s", buf);
234 goto bail;
235 }
236 CharacterClassesLoadJSON(&c->Classes, root);
237
238 bail:
239 if (f != NULL)
240 {
241 fclose(f);
242 }
243 json_free_value(&root);
244 }
245 static void LoadCharacterClass(CharacterClass *c, json_t *node);
CharacterClassesLoadJSON(CArray * classes,json_t * root)246 void CharacterClassesLoadJSON(CArray *classes, json_t *root)
247 {
248 int version;
249 LoadInt(&version, root, "Version");
250 if (version > VERSION || version <= 0)
251 {
252 LOG(LM_MAIN, LL_ERROR, "Cannot read character file version: %d",
253 version);
254 return;
255 }
256
257 json_t *charactersNode = json_find_first_label(root, "Characters")->child;
258 for (json_t *child = charactersNode->child; child; child = child->next)
259 {
260 CharacterClass cc;
261 LoadCharacterClass(&cc, child);
262 CArrayPushBack(classes, &cc);
263 }
264 }
LoadCharacterClass(CharacterClass * c,json_t * node)265 static void LoadCharacterClass(CharacterClass *c, json_t *node)
266 {
267 memset(c, 0, sizeof *c);
268 c->Name = GetString(node, "Name");
269 LoadBool(&c->Vehicle, node, "Vehicle");
270 // TODO: allow non-directional head sprites?
271 json_t *headPics = json_find_first_label(node, "HeadPics")->child;
272 c->HeadSprites = GetString(headPics, "Sprites");
273 LoadStr(&c->Body, node, "Body");
274 if (c->Body == NULL)
275 {
276 CSTRDUP(c->Body, "base");
277 }
278 LoadStr(&c->DeathSprites, node, "DeathSprites");
279 if (c->DeathSprites == NULL)
280 {
281 CSTRDUP(c->DeathSprites, "death");
282 }
283 c->Mass = CHARACTER_DEFAULT_MASS;
284 LoadInt(&c->Mass, node, "Mass");
285 c->Sprites = StrCharSpriteClass(c->Body);
286
287 LoadStr(&c->Sounds, node, "Sounds");
288
289 LoadStr(&c->Footsteps, node, "Footsteps");
290 if (c->Footsteps == NULL)
291 {
292 CSTRDUP(c->Footsteps, "boots");
293 }
294 c->FootstepsDistancePlus = FOOTSTEP_DISTANCE_PLUS;
295 LoadInt(&c->FootstepsDistancePlus, node, "FootstepsDistancePlus");
296
297 c->BloodColor = colorRed;
298 LoadColor(&c->BloodColor, node, "BloodColor");
299
300 c->HasHair = true;
301 LoadBool(&c->HasHair, node, "HasHair");
302
303 LoadStr(&c->Corpse, node, "Corpse");
304 }
305 static void CharacterClassFree(CharacterClass *c);
CharacterClassesClear(CArray * classes)306 void CharacterClassesClear(CArray *classes)
307 {
308 for (int i = 0; i < (int)classes->size; i++)
309 {
310 CharacterClassFree(CArrayGet(classes, i));
311 }
312 CArrayClear(classes);
313 }
CharacterClassFree(CharacterClass * c)314 static void CharacterClassFree(CharacterClass *c)
315 {
316 CFREE(c->Name);
317 CFREE(c->HeadSprites);
318 CFREE(c->Body);
319 CFREE(c->DeathSprites);
320 CFREE(c->Sounds);
321 CFREE(c->Footsteps);
322 CFREE(c->Corpse);
323 }
CharacterClassesTerminate(CharacterClasses * c)324 void CharacterClassesTerminate(CharacterClasses *c)
325 {
326 CharacterClassesClear(&c->Classes);
327 CArrayTerminate(&c->Classes);
328 CharacterClassesClear(&c->CustomClasses);
329 CArrayTerminate(&c->CustomClasses);
330 }
331