1 /*
2 C-Dogs SDL
3 A port of the legendary (and fun) action/arcade cdogs.
4 Copyright (c) 2013-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 "weapon_class.h"
29
30 #include "ammo.h"
31 #include "game_events.h"
32 #include "json_utils.h"
33 #include "log.h"
34 #include "net_util.h"
35 #include "utils.h"
36
37 WeaponClasses gWeaponClasses;
38
GunTypeStr(const GunType t)39 static const char *GunTypeStr(const GunType t)
40 {
41 switch (t)
42 {
43 T2S(GUNTYPE_NORMAL, "Normal");
44 T2S(GUNTYPE_GRENADE, "Grenade");
45 T2S(GUNTYPE_MULTI, "Multi");
46 default:
47 return "";
48 }
49 }
50
51 // Initialise all the static weapon data
52 #define VERSION 3
WeaponClassesInitialize(WeaponClasses * wcs)53 void WeaponClassesInitialize(WeaponClasses *wcs)
54 {
55 memset(wcs, 0, sizeof *wcs);
56 CArrayInit(&wcs->Guns, sizeof(WeaponClass));
57 CArrayInit(&wcs->CustomGuns, sizeof(WeaponClass));
58 }
59 static void LoadWeaponClass(WeaponClass *wc, json_t *node, const int version);
60 static void WeaponClassTerminate(WeaponClass *wc);
WeaponClassesLoadJSON(WeaponClasses * wcs,CArray * classes,json_t * root)61 void WeaponClassesLoadJSON(WeaponClasses *wcs, CArray *classes, json_t *root)
62 {
63 LOG(LM_MAP, LL_DEBUG, "loading weapons");
64 int version;
65 LoadInt(&version, root, "Version");
66 if (version > VERSION || version <= 0)
67 {
68 CASSERT(false, "cannot read guns file version");
69 return;
70 }
71
72 if (classes == &wcs->Guns)
73 {
74 CASSERT(wcs->Guns.size == 0, "guns not empty");
75 for (int i = 0; i < GUN_COUNT; i++)
76 {
77 WeaponClass gd;
78 memset(&gd, 0, sizeof gd);
79 CArrayPushBack(&wcs->Guns, &gd);
80 }
81 }
82 json_t *gunsNode = json_find_first_label(root, "Guns")->child;
83 for (json_t *child = gunsNode->child; child; child = child->next)
84 {
85 WeaponClass gd;
86 LoadWeaponClass(&gd, child, version);
87 int idx = -1;
88 // Only allow index for non-custom guns
89 if (classes == &wcs->Guns)
90 {
91 LoadInt(&idx, child, "Index");
92 }
93 if (idx >= 0 && idx < GUN_COUNT)
94 {
95 WeaponClass *gExisting = CArrayGet(&wcs->Guns, idx);
96 WeaponClassTerminate(gExisting);
97 memcpy(gExisting, &gd, sizeof gd);
98 }
99 else
100 {
101 CArrayPushBack(classes, &gd);
102 }
103 }
104 json_t *pseudoGunsNode = json_find_first_label(root, "PseudoGuns");
105 if (pseudoGunsNode != NULL)
106 {
107 for (json_t *child = pseudoGunsNode->child->child; child;
108 child = child->next)
109 {
110 WeaponClass gd;
111 LoadWeaponClass(&gd, child, version);
112 gd.IsRealGun = false;
113 CArrayPushBack(classes, &gd);
114 }
115 }
116 }
LoadWeaponClass(WeaponClass * wc,json_t * node,const int version)117 static void LoadWeaponClass(WeaponClass *wc, json_t *node, const int version)
118 {
119 memset(wc, 0, sizeof *wc);
120
121 const json_t *gunsNode = json_find_first_label(node, "Guns");
122 if (gunsNode)
123 {
124 wc->Type = GUNTYPE_MULTI;
125 }
126 else
127 {
128 bool isGrenade = false;
129 LoadBool(&isGrenade, node, "IsGrenade");
130 if (isGrenade)
131 {
132 wc->Type = GUNTYPE_GRENADE;
133 }
134 }
135
136 char *tmp;
137
138 if (wc->Type != GUNTYPE_MULTI)
139 {
140 // Initialise default gun values
141 CSTRDUP(wc->u.Normal.Sprites, "chars/guns/blaster");
142 wc->u.Normal.Grips = 1;
143 wc->Icon = PicManagerGetPic(&gPicManager, "peashooter");
144 wc->u.Normal.Sound = StrSound("bang");
145 wc->SwitchSound = StrSound("switch");
146 wc->u.Normal.Spread.Count = 1;
147 wc->u.Normal.MuzzleHeight = 10 * Z_FACTOR;
148 wc->u.Normal.MuzzleFlash =
149 StrParticleClass(&gParticleClasses, "muzzle_flash_default");
150 wc->u.Normal.AmmoId = -1;
151 wc->u.Normal.CanShoot = true;
152 wc->CanDrop = true;
153 }
154
155 const Pic *icon = NULL;
156 LoadPic(&icon, node, "Icon");
157 if (icon != NULL)
158 {
159 wc->Icon = icon;
160 }
161
162 tmp = NULL;
163 LoadStr(&tmp, node, "Name");
164 if (tmp != NULL)
165 {
166 CFREE(wc->name);
167 wc->name = tmp;
168 }
169
170 tmp = NULL;
171 LoadStr(&tmp, node, "Description");
172 if (tmp != NULL)
173 {
174 CFREE(wc->Description);
175 wc->Description = tmp;
176 }
177
178 LoadInt(&wc->Lock, node, "Lock");
179
180 LoadSoundFromNode(&wc->SwitchSound, node, "SwitchSound");
181
182 LoadBool(&wc->CanDrop, node, "CanDrop");
183
184 LoadStr(&wc->DropGun, node, "DropGun");
185
186 switch (wc->Type)
187 {
188 case GUNTYPE_NORMAL:
189 case GUNTYPE_GRENADE: // fallthrough
190 {
191 tmp = NULL;
192 LoadStr(&tmp, node, "Pic");
193 if (tmp != NULL)
194 {
195 CFREE(wc->u.Normal.Sprites);
196 wc->u.Normal.Sprites = NULL;
197 if (strlen(tmp) > 0)
198 {
199 char buf[CDOGS_PATH_MAX];
200 sprintf(buf, "chars/guns/%s", tmp);
201 CSTRDUP(wc->u.Normal.Sprites, buf);
202 }
203 CFREE(tmp);
204 }
205
206 LoadInt(&wc->u.Normal.Grips, node, "Grips");
207
208 CArrayInit(&wc->u.Normal.Bullets, sizeof(const BulletClass *));
209 tmp = NULL;
210 LoadStr(&tmp, node, "Bullet");
211 if (tmp != NULL)
212 {
213 const BulletClass *bullet = StrBulletClass(tmp);
214 CArrayPushBack(&wc->u.Normal.Bullets, &bullet);
215 CFREE(tmp);
216 }
217 const json_t *bulletsNode = json_find_first_label(node, "Bullets");
218 if (bulletsNode)
219 {
220 for (const json_t *bulletNode = bulletsNode->child->child;
221 bulletNode; bulletNode = bulletNode->next)
222 {
223 const BulletClass *bullet = StrBulletClass(bulletNode->text);
224 CArrayPushBack(&wc->u.Normal.Bullets, &bullet);
225 }
226 }
227
228 tmp = NULL;
229 LoadStr(&tmp, node, "Ammo");
230 if (tmp != NULL)
231 {
232 wc->u.Normal.AmmoId = StrAmmoId(tmp);
233 if (wc->Type == GUNTYPE_GRENADE)
234 {
235 // Grenade weapons also allow the ammo pickups to act as gun
236 // pickups
237 Ammo *ammo = AmmoGetById(&gAmmo, wc->u.Normal.AmmoId);
238 CFREE(ammo->DefaultGun);
239 CSTRDUP(ammo->DefaultGun, wc->name);
240 // Replace icon with that of the ammo
241 wc->Icon = CPicGetPic(&ammo->Pic, 0);
242 }
243 CFREE(tmp);
244 }
245
246 LoadInt(&wc->u.Normal.Cost, node, "Cost");
247
248 LoadInt(&wc->u.Normal.ReloadLead, node, "ReloadLead");
249
250 LoadSoundFromNode(&wc->u.Normal.Sound, node, "Sound");
251 LoadSoundFromNode(&wc->u.Normal.ReloadSound, node, "ReloadSound");
252
253 wc->u.Normal.SoundLockLength = wc->Lock;
254 LoadInt(&wc->u.Normal.SoundLockLength, node, "SoundLockLength");
255
256 LoadFloat(&wc->u.Normal.Recoil, node, "Recoil");
257
258 LoadInt(&wc->u.Normal.Spread.Count, node, "SpreadCount");
259 LoadFloat(&wc->u.Normal.Spread.Width, node, "SpreadWidth");
260 LoadFloat(&wc->u.Normal.AngleOffset, node, "AngleOffset");
261
262 int muzzleHeight = 0;
263 LoadInt(&muzzleHeight, node, "MuzzleHeight");
264 if (muzzleHeight)
265 {
266 wc->u.Normal.MuzzleHeight = muzzleHeight * Z_FACTOR;
267 }
268 if (json_find_first_label(node, "Elevation"))
269 {
270 LoadInt(&wc->u.Normal.ElevationLow, node, "Elevation");
271 wc->u.Normal.ElevationHigh = wc->u.Normal.ElevationLow;
272 }
273 LoadInt(&wc->u.Normal.ElevationLow, node, "ElevationLow");
274 LoadInt(&wc->u.Normal.ElevationHigh, node, "ElevationHigh");
275 wc->u.Normal.ElevationLow =
276 MIN(wc->u.Normal.ElevationLow, wc->u.Normal.ElevationHigh);
277 wc->u.Normal.ElevationHigh =
278 MAX(wc->u.Normal.ElevationLow, wc->u.Normal.ElevationHigh);
279 tmp = NULL;
280 LoadStr(&tmp, node, "MuzzleFlashParticle");
281 if (tmp != NULL)
282 {
283 wc->u.Normal.MuzzleFlash =
284 StrParticleClass(&gParticleClasses, tmp);
285 CFREE(tmp);
286 }
287
288 tmp = NULL;
289 LoadStr(&tmp, node, "Brass");
290 if (tmp != NULL)
291 {
292 wc->u.Normal.Brass = StrParticleClass(&gParticleClasses, tmp);
293 CFREE(tmp);
294 }
295
296 LoadBool(&wc->u.Normal.CanShoot, node, "CanShoot");
297
298 if (version < 3)
299 {
300 LoadInt(&wc->u.Normal.Shake.Amount, node, "ShakeAmount");
301 }
302 else if (json_find_first_label(node, "Shake"))
303 {
304 json_t *shake = json_find_first_label(node, "Shake")->child;
305 LoadInt(&wc->u.Normal.Shake.Amount, shake, "Amount");
306 LoadBool(
307 &wc->u.Normal.Shake.CameraSubjectOnly, shake,
308 "CameraSubjectOnly");
309 }
310 }
311 break;
312 case GUNTYPE_MULTI: {
313 int i = 0;
314 for (const json_t *gunNode = gunsNode->child->child; gunNode;
315 gunNode = gunNode->next, i++)
316 {
317 wc->u.Guns[i] = json_unescape(gunNode->text);
318 }
319 }
320 break;
321 default:
322 CASSERT(false, "unknown gun type");
323 break;
324 }
325
326 wc->IsRealGun = true;
327
328 if (version < 2)
329 {
330 CASSERT(wc->Type == GUNTYPE_NORMAL, "unexpected gun type");
331 if (!wc->u.Normal.CanShoot)
332 {
333 wc->Lock = 0;
334 }
335 }
336
337 LOG(LM_MAP, LL_DEBUG, "loaded %s name(%s) lock(%d)...",
338 GunTypeStr(wc->Type), wc->name, wc->Lock);
339 LOG(LM_MAP, LL_DEBUG, "...canDrop(%s)", wc->CanDrop ? "true" : "false");
340 switch (wc->Type)
341 {
342 case GUNTYPE_NORMAL:
343 case GUNTYPE_GRENADE: // fallthrough
344 LOG(LM_MAP, LL_DEBUG, "bullets(");
345 CA_FOREACH(const BulletClass *, bc, wc->u.Normal.Bullets)
346 if (_ca_index > 0)
347 {
348 LOG(LM_MAP, LL_DEBUG, ", ");
349 }
350 LOG(LM_MAP, LL_DEBUG, "%s", (*bc)->Name);
351 CA_FOREACH_END()
352 LOG(LM_MAP, LL_DEBUG, ") ammo(%d) cost(%d)...", wc->u.Normal.AmmoId,
353 wc->u.Normal.Cost);
354 LOG(LM_MAP, LL_DEBUG,
355 "...reloadLead(%d) soundLockLength(%d) recoil(%f)...",
356 wc->u.Normal.ReloadLead, wc->u.Normal.SoundLockLength,
357 wc->u.Normal.Recoil);
358 LOG(LM_MAP, LL_DEBUG,
359 "...spread(%frad x%d) angleOffset(%f) muzzleHeight(%d)...",
360 wc->u.Normal.Spread.Width, wc->u.Normal.Spread.Count,
361 wc->u.Normal.AngleOffset, wc->u.Normal.MuzzleHeight);
362 LOG(LM_MAP, LL_DEBUG,
363 "...elevation(%d-%d) muzzleFlash(%s) brass(%s) canShoot(%s)...",
364 wc->u.Normal.ElevationLow, wc->u.Normal.ElevationHigh,
365 wc->u.Normal.MuzzleFlash != NULL ? wc->u.Normal.MuzzleFlash->Name
366 : "",
367 wc->u.Normal.Brass != NULL ? wc->u.Normal.Brass->Name : "",
368 wc->u.Normal.CanShoot ? "true" : "false");
369 LOG(LM_MAP, LL_DEBUG, "...shake{amount(%d), cameraSubjectOnly(%s)}",
370 wc->u.Normal.Shake.Amount,
371 wc->u.Normal.Shake.CameraSubjectOnly ? "true" : "false");
372 break;
373 case GUNTYPE_MULTI:
374 LOG(LM_MAP, LL_DEBUG, "...guns{%s, %s}",
375 wc->u.Guns[0] ? wc->u.Guns[0] : "",
376 wc->u.Guns[1] ? wc->u.Guns[1] : "");
377 break;
378 default:
379 CASSERT(false, "unknown gun type");
380 break;
381 }
382 }
WeaponClassesTerminate(WeaponClasses * wcs)383 void WeaponClassesTerminate(WeaponClasses *wcs)
384 {
385 WeaponClassesClear(&wcs->Guns);
386 CArrayTerminate(&wcs->Guns);
387 WeaponClassesClear(&wcs->CustomGuns);
388 CArrayTerminate(&wcs->CustomGuns);
389 }
WeaponClassesClear(CArray * classes)390 void WeaponClassesClear(CArray *classes)
391 {
392 CA_FOREACH(WeaponClass, g, *classes)
393 WeaponClassTerminate(g);
394 CA_FOREACH_END()
395 CArrayClear(classes);
396 }
WeaponClassTerminate(WeaponClass * wc)397 static void WeaponClassTerminate(WeaponClass *wc)
398 {
399 CFREE(wc->name);
400 CFREE(wc->Description);
401 CFREE(wc->DropGun);
402 switch (wc->Type)
403 {
404 case GUNTYPE_NORMAL:
405 case GUNTYPE_GRENADE: // fallthrough
406 CFREE(wc->u.Normal.Sprites);
407 CArrayTerminate(&wc->u.Normal.Bullets);
408 break;
409 case GUNTYPE_MULTI:
410 for (int i = 0; i < MAX_BARRELS; i++)
411 {
412 CFREE(wc->u.Guns[i]);
413 }
414 break;
415 default:
416 CASSERT(false, "unknown gun type");
417 break;
418 }
419 memset(wc, 0, sizeof *wc);
420 }
421
422 // TODO: use map structure?
StrWeaponClass(const char * s)423 const WeaponClass *StrWeaponClass(const char *s)
424 {
425 CA_FOREACH(const WeaponClass, gd, gWeaponClasses.CustomGuns)
426 if (strcmp(s, gd->name) == 0)
427 {
428 return gd;
429 }
430 CA_FOREACH_END()
431 CA_FOREACH(const WeaponClass, gd, gWeaponClasses.Guns)
432 if (strcmp(s, gd->name) == 0)
433 {
434 return gd;
435 }
436 CA_FOREACH_END()
437 return NULL;
438 }
IdWeaponClass(const int i)439 WeaponClass *IdWeaponClass(const int i)
440 {
441 CASSERT(
442 i >= 0 && i < (int)gWeaponClasses.Guns.size +
443 (int)gWeaponClasses.CustomGuns.size,
444 "Gun index out of bounds");
445 if (i < (int)gWeaponClasses.Guns.size)
446 {
447 return CArrayGet(&gWeaponClasses.Guns, i);
448 }
449 return CArrayGet(&gWeaponClasses.CustomGuns, i - gWeaponClasses.Guns.size);
450 }
WeaponClassId(const WeaponClass * wc)451 int WeaponClassId(const WeaponClass *wc)
452 {
453 int idx = 0;
454 for (int i = 0; i < (int)gWeaponClasses.Guns.size; i++, idx++)
455 {
456 const WeaponClass *wc2 = CArrayGet(&gWeaponClasses.Guns, i);
457 if (wc2 == wc)
458 {
459 return idx;
460 }
461 }
462 for (int i = 0; i < (int)gWeaponClasses.CustomGuns.size; i++, idx++)
463 {
464 const WeaponClass *wc2 = CArrayGet(&gWeaponClasses.CustomGuns, i);
465 if (wc2 == wc)
466 {
467 return idx;
468 }
469 }
470 CASSERT(false, "cannot find gun");
471 return -1;
472 }
473
WeaponClassFire(const WeaponClass * wc,const struct vec2 pos,const float z,const double radians,const int flags,const int actorUID,const bool playSound,const bool isGun)474 void WeaponClassFire(
475 const WeaponClass *wc, const struct vec2 pos, const float z,
476 const double radians, const int flags, const int actorUID,
477 const bool playSound, const bool isGun)
478 {
479 CASSERT(wc->Type != GUNTYPE_MULTI, "unexpected gun type");
480 GameEvent e = GameEventNew(GAME_EVENT_GUN_FIRE);
481 e.u.GunFire.ActorUID = actorUID;
482 strcpy(e.u.GunFire.Gun, wc->name);
483 e.u.GunFire.MuzzlePos = Vec2ToNet(pos);
484 // TODO: GunFire Z to float
485 e.u.GunFire.Z = (int)z;
486 e.u.GunFire.Angle = (float)radians;
487 e.u.GunFire.Sound = playSound;
488 e.u.GunFire.Flags = flags;
489 e.u.GunFire.IsGun = isGun;
490 GameEventsEnqueue(&gGameEvents, e);
491 }
492
WeaponClassAddBrass(const WeaponClass * wc,const direction_e d,const struct vec2 pos)493 void WeaponClassAddBrass(
494 const WeaponClass *wc, const direction_e d, const struct vec2 pos)
495 {
496 // Check configuration
497 if (!ConfigGetBool(&gConfig, "Graphics.Brass"))
498 {
499 return;
500 }
501 CASSERT(wc->Type == GUNTYPE_NORMAL, "gun type can't have brass");
502 CASSERT(
503 wc->u.Normal.Brass != NULL, "Cannot create brass for no-brass weapon");
504 GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
505 e.u.AddParticle.Class = wc->u.Normal.Brass;
506 const float radians = dir2radians[d];
507 const struct vec2 ejectionPortOffset =
508 svec2_scale(Vec2FromRadiansScaled(radians), 7);
509 e.u.AddParticle.Pos = svec2_subtract(pos, ejectionPortOffset);
510 e.u.AddParticle.Z = (float)wc->u.Normal.MuzzleHeight;
511 e.u.AddParticle.Vel =
512 svec2_scale(Vec2FromRadians(radians + MPI_2), 0.333333f);
513 e.u.AddParticle.Vel.x += RAND_FLOAT(-0.25f, 0.25f);
514 e.u.AddParticle.Vel.y += RAND_FLOAT(-0.25f, 0.25f);
515 e.u.AddParticle.Angle = RAND_DOUBLE(0, MPI * 2);
516 e.u.AddParticle.DZ = (float)((rand() % 6) + 6);
517 e.u.AddParticle.Spin = RAND_DOUBLE(-0.1, 0.1);
518 GameEventsEnqueue(&gGameEvents, e);
519 }
520
521 static struct vec2 GetMuzzleOffset(
522 const direction_e d, const gunstate_e state);
WeaponClassGetBarrelMuzzleOffset(const WeaponClass * wc,const CharSprites * cs,const int barrel,direction_e dir,const gunstate_e state)523 struct vec2 WeaponClassGetBarrelMuzzleOffset(
524 const WeaponClass *wc, const CharSprites *cs, const int barrel,
525 direction_e dir, const gunstate_e state)
526 {
527 if (wc->Type == GUNTYPE_MULTI)
528 {
529 wc = StrWeaponClass(wc->u.Guns[barrel]);
530 CASSERT(wc != NULL, "cannot find gun");
531 }
532 else
533 {
534 CASSERT(barrel == 0, "unexpected barrel index");
535 }
536 if (!WeaponClassHasMuzzle(wc))
537 {
538 return svec2_zero();
539 }
540 CASSERT(wc->u.Normal.Sprites != NULL, "Gun has no pic");
541 CASSERT(barrel < 2, "up to two barrels supported");
542 // For the other barrel, mirror dir and offset along X axis
543 if (barrel == 1)
544 {
545 dir = DirectionMirrorX(dir);
546 }
547 const struct vec2 gunOffset =
548 cs->Offsets.Dir[barrel == 0 ? BODY_PART_GUN_R : BODY_PART_GUN_L][dir];
549 const struct vec2 offset =
550 svec2_add(gunOffset, GetMuzzleOffset(dir, state));
551 if (barrel == 1)
552 {
553 return svec2(-offset.x, offset.y);
554 }
555 return offset;
556 }
GetMuzzleOffset(const direction_e d,const gunstate_e state)557 static struct vec2 GetMuzzleOffset(const direction_e d, const gunstate_e state)
558 {
559 // TODO: gun-specific muzzle offsets
560 #define BARREL_LENGTH 10
561 #define BARREL_LENGTH_READY 7
562 // Barrel slightly shortened when not firing
563 const double barrelLength =
564 state == GUNSTATE_FIRING ? BARREL_LENGTH : BARREL_LENGTH_READY;
565 return svec2_scale(Vec2FromRadians(dir2radians[d]), (float)barrelLength);
566 }
WeaponClassGetMuzzleHeight(const WeaponClass * wc,const gunstate_e state,const int barrel)567 float WeaponClassGetMuzzleHeight(
568 const WeaponClass *wc, const gunstate_e state, const int barrel)
569 {
570 // Muzzle slightly higher when not firing
571 // TODO: convert MuzzleHeight to float
572 const int muzzleHeight = WC_BARREL_ATTR(*wc, MuzzleHeight, barrel);
573 return (
574 float)(muzzleHeight + (state == GUNSTATE_FIRING ? 0 : 4 * Z_FACTOR));
575 }
576
WeaponClassHasMuzzle(const WeaponClass * wc)577 bool WeaponClassHasMuzzle(const WeaponClass *wc)
578 {
579 return wc->Type == GUNTYPE_NORMAL && wc->u.Normal.Sprites != NULL &&
580 wc->u.Normal.CanShoot;
581 }
WeaponClassIsHighDPS(const WeaponClass * wc)582 bool WeaponClassIsHighDPS(const WeaponClass *wc)
583 {
584 if (wc->Type == GUNTYPE_MULTI)
585 {
586 return WeaponClassIsHighDPS(StrWeaponClass(wc->u.Guns[0])) ||
587 WeaponClassIsHighDPS(StrWeaponClass(wc->u.Guns[1]));
588 }
589 CA_FOREACH(const BulletClass *, bc, wc->u.Normal.Bullets)
590 // TODO: generalised way of determining explosive bullets
591 if ((*bc)->Falling.DropGuns.size > 0 || (*bc)->OutOfRangeGuns.size > 0 ||
592 (*bc)->HitGuns.size > 0)
593 {
594 return true;
595 }
596 CA_FOREACH_END()
597 return false;
598 }
WeaponClassGetRange(const WeaponClass * wc)599 float WeaponClassGetRange(const WeaponClass *wc)
600 {
601 if (wc->Type == GUNTYPE_MULTI)
602 {
603 return (WeaponClassGetRange(StrWeaponClass(wc->u.Guns[0])) +
604 WeaponClassGetRange(StrWeaponClass(wc->u.Guns[1]))) /
605 2.0f;
606 }
607 float maxRange = 0;
608 CA_FOREACH(const BulletClass *, bc, wc->u.Normal.Bullets)
609 const float speed = ((*bc)->SpeedLow + (*bc)->SpeedHigh) / 2;
610 const int range = ((*bc)->RangeLow + (*bc)->RangeHigh) / 2;
611 float effectiveRange = speed * range;
612 if ((*bc)->Falling.GravityFactor != 0 && (*bc)->Falling.DestroyOnDrop)
613 {
614 // Halve effective range
615 // TODO: this assumes a certain bouncing range
616 effectiveRange *= 0.5f;
617 }
618 maxRange = MAX(maxRange, effectiveRange);
619 CA_FOREACH_END()
620 return maxRange;
621 }
WeaponClassIsLongRange(const WeaponClass * wc)622 bool WeaponClassIsLongRange(const WeaponClass *wc)
623 {
624 return WeaponClassGetRange(wc) > 130;
625 }
WeaponClassIsShortRange(const WeaponClass * wc)626 bool WeaponClassIsShortRange(const WeaponClass *wc)
627 {
628 return WeaponClassGetRange(wc) < 100;
629 }
WeaponClassCanShoot(const WeaponClass * wc)630 bool WeaponClassCanShoot(const WeaponClass *wc)
631 {
632 if (wc->Type == GUNTYPE_MULTI)
633 {
634 return WeaponClassCanShoot(WeaponClassGetBarrel(wc, 0)) ||
635 WeaponClassCanShoot(WeaponClassGetBarrel(wc, 1));
636 }
637 return wc->u.Normal.CanShoot;
638 }
WeaponClassNumBarrels(const WeaponClass * wc)639 int WeaponClassNumBarrels(const WeaponClass *wc)
640 {
641 return wc->Type == GUNTYPE_MULTI ? 2 : 1;
642 }
WeaponClassGetBarrel(const WeaponClass * wc,const int barrel)643 const WeaponClass *WeaponClassGetBarrel(
644 const WeaponClass *wc, const int barrel)
645 {
646 if (wc->Type == GUNTYPE_MULTI)
647 {
648 return StrWeaponClass(wc->u.Guns[barrel]);
649 }
650 return wc;
651 }
WeaponClassGetBullet(const WeaponClass * wc,const int barrel)652 const BulletClass *WeaponClassGetBullet(
653 const WeaponClass *wc, const int barrel)
654 {
655 if (barrel == -1)
656 {
657 return NULL;
658 }
659 wc = WeaponClassGetBarrel(wc, barrel);
660 CA_FOREACH(const BulletClass *, bc, wc->u.Normal.Bullets)
661 if (*bc)
662 return *bc;
663 CA_FOREACH_END()
664 return NULL;
665 }
666
BulletAndWeaponInitialize(BulletClasses * b,WeaponClasses * wcs,const char * bpath,const char * gpath)667 void BulletAndWeaponInitialize(
668 BulletClasses *b, WeaponClasses *wcs, const char *bpath, const char *gpath)
669 {
670 BulletInitialize(b);
671
672 FILE *bf = NULL;
673 FILE *gf = NULL;
674 json_t *broot = NULL;
675 json_t *groot = NULL;
676 enum json_error e;
677
678 // 2-pass bullet loading will free root for us
679 bool freeBRoot = true;
680 char buf[CDOGS_PATH_MAX];
681 GetDataFilePath(buf, bpath);
682 bf = fopen(buf, "r");
683 if (bf == NULL)
684 {
685 LOG(LM_MAP, LL_ERROR, "Error: cannot load bullets file %s", buf);
686 goto bail;
687 }
688 e = json_stream_parse(bf, &broot);
689 if (e != JSON_OK)
690 {
691 LOG(LM_MAP, LL_ERROR, "Error parsing bullets file %s [error %d]", buf,
692 (int)e);
693 goto bail;
694 }
695 BulletLoadJSON(b, &b->Classes, broot);
696
697 WeaponClassesInitialize(wcs);
698 GetDataFilePath(buf, gpath);
699 gf = fopen(buf, "r");
700 if (gf == NULL)
701 {
702 LOG(LM_MAP, LL_ERROR, "Error: cannot load guns file %s", buf);
703 goto bail;
704 }
705 e = json_stream_parse(gf, &groot);
706 if (e != JSON_OK)
707 {
708 LOG(LM_MAP, LL_ERROR, "Error parsing guns file %s [error %d]", buf,
709 (int)e);
710 goto bail;
711 }
712 WeaponClassesLoadJSON(wcs, &wcs->Guns, groot);
713
714 BulletLoadWeapons(b);
715 freeBRoot = false;
716
717 bail:
718 if (bf)
719 {
720 fclose(bf);
721 }
722 if (gf)
723 {
724 fclose(gf);
725 }
726 if (freeBRoot)
727 {
728 json_free_value(&broot);
729 }
730 json_free_value(&groot);
731 }
732