blobwarsAttrition/src/entities/blobs/bob.c

728 lines
15 KiB
C

/*
Copyright (C) 2018-2019 Parallel Realities
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "bob.h"
static SDL_Rect *getCurrentSprite(void);
static void (*superAnimate)(void);
static void animate(void);
static void init(void);
static void tick(void);
static void doAlive(void);
static void doDying(void);
static void doBobInWater(void);
static void doBobInAir(void);
static void handeImmunity(void);
static void changeSprite(Sprite **sprite);
static void load(cJSON *root);
static void save(cJSON *root);
static int completelyOnGround(void);
void resetAtCheckpoint(void);
void terminateJetpack(void);
static void bobFly(void);
static void bobWalk(void);
static void bobSwim(void);
static void fireWeapon(void);
static void activate(int activate);
static void changeEnvironment(void);
static void applyDamage(int damage);
static void die(void);
static Sprite *walkSprite[3];
static Sprite *swimSprite[3];
static Sprite *flySprite[3];
static int checkpointTimer;
static int oldFacing;
Entity *initBob(void)
{
Bob *b;
b = malloc(sizeof(Bob));
memset(b, 0, sizeof(Bob));
initEntity((Entity*)b);
STRNCPY(b->name, "Bob", MAX_NAME_LENGTH);
b->type = ET_BOB;
walkSprite[FACING_LEFT] = getSprite("BobLeft");
walkSprite[FACING_RIGHT] = getSprite("BobRight");
walkSprite[FACING_DIE] = getSprite("BobSpin");
swimSprite[FACING_LEFT] = getSprite("BobAquaLeft");
swimSprite[FACING_RIGHT] = getSprite("BobAquaRight");
swimSprite[FACING_DIE] = getSprite("BobAquaSpin");
flySprite[FACING_LEFT] = getSprite("BobJPLeft");
flySprite[FACING_RIGHT] = getSprite("BobJPRight");
flySprite[FACING_DIE] = getSprite("BobSpin");
b->health = b->healthMax = game.hearts;
b->power = b->powerMax = game.cells;
b->oxygen = MAX_OXYGEN;
b->weaponType = WPN_PISTOL;
b->reload = 0;
b->flags |= EF_SWIMS | EF_BOMB_SHIELD;
superAnimate = b->animate;
b->tick = tick;
b->init = init;
b->applyDamage = applyDamage;
b->getCurrentSprite = getCurrentSprite;
b->animate = animate;
b->activate = activate;
b->changeEnvironment = changeEnvironment;
b->die = die;
b->load = load;
b->save = save;
checkpointTimer = 0;
oldFacing = b->facing;
return (Entity*)b;
}
static void init(void)
{
changeSprite(walkSprite);
world.bob->checkpoints[0].x = world.bob->x;
world.bob->checkpoints[0].y = world.bob->y;
superAnimate();
}
static void tick(void)
{
if (world.missionType == MT_TRAINING || dev.cheatHealth)
{
world.bob->alive = ALIVE_ALIVE;
world.bob->health = world.bob->healthMax;
}
if (world.missionType == MT_TRAINING)
{
world.bob->power = MIN(world.bob->power + 0.01, world.bob->powerMax);
}
else
{
world.bob->power = MIN(world.bob->power + 0.00065, world.bob->powerMax);
}
if (dev.cheatPower)
{
world.bob->power = world.bob->powerMax;
}
if (dev.cheatReload && world.bob->reload > 4)
{
world.bob->reload = 4;
}
switch (world.bob->alive)
{
case ALIVE_ALIVE:
doAlive();
break;
case ALIVE_DYING:
doDying();
break;
default:
break;
}
}
static void doAlive(void)
{
handeImmunity();
checkpointTimer = MAX(checkpointTimer - 1, 0);
world.bob->reload = limit(world.bob->reload - 1, 0, FPS);
switch (world.bob->environment)
{
case ENV_AIR:
doBobInAir();
break;
case ENV_WATER:
doBobInWater();
break;
case ENV_SLIME:
if (world.bob->alive == ALIVE_ALIVE && world.bob->outTimer == 0)
{
world.bob->outTimer = FPS * 3;
world.bob->stunTimer = 1;
if (!(world.bob->flags & EF_IMMUNE))
{
world.bob->health--;
}
}
break;
case ENV_LAVA:
if (world.bob->alive == ALIVE_ALIVE && world.bob->outTimer == 0)
{
world.bob->outTimer = FPS * 3;
world.bob->stunTimer = 1;
if (!(world.bob->flags & EF_IMMUNE))
{
world.bob->health -= 2;
}
}
break;
}
}
static void handeImmunity(void)
{
int i;
if (world.bob->outTimer > 0)
{
if (--world.bob->outTimer <= 0)
{
world.betweenTimer = FPS / 2;
resetAtCheckpoint();
}
}
else if (world.bob->stunTimer > 0)
{
if (--world.bob->stunTimer <= 0)
{
if (!world.bob->isOnGround && world.bob->environment != ENV_WATER)
{
world.bob->stunTimer = 1;
}
else
{
world.bob->facing = oldFacing;
world.bob->spriteFrame = 0;
world.bob->spriteTime = 0;
world.bob->dy = 0;
world.bob->flags &= ~EF_BOUNCES;
world.bob->flags |= EF_FLICKER;
}
}
}
else if (world.bob->immuneTimer > 0)
{
if (--world.bob->immuneTimer <= 0)
{
world.bob->flags &= ~(EF_FLICKER | EF_IMMUNE);
}
}
else if (checkpointTimer == 0 && world.bob->isOnGround && completelyOnGround() && world.bob->environment == ENV_AIR && world.bob->riding == NULL)
{
for (i = MAX_CHECKPOINTS - 1 ; i > 0 ; i--)
{
world.bob->checkpoints[i].x = world.bob->checkpoints[i - 1].x;
world.bob->checkpoints[i].y = world.bob->checkpoints[i - 1].y;
}
world.bob->checkpoints[0].x = world.bob->x;
world.bob->checkpoints[0].y = world.bob->y;
checkpointTimer = FPS;
}
}
static int completelyOnGround(void)
{
int x1, x2, y;
x1 = (world.bob->x / MAP_TILE_SIZE);
x2 = ((world.bob->x + world.bob->w) / MAP_TILE_SIZE);
y = ((world.bob->y + world.bob->h + 8) / MAP_TILE_SIZE);
return isSolid(x1, y) && isSolid(x2, y);
}
static void activate(int activate)
{
if (world.bob->stunTimer <= 0)
{
if (world.bob->flags & (EF_WATER_BREATHING | EF_WEIGHTLESS))
{
world.bob->flags &= ~(EF_WATER_BREATHING | EF_WEIGHTLESS);
changeSprite(walkSprite);
}
else if (world.bob->power > 0)
{
if (world.bob->environment == ENV_AIR)
{
world.bob->flags |= EF_WEIGHTLESS;
changeSprite(flySprite);
}
else if (world.bob->environment == ENV_WATER)
{
world.bob->flags |= EF_WATER_BREATHING;
changeSprite(swimSprite);
}
world.bob->dy = 0;
}
else
{
if (world.bob->environment == ENV_AIR)
{
setGameplayMessage(MSG_STANDARD, app.strings[ST_JETPACK_POWER]);
}
else
{
setGameplayMessage(MSG_STANDARD, app.strings[ST_AQUALUNG_POWER]);
}
}
}
}
static void doBobInAir(void)
{
world.bob->oxygen = limit(world.bob->oxygen + 4, 0, MAX_OXYGEN);
if (world.bob->flags & EF_WEIGHTLESS)
{
if (!dev.cheatPower)
{
world.bob->power = limit(world.bob->power - (1.0 / FPS), 0, world.bob->powerMax);
}
if (--world.bob->jpEffectTimer <= 0)
{
addFlameParticles(world.bob->x + (world.bob->facing * 25) + rrnd(-1, 1), world.bob->y + 25 + rrnd(-1, 1), 0);
world.bob->jpEffectTimer = 1;
}
if (world.bob->power == 0)
{
terminateJetpack();
}
}
}
void terminateJetpack(void)
{
changeSprite(walkSprite);
world.bob->flags &= ~EF_WEIGHTLESS;
}
static void doBobInWater(void)
{
if (world.bob->flags & EF_WATER_BREATHING)
{
if (!dev.cheatPower)
{
world.bob->power = limit(world.bob->power - (1.0 / FPS), 0, world.bob->powerMax);
}
if (world.bob->power == 0)
{
world.bob->flags &= ~EF_WATER_BREATHING;
changeSprite(walkSprite);
}
}
else
{
world.bob->oxygen = limit(--world.bob->oxygen, 0, MAX_OXYGEN);
if (world.bob->oxygen == 0 && (world.frameCounter % 30) == 0)
{
world.bob->health--;
playSound(SND_DROWN, world.bob->uniqueId % MAX_SND_CHANNELS);
}
}
}
static void doDying(void)
{
int mx, my;
if (--world.bob->health <= -(FPS * 2))
{
world.bob->flags |= EF_GONE;
if (app.config.blood)
{
throwFleshChunks(world.bob->x + world.bob->w / 2, world.bob->y + world.bob->h / 2, rrnd(3, 6));
}
else
{
addPopParticles(world.bob->x + world.bob->w / 2, world.bob->y + world.bob->h / 2);
}
world.state = WS_GAME_OVER;
if (app.config.blood)
{
playSound(SND_SPLAT, world.bob->uniqueId % MAX_SND_CHANNELS);
}
else
{
playSound(SND_POP, world.bob->uniqueId % MAX_SND_CHANNELS);
}
mx = (int) ((world.bob->x + (world.bob->w / 2)) / MAP_TILE_SIZE);
my = (int) (world.bob->y / MAP_TILE_SIZE) + 1;
addBloodDecal(mx, my);
game.stats[STAT_DEATHS]++;
}
}
void stunBob(void)
{
terminateJetpack();
world.bob->stunTimer = FPS;
world.bob->immuneTimer = FPS * 2;
world.bob->spriteFrame = 0;
world.bob->spriteTime = 0;
world.bob->flags &= ~EF_FLICKER;
world.bob->flags |= (EF_BOUNCES | EF_IMMUNE);
}
static void applyDamage(int damage)
{
if (!(world.bob->flags & EF_IMMUNE) && world.missionType != MT_TRAINING && !dev.cheatHealth && world.bob->alive != ALIVE_DEAD)
{
if (world.bob->health < 0)
{
world.bob->health = 0;
world.bob->alive = ALIVE_ALIVE;
}
world.bob->health -= damage;
if (world.bob->health > 0)
{
world.bob->immuneTimer = FPS * 2;
world.bob->flags |= EF_FLICKER | EF_IMMUNE;
}
}
}
void doBob(void)
{
if (world.bob->stunTimer <= 0 && world.bob->health > 0)
{
if (world.bob->flags & EF_WEIGHTLESS)
{
bobFly();
}
else if (world.bob->environment == ENV_AIR)
{
bobWalk();
}
else if (world.bob->environment == ENV_WATER)
{
bobSwim();
}
}
}
static void bobWalk(void)
{
world.bob->dx = 0;
if (isControl(CONTROL_LEFT))
{
world.bob->dx = -WALK_SPEED;
world.bob->facing = FACING_LEFT;
}
if (isControl(CONTROL_RIGHT))
{
world.bob->dx = WALK_SPEED;
world.bob->facing = FACING_RIGHT;
}
if (isControl(CONTROL_JUMP) && world.bob->isOnGround)
{
world.bob->dy = JUMP_POWER;
}
if (isControl(CONTROL_FIRE) && world.bob->reload == 0)
{
fireWeapon();
}
if (isControl(CONTROL_JETPACK))
{
activate(1);
clearControl(CONTROL_JETPACK);
}
}
static void bobSwim(void)
{
world.bob->dx = 0;
world.bob->dy = 0.5f;
if (isControl(CONTROL_LEFT))
{
world.bob->dx = -SWIM_SPEED;
world.bob->facing = FACING_LEFT;
}
if (isControl(CONTROL_RIGHT))
{
world.bob->dx = SWIM_SPEED;
world.bob->facing = FACING_RIGHT;
}
if (isControl(CONTROL_JUMP) || isControl(CONTROL_UP))
{
world.bob->dy = -SWIM_SPEED;
}
if (isControl(CONTROL_DOWN))
{
world.bob->dy = SWIM_SPEED;
}
if (isControl(CONTROL_FIRE) && world.bob->reload == 0 && world.bob->weaponType == WPN_PISTOL)
{
firePistol();
}
if (isControl(CONTROL_JETPACK))
{
activate(1);
clearControl(CONTROL_JETPACK);
}
game.stats[STAT_SWIM_TIME]++;
}
static void bobFly(void)
{
if (isControl(CONTROL_LEFT))
{
world.bob->dx -= FLY_ACCEL;
world.bob->facing = FACING_LEFT;
}
if (isControl(CONTROL_RIGHT))
{
world.bob->dx += FLY_ACCEL;
world.bob->facing = FACING_RIGHT;
}
if (isControl(CONTROL_UP))
{
world.bob->dy -= FLY_ACCEL;
}
if (isControl(CONTROL_DOWN))
{
world.bob->dy += FLY_ACCEL;
}
if (isControl(CONTROL_FIRE) && world.bob->reload == 0)
{
fireWeapon();
}
if (isControl(CONTROL_JETPACK))
{
activate(1);
clearControl(CONTROL_JETPACK);
}
world.bob->dx = MIN(FLY_SPEED, MAX(world.bob->dx, -FLY_SPEED));
world.bob->dy = MIN(FLY_SPEED, MAX(world.bob->dy, -FLY_SPEED));
game.stats[STAT_FLY_TIME]++;
}
static void fireWeapon(void)
{
switch (world.bob->weaponType)
{
case WPN_PISTOL:
firePistol();
break;
case WPN_PLASMA:
firePlasma((Entity*)world.bob);
break;
case WPN_SPREAD:
fireSpread((Entity*)world.bob, 3);
break;
case WPN_LASER:
fireLaser((Entity*)world.bob);
break;
case WPN_GRENADES:
fireGrenade((Entity*)world.bob);
break;
default:
break;
}
game.stats[STAT_SHOTS_FIRED]++;
}
static void changeEnvironment(void)
{
switch (world.bob->environment)
{
case ENV_AIR:
case ENV_LAVA:
case ENV_SLIME:
world.bob->flags &= ~EF_WATER_BREATHING;
changeSprite(walkSprite);
break;
case ENV_WATER:
terminateJetpack();
break;
}
}
static void changeSprite(Sprite **sprite)
{
world.bob->sprite[0] = sprite[0];
world.bob->sprite[1] = sprite[1];
world.bob->sprite[2] = sprite[2];
world.bob->spriteFrame = 0;
world.bob->spriteTime = 0;
}
void resetAtCheckpoint(void)
{
world.bob->x = world.bob->checkpoints[0].x;
world.bob->y = world.bob->checkpoints[0].y;
world.bob->facing = oldFacing;
world.bob->outTimer = 0;
world.bob->flags |= EF_FLICKER;
world.bob->flags &= ~EF_WEIGHTLESS;
changeSprite(walkSprite);
world.bob->environment = ENV_AIR;
world.bob->immuneTimer = FPS * 2;
addTeleportStars((Entity*)world.bob);
if (world.state == WS_IN_PROGRESS)
{
playSound(SND_APPEAR, -1);
}
}
static void die(void)
{
world.bob->flags &= ~EF_WEIGHTLESS;
world.bob->flags |= EF_BOUNCES;
if (world.bob->environment == ENV_AIR)
{
world.bob->dy = -9;
}
world.bob->dx = (randF() - randF()) * 5;
switch (rand() % 3)
{
case 0:
playSound(SND_DEATH_1, world.bob->uniqueId % MAX_SND_CHANNELS);
break;
case 1:
playSound(SND_DEATH_2, world.bob->uniqueId % MAX_SND_CHANNELS);
break;
case 2:
playSound(SND_DEATH_3, world.bob->uniqueId % MAX_SND_CHANNELS);
break;
}
}
static SDL_Rect *getCurrentSprite(void)
{
Sprite *s;
s = (world.bob->alive == ALIVE_ALIVE && world.bob->stunTimer <= 0) ? world.bob->sprite[world.bob->facing] : world.bob->sprite[FACING_DIE];
{
return &world.bob->sprite[world.bob->facing]->frames[world.bob->spriteFrame]->rect;
}
if (world.bob->spriteFrame >= s->numFrames)
{
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_WARN, "WARNING: %s (%d) bad sprite frames - %d > %d\n", world.bob->name, world.bob->type, world.bob->spriteFrame, s->numFrames);
world.bob->spriteFrame = 0;
}
return &s->frames[world.bob->spriteFrame]->rect;
}
static void animate(void)
{
if (world.bob->stunTimer > 0 || world.bob->alive != ALIVE_ALIVE)
{
if (world.bob->facing != FACING_DIE)
{
oldFacing = world.bob->facing;
}
world.bob->facing = FACING_DIE;
superAnimate();
}
else if (world.bob->dx != 0 || world.bob->flags & EF_WEIGHTLESS)
{
superAnimate();
}
}
static void load(cJSON *root)
{
world.bob->x = cJSON_GetObjectItem(root, "x")->valueint;
world.bob->y = cJSON_GetObjectItem(root, "y")->valueint;
world.bob->facing = lookup(cJSON_GetObjectItem(root, "facing")->valuestring);
if (game.plus & PLUS_MIRROR)
{
world.bob->x = MAP_PIXEL_WIDTH - world.bob->x;
}
}
static void save(cJSON *root)
{
/* in case Bob was put into stun state during save */
if (world.bob->facing == FACING_DIE)
{
world.bob->facing = oldFacing;
}
cJSON_AddStringToObject(root, "type", "Bob");
cJSON_AddNumberToObject(root, "x", world.bob->checkpoints[0].x);
cJSON_AddNumberToObject(root, "y", world.bob->checkpoints[0].y);
cJSON_AddStringToObject(root, "facing", getLookupName("FACING_", world.bob->facing));
}