2016-11-09 11:36:41 +00:00
|
|
|
#include "Dengine/Asteroid.h"
|
|
|
|
#include "Dengine/Debug.h"
|
|
|
|
|
2016-11-27 09:13:33 +00:00
|
|
|
INTERNAL void loadGameAssets(GameState *state)
|
2016-11-09 11:36:41 +00:00
|
|
|
{
|
|
|
|
AssetManager *assetManager = &state->assetManager;
|
|
|
|
MemoryArena_ *arena = &state->persistentArena;
|
|
|
|
|
2016-11-27 09:13:33 +00:00
|
|
|
{ // Init font assets
|
2016-11-28 05:11:53 +00:00
|
|
|
#if 0
|
2016-11-27 06:26:23 +00:00
|
|
|
i32 result =
|
2016-11-27 10:16:12 +00:00
|
|
|
asset_fontLoadTTF(assetManager, arena, &state->transientArena,
|
2016-11-27 07:23:43 +00:00
|
|
|
"C:/Windows/Fonts/Arialbd.ttf", "Arial", 15);
|
2016-11-28 05:11:53 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
asset_fontLoadTTF(assetManager, arena, &state->transientArena,
|
|
|
|
"F:/Workspace/Dropbox/Apps/Fonts/"
|
|
|
|
"league-spartan-master/_webfonts/"
|
|
|
|
"leaguespartan-bold.ttf",
|
|
|
|
"Arial", 15);
|
2016-11-24 11:54:07 +00:00
|
|
|
}
|
2016-11-09 11:36:41 +00:00
|
|
|
|
2016-11-24 11:54:07 +00:00
|
|
|
{ // Init shaders assets
|
2016-11-27 10:16:12 +00:00
|
|
|
asset_shaderLoad(
|
2016-11-24 11:54:07 +00:00
|
|
|
assetManager, arena, "data/shaders/default_tex.vert.glsl",
|
|
|
|
"data/shaders/default_tex.frag.glsl", shaderlist_default);
|
|
|
|
|
2016-11-27 10:16:12 +00:00
|
|
|
asset_shaderLoad(
|
2016-11-24 11:54:07 +00:00
|
|
|
assetManager, arena, "data/shaders/default_no_tex.vert.glsl",
|
|
|
|
"data/shaders/default_no_tex.frag.glsl", shaderlist_default_no_tex);
|
|
|
|
}
|
|
|
|
|
|
|
|
{ // Init audio assets
|
2016-11-27 10:16:12 +00:00
|
|
|
i32 result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/bang_large.ogg",
|
|
|
|
"bang_large");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/bang_medium.ogg",
|
|
|
|
"bang_medium");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/bang_small.ogg",
|
|
|
|
"bang_small");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/beat1.ogg", "beat1");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/beat2.ogg", "beat2");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/extra_ship.ogg",
|
|
|
|
"extra_ship");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/fire.ogg", "fire");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/saucer_big.ogg",
|
|
|
|
"saucer_big");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/saucer_small.ogg",
|
|
|
|
"saucer_small");
|
|
|
|
ASSERT(!result);
|
2016-11-27 10:16:12 +00:00
|
|
|
result = asset_vorbisLoad(assetManager, arena,
|
2016-11-24 11:54:07 +00:00
|
|
|
"data/audio/Asteroids/thrust.ogg", "thrust");
|
|
|
|
ASSERT(!result);
|
|
|
|
}
|
2016-11-09 11:36:41 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 13:50:49 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <time.h>
|
2016-11-28 05:11:53 +00:00
|
|
|
INTERNAL v2 *createAsteroidVertexList(MemoryArena_ *arena, i32 iterations,
|
|
|
|
i32 asteroidRadius)
|
2016-11-16 13:50:49 +00:00
|
|
|
{
|
|
|
|
f32 iterationAngle = 360.0f / iterations;
|
|
|
|
iterationAngle = DEGREES_TO_RADIANS(iterationAngle);
|
2016-11-23 12:47:23 +00:00
|
|
|
v2 *result = memory_pushBytes(arena, iterations * sizeof(v2));
|
2016-11-16 13:50:49 +00:00
|
|
|
|
|
|
|
for (i32 i = 0; i < iterations; i++)
|
|
|
|
{
|
2016-11-17 07:15:36 +00:00
|
|
|
i32 randValue = rand();
|
2016-11-28 14:41:33 +00:00
|
|
|
|
2016-11-19 05:42:13 +00:00
|
|
|
// NOTE(doyle): Sin/cos generate values from +-1, we want to create
|
|
|
|
// vertices that start from 0, 0 (i.e. strictly positive)
|
|
|
|
result[i] = V2(((math_cosf(iterationAngle * i) + 1) * asteroidRadius),
|
|
|
|
((math_sinf(iterationAngle * i) + 1) * asteroidRadius));
|
2016-11-16 13:50:49 +00:00
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
ASSERT(result[i].x >= 0 && result[i].y >= 0);
|
|
|
|
|
2016-11-16 13:50:49 +00:00
|
|
|
#if 1
|
2016-11-28 14:41:33 +00:00
|
|
|
f32 displacementDist = 0.50f * asteroidRadius;
|
2016-11-16 13:50:49 +00:00
|
|
|
i32 vertexDisplacement =
|
2016-11-17 07:15:36 +00:00
|
|
|
randValue % (i32)displacementDist + (i32)(displacementDist * 0.25f);
|
2016-11-16 13:50:49 +00:00
|
|
|
|
|
|
|
i32 quadrantSize = iterations / 4;
|
|
|
|
|
|
|
|
i32 firstQuadrant = quadrantSize;
|
|
|
|
i32 secondQuadrant = quadrantSize * 2;
|
|
|
|
i32 thirdQuadrant = quadrantSize * 3;
|
|
|
|
i32 fourthQuadrant = quadrantSize * 4;
|
|
|
|
|
|
|
|
if (i < firstQuadrant)
|
|
|
|
{
|
|
|
|
result[i].x += vertexDisplacement;
|
|
|
|
result[i].y += vertexDisplacement;
|
|
|
|
}
|
|
|
|
else if (i < secondQuadrant)
|
|
|
|
{
|
|
|
|
result[i].x -= vertexDisplacement;
|
|
|
|
result[i].y += vertexDisplacement;
|
|
|
|
}
|
|
|
|
else if (i < thirdQuadrant)
|
|
|
|
{
|
|
|
|
result[i].x -= vertexDisplacement;
|
|
|
|
result[i].y -= vertexDisplacement;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
result[i].x += vertexDisplacement;
|
|
|
|
result[i].y -= vertexDisplacement;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
INTERNAL v2 *createNormalEdgeList(MemoryArena_ *transientArena, v2 *vertexList,
|
|
|
|
i32 vertexListSize)
|
2016-11-18 06:42:11 +00:00
|
|
|
{
|
2016-11-23 02:42:46 +00:00
|
|
|
v2 *result = memory_pushBytes(transientArena, sizeof(v2) * vertexListSize);
|
|
|
|
for (i32 i = 0; i < vertexListSize - 1; i++)
|
2016-11-18 06:42:11 +00:00
|
|
|
{
|
2016-11-23 02:42:46 +00:00
|
|
|
ASSERT((i + 1) < vertexListSize);
|
|
|
|
result[i] = v2_sub(vertexList[i + 1], vertexList[i]);
|
|
|
|
result[i] = v2_perpendicular(result[i]);
|
2016-11-18 06:42:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE(doyle): Creating the last edge requires using the first
|
|
|
|
// vertex point which is at index 0
|
2016-11-23 02:42:46 +00:00
|
|
|
result[vertexListSize - 1] =
|
|
|
|
v2_sub(vertexList[0], vertexList[vertexListSize - 1]);
|
|
|
|
result[vertexListSize - 1] = v2_perpendicular(result[vertexListSize - 1]);
|
2016-11-18 06:42:11 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
INTERNAL v2 calculateProjectionRangeForEdge(v2 *vertexList, i32 vertexListSize,
|
|
|
|
v2 edgeNormal)
|
2016-11-19 05:42:13 +00:00
|
|
|
{
|
2016-11-28 05:11:53 +00:00
|
|
|
v2 result = {0};
|
2016-11-23 02:42:46 +00:00
|
|
|
result.min = v2_dot(vertexList[0], edgeNormal);
|
2016-11-19 05:42:13 +00:00
|
|
|
result.max = result.min;
|
|
|
|
|
2016-11-23 02:42:46 +00:00
|
|
|
for (i32 vertexIndex = 0; vertexIndex < vertexListSize; vertexIndex++)
|
2016-11-19 05:42:13 +00:00
|
|
|
{
|
2016-11-23 02:42:46 +00:00
|
|
|
f32 dist = v2_dot(vertexList[vertexIndex], edgeNormal);
|
2016-11-19 05:42:13 +00:00
|
|
|
|
|
|
|
if (dist < result.min)
|
|
|
|
result.min = dist;
|
|
|
|
else if (dist > result.max)
|
|
|
|
result.max = dist;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
INTERNAL b32 checkEdgeProjectionOverlap(v2 *vertexList, i32 listSize,
|
|
|
|
v2 *checkVertexList, i32 checkListSize,
|
|
|
|
v2 *edgeList, i32 totalNumEdges)
|
2016-11-18 06:42:11 +00:00
|
|
|
{
|
|
|
|
b32 result = TRUE;
|
2016-11-23 02:42:46 +00:00
|
|
|
for (i32 edgeIndex = 0; edgeIndex < totalNumEdges && result; edgeIndex++)
|
2016-11-18 06:42:11 +00:00
|
|
|
{
|
2016-11-23 02:42:46 +00:00
|
|
|
v2 projectionRange = calculateProjectionRangeForEdge(
|
|
|
|
vertexList, listSize, edgeList[edgeIndex]);
|
|
|
|
|
|
|
|
v2 checkProjectionRange = calculateProjectionRangeForEdge(
|
|
|
|
checkVertexList, checkListSize, edgeList[edgeIndex]);
|
2016-11-18 06:42:11 +00:00
|
|
|
|
2016-11-23 02:42:46 +00:00
|
|
|
if (!v2_intervalsOverlap(projectionRange, checkProjectionRange))
|
2016-11-18 06:42:11 +00:00
|
|
|
{
|
|
|
|
result = FALSE;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL u32 moveEntity(GameWorldState *world, MemoryArena_ *transientArena,
|
2016-11-24 07:57:44 +00:00
|
|
|
Entity *entity, i32 entityIndex, v2 ddP, f32 dt,
|
|
|
|
f32 ddPSpeed)
|
2016-11-18 02:43:50 +00:00
|
|
|
{
|
|
|
|
ASSERT(ABS(ddP.x) <= 1.0f && ABS(ddP.y) <= 1.0f);
|
|
|
|
/*
|
|
|
|
Assuming acceleration A over t time, then integrate twice to get
|
|
|
|
|
|
|
|
newVelocity = a*t + oldVelocity
|
|
|
|
newPos = (a*t^2)/2 + oldVelocity*t + oldPos
|
|
|
|
*/
|
|
|
|
|
2016-11-25 02:17:24 +00:00
|
|
|
if (ddP.x > 0.0f && ddP.y > 0.0f)
|
|
|
|
{
|
|
|
|
// NOTE(doyle): Cheese it and pre-compute the vector for
|
|
|
|
// diagonal using pythagoras theorem on a unit triangle 1^2
|
|
|
|
// + 1^2 = c^2
|
|
|
|
ddP = v2_scale(ddP, 0.70710678118f);
|
|
|
|
}
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
ddP = v2_scale(ddP, world->pixelsPerMeter * ddPSpeed);
|
2016-11-18 02:43:50 +00:00
|
|
|
v2 oldDp = entity->dP;
|
|
|
|
v2 resistance = v2_scale(oldDp, 2.0f);
|
|
|
|
ddP = v2_sub(ddP, resistance);
|
|
|
|
|
|
|
|
v2 newDp = v2_add(v2_scale(ddP, dt), oldDp);
|
|
|
|
|
|
|
|
v2 ddPHalf = v2_scale(ddP, 0.5f);
|
|
|
|
v2 ddPHalfDtSquared = v2_scale(ddPHalf, (SQUARED(dt)));
|
|
|
|
v2 oldDpDt = v2_scale(oldDp, dt);
|
|
|
|
v2 oldPos = entity->pos;
|
|
|
|
|
|
|
|
v2 newPos = v2_add(v2_add(ddPHalfDtSquared, oldDpDt), oldPos);
|
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
i32 collisionIndex = -1;
|
2016-11-23 12:47:23 +00:00
|
|
|
// TODO(doyle): Collision for rects, (need to create vertex list for it)
|
2016-11-24 10:05:14 +00:00
|
|
|
for (i32 i = 1; i < world->entityIndex; i++)
|
2016-11-18 02:43:50 +00:00
|
|
|
{
|
2016-11-24 10:05:14 +00:00
|
|
|
if (i == entityIndex) continue;
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
Entity *checkEntity = &world->entityList[i];
|
|
|
|
ASSERT(checkEntity->id != entity->id);
|
2016-11-18 06:42:11 +00:00
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
if (world->collisionTable[entity->type][checkEntity->type])
|
|
|
|
{
|
|
|
|
ASSERT(entity->vertexPoints);
|
|
|
|
ASSERT(checkEntity->vertexPoints);
|
|
|
|
|
|
|
|
/* Create entity edge lists */
|
2016-11-28 14:41:33 +00:00
|
|
|
v2 *entityVertexListOffsetToP =
|
|
|
|
entity_generateUpdatedVertexList(transientArena, entity);
|
2016-11-24 07:57:44 +00:00
|
|
|
|
|
|
|
v2 *checkEntityVertexListOffsetToP =
|
2016-11-28 14:41:33 +00:00
|
|
|
entity_generateUpdatedVertexList(transientArena, checkEntity);
|
2016-11-24 07:57:44 +00:00
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
v2 *entityEdgeList =
|
|
|
|
createNormalEdgeList(transientArena, entityVertexListOffsetToP,
|
|
|
|
entity->numVertexPoints);
|
2016-11-24 07:57:44 +00:00
|
|
|
|
|
|
|
v2 *checkEntityEdgeList = createNormalEdgeList(
|
|
|
|
transientArena, checkEntityVertexListOffsetToP,
|
|
|
|
checkEntity->numVertexPoints);
|
|
|
|
|
|
|
|
/* Combine both edge lists into one */
|
|
|
|
i32 totalNumEdges =
|
|
|
|
checkEntity->numVertexPoints + entity->numVertexPoints;
|
|
|
|
v2 *edgeList =
|
|
|
|
memory_pushBytes(transientArena, totalNumEdges * sizeof(v2));
|
|
|
|
for (i32 i = 0; i < entity->numVertexPoints; i++)
|
2016-11-18 06:42:11 +00:00
|
|
|
{
|
2016-11-24 07:57:44 +00:00
|
|
|
edgeList[i] = entityEdgeList[i];
|
|
|
|
}
|
2016-11-18 06:42:11 +00:00
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
for (i32 i = 0; i < checkEntity->numVertexPoints; i++)
|
|
|
|
{
|
|
|
|
edgeList[i + entity->numVertexPoints] = checkEntityEdgeList[i];
|
2016-11-18 06:42:11 +00:00
|
|
|
}
|
2016-11-18 02:43:50 +00:00
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
if (checkEdgeProjectionOverlap(
|
|
|
|
entityVertexListOffsetToP, entity->numVertexPoints,
|
|
|
|
checkEntityVertexListOffsetToP,
|
|
|
|
checkEntity->numVertexPoints, edgeList, totalNumEdges))
|
|
|
|
{
|
2016-11-24 10:05:14 +00:00
|
|
|
collisionIndex = i;
|
2016-11-19 05:42:13 +00:00
|
|
|
}
|
2016-11-18 06:42:11 +00:00
|
|
|
}
|
2016-11-24 07:57:44 +00:00
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
if (collisionIndex != -1) break;
|
2016-11-23 02:42:46 +00:00
|
|
|
}
|
|
|
|
|
2016-11-23 12:47:23 +00:00
|
|
|
entity->dP = newDp;
|
|
|
|
entity->pos = newPos;
|
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
return collisionIndex;
|
2016-11-18 02:43:50 +00:00
|
|
|
}
|
|
|
|
|
2016-11-25 02:17:24 +00:00
|
|
|
enum AsteroidSize
|
|
|
|
{
|
|
|
|
asteroidsize_small,
|
|
|
|
asteroidsize_medium,
|
|
|
|
asteroidsize_large,
|
|
|
|
asteroidsize_count,
|
|
|
|
};
|
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
2016-11-25 02:17:24 +00:00
|
|
|
v2 pos;
|
|
|
|
v2 dP;
|
|
|
|
} AsteroidSpec;
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL void addAsteroidWithSpec(GameWorldState *world,
|
|
|
|
enum AsteroidSize asteroidSize,
|
2016-11-25 02:17:24 +00:00
|
|
|
AsteroidSpec *spec)
|
2016-11-23 13:27:24 +00:00
|
|
|
{
|
2016-11-24 10:05:14 +00:00
|
|
|
world->asteroidCounter++;
|
|
|
|
|
2016-11-25 02:17:24 +00:00
|
|
|
enum EntityType type;
|
|
|
|
v2 size;
|
|
|
|
v2 **vertexCache = NULL;
|
|
|
|
|
|
|
|
if (asteroidSize == asteroidsize_small)
|
|
|
|
{
|
|
|
|
size = V2i(25, 25);
|
|
|
|
type = entitytype_asteroid_small;
|
|
|
|
vertexCache = world->asteroidSmallVertexCache;
|
|
|
|
}
|
|
|
|
else if (asteroidSize == asteroidsize_medium)
|
|
|
|
{
|
|
|
|
size = V2i(50, 50);
|
|
|
|
type = entitytype_asteroid_medium;
|
|
|
|
vertexCache = world->asteroidMediumVertexCache;
|
|
|
|
}
|
|
|
|
else if (asteroidSize == asteroidsize_large)
|
|
|
|
{
|
|
|
|
type = entitytype_asteroid_large;
|
|
|
|
size = V2i(100, 100);
|
|
|
|
vertexCache = world->asteroidLargeVertexCache;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ASSERT(INVALID_CODE_PATH);
|
|
|
|
}
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
Entity *asteroid = &world->entityList[world->entityIndex++];
|
|
|
|
asteroid->id = world->entityIdCounter++;
|
2016-11-23 13:27:24 +00:00
|
|
|
|
|
|
|
i32 randValue = rand();
|
2016-11-25 02:17:24 +00:00
|
|
|
if (!spec)
|
|
|
|
{
|
2016-11-28 08:11:38 +00:00
|
|
|
i32 randX = (randValue % (i32)world->size.w);
|
|
|
|
i32 randY = (randValue % (i32)world->size.h);
|
2016-11-25 02:17:24 +00:00
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
v2 midpoint = v2_scale(world->size, 0.5f);
|
2016-11-25 02:17:24 +00:00
|
|
|
|
|
|
|
Rect topLeftQuadrant = {V2(0, midpoint.y),
|
2016-11-28 08:11:38 +00:00
|
|
|
V2(midpoint.x, world->size.y)};
|
2016-11-25 02:17:24 +00:00
|
|
|
Rect botLeftQuadrant = {V2(0, 0), midpoint};
|
2016-11-28 08:11:38 +00:00
|
|
|
Rect topRightQuadrant = {midpoint, world->size};
|
2016-11-25 02:17:24 +00:00
|
|
|
Rect botRightQuadrant = {V2(midpoint.x, 0),
|
2016-11-28 08:11:38 +00:00
|
|
|
V2(world->size.x, midpoint.y)};
|
2016-11-25 02:17:24 +00:00
|
|
|
|
|
|
|
// NOTE(doyle): Off-screen so asteroids "float" into view. There's no
|
|
|
|
// particular order, just pushing things offscreen when they get
|
|
|
|
// generated
|
|
|
|
// to float back into game space
|
|
|
|
v2 newP = V2i(randX, randY);
|
2016-11-27 10:16:12 +00:00
|
|
|
if (math_rectContainsP(topLeftQuadrant, newP))
|
2016-11-25 02:17:24 +00:00
|
|
|
{
|
|
|
|
newP.y += midpoint.y;
|
|
|
|
}
|
2016-11-27 10:16:12 +00:00
|
|
|
else if (math_rectContainsP(botLeftQuadrant, newP))
|
2016-11-25 02:17:24 +00:00
|
|
|
{
|
|
|
|
newP.x -= midpoint.x;
|
|
|
|
}
|
2016-11-27 10:16:12 +00:00
|
|
|
else if (math_rectContainsP(topRightQuadrant, newP))
|
2016-11-25 02:17:24 +00:00
|
|
|
{
|
|
|
|
newP.y -= midpoint.y;
|
|
|
|
}
|
2016-11-27 10:16:12 +00:00
|
|
|
else if (math_rectContainsP(botRightQuadrant, newP))
|
2016-11-25 02:17:24 +00:00
|
|
|
{
|
|
|
|
newP.x += midpoint.x;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ASSERT(INVALID_CODE_PATH);
|
|
|
|
}
|
2016-11-28 14:41:33 +00:00
|
|
|
asteroid->pos = newP;
|
2016-11-25 02:17:24 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-11-28 14:41:33 +00:00
|
|
|
asteroid->pos = spec->pos;
|
|
|
|
asteroid->dP = spec->dP;
|
2016-11-25 02:17:24 +00:00
|
|
|
}
|
2016-11-23 13:27:24 +00:00
|
|
|
|
2016-11-25 02:17:24 +00:00
|
|
|
asteroid->size = size;
|
|
|
|
asteroid->hitbox = asteroid->size;
|
|
|
|
asteroid->offset = v2_scale(asteroid->size, -0.5f);
|
|
|
|
asteroid->type = type;
|
|
|
|
asteroid->renderMode = rendermode_polygon;
|
2016-11-23 13:27:24 +00:00
|
|
|
asteroid->numVertexPoints = 10;
|
|
|
|
|
2016-11-25 02:17:24 +00:00
|
|
|
i32 cacheIndex = randValue % ARRAY_COUNT(world->asteroidSmallVertexCache);
|
|
|
|
ASSERT(ARRAY_COUNT(world->asteroidSmallVertexCache) ==
|
|
|
|
ARRAY_COUNT(world->asteroidMediumVertexCache));
|
|
|
|
ASSERT(ARRAY_COUNT(world->asteroidSmallVertexCache) ==
|
|
|
|
ARRAY_COUNT(world->asteroidLargeVertexCache));
|
|
|
|
|
|
|
|
if (!vertexCache[cacheIndex])
|
2016-11-24 10:05:14 +00:00
|
|
|
{
|
2016-11-25 02:17:24 +00:00
|
|
|
vertexCache[cacheIndex] = createAsteroidVertexList(
|
2016-11-24 10:05:14 +00:00
|
|
|
&world->entityArena, asteroid->numVertexPoints,
|
|
|
|
(i32)(asteroid->size.w * 0.5f));
|
|
|
|
}
|
|
|
|
|
2016-11-25 02:17:24 +00:00
|
|
|
asteroid->vertexPoints = vertexCache[cacheIndex];
|
2016-11-28 14:41:33 +00:00
|
|
|
asteroid->color = V4(1.0f, 1.0f, 1.0f, 1.0f);
|
2016-11-24 07:57:44 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL void addAsteroid(GameWorldState *world, enum AsteroidSize asteroidSize)
|
2016-11-25 02:17:24 +00:00
|
|
|
{
|
|
|
|
addAsteroidWithSpec(world, asteroidSize, NULL);
|
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL void addBullet(GameWorldState *world, Entity *shooter)
|
2016-11-24 07:57:44 +00:00
|
|
|
{
|
|
|
|
Entity *bullet = &world->entityList[world->entityIndex++];
|
|
|
|
bullet->id = world->entityIdCounter++;
|
|
|
|
|
2016-11-25 09:43:43 +00:00
|
|
|
bullet->pos = shooter->pos;
|
|
|
|
bullet->size = V2(2.0f, 20.0f);
|
2016-11-24 07:57:44 +00:00
|
|
|
bullet->offset = v2_scale(bullet->size, -0.5f);
|
|
|
|
bullet->hitbox = bullet->size;
|
|
|
|
bullet->rotation = shooter->rotation;
|
2016-11-25 09:43:43 +00:00
|
|
|
bullet->renderMode = rendermode_polygon;
|
2016-11-24 07:57:44 +00:00
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
if (!world->bulletVertexCache)
|
|
|
|
{
|
|
|
|
world->bulletVertexCache =
|
2016-11-25 09:43:43 +00:00
|
|
|
MEMORY_PUSH_ARRAY(&world->entityArena, 4, v2);
|
2016-11-24 10:05:14 +00:00
|
|
|
world->bulletVertexCache[0] = V2(0, bullet->size.h);
|
|
|
|
world->bulletVertexCache[1] = V2(0, 0);
|
|
|
|
world->bulletVertexCache[2] = V2(bullet->size.w, 0);
|
|
|
|
world->bulletVertexCache[3] = bullet->size;
|
|
|
|
}
|
2016-11-24 07:57:44 +00:00
|
|
|
|
2016-11-25 09:43:43 +00:00
|
|
|
bullet->vertexPoints = world->bulletVertexCache;
|
2016-11-24 07:57:44 +00:00
|
|
|
bullet->numVertexPoints = 4;
|
|
|
|
|
|
|
|
bullet->type = entitytype_bullet;
|
|
|
|
bullet->color = V4(1.0f, 1.0f, 0, 1.0f);
|
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL void setCollisionRule(GameWorldState *world, enum EntityType a,
|
2016-11-24 07:57:44 +00:00
|
|
|
enum EntityType b, b32 rule)
|
|
|
|
{
|
|
|
|
ASSERT(a <= entitytype_count);
|
|
|
|
ASSERT(b <= entitytype_count);
|
|
|
|
world->collisionTable[a][b] = rule;
|
|
|
|
world->collisionTable[b][a] = rule;
|
2016-11-23 13:27:24 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL AudioRenderer *getFreeAudioRenderer(GameWorldState *world,
|
|
|
|
AudioVorbis *vorbis,
|
2016-11-28 05:11:53 +00:00
|
|
|
i32 maxSimultaneousPlayers)
|
2016-11-24 11:54:07 +00:00
|
|
|
{
|
2016-11-28 14:41:33 +00:00
|
|
|
i32 freeIndex = -1;
|
2016-11-28 05:11:53 +00:00
|
|
|
i32 sameAudioPlayingCount = 0;
|
|
|
|
|
|
|
|
AudioRenderer *result = NULL;
|
2016-11-24 11:54:07 +00:00
|
|
|
for (i32 i = 0; i < world->numAudioRenderers; i++)
|
|
|
|
{
|
|
|
|
AudioRenderer *renderer = &world->audioRenderer[i];
|
2016-11-28 05:11:53 +00:00
|
|
|
if (renderer->state == audiostate_playing &&
|
|
|
|
common_strcmp(renderer->audio->key, vorbis->key) == 0)
|
2016-11-24 11:54:07 +00:00
|
|
|
{
|
2016-11-28 05:11:53 +00:00
|
|
|
sameAudioPlayingCount++;
|
2016-11-24 11:54:07 +00:00
|
|
|
}
|
2016-11-28 05:11:53 +00:00
|
|
|
else if (renderer->state == audiostate_stopped && freeIndex == -1)
|
|
|
|
{
|
|
|
|
freeIndex = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sameAudioPlayingCount < maxSimultaneousPlayers && freeIndex != -1)
|
|
|
|
{
|
|
|
|
result = &world->audioRenderer[freeIndex];
|
2016-11-24 11:54:07 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL void addPlayer(GameWorldState *world)
|
2016-11-28 05:11:53 +00:00
|
|
|
{
|
|
|
|
Entity *ship = &world->entityList[world->entityIndex++];
|
|
|
|
ship->id = world->entityIdCounter++;
|
|
|
|
ship->pos = math_rectGetCentre(world->camera);
|
|
|
|
ship->size = V2(25.0f, 50.0f);
|
|
|
|
ship->hitbox = ship->size;
|
|
|
|
ship->offset = v2_scale(ship->size, -0.5f);
|
|
|
|
|
|
|
|
ship->numVertexPoints = 3;
|
|
|
|
ship->vertexPoints = memory_pushBytes(&world->entityArena,
|
|
|
|
sizeof(v2) * ship->numVertexPoints);
|
|
|
|
|
|
|
|
v2 triangleBaseP = V2(0, 0);
|
|
|
|
v2 triangleTopP = V2(ship->size.w * 0.5f, ship->size.h);
|
|
|
|
v2 triangleRightP = V2(ship->size.w, triangleBaseP.y);
|
|
|
|
|
|
|
|
ship->vertexPoints[0] = triangleBaseP;
|
|
|
|
ship->vertexPoints[1] = triangleRightP;
|
|
|
|
ship->vertexPoints[2] = triangleTopP;
|
|
|
|
|
|
|
|
ship->scale = 1;
|
|
|
|
ship->type = entitytype_ship;
|
|
|
|
ship->renderMode = rendermode_polygon;
|
|
|
|
ship->color = V4(1.0f, 0.5f, 0.5f, 1.0f);
|
2016-11-24 11:54:07 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
INTERNAL void deleteEntity(GameWorldState *world, i32 entityIndex)
|
2016-11-25 09:43:43 +00:00
|
|
|
{
|
|
|
|
ASSERT(entityIndex > 0);
|
2016-11-28 14:41:33 +00:00
|
|
|
ASSERT(entityIndex < world->entityListSize);
|
2016-11-25 09:43:43 +00:00
|
|
|
|
|
|
|
/* Last entity replaces the entity to delete */
|
|
|
|
world->entityList[entityIndex] = world->entityList[world->entityIndex - 1];
|
|
|
|
|
|
|
|
/* Make sure the replaced entity from end of list is cleared out */
|
2016-11-28 14:41:33 +00:00
|
|
|
Entity emptyEntity = {0};
|
2016-11-25 09:43:43 +00:00
|
|
|
world->entityList[--world->entityIndex] = emptyEntity;
|
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
#define GET_STATE_DATA(state, arena, type) \
|
|
|
|
(type *)getStateData_(state, arena, appstate_##type)
|
|
|
|
INTERNAL void *getStateData_(GameState *state, MemoryArena_ *persistentArena,
|
|
|
|
enum AppState appState)
|
|
|
|
{
|
|
|
|
void *result = NULL;
|
|
|
|
switch (appState)
|
|
|
|
{
|
|
|
|
case appstate_StartMenuState:
|
|
|
|
{
|
|
|
|
if (!state->appStateData[appState])
|
|
|
|
{
|
|
|
|
state->appStateData[appState] =
|
|
|
|
MEMORY_PUSH_STRUCT(persistentArena, StartMenuState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case appstate_GameWorldState:
|
|
|
|
{
|
|
|
|
if (!state->appStateData[appState])
|
|
|
|
{
|
|
|
|
state->appStateData[appState] =
|
|
|
|
MEMORY_PUSH_STRUCT(persistentArena, GameWorldState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
ASSERT(INVALID_CODE_PATH);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASSERT(state->appStateData[appState]);
|
|
|
|
result = state->appStateData[appState];
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
INTERNAL v2 wrapPAroundBounds(v2 p, Rect bounds)
|
|
|
|
{
|
|
|
|
v2 result = p;
|
|
|
|
|
|
|
|
if (p.y >= bounds.max.y)
|
|
|
|
result.y = 0;
|
|
|
|
else if (p.y < bounds.min.y)
|
|
|
|
result.y = bounds.max.y;
|
|
|
|
|
|
|
|
if (p.x >= bounds.max.x)
|
|
|
|
result.x = 0;
|
|
|
|
else if (p.x < bounds.min.x)
|
|
|
|
result.x = bounds.max.x;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-27 09:13:33 +00:00
|
|
|
INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
|
2016-11-09 11:36:41 +00:00
|
|
|
{
|
2016-11-28 06:47:15 +00:00
|
|
|
GameWorldState *world =
|
|
|
|
GET_STATE_DATA(state, &state->persistentArena, GameWorldState);
|
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
if (!common_isSet(world->flags, gameworldstateflags_init))
|
2016-11-16 02:00:59 +00:00
|
|
|
{
|
2016-11-30 10:21:28 +00:00
|
|
|
|
|
|
|
#ifdef DENGINE_DEBUG
|
|
|
|
{
|
|
|
|
u8 *data = (u8 *)world;
|
|
|
|
for (i32 i = 0; i < sizeof(GameWorldState); i++)
|
|
|
|
ASSERT(data[i] == 0);
|
|
|
|
}
|
|
|
|
#endif
|
2016-11-24 07:57:44 +00:00
|
|
|
world->pixelsPerMeter = 70.0f;
|
|
|
|
|
|
|
|
MemoryIndex entityArenaSize =
|
|
|
|
(MemoryIndex)((f32)memory->transientSize * 0.5f);
|
|
|
|
|
|
|
|
u8 *arenaBase = state->transientArena.base + state->transientArena.size;
|
|
|
|
memory_arenaInit(&world->entityArena, arenaBase, entityArenaSize);
|
2016-11-11 10:43:03 +00:00
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
world->camera.min = V2(0, 0);
|
|
|
|
world->camera.max = state->renderer.size;
|
|
|
|
world->size = state->renderer.size;
|
2016-11-27 09:13:33 +00:00
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
world->entityListSize = 1024;
|
|
|
|
world->entityList = MEMORY_PUSH_ARRAY(&world->entityArena,
|
|
|
|
world->entityListSize, Entity);
|
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
{ // Init null entity
|
|
|
|
Entity *nullEntity = &world->entityList[world->entityIndex++];
|
|
|
|
nullEntity->id = world->entityIdCounter++;
|
|
|
|
}
|
2016-11-24 11:54:07 +00:00
|
|
|
|
2016-11-23 12:47:23 +00:00
|
|
|
{ // Init asteroid entities
|
2016-11-24 10:05:14 +00:00
|
|
|
world->numAsteroids = 15;
|
2016-11-23 12:47:23 +00:00
|
|
|
}
|
|
|
|
|
2016-11-24 11:54:07 +00:00
|
|
|
{ // Init audio renderer
|
2016-11-28 05:11:53 +00:00
|
|
|
world->numAudioRenderers = 8;
|
2016-11-24 11:54:07 +00:00
|
|
|
world->audioRenderer = MEMORY_PUSH_ARRAY(
|
|
|
|
&world->entityArena, world->numAudioRenderers, AudioRenderer);
|
|
|
|
}
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
{ // Global Collision Rules
|
2016-11-25 02:17:24 +00:00
|
|
|
setCollisionRule(world, entitytype_ship, entitytype_asteroid_small,
|
|
|
|
TRUE);
|
|
|
|
setCollisionRule(world, entitytype_ship, entitytype_asteroid_medium,
|
|
|
|
TRUE);
|
|
|
|
setCollisionRule(world, entitytype_ship, entitytype_asteroid_large,
|
2016-11-24 07:57:44 +00:00
|
|
|
TRUE);
|
2016-11-25 02:17:24 +00:00
|
|
|
setCollisionRule(world, entitytype_bullet,
|
|
|
|
entitytype_asteroid_small, TRUE);
|
|
|
|
setCollisionRule(world, entitytype_bullet,
|
|
|
|
entitytype_asteroid_medium, TRUE);
|
|
|
|
setCollisionRule(world, entitytype_bullet,
|
|
|
|
entitytype_asteroid_large, TRUE);
|
2016-11-09 11:36:41 +00:00
|
|
|
}
|
2016-11-23 12:47:23 +00:00
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
world->numStarP = 100;
|
|
|
|
world->starPList =
|
2016-11-28 14:41:33 +00:00
|
|
|
MEMORY_PUSH_ARRAY(&world->entityArena, world->numStarP, v2);
|
2016-11-30 07:18:36 +00:00
|
|
|
world->starMinOpacity = 0.25f;
|
2016-11-09 11:36:41 +00:00
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
for (i32 i = 0; i < world->numStarP; i++)
|
|
|
|
{
|
2016-11-28 08:11:38 +00:00
|
|
|
i32 randX = rand() % (i32)world->size.x;
|
|
|
|
i32 randY = rand() % (i32)world->size.y;
|
2016-11-16 13:50:49 +00:00
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
world->starPList[i] = V2i(randX, randY);
|
|
|
|
}
|
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
world->flags |= gameworldstateflags_init;
|
|
|
|
|
|
|
|
world->scoreMultiplier = 5;
|
|
|
|
world->scoreMultiplierBarTimer = 0.0f;
|
|
|
|
world->scoreMultiplierBarThresholdInS = 2.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (common_isSet(world->flags, gameworldstateflags_level_started))
|
|
|
|
{
|
|
|
|
Font *arial40 = asset_fontGet(&state->assetManager, "Arial", 40);
|
|
|
|
|
|
|
|
Renderer *renderer = &state->renderer;
|
|
|
|
|
|
|
|
/* Render scores onto screen */
|
|
|
|
v2 stringP =
|
|
|
|
V2((renderer->size.w * 0.5f), renderer->size.h - arial40->size);
|
|
|
|
char gamePointsString[COMMON_ITOA_MAX_BUFFER_32BIT] = {0};
|
|
|
|
common_itoa(world->score, gamePointsString,
|
|
|
|
ARRAY_COUNT(gamePointsString));
|
|
|
|
|
|
|
|
renderer_stringFixedCentered(renderer, &state->transientArena, arial40,
|
|
|
|
gamePointsString, stringP, V2(0, 0), 0,
|
|
|
|
V4(1.0f, 1.0f, 1.0f, 1.0f), 1, 0);
|
|
|
|
|
|
|
|
/* Render multiplier accumulator bar onto screen */
|
|
|
|
v2 stringDim = asset_fontStringDimInPixels(arial40, gamePointsString);
|
|
|
|
v2 multiplierOutlineSize =
|
|
|
|
V2(renderer->size.w * 0.5f, stringDim.h * 0.25f);
|
|
|
|
|
|
|
|
v2 multiplierOutlineP = V2(renderer->size.w * 0.5f, stringP.h);
|
|
|
|
multiplierOutlineP.x -= (multiplierOutlineSize.w * 0.5f);
|
|
|
|
multiplierOutlineP.y -= stringDim.h * 1.5f;
|
|
|
|
|
|
|
|
renderer_rectFixedOutline(
|
|
|
|
renderer, multiplierOutlineP, multiplierOutlineSize, V2(0, 0), 2, 0,
|
|
|
|
NULL, V4(0.2f, 0.3f, 0.8f, 1.0f), 2, renderflag_no_texture);
|
|
|
|
|
|
|
|
f32 progressNormalised = world->scoreMultiplierBarTimer /
|
|
|
|
world->scoreMultiplierBarThresholdInS;
|
|
|
|
renderer_rectFixed(renderer, multiplierOutlineP,
|
|
|
|
V2(multiplierOutlineSize.w * progressNormalised,
|
|
|
|
multiplierOutlineSize.h),
|
|
|
|
V2(0, 0), 0, NULL, V4(0.2f, 0.3f, 0.8f, 1.0f), 1,
|
|
|
|
renderflag_no_texture);
|
|
|
|
|
|
|
|
/* Render multiplier counter hud onto screen */
|
|
|
|
v2 multiplierHudP = V2(0, 0.05f * renderer->size.h);
|
|
|
|
v2 multiplierHudSize =
|
|
|
|
V2((arial40->maxSize.w * 3.5f), arial40->fontHeight * 1.2f);
|
|
|
|
|
|
|
|
renderer_rectFixed(renderer, multiplierHudP, multiplierHudSize,
|
|
|
|
V2(0, 0), 0, NULL, V4(1, 1, 1, 0.1f), 2,
|
|
|
|
renderflag_no_texture);
|
|
|
|
|
|
|
|
/* Render multiplier counter string to hud */
|
|
|
|
char multiplierToString[COMMON_ITOA_MAX_BUFFER_32BIT + 1] = {0};
|
|
|
|
common_itoa(world->scoreMultiplier, multiplierToString + 1,
|
|
|
|
ARRAY_COUNT(multiplierToString) - 1);
|
|
|
|
multiplierToString[0] = 'x';
|
|
|
|
|
|
|
|
v2 multiplierToStringP = multiplierHudP;
|
|
|
|
multiplierToStringP =
|
|
|
|
v2_add(multiplierToStringP, v2_scale(multiplierHudSize, 0.5f));
|
|
|
|
|
|
|
|
renderer_stringFixedCentered(
|
|
|
|
renderer, &state->transientArena, arial40, multiplierToString,
|
|
|
|
multiplierToStringP, V2(0, 0), 0, V4(1.0f, 1.0f, 1.0f, 1.0f), 3, 0);
|
|
|
|
|
|
|
|
/* Process multiplier bar updates */
|
2016-12-18 08:48:14 +00:00
|
|
|
if (!common_isSet(world->flags, gameworldstateflags_player_lost))
|
2016-11-30 10:21:28 +00:00
|
|
|
{
|
2016-12-18 08:48:14 +00:00
|
|
|
f32 barTimerPenalty = 1.0f;
|
|
|
|
if (world->timeSinceLastShot < 1.5f)
|
|
|
|
{
|
|
|
|
barTimerPenalty = 0.1f;
|
|
|
|
}
|
|
|
|
|
|
|
|
world->scoreMultiplierBarTimer += (barTimerPenalty * dt);
|
|
|
|
world->timeSinceLastShot += dt;
|
|
|
|
|
|
|
|
if (world->scoreMultiplierBarTimer >
|
|
|
|
world->scoreMultiplierBarThresholdInS)
|
|
|
|
{
|
|
|
|
world->scoreMultiplierBarTimer = 0;
|
|
|
|
world->scoreMultiplier++;
|
|
|
|
|
|
|
|
if (world->scoreMultiplier > 9999)
|
|
|
|
world->scoreMultiplier = 9999;
|
|
|
|
}
|
2016-11-30 10:21:28 +00:00
|
|
|
}
|
2016-12-18 08:48:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (common_isSet(world->flags, gameworldstateflags_player_lost))
|
|
|
|
{
|
|
|
|
Font *arial40 = asset_fontGet(&state->assetManager, "Arial", 40);
|
|
|
|
|
|
|
|
char *gameOver = "Game Over";
|
|
|
|
v2 gameOverP = v2_scale(state->renderer.size, 0.5f);
|
|
|
|
renderer_stringFixedCentered(
|
|
|
|
&state->renderer, &state->transientArena, arial40, "Game Over",
|
|
|
|
gameOverP, V2(0, 0), 0, V4(1, 1, 1, 1), 0, 0);
|
2016-11-30 10:21:28 +00:00
|
|
|
|
2016-12-18 08:48:14 +00:00
|
|
|
v2 gameOverSize = asset_fontStringDimInPixels(arial40, gameOver);
|
|
|
|
v2 replayP = V2(gameOverP.x, gameOverP.y - (gameOverSize.h * 1.2f));
|
2016-11-30 10:21:28 +00:00
|
|
|
|
2016-12-18 08:48:14 +00:00
|
|
|
renderer_stringFixedCentered(
|
|
|
|
&state->renderer, &state->transientArena, arial40,
|
|
|
|
"Press enter to play again or backspace to return to menu", replayP,
|
|
|
|
V2(0, 0), 0, V4(1, 1, 1, 1), 0, 0);
|
|
|
|
|
|
|
|
if (platform_queryKey(&state->input.keys[keycode_enter],
|
|
|
|
readkeytype_one_shot, 0.0f))
|
2016-11-30 10:21:28 +00:00
|
|
|
{
|
2016-12-18 08:48:14 +00:00
|
|
|
// TODO(doyle): Extract score init default values to some game
|
|
|
|
// definitions file
|
|
|
|
world->score = 0;
|
|
|
|
world->scoreMultiplier = 5;
|
|
|
|
world->scoreMultiplierBarTimer = 0.0f;
|
|
|
|
world->scoreMultiplierBarThresholdInS = 2.0f;
|
|
|
|
|
|
|
|
addPlayer(world);
|
2016-11-30 10:21:28 +00:00
|
|
|
|
2016-12-18 08:48:14 +00:00
|
|
|
world->flags ^= gameworldstateflags_player_lost;
|
|
|
|
}
|
|
|
|
else if (platform_queryKey(&state->input.keys[keycode_backspace],
|
|
|
|
readkeytype_one_shot, 0.0f))
|
|
|
|
{
|
|
|
|
common_memset((u8 *)world, 0, sizeof(*world));
|
|
|
|
state->currState = appstate_StartMenuState;
|
|
|
|
return;
|
2016-11-30 10:21:28 +00:00
|
|
|
}
|
2016-11-27 09:13:33 +00:00
|
|
|
}
|
2016-11-28 05:11:53 +00:00
|
|
|
|
|
|
|
for (u32 i = world->asteroidCounter; i < world->numAsteroids; i++)
|
|
|
|
addAsteroid(world, (rand() % asteroidsize_count));
|
|
|
|
|
2016-11-30 07:18:36 +00:00
|
|
|
Radians starRotation = DEGREES_TO_RADIANS(45.0f);
|
|
|
|
v2 starSize = V2(2, 2);
|
|
|
|
|
|
|
|
ASSERT(world->starMinOpacity >= 0.0f && world->starMinOpacity <= 1.0f);
|
|
|
|
f32 opacityFadeRateInS = 0.5f;
|
|
|
|
if (world->starFadeAway)
|
|
|
|
{
|
|
|
|
opacityFadeRateInS *= -1.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (world->starOpacity > 1.0f)
|
|
|
|
{
|
|
|
|
world->starOpacity = 1.0f;
|
|
|
|
world->starFadeAway = TRUE;
|
|
|
|
}
|
|
|
|
else if (world->starOpacity < world->starMinOpacity)
|
|
|
|
{
|
|
|
|
world->starOpacity = world->starMinOpacity;
|
|
|
|
world->starFadeAway = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
world->starOpacity += (opacityFadeRateInS * dt);
|
|
|
|
DEBUG_PUSH_VAR("Star Opacity: %5.2f", world->starOpacity, "f32");
|
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
for (i32 i = 0; i < world->numStarP; i++)
|
2016-11-27 09:13:33 +00:00
|
|
|
{
|
2016-11-30 10:21:28 +00:00
|
|
|
world->starPList[i] = v2_add(world->starPList[i], V2(4.0f * dt, 0));
|
|
|
|
world->starPList[i] = wrapPAroundBounds(
|
|
|
|
world->starPList[i], math_rectCreate(V2(0, 0), world->size));
|
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
renderer_rect(&state->renderer, world->camera, world->starPList[i],
|
|
|
|
starSize, V2(0, 0), starRotation, NULL,
|
2016-11-30 10:21:28 +00:00
|
|
|
V4(0.8f, 0.8f, 0.8f, world->starOpacity), 0,
|
|
|
|
renderflag_no_texture | renderflag_wireframe);
|
2016-11-27 09:13:33 +00:00
|
|
|
}
|
2016-11-09 11:36:41 +00:00
|
|
|
|
2016-12-18 08:48:14 +00:00
|
|
|
#ifdef DENGINE_DEBUG
|
2016-11-25 07:03:25 +00:00
|
|
|
if (platform_queryKey(&state->input.keys[keycode_left_square_bracket],
|
2016-11-28 14:41:33 +00:00
|
|
|
readkeytype_repeat, 0.2f))
|
2016-11-23 13:27:24 +00:00
|
|
|
{
|
2016-11-25 02:17:24 +00:00
|
|
|
addAsteroid(world, (rand() % asteroidsize_count));
|
2016-11-23 13:27:24 +00:00
|
|
|
}
|
2016-12-18 08:48:14 +00:00
|
|
|
#endif
|
2016-11-23 13:27:24 +00:00
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
ASSERT(world->entityList[0].id == NULL_ENTITY_ID);
|
|
|
|
for (i32 i = 1; i < world->entityIndex; i++)
|
2016-11-09 11:36:41 +00:00
|
|
|
{
|
2016-11-24 07:57:44 +00:00
|
|
|
Entity *entity = &world->entityList[i];
|
2016-11-09 11:36:41 +00:00
|
|
|
ASSERT(entity->type != entitytype_invalid);
|
|
|
|
|
2016-11-25 09:43:43 +00:00
|
|
|
v2 pivotPoint = {0};
|
2016-11-18 02:43:50 +00:00
|
|
|
f32 ddPSpeedInMs = 0;
|
|
|
|
v2 ddP = {0};
|
2016-11-16 13:50:49 +00:00
|
|
|
if (entity->type == entitytype_ship)
|
|
|
|
{
|
2016-11-25 07:03:25 +00:00
|
|
|
if (platform_queryKey(&state->input.keys[keycode_up],
|
|
|
|
readkeytype_repeat, 0.0f))
|
2016-11-11 10:43:03 +00:00
|
|
|
{
|
2016-11-16 02:00:59 +00:00
|
|
|
// TODO(doyle): Renderer creates upfacing triangles by default,
|
|
|
|
// but we need to offset rotation so that our base "0 degrees"
|
|
|
|
// is right facing for trig to work
|
2016-11-16 13:50:49 +00:00
|
|
|
Radians rotation =
|
|
|
|
DEGREES_TO_RADIANS((entity->rotation + 90.0f));
|
|
|
|
v2 direction = V2(math_cosf(rotation), math_sinf(rotation));
|
|
|
|
ddP = direction;
|
2016-12-18 08:48:14 +00:00
|
|
|
|
|
|
|
AudioVorbis *thrust =
|
|
|
|
asset_vorbisGet(&state->assetManager, "thrust");
|
|
|
|
AudioRenderer *audioRenderer =
|
|
|
|
getFreeAudioRenderer(world, thrust, 3);
|
|
|
|
if (audioRenderer)
|
|
|
|
{
|
|
|
|
audio_vorbisPlay(&state->transientArena,
|
|
|
|
&state->audioManager, audioRenderer,
|
|
|
|
thrust, 1);
|
|
|
|
}
|
2016-11-09 11:36:41 +00:00
|
|
|
}
|
|
|
|
|
2016-11-25 07:03:25 +00:00
|
|
|
if (platform_queryKey(&state->input.keys[keycode_space],
|
|
|
|
readkeytype_one_shot, KEY_DELAY_NONE))
|
2016-11-24 07:57:44 +00:00
|
|
|
{
|
|
|
|
addBullet(world, entity);
|
2016-11-24 11:54:07 +00:00
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
if (world->timeSinceLastShot >= 0)
|
|
|
|
{
|
|
|
|
world->timeSinceLastShot = 0;
|
|
|
|
|
|
|
|
f32 multiplierPenalty = -2.0f;
|
|
|
|
world->timeSinceLastShot += multiplierPenalty;
|
|
|
|
}
|
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
AudioVorbis *fire =
|
|
|
|
asset_vorbisGet(&state->assetManager, "fire");
|
|
|
|
AudioRenderer *audioRenderer =
|
|
|
|
getFreeAudioRenderer(world, fire, 2);
|
|
|
|
if (audioRenderer)
|
|
|
|
{
|
|
|
|
// TODO(doyle): Atm transient arena is not used, this is
|
|
|
|
// just to fill out the arguments
|
|
|
|
audio_vorbisPlay(&state->transientArena,
|
|
|
|
&state->audioManager, audioRenderer, fire,
|
|
|
|
1);
|
2016-11-24 11:54:07 +00:00
|
|
|
}
|
2016-11-24 07:57:44 +00:00
|
|
|
}
|
|
|
|
|
2016-11-25 09:43:43 +00:00
|
|
|
Degrees rotationsPerSecond = 180.0f;
|
|
|
|
if (platform_queryKey(&state->input.keys[keycode_left],
|
|
|
|
readkeytype_repeat, 0.0f))
|
|
|
|
{
|
|
|
|
entity->rotation += (rotationsPerSecond)*dt;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (platform_queryKey(&state->input.keys[keycode_right],
|
|
|
|
readkeytype_repeat, 0.0f))
|
|
|
|
{
|
|
|
|
entity->rotation -= (rotationsPerSecond)*dt;
|
|
|
|
}
|
|
|
|
entity->rotation = (f32)((i32)entity->rotation);
|
|
|
|
|
2016-11-18 02:43:50 +00:00
|
|
|
ddPSpeedInMs = 25;
|
2016-11-16 13:50:49 +00:00
|
|
|
DEBUG_PUSH_VAR("Pos: %5.2f, %5.2f", entity->pos, "v2");
|
|
|
|
DEBUG_PUSH_VAR("Velocity: %5.2f, %5.2f", entity->dP, "v2");
|
|
|
|
DEBUG_PUSH_VAR("Rotation: %5.2f", entity->rotation, "f32");
|
2016-11-24 07:57:44 +00:00
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
DEBUG_PUSH_VAR("TimeSinceLastShot: %5.2f", world->timeSinceLastShot,
|
|
|
|
"f32");
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
renderer_rect(&state->renderer, world->camera, entity->pos,
|
|
|
|
V2(5, 5), V2(0, 0),
|
|
|
|
DEGREES_TO_RADIANS(entity->rotation), NULL,
|
2016-11-30 07:18:36 +00:00
|
|
|
V4(1.0f, 1.0f, 1.0f, 1.0f), 0, renderflag_no_texture);
|
2016-11-17 07:15:36 +00:00
|
|
|
}
|
2016-11-25 02:17:24 +00:00
|
|
|
else if (entity->type >= entitytype_asteroid_small &&
|
|
|
|
entity->type <= entitytype_asteroid_large)
|
2016-11-17 07:15:36 +00:00
|
|
|
{
|
2016-11-09 11:36:41 +00:00
|
|
|
|
2016-11-17 07:15:36 +00:00
|
|
|
i32 randValue = rand();
|
|
|
|
|
2016-11-25 04:56:26 +00:00
|
|
|
// NOTE(doyle): If it is a new asteroid with no dp set, we need to
|
|
|
|
// set a initial dp for it to move from.
|
|
|
|
v2 localDp = {0};
|
|
|
|
if ((i32)entity->dP.x == 0 && (i32)entity->dP.y == 0)
|
2016-11-17 07:15:36 +00:00
|
|
|
{
|
2016-11-25 04:56:26 +00:00
|
|
|
enum Direction direction = randValue % direction_count;
|
|
|
|
switch (direction)
|
|
|
|
{
|
|
|
|
case direction_north:
|
|
|
|
case direction_northwest:
|
|
|
|
{
|
|
|
|
localDp.x = 1.0f;
|
|
|
|
localDp.y = 1.0f;
|
|
|
|
}
|
|
|
|
break;
|
2016-11-17 07:15:36 +00:00
|
|
|
|
2016-11-25 04:56:26 +00:00
|
|
|
case direction_west:
|
|
|
|
case direction_southwest:
|
|
|
|
{
|
|
|
|
localDp.x = -1.0f;
|
|
|
|
localDp.y = -1.0f;
|
|
|
|
}
|
|
|
|
break;
|
2016-11-17 07:15:36 +00:00
|
|
|
|
2016-11-25 04:56:26 +00:00
|
|
|
case direction_south:
|
|
|
|
case direction_southeast:
|
|
|
|
{
|
|
|
|
localDp.x = 1.0f;
|
|
|
|
localDp.y = -1.0f;
|
|
|
|
}
|
|
|
|
break;
|
2016-11-17 07:15:36 +00:00
|
|
|
|
2016-11-25 04:56:26 +00:00
|
|
|
case direction_east:
|
|
|
|
case direction_northeast:
|
|
|
|
{
|
|
|
|
localDp.x = 1.0f;
|
|
|
|
localDp.y = 1.0f;
|
|
|
|
}
|
|
|
|
break;
|
2016-11-17 07:15:36 +00:00
|
|
|
|
2016-11-25 04:56:26 +00:00
|
|
|
default:
|
|
|
|
{
|
|
|
|
ASSERT(INVALID_CODE_PATH);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// NOTE(doyle): Otherwise, if it has pre-existing dp, maintain our
|
|
|
|
// direction by extrapolating from it's current dp
|
|
|
|
else
|
2016-11-17 07:15:36 +00:00
|
|
|
{
|
2016-11-28 14:41:33 +00:00
|
|
|
if (entity->dP.x >= 0)
|
|
|
|
localDp.x = 1.0f;
|
|
|
|
else
|
|
|
|
localDp.x = -1.0f;
|
2016-11-25 04:56:26 +00:00
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
if (entity->dP.y >= 0)
|
|
|
|
localDp.y = 1.0f;
|
|
|
|
else
|
|
|
|
localDp.y = -1.0f;
|
2016-11-17 07:15:36 +00:00
|
|
|
}
|
|
|
|
|
2016-11-25 04:56:26 +00:00
|
|
|
/*
|
|
|
|
NOTE(doyle): We compare current dP with the calculated dP. In the
|
|
|
|
event we want to artificially boost the asteroid, we set a higher
|
|
|
|
dP on creation, which will have a higher dP than the default dP
|
|
|
|
we calculate. So here we choose to keep it until it decays enough
|
|
|
|
that the default dP of the asteroid is accepted.
|
|
|
|
*/
|
|
|
|
v2 newDp = v2_scale(localDp, world->pixelsPerMeter * 1.5f);
|
|
|
|
f32 newDpSum = ABS(newDp.x) + ABS(newDp.y);
|
|
|
|
f32 oldDpSum = ABS(entity->dP.x) + ABS(entity->dP.y);
|
|
|
|
|
|
|
|
if (newDpSum > oldDpSum)
|
2016-11-17 07:15:36 +00:00
|
|
|
{
|
2016-11-25 04:56:26 +00:00
|
|
|
entity->dP = newDp;
|
2016-11-17 07:15:36 +00:00
|
|
|
}
|
2016-11-16 13:50:49 +00:00
|
|
|
}
|
2016-11-24 07:57:44 +00:00
|
|
|
else if (entity->type == entitytype_bullet)
|
|
|
|
{
|
2016-11-27 10:16:12 +00:00
|
|
|
if (!math_rectContainsP(world->camera, entity->pos))
|
2016-11-24 10:05:14 +00:00
|
|
|
{
|
2016-11-25 09:43:43 +00:00
|
|
|
deleteEntity(world, i--);
|
2016-11-24 10:05:14 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
Radians rotation = DEGREES_TO_RADIANS((entity->rotation + 90.0f));
|
2016-11-25 07:03:25 +00:00
|
|
|
v2 localDp = V2(math_cosf(rotation), math_sinf(rotation));
|
2016-11-25 09:43:43 +00:00
|
|
|
entity->dP = v2_scale(localDp, world->pixelsPerMeter * 5);
|
2016-11-24 07:57:44 +00:00
|
|
|
}
|
2016-11-26 10:06:41 +00:00
|
|
|
else if (entity->type == entitytype_particle)
|
|
|
|
{
|
|
|
|
f32 diff = entity->color.a - 0.1f;
|
|
|
|
if (diff < 0.01f)
|
|
|
|
{
|
|
|
|
deleteEntity(world, i--);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
f32 divisor =
|
|
|
|
MAX(entity->particleInitDp.x, entity->particleInitDp.y);
|
2016-11-28 14:41:33 +00:00
|
|
|
f32 maxDp = MAX(entity->dP.x, entity->dP.y);
|
2016-11-26 10:06:41 +00:00
|
|
|
|
|
|
|
entity->color.a = maxDp / divisor;
|
|
|
|
}
|
2016-11-09 11:36:41 +00:00
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
entity->pos = wrapPAroundBounds(entity->pos,
|
|
|
|
math_rectCreate(V2(0, 0), world->size));
|
2016-11-24 10:05:14 +00:00
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
/* Loop entity around world */
|
2016-11-24 10:05:14 +00:00
|
|
|
i32 collisionIndex = moveEntity(world, &state->transientArena, entity,
|
|
|
|
i, ddP, dt, ddPSpeedInMs);
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
v4 collideColor = {0};
|
2016-11-24 10:05:14 +00:00
|
|
|
if (collisionIndex != -1)
|
2016-11-23 12:47:23 +00:00
|
|
|
{
|
2016-11-24 10:05:14 +00:00
|
|
|
ASSERT(collisionIndex < world->entityIndex);
|
2016-11-24 07:57:44 +00:00
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
Entity *collideEntity = &world->entityList[collisionIndex];
|
|
|
|
|
|
|
|
Entity *colliderA;
|
|
|
|
Entity *colliderB;
|
|
|
|
|
|
|
|
if (collideEntity->type < entity->type)
|
2016-11-24 07:57:44 +00:00
|
|
|
{
|
2016-11-24 10:05:14 +00:00
|
|
|
colliderA = collideEntity;
|
|
|
|
colliderB = entity;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
colliderA = entity;
|
|
|
|
colliderB = collideEntity;
|
|
|
|
}
|
|
|
|
|
2016-12-18 08:48:14 +00:00
|
|
|
// Assumptions made that the collision detect system relies on
|
|
|
|
ASSERT(entitytype_ship < entitytype_asteroid_small);
|
|
|
|
ASSERT(entitytype_asteroid_small < entitytype_asteroid_medium);
|
|
|
|
ASSERT(entitytype_asteroid_medium < entitytype_asteroid_large);
|
|
|
|
ASSERT(entitytype_asteroid_large < entitytype_bullet);
|
|
|
|
ASSERT(entitytype_asteroid_small + 1 == entitytype_asteroid_medium);
|
|
|
|
ASSERT(entitytype_asteroid_medium + 1 == entitytype_asteroid_large);
|
|
|
|
|
2016-11-25 02:17:24 +00:00
|
|
|
if (colliderA->type >= entitytype_asteroid_small &&
|
|
|
|
colliderA->type <= entitytype_asteroid_large)
|
2016-11-24 10:05:14 +00:00
|
|
|
{
|
2016-11-26 10:06:41 +00:00
|
|
|
f32 numParticles = 4;
|
2016-11-25 02:17:24 +00:00
|
|
|
if (colliderA->type == entitytype_asteroid_medium)
|
|
|
|
{
|
|
|
|
AsteroidSpec spec = {0};
|
|
|
|
spec.pos = colliderA->pos;
|
2016-11-25 04:56:26 +00:00
|
|
|
spec.dP = v2_scale(colliderA->dP, -2.0f);
|
2016-11-25 02:17:24 +00:00
|
|
|
addAsteroidWithSpec(world, asteroidsize_small, &spec);
|
2016-11-26 10:06:41 +00:00
|
|
|
|
|
|
|
numParticles = 8;
|
2016-11-30 10:21:28 +00:00
|
|
|
world->score += (10 * world->scoreMultiplier);
|
2016-11-25 02:17:24 +00:00
|
|
|
}
|
|
|
|
else if (colliderA->type == entitytype_asteroid_large)
|
|
|
|
{
|
|
|
|
AsteroidSpec spec = {0};
|
|
|
|
spec.pos = colliderA->pos;
|
2016-11-25 04:56:26 +00:00
|
|
|
spec.dP = v2_scale(colliderA->dP, -4.0f);
|
2016-11-25 02:17:24 +00:00
|
|
|
addAsteroidWithSpec(world, asteroidsize_medium, &spec);
|
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
spec.dP = v2_perpendicular(spec.dP);
|
2016-11-25 02:17:24 +00:00
|
|
|
addAsteroidWithSpec(world, asteroidsize_small, &spec);
|
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
spec.dP = v2_perpendicular(colliderA->dP);
|
2016-11-25 02:17:24 +00:00
|
|
|
addAsteroidWithSpec(world, asteroidsize_small, &spec);
|
2016-11-26 10:06:41 +00:00
|
|
|
|
|
|
|
numParticles = 16;
|
2016-11-30 10:21:28 +00:00
|
|
|
world->score += (20 * world->scoreMultiplier);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
world->score += (5 * world->scoreMultiplier);
|
2016-11-26 10:06:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i32 i = 0; i < numParticles; i++)
|
|
|
|
{
|
|
|
|
{ // Add particles
|
|
|
|
Entity *particle =
|
|
|
|
&world->entityList[world->entityIndex++];
|
|
|
|
particle->id = world->entityIdCounter++;
|
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
particle->pos = colliderA->pos;
|
|
|
|
particle->size = V2(4.0f, 4.0f);
|
2016-11-26 10:06:41 +00:00
|
|
|
|
|
|
|
i32 randValue = rand();
|
|
|
|
Radians rotation =
|
|
|
|
DEGREES_TO_RADIANS((randValue % 360));
|
|
|
|
v2 randDirectionVec =
|
|
|
|
V2(math_cosf(rotation), math_sinf(rotation));
|
|
|
|
|
|
|
|
i32 particleDpLimit = 8;
|
|
|
|
f32 randDpMultiplier =
|
|
|
|
(f32)(randValue % particleDpLimit) + 1;
|
|
|
|
|
|
|
|
v2 newDp = v2_scale(colliderA->dP, randDpMultiplier);
|
|
|
|
newDp = v2_hadamard(newDp, randDirectionVec);
|
|
|
|
|
|
|
|
particle->dP = newDp;
|
|
|
|
particle->particleInitDp = newDp;
|
|
|
|
|
|
|
|
particle->offset = v2_scale(particle->size, -0.5f);
|
|
|
|
particle->hitbox = particle->size;
|
|
|
|
particle->rotation = 0;
|
|
|
|
particle->renderMode = rendermode_polygon;
|
|
|
|
|
|
|
|
if (!world->particleVertexCache)
|
|
|
|
{
|
|
|
|
world->particleVertexCache =
|
|
|
|
MEMORY_PUSH_ARRAY(&world->entityArena, 4, v2);
|
|
|
|
world->particleVertexCache[0] =
|
|
|
|
V2(0, particle->size.h);
|
|
|
|
world->particleVertexCache[1] = V2(0, 0);
|
|
|
|
world->particleVertexCache[2] =
|
|
|
|
V2(particle->size.w, 0);
|
|
|
|
world->particleVertexCache[3] = particle->size;
|
|
|
|
}
|
|
|
|
|
|
|
|
particle->vertexPoints = world->particleVertexCache;
|
|
|
|
particle->numVertexPoints = 4;
|
|
|
|
|
|
|
|
particle->type = entitytype_particle;
|
|
|
|
particle->color = V4(1.0f, 0.0f, 0, 1.0f);
|
|
|
|
}
|
2016-11-25 02:17:24 +00:00
|
|
|
}
|
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
ASSERT(colliderB->type == entitytype_bullet);
|
2016-11-25 09:43:43 +00:00
|
|
|
|
|
|
|
deleteEntity(world, collisionIndex);
|
|
|
|
deleteEntity(world, i--);
|
2016-11-24 10:05:14 +00:00
|
|
|
world->asteroidCounter--;
|
2016-11-25 09:43:43 +00:00
|
|
|
|
2016-11-24 10:05:14 +00:00
|
|
|
ASSERT(world->asteroidCounter >= 0);
|
2016-11-24 11:54:07 +00:00
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
char *sound;
|
|
|
|
i32 choice = rand() % 3;
|
|
|
|
if (choice == 0)
|
2016-11-24 11:54:07 +00:00
|
|
|
{
|
2016-11-28 05:11:53 +00:00
|
|
|
sound = "bang_small";
|
|
|
|
}
|
|
|
|
else if (choice == 1)
|
|
|
|
{
|
|
|
|
sound = "bang_medium";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sound = "bang_large";
|
|
|
|
}
|
2016-11-24 11:54:07 +00:00
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
AudioVorbis *explode =
|
|
|
|
asset_vorbisGet(&state->assetManager, sound);
|
|
|
|
AudioRenderer *audioRenderer =
|
|
|
|
getFreeAudioRenderer(world, explode, 3);
|
|
|
|
if (audioRenderer)
|
|
|
|
{
|
2016-11-27 10:16:12 +00:00
|
|
|
audio_vorbisPlay(&state->transientArena,
|
2016-11-24 11:54:07 +00:00
|
|
|
&state->audioManager, audioRenderer,
|
|
|
|
explode, 1);
|
|
|
|
}
|
|
|
|
|
2016-11-24 07:57:44 +00:00
|
|
|
continue;
|
|
|
|
}
|
2016-12-18 08:48:14 +00:00
|
|
|
else if (colliderA->type == entitytype_ship)
|
|
|
|
{
|
|
|
|
if (colliderB->type >= entitytype_asteroid_small &&
|
|
|
|
colliderB->type <= entitytype_asteroid_large)
|
|
|
|
{
|
|
|
|
world->flags |= gameworldstateflags_player_lost;
|
|
|
|
|
|
|
|
if (collideEntity->type == entitytype_ship)
|
|
|
|
{
|
|
|
|
deleteEntity(world, collisionIndex);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
deleteEntity(world, i--);
|
|
|
|
}
|
|
|
|
|
|
|
|
AudioVorbis *explode =
|
|
|
|
asset_vorbisGet(&state->assetManager, "bang_large");
|
|
|
|
AudioRenderer *audioRenderer =
|
|
|
|
getFreeAudioRenderer(world, explode, 3);
|
|
|
|
if (audioRenderer)
|
|
|
|
{
|
|
|
|
audio_vorbisPlay(&state->transientArena,
|
|
|
|
&state->audioManager, audioRenderer,
|
|
|
|
explode, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2016-11-23 12:47:23 +00:00
|
|
|
}
|
2016-11-18 02:43:50 +00:00
|
|
|
|
2016-11-17 07:15:36 +00:00
|
|
|
RenderFlags flags = renderflag_wireframe | renderflag_no_texture;
|
2016-11-24 07:57:44 +00:00
|
|
|
renderer_entity(&state->renderer, &state->transientArena, world->camera,
|
2016-11-30 07:18:36 +00:00
|
|
|
entity, V2(0, 0), 0, collideColor, 0, flags);
|
2016-11-17 07:15:36 +00:00
|
|
|
}
|
2016-11-16 13:50:49 +00:00
|
|
|
|
2016-11-27 09:13:33 +00:00
|
|
|
for (i32 i = 0; i < world->numAudioRenderers; i++)
|
|
|
|
{
|
|
|
|
AudioRenderer *audioRenderer = &world->audioRenderer[i];
|
|
|
|
audio_updateAndPlay(&state->transientArena, &state->audioManager,
|
|
|
|
audioRenderer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
INTERNAL void startMenuUpdate(GameState *state, Memory *memory, f32 dt)
|
|
|
|
{
|
|
|
|
AssetManager *assetManager = &state->assetManager;
|
2016-11-28 05:11:53 +00:00
|
|
|
InputBuffer *inputBuffer = &state->input;
|
2016-11-27 09:13:33 +00:00
|
|
|
Renderer *renderer = &state->renderer;
|
2016-11-28 05:11:53 +00:00
|
|
|
MemoryArena_ *transientArena = &state->transientArena;
|
|
|
|
UiState *uiState = &state->uiState;
|
2016-11-28 06:47:15 +00:00
|
|
|
StartMenuState *menuState =
|
|
|
|
GET_STATE_DATA(state, &state->persistentArena, StartMenuState);
|
2016-11-27 09:13:33 +00:00
|
|
|
|
2016-11-30 07:18:36 +00:00
|
|
|
i32 uiZDepth = 2;
|
2016-11-28 14:41:33 +00:00
|
|
|
if (!menuState->init)
|
|
|
|
{
|
|
|
|
MemoryArena_ *persistentArena = &state->persistentArena;
|
|
|
|
OptimalArrayV2 *resolutionArray = inputBuffer->resolutionList;
|
|
|
|
v2 currRes = renderer->size;
|
|
|
|
i32 resIndex = -1;
|
|
|
|
|
|
|
|
menuState->resStrings = memory_pushBytes(
|
|
|
|
persistentArena, resolutionArray->index * sizeof(String *));
|
|
|
|
|
|
|
|
for (i32 i = 0; i < resolutionArray->index; i++)
|
|
|
|
{
|
|
|
|
v2 res = resolutionArray->ptr[i];
|
|
|
|
if (v2_equals(res, currRes)) resIndex = i;
|
|
|
|
|
|
|
|
char widthString[8] = {0};
|
|
|
|
char heightString[8] = {0};
|
|
|
|
common_itoa((i32)res.w, widthString, ARRAY_COUNT(widthString));
|
|
|
|
common_itoa((i32)res.h, heightString, ARRAY_COUNT(heightString));
|
|
|
|
|
|
|
|
String *resString = common_stringMake(transientArena, widthString);
|
|
|
|
resString = common_stringAppend(transientArena, resString, "x", 1);
|
|
|
|
resString =
|
|
|
|
common_stringAppend(transientArena, resString, heightString,
|
|
|
|
ARRAY_COUNT(heightString));
|
|
|
|
|
|
|
|
menuState->resStrings[i] = MEMORY_PUSH_ARRAY(
|
|
|
|
persistentArena, common_stringLen(resString), char);
|
|
|
|
common_strncpy(menuState->resStrings[i], resString,
|
|
|
|
common_stringLen(resString));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resIndex == -1) ASSERT(INVALID_CODE_PATH);
|
|
|
|
|
|
|
|
menuState->init = TRUE;
|
|
|
|
menuState->numResStrings = resolutionArray->index;
|
|
|
|
menuState->resStringDisplayIndex = resIndex;
|
|
|
|
}
|
|
|
|
|
2016-11-27 10:16:12 +00:00
|
|
|
Font *arial15 = asset_fontGetOrCreateOnDemand(
|
2016-11-27 09:13:33 +00:00
|
|
|
assetManager, &state->persistentArena, transientArena, "Arial", 15);
|
2016-11-30 10:21:28 +00:00
|
|
|
Font *arial40 = asset_fontGetOrCreateOnDemand(
|
2016-11-27 09:13:33 +00:00
|
|
|
assetManager, &state->persistentArena, transientArena, "Arial", 40);
|
|
|
|
|
2016-11-28 05:11:53 +00:00
|
|
|
v2 screenCenter = v2_scale(renderer->size, 0.5f);
|
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
ui_beginState(uiState);
|
|
|
|
if (menuState->optionsShow)
|
|
|
|
{
|
|
|
|
if (platform_queryKey(&inputBuffer->keys[keycode_o],
|
|
|
|
readkeytype_one_shot, KEY_DELAY_NONE) ||
|
|
|
|
platform_queryKey(&inputBuffer->keys[keycode_backspace],
|
|
|
|
readkeytype_one_shot, KEY_DELAY_NONE))
|
|
|
|
{
|
|
|
|
menuState->optionsShow = FALSE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-11-30 07:18:36 +00:00
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
if (platform_queryKey(&inputBuffer->keys[keycode_enter],
|
|
|
|
readkeytype_one_shot, KEY_DELAY_NONE))
|
|
|
|
{
|
2016-11-28 14:41:33 +00:00
|
|
|
OptimalArrayV2 *resolutionArray = inputBuffer->resolutionList;
|
2016-11-28 08:11:38 +00:00
|
|
|
|
2016-11-28 14:41:33 +00:00
|
|
|
menuState->newResolutionRequest = TRUE;
|
|
|
|
v2 newSize =
|
|
|
|
resolutionArray->ptr[menuState->resStringDisplayIndex];
|
|
|
|
renderer_updateSize(renderer, &state->assetManager, newSize);
|
|
|
|
|
|
|
|
// TODO(doyle): reset world arena instead of zeroing out struct
|
2016-12-18 08:48:14 +00:00
|
|
|
GameWorldState *world = GET_STATE_DATA(
|
|
|
|
state, &state->persistentArena, GameWorldState);
|
2016-11-28 14:41:33 +00:00
|
|
|
common_memset((u8 *)world, 0, sizeof(GameWorldState));
|
|
|
|
debug_init(newSize, *arial15);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (platform_queryKey(&inputBuffer->keys[keycode_left],
|
|
|
|
readkeytype_one_shot, KEY_DELAY_NONE))
|
|
|
|
{
|
|
|
|
menuState->resStringDisplayIndex--;
|
|
|
|
}
|
|
|
|
else if (platform_queryKey(&inputBuffer->keys[keycode_right],
|
|
|
|
readkeytype_one_shot,
|
|
|
|
KEY_DELAY_NONE))
|
|
|
|
{
|
|
|
|
menuState->resStringDisplayIndex++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (menuState->resStringDisplayIndex < 0)
|
|
|
|
{
|
|
|
|
menuState->resStringDisplayIndex = 0;
|
|
|
|
}
|
|
|
|
else if (menuState->resStringDisplayIndex >=
|
|
|
|
menuState->numResStrings)
|
|
|
|
{
|
|
|
|
menuState->resStringDisplayIndex =
|
|
|
|
menuState->numResStrings - 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-30 10:21:28 +00:00
|
|
|
f32 textYOffset = arial40->size * 1.5f;
|
2016-11-28 14:41:33 +00:00
|
|
|
{ // Options Title String Display
|
|
|
|
const char *const title = "Options";
|
|
|
|
|
|
|
|
v2 p = v2_add(screenCenter, V2(0, textYOffset));
|
2016-11-30 10:21:28 +00:00
|
|
|
renderer_stringFixedCentered(renderer, transientArena, arial40,
|
2016-11-28 14:41:33 +00:00
|
|
|
title, p, V2(0, 0), 0,
|
2016-11-30 07:18:36 +00:00
|
|
|
V4(1, 1, 1, 1), uiZDepth, 0);
|
2016-11-28 14:41:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{ // Resolution String Display
|
|
|
|
|
|
|
|
/* Draw label */
|
|
|
|
const char *const resolutionLabel = "Resolution";
|
|
|
|
|
|
|
|
v2 p = v2_add(screenCenter, V2(0, 0));
|
2016-11-30 10:21:28 +00:00
|
|
|
renderer_stringFixedCentered(renderer, transientArena, arial40,
|
2016-11-28 14:41:33 +00:00
|
|
|
resolutionLabel, p, V2(0, 0), 0,
|
2016-11-30 07:18:36 +00:00
|
|
|
V4(1, 1, 1, 1), uiZDepth, 0);
|
2016-11-28 14:41:33 +00:00
|
|
|
|
|
|
|
/* Draw label value */
|
|
|
|
char *resStringToDisplay =
|
|
|
|
menuState->resStrings[menuState->resStringDisplayIndex];
|
|
|
|
|
|
|
|
p = v2_add(screenCenter, V2(0, -textYOffset));
|
2016-11-30 10:21:28 +00:00
|
|
|
renderer_stringFixedCentered(renderer, transientArena, arial40,
|
2016-11-28 14:41:33 +00:00
|
|
|
resStringToDisplay, p, V2(0, 0), 0,
|
2016-11-30 07:18:36 +00:00
|
|
|
V4(1, 1, 1, 1), uiZDepth, 0);
|
2016-11-28 08:11:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Draw title text */
|
|
|
|
const char *const title = "Asteroids";
|
|
|
|
v2 p = v2_add(screenCenter, V2(0, 40));
|
2016-11-30 10:21:28 +00:00
|
|
|
renderer_stringFixedCentered(renderer, transientArena, arial40, title,
|
2016-11-30 07:18:36 +00:00
|
|
|
p, V2(0, 0), 0, V4(1, 1, 1, 1), uiZDepth,
|
|
|
|
0);
|
2016-11-28 08:11:38 +00:00
|
|
|
|
|
|
|
/* Draw blinking start game prompt */
|
|
|
|
menuState->startPromptBlinkTimer -= dt;
|
|
|
|
if (menuState->startPromptBlinkTimer < 0.0f)
|
2016-11-28 06:47:15 +00:00
|
|
|
{
|
2016-11-28 08:11:38 +00:00
|
|
|
menuState->startPromptBlinkTimer = 1.0f;
|
|
|
|
menuState->startPromptShow =
|
|
|
|
(menuState->startPromptShow) ? FALSE : TRUE;
|
2016-11-28 06:47:15 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
if (menuState->startPromptShow)
|
2016-11-28 06:47:15 +00:00
|
|
|
{
|
|
|
|
const char *const gameStart = "Press enter to start";
|
2016-11-28 08:11:38 +00:00
|
|
|
v2 p = v2_add(screenCenter, V2(0, -40));
|
2016-11-30 10:21:28 +00:00
|
|
|
renderer_stringFixedCentered(renderer, transientArena, arial40,
|
2016-11-28 08:11:38 +00:00
|
|
|
gameStart, p, V2(0, 0), 0,
|
2016-11-30 07:18:36 +00:00
|
|
|
V4(1, 1, 1, 1), uiZDepth, 0);
|
2016-11-28 08:11:38 +00:00
|
|
|
}
|
2016-11-28 06:47:15 +00:00
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
{ // Draw show options prompt
|
|
|
|
const char *const optionPrompt = "Press [o] for options ";
|
2016-11-28 14:41:33 +00:00
|
|
|
v2 p = v2_add(screenCenter, V2(0, -120));
|
2016-11-30 10:21:28 +00:00
|
|
|
renderer_stringFixedCentered(renderer, transientArena, arial40,
|
2016-11-28 14:41:33 +00:00
|
|
|
optionPrompt, p, V2(0, 0), 0,
|
2016-11-30 07:18:36 +00:00
|
|
|
V4(1, 1, 1, 1), uiZDepth, 0);
|
2016-11-28 06:47:15 +00:00
|
|
|
}
|
2016-11-28 05:11:53 +00:00
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
if (platform_queryKey(&inputBuffer->keys[keycode_enter],
|
|
|
|
readkeytype_one_shot, KEY_DELAY_NONE))
|
|
|
|
{
|
2016-11-28 05:11:53 +00:00
|
|
|
|
2016-11-28 08:11:38 +00:00
|
|
|
GameWorldState *world =
|
|
|
|
GET_STATE_DATA(state, &state->persistentArena, GameWorldState);
|
|
|
|
state->currState = appstate_GameWorldState;
|
2016-11-30 10:21:28 +00:00
|
|
|
world->flags |= gameworldstateflags_level_started;
|
2016-12-18 08:48:14 +00:00
|
|
|
addPlayer(world);
|
2016-11-28 08:11:38 +00:00
|
|
|
}
|
|
|
|
else if (platform_queryKey(&inputBuffer->keys[keycode_o],
|
|
|
|
readkeytype_one_shot, KEY_DELAY_NONE))
|
|
|
|
{
|
|
|
|
menuState->optionsShow = TRUE;
|
|
|
|
}
|
2016-11-27 09:13:33 +00:00
|
|
|
}
|
2016-11-26 03:11:59 +00:00
|
|
|
|
2016-11-27 10:16:12 +00:00
|
|
|
ui_endState(uiState, inputBuffer);
|
2016-11-27 09:13:33 +00:00
|
|
|
}
|
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
#define ASTEROID_GET_STATE_DATA(state, type) \
|
|
|
|
(type *)asteroid_getStateData_(state, appstate_##type)
|
|
|
|
void *asteroid_getStateData_(GameState *state, enum AppState appState)
|
|
|
|
{
|
|
|
|
void *result = state->appStateData[appState];
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-11-27 09:13:33 +00:00
|
|
|
void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
|
|
|
|
v2 windowSize, f32 dt)
|
|
|
|
{
|
|
|
|
MemoryIndex globalTransientArenaSize =
|
|
|
|
(MemoryIndex)((f32)memory->transientSize * 0.5f);
|
|
|
|
memory_arenaInit(&state->transientArena, memory->transient,
|
|
|
|
globalTransientArenaSize);
|
|
|
|
|
|
|
|
if (!state->init)
|
|
|
|
{
|
|
|
|
srand((u32)time(NULL));
|
|
|
|
asset_init(&state->assetManager, &state->persistentArena);
|
|
|
|
audio_init(&state->audioManager);
|
2016-11-28 14:41:33 +00:00
|
|
|
|
2016-11-27 09:13:33 +00:00
|
|
|
// NOTE(doyle): Load game assets must be before init_renderer so that
|
|
|
|
// shaders are available for the renderer configuration
|
|
|
|
loadGameAssets(state);
|
|
|
|
renderer_init(&state->renderer, &state->assetManager,
|
|
|
|
&state->persistentArena, windowSize);
|
2016-11-27 07:23:43 +00:00
|
|
|
|
2016-11-27 10:16:12 +00:00
|
|
|
Font *arial15 = asset_fontGet(&state->assetManager, "Arial", 15);
|
2016-11-28 14:41:33 +00:00
|
|
|
debug_init(windowSize, *arial15);
|
2016-11-26 03:11:59 +00:00
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
state->currState = appstate_StartMenuState;
|
|
|
|
state->init = TRUE;
|
2016-11-27 09:13:33 +00:00
|
|
|
}
|
2016-11-26 03:11:59 +00:00
|
|
|
|
2016-11-27 10:16:12 +00:00
|
|
|
platform_inputBufferProcess(&state->input, dt);
|
2016-11-26 03:11:59 +00:00
|
|
|
|
2016-11-28 06:47:15 +00:00
|
|
|
switch (state->currState)
|
2016-11-27 09:13:33 +00:00
|
|
|
{
|
2016-11-28 06:47:15 +00:00
|
|
|
case appstate_StartMenuState:
|
2016-11-27 09:13:33 +00:00
|
|
|
{
|
2016-11-28 05:11:53 +00:00
|
|
|
// NOTE(doyle): Let menu overlay the game menu. We add player on "enter"
|
|
|
|
// So fall through to appstate_game is valid here!
|
2016-11-27 09:13:33 +00:00
|
|
|
startMenuUpdate(state, memory, dt);
|
|
|
|
}
|
2016-11-28 06:47:15 +00:00
|
|
|
case appstate_GameWorldState:
|
2016-11-27 09:13:33 +00:00
|
|
|
{
|
|
|
|
gameUpdate(state, memory, dt);
|
2016-11-26 03:11:59 +00:00
|
|
|
}
|
2016-11-27 09:13:33 +00:00
|
|
|
break;
|
2016-11-26 03:11:59 +00:00
|
|
|
|
2016-11-27 09:13:33 +00:00
|
|
|
default:
|
2016-11-24 11:54:07 +00:00
|
|
|
{
|
2016-11-27 09:13:33 +00:00
|
|
|
ASSERT(INVALID_CODE_PATH);
|
|
|
|
}
|
|
|
|
break;
|
2016-11-24 11:54:07 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 02:00:59 +00:00
|
|
|
debug_drawUi(state, dt);
|
2016-11-09 11:36:41 +00:00
|
|
|
renderer_renderGroups(&state->renderer);
|
|
|
|
}
|