diff --git a/common.mk b/common.mk index 785d781..070b3ff 100644 --- a/common.mk +++ b/common.mk @@ -33,7 +33,7 @@ OBJS += atlas.o atlasTest.o aquaBlob.o OBJS += battery.o blaze.o bob.o boss.o blobBoss.o OBJS += camera.o cannon.o cardReader.o cell.o cherry.o combat.o consumable.o OBJS += debris.o destructable.o door.o draw.o -OBJS += effects.o entities.o entity.o entityFactory.o exit.o explosions.o eyeDroid.o eyeDroidCommander.o evilBlob.o +OBJS += effects.o ending.o entities.o entity.o entityFactory.o exit.o explosions.o eyeDroid.o eyeDroidCommander.o evilBlob.o OBJS += fleshChunk.o frost.o OBJS += game.o grenade.o OBJS += heart.o horizontalDoor.o horizontalLaserTrap.o hub.o hud.o diff --git a/src/defs.h b/src/defs.h index 759b403..e240328 100644 --- a/src/defs.h +++ b/src/defs.h @@ -208,8 +208,13 @@ enum enum { + WS_START, WS_IN_PROGRESS, + WS_PAUSED, + WS_GAME_COMPLETE, + WS_OBSERVING, WS_COMPLETE, + WS_MISSION_COMPLETE, WS_GAME_OVER }; diff --git a/src/entities/blobs/bob.c b/src/entities/blobs/bob.c index cd5c2af..ca46516 100644 --- a/src/entities/blobs/bob.c +++ b/src/entities/blobs/bob.c @@ -631,6 +631,10 @@ int numCarriedItems(void) return 0; } +void dropCarriedItems(void) +{ +} + static SDL_Rect *getCurrentSprite(void) { if (world.bob->alive == ALIVE_ALIVE && world.bob->stunTimer <= 0) diff --git a/src/game/ending.c b/src/game/ending.c new file mode 100644 index 0000000..b7cb20d --- /dev/null +++ b/src/game/ending.c @@ -0,0 +1,25 @@ +/* +Copyright (C) 2018 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 "ending.h" + +void initEnding(void) +{ +} diff --git a/src/game/ending.h b/src/game/ending.h new file mode 100644 index 0000000..8ad0dc9 --- /dev/null +++ b/src/game/ending.h @@ -0,0 +1,21 @@ +/* +Copyright (C) 2018 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 "../common.h" diff --git a/src/structs.h b/src/structs.h index bbbcd6d..6c3701b 100644 --- a/src/structs.h +++ b/src/structs.h @@ -58,6 +58,7 @@ typedef struct { int cheatLevels; int cheatReload; int cheatBlind; + int cheatNoEnemies; int fps; } Dev; @@ -129,7 +130,9 @@ struct Entity { int isSolid; int isStatic; int isOnGround; + int isOnScreen; int isMissionTarget; + int observationTime; SDL_Rect bounds; unsigned long flags; void (*init)(void); @@ -436,9 +439,8 @@ typedef struct { int state; int minEnemySpawnTime, maxEnemySpawnTime; unsigned long entityCounter; - Bob *bob; - Boss *boss; - Map map; + int entityChaseTimer; + int showingInfoMessage; int allObjectivesComplete; int frameCounter; int currentStatus; @@ -448,8 +450,18 @@ typedef struct { int isOutpostMission; int isReturnVisit; int missionCompleteTimer; + int observationTimer; + int gameOverTimer; int betweenTimer; int mapAnimTimer; + int enemySpawnTimer; + int helperItemTimer; + int spawnInterval; + int numToSpawn; + Bob *bob; + Boss *boss; + Entity *entityToTrack; + Map map; Quadtree quadtree; Entity entityHead, *entityTail; Particle particleHead, *particleTail; diff --git a/src/system/widgets.c b/src/system/widgets.c index e036978..c61bdf6 100644 --- a/src/system/widgets.c +++ b/src/system/widgets.c @@ -96,7 +96,7 @@ Widget *getWidgetAt(int x, int y) return NULL; } -void hideAll(void) +void hideAllWidgets(void) { Widget *w; @@ -112,7 +112,7 @@ void showWidgetGroup(char *group) { Widget *w; - hideAll(); + hideAllWidgets(); for (w = widgetHead.next ; w != NULL ; w = w->next) { diff --git a/src/world/entities.c b/src/world/entities.c index b4c3cef..1a79426 100644 --- a/src/world/entities.c +++ b/src/world/entities.c @@ -114,3 +114,23 @@ Entity *findEntity(char *name) return NULL; } + +Entity *getRandomObjectiveEntity(void) +{ + Entity *rtn, *e; + + rtn = (Entity*)world.bob; + + for (e = world.entityHead.next ; e != NULL ; e = e->next) + { + if (e->isMissionTarget) + { + if (rand() % 4 == 0) + { + rtn = e; + } + } + } + + return rtn; +} diff --git a/src/world/hud.c b/src/world/hud.c index 1bfd622..616414c 100644 --- a/src/world/hud.c +++ b/src/world/hud.c @@ -32,7 +32,7 @@ void initHud(void) messageType = MSG_STANDARD; } -void doLogic(void) +void doHud(void) { messageTime--; diff --git a/src/world/world.c b/src/world/world.c index acd22f6..809844d 100644 --- a/src/world/world.c +++ b/src/world/world.c @@ -20,21 +20,418 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "world.h" +static void doWorldStart(void); +static void doWorldInProgress(void); +static void doWorldObserving(void); +static void doWorldPaused(void); +static void doWorldComplete(void); +static void doGameComplete(void); +static void doGameOver(void); +static void doCommon(void); +static void addHelperItems(void); +static void spawnEnemies(void); +static int canAdd(Unit *u, int mx, int my); +void startMission(void); + static Texture *background; +static Entity* entitiesToObserve[MAX_ENTS_TO_OBSERVE]; void initWorld(void) { background = getTexture(world.background); + + initObjectives(); + + world.enemySpawnTimer = (FPS * rrnd(world.minEnemySpawnTime, world.maxEnemySpawnTime)); + + world.state = WS_START; + + if (world.isBossMission) + { + startMission(); + hideAllWidgets(); + world.betweenTimer = 0; + } + + if (!game.isResumingMission) + { + game.missionsPlayed++; + } } void doWorld(void) { + if (world.betweenTimer == 0) + { + switch (world.state) + { + case WS_START: + doWorldStart(); + break; + case WS_IN_PROGRESS: + doWorldInProgress(); + break; + case WS_OBSERVING: + doWorldObserving(); + break; + case WS_PAUSED: + doWorldPaused(); + break; + case WS_COMPLETE: + doWorldComplete(); + break; + case WS_GAME_OVER: + doGameOver(); + break; + case WS_GAME_COMPLETE: + doGameComplete(); + break; + default: + break; + } + } + if (--world.mapAnimTimer < 0) { world.mapAnimTimer = 4; } } +void startMission(void) +{ + world.state = WS_IN_PROGRESS; + world.betweenTimer = FPS / 2; + resetAtCheckpoint(); + world.entityToTrack = (Entity*)world.bob; +} + +static void doWorldStart(void) +{ + if (world.entityToTrack == NULL) + { + world.entityToTrack = getRandomObjectiveEntity(); + + cameraTrack(world.entityToTrack); + } + + world.entityChaseTimer = MAX(world.entityChaseTimer - 1, 0); + + doCommon(); +} + +static void doWorldInProgress(void) +{ + if (!world.showingInfoMessage) + { + doBob(); + + doCommon(); + + doLocationTriggers(); + + if (world.allObjectivesComplete && world.state != WS_COMPLETE) + { + world.bob->flags |= EF_IMMUNE; + + if (strcmp(world.id, "teeka") == 0) + { + world.state = WS_GAME_COMPLETE; + } + else + { + world.state = WS_COMPLETE; + } + + world.missionCompleteTimer = FPS * 3; + + stopMusic(); + } + } + + if (world.observationTimer > 0) + { + world.observationTimer--; + + if (world.observationTimer == FPS * 1.5) + { + world.entityToTrack = entitiesToObserve[0]; + + world.state = WS_OBSERVING; + } + } +} + +static void doWorldObserving(void) +{ + int i; + + world.observationTimer--; + + if (world.observationTimer == 0) + { + for (i = 0 ; i < MAX_ENTS_TO_OBSERVE ; i++) + { + entitiesToObserve[i]->observationTime = SDL_GetTicks() + 5000; + } + + memset(entitiesToObserve, 0, sizeof(Entity*) * MAX_ENTS_TO_OBSERVE); + world.entityToTrack = (Entity*)world.bob; + world.state = WS_IN_PROGRESS; + } +} + +static void doWorldPaused(void) +{ + animateSprites(); +} + +static void doWorldComplete(void) +{ + world.missionCompleteTimer--; + + if (world.missionCompleteTimer <= 0) + { + + } + else if (world.missionCompleteTimer == FPS * 1.5) + { + dropCarriedItems(); + world.bob->flags |= EF_GONE; + addTeleportStars((Entity*)world.bob); + playSound(SND_TELEPORT, CH_BOB); + } + else + { + doBob(); + } + + doCommon(); +} + +static void doGameComplete(void) +{ + world.missionCompleteTimer--; + + if (world.missionCompleteTimer <= 0) + { + initEnding(); + } + else + { + doBob(); + } + + doCommon(); +} + +static void doGameOver(void) +{ + world.gameOverTimer--; + + doCommon(); + + if (world.gameOverTimer <= -(FPS * 5)) + { + initTitle(); + } +} + +static void doCommon(void) +{ + animateSprites(); + + world.frameCounter++; + world.frameCounter %= 4096; + + doHud(); + + if (world.minEnemySpawnTime > 0 && !dev.cheatNoEnemies) + { + spawnEnemies(); + } + + if (world.isBossMission && --world.helperItemTimer <= 0) + { + addHelperItems(); + } + + doEntities(); + + doParticles(); +} + +static void addHelperItems(void) +{ + int x, y, w, h; + + w = world.map.bounds.w - world.map.bounds.x; + h = world.map.bounds.h - world.map.bounds.y; + + x = world.map.bounds.x + (rand() % w); + y = world.map.bounds.y + 1; + + if (world.map.data[x / MAP_TILE_SIZE][y / MAP_TILE_SIZE] == 0) + { + dropRandomCherry(x, y); + } + + x = world.map.bounds.x + (rand() % w); + y = world.map.bounds.y + (rand() % h); + + if (world.map.data[x / MAP_TILE_SIZE][y / MAP_TILE_SIZE] == 0) + { + addRandomWeapon(x, y); + } + + world.helperItemTimer = FPS * rrnd(3, 5); +} + +static void spawnEnemies(void) +{ + char name[MAX_NAME_LENGTH]; + int r, x, y; + Unit *u; + + if (world.numToSpawn == 0) + { + if (--world.enemySpawnTimer <= 0) + { + world.numToSpawn = 3 + (rand() % 3); + world.spawnInterval = 0; + } + return; + } + + if (--world.spawnInterval <= 0) + { + r = (rand() % world.numEnemyTypes); + + x = world.bob->x; + x += ((randF() - randF()) * 5) * MAP_TILE_SIZE; + + y = world.bob->y; + y += ((randF() - randF()) * 5) * MAP_TILE_SIZE; + + if (x >= world.map.bounds.x && y >= world.map.bounds.y && x < world.map.bounds.w + SCREEN_WIDTH - 64 && y < world.map.bounds.h + SCREEN_HEIGHT - 64) + { + sprintf(name, "%s%s", world.enemyTypes[r], (rand() % 2 ? "Blob" : "Droid")); + + u = (Unit*) createEntity(name); + + u->animate(); + u->setSize(); + + x /= MAP_TILE_SIZE; + y /= MAP_TILE_SIZE; + + if (canAdd(u, x, y)) + { + u->x = x * MAP_TILE_SIZE; + u->y = y * MAP_TILE_SIZE; + + u->spawnedIn = 1; + u->canCarryItem = 0; + addTeleportStars((Entity*)u); + playSound(SND_APPEAR, CH_ANY); + } + } + + world.spawnInterval = rrnd(FPS / 4, FPS / 2); + + if (--world.numToSpawn <= 0) + { + world.enemySpawnTimer = (FPS * rrnd(world.minEnemySpawnTime, world.maxEnemySpawnTime)); + } + } +} + +static int canAdd(Unit *u, int mx, int my) +{ + int i; + + if (isSolid(mx, my)) + { + return 0; + } + + if (isLiquid(mx, my) && (!(u->flags & EF_WATER_BREATHING))) + { + return 0; + } + + if (!(u->flags & EF_WEIGHTLESS)) + { + for (i = 0 ; i < 10 ; i++) + { + if (isLiquid(mx, my + i)) + { + return 0; + } + + if (isWalkable(mx, my + i)) + { + return 1; + } + } + + /* long drop */ + return 0; + } + + return 1; +} + +int getMissionStatus(void) +{ + Objective *o; + Entity *e; + int status; + + status = MS_COMPLETE; + + for (o = world.objectiveHead.next ; o != NULL ; o = o->next) + { + if (o->required && o->currentValue < o->targetValue) + { + return MS_INCOMPLETE; + } + + if (o->currentValue < o->totalValue) + { + status = MS_PARTIAL; + } + } + + if (status == MS_COMPLETE) + { + for (e = world.entityHead.next ; e != NULL ; e = e->next) + { + if (e->type == ET_HEART_CELL) + { + return MS_MISSING_HEART_CELL; + } + } + } + + return status; +} + void observeActivation(Entity *e) { + int i; + + if (e->observationTime < SDL_GetTicks() && !e->isOnScreen) + { + for (i = 0 ; i < MAX_ENTS_TO_OBSERVE ; i++) + { + if (entitiesToObserve[i] == NULL) + { + entitiesToObserve[i] = e; + world.observationTimer = FPS * 2; + } + } + } + + SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_ERROR, "Can't observe entity - out of array space"); + exit(1); } diff --git a/src/world/world.h b/src/world/world.h index 47828c9..1c86d35 100644 --- a/src/world/world.h +++ b/src/world/world.h @@ -20,6 +20,35 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "../common.h" -extern Texture *getTexture(const char *filename); +#define MAX_ENTS_TO_OBSERVE 12 +extern Texture *getTexture(const char *filename); +extern void initObjectives(void); +extern Entity *getRandomObjectiveEntity(void); +extern void doBob(void); +extern void doLocationTriggers(void); +extern void dropCarriedItems(void); +extern void playSound(int snd, int ch); +extern void initEnding(void); +extern void initTitle(void); +extern int rrnd(int low, int high); +extern void hideAllWidgets(void); +extern void resetAtCheckpoint(void); +extern void cameraTrack(Entity *e); +extern void stopMusic(void); +extern void animateSprites(void); +extern void addTeleportStars(Entity *e); +extern double randF(void); +extern int isSolid(int x, int y); +extern int isLiquid(int x, int y); +extern int isWalkable(int x, int y); +extern void doEntities(void); +extern void doParticles(void); +extern void doHud(void); +extern Entity *createEntity(char *typeStr); +extern void dropRandomCherry(int x, int y); +extern void addRandomWeapon(int x, int y); + +extern Dev dev; +extern Game game; extern World world;