Add ammo pickups (#278)
Guns play clicking sound when out of ammo Cash replaces score if ammo enabled
77
data/ammo.json
Normal file
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"Version": 1,
|
||||
"Ammo": [
|
||||
{
|
||||
"Name": "Bullets",
|
||||
"Pic": "bullet_pickup",
|
||||
"Amount": 20,
|
||||
"Max": 200
|
||||
},
|
||||
{
|
||||
"Name": "Grenades",
|
||||
"Pic": "grenade_pickup",
|
||||
"Amount": 1,
|
||||
"Max": 5
|
||||
},
|
||||
{
|
||||
"Name": "Gas tank",
|
||||
"Pic": "gas_tank",
|
||||
"Amount": 30,
|
||||
"Max": 300
|
||||
},
|
||||
{
|
||||
"Name": "Shells",
|
||||
"Pic": "shells",
|
||||
"Amount": 5,
|
||||
"Max": 50
|
||||
},
|
||||
{
|
||||
"Name": "Cells",
|
||||
"Pic": "cells",
|
||||
"Amount": 8,
|
||||
"Max": 100
|
||||
},
|
||||
{
|
||||
"Name": "Frag grenades",
|
||||
"Pic": "frag_grenade_pickup",
|
||||
"Amount": 1,
|
||||
"Max": 5
|
||||
},
|
||||
{
|
||||
"Name": "Molotovs",
|
||||
"Pic": "molotov",
|
||||
"Amount": 1,
|
||||
"Max": 5
|
||||
},
|
||||
{
|
||||
"Name": "Mines",
|
||||
"Pic": "mine_pickup",
|
||||
"Amount": 1,
|
||||
"Max": 5
|
||||
},
|
||||
{
|
||||
"Name": "Dynamite",
|
||||
"Pic": "dynamite_pickup",
|
||||
"Amount": 2,
|
||||
"Max": 10
|
||||
},
|
||||
{
|
||||
"Name": "Gas bombs",
|
||||
"Pic": "gas_grenade_pickup",
|
||||
"Amount": 4,
|
||||
"Max": 20
|
||||
},
|
||||
{
|
||||
"Name": "Mini cells",
|
||||
"Pic": "mini_cells",
|
||||
"Amount": 20,
|
||||
"Max": 500
|
||||
},
|
||||
{
|
||||
"Name": "Rockets",
|
||||
"Pic": "rockets",
|
||||
"Amount": 4,
|
||||
"Max": 32
|
||||
}
|
||||
]
|
||||
}
|
@@ -40,7 +40,8 @@
|
||||
"Sound": "mg",
|
||||
"Recoil": 0.17,
|
||||
"MuzzleFlashParticle": "muzzle_flash_mg",
|
||||
"Brass": "brass_big"
|
||||
"Brass": "brass_big",
|
||||
"Ammo": "Bullets"
|
||||
},
|
||||
{
|
||||
"Index": 2,
|
||||
@@ -51,7 +52,8 @@
|
||||
"Cost": 20,
|
||||
"Lock": 30,
|
||||
"Sound": "launch",
|
||||
"Elevation": 24
|
||||
"Elevation": 24,
|
||||
"Ammo": "Grenades"
|
||||
},
|
||||
{
|
||||
"Index": 3,
|
||||
@@ -62,7 +64,8 @@
|
||||
"Lock": 6,
|
||||
"Sound": "flamer",
|
||||
"SoundLockLength": 36,
|
||||
"MuzzleFlashParticle": "muzzle_flash_flamer"
|
||||
"MuzzleFlashParticle": "muzzle_flash_flamer",
|
||||
"Ammo": "Gas tank"
|
||||
},
|
||||
{
|
||||
"Index": 4,
|
||||
@@ -77,7 +80,8 @@
|
||||
"SpreadCount": 5,
|
||||
"SpreadWidth": 0.2,
|
||||
"MuzzleFlashParticle": "muzzle_flash_shotgun",
|
||||
"Brass": "shotshell"
|
||||
"Brass": "shotshell",
|
||||
"Ammo": "Shells"
|
||||
},
|
||||
{
|
||||
"Index": 5,
|
||||
@@ -87,7 +91,8 @@
|
||||
"Cost": 2,
|
||||
"Lock": 20,
|
||||
"Sound": "fusion",
|
||||
"MuzzleFlashParticle": "muzzle_flash_powergun"
|
||||
"MuzzleFlashParticle": "muzzle_flash_powergun",
|
||||
"Ammo": "Cells"
|
||||
},
|
||||
{
|
||||
"Index": 6,
|
||||
@@ -98,7 +103,8 @@
|
||||
"Cost": 20,
|
||||
"Lock": 30,
|
||||
"Sound": "launch",
|
||||
"Elevation": 24
|
||||
"Elevation": 24,
|
||||
"Ammo": "Frag grenades"
|
||||
},
|
||||
{
|
||||
"Index": 7,
|
||||
@@ -109,7 +115,8 @@
|
||||
"Cost": 20,
|
||||
"Lock": 30,
|
||||
"Sound": "launch",
|
||||
"Elevation": 24
|
||||
"Elevation": 24,
|
||||
"Ammo": "Molotovs"
|
||||
},
|
||||
{
|
||||
"Index": 8,
|
||||
@@ -123,7 +130,8 @@
|
||||
"ReloadSound": "powergun_r",
|
||||
"MuzzleFlashSprite": "muzzle_flash_big",
|
||||
"MuzzleFlashColor": "00ffff",
|
||||
"MuzzleFlashDuration": 10
|
||||
"MuzzleFlashDuration": 10,
|
||||
"Ammo": "Cells"
|
||||
},
|
||||
{
|
||||
"Index": 9,
|
||||
@@ -136,7 +144,8 @@
|
||||
"ReloadLead": 15,
|
||||
"Sound": "hahaha",
|
||||
"ReloadSound": "package_r",
|
||||
"MuzzleHeight": 0
|
||||
"MuzzleHeight": 0,
|
||||
"Ammo": "Mines"
|
||||
},
|
||||
{
|
||||
"Index": 10,
|
||||
@@ -149,7 +158,8 @@
|
||||
"ReloadLead": 15,
|
||||
"Sound": "hahaha",
|
||||
"ReloadSound": "package_r",
|
||||
"MuzzleHeight": 0
|
||||
"MuzzleHeight": 0,
|
||||
"Ammo": "Dynamite"
|
||||
},
|
||||
{
|
||||
"Index": 11,
|
||||
@@ -160,7 +170,8 @@
|
||||
"Cost": 7,
|
||||
"Lock": 30,
|
||||
"Sound": "launch",
|
||||
"Elevation": 24
|
||||
"Elevation": 24,
|
||||
"Ammo": "Gas bombs"
|
||||
},
|
||||
{
|
||||
"Index": 12,
|
||||
@@ -172,7 +183,8 @@
|
||||
"ReloadLead": 15,
|
||||
"Sound": "powergun",
|
||||
"ReloadSound": "powergun_r",
|
||||
"MuzzleFlashParticle": "muzzle_flash_petrifier"
|
||||
"MuzzleFlashParticle": "muzzle_flash_petrifier",
|
||||
"Ammo": "Cells"
|
||||
},
|
||||
{
|
||||
"Index": 13,
|
||||
@@ -182,7 +194,8 @@
|
||||
"Cost": 5,
|
||||
"Lock": 30,
|
||||
"Sound": "fusion",
|
||||
"MuzzleFlashParticle": "muzzle_flash_browny"
|
||||
"MuzzleFlashParticle": "muzzle_flash_browny",
|
||||
"Ammo": "Cells"
|
||||
},
|
||||
{
|
||||
"Index": 14,
|
||||
@@ -193,7 +206,8 @@
|
||||
"Cost": 10,
|
||||
"Lock": 30,
|
||||
"Sound": "launch",
|
||||
"Elevation": 24
|
||||
"Elevation": 24,
|
||||
"Ammo": "Gas bombs"
|
||||
},
|
||||
{
|
||||
"Index": 15,
|
||||
@@ -204,7 +218,8 @@
|
||||
"Lock": 6,
|
||||
"Sound": "flamer",
|
||||
"SoundLockLength": 36,
|
||||
"MuzzleFlashParticle": "muzzle_flash_gasgun"
|
||||
"MuzzleFlashParticle": "muzzle_flash_gasgun",
|
||||
"Ammo": "Gas tank"
|
||||
},
|
||||
{
|
||||
"Index": 16,
|
||||
@@ -215,7 +230,8 @@
|
||||
"Lock": 4,
|
||||
"Sound": "pulse",
|
||||
"Recoil": 0.37,
|
||||
"MuzzleFlashParticle": "muzzle_flash_pulse"
|
||||
"MuzzleFlashParticle": "muzzle_flash_pulse",
|
||||
"Ammo": "Mini cells"
|
||||
},
|
||||
{
|
||||
"Index": 17,
|
||||
@@ -225,7 +241,8 @@
|
||||
"Cost": 7,
|
||||
"Lock": 30,
|
||||
"Sound": "swell",
|
||||
"MuzzleFlashParticle": "muzzle_flash_heatseeker"
|
||||
"MuzzleFlashParticle": "muzzle_flash_heatseeker",
|
||||
"Ammo": "Rockets"
|
||||
},
|
||||
{
|
||||
"Name": "Swarmer",
|
||||
@@ -237,7 +254,8 @@
|
||||
"SpreadCount": 4,
|
||||
"SpreadWidth": 0.4,
|
||||
"Sound": "swarmer",
|
||||
"MuzzleFlashParticle": "muzzle_flash_heatseeker"
|
||||
"MuzzleFlashParticle": "muzzle_flash_heatseeker",
|
||||
"Ammo": "Rockets"
|
||||
},
|
||||
{
|
||||
"Name": "Launcher",
|
||||
@@ -246,7 +264,8 @@
|
||||
"Cost": 15,
|
||||
"Lock": 70,
|
||||
"Sound": "rocket_launch",
|
||||
"MuzzleFlashParticle": "muzzle_flash_shotgun"
|
||||
"MuzzleFlashParticle": "muzzle_flash_shotgun",
|
||||
"Ammo": "Rockets"
|
||||
},
|
||||
{
|
||||
"Name": "Pistol",
|
||||
|
BIN
graphics/bullet_pickup.png
Normal file
After Width: | Height: | Size: 198 B |
4
graphics/bullet_pickup.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/cells.png
Normal file
After Width: | Height: | Size: 441 B |
4
graphics/cells.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/dynamite_pickup.png
Normal file
After Width: | Height: | Size: 294 B |
4
graphics/dynamite_pickup.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/frag_grenade_pickup.png
Normal file
After Width: | Height: | Size: 229 B |
4
graphics/frag_grenade_pickup.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/gas_grenade_pickup.png
Normal file
After Width: | Height: | Size: 225 B |
4
graphics/gas_grenade_pickup.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/gas_tank.png
Normal file
After Width: | Height: | Size: 309 B |
4
graphics/gas_tank.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/grenade_pickup.png
Normal file
After Width: | Height: | Size: 166 B |
5
graphics/grenade_pickup.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Adapted from Various inventory 24 pixel icon set by OceansDream
|
||||
http://opengameart.org/content/various-inventory-24-pixel-icon-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
||||
http://creativecommons.org/licenses/by-sa/3.0/
|
BIN
graphics/mine_pickup.png
Normal file
After Width: | Height: | Size: 443 B |
BIN
graphics/mini_cells.png
Normal file
After Width: | Height: | Size: 269 B |
4
graphics/mini_cells.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/molotov.png
Normal file
After Width: | Height: | Size: 334 B |
4
graphics/molotov.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from Molotov Cocktail by OpenClips
|
||||
http://pixabay.com/en/molotov-cocktail-bottle-explosive-157193/
|
||||
|
||||
http://creativecommons.org/publicdomain/zero/1.0/deed.en
|
4
graphics/molotov_pickup.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
graphics/rockets.png
Normal file
After Width: | Height: | Size: 440 B |
BIN
graphics/shells.png
Normal file
After Width: | Height: | Size: 289 B |
4
graphics/shells.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Adapted from 141 Military Icons Set by AngryMeteor.com
|
||||
http://opengameart.org/content/141-military-icons-set
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
BIN
sounds/ammo.ogg
Normal file
BIN
sounds/click.ogg
Normal file
4
sounds/click.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Derived from Stapler_Hands_05.wav by Simon_Lacelle
|
||||
http://freesound.org/people/Simon_Lacelle/sounds/67352/
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
@@ -53,6 +53,7 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <cdogs/ammo.h>
|
||||
#include <cdogs/campaigns.h>
|
||||
#include <cdogs/config.h>
|
||||
#include <cdogs/draw.h>
|
||||
@@ -377,6 +378,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
GetDataFilePath(buf, "data/particles.json");
|
||||
ParticleClassesInit(&gParticleClasses, buf);
|
||||
GetDataFilePath(buf, "data/ammo.json");
|
||||
AmmoInitialize(&gAmmo, buf);
|
||||
GetDataFilePath(buf, "data/bullets.json");
|
||||
GetDataFilePath(buf2, "data/guns.json");
|
||||
BulletAndWeaponInitialize(
|
||||
@@ -423,6 +426,7 @@ bail:
|
||||
MapTerminate(&gMap);
|
||||
PlayerDataTerminate(&gPlayerDatas);
|
||||
ParticleClassesTerminate(&gParticleClasses);
|
||||
AmmoTerminate(&gAmmo);
|
||||
WeaponTerminate(&gGunDescriptions);
|
||||
BulletTerminate(&gBulletClasses);
|
||||
MissionOptionsTerminate(&gMission);
|
||||
|
@@ -9,6 +9,7 @@ set(CDOGS_SOURCES
|
||||
ai_coop.c
|
||||
ai_utils.c
|
||||
algorithms.c
|
||||
ammo.c
|
||||
AStar.c
|
||||
automap.c
|
||||
blit.c
|
||||
@@ -38,7 +39,6 @@ set(CDOGS_SOURCES
|
||||
grafx.c
|
||||
grafx_bg.c
|
||||
handle_game_events.c
|
||||
health_pickup.c
|
||||
hiscores.c
|
||||
hud.c
|
||||
input.c
|
||||
@@ -71,6 +71,7 @@ set(CDOGS_SOURCES
|
||||
pics.c
|
||||
player.c
|
||||
player_template.c
|
||||
powerup.c
|
||||
quick_play.c
|
||||
screen_shake.c
|
||||
sounds.c
|
||||
@@ -87,6 +88,7 @@ set(CDOGS_HEADERS
|
||||
ai_coop.h
|
||||
ai_utils.h
|
||||
algorithms.h
|
||||
ammo.h
|
||||
AStar.h
|
||||
automap.h
|
||||
blit.h
|
||||
@@ -115,7 +117,6 @@ set(CDOGS_HEADERS
|
||||
grafx.h
|
||||
grafx_bg.h
|
||||
handle_game_events.h
|
||||
health_pickup.h
|
||||
hiscores.h
|
||||
hud.h
|
||||
input.h
|
||||
@@ -148,6 +149,7 @@ set(CDOGS_HEADERS
|
||||
pics.h
|
||||
player.h
|
||||
player_template.h
|
||||
powerup.h
|
||||
quick_play.h
|
||||
screen_shake.h
|
||||
sounds.h
|
||||
|
@@ -195,7 +195,8 @@ Vec2i PlacePlayer(
|
||||
if (pumpEvents)
|
||||
{
|
||||
// Process the events that actually place the players
|
||||
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
|
||||
HandleGameEvents(
|
||||
&gGameEvents, NULL, NULL, NULL, NULL, &gEventHandlers);
|
||||
}
|
||||
|
||||
return Vec2iNew(aa.FullPos.x, aa.FullPos.y);
|
||||
|
@@ -55,6 +55,7 @@
|
||||
|
||||
#include "actor_placement.h"
|
||||
#include "ai_utils.h"
|
||||
#include "ammo.h"
|
||||
#include "character.h"
|
||||
#include "collision.h"
|
||||
#include "config.h"
|
||||
@@ -80,6 +81,7 @@
|
||||
#define SLIDE_Y (TILE_HEIGHT / 3)
|
||||
#define VEL_DECAY_X (TILE_WIDTH * 2)
|
||||
#define VEL_DECAY_Y (TILE_WIDTH * 2) // Note: deliberately tile width
|
||||
#define SOUND_LOCK_WEAPON_CLICK 20
|
||||
|
||||
|
||||
CArray gPlayerIds;
|
||||
@@ -604,11 +606,34 @@ void InjureActor(TActor * actor, int injury)
|
||||
}
|
||||
}
|
||||
|
||||
void ActorAddAmmo(TActor *actor, AddAmmo a)
|
||||
{
|
||||
int *ammo = CArrayGet(&actor->ammo, a.Id);
|
||||
*ammo += a.Amount;
|
||||
const int ammoMax = AmmoGetById(&gAmmo, a.Id)->Max;
|
||||
*ammo = CLAMP(*ammo, 0, ammoMax);
|
||||
}
|
||||
|
||||
void Shoot(TActor *actor)
|
||||
{
|
||||
Weapon *gun = ActorGetGun(actor);
|
||||
if (!WeaponCanFire(gun))
|
||||
if (!ActorCanFire(actor))
|
||||
{
|
||||
if (!WeaponIsLocked(gun) && gConfig.Game.Ammo)
|
||||
{
|
||||
CASSERT(
|
||||
*(int *)CArrayGet(&actor->ammo, gun->Gun->AmmoId) == 0,
|
||||
"should be out of ammo");
|
||||
// Play a clicking sound if this gun is out of ammo
|
||||
if (gun->clickLock <= 0)
|
||||
{
|
||||
GameEvent e = GameEventNew(GAME_EVENT_SOUND_AT);
|
||||
e.u.SoundAt.Pos = Vec2iFull2Real(actor->Pos);
|
||||
e.u.SoundAt.Sound = gSoundDevice.clickSound;
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
gun->clickLock = SOUND_LOCK_WEAPON_CLICK;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
WeaponFire(
|
||||
@@ -618,12 +643,24 @@ void Shoot(TActor *actor)
|
||||
actor->flags,
|
||||
actor->playerIndex,
|
||||
actor->uid);
|
||||
if (actor->playerIndex >= 0 && gun->Gun->Cost != 0)
|
||||
if (actor->playerIndex >= 0)
|
||||
{
|
||||
GameEvent e = GameEventNew(GAME_EVENT_SCORE);
|
||||
e.u.Score.PlayerIndex = actor->playerIndex;
|
||||
e.u.Score.Score = -gun->Gun->Cost;
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
if (gConfig.Game.Ammo)
|
||||
{
|
||||
GameEvent e = GameEventNew(GAME_EVENT_USE_AMMO);
|
||||
e.u.UseAmmo.PlayerIndex = actor->playerIndex;
|
||||
e.u.UseAmmo.UseAmmo.Id = gun->Gun->AmmoId;
|
||||
e.u.UseAmmo.UseAmmo.Amount = -1;
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
}
|
||||
else if (gun->Gun->Cost != 0)
|
||||
{
|
||||
// Classic C-Dogs score consumption
|
||||
GameEvent e = GameEventNew(GAME_EVENT_SCORE);
|
||||
e.u.Score.PlayerIndex = actor->playerIndex;
|
||||
e.u.Score.Score = -gun->Gun->Cost;
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -944,6 +981,14 @@ TActor *ActorAdd(NetMsgActorAdd aa)
|
||||
TActor *actor = CArrayGet(&gActors, aa.Id);
|
||||
memset(actor, 0, sizeof *actor);
|
||||
CArrayInit(&actor->guns, sizeof(Weapon));
|
||||
CArrayInit(&actor->ammo, sizeof(int));
|
||||
for (int i = 0; i < AmmoGetNumClasses(&gAmmo); i++)
|
||||
{
|
||||
// Initialise with twice the standard ammo amount
|
||||
// TODO: special game modes, keeping track of ammo, ammo persistence
|
||||
int amount = AmmoGetById(&gAmmo, i)->Amount * 2;
|
||||
CArrayPushBack(&actor->ammo, &amount);
|
||||
}
|
||||
Character *c;
|
||||
if (aa.PlayerId >= 0)
|
||||
{
|
||||
@@ -1002,6 +1047,7 @@ void ActorDestroy(int id)
|
||||
TActor *actor = CArrayGet(&gActors, id);
|
||||
CASSERT(actor->isInUse, "Destroying in-use actor");
|
||||
CArrayTerminate(&actor->guns);
|
||||
CArrayTerminate(&actor->ammo);
|
||||
MapRemoveTileItem(&gMap, &actor->tileItem);
|
||||
for (int i = 0; i < (int)gPlayerDatas.size; i++)
|
||||
{
|
||||
@@ -1109,6 +1155,12 @@ Weapon *ActorGetGun(const TActor *a)
|
||||
{
|
||||
return CArrayGet(&a->guns, a->gunIndex);
|
||||
}
|
||||
bool ActorCanFire(const TActor *a)
|
||||
{
|
||||
const Weapon *w = ActorGetGun(a);
|
||||
const bool hasAmmo = *(int *)CArrayGet(&a->ammo, w->Gun->AmmoId) > 0;
|
||||
return !WeaponIsLocked(w) && (!gConfig.Game.Ammo || hasAmmo);
|
||||
}
|
||||
bool ActorTrySwitchGun(TActor *a)
|
||||
{
|
||||
if (a->guns.size < 2)
|
||||
|
@@ -125,6 +125,7 @@ typedef struct Actor
|
||||
int playerIndex; // -1 unless a human player
|
||||
int uid; // unique ID across all actors
|
||||
CArray guns; // of Weapon
|
||||
CArray ammo; // of int
|
||||
int gunIndex;
|
||||
|
||||
int health;
|
||||
@@ -175,6 +176,13 @@ void BuildTranslationTables(const TPalette palette);
|
||||
void ActorHeal(TActor *actor, int health);
|
||||
void InjureActor(TActor * actor, int injury);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int Id;
|
||||
int Amount;
|
||||
} AddAmmo;
|
||||
void ActorAddAmmo(TActor *actor, AddAmmo a);
|
||||
|
||||
void ActorsInit(void);
|
||||
void ActorsTerminate(void);
|
||||
int ActorsGetFreeIndex(void);
|
||||
@@ -183,6 +191,7 @@ void ActorDestroy(int id);
|
||||
|
||||
const Character *ActorGetCharacter(const TActor *a);
|
||||
Weapon *ActorGetGun(const TActor *a);
|
||||
bool ActorCanFire(const TActor *a);
|
||||
bool ActorTrySwitchGun(TActor *a);
|
||||
bool ActorIsImmune(const TActor *actor, const special_damage_e damage);
|
||||
// Taking a hit only gives the appearance (pushback, special effect)
|
||||
|
@@ -246,7 +246,7 @@ static int WillFire(TActor * actor, int roll)
|
||||
{
|
||||
const CharBot *bot = ActorGetCharacter(actor)->bot;
|
||||
if ((actor->flags & FLAGS_VISIBLE) != 0 &&
|
||||
WeaponCanFire(ActorGetGun(actor)) &&
|
||||
ActorCanFire(actor) &&
|
||||
roll < bot->probabilityToShoot)
|
||||
{
|
||||
if ((actor->flags & FLAGS_GOOD_GUY) != 0)
|
||||
@@ -534,7 +534,8 @@ void InitializeBadGuys(void)
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
|
||||
// Process the events that actually place the actors
|
||||
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
|
||||
HandleGameEvents(
|
||||
&gGameEvents, NULL, NULL, NULL, NULL, &gEventHandlers);
|
||||
}
|
||||
}
|
||||
else if (mobj->Type == OBJECTIVE_RESCUE)
|
||||
@@ -563,7 +564,8 @@ void InitializeBadGuys(void)
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
|
||||
// Process the events that actually place the actors
|
||||
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
|
||||
HandleGameEvents(
|
||||
&gGameEvents, NULL, NULL, NULL, NULL, &gEventHandlers);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,6 +600,7 @@ void CreateEnemies(void)
|
||||
gBaddieCount++;
|
||||
|
||||
// Process the events that actually place the actors
|
||||
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
|
||||
HandleGameEvents(
|
||||
&gGameEvents, NULL, NULL, NULL, NULL, &gEventHandlers);
|
||||
}
|
||||
}
|
||||
|
166
src/cdogs/ammo.c
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
C-Dogs SDL
|
||||
A port of the legendary (and fun) action/arcade cdogs.
|
||||
Copyright (c) 2014, Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "ammo.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "json_utils.h"
|
||||
#include "pic_manager.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
AmmoClasses gAmmo;
|
||||
|
||||
|
||||
// TODO: use map structure?
|
||||
Ammo *StrAmmo(const char *s)
|
||||
{
|
||||
return AmmoGetById(&gAmmo, StrAmmoId(s));
|
||||
}
|
||||
int StrAmmoId(const char *s)
|
||||
{
|
||||
if (s == NULL || strlen(s) == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < (int)gAmmo.CustomAmmo.size; i++)
|
||||
{
|
||||
Ammo *a = CArrayGet(&gAmmo.CustomAmmo, i);
|
||||
if (strcmp(s, a->Name) == 0)
|
||||
{
|
||||
return i + (int)gAmmo.Ammo.size;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < (int)gAmmo.Ammo.size; i++)
|
||||
{
|
||||
Ammo *a = CArrayGet(&gAmmo.Ammo, i);
|
||||
if (strcmp(s, a->Name) == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
CASSERT(false, "cannot parse ammo name");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define VERSION 1
|
||||
static void LoadAmmo(Ammo *a, json_t *node);
|
||||
void AmmoInitialize(AmmoClasses *ammo, const char *path)
|
||||
{
|
||||
memset(ammo, 0, sizeof *ammo);
|
||||
CArrayInit(&ammo->Ammo, sizeof(Ammo));
|
||||
CArrayInit(&ammo->CustomAmmo, sizeof(Ammo));
|
||||
|
||||
json_t *root = NULL;
|
||||
enum json_error e;
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (f == NULL)
|
||||
{
|
||||
printf("Error: cannot load ammo file %s\n", path);
|
||||
goto bail;
|
||||
}
|
||||
e = json_stream_parse(f, &root);
|
||||
if (e != JSON_OK)
|
||||
{
|
||||
printf("Error parsing ammo file %s [error %d]\n", path, (int)e);
|
||||
goto bail;
|
||||
}
|
||||
AmmoLoadJSON(&ammo->Ammo, root);
|
||||
|
||||
bail:
|
||||
if (f)
|
||||
{
|
||||
fclose(f);
|
||||
}
|
||||
json_free_value(&root);
|
||||
}
|
||||
void AmmoLoadJSON(CArray *ammo, json_t *node)
|
||||
{
|
||||
int version;
|
||||
LoadInt(&version, node, "Version");
|
||||
if (version > VERSION || version <= 0)
|
||||
{
|
||||
CASSERT(false, "cannot read ammo file version");
|
||||
return;
|
||||
}
|
||||
|
||||
json_t *ammoNode = json_find_first_label(node, "Ammo")->child;
|
||||
for (json_t *child = ammoNode->child; child; child = child->next)
|
||||
{
|
||||
Ammo a;
|
||||
LoadAmmo(&a, child);
|
||||
CArrayPushBack(ammo, &a);
|
||||
}
|
||||
}
|
||||
static void LoadAmmo(Ammo *a, json_t *node)
|
||||
{
|
||||
if (json_find_first_label(node, "Name"))
|
||||
{
|
||||
a->Name = GetString(node, "Name");
|
||||
}
|
||||
char *tmp;
|
||||
tmp = GetString(node, "Pic");
|
||||
a->Pic = PicManagerGetPic(&gPicManager, tmp);
|
||||
CFREE(tmp);
|
||||
LoadInt(&a->Amount, node, "Amount");
|
||||
LoadInt(&a->Max, node, "Max");
|
||||
}
|
||||
void AmmoClassesClear(CArray *ammo)
|
||||
{
|
||||
for (int i = 0; i < (int)ammo->size; i++)
|
||||
{
|
||||
Ammo *a = CArrayGet(ammo, i);
|
||||
CFREE(a->Name);
|
||||
}
|
||||
CArrayClear(ammo);
|
||||
}
|
||||
void AmmoTerminate(AmmoClasses *ammo)
|
||||
{
|
||||
AmmoClassesClear(&ammo->Ammo);
|
||||
CArrayTerminate(&ammo->Ammo);
|
||||
AmmoClassesClear(&ammo->CustomAmmo);
|
||||
CArrayTerminate(&ammo->CustomAmmo);
|
||||
}
|
||||
|
||||
Ammo *AmmoGetById(AmmoClasses *ammo, const int id)
|
||||
{
|
||||
if (id < (int)ammo->Ammo.size)
|
||||
{
|
||||
return CArrayGet(&ammo->Ammo, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CArrayGet(&ammo->CustomAmmo, id - (int)ammo->Ammo.size);
|
||||
}
|
||||
}
|
||||
|
||||
int AmmoGetNumClasses(const AmmoClasses *ammo)
|
||||
{
|
||||
return (int)ammo->Ammo.size + (int)ammo->CustomAmmo.size;
|
||||
}
|
58
src/cdogs/ammo.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
C-Dogs SDL
|
||||
A port of the legendary (and fun) action/arcade cdogs.
|
||||
Copyright (c) 2014, Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include "c_array.h"
|
||||
#include "pic.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *Name;
|
||||
const Pic *Pic;
|
||||
int Amount;
|
||||
int Max;
|
||||
} Ammo;
|
||||
typedef struct
|
||||
{
|
||||
CArray Ammo; // of Ammo
|
||||
CArray CustomAmmo; // of Ammo
|
||||
} AmmoClasses;
|
||||
extern AmmoClasses gAmmo;
|
||||
|
||||
Ammo *StrAmmo(const char *s);
|
||||
int StrAmmoId(const char *s);
|
||||
|
||||
void AmmoInitialize(AmmoClasses *ammo, const char *path);
|
||||
void AmmoLoadJSON(CArray *ammo, json_t *node);
|
||||
void AmmoClassesClear(CArray *ammo);
|
||||
void AmmoTerminate(AmmoClasses *ammo);
|
||||
|
||||
Ammo *AmmoGetById(AmmoClasses *ammo, const int id);
|
||||
int AmmoGetNumClasses(const AmmoClasses *ammo);
|
@@ -408,6 +408,7 @@ void ConfigLoadDefault(Config *config)
|
||||
config->Game.ShotsPushback = 1;
|
||||
config->Game.AllyCollision = ALLYCOLLISION_REPEL;
|
||||
config->Game.HealthPickups = true;
|
||||
config->Game.Ammo = true;
|
||||
config->Game.Gore = GORE_LOW;
|
||||
config->Game.LaserSight = LASER_SIGHT_NONE;
|
||||
config->Graphics.Brightness = 0;
|
||||
|
@@ -115,6 +115,7 @@ typedef struct
|
||||
bool ShotsPushback;
|
||||
AllyCollision AllyCollision;
|
||||
bool HealthPickups;
|
||||
bool Ammo;
|
||||
GoreAmount Gore;
|
||||
LaserSight LaserSight;
|
||||
} GameConfig;
|
||||
|
@@ -77,6 +77,7 @@ static void LoadGameConfigNode(
|
||||
JSON_UTILS_LOAD_ENUM(
|
||||
config->AllyCollision, node, "AllyCollision", StrAllyCollision);
|
||||
LoadBool(&config->HealthPickups, node, "HealthPickups");
|
||||
LoadBool(&config->Ammo, node, "Ammo");
|
||||
JSON_UTILS_LOAD_ENUM(
|
||||
config->Gore, node, "Gore", StrGoreAmount);
|
||||
JSON_UTILS_LOAD_ENUM(
|
||||
@@ -113,6 +114,8 @@ static void AddGameConfigNode(GameConfig *config, json_t *root)
|
||||
subConfig, "AllyCollision", config->AllyCollision, AllyCollisionStr);
|
||||
json_insert_pair_into_object(
|
||||
subConfig, "HealthPickups", json_new_bool(config->HealthPickups));
|
||||
json_insert_pair_into_object(
|
||||
subConfig, "Ammo", json_new_bool(config->Ammo));
|
||||
json_insert_pair_into_object(root, "Game", subConfig);
|
||||
JSON_UTILS_ADD_ENUM_PAIR(
|
||||
subConfig, "Gore", config->Gore, GoreAmountStr);
|
||||
|
@@ -47,6 +47,22 @@ FontOpts FontOptsNew(void)
|
||||
return opts;
|
||||
}
|
||||
|
||||
FontAlign FontAlignOpposite(const FontAlign align)
|
||||
{
|
||||
switch (align)
|
||||
{
|
||||
case ALIGN_START:
|
||||
return ALIGN_END;
|
||||
case ALIGN_CENTER:
|
||||
return ALIGN_CENTER;
|
||||
case ALIGN_END:
|
||||
return ALIGN_END;
|
||||
default:
|
||||
CASSERT(false, "Unknown font alignment");
|
||||
return ALIGN_START;
|
||||
}
|
||||
}
|
||||
|
||||
void FontLoad(Font *f, const char *imgPath, const char *jsonPath)
|
||||
{
|
||||
FILE *file = NULL;
|
||||
|
@@ -71,6 +71,8 @@ extern Font gFont;
|
||||
|
||||
FontOpts FontOptsNew(void);
|
||||
|
||||
FontAlign FontAlignOpposite(const FontAlign align);
|
||||
|
||||
void FontLoad(Font *f, const char *imgPath, const char *jsonPath);
|
||||
void FontFromImage(Font *f, SDL_Surface *image, json_t *data);
|
||||
void FontTerminate(Font *f);
|
||||
|
@@ -54,6 +54,9 @@ typedef enum
|
||||
GAME_EVENT_ACTOR_MOVE,
|
||||
GAME_EVENT_ADD_HEALTH_PICKUP,
|
||||
GAME_EVENT_TAKE_HEALTH_PICKUP,
|
||||
GAME_EVENT_ADD_AMMO_PICKUP,
|
||||
GAME_EVENT_TAKE_AMMO_PICKUP,
|
||||
GAME_EVENT_USE_AMMO,
|
||||
GAME_EVENT_MOBILE_OBJECT_REMOVE,
|
||||
GAME_EVENT_PARTICLE_REMOVE,
|
||||
GAME_EVENT_ADD_BULLET,
|
||||
@@ -99,11 +102,22 @@ typedef struct
|
||||
NetMsgActorAdd ActorAdd;
|
||||
NetMsgActorMove ActorMove;
|
||||
Vec2i AddPos;
|
||||
AddAmmoPickup AddAmmoPickup;
|
||||
struct
|
||||
{
|
||||
int PlayerIndex;
|
||||
int Health;
|
||||
} Heal;
|
||||
struct
|
||||
{
|
||||
int PlayerIndex;
|
||||
AddAmmo AddAmmo;
|
||||
} AddAmmo;
|
||||
struct
|
||||
{
|
||||
int PlayerIndex;
|
||||
AddAmmo UseAmmo;
|
||||
} UseAmmo;
|
||||
int MobileObjectRemoveId;
|
||||
int ParticleRemoveId;
|
||||
AddBullet AddBullet;
|
||||
|
@@ -39,19 +39,22 @@ static void HandleGameEvent(
|
||||
GameEvent *e,
|
||||
HUD *hud,
|
||||
ScreenShake *shake,
|
||||
HealthPickups *hp,
|
||||
PowerupSpawner *healthSpawner,
|
||||
CArray *ammoSpawners,
|
||||
EventHandlers *eventHandlers);
|
||||
void HandleGameEvents(
|
||||
CArray *store,
|
||||
HUD *hud,
|
||||
ScreenShake *shake,
|
||||
HealthPickups *hp,
|
||||
PowerupSpawner *healthSpawner,
|
||||
CArray *ammoSpawners,
|
||||
EventHandlers *eventHandlers)
|
||||
{
|
||||
for (int i = 0; i < (int)store->size; i++)
|
||||
{
|
||||
GameEvent *e = CArrayGet(store, i);
|
||||
HandleGameEvent(e, hud, shake, hp, eventHandlers);
|
||||
HandleGameEvent(
|
||||
e, hud, shake, healthSpawner, ammoSpawners, eventHandlers);
|
||||
}
|
||||
GameEventsClear(store);
|
||||
}
|
||||
@@ -59,7 +62,8 @@ static void HandleGameEvent(
|
||||
GameEvent *e,
|
||||
HUD *hud,
|
||||
ScreenShake *shake,
|
||||
HealthPickups *hp,
|
||||
PowerupSpawner *healthSpawner,
|
||||
CArray *ammoSpawners,
|
||||
EventHandlers *eventHandlers)
|
||||
{
|
||||
e->Delay--;
|
||||
@@ -137,12 +141,49 @@ static void HandleGameEvent(
|
||||
break;
|
||||
}
|
||||
ActorHeal(a, e->u.Heal.Health);
|
||||
HealthPickupsRemoveOne(hp);
|
||||
PowerupSpawnerRemoveOne(healthSpawner);
|
||||
HUDAddHealthUpdate(
|
||||
hud, e->u.Heal.PlayerIndex, e->u.Heal.Health);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GAME_EVENT_ADD_AMMO_PICKUP:
|
||||
MapPlaceAmmo(e->u.AddAmmoPickup);
|
||||
break;
|
||||
case GAME_EVENT_TAKE_AMMO_PICKUP:
|
||||
{
|
||||
const PlayerData *p =
|
||||
CArrayGet(&gPlayerDatas, e->u.Heal.PlayerIndex);
|
||||
if (IsPlayerAlive(p))
|
||||
{
|
||||
TActor *a = CArrayGet(&gActors, p->Id);
|
||||
if (!a->isInUse)
|
||||
{
|
||||
break;
|
||||
}
|
||||
ActorAddAmmo(a, e->u.AddAmmo.AddAmmo);
|
||||
PowerupSpawnerRemoveOne(
|
||||
CArrayGet(ammoSpawners, e->u.AddAmmo.AddAmmo.Id));
|
||||
// TODO: some sort of text effect showing ammo grab
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GAME_EVENT_USE_AMMO:
|
||||
{
|
||||
const PlayerData *p =
|
||||
CArrayGet(&gPlayerDatas, e->u.UseAmmo.PlayerIndex);
|
||||
if (IsPlayerAlive(p))
|
||||
{
|
||||
TActor *a = CArrayGet(&gActors, p->Id);
|
||||
if (!a->isInUse)
|
||||
{
|
||||
break;
|
||||
}
|
||||
ActorAddAmmo(a, e->u.UseAmmo.UseAmmo);
|
||||
// TODO: some sort of text effect showing ammo usage
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GAME_EVENT_MOBILE_OBJECT_REMOVE:
|
||||
MobObjDestroy(e->u.MobileObjectRemoveId);
|
||||
break;
|
||||
@@ -217,7 +258,9 @@ static void HandleGameEvent(
|
||||
e1.u.Score.PlayerIndex =
|
||||
e->u.UpdateObjective.PlayerIndex;
|
||||
e1.u.Score.Score = OBJECT_SCORE;
|
||||
HandleGameEvent(&e1, hud, shake, hp, eventHandlers);
|
||||
HandleGameEvent(
|
||||
&e1, hud, shake, healthSpawner, ammoSpawners,
|
||||
eventHandlers);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@@ -30,15 +30,16 @@
|
||||
|
||||
#include "c_array.h"
|
||||
#include "events.h"
|
||||
#include "health_pickup.h"
|
||||
#include "hud.h"
|
||||
#include "powerup.h"
|
||||
#include "screen_shake.h"
|
||||
|
||||
void HandleGameEvents(
|
||||
CArray *store,
|
||||
HUD *hud,
|
||||
ScreenShake *shake,
|
||||
HealthPickups *hp,
|
||||
PowerupSpawner *healthSpawner,
|
||||
CArray *ammoSpawners,
|
||||
EventHandlers *eventHandlers);
|
||||
|
||||
#endif
|
||||
|
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
Copyright (c) 2014, Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "health_pickup.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "ai_utils.h"
|
||||
#include "gamedata.h"
|
||||
|
||||
|
||||
#define SPAWN_TIME (20 * FPS_FRAMELIMIT)
|
||||
#define TIME_DECAY_EXPONENT 1.04
|
||||
#define HEALTH_W 6
|
||||
#define HEALTH_H 6
|
||||
#define MAX_TILES_PER_PICKUP 625
|
||||
|
||||
void HealthPickupsInit(HealthPickups *h, Map *map)
|
||||
{
|
||||
h->map = map;
|
||||
h->timer = 0;
|
||||
h->numPickups = 0;
|
||||
h->pickupsSpawned = 0;
|
||||
|
||||
// Update once
|
||||
HealthPickupsUpdate(h, 0);
|
||||
}
|
||||
|
||||
static bool TryPlacePickup(HealthPickups *h);
|
||||
void HealthPickupsUpdate(HealthPickups *h, int ticks)
|
||||
{
|
||||
// Don't spawn pickups if not allowed
|
||||
if (!AreHealthPickupsAllowed(gCampaign.Entry.Mode) ||
|
||||
!gConfig.Game.HealthPickups)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double scalar = 1.0;
|
||||
// Update time until next spawn based on:
|
||||
// Damage taken (find player with lowest health)
|
||||
int minHealth = ModeMaxHealth(gCampaign.Entry.Mode);
|
||||
int maxHealth = minHealth;
|
||||
for (int i = 0; i < (int)gPlayerDatas.size; i++)
|
||||
{
|
||||
const PlayerData *p = CArrayGet(&gPlayerDatas, i);
|
||||
if (!IsPlayerAlive(p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const TActor *player = CArrayGet(&gActors, p->Id);
|
||||
minHealth = MIN(minHealth, player->health);
|
||||
}
|
||||
// Double spawn rate if near 0 health
|
||||
scalar *= (minHealth + maxHealth) / (maxHealth * 2.0);
|
||||
|
||||
// Scale down over time
|
||||
scalar *= pow(TIME_DECAY_EXPONENT, h->pickupsSpawned);
|
||||
|
||||
h->timeUntilNextSpawn = (int)floor(scalar * SPAWN_TIME);
|
||||
|
||||
// Update time
|
||||
h->timer += ticks;
|
||||
|
||||
// Attempt to add health if time reached, and we haven't placed too many
|
||||
if (h->timer >= h->timeUntilNextSpawn &&
|
||||
h->map->NumExplorableTiles / MAX_TILES_PER_PICKUP + 1 > h->numPickups)
|
||||
{
|
||||
h->timer = 0;
|
||||
|
||||
if (TryPlacePickup(h))
|
||||
{
|
||||
h->pickupsSpawned++;
|
||||
h->numPickups++;
|
||||
}
|
||||
}
|
||||
}
|
||||
static bool TryPlacePickup(HealthPickups *h)
|
||||
{
|
||||
Vec2i size = Vec2iNew(HEALTH_W, HEALTH_H);
|
||||
// Attempt to place one in unexplored area
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
const Vec2i v = MapGenerateFreePosition(h->map, size);
|
||||
if (!Vec2iIsZero(v) && !MapGetTile(h->map, Vec2iToTile(v))->isVisited)
|
||||
{
|
||||
MapPlaceHealth(v);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Attempt to place one in out-of-sight area
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
const Vec2i v = MapGenerateFreePosition(h->map, size);
|
||||
const Vec2i fullpos = Vec2iReal2Full(v);
|
||||
const TActor *closestPlayer = AIGetClosestPlayer(fullpos);
|
||||
if (!Vec2iIsZero(v) &&
|
||||
(!closestPlayer || CHEBYSHEV_DISTANCE(
|
||||
fullpos.x, fullpos.y,
|
||||
closestPlayer->Pos.x, closestPlayer->Pos.y) >= 256 * 150))
|
||||
{
|
||||
MapPlaceHealth(v);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Attempt to place one anyway
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
const Vec2i v = MapGenerateFreePosition(h->map, size);
|
||||
if (!Vec2iIsZero(v))
|
||||
{
|
||||
MapPlaceHealth(v);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void HealthPickupsRemoveOne(HealthPickups *h)
|
||||
{
|
||||
CASSERT(h->numPickups > 0, "unexpectedly removing health pickup");
|
||||
h->numPickups--;
|
||||
}
|
114
src/cdogs/hud.c
@@ -53,6 +53,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#include "actors.h"
|
||||
#include "ammo.h"
|
||||
#include "automap.h"
|
||||
#include "draw.h"
|
||||
#include "drawtools.h"
|
||||
@@ -174,10 +175,9 @@ void HUDDisplayMessage(HUD *hud, const char *msg, int ticks)
|
||||
hud->messageTicks = ticks;
|
||||
}
|
||||
|
||||
void HUDAddHealthUpdate(HUD *hud, int playerIndex, int health)
|
||||
static int FindLocalPlayerIndex(const int playerIndex)
|
||||
{
|
||||
HUDNumUpdate s;
|
||||
s.Index = 0;
|
||||
int idx = 0;
|
||||
// Find the local index of this player
|
||||
for (int i = 0; i <= playerIndex; i++)
|
||||
{
|
||||
@@ -187,14 +187,26 @@ void HUDAddHealthUpdate(HUD *hud, int playerIndex, int health)
|
||||
if (!p->IsLocal)
|
||||
{
|
||||
// This update was for a non-local player; abort
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (p->IsLocal)
|
||||
{
|
||||
s.Index++;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
void HUDAddHealthUpdate(HUD *hud, int playerIndex, int health)
|
||||
{
|
||||
HUDNumUpdate s;
|
||||
s.Index = FindLocalPlayerIndex(playerIndex);
|
||||
if (s.Index < 0)
|
||||
{
|
||||
// This update was for a non-local player; abort
|
||||
return;
|
||||
}
|
||||
s.Amount = health;
|
||||
s.Timer = NUM_UPDATE_TIMER_MS;
|
||||
s.TimerMax = NUM_UPDATE_TIMER_MS;
|
||||
@@ -204,23 +216,11 @@ void HUDAddHealthUpdate(HUD *hud, int playerIndex, int health)
|
||||
void HUDAddScoreUpdate(HUD *hud, int playerIndex, int score)
|
||||
{
|
||||
HUDNumUpdate s;
|
||||
s.Index = 0;
|
||||
// Find the local index of this player
|
||||
for (int i = 0; i <= playerIndex; i++)
|
||||
s.Index = FindLocalPlayerIndex(playerIndex);
|
||||
if (s.Index < 0)
|
||||
{
|
||||
const PlayerData *p = CArrayGet(&gPlayerDatas, i);
|
||||
if (i == playerIndex)
|
||||
{
|
||||
if (!p->IsLocal)
|
||||
{
|
||||
// This update was for a non-local player; abort
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (p->IsLocal)
|
||||
{
|
||||
s.Index++;
|
||||
}
|
||||
// This update was for a non-local player; abort
|
||||
return;
|
||||
}
|
||||
s.Amount = score;
|
||||
s.Timer = NUM_UPDATE_TIMER_MS;
|
||||
@@ -238,6 +238,7 @@ void HUDAddObjectiveUpdate(HUD *hud, int objectiveIndex, int update)
|
||||
CArrayPushBack(&hud->objectiveUpdates, &u);
|
||||
}
|
||||
|
||||
static void NumUpdate(CArray *updates, const int ms);
|
||||
void HUDUpdate(HUD *hud, int ms)
|
||||
{
|
||||
if (hud->messageTicks >= 0)
|
||||
@@ -250,33 +251,19 @@ void HUDUpdate(HUD *hud, int ms)
|
||||
}
|
||||
FPSCounterUpdate(&hud->fpsCounter, ms);
|
||||
WallClockUpdate(&hud->clock, ms);
|
||||
for (int i = 0; i < (int)hud->healthUpdates.size; i++)
|
||||
NumUpdate(&hud->healthUpdates, ms);
|
||||
NumUpdate(&hud->scoreUpdates, ms);
|
||||
NumUpdate(&hud->objectiveUpdates, ms);
|
||||
}
|
||||
static void NumUpdate(CArray *updates, const int ms)
|
||||
{
|
||||
for (int i = 0; i < (int)updates->size; i++)
|
||||
{
|
||||
HUDNumUpdate *health = CArrayGet(&hud->healthUpdates, i);
|
||||
health->Timer -= ms;
|
||||
if (health->Timer <= 0)
|
||||
{
|
||||
CArrayDelete(&hud->healthUpdates, i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < (int)hud->scoreUpdates.size; i++)
|
||||
{
|
||||
HUDNumUpdate *score = CArrayGet(&hud->scoreUpdates, i);
|
||||
score->Timer -= ms;
|
||||
if (score->Timer <= 0)
|
||||
{
|
||||
CArrayDelete(&hud->scoreUpdates, i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < (int)hud->objectiveUpdates.size; i++)
|
||||
{
|
||||
HUDNumUpdate *update = CArrayGet(&hud->objectiveUpdates, i);
|
||||
HUDNumUpdate *update = CArrayGet(updates, i);
|
||||
update->Timer -= ms;
|
||||
if (update->Timer <= 0)
|
||||
{
|
||||
CArrayDelete(&hud->objectiveUpdates, i);
|
||||
CArrayDelete(updates, i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
@@ -314,9 +301,10 @@ static void DrawGauge(
|
||||
|
||||
#define GAUGE_WIDTH 50
|
||||
static void DrawWeaponStatus(
|
||||
GraphicsDevice *device, const Weapon *weapon, Vec2i pos,
|
||||
GraphicsDevice *device, const TActor *actor, Vec2i pos,
|
||||
const FontAlign hAlign, const FontAlign vAlign)
|
||||
{
|
||||
const Weapon *weapon = ActorGetGun(actor);
|
||||
// don't draw gauge if not reloading
|
||||
if (weapon->lock > 0)
|
||||
{
|
||||
@@ -343,7 +331,20 @@ static void DrawWeaponStatus(
|
||||
opts.VAlign = vAlign;
|
||||
opts.Area = gGraphicsDevice.cachedConfig.Res;
|
||||
opts.Pad = pos;
|
||||
FontStrOpt(weapon->Gun->name, Vec2iZero(), opts);
|
||||
char buf[128];
|
||||
if (gConfig.Game.Ammo)
|
||||
{
|
||||
// Include ammo counter
|
||||
sprintf(buf, "%s %d/%d",
|
||||
weapon->Gun->name,
|
||||
*(int *)CArrayGet(&actor->ammo, weapon->Gun->AmmoId),
|
||||
AmmoGetById(&gAmmo, weapon->Gun->AmmoId)->Max);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(buf, weapon->Gun->name);
|
||||
}
|
||||
FontStrOpt(buf, Vec2iZero(), opts);
|
||||
}
|
||||
|
||||
static void DrawHealth(
|
||||
@@ -582,7 +583,15 @@ static void DrawPlayerStatus(
|
||||
char s[50];
|
||||
if (IsScoreNeeded(gCampaign.Entry.Mode))
|
||||
{
|
||||
sprintf(s, "Score: %d", data->score);
|
||||
if (gConfig.Game.Ammo)
|
||||
{
|
||||
// Display money instead of ammo
|
||||
sprintf(s, "Cash: $%d", data->score);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(s, "Score: %d", data->score);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -591,8 +600,7 @@ static void DrawPlayerStatus(
|
||||
if (p)
|
||||
{
|
||||
// Weapon
|
||||
DrawWeaponStatus(
|
||||
device, ActorGetGun(p), pos, opts.HAlign, opts.VAlign);
|
||||
DrawWeaponStatus(device, p, pos, opts.HAlign, opts.VAlign);
|
||||
pos.y += rowHeight;
|
||||
|
||||
// Player name
|
||||
@@ -947,12 +955,12 @@ void HUDDraw(HUD *hud, int isPaused)
|
||||
}
|
||||
|
||||
static void DrawNumUpdate(
|
||||
HUDNumUpdate *update,
|
||||
const HUDNumUpdate *update,
|
||||
const char *formatText, int currentValue, Vec2i pos, int flags);
|
||||
static void DrawHealthUpdate(HUDNumUpdate *health, int flags)
|
||||
{
|
||||
const int rowHeight = 1 + FontH();
|
||||
int y = 5 + 1 + FontH() + rowHeight * 2;
|
||||
int y = 5 + rowHeight * 3;
|
||||
const PlayerData *p = CArrayGet(&gPlayerDatas, health->Index);
|
||||
if (IsPlayerAlive(p))
|
||||
{
|
||||
@@ -967,7 +975,7 @@ static void DrawScoreUpdate(HUDNumUpdate *score, int flags)
|
||||
return;
|
||||
}
|
||||
const int rowHeight = 1 + FontH();
|
||||
int y = 5 + 1 + FontH() + rowHeight;
|
||||
int y = 5 + rowHeight * 2;
|
||||
const PlayerData *p = CArrayGet(&gPlayerDatas, score->Index);
|
||||
DrawNumUpdate(score, "Score: %d", p->score, Vec2iNew(5, y), flags);
|
||||
}
|
||||
@@ -980,7 +988,7 @@ static void DrawScoreUpdate(HUDNumUpdate *score, int flags)
|
||||
#define NUM_UPDATE_FALL_DOWN_DURATION_MS 100
|
||||
#define NUM_UPDATE_POP_UP_HEIGHT 5
|
||||
static void DrawNumUpdate(
|
||||
HUDNumUpdate *update,
|
||||
const HUDNumUpdate *update,
|
||||
const char *formatText, int currentValue, Vec2i pos, int flags)
|
||||
{
|
||||
CASSERT(update->Amount != 0, "num update with zero amount");
|
||||
|
@@ -52,14 +52,15 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ammo.h"
|
||||
#include "collision.h"
|
||||
#include "config.h"
|
||||
#include "health_pickup.h"
|
||||
#include "map_build.h"
|
||||
#include "map_classic.h"
|
||||
#include "map_static.h"
|
||||
#include "pic_manager.h"
|
||||
#include "pickup.h"
|
||||
#include "powerup.h"
|
||||
#include "objs.h"
|
||||
#include "triggers.h"
|
||||
#include "sounds.h"
|
||||
@@ -532,8 +533,8 @@ void MapPlaceCollectible(
|
||||
const Vec2i fullPos = Vec2iReal2Full(realPos);
|
||||
const int id = PickupAdd(
|
||||
fullPos,
|
||||
NULL,
|
||||
cGeneralPics[o->pickupItem].picIndex,
|
||||
PicManagerGet(
|
||||
&gPicManager, NULL, cGeneralPics[o->pickupItem].picIndex),
|
||||
PICKUP_JEWEL);
|
||||
Pickup *p = CArrayGet(&gPickups, id);
|
||||
p->u.Score = PICKUP_SCORE;
|
||||
@@ -569,7 +570,10 @@ static int MapTryPlaceCollectible(
|
||||
|
||||
void MapPlaceHealth(Vec2i pos)
|
||||
{
|
||||
const int id = PickupAdd(Vec2iReal2Full(pos), "health", 0, PICKUP_HEALTH);
|
||||
const int id = PickupAdd(
|
||||
Vec2iReal2Full(pos),
|
||||
PicManagerGetPic(&gPicManager, "health"),
|
||||
PICKUP_HEALTH);
|
||||
Pickup *p = CArrayGet(&gPickups, id);
|
||||
p->u.Health = HEALTH_PICKUP_HEAL_AMOUNT;
|
||||
}
|
||||
@@ -626,13 +630,22 @@ void MapPlaceKey(
|
||||
const Vec2i fullPos = Vec2iReal2Full(Vec2iCenterOfTile(pos));
|
||||
const int id = PickupAdd(
|
||||
fullPos,
|
||||
NULL,
|
||||
cGeneralPics[mo->keyPics[keyIndex]].picIndex,
|
||||
PicManagerGet(
|
||||
&gPicManager, NULL, cGeneralPics[mo->keyPics[keyIndex]].picIndex),
|
||||
PICKUP_KEYCARD);
|
||||
Pickup *p = CArrayGet(&gPickups, id);
|
||||
p->u.Keys = 1 << keyIndex;
|
||||
}
|
||||
|
||||
void MapPlaceAmmo(AddAmmoPickup a)
|
||||
{
|
||||
const Ammo *ammo = AmmoGetById(&gAmmo, a.Id);
|
||||
const int id = PickupAdd(Vec2iReal2Full(a.Pos), ammo->Pic, PICKUP_AMMO);
|
||||
Pickup *p = CArrayGet(&gPickups, id);
|
||||
p->u.Ammo.Id = a.Id;
|
||||
p->u.Ammo.Amount = ammo->Amount;
|
||||
}
|
||||
|
||||
static void MapPlaceCard(Map *map, int keyIndex, int map_access)
|
||||
{
|
||||
for (;;)
|
||||
|
@@ -156,5 +156,11 @@ void MapPlaceHealth(Vec2i pos);
|
||||
void MapPlaceKey(
|
||||
Map *map, const struct MissionOptions *mo, const Vec2i pos,
|
||||
const int keyIndex);
|
||||
typedef struct
|
||||
{
|
||||
Vec2i Pos;
|
||||
int Id;
|
||||
} AddAmmoPickup;
|
||||
void MapPlaceAmmo(AddAmmoPickup a);
|
||||
|
||||
#endif
|
||||
|
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <SDL_image.h>
|
||||
|
||||
#include "ammo.h"
|
||||
#include "json_utils.h"
|
||||
#include "map_new.h"
|
||||
#include "physfs/physfs.h"
|
||||
@@ -109,6 +110,13 @@ int MapNewLoadArchive(const char *filename, CampaignSetting *c)
|
||||
&gBulletClasses, &gBulletClasses.CustomClasses, root);
|
||||
}
|
||||
|
||||
root = ReadPhysFSJSON(filename, "ammo.json");
|
||||
if (root != NULL)
|
||||
{
|
||||
AmmoLoadJSON(&gAmmo.CustomAmmo, root);
|
||||
json_free_value(&root);
|
||||
}
|
||||
|
||||
root = ReadPhysFSJSON(filename, "guns.json");
|
||||
if (root != NULL)
|
||||
{
|
||||
|
@@ -116,7 +116,8 @@ void MapStaticLoadDynamic(
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
|
||||
// Process the events that actually place the players
|
||||
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
|
||||
HandleGameEvents(
|
||||
&gGameEvents, NULL, NULL, NULL, NULL, &gEventHandlers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +186,8 @@ void MapStaticLoadDynamic(
|
||||
obj->placed++;
|
||||
|
||||
// Process the events that actually place the objectives
|
||||
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
|
||||
HandleGameEvents(
|
||||
&gGameEvents, NULL, NULL, NULL, NULL, &gEventHandlers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@
|
||||
*/
|
||||
#include "pickup.h"
|
||||
|
||||
#include "ammo.h"
|
||||
#include "game_events.h"
|
||||
#include "map.h"
|
||||
|
||||
@@ -52,9 +53,7 @@ void PickupsTerminate(void)
|
||||
CArrayTerminate(&gPickups);
|
||||
}
|
||||
static const Pic *GetPickupPic(const int id, Vec2i *offset);
|
||||
int PickupAdd(
|
||||
const Vec2i pos, const char *picName, const int oldIdx,
|
||||
const PickupType type)
|
||||
int PickupAdd(const Vec2i pos, const Pic *pic, const PickupType type)
|
||||
{
|
||||
// Find an empty slot in pickup list
|
||||
Pickup *p = NULL;
|
||||
@@ -77,7 +76,7 @@ int PickupAdd(
|
||||
p = CArrayGet(&gPickups, i);
|
||||
}
|
||||
memset(p, 0, sizeof *p);
|
||||
p->Pic = PicManagerGet(&gPicManager, picName, oldIdx);
|
||||
p->Pic = pic;
|
||||
p->Type = type;
|
||||
p->tileItem.x = p->tileItem.y = -1;
|
||||
p->tileItem.flags = 0;
|
||||
@@ -130,6 +129,26 @@ void PickupPickup(const TActor *a, const Pickup *p)
|
||||
}
|
||||
break;
|
||||
|
||||
case PICKUP_AMMO:
|
||||
{
|
||||
// Don't pickup if ammo full
|
||||
canPickup = false;
|
||||
const int ammoMax = AmmoGetById(&gAmmo, p->u.Ammo.Id)->Max;
|
||||
const int current = *(int *)CArrayGet(&a->ammo, p->u.Ammo.Id);
|
||||
if (current < ammoMax)
|
||||
{
|
||||
canPickup = true;
|
||||
GameEvent e = GameEventNew(GAME_EVENT_TAKE_AMMO_PICKUP);
|
||||
e.u.AddAmmo.PlayerIndex = a->playerIndex;
|
||||
e.u.AddAmmo.AddAmmo = p->u.Ammo;
|
||||
// Note: receiving end will prevent ammo from exceeding max
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
// TODO: per-ammo sound
|
||||
sound = gSoundDevice.ammoSound;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PICKUP_KEYCARD:
|
||||
gMission.flags |= p->u.Keys;
|
||||
sound = gSoundDevice.keySound;
|
||||
|
@@ -37,6 +37,7 @@ typedef enum
|
||||
PICKUP_NONE,
|
||||
PICKUP_JEWEL,
|
||||
PICKUP_HEALTH,
|
||||
PICKUP_AMMO,
|
||||
PICKUP_KEYCARD
|
||||
} PickupType;
|
||||
|
||||
@@ -50,6 +51,7 @@ typedef struct
|
||||
{
|
||||
int Score;
|
||||
int Health;
|
||||
AddAmmo Ammo;
|
||||
int Keys; // Refer to flags in mission.h
|
||||
} u;
|
||||
TTileItem tileItem;
|
||||
@@ -64,9 +66,7 @@ extern CArray gPickups; // of Pickup
|
||||
|
||||
void PickupsInit(void);
|
||||
void PickupsTerminate(void);
|
||||
int PickupAdd(
|
||||
const Vec2i pos, const char *picName, const int oldIdx,
|
||||
const PickupType type);
|
||||
int PickupAdd(const Vec2i pos, const Pic *pic, const PickupType type);
|
||||
void PickupDestroy(const int id);
|
||||
|
||||
void PickupPickup(const TActor *a, const Pickup *p);
|
||||
|
233
src/cdogs/powerup.c
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
Copyright (c) 2014, Cong Xu
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include "powerup.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "ai_utils.h"
|
||||
#include "ammo.h"
|
||||
#include "gamedata.h"
|
||||
#include "game_events.h"
|
||||
|
||||
|
||||
#define TIME_DECAY_EXPONENT 1.04
|
||||
#define HEALTH_W 6
|
||||
#define HEALTH_H 6
|
||||
#define MAX_TILES_PER_PICKUP 625
|
||||
|
||||
void PowerupSpawnerInit(PowerupSpawner *p, Map *map)
|
||||
{
|
||||
memset(p, 0, sizeof *p);
|
||||
p->map = map;
|
||||
p->timer = 0;
|
||||
p->numPickups = 0;
|
||||
p->pickupsSpawned = 0;
|
||||
p->Enabled = true;
|
||||
}
|
||||
void PowerupSpawnerTerminate(PowerupSpawner *p)
|
||||
{
|
||||
CFREE(p->Data);
|
||||
}
|
||||
|
||||
static bool TryPlacePickup(PowerupSpawner *p);
|
||||
void PowerupSpawnerUpdate(PowerupSpawner *p, const int ticks)
|
||||
{
|
||||
// Don't spawn pickups if not allowed
|
||||
if (!p->Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double scalar = p->RateScaleFunc(p->Data);
|
||||
// Scale down over time
|
||||
scalar *= pow(TIME_DECAY_EXPONENT, p->pickupsSpawned);
|
||||
|
||||
p->timeUntilNextSpawn = (int)floor(scalar * p->SpawnTime);
|
||||
|
||||
// Update time
|
||||
p->timer += ticks;
|
||||
|
||||
// Attempt to add pickup if time reached, and we haven't placed too many
|
||||
if (p->timer >= p->timeUntilNextSpawn &&
|
||||
p->map->NumExplorableTiles / MAX_TILES_PER_PICKUP + 1 > p->numPickups)
|
||||
{
|
||||
p->timer -= p->timeUntilNextSpawn;
|
||||
|
||||
if (TryPlacePickup(p))
|
||||
{
|
||||
p->pickupsSpawned++;
|
||||
p->numPickups++;
|
||||
}
|
||||
}
|
||||
}
|
||||
static bool TryPlacePickup(PowerupSpawner *p)
|
||||
{
|
||||
const Vec2i size = Vec2iNew(HEALTH_W, HEALTH_H);
|
||||
// Attempt to place one in out-of-sight area
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
const Vec2i v = MapGenerateFreePosition(p->map, size);
|
||||
const Vec2i fullpos = Vec2iReal2Full(v);
|
||||
const TActor *closestPlayer = AIGetClosestPlayer(fullpos);
|
||||
if (!Vec2iIsZero(v) &&
|
||||
(!closestPlayer || CHEBYSHEV_DISTANCE(
|
||||
fullpos.x, fullpos.y,
|
||||
closestPlayer->Pos.x, closestPlayer->Pos.y) >= 256 * 150))
|
||||
{
|
||||
p->PlaceFunc(v, p->Data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Attempt to place one anyway
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
const Vec2i v = MapGenerateFreePosition(p->map, size);
|
||||
if (!Vec2iIsZero(v))
|
||||
{
|
||||
p->PlaceFunc(v, p->Data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PowerupSpawnerRemoveOne(PowerupSpawner *p)
|
||||
{
|
||||
CASSERT(p->numPickups > 0, "unexpectedly removing powerup");
|
||||
p->numPickups--;
|
||||
}
|
||||
|
||||
|
||||
#define HEALTH_SPAWN_TIME (20 * FPS_FRAMELIMIT)
|
||||
|
||||
static double HealthScale(void *data);
|
||||
static void HealthPlace(const Vec2i pos, void *data);
|
||||
void HealthSpawnerInit(PowerupSpawner *p, Map *map)
|
||||
{
|
||||
PowerupSpawnerInit(p, map);
|
||||
p->Enabled =
|
||||
AreHealthPickupsAllowed(gCampaign.Entry.Mode) &&
|
||||
gConfig.Game.HealthPickups;
|
||||
p->SpawnTime = HEALTH_SPAWN_TIME;
|
||||
p->RateScaleFunc = HealthScale;
|
||||
p->PlaceFunc = HealthPlace;
|
||||
|
||||
// Update once
|
||||
PowerupSpawnerUpdate(p, 0);
|
||||
}
|
||||
static double HealthScale(void *data)
|
||||
{
|
||||
UNUSED(data);
|
||||
// Update time until next spawn based on:
|
||||
// Damage taken (find player with lowest health)
|
||||
int minHealth = ModeMaxHealth(gCampaign.Entry.Mode);
|
||||
int maxHealth = minHealth;
|
||||
for (int i = 0; i < (int)gPlayerDatas.size; i++)
|
||||
{
|
||||
const PlayerData *p = CArrayGet(&gPlayerDatas, i);
|
||||
if (!IsPlayerAlive(p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const TActor *player = CArrayGet(&gActors, p->Id);
|
||||
minHealth = MIN(minHealth, player->health);
|
||||
}
|
||||
// Double spawn rate if near 0 health
|
||||
return (minHealth + maxHealth) / (maxHealth * 2.0);
|
||||
}
|
||||
static void HealthPlace(const Vec2i pos, void *data)
|
||||
{
|
||||
UNUSED(data);
|
||||
GameEvent e = GameEventNew(GAME_EVENT_ADD_HEALTH_PICKUP);
|
||||
e.u.AddPos = pos;
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
}
|
||||
|
||||
|
||||
#define AMMO_SPAWN_TIME (20 * FPS_FRAMELIMIT)
|
||||
|
||||
static double AmmoScale(void *data);
|
||||
static void AmmoPlace(const Vec2i pos, void *data);
|
||||
void AmmoSpawnerInit(PowerupSpawner *p, Map *map, const int ammoId)
|
||||
{
|
||||
PowerupSpawnerInit(p, map);
|
||||
// TODO: disable ammo spawners if map is deathmatch and contains ammo
|
||||
p->Enabled = gConfig.Game.Ammo;
|
||||
p->SpawnTime = AMMO_SPAWN_TIME;
|
||||
p->RateScaleFunc = AmmoScale;
|
||||
p->PlaceFunc = AmmoPlace;
|
||||
CMALLOC(p->Data, sizeof ammoId);
|
||||
*(int *)p->Data = ammoId;
|
||||
|
||||
// Update once
|
||||
PowerupSpawnerUpdate(p, 0);
|
||||
}
|
||||
static double AmmoScale(void *data)
|
||||
{
|
||||
const int ammoId = *(int *)data;
|
||||
// Update time until next spawn based on:
|
||||
// Ammo left (find player with lowest ammo)
|
||||
// But make sure at least one player has a gun that uses this ammo
|
||||
int minVal = AmmoGetById(&gAmmo, ammoId)->Max;
|
||||
int maxVal = minVal;
|
||||
int numPlayersWithAmmo = 0;
|
||||
for (int i = 0; i < (int)gPlayerDatas.size; i++)
|
||||
{
|
||||
const PlayerData *p = CArrayGet(&gPlayerDatas, i);
|
||||
if (!IsPlayerAlive(p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const TActor *player = CArrayGet(&gActors, p->Id);
|
||||
for (int j = 0; j < (int)player->guns.size; j++)
|
||||
{
|
||||
const Weapon *w = CArrayGet(&player->guns, j);
|
||||
if (w->Gun->AmmoId == ammoId)
|
||||
{
|
||||
numPlayersWithAmmo++;
|
||||
}
|
||||
}
|
||||
minVal = MIN(minVal, *(int *)CArrayGet(&player->ammo, ammoId));
|
||||
}
|
||||
if (numPlayersWithAmmo > 0)
|
||||
{
|
||||
// Double spawn rate if near 0 ammo
|
||||
return (minVal + maxVal) / (maxVal * 2.0) / numPlayersWithAmmo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No players have guns that use this ammo; spawn very slowly
|
||||
return 5.0;
|
||||
}
|
||||
}
|
||||
static void AmmoPlace(const Vec2i pos, void *data)
|
||||
{
|
||||
const int ammoId = *(int *)data;
|
||||
GameEvent e = GameEventNew(GAME_EVENT_ADD_AMMO_PICKUP);
|
||||
e.u.AddAmmoPickup.Pos = pos;
|
||||
e.u.AddAmmoPickup.Id = ammoId;
|
||||
GameEventsEnqueue(&gGameEvents, e);
|
||||
}
|
@@ -23,8 +23,7 @@
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#ifndef __HEALTH_PICKUP
|
||||
#define __HEALTH_PICKUP
|
||||
#pragma once
|
||||
|
||||
#include "actors.h"
|
||||
|
||||
@@ -37,10 +36,19 @@ typedef struct
|
||||
int timer;
|
||||
int numPickups;
|
||||
int pickupsSpawned;
|
||||
} HealthPickups;
|
||||
|
||||
void HealthPickupsInit(HealthPickups *h, Map *map);
|
||||
void HealthPickupsUpdate(HealthPickups *h, int ticks);
|
||||
void HealthPickupsRemoveOne(HealthPickups *h);
|
||||
bool Enabled;
|
||||
int SpawnTime;
|
||||
double (*RateScaleFunc)(void *);
|
||||
void (*PlaceFunc)(const Vec2i, void *);
|
||||
void *Data;
|
||||
} PowerupSpawner;
|
||||
|
||||
#endif
|
||||
void PowerupSpawnerInit(PowerupSpawner *p, Map *map);
|
||||
void PowerupSpawnerTerminate(PowerupSpawner *p);
|
||||
void PowerupSpawnerUpdate(PowerupSpawner *p, const int ticks);
|
||||
void PowerupSpawnerRemoveOne(PowerupSpawner *p);
|
||||
|
||||
void HealthSpawnerInit(PowerupSpawner *p, Map *map);
|
||||
|
||||
void AmmoSpawnerInit(PowerupSpawner *p, Map *map, const int ammoId);
|
@@ -153,6 +153,8 @@ void SoundInitialize(
|
||||
device->switchSound = StrSound("switch");
|
||||
device->pickupSound = StrSound("pickup");
|
||||
device->healthSound = StrSound("health");
|
||||
device->ammoSound = StrSound("ammo");
|
||||
device->clickSound = StrSound("click");
|
||||
device->keySound = StrSound("key");
|
||||
device->wreckSound = StrSound("bang");
|
||||
CArrayInit(&device->screamSounds, sizeof(Mix_Chunk *));
|
||||
|
@@ -96,6 +96,8 @@ typedef struct
|
||||
Mix_Chunk *switchSound;
|
||||
Mix_Chunk *pickupSound;
|
||||
Mix_Chunk *healthSound;
|
||||
Mix_Chunk *ammoSound;
|
||||
Mix_Chunk *clickSound;
|
||||
Mix_Chunk *keySound;
|
||||
Mix_Chunk *wreckSound;
|
||||
CArray screamSounds; // of Mix_Chunk *
|
||||
|
@@ -53,6 +53,7 @@
|
||||
|
||||
#include <json/json.h>
|
||||
|
||||
#include "ammo.h"
|
||||
#include "config.h"
|
||||
#include "game_events.h"
|
||||
#include "json_utils.h"
|
||||
@@ -212,6 +213,13 @@ static void LoadGunDescription(
|
||||
CFREE(tmp);
|
||||
}
|
||||
|
||||
if (json_find_first_label(node, "Ammo"))
|
||||
{
|
||||
tmp = GetString(node, "Ammo");
|
||||
g->AmmoId = StrAmmoId(tmp);
|
||||
CFREE(tmp);
|
||||
}
|
||||
|
||||
LoadInt(&g->Cost, node, "Cost");
|
||||
|
||||
LoadInt(&g->Lock, node, "Lock");
|
||||
@@ -351,6 +359,11 @@ void WeaponUpdate(
|
||||
{
|
||||
w->soundLock = 0;
|
||||
}
|
||||
w->clickLock -= ticks;
|
||||
if (w->clickLock < 0)
|
||||
{
|
||||
w->clickLock = 0;
|
||||
}
|
||||
if (w->stateCounter >= 0)
|
||||
{
|
||||
w->stateCounter = MAX(0, w->stateCounter - ticks);
|
||||
@@ -372,9 +385,9 @@ void WeaponUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
int WeaponCanFire(Weapon *w)
|
||||
bool WeaponIsLocked(const Weapon *w)
|
||||
{
|
||||
return w->lock <= 0;
|
||||
return w->lock > 0;
|
||||
}
|
||||
|
||||
void WeaponFire(
|
||||
@@ -390,8 +403,6 @@ void WeaponFire(
|
||||
return;
|
||||
}
|
||||
|
||||
CASSERT(WeaponCanFire(w), "Can't fire weapon");
|
||||
|
||||
const double radians = dir2radians[d];
|
||||
const Vec2i muzzleOffset = GunGetMuzzleOffset(w->Gun, d);
|
||||
const Vec2i muzzlePosition = Vec2iAdd(pos, muzzleOffset);
|
||||
|
@@ -102,6 +102,7 @@ typedef struct
|
||||
char *name;
|
||||
char *Description;
|
||||
const BulletClass *Bullet;
|
||||
int AmmoId;
|
||||
int Cost; // Cost in score to fire weapon
|
||||
int Lock;
|
||||
int ReloadLead;
|
||||
@@ -137,6 +138,7 @@ typedef struct
|
||||
gunstate_e state;
|
||||
int lock;
|
||||
int soundLock;
|
||||
int clickLock;
|
||||
int stateCounter;
|
||||
} Weapon;
|
||||
|
||||
@@ -153,7 +155,7 @@ const GunDescription *StrGunDescription(const char *s);
|
||||
Vec2i GunGetMuzzleOffset(const GunDescription *desc, const direction_e dir);
|
||||
void WeaponUpdate(
|
||||
Weapon *w, const int ticks, const Vec2i fullPos, const direction_e d);
|
||||
int WeaponCanFire(Weapon *w);
|
||||
bool WeaponIsLocked(const Weapon *w);
|
||||
void WeaponFire(
|
||||
Weapon *w, const direction_e d, const Vec2i pos,
|
||||
const int flags, const int player, const int uid);
|
||||
|
@@ -55,6 +55,7 @@
|
||||
#include <SDL.h>
|
||||
|
||||
#include <cdogs/actors.h>
|
||||
#include <cdogs/ammo.h>
|
||||
#include <cdogs/automap.h>
|
||||
#include <cdogs/config.h>
|
||||
#include <cdogs/draw.h>
|
||||
@@ -1220,6 +1221,8 @@ int main(int argc, char *argv[])
|
||||
|
||||
GetDataFilePath(buf, "data/particles.json");
|
||||
ParticleClassesInit(&gParticleClasses, buf);
|
||||
GetDataFilePath(buf, "data/ammo.json");
|
||||
AmmoInitialize(&gAmmo, buf);
|
||||
GetDataFilePath(buf, "data/bullets.json");
|
||||
GetDataFilePath(buf2, "data/guns.json");
|
||||
BulletAndWeaponInitialize(&gBulletClasses, &gGunDescriptions, buf, buf2);
|
||||
@@ -1275,6 +1278,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
MapTerminate(&gMap);
|
||||
ParticleClassesTerminate(&gParticleClasses);
|
||||
AmmoTerminate(&gAmmo);
|
||||
WeaponTerminate(&gGunDescriptions);
|
||||
BulletTerminate(&gBulletClasses);
|
||||
CampaignTerminate(&gCampaign);
|
||||
|
31
src/game.c
@@ -59,13 +59,13 @@
|
||||
#include <cdogs/actors.h>
|
||||
#include <cdogs/ai.h>
|
||||
#include <cdogs/ai_coop.h>
|
||||
#include <cdogs/ammo.h>
|
||||
#include <cdogs/automap.h>
|
||||
#include <cdogs/config.h>
|
||||
#include <cdogs/draw.h>
|
||||
#include <cdogs/events.h>
|
||||
#include <cdogs/game_events.h>
|
||||
#include <cdogs/handle_game_events.h>
|
||||
#include <cdogs/health_pickup.h>
|
||||
#include <cdogs/hud.h>
|
||||
#include <cdogs/joystick.h>
|
||||
#include <cdogs/mission.h>
|
||||
@@ -75,6 +75,7 @@
|
||||
#include <cdogs/particle.h>
|
||||
#include <cdogs/pic_manager.h>
|
||||
#include <cdogs/pics.h>
|
||||
#include <cdogs/powerup.h>
|
||||
#include <cdogs/screen_shake.h>
|
||||
#include <cdogs/triggers.h>
|
||||
|
||||
@@ -379,7 +380,8 @@ typedef struct
|
||||
bool isPaused;
|
||||
bool isMap;
|
||||
int cmds[MAX_LOCAL_PLAYERS];
|
||||
HealthPickups hp;
|
||||
PowerupSpawner healthSpawner;
|
||||
CArray ammoSpawners; // of PowerupSpawner
|
||||
ScreenShake shake;
|
||||
Vec2i lastPosition;
|
||||
GameLoopData loop;
|
||||
@@ -397,7 +399,14 @@ bool RunGame(struct MissionOptions *m, Map *map)
|
||||
|
||||
DrawBufferInit(&data.buffer, Vec2iNew(X_TILES, Y_TILES), &gGraphicsDevice);
|
||||
HUDInit(&data.hud, &gConfig.Interface, &gGraphicsDevice, m);
|
||||
HealthPickupsInit(&data.hp, map);
|
||||
HealthSpawnerInit(&data.healthSpawner, map);
|
||||
CArrayInit(&data.ammoSpawners, sizeof(PowerupSpawner));
|
||||
for (int i = 0; i < AmmoGetNumClasses(&gAmmo); i++)
|
||||
{
|
||||
PowerupSpawner ps;
|
||||
AmmoSpawnerInit(&ps, map, i);
|
||||
CArrayPushBack(&data.ammoSpawners, &ps);
|
||||
}
|
||||
data.shake = ScreenShakeZero();
|
||||
|
||||
if (MusicGetStatus(&gSoundDevice) != MUSIC_OK)
|
||||
@@ -428,6 +437,12 @@ bool RunGame(struct MissionOptions *m, Map *map)
|
||||
data.loop.InputEverySecondFrame = true;
|
||||
GameLoop(&data.loop);
|
||||
|
||||
PowerupSpawnerTerminate(&data.healthSpawner);
|
||||
for (int i = 0; i < (int)data.ammoSpawners.size; i++)
|
||||
{
|
||||
PowerupSpawnerTerminate(CArrayGet(&data.ammoSpawners, i));
|
||||
}
|
||||
CArrayTerminate(&data.ammoSpawners);
|
||||
HUDTerminate(&data.hud);
|
||||
DrawBufferTerminate(&data.buffer);
|
||||
|
||||
@@ -602,7 +617,11 @@ static GameLoopResult RunGameUpdate(void *data)
|
||||
|
||||
UpdateWatches(&rData->map->triggers);
|
||||
|
||||
HealthPickupsUpdate(&rData->hp, ticksPerFrame);
|
||||
PowerupSpawnerUpdate(&rData->healthSpawner, ticksPerFrame);
|
||||
for (int i = 0; i < (int)rData->ammoSpawners.size; i++)
|
||||
{
|
||||
PowerupSpawnerUpdate(CArrayGet(&rData->ammoSpawners, i), ticksPerFrame);
|
||||
}
|
||||
|
||||
if (!gCampaign.IsClient)
|
||||
{
|
||||
@@ -610,7 +629,9 @@ static GameLoopResult RunGameUpdate(void *data)
|
||||
}
|
||||
|
||||
HandleGameEvents(
|
||||
&gGameEvents, &rData->hud, &rData->shake, &rData->hp, &gEventHandlers);
|
||||
&gGameEvents, &rData->hud, &rData->shake,
|
||||
&rData->healthSpawner, &rData->ammoSpawners,
|
||||
&gEventHandlers);
|
||||
|
||||
rData->m->time += ticksPerFrame;
|
||||
|
||||
|
@@ -355,6 +355,12 @@ menu_t *MenuCreateOptionsGame(const char *name, MenuSystem *ms)
|
||||
"Health pickups",
|
||||
&gConfig.Game.HealthPickups,
|
||||
MENU_OPTION_DISPLAY_STYLE_YES_NO));
|
||||
MenuAddSubmenu(
|
||||
menu,
|
||||
MenuCreateOptionToggle(
|
||||
"Ammo",
|
||||
&gConfig.Game.Ammo,
|
||||
MENU_OPTION_DISPLAY_STYLE_YES_NO));
|
||||
MenuAddSubmenu(
|
||||
menu,
|
||||
MenuCreateOptionToggle(
|
||||
|