Add lives and respawning (fixes #58)

Fix crash after quitting from player select menu
Add event delay attribute
This commit is contained in:
Cong
2014-11-13 21:57:01 +11:00
parent c46d7d2e93
commit 8587723934
34 changed files with 285 additions and 184 deletions

BIN
sounds/spawn.ogg Normal file

Binary file not shown.

8
sounds/spawn.txt Normal file
View File

@@ -0,0 +1,8 @@
Derived from
Ambient power chord (E2) by Khoon
http://freesound.org/people/Khoon/sounds/168741/
http://creativecommons.org/publicdomain/zero/1.0/
and
teleport by fins
http://freesound.org/people/fins/sounds/172207/
http://creativecommons.org/publicdomain/zero/1.0/

View File

@@ -121,7 +121,7 @@ static void AddAndPlacePlayers(void)
continue;
}
firstPos = PlacePlayer(&gMap, p, firstPos);
firstPos = PlacePlayer(&gMap, p, firstPos, true);
}
}
@@ -391,6 +391,10 @@ void MainLoop(credits_displayer_t *creditsDisplayer, custom_campaigns_t *campaig
gCampaign.IsLoaded ||
MainMenu(&gGraphicsDevice, creditsDisplayer, campaigns))
{
// Reset player datas
PlayerDataTerminate(&gPlayerDatas);
PlayerDataInit(&gPlayerDatas);
debug(D_NORMAL, ">> Entering campaign\n");
if (IsIntroNeeded(gCampaign.Entry.Mode))
{
@@ -448,9 +452,6 @@ void MainLoop(credits_displayer_t *creditsDisplayer, custom_campaigns_t *campaig
}
gCampaign.IsLoaded = false;
gCampaign.IsClient = false; // TODO: select is client from menu
// Reset player datas
PlayerDataTerminate(&gPlayerDatas);
PlayerDataInit(&gPlayerDatas);
}
debug(D_NORMAL, ">> Leaving Main Game Loop\n");

View File

@@ -156,7 +156,8 @@ NetMsgVec2i PlacePrisoner(Map *map)
return posNet;
}
Vec2i PlacePlayer(Map *map, const PlayerData *p, const Vec2i firstPos)
Vec2i PlacePlayer(
Map *map, const PlayerData *p, const Vec2i firstPos, const bool pumpEvents)
{
NetMsgActorAdd aa = NetMsgActorAdd_init_default;
aa.Id = ActorsGetFreeIndex();
@@ -187,13 +188,15 @@ Vec2i PlacePlayer(Map *map, const PlayerData *p, const Vec2i firstPos)
aa.FullPos = PlaceActor(map);
}
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
// Process the events that actually place the players
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
if (pumpEvents)
{
// Process the events that actually place the players
HandleGameEvents(&gGameEvents, NULL, NULL, NULL, &gEventHandlers);
}
return Vec2iNew(aa.FullPos.x, aa.FullPos.y);
}

View File

@@ -36,6 +36,7 @@
NetMsgVec2i PlaceBaddie(Map *map);
NetMsgVec2i PlacePrisoner(Map *map);
Vec2i PlacePlayer(Map *map, const PlayerData *p, const Vec2i firstPos);
Vec2i PlacePlayer(
Map *map, const PlayerData *p, const Vec2i firstPos, const bool pumpEvents);
#endif

View File

@@ -53,6 +53,7 @@
#include <stdlib.h>
#include <string.h>
#include "actor_placement.h"
#include "ai_utils.h"
#include "character.h"
#include "collision.h"
@@ -338,8 +339,7 @@ static void CheckTrigger(const Vec2i tilePos)
Trigger **tp = CArrayGet(&t->triggers, i);
if (TriggerCanActivate(*tp, gMission.flags))
{
GameEvent e;
e.Type = GAME_EVENT_TRIGGER;
GameEvent e = GameEventNew(GAME_EVENT_TRIGGER);
e.u.Trigger.Id = (*tp)->id;
e.u.Trigger.TilePos = tilePos;
GameEventsEnqueue(&gGameEvents, e);
@@ -355,8 +355,7 @@ static void PickupObject(TActor * actor, TObject * object)
{
case OBJ_JEWEL:
{
GameEvent e;
e.Type = GAME_EVENT_SCORE;
GameEvent e = GameEventNew(GAME_EVENT_SCORE);
e.u.Score.PlayerIndex = actor->playerIndex;
e.u.Score.Score = PICKUP_SCORE;
GameEventsEnqueue(&gGameEvents, e);
@@ -373,8 +372,7 @@ static void PickupObject(TActor * actor, TObject * object)
if (actor->health < ActorGetCharacter(actor)->maxHealth)
{
canPickup = true;
GameEvent e;
e.Type = GAME_EVENT_TAKE_HEALTH_PICKUP;
GameEvent e = GameEventNew(GAME_EVENT_TAKE_HEALTH_PICKUP);
e.u.PickupPlayer = actor->playerIndex;
GameEventsEnqueue(&gGameEvents, e);
SoundPlayAt(
@@ -540,8 +538,7 @@ bool TryMoveActor(TActor *actor, Vec2i pos)
}
}
GameEvent e;
e.Type = GAME_EVENT_ACTOR_MOVE;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_MOVE);
e.u.ActorMove.Id = actor->tileItem.id;
e.u.ActorMove.Pos.x = pos.x;
e.u.ActorMove.Pos.y = pos.y;
@@ -660,8 +657,7 @@ void InjureActor(TActor * actor, int injury)
{
actor->stateCounter = 0;
Vec2i pos = Vec2iNew(actor->tileItem.x, actor->tileItem.y);
GameEvent sound;
sound.Type = GAME_EVENT_SOUND_AT;
GameEvent sound = GameEventNew(GAME_EVENT_SOUND_AT);
sound.u.SoundAt.Sound = SoundGetRandomScream(&gSoundDevice);
sound.u.SoundAt.Pos = pos;
GameEventsEnqueue(&gGameEvents, sound);
@@ -694,8 +690,7 @@ void Shoot(TActor *actor)
actor->uid);
if (actor->playerIndex >= 0 && gun->Gun->Cost != 0)
{
GameEvent e;
e.Type = GAME_EVENT_SCORE;
GameEvent e = GameEventNew(GAME_EVENT_SCORE);
e.u.Score.PlayerIndex = actor->playerIndex;
e.u.Score.Score = -gun->Gun->Cost;
GameEventsEnqueue(&gGameEvents, e);
@@ -848,6 +843,7 @@ void SlideActor(TActor *actor, int cmd)
}
static void ActorUpdatePosition(TActor *actor, int ticks);
static void ActorDie(TActor *actor, const int idx);
void UpdateAllActors(int ticks)
{
for (int i = 0; i < (int)gActors.size; i++)
@@ -861,47 +857,38 @@ void UpdateAllActors(int ticks)
UpdateActorState(actor, ticks);
if (actor->dead > DEATH_MAX)
{
AddObjectOld(
actor->Pos.x, actor->Pos.y,
Vec2iZero(),
&cBloodPics[rand() % BLOOD_MAX],
OBJ_NONE,
TILEITEM_IS_WRECK);
ActorDestroy(i);
ActorDie(actor, i);
continue;
}
else
// Find actors that are on the same team and colliding,
// and repel them
if (gConfig.Game.AllyCollision == ALLYCOLLISION_REPEL)
{
// Find actors that are on the same team and colliding,
// and repel them
if (gConfig.Game.AllyCollision == ALLYCOLLISION_REPEL)
Vec2i realPos = Vec2iFull2Real(actor->Pos);
TTileItem *collidingItem = GetItemOnTileInCollision(
&actor->tileItem, realPos, TILEITEM_IMPASSABLE,
COLLISIONTEAM_NONE,
gCampaign.Entry.Mode == CAMPAIGN_MODE_DOGFIGHT);
if (collidingItem && collidingItem->kind == KIND_CHARACTER)
{
Vec2i realPos = Vec2iFull2Real(actor->Pos);
TTileItem *collidingItem = GetItemOnTileInCollision(
&actor->tileItem, realPos, TILEITEM_IMPASSABLE,
COLLISIONTEAM_NONE,
gCampaign.Entry.Mode == CAMPAIGN_MODE_DOGFIGHT);
if (collidingItem && collidingItem->kind == KIND_CHARACTER)
TActor *collidingActor = CArrayGet(
&gActors, collidingItem->id);
if (CalcCollisionTeam(1, collidingActor) ==
CalcCollisionTeam(1, actor))
{
TActor *collidingActor = CArrayGet(
&gActors, collidingItem->id);
if (CalcCollisionTeam(1, collidingActor) ==
CalcCollisionTeam(1, actor))
Vec2i v = Vec2iMinus(actor->Pos, collidingActor->Pos);
if (Vec2iIsZero(v))
{
Vec2i v = Vec2iMinus(actor->Pos, collidingActor->Pos);
if (Vec2iIsZero(v))
{
v = Vec2iNew(1, 0);
}
v = Vec2iScale(Vec2iNorm(v), REPEL_STRENGTH);
GameEvent e;
e.Type = GAME_EVENT_ACTOR_IMPULSE;
e.u.ActorImpulse.Id = actor->tileItem.id;
e.u.ActorImpulse.Vel = v;
GameEventsEnqueue(&gGameEvents, e);
e.u.ActorImpulse.Id = collidingActor->tileItem.id;
e.u.ActorImpulse.Vel = Vec2iScale(v, -1);
GameEventsEnqueue(&gGameEvents, e);
v = Vec2iNew(1, 0);
}
v = Vec2iScale(Vec2iNorm(v), REPEL_STRENGTH);
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_IMPULSE);
e.u.ActorImpulse.Id = actor->tileItem.id;
e.u.ActorImpulse.Vel = v;
GameEventsEnqueue(&gGameEvents, e);
e.u.ActorImpulse.Id = collidingActor->tileItem.id;
e.u.ActorImpulse.Vel = Vec2iScale(v, -1);
GameEventsEnqueue(&gGameEvents, e);
}
}
}
@@ -941,6 +928,50 @@ static void ActorUpdatePosition(TActor *actor, int ticks)
actor->MovePos = Vec2iZero();
}
}
static void ActorDie(TActor *actor, const int idx)
{
// Check if the player has lives to revive
if (actor->playerIndex >= 0)
{
PlayerData *p = CArrayGet(&gPlayerDatas, actor->playerIndex);
p->Lives--;
CASSERT(p->Lives >= 0, "Player has died too many times");
if (p->Lives > 0)
{
// Find the first player alive; try to spawn next to that position
// if no other suitable position exists
Vec2i defaultSpawnPosition = Vec2iZero();
for (int i = 0; i < (int)gPlayerDatas.size; i++)
{
const PlayerData *pOther = CArrayGet(&gPlayerDatas, i);
if (IsPlayerAlive(pOther))
{
const TActor *a = CArrayGet(&gActors, pOther->Id);
defaultSpawnPosition = a->Pos;
break;
}
}
const Vec2i spawnPos =
PlacePlayer(&gMap, p, defaultSpawnPosition, false);
// Play a spawn sound for players
GameEvent sound = GameEventNew(GAME_EVENT_SOUND_AT);
// Need to delay it a bit because the camera takes time to update
sound.Delay = 1;
sound.u.SoundAt.Sound = StrSound("spawn");
sound.u.SoundAt.Pos = Vec2iFull2Real(spawnPos);
GameEventsEnqueue(&gGameEvents, sound);
}
}
// Add a blood pool
AddObjectOld(
actor->Pos.x, actor->Pos.y,
Vec2iZero(),
&cBloodPics[rand() % BLOOD_MAX],
OBJ_NONE,
TILEITEM_IS_WRECK);
ActorDestroy(idx);
}
void ActorsInit(void)
{

View File

@@ -500,8 +500,7 @@ void CommandBadGuys(int ticks)
CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
aa.Health = CharacterGetStartingHealth(c, true);
aa.FullPos = PlaceBaddie(&gMap);
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
gBaddieCount++;
@@ -530,8 +529,7 @@ void InitializeBadGuys(void)
CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
aa.Health = CharacterGetStartingHealth(c, true);
aa.FullPos = PlaceBaddie(&gMap);
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
@@ -560,8 +558,7 @@ void InitializeBadGuys(void)
{
aa.FullPos = PlaceBaddie(&gMap);
}
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
@@ -595,8 +592,7 @@ void CreateEnemies(void)
const Character *c =
CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
aa.Health = CharacterGetStartingHealth(c, true);
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
gBaddieCount++;

View File

@@ -199,8 +199,7 @@ bool UpdateBullet(TMobileObject *obj, const int ticks)
{
return false;
}
GameEvent e;
e.Type = GAME_EVENT_SOUND_AT;
GameEvent e = GameEventNew(GAME_EVENT_SOUND_AT);
e.u.SoundAt.Sound = obj->bulletClass->HitSound.Wall;
e.u.SoundAt.Pos = realPos;
GameEventsEnqueue(&gGameEvents, e);
@@ -246,8 +245,7 @@ bool UpdateBullet(TMobileObject *obj, const int ticks)
MapIsRealPosIn(&gMap, realPos) && ShootWall(realPos.x, realPos.y);
if (hitWall && !Vec2iIsZero(obj->vel))
{
GameEvent e;
e.Type = GAME_EVENT_SOUND_AT;
GameEvent e = GameEventNew(GAME_EVENT_SOUND_AT);
e.u.SoundAt.Sound = obj->bulletClass->HitSound.Wall;
e.u.SoundAt.Pos = realPos;
GameEventsEnqueue(&gGameEvents, e);
@@ -258,9 +256,7 @@ bool UpdateBullet(TMobileObject *obj, const int ticks)
FireGuns(obj, &obj->bulletClass->HitGuns);
if (obj->bulletClass->Spark != NULL)
{
GameEvent e;
memset(&e, 0, sizeof e);
e.Type = GAME_EVENT_ADD_PARTICLE;
GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
e.u.AddParticle.Class = obj->bulletClass->Spark;
e.u.AddParticle.FullPos = pos;
e.u.AddParticle.Z = obj->z;

View File

@@ -396,7 +396,8 @@ void ConfigLoadDefault(Config *config)
config->Game.EnemyDensity = 100;
config->Game.FriendlyFire = 0;
config->Game.NonPlayerHP = 100;
config->Game.PlayerHP = 100;
config->Game.PlayerHP = 75;
config->Game.Lives = 2;
config->Game.RandomSeed = 0;
config->Game.SlowMotion = 0;
config->Game.Fog = 1;

View File

@@ -106,6 +106,7 @@ typedef struct
int EnemyDensity;
int NonPlayerHP;
int PlayerHP;
int Lives;
bool Fog;
int SightRange;
bool Shadows;

View File

@@ -55,6 +55,7 @@ static void LoadGameConfigNode(
LoadInt(&config->EnemyDensity, node, "EnemyDensity");
LoadInt(&config->NonPlayerHP, node, "NonPlayerHP");
LoadInt(&config->PlayerHP, node, "PlayerHP");
LoadInt(&config->Lives, node, "Lives");
LoadBool(&config->Fog, node, "Fog");
LoadInt(&config->SightRange, node, "SightRange");
LoadBool(&config->Shadows, node, "Shadows");
@@ -96,6 +97,7 @@ static void AddGameConfigNode(GameConfig *config, json_t *root)
AddIntPair(subConfig, "EnemyDensity", config->EnemyDensity);
AddIntPair(subConfig, "NonPlayerHP", config->NonPlayerHP);
AddIntPair(subConfig, "PlayerHP", config->PlayerHP);
AddIntPair(subConfig, "Lives", config->Lives);
json_insert_pair_into_object(
subConfig, "Fog", json_new_bool(config->Fog));
AddIntPair(subConfig, "SightRange", config->SightRange);

View File

@@ -553,6 +553,16 @@ static void DrawObjectiveHighlight(
}
}
TOffsetPic GetHeadPic(
const int bodyType, const direction_e dir, const int face, const int state)
{
TOffsetPic head;
head.dx = cNeckOffset[bodyType][dir].dx + cHeadOffset[face][dir].dx;
head.dy = cNeckOffset[bodyType][dir].dy + cHeadOffset[face][dir].dy;
head.picIndex = cHeadPic[face][dir][state];
return head;
}
void DrawCharacterSimple(
const Character *c, const Vec2i pos,
const direction_e dir, const int state,
@@ -582,13 +592,7 @@ void DrawCharacterSimple(
body.dy = cBodyOffset[bodyType][dir].dy;
body.picIndex = cBodyPic[bodyType][dir][state];
head.dx =
cNeckOffset[bodyType][headDir].dx +
cHeadOffset[c->looks.face][headDir].dx;
head.dy =
cNeckOffset[bodyType][headDir].dy +
cHeadOffset[c->looks.face][headDir].dy;
head.picIndex = cHeadPic[c->looks.face][headDir][headState];
head = GetHeadPic(bodyType, headDir, c->looks.face, headState);
gun.picIndex = -1;
if (gunPic >= 0)

View File

@@ -62,5 +62,7 @@ void DrawCharacterSimple(
const direction_e dir, const int state,
const int gunPic, const gunstate_e gunState,
const TranslationTable *table);
TOffsetPic GetHeadPic(
const int bodyType, const direction_e dir, const int face, const int state);
#endif

View File

@@ -69,7 +69,20 @@ void GameEventsEnqueue(CArray *store, GameEvent e)
}
CArrayPushBack(store, &e);
}
static bool EventComplete(const void *elem);
void GameEventsClear(CArray *store)
{
CArrayClear(store);
CArrayRemoveIf(store, EventComplete);
}
static bool EventComplete(const void *elem)
{
return ((GameEvent *)elem)->Delay < 0;
}
GameEvent GameEventNew(GameEventType type)
{
GameEvent e;
memset(&e, 0, sizeof e);
e.Type = type;
return e;
}

View File

@@ -77,6 +77,7 @@ typedef enum
typedef struct
{
GameEventType Type;
int Delay;
union
{
struct
@@ -142,4 +143,6 @@ void GameEventsTerminate(CArray *store);
void GameEventsEnqueue(CArray *store, GameEvent e);
void GameEventsClear(CArray *store);
GameEvent GameEventNew(GameEventType type);
#endif

View File

@@ -62,6 +62,11 @@ static void HandleGameEvent(
HealthPickups *hp,
EventHandlers *eventHandlers)
{
e->Delay--;
if (e->Delay >= 0)
{
return;
}
switch (e->Type)
{
case GAME_EVENT_SCORE:
@@ -205,8 +210,7 @@ static void HandleGameEvent(
{
case OBJECTIVE_COLLECT:
{
GameEvent e1;
e1.Type = GAME_EVENT_SCORE;
GameEvent e1 = GameEventNew(GAME_EVENT_SCORE);
e1.u.Score.PlayerIndex =
e->u.UpdateObjective.PlayerIndex;
e1.u.Score.Score = PICKUP_SCORE;
@@ -215,8 +219,7 @@ static void HandleGameEvent(
break;
case OBJECTIVE_DESTROY:
{
GameEvent e1;
e1.Type = GAME_EVENT_SCORE;
GameEvent e1 = GameEventNew(GAME_EVENT_SCORE);
e1.u.Score.PlayerIndex =
e->u.UpdateObjective.PlayerIndex;
e1.u.Score.Score = OBJECT_SCORE;

View File

@@ -54,6 +54,7 @@
#include "actors.h"
#include "automap.h"
#include "draw.h"
#include "drawtools.h"
#include "font.h"
#include "game_events.h"
@@ -385,6 +386,35 @@ static void DrawHealth(
FontStrOpt(s, Vec2iZero(), opts);
}
static void DrawLives(
const GraphicsDevice *device, const PlayerData *player, const Vec2i pos,
const FontAlign hAlign, const FontAlign vAlign)
{
const int xStep = hAlign == ALIGN_START ? 10 : -10;
const Vec2i offset = Vec2iNew(5, 20);
Vec2i drawPos = Vec2iAdd(pos, offset);
if (hAlign == ALIGN_END)
{
const int w = device->cachedConfig.Res.x;
drawPos.x = w - drawPos.x - offset.x;
}
if (vAlign == ALIGN_END)
{
const int h = device->cachedConfig.Res.y;
drawPos.y = h - drawPos.y + offset.y + 5;
}
const TOffsetPic head = GetHeadPic(
BODY_ARMED, DIRECTION_DOWN, player->Char.looks.face, STATE_IDLE);
for (int i = 0; i < player->Lives; i++)
{
BlitOld(
drawPos.x + head.dx, drawPos.y + head.dy,
PicManagerGetOldPic(&gPicManager, head.picIndex),
&player->Char.table, BLIT_TRANSPARENT);
drawPos.x += xStep;
}
}
#define HUDFLAGS_PLACE_RIGHT 0x01
#define HUDFLAGS_PLACE_BOTTOM 0x02
#define HUDFLAGS_HALF_SCREEN 0x04
@@ -560,15 +590,22 @@ static void DrawPlayerStatus(
}
if (p)
{
// Weapon
DrawWeaponStatus(
device, ActorGetGun(p), pos, opts.HAlign, opts.VAlign);
pos.y += rowHeight;
// Player name
opts.Pad = pos;
FontStrOpt(s, Vec2iZero(), opts);
// Health
pos.y += rowHeight;
DrawHealth(device, p, pos, opts.HAlign, opts.VAlign);
// Lives
pos.y += rowHeight;
DrawLives(device, data, pos, opts.HAlign, opts.VAlign);
}
else
{
@@ -845,13 +882,16 @@ void HUDDraw(HUD *hud, int isPaused)
if (numPlayersAlive == 0)
{
if (gCampaign.Entry.Mode != CAMPAIGN_MODE_DOGFIGHT)
if (AreAllPlayersDeadAndNoLives())
{
FontStrCenter("Game Over!");
}
else
{
FontStrCenter("All Kill!");
if (gCampaign.Entry.Mode != CAMPAIGN_MODE_DOGFIGHT)
{
FontStrCenter("Game Over!");
}
else
{
FontStrCenter("All Kill!");
}
}
}
else if (hud->mission->state == MISSION_STATE_PICKUP)

View File

@@ -111,8 +111,7 @@ void MapStaticLoadDynamic(
const Vec2i fullPos = Vec2iReal2Full(Vec2iCenterOfTile(*pos));
aa.FullPos.x = fullPos.x;
aa.FullPos.y = fullPos.y;
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
@@ -149,8 +148,7 @@ void MapStaticLoadDynamic(
CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
aa.Health = CharacterGetStartingHealth(c, true);
aa.FullPos = fullPosNet;
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
}
@@ -175,8 +173,7 @@ void MapStaticLoadDynamic(
CArrayGet(&gCampaign.Setting.characters.OtherChars, aa.CharId);
aa.Health = CharacterGetStartingHealth(c, true);
aa.FullPos = fullPosNet;
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
e.u.ActorAdd = aa;
GameEventsEnqueue(&gGameEvents, e);
}

View File

@@ -656,8 +656,7 @@ void MissionSetMessageIfComplete(struct MissionOptions *options)
{
if (CanCompleteMission(options))
{
GameEvent msg;
msg.Type = GAME_EVENT_MISSION_COMPLETE;
GameEvent msg = GameEventNew(GAME_EVENT_MISSION_COMPLETE);
GameEventsEnqueue(&gGameEvents, msg);
}
}
@@ -676,8 +675,7 @@ void UpdateMissionObjective(
{
return;
}
GameEvent e;
e.Type = GAME_EVENT_UPDATE_OBJECTIVE;
GameEvent e = GameEventNew(GAME_EVENT_UPDATE_OBJECTIVE);
e.u.UpdateObjective.ObjectiveIndex = idx;
e.u.UpdateObjective.Update = 1;
e.u.UpdateObjective.PlayerIndex = player;

View File

@@ -218,8 +218,7 @@ static void OnReceive(NetClient *n, ENetEvent event)
case SERVER_MSG_ACTOR_ADD:
{
debug(D_VERBOSE, "NetClient: received actor add");
GameEvent e;
e.Type = GAME_EVENT_ACTOR_ADD;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_ADD);
NetDecode(event.packet, &e.u.ActorAdd, NetMsgActorAdd_fields);
GameEventsEnqueue(&gGameEvents, e);
}
@@ -227,8 +226,7 @@ static void OnReceive(NetClient *n, ENetEvent event)
case SERVER_MSG_ACTOR_MOVE:
{
debug(D_VERBOSE, "NetClient: received actor move");
GameEvent e;
e.Type = GAME_EVENT_ACTOR_MOVE;
GameEvent e = GameEventNew(GAME_EVENT_ACTOR_MOVE);
NetDecode(event.packet, &e.u.ActorMove, NetMsgActorMove_fields);
GameEventsEnqueue(&gGameEvents, e);
}

View File

@@ -226,7 +226,7 @@ static void SendGameStartMessages(
NetServer *n, const int peerId, const PlayerData *pData)
{
// Add the player's actor
PlacePlayer(&gMap, pData, Vec2iZero());
PlacePlayer(&gMap, pData, Vec2iZero(), true);
// Send all players
for (int i = 0; i < (int)gActors.size; i++)

View File

@@ -73,6 +73,7 @@ NetMsgPlayerData NetMsgMakePlayerData(const PlayerData *p)
{
strcpy(d.Weapons[i], p->weapons[i]->name);
}
d.Lives = p->Lives;
d.Score = p->score;
d.TotalScore = p->totalScore;
d.Kills = p->kills;
@@ -104,6 +105,7 @@ void NetMsgPlayerDataUpdate(const NetMsgPlayerData *pd)
{
p->weapons[i] = StrGunDescription(pd->Weapons[i]);
}
p->Lives = pd->Lives;
p->score = pd->Score;
p->totalScore = pd->Score;
p->kills = pd->Kills;

View File

@@ -135,8 +135,7 @@ static void DamageObject(
player, pos);
if (object->flags & OBJFLAG_QUAKE)
{
GameEvent shake;
shake.Type = GAME_EVENT_SCREEN_SHAKE;
GameEvent shake = GameEventNew(GAME_EVENT_SCREEN_SHAKE);
shake.u.ShakeAmount = SHAKE_BIG_AMOUNT;
GameEventsEnqueue(&gGameEvents, shake);
}
@@ -175,9 +174,7 @@ static void DamageObject(
else
{
// A wreck left after the destruction of this object
GameEvent e;
memset(&e, 0, sizeof e);
e.Type = GAME_EVENT_ADD_BULLET;
GameEvent e = GameEventNew(GAME_EVENT_ADD_BULLET);
e.u.AddBullet.BulletClass = StrBulletClass("fireball_wreck");
e.u.AddBullet.MuzzlePos = fullPos;
e.u.AddBullet.MuzzleHeight = 0;
@@ -245,8 +242,7 @@ bool DamageSomething(
DamageObject(power, flags, player, uid, target);
if (gConfig.Sound.Hits && hitSounds != NULL && power > 0)
{
GameEvent es;
es.Type = GAME_EVENT_SOUND_AT;
GameEvent es = GameEventNew(GAME_EVENT_SOUND_AT);
es.u.SoundAt.Sound = hitSounds->Object;
es.u.SoundAt.Pos = pos;
GameEventsEnqueue(&gGameEvents, es);
@@ -278,8 +274,7 @@ static bool DoDamageCharacter(
bool canHit = CanHitCharacter(flags, uid, actor);
if (canHit)
{
GameEvent e;
e.Type = GAME_EVENT_HIT_CHARACTER;
GameEvent e = GameEventNew(GAME_EVENT_HIT_CHARACTER);
e.u.HitCharacter.TargetId = actor->tileItem.id;
e.u.HitCharacter.Special = special;
GameEventsEnqueue(&gGameEvents, e);
@@ -288,16 +283,14 @@ static bool DoDamageCharacter(
(allowFriendlyHitSound || !ActorIsInvulnerable(
actor, flags, player, gCampaign.Entry.Mode)))
{
GameEvent es;
es.Type = GAME_EVENT_SOUND_AT;
GameEvent es = GameEventNew(GAME_EVENT_SOUND_AT);
es.u.SoundAt.Sound = hitSounds->Flesh;
es.u.SoundAt.Pos = pos;
GameEventsEnqueue(&gGameEvents, es);
}
if (gConfig.Game.ShotsPushback)
{
GameEvent ei;
ei.Type = GAME_EVENT_ACTOR_IMPULSE;
GameEvent ei = GameEventNew(GAME_EVENT_ACTOR_IMPULSE);
ei.u.ActorImpulse.Id = actor->tileItem.id;
ei.u.ActorImpulse.Vel = Vec2iScaleDiv(
Vec2iScale(hitVector, power), SHOT_IMPULSE_DIVISOR);
@@ -305,8 +298,7 @@ static bool DoDamageCharacter(
}
if (CanDamageCharacter(flags, player, uid, actor, special))
{
GameEvent e1;
e1.Type = GAME_EVENT_DAMAGE_CHARACTER;
GameEvent e1 = GameEventNew(GAME_EVENT_DAMAGE_CHARACTER);
e1.u.DamageCharacter.Power = power;
e1.u.DamageCharacter.PlayerIndex = player;
e1.u.DamageCharacter.TargetId = actor->tileItem.id;
@@ -320,9 +312,7 @@ static bool DoDamageCharacter(
if (gConfig.Game.Gore != GORE_NONE)
{
GameEvent eb;
memset(&eb, 0, sizeof eb);
eb.Type = GAME_EVENT_ADD_PARTICLE;
GameEvent eb = GameEventNew(GAME_EVENT_ADD_PARTICLE);
eb.u.AddParticle.FullPos = Vec2iReal2Full(pos);
eb.u.AddParticle.Z = 10 * Z_FACTOR;
int bloodPower = power * 2;
@@ -389,8 +379,7 @@ static bool DoDamageCharacter(
{
// Calculate score based on
// if they hit a penalty character
GameEvent e2;
e2.Type = GAME_EVENT_SCORE;
GameEvent e2 = GameEventNew(GAME_EVENT_SCORE);
e2.u.Score.PlayerIndex = player;
if (actor->flags & FLAGS_PENALTY)
{
@@ -419,8 +408,7 @@ void UpdateMobileObjects(int ticks)
}
if ((*(obj->updateFunc))(obj, ticks) == 0)
{
GameEvent e;
e.Type = GAME_EVENT_MOBILE_OBJECT_REMOVE;
GameEvent e = GameEventNew(GAME_EVENT_MOBILE_OBJECT_REMOVE);
e.u.MobileObjectRemoveId = i;
GameEventsEnqueue(&gGameEvents, e);
}

View File

@@ -211,8 +211,7 @@ void ParticlesUpdate(CArray *particles, const int ticks)
}
if (!ParticleUpdate(p, ticks))
{
GameEvent e;
e.Type = GAME_EVENT_PARTICLE_REMOVE;
GameEvent e = GameEventNew(GAME_EVENT_PARTICLE_REMOVE);
e.u.ParticleRemoveId = i;
GameEventsEnqueue(&gGameEvents, e);
}

View File

@@ -172,6 +172,8 @@ void PlayerDataTerminate(CArray *p)
void PlayerDataStart(PlayerData *p, const int maxHealth, const int mission)
{
p->Lives = gConfig.Game.Lives;
p->score = 0;
p->kills = 0;
p->friendlies = 0;
@@ -198,6 +200,19 @@ int GetNumPlayers(const bool alive, const bool human, const bool local)
return numPlayers;
}
bool AreAllPlayersDeadAndNoLives(void)
{
for (int i = 0; i < (int)gPlayerDatas.size; i++)
{
const PlayerData *p = CArrayGet(&gPlayerDatas, i);
if (IsPlayerAlive(p) || p->Lives > 0)
{
return false;
}
}
return true;
}
const PlayerData *GetFirstPlayer(
const bool alive, const bool human, const bool local)
{

View File

@@ -40,6 +40,7 @@ typedef struct
char name[20];
int weaponCount;
const GunDescription *weapons[MAX_WEAPONS];
int Lives;
int score;
int totalScore;
@@ -71,6 +72,7 @@ void PlayerDataTerminate(CArray *p);
void PlayerDataStart(PlayerData *p, const int maxHealth, const int mission);
int GetNumPlayers(const bool alive, const bool human, const bool local);
bool AreAllPlayersDeadAndNoLives(void);
const PlayerData *GetFirstPlayer(
const bool alive, const bool human, const bool local);
bool IsPlayerAlive(const PlayerData *player);

View File

@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.0 at Sat Oct 04 14:39:20 2014. */
/* Generated by nanopb-0.3.0 at Wed Nov 12 23:08:53 2014. */
#include "client.pb.h"

View File

@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.0 at Sat Oct 04 14:39:20 2014. */
/* Generated by nanopb-0.3.0 at Wed Nov 12 23:08:53 2014. */
#ifndef PB_CLIENT_PB_H_INCLUDED
#define PB_CLIENT_PB_H_INCLUDED

View File

@@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.0 at Sat Oct 04 14:39:20 2014. */
/* Generated by nanopb-0.3.0 at Wed Nov 12 23:08:53 2014. */
#include "server.pb.h"
@@ -21,16 +21,17 @@ const pb_field_t NetMsgCampaignDef_fields[3] = {
PB_LAST_FIELD
};
const pb_field_t NetMsgPlayerData_fields[10] = {
const pb_field_t NetMsgPlayerData_fields[11] = {
PB_FIELD( 1, STRING , REQUIRED, STATIC , FIRST, NetMsgPlayerData, Name, Name, 0),
PB_FIELD( 2, MESSAGE , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Looks, Name, &NetMsgPlayerData_CharLooks_fields),
PB_FIELD( 3, STRING , REPEATED, STATIC , OTHER, NetMsgPlayerData, Weapons, Looks, 0),
PB_FIELD( 4, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Score, Weapons, 0),
PB_FIELD( 5, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, TotalScore, Score, 0),
PB_FIELD( 6, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Kills, TotalScore, 0),
PB_FIELD( 7, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Friendlies, Kills, 0),
PB_FIELD( 8, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, PlayerIndex, Friendlies, 0),
PB_FIELD( 9, BOOL , REQUIRED, STATIC , OTHER, NetMsgPlayerData, IsUsed, PlayerIndex, 0),
PB_FIELD( 4, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Lives, Weapons, 0),
PB_FIELD( 5, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Score, Lives, 0),
PB_FIELD( 6, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, TotalScore, Score, 0),
PB_FIELD( 7, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Kills, TotalScore, 0),
PB_FIELD( 8, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, Friendlies, Kills, 0),
PB_FIELD( 9, INT32 , REQUIRED, STATIC , OTHER, NetMsgPlayerData, PlayerIndex, Friendlies, 0),
PB_FIELD( 10, BOOL , REQUIRED, STATIC , OTHER, NetMsgPlayerData, IsUsed, PlayerIndex, 0),
PB_LAST_FIELD
};

View File

@@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.0 at Sat Oct 04 14:39:20 2014. */
/* Generated by nanopb-0.3.0 at Wed Nov 12 23:08:53 2014. */
#ifndef PB_SERVER_PB_H_INCLUDED
#define PB_SERVER_PB_H_INCLUDED
@@ -64,6 +64,7 @@ typedef struct _NetMsgPlayerData {
NetMsgPlayerData_CharLooks Looks;
pb_size_t Weapons_count;
char Weapons[3][128];
int32_t Lives;
int32_t Score;
int32_t TotalScore;
int32_t Kills;
@@ -78,7 +79,7 @@ extern const int32_t NetMsgActorAdd_PlayerId_default;
/* Initializer values for message structs */
#define NetMsgClientId_init_default {0}
#define NetMsgCampaignDef_init_default {"", 0}
#define NetMsgPlayerData_init_default {"", NetMsgPlayerData_CharLooks_init_default, 0, {"", "", ""}, 0, 0, 0, 0, 0, 0}
#define NetMsgPlayerData_init_default {"", NetMsgPlayerData_CharLooks_init_default, 0, {"", "", ""}, 0, 0, 0, 0, 0, 0, 0}
#define NetMsgPlayerData_CharLooks_init_default {0, 0, 0, 0, 0, 0}
#define NetMsgAddPlayers_init_default {0, 0, {0, 0, 0, 0}}
#define NetMsgVec2i_init_default {0, 0}
@@ -86,7 +87,7 @@ extern const int32_t NetMsgActorAdd_PlayerId_default;
#define NetMsgActorMove_init_default {0, NetMsgVec2i_init_default}
#define NetMsgClientId_init_zero {0}
#define NetMsgCampaignDef_init_zero {"", 0}
#define NetMsgPlayerData_init_zero {"", NetMsgPlayerData_CharLooks_init_zero, 0, {"", "", ""}, 0, 0, 0, 0, 0, 0}
#define NetMsgPlayerData_init_zero {"", NetMsgPlayerData_CharLooks_init_zero, 0, {"", "", ""}, 0, 0, 0, 0, 0, 0, 0}
#define NetMsgPlayerData_CharLooks_init_zero {0, 0, 0, 0, 0, 0}
#define NetMsgAddPlayers_init_zero {0, 0, {0, 0, 0, 0}}
#define NetMsgVec2i_init_zero {0, 0}
@@ -119,17 +120,18 @@ extern const int32_t NetMsgActorAdd_PlayerId_default;
#define NetMsgPlayerData_Name_tag 1
#define NetMsgPlayerData_Looks_tag 2
#define NetMsgPlayerData_Weapons_tag 3
#define NetMsgPlayerData_Score_tag 4
#define NetMsgPlayerData_TotalScore_tag 5
#define NetMsgPlayerData_Kills_tag 6
#define NetMsgPlayerData_Friendlies_tag 7
#define NetMsgPlayerData_PlayerIndex_tag 8
#define NetMsgPlayerData_IsUsed_tag 9
#define NetMsgPlayerData_Lives_tag 4
#define NetMsgPlayerData_Score_tag 5
#define NetMsgPlayerData_TotalScore_tag 6
#define NetMsgPlayerData_Kills_tag 7
#define NetMsgPlayerData_Friendlies_tag 8
#define NetMsgPlayerData_PlayerIndex_tag 9
#define NetMsgPlayerData_IsUsed_tag 10
/* Struct field encoding specification for nanopb */
extern const pb_field_t NetMsgClientId_fields[2];
extern const pb_field_t NetMsgCampaignDef_fields[3];
extern const pb_field_t NetMsgPlayerData_fields[10];
extern const pb_field_t NetMsgPlayerData_fields[11];
extern const pb_field_t NetMsgPlayerData_CharLooks_fields[7];
extern const pb_field_t NetMsgAddPlayers_fields[3];
extern const pb_field_t NetMsgVec2i_fields[3];
@@ -139,7 +141,7 @@ extern const pb_field_t NetMsgActorMove_fields[3];
/* Maximum encoded size of messages (where known) */
#define NetMsgClientId_size 11
#define NetMsgCampaignDef_size 4105
#define NetMsgPlayerData_size 540
#define NetMsgPlayerData_size 551
#define NetMsgPlayerData_CharLooks_size 66
#define NetMsgAddPlayers_size 55
#define NetMsgVec2i_size 22

View File

@@ -19,12 +19,13 @@ message NetMsgPlayerData {
}
required CharLooks Looks = 2;
repeated string Weapons = 3;
required int32 Score = 4;
required int32 TotalScore = 5;
required int32 Kills = 6;
required int32 Friendlies = 7;
required int32 PlayerIndex = 8;
required bool IsUsed = 9;
required int32 Lives = 4;
required int32 Score = 5;
required int32 TotalScore = 6;
required int32 Kills = 7;
required int32 Friendlies = 8;
required int32 PlayerIndex = 9;
required bool IsUsed = 10;
}
message NetMsgAddPlayers {

View File

@@ -432,9 +432,7 @@ void GunAddBullets(
((double)rand() / RAND_MAX * g->Recoil) - g->Recoil / 2;
const double finalAngle =
radians + spreadStartAngle + i * g->Spread.Width + recoil;
GameEvent e;
memset(&e, 0, sizeof e);
e.Type = GAME_EVENT_ADD_BULLET;
GameEvent e = GameEventNew(GAME_EVENT_ADD_BULLET);
e.u.AddBullet.BulletClass = g->Bullet;
e.u.AddBullet.MuzzlePos = fullPos;
e.u.AddBullet.MuzzleHeight = z;
@@ -461,8 +459,7 @@ void GunAddBullets(
}
if (g->ShakeAmount > 0)
{
GameEvent shake;
shake.Type = GAME_EVENT_SCREEN_SHAKE;
GameEvent shake = GameEventNew(GAME_EVENT_SCREEN_SHAKE);
shake.u.ShakeAmount = g->ShakeAmount;
GameEventsEnqueue(&gGameEvents, shake);
}
@@ -472,9 +469,7 @@ static void AddBrass(
const GunDescription *g, const direction_e d, const Vec2i pos)
{
CASSERT(g->Brass, "Cannot create brass for no-brass weapon");
GameEvent e;
memset(&e, 0, sizeof e);
e.Type = GAME_EVENT_ADD_PARTICLE;
GameEvent e = GameEventNew(GAME_EVENT_ADD_PARTICLE);
e.u.AddParticle.Class = g->Brass;
double x, y;
const double radians = dir2radians[d];

View File

@@ -409,8 +409,7 @@ bool RunGame(struct MissionOptions *m, Map *map)
crosshair->offset.y = -crosshair->size.y / 2;
EventReset(&gEventHandlers, crosshair);
GameEvent start;
start.Type = GAME_EVENT_GAME_START;
GameEvent start = GameEventNew(GAME_EVENT_GAME_START);
GameEventsEnqueue(&gGameEvents, start);
// Set mission complete and display exit if it is complete
@@ -437,8 +436,7 @@ static void RunGameInput(void *data)
if (gEventHandlers.HasQuit)
{
GameEvent e;
e.Type = GAME_EVENT_MISSION_END;
GameEvent e = GameEventNew(GAME_EVENT_MISSION_END);
GameEventsEnqueue(&gGameEvents, e);
return;
}
@@ -482,8 +480,7 @@ static void RunGameInput(void *data)
if (rData->isPaused)
{
// Exit
GameEvent e;
e.Type = GAME_EVENT_MISSION_END;
GameEvent e = GameEventNew(GAME_EVENT_MISSION_END);
GameEventsEnqueue(&gGameEvents, e);
// Need to unpause to process the quit
rData->isPaused = false;
@@ -564,8 +561,7 @@ static GameLoopResult RunGameUpdate(void *data)
}
const TActor *p = CArrayGet(&gActors, pd->Id);
const int pad = SPLIT_PADDING;
GameEvent ei;
ei.Type = GAME_EVENT_ACTOR_IMPULSE;
GameEvent ei = GameEventNew(GAME_EVENT_ACTOR_IMPULSE);
ei.u.ActorImpulse.Id = p->tileItem.id;
ei.u.ActorImpulse.Vel = p->Vel;
if (screen.x + pad > p->tileItem.x && p->Vel.x < 256)
@@ -626,21 +622,18 @@ static void CheckMissionCompletion(const struct MissionOptions *mo)
GetNumPlayers(true, false, false) > 0 && IsMissionComplete(mo);
if (mo->state == MISSION_STATE_PLAY && isMissionComplete)
{
GameEvent e;
e.Type = GAME_EVENT_MISSION_PICKUP;
GameEvent e = GameEventNew(GAME_EVENT_MISSION_PICKUP);
GameEventsEnqueue(&gGameEvents, e);
}
if (mo->state == MISSION_STATE_PICKUP && !isMissionComplete)
{
GameEvent e;
e.Type = GAME_EVENT_MISSION_INCOMPLETE;
GameEvent e = GameEventNew(GAME_EVENT_MISSION_INCOMPLETE);
GameEventsEnqueue(&gGameEvents, e);
}
if (mo->state == MISSION_STATE_PICKUP &&
mo->pickupTime + PICKUP_LIMIT <= mo->time)
{
GameEvent e;
e.Type = GAME_EVENT_MISSION_END;
GameEvent e = GameEventNew(GAME_EVENT_MISSION_END);
GameEventsEnqueue(&gGameEvents, e);
}
@@ -657,10 +650,9 @@ static void CheckMissionCompletion(const struct MissionOptions *mo)
break;
}
}
if (allPlayersDestroyed)
if (allPlayersDestroyed && AreAllPlayersDeadAndNoLives())
{
GameEvent e;
e.Type = GAME_EVENT_MISSION_END;
GameEvent e = GameEventNew(GAME_EVENT_MISSION_END);
GameEventsEnqueue(&gGameEvents, e);
}
}
@@ -696,8 +688,7 @@ static void MissionUpdateObjectives(struct MissionOptions *mo, Map *map)
int update = MapGetExploredPercentage(map) - o->done;
if (update > 0)
{
GameEvent e;
e.Type = GAME_EVENT_UPDATE_OBJECTIVE;
GameEvent e = GameEventNew(GAME_EVENT_UPDATE_OBJECTIVE);
e.u.UpdateObjective.ObjectiveIndex = i;
e.u.UpdateObjective.Update = update;
GameEventsEnqueue(&gGameEvents, e);

View File

@@ -326,6 +326,13 @@ menu_t *MenuCreateOptionsGame(const char *name, MenuSystem *ms)
&gConfig.Game.PlayerHP,
25, 200, 25,
MENU_OPTION_DISPLAY_STYLE_INT_TO_STR_FUNC, (void (*)(void))PercentStr));
MenuAddSubmenu(
menu,
MenuCreateOptionRange(
"Lives",
&gConfig.Game.Lives,
0, 5, 1,
MENU_OPTION_DISPLAY_STYLE_INT, NULL));
MenuAddSubmenu(
menu,
MenuCreateOptionToggle(