19 Commits

Author SHA1 Message Date
874b2b59f7 Merge branch 'main' of yibble.dev:bulmanator/ld58 2025-10-07 14:14:51 +01:00
dbff261805 Merge branch 'main' of yibble.dev:bulmanator/ld58 2025-10-07 14:09:59 +01:00
8363560fd2 fix: make it compile 2025-10-06 23:13:46 +01:00
31863b0d31 fix: import 2025-10-06 23:09:53 +01:00
7f77d7ad52 npc and bandit dressup, bits of UI added, WIP NPC interaction 2025-10-06 23:08:15 +01:00
df2c02a6a9 VERY hacky positional audio 2025-10-06 22:17:41 +01:00
f18d9d2b0e feat: made hitboxes area dependent 2025-10-06 21:57:06 +01:00
59f643b72c Added audio playback
Playing music
2025-10-06 21:54:48 +01:00
c01a6be4e5 feat: Initial prop placement 2025-10-06 21:35:32 +01:00
f93b543924 chore: fix \n 2025-10-06 21:30:13 +01:00
72a86ddb41 Merge remote-tracking branch 'origin' 2025-10-06 21:19:44 +01:00
680d375b8c feat: Added portals 2025-10-06 21:07:55 +01:00
87d3c9087e Merge branch 'debug'
Fixed some warnings
2025-10-06 19:52:28 +01:00
0c33944721 Merge branch 'debug/world_change' of yibble.dev:bulmanator/ld58 into debug 2025-10-06 19:38:14 +01:00
c1fb705f68 Fixed level saving and loading 2025-10-06 19:36:41 +01:00
be49003a8d Merge remote-tracking branch 'origin' 2025-10-06 19:32:12 +01:00
7b7f088116 fix: save/load 2025-10-06 19:27:45 +01:00
d9957eead1 feat: Nav mesh generation 2025-10-06 18:23:47 +01:00
c4cf8f532b feat: World loading that's broken 2025-10-06 16:35:22 +01:00
24 changed files with 854 additions and 35218 deletions

BIN
assets/heart.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

BIN
assets/poster.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/saloon_outside.wav Normal file

Binary file not shown.

View File

@@ -47,6 +47,11 @@ V3f V3f_Sub(V3f a, V3f b) {
return result;
}
F32 V2f_Dot(V2f a, V2f b) {
F32 result = (a.x * b.x) + (a.y * b.y);
return result;
}
F32 V3f_Dot(V3f a, V3f b) {
F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
return result;

View File

@@ -118,6 +118,7 @@ function V3f V3f_Neg(V3f x);
function V3f V3f_Scale(V3f x, F32 s);
function V3f V3f_Sub(V3f a, V3f b);
function F32 V2f_Dot(V2f a, V2f b);
function F32 V3f_Dot(V3f a, V3f b);
function F32 V4f_Dot(V4f a, V4f b);

View File

@@ -24,7 +24,7 @@
#include "game/impl/world.c"
#include "game/impl/npc.c"
#include "game/impl/bandit.c"
#include "game/testnavmesh.h"
#include "game/impl/outfit.c"
int main(int argc, char **argv)
{
@@ -44,30 +44,6 @@ int main(int argc, char **argv)
return 1;
}
SDL_AudioStream *austream;
Str8 audio_data;
M_TempScope(0, 0) {
SDL_AudioSpec spec;
spec.format = SDL_AUDIO_S16LE;
spec.channels = 2;
spec.freq = 44100;
austream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, 0, 0);
if (!austream) {
printf("Failed to open audio stream (%s)\n", SDL_GetError());
}
Str8 exec = FS_SystemPath(temp.arena, FS_SYSTEM_PATH_EXE);
Str8 path = Sf(temp.arena, "%.*s/assets/outside_ambience.wav", Sv(exec));
SDL_AudioSpec wav_spec;
U32 count;
SDL_LoadWAV((const char *) path.data, &wav_spec, &audio_data.data, &count);
audio_data.count = count;
}
Vk_Setup(window);
G_State *game = 0;
@@ -79,17 +55,25 @@ int main(int argc, char **argv)
game->draw.arena = arena;
G_ImagesLoad(game);
D_FontLoad(&game->draw, S("ubuntu"), 60);
//D_FontLoad(&game->draw, S("ubuntu"), 60);
G_PipelinesLoad(game);
G_AudioLoad(game);
Audio_Context *audio = M_ArenaPush(game->arena, Audio_Context);
Audio_Init(game->arena, audio, 0.0f); // disabled audio
U32 bgm = Audio_Play(audio, 0);
U32 saloon_music = Audio_Play(audio, 1);
SDL_ResumeAudioStreamDevice(audio->stream);
G_Camera *camera = &game->camera;
camera->x = V3F(1, 0, 0);
camera->y = V3F(0, 1, 0);
camera->z = V3F(0, 0, 1);
camera->p = V3F(0, 0, 16);
camera->p = V3F(0, 0, 48);
camera->fov = 60.0f;
@@ -97,24 +81,30 @@ int main(int argc, char **argv)
camera->farp = 1000.0f;
game->draw.camera = camera;
World *world = M_ArenaPush(arena, World);//LoadWorld(arena);
World *world = M_ArenaPush(arena, World);
LoadWorld(arena, world);
game->world = world;
world->audio = audio;
world->audio_handles[0] = bgm;
world->audio_handles[1] = saloon_music;
world->arena = arena;
world->navMesh = &TestNavMesh;
world->random = Random_Seed(29237489723847);
world->npcCount = 2;
world->npcCount = 12;
for(U32 i = 0; i < world->npcCount; i++) {
NPC *npc1 = &world->npcs[i];
npc1->collision.pos.x = 15;
npc1->collision.pos.y = 15;
NPC *npc1 = &world->npcs[i];
npc1->collision.pos.x = 0;
npc1->collision.pos.y = 0;
npc1->collision.size.x = 1;
npc1->collision.size.y = 2;
npc1->name = S("Matt");
npc1->mode = NPC_ACTION_WAITING;
npc1->currentArea = i;
npc1->currentArea = WORLD_AREA_OUTSIDE;
npc1->waitTime = 0;
npc1->maxWaitTime = 5;
npc1->maxWaitTime = 1;
npc1->currentNavNode = 0;
GenOutfit(&npc1->outfit,world,game);
}
Bandit *badman = &world->bandit;
@@ -128,7 +118,7 @@ int main(int argc, char **argv)
badman->maxWaitTime = 2;
badman->poiCount = 2;
badman->shootoutTimer = 1.5;
badman->agroRadius = 600.0;
badman->agroRadius = 6.0;
badman->bullets = 6;
badman->shootDelay = 1;
badman->accuracyRange = 0.25;
@@ -136,95 +126,101 @@ int main(int argc, char **argv)
badman->reloadTimer = 0;
badman->pointsOfInterest[0] = 937;
badman->pointsOfInterest[1] = 12;
badman->outfitChoices = GenOutfit(&badman->outfit, world, game);
badman->currentArea = WORLD_AREA_OUTSIDE;
world->npcPOI[0] = 100;
PlayerInit(game, &world->player);
for(int i =0; i< 4200; i++) {
world->map[i] = map[i];
}
world->tileTypes = M_ArenaPush(arena, World_Tile, .count=WORLD_TILE_TYPE_MAX);
world->tileTypes[0].rotation=0;
world->tileTypes[0].collision=false;
world->tileTypes[0].tile=D_ImageHandle(&game->draw, S("tile_dirt_0"));
world->tileTypes[0].tag=S("tile_dirt_0");
world->tileTypes[1].rotation=0,
world->tileTypes[1].collision=false,
world->tileTypes[1].tile=D_ImageHandle(&game->draw, S("path_middle"));
world->tileTypes[1].tag=S("path_middle");
world->tileTypes[2].rotation=0;
world->tileTypes[2].collision=false;
world->tileTypes[2].tile=D_ImageHandle(&game->draw, S("path_middle_edge"));
world->tileTypes[2].tag=S("path_middle_edge");
world->tileTypes[3].rotation=PI_F32/2;
world->tileTypes[3].collision=false;
world->tileTypes[3].tile=D_ImageHandle(&game->draw, S("path_middle_edge"));
world->tileTypes[3].tag=S("path_middle_edge");
world->tileTypes[4].rotation=PI_F32;
world->tileTypes[4].collision=false;
world->tileTypes[4].tile=D_ImageHandle(&game->draw, S("path_middle_edge"));
world->tileTypes[4].tag=S("path_middle_edge");
world->tileTypes[5].rotation=-PI_F32/2;
world->tileTypes[5].collision=false;
world->tileTypes[5].tile=D_ImageHandle(&game->draw, S("path_middle_edge"));
world->tileTypes[5].tag=S("path_middle_edge");
world->tileTypes[6].rotation=0;
world->tileTypes[6].collision=false;
world->tileTypes[6].tile=D_ImageHandle(&game->draw, S("path_middle"));
world->tileTypes[6].tag=S("path_middle");
world->tileTypes[7].rotation=PI_F32/2;
world->tileTypes[7].collision=false;
world->tileTypes[7].tile=D_ImageHandle(&game->draw, S("path_middle"));
world->tileTypes[7].tag=S("path_middle");
world->tileTypes[8].rotation=-PI_F32;
world->tileTypes[8].collision=false;
world->tileTypes[8].tile=D_ImageHandle(&game->draw, S("path_middle"));
world->tileTypes[8].tag=S("path_middle");
world->tileTypes[9].rotation=-PI_F32/2;
world->tileTypes[9].collision=false;
world->tileTypes[9].tile=D_ImageHandle(&game->draw, S("path_middle"));
world->tileTypes[9].tag=S("path_middle");
world->tileTypes[10].rotation=0;
world->tileTypes[10].collision=false;
world->tileTypes[10].tile=D_ImageHandle(&game->draw, S("path_corner"));
world->tileTypes[10].tag=S("path_corner");
world->tileTypes[11].rotation=PI_F32/2;
world->tileTypes[11].collision=false;
world->tileTypes[11].tile=D_ImageHandle(&game->draw, S("path_corner"));
world->tileTypes[11].tag=S("path_corner");
world->tileTypes[12].rotation=-PI_F32/2;
world->tileTypes[12].collision=false;
world->tileTypes[12].tile=D_ImageHandle(&game->draw, S("path_corner"));
world->tileTypes[12].tag=S("path_corner");
world->tileTypes[13].rotation=PI_F32;
world->tileTypes[13].collision=false;
world->tileTypes[13].tile=D_ImageHandle(&game->draw, S("path_corner"));
world->tileTypes[13].tag=S("path_corner");
world->tileTypes[14].rotation=0;
world->tileTypes[14].collision=false;
world->tileTypes[14].tile=D_ImageHandle(&game->draw, S("tile_dirt_1"));
world->tileTypes[14].tag=S("tile_dirt_1");
world->propTypes[0].assetHandle=D_ImageHandle(&game->draw, S("rug0"));
world->propTypes[0].scale=1;
world->propTypes[1].assetHandle=D_ImageHandle(&game->draw, S("rug1"));
world->propTypes[1].scale=1;
world->propTypes[2].assetHandle=D_ImageHandle(&game->draw, S("skull"));
world->propTypes = M_ArenaPush(arena, World_PropType, .count=WORLD_PROP_TYPE_MAX);
world->propTypes[0].tag=S("rug0");
world->propTypes[0].scale=3;
world->propTypes[1].tag=S("rug1");
world->propTypes[1].scale=3;
world->propTypes[2].tag=S("skull");
world->propTypes[2].scale=1;
world->propTypes[3].assetHandle=D_ImageHandle(&game->draw, S("table"));
world->propTypes[3].scale=1;
world->propTypes[4].assetHandle=D_ImageHandle(&game->draw, S("barrel"));
world->propTypes[3].tag = S("table");
world->propTypes[3].scale=2;
world->propTypes[4].tag = S("barrel");
world->propTypes[4].scale=1;
world->propTypes[5].assetHandle=D_ImageHandle(&game->draw, S("can"));
world->propTypes[5].scale=1;
world->propTypes[6].assetHandle=D_ImageHandle(&game->draw, S("candle"));
world->propTypes[6].scale=1;
world->propTypes[7].assetHandle=D_ImageHandle(&game->draw, S("clock"));
world->propTypes[7].scale=1;
world->propTypes[8].assetHandle=D_ImageHandle(&game->draw, S("log_pile"));
world->propTypes[5].tag = S("can");
world->propTypes[5].scale=0.5;
world->propTypes[6].tag = S("candle");
world->propTypes[6].scale=0.5;
world->propTypes[7].tag = S("clock");
world->propTypes[7].scale=1.5;
world->propTypes[8].tag = S("log_pile");
world->propTypes[8].scale=1;
world->propTypes[9].assetHandle=D_ImageHandle(&game->draw, S("nightstand"));
world->propTypes[9].tag = S("nightstand");
world->propTypes[9].scale=1;
world->propTypes[10].assetHandle=D_ImageHandle(&game->draw, S("pool_table"));
world->propTypes[10].scale=1;
world->propTypes[11].assetHandle=D_ImageHandle(&game->draw, S("saloon_ext"));
world->propTypes[10].tag = S("pool_table");
world->propTypes[10].scale=3;
world->propTypes[11].tag = S("saloon_ext");
world->propTypes[11].scale=6.875f;
world->propTypes[12].assetHandle=D_ImageHandle(&game->draw, S("saloon_int"));
world->propTypes[12].scale=6.875f;
world->propTypes[11].assetHandle=D_ImageHandle(&game->draw, S("house"));
world->propTypes[11].scale=6.875f;
world->propTypes[12].assetHandle=D_ImageHandle(&game->draw, S("house_int"));
world->propTypes[12].scale=6.875f;
world->propTypes[12].tag = S("saloon_int");
world->propTypes[12].scale=2*6.875f;
world->propTypes[13].tag = S("house");
world->propTypes[13].scale=6.875f;
world->propTypes[14].tag = S("house_int");
world->propTypes[14].scale=12.875f;
world->propTypes[15].tag=S("tile_detail_0");
world->propTypes[15].scale=1;
world->propTypes[16].tag = S("tile_detail_1");
world->propTypes[16].scale=1;
world->propTypes[17].tag = S("tile_detail_2");
world->propTypes[17].scale=1;
world->propTypes[18].tag= S("tile_detail_3");
world->propTypes[18].scale=1;
world->propTypes[19].tag= S("tile_detail_4");
world->propTypes[19].scale=1;
world->propTypes[20].tag=S("tile_detail_5");
world->propTypes[20].scale=1;
world->propTypes[21].tag=S("tile_detail_6");
world->propTypes[21].scale=1;
world->propCount = 0;
world->props = M_ArenaPush(arena, World_Prop, .count=WORLD_PROP_MAX);
world->hitboxes = M_ArenaPush(arena, World_Hitbox, .count=WORLD_HITBOX_MAX);
world->portals = M_ArenaPush(arena, World_Portal, .count=64);
world->portalCount=0;
GenerateNavMesh(arena, world);
}
game->editor.enabled = false;
game->editor.enabled = true;
game->editor.mode = G_EDITOR_MODE_TILE;
game->editor.currentLevel = WORLD_AREA_OUTSIDE;
game->editor.selectedNode = 0;
bool running = true;
@@ -244,6 +240,7 @@ int main(int argc, char **argv)
Player *player = &game->world->player;
switch (e.key.key) {
case SDLK_R: { PlayerInit(game, player); } break;
case SDLK_F: {game->world->showPoster=!game->world->showPoster;}break;
}
}
@@ -274,19 +271,33 @@ int main(int argc, char **argv)
break;
}
case G_EDITOR_MODE_HITBOX: {
case G_EDITOR_MODE_PORTAL: {
game->editor.dragStart = game->editor.cursor;
break;
}
}
} else if(e.type==SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT && game->editor.mode == G_EDITOR_MODE_HITBOX) {
}} else if(e.type==SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT && game->editor.mode == G_EDITOR_MODE_HITBOX) {
// Add hitbox
V2f topLeft = V2F(Min(game->editor.cursor.x, game->editor.dragStart.x), Min(game->editor.cursor.y, game->editor.dragStart.y));
game->world->hitboxes[game->world->hitboxCount].pos = topLeft;
game->world->hitboxes[game->world->hitboxCount].size = V2F(
game->world->hitboxes[game->world->hitboxCount].box.pos = topLeft;
game->world->hitboxes[game->world->hitboxCount].box.size = V2F(
Abs(game->editor.cursor.x-game->editor.dragStart.x),
Abs(game->editor.cursor.y-game->editor.dragStart.y)
);
game->world->hitboxes[game->world->hitboxCount].area = game->editor.currentAsset;
game->world->hitboxCount++;
GenerateNavMesh(game->arena, game->world);
} else if(e.type==SDL_EVENT_MOUSE_BUTTON_UP && e.button.button == SDL_BUTTON_LEFT && game->editor.mode == G_EDITOR_MODE_PORTAL) {
// Add portal
V2f topLeft = V2F(Min(game->editor.cursor.x, game->editor.dragStart.x), Min(game->editor.cursor.y, game->editor.dragStart.y));
game->world->portals[game->world->portalCount].box.pos = topLeft;
game->world->portals[game->world->portalCount].box.size = V2F(
Abs(game->editor.cursor.x-game->editor.dragStart.x),
Abs(game->editor.cursor.y-game->editor.dragStart.y)
);
game->world->portals[game->world->portalCount].area = game->editor.currentAsset;
game->world->portalCount++;
GenerateNavMesh(game->arena, game->world);
}
}
@@ -298,10 +309,12 @@ int main(int argc, char **argv)
}
case SDLK_RIGHT: {
game->editor.currentAsset = Min(game->editor.currentAsset+1, 64);
printf("editing with %d\n", game->editor.currentAsset);
break;
}
case SDLK_LEFT: {
game->editor.currentAsset = Max(game->editor.currentAsset-1, 0);
printf("editing with %d\n", game->editor.currentAsset);
break;
}
case SDLK_A: {
@@ -330,14 +343,26 @@ int main(int argc, char **argv)
game->world->player.currentArea--;
break;
}
case SDLK_E: {
game->editor.selectedNode++;
printf("selected %d\n", game->editor.selectedNode);
break;
}
case SDLK_Q: {
game->editor.selectedNode--;
printf("selected %d\n", game->editor.selectedNode);
break;
}
case SDLK_SPACE: {
game->editor.mode = (game->editor.mode + 1) % 4;
printf("EDITOR MODE %d\n", game->editor.mode);
break;
}
case SDLK_U: {
switch(game->editor.mode) {
case G_EDITOR_MODE_PROP: {game->world->propCount--;}
case G_EDITOR_MODE_HITBOX: {game->world->hitboxCount--;}
case G_EDITOR_MODE_PORTAL: {game->world->portalCount--;}
}
break;
}
@@ -346,7 +371,7 @@ int main(int argc, char **argv)
}
if(!game->editor.enabled) {
UpdateWorld(1.0f / 60.0f, game->world);
UpdateWorld(1.0f / 250.0f, game->world);
game->camera.p.x = game->world->player.collision.pos.x;
game->camera.p.y = game->world->player.collision.pos.y;
@@ -388,33 +413,99 @@ int main(int argc, char **argv)
D_Begin(&game->draw, frame, D_MAX_RECTS);
RenderWorld(game->world, &game->draw);
D_Text(&game->draw, game->draw.fonts, S("Small Test"), 0, 0);
R3f bounds = G_CameraBounds(&game->camera);
if (game->world->showPoster) {
D_Rect(&game->draw, bounds.min.x + 4.8f, bounds.min.y + 6.4f, .texture = D_ImageHandle(&game->draw, S("poster")), .scale = 12.0f);
}
for (U32 i = 0; i < game->world->player.health; i++) {
D_Rect(&game->draw, (bounds.max.x - 3) + i, bounds.min.y + 1, .texture = D_ImageHandle(&game->draw, S("heart")));
}
//D_Text(&game->draw, game->draw.fonts, S("Small Test"), 0, 0);
if(game->editor.enabled) {
G_Editor editor = game->editor;
F32 tilex = cast(F32) floor(editor.cursor.x+TILE_SIZE/2);
F32 tiley = cast(F32) floor(editor.cursor.y+TILE_SIZE/2);
for (U32 i = 0; i < game->world->navMesh->nodeCount; i++) {
NavNode n = game->world->navMesh->nodes[i];
D_Rect(
&game->draw,
n.pos.x,
n.pos.y,
.texture = 0,
.scale = 0.2f,
);
}
for (U32 i = 0; i < game->world->navMesh->nodeCount; i++) {
NavNode n = game->world->navMesh->nodes[i];
if(i == editor.selectedNode) {
D_Rect(
&game->draw,
n.pos.x,
n.pos.y,
.texture = 0,
.scale = 0.2f,
.c = V4F(100, 255, 0, 100),
);
for(int j = 0; j < n.connectionCount; j++) {
D_Rect(
&game->draw,
game->world->navMesh->nodes[n.connections[j].NodeIndex].pos.x,
game->world->navMesh->nodes[n.connections[j].NodeIndex].pos.y,
.texture = 0,
.scale = 0.2f,
.c = V4F(0, 100, 0, 100),
);
}
}
}
switch(game->editor.mode) {
case G_EDITOR_MODE_TILE: {
World_Tile asset = game->world->tileTypes[editor.currentAsset];
D_Rect(&game->draw, tilex, tiley, .texture=asset.tile, .angle=(F32) asset.rotation);
D_Rect(&game->draw, tilex, tiley, .texture=D_ImageHandle(&game->draw, asset.tag), .angle=asset.rotation);
break;
}
case G_EDITOR_MODE_PROP: {
World_PropType prop = game->world->propTypes[editor.currentAsset];
D_Rect(&game->draw, editor.cursor.x, editor.cursor.y, .texture=prop.assetHandle, .scale=prop.scale);
D_Rect(&game->draw, editor.cursor.x, editor.cursor.y, .texture=D_ImageHandle(&game->draw, prop.tag), .scale=prop.scale);
break;
}
case G_EDITOR_MODE_HITBOX: {
for(U32 i = 0; i < game->world->hitboxCount; i++) {
V2f centre = AABB_Centre(game->world->hitboxes[i]);
D_Rect(&game->draw, centre.x, centre.y, .texture=0, .dim=game->world->hitboxes[i].size, .flags=D_RECT_IGNORE_ASPECT);
if(game->world->player.currentArea == game->world->hitboxes[i].area) {
V2f centre = AABB_Centre(game->world->hitboxes[i].box);
D_Rect(
&game->draw,
centre.x,
centre.y,
.texture=0,
.dim=game->world->hitboxes[i].box.size,
.flags=D_RECT_IGNORE_ASPECT,
.c=V4F(100,0,0,0.7f),
);
}
}
break;
}
case G_EDITOR_MODE_POI: {
case G_EDITOR_MODE_PORTAL: {
for(U32 i = 0; i < game->world->portalCount; i++) {
V2f centre = AABB_Centre(game->world->portals[i].box);
D_Rect(
&game->draw,
centre.x,
centre.y,
.texture=0,
.dim=game->world->portals[i].box.size,
.flags=D_RECT_IGNORE_ASPECT,
.c=V4F(0,0,100,0.7f),
);
}
break;
}
}

View File

@@ -1,5 +1,6 @@
#if !defined(LD_GAME_BANDIT_H_)
#define LD_GAME_BANDIT_H_
#include "outfit.h"
typedef enum BANDIT_ACTION BANDIT_ACTION;
enum BANDIT_ACTION
@@ -62,8 +63,12 @@ struct Bandit {
F32 accuracyRange;
// A the circle around the bandit where they will trigger the quicktime reaction scene
F32 agroRadius;
// What the bandit is wearing
G_Outfit outfit;
// What the bandit's outfit id's are
U32 *outfitChoices;
};
function V2f shootTowards(Bandit* bandit, V2f target, Random* r);
function V2f ShootTowards(Bandit* bandit, V2f target, Random* r);
function void BanditDraw(D_Context *draw, Bandit *bandit);
#endif // LD_GAME_BANDIT_H_

View File

@@ -21,7 +21,7 @@ enum G_EDITOR_MODE {
G_EDITOR_MODE_TILE,
G_EDITOR_MODE_PROP,
G_EDITOR_MODE_HITBOX,
G_EDITOR_MODE_POI,
G_EDITOR_MODE_PORTAL,
};
typedef struct G_Editor G_Editor;
@@ -32,6 +32,7 @@ struct G_Editor {
G_EDITOR_MODE mode;
V2f cursor;
V2f dragStart;
U32 selectedNode;
};
typedef struct G_State G_State;

View File

@@ -11,7 +11,7 @@ bool AABB_Collide(AABB a, AABB b)
bool AABB_Point(AABB a, V2f v)
{
bool collision_x = a.pos.x + a.size.x >= v.x && a.pos.x <= v.x;
bool collision_y = a.pos.x + a.size.y >= v.y && a.pos.y <= v.y;
bool collision_y = a.pos.y + a.size.y >= v.y && a.pos.y <= v.y;
return collision_x && collision_y;
}
@@ -45,4 +45,4 @@ bool AABB_Circle(F32 rad, V2f radOrigin, AABB a)
F32 xSq = (Abs(aCentre.x) - Abs(radOrigin.x)) * (Abs(aCentre.x) - Abs(radOrigin.x));
F32 ySq = (Abs(aCentre.y) - Abs(radOrigin.y)) * (Abs(aCentre.y) - Abs(radOrigin.y));
return SDL_sqrt(xSq + ySq) < rad;
}
}

View File

@@ -1,7 +1,7 @@
#include "game/world.h"
#include "game/bandit.h"
V2f shootTowards(Bandit *bandit, V2f target, Random* r)
V2f ShootTowards(Bandit *bandit, V2f target, Random* r)
{
V2f shooterV2 = bandit->collision.pos;
F32 randX = Random_F32(r, -bandit->accuracyRange, bandit->accuracyRange);
@@ -10,15 +10,18 @@ V2f shootTowards(Bandit *bandit, V2f target, Random* r)
}
void UpdateBandit(F32 delta, Bandit *bandit, World *world) {
for(U32 i = 0; i < world->portalCount; i++) {
if(AABB_Collide(world->portals[0].box, bandit->collision)) {
bandit->currentArea = world->portals[0].area;
}
}
if (
world->player.controls.shot && AABB_Slab(world->player.collision.pos, world->player.shotPos, bandit->collision) && bandit->currentArea == world->player.currentArea)
{
printf("You shot the bandit %*.s\n", Sv(bandit->name));
bandit->health--;
}
if (AABB_Circle(bandit->agroRadius, AABB_Centre(bandit->collision), world->player.collision) && !(bandit->mode == BANDIT_SHOOTING || bandit->mode == BANDIT_SHOOTOUT))
{
printf("begin shootout");
// shootout time o.o
bandit->mode = BANDIT_SHOOTOUT;
}
@@ -56,7 +59,7 @@ void UpdateBandit(F32 delta, Bandit *bandit, World *world) {
bandit->collision.pos.y = cNav.pos.y * (1 - bandit->walkTimer/NPC_SPEED) + tNav.pos.y * bandit->walkTimer/NPC_SPEED;
break;
case BANDIT_SHOOTOUT:
bandit->shootoutTimer-=delta;
bandit->shootoutTimer-=delta;
if(bandit->shootoutTimer < 0){
bandit->mode=BANDIT_SHOOTING;
}
@@ -66,17 +69,14 @@ void UpdateBandit(F32 delta, Bandit *bandit, World *world) {
bandit->reloadTimer -= delta;
if (bandit->shootCooldownTimer < 0 && bandit->reloadTimer < 0)
{
printf("shoot at player");
bandit->bullets--;
bandit->shootCooldownTimer = bandit->shootDelay;
V2f banditShot = shootTowards(bandit, world->player.collision.pos, &world->random);
V2f banditShot = ShootTowards(bandit, world->player.collision.pos, &world->random);
if(AABB_Slab(bandit->collision.pos, banditShot, world->player.collision)){
// gets shot lmao
printf("hit");
world->player.health--;
}
if(bandit->bullets == 0){
printf("enemy reload");
bandit->bullets = 6;
bandit->reloadTimer = bandit->reloadTime;
}
@@ -85,3 +85,23 @@ void UpdateBandit(F32 delta, Bandit *bandit, World *world) {
// TODO Running away
}
}
void BanditDraw(D_Context *draw, Bandit *bandit)
{
G_Outfit *outfit = &bandit->outfit;
R2f pframe = D_AnimationFrame(&outfit->state);
for (U32 it = 0; it < G_OUTFIT_COMPONENT_COUNT; ++it)
{
U32 flipped = (outfit->dir & G_OUTFIT_DIR_FLIPPED) != 0;
U32 dir = (outfit->dir & ~G_OUTFIT_DIR_FLIPPED);
U32 tid = outfit->e[dir].e[it];
if (tid != 0)
{
U32 flags = D_RECT_UV_ASPECT | (flipped ? D_RECT_FLIP_X : 0);
D_Rect(draw, bandit->collision.pos.x, bandit->collision.pos.y, .texture = tid, .uv = pframe, .flags = flags);
}
}
}

View File

@@ -4,6 +4,11 @@
#include "core/math.h"
void UpdateNPC(F32 delta, NPC *npc, World *world) {
for(U32 i = 0; i < world->portalCount; i++) {
if(AABB_Collide(world->portals[0].box, npc->collision)) {
npc->currentArea = world->portals[0].area;
}
}
switch (npc->mode) {
case NPC_ACTION_WAITING:
npc->waitTime+=delta;
@@ -41,3 +46,23 @@ void UpdateNPC(F32 delta, NPC *npc, World *world) {
break;
}
}
void NPCDraw(D_Context *draw, NPC *npc)
{
G_Outfit *outfit = &npc->outfit;
R2f pframe = D_AnimationFrame(&outfit->state);
for (U32 it = 0; it < G_OUTFIT_COMPONENT_COUNT; ++it)
{
U32 flipped = (outfit->dir & G_OUTFIT_DIR_FLIPPED) != 0;
U32 dir = (outfit->dir & ~G_OUTFIT_DIR_FLIPPED);
U32 tid = outfit->e[dir].e[it];
if (tid != 0)
{
U32 flags = D_RECT_UV_ASPECT | (flipped ? D_RECT_FLIP_X : 0);
D_Rect(draw, npc->collision.pos.x, npc->collision.pos.y, .texture = tid, .uv = pframe, .flags = flags);
}
}
}

31
code/game/impl/outfit.c Normal file
View File

@@ -0,0 +1,31 @@
U32* GenOutfit(G_Outfit *o, World *world, G_State *game)
{
U32 *outfitChoices = M_ArenaPush(world->arena, U32, .count = 8);
G_Outfit *outfit = o;
D_AnimationInit(&outfit->state, 0, 1, 4, 1.0f / 6.0f);
M_TempScope(0, 0)
{
for (U32 it = 0; it < G_OUTFIT_COMPONENT_COUNT; ++it)
{
U32 idx = Random_Next(&world->random) % __outfit_counts[it];
// We just allow face, hair and hat to default to 0 meaning the player doesn't have one
if (idx != 0 || it < 2 || it > 4)
{
outfit->front.e[it] = OUTFIT_IMG(front, idx);
outfit->side.e[it] = OUTFIT_IMG(side, idx);
if ((idx + 1) <= __outfit_counts[it])
{
outfit->back.e[it] = OUTFIT_IMG(back, idx);
}
}
outfitChoices[it] = idx;
}
}
return outfitChoices;
}

View File

@@ -2,47 +2,9 @@
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keycode.h>
#include <stdio.h>
#include "../outfit.h"
#include "../npc.h"
// @Todo: move/extern these so the npc/bandit can use them
//
#define G_OUTFIT_COMPONENT_COUNT 8
global_var Str8 __outfit_names[] = {
Sl("npc_%s_base_%d"),
Sl("npc_%s_eyes_%d"),
Sl("npc_%s_face_%d"),
Sl("npc_%s_hair_%d"),
Sl("npc_%s_hat_%d"),
Sl("npc_%s_shirt_%d"),
Sl("npc_%s_shoes_%d"),
Sl("npc_%s_trousers_%d")
};
global_var U32 __outfit_counts[] = {
2, // base
3, // eyes
5, // face
4, // hair
3, // hat
2, // shirt
1, // shoes
2 // trousers
};
global_var U32 __outfit_back_counts[] = {
2, // base
0, // eyes
3, // face
4, // hair
3, // hat
2, // shirt
1, // shoes
2 // trousers
};
StaticAssert(ArraySize(__outfit_names) == ArraySize(__outfit_counts));
#define OUTFIT_IMG(dir, n) D_ImageHandle(&game->draw, Sf(temp.arena, (const char *) __outfit_names[it].data, #dir, n))
void PlayerInit(G_State *game, Player *player) {
World *world = game->world;
@@ -105,6 +67,16 @@ void PlayerInput(SDL_Event *event, Player *player)
player->controls.downDown = val;
break;
}
case SDLK_E:
{
for(int i = 0; player->world->npcCount; i++){
NPC *npc = &player->world->npcs[i];
if(AABB_Circle(npc->interationRadius, npc->collision.pos, player->collision) && !npc->infoGiven){
npc->infoGiven=true;
player->knownDetails[player->detailCount] = player->world->bandit.outfitChoices[player->detailCount];
}
}
}
}
}
if (
@@ -128,7 +100,6 @@ void PlayerUpdate(F32 delta, Player *player) {
player->controls.shot = false;
V2f dir = V2F(0, 0);
if(player->health == 0){
printf("dead :(");
player->health = 3;
}
if(player->controls.upDown) {
@@ -165,6 +136,23 @@ void PlayerUpdate(F32 delta, Player *player) {
dir = V2f_Scale(NormaliseV2F(dir), PLAYER_SPEED*delta);
player->collision.pos.x += dir.x;
player->collision.pos.y += dir.y;
for(U32 i = 0; i < player->world->portalCount; i++) {
if(AABB_Collide(player->world->portals[i].box, player->collision)) {
player->currentArea = player->world->portals[i].area;
}
}
V2f test;
test.x = player->collision.pos.x - 14;
test.y = player->collision.pos.y - 14;
F32 len = V2f_Dot(test, test);
F32 vol = Max(0.0f, 1.0f - (len / 25.0f));
Audio_ChangeVolume(player->world->audio, player->world->audio_handles[1], vol);
Audio_ChangeVolume(player->world->audio, player->world->audio_handles[0], 1.0f - vol);
}
void PlayerDraw(D_Context *draw, Player *player) {

View File

@@ -35,7 +35,10 @@ void ProcessEvents(SDL_Event *event, World *world)
{
PlayerInput(event, &world->player);
if(event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_F5){
SaveWorld(world->arena, world);
SaveWorld(world);
}
if(event->type == SDL_EVENT_KEY_DOWN && event->key.key == SDLK_F6){
LoadWorld(world->arena, world);
}
}
@@ -45,7 +48,7 @@ void RenderWorld(World *world, D_Context *draw) {
D_Rect(
draw,
(F32) (i % 96), (F32) (i / 96),
.texture = world->tileTypes[world->map[i]].tile,
.texture = D_ImageHandle(draw,world->tileTypes[world->map[i]].tag),
.angle = (F32) world->tileTypes[world->map[i]].rotation,
);
}
@@ -56,7 +59,7 @@ void RenderWorld(World *world, D_Context *draw) {
draw,
world->props[i].pos.x,
world->props[i].pos.y,
.texture = world->propTypes[world->props[i].propType].assetHandle,
.texture = D_ImageHandle(draw,world->propTypes[world->props[i].propType].tag),
.scale = world->propTypes[world->props[i].propType].scale,
);
}
@@ -64,40 +67,238 @@ void RenderWorld(World *world, D_Context *draw) {
for(U32 i = 0; i < world->npcCount; i++) {
NPC npc = world->npcs[i];
if(npc.currentArea == world->player.currentArea) {
V2f drawPos = AABB_Centre(npc.collision);
D_Rect(draw, drawPos.x, drawPos.y, .texture = 1);
NPCDraw(draw, &world->npcs[i]);
}
}
if(world->bandit.currentArea == world->player.currentArea) {
V2f drawPos = AABB_Centre(world->bandit.collision);
D_Rect(draw, drawPos.x, drawPos.y, .texture = 9);
}
BanditDraw(draw, &world->bandit);
PlayerDraw(draw, &world->player);
}
void SaveWorld(M_Arena *arena, World *world) {
(void) arena;
#define WRITE(v) FS_FileWrite(file, &(v), sizeof(v), offset); offset += sizeof(v)
#define WRITEN(v, n) FS_FileWrite(file, v, (n) * sizeof(*(v)), offset); offset += ((n) * sizeof(*(v)))
void SaveWorld(World *world) {
printf("--- Saving World ---\n");
printf(" - %d props\n", world->propCount);
printf(" - %d hitboxes\n", world->hitboxCount);
printf("Saving world\n");
OS_Handle file = FS_FileOpen(S("world.sgdat"), FS_ACCESS_WRITE);
FS_FileWrite(file, world, sizeof(World)+sizeof(NavMesh), 0);
FS_FileWrite(file, world->navMesh, sizeof(World)+sizeof(NavMesh), sizeof(World));
U64 offset = 0;
for (U32 i = 0; i < WORLD_TILE_TYPE_MAX; i++) {
World_Tile *tile = &world->tileTypes[i];
WRITE(tile->tag.count);
WRITEN(tile->tag.data, tile->tag.count);
WRITE(tile->rotation);
}
for (U32 i = 0; i < WORLD_PROP_TYPE_MAX; i++) {
World_PropType *type = &world->propTypes[i];
WRITE(type->tag.count);
WRITEN(type->tag.data, type->tag.count);
WRITE(type->scale);
}
WRITE(world->propCount);
WRITEN(world->props, WORLD_PROP_MAX);
WRITE(world->hitboxCount);
WRITEN(world->hitboxes, WORLD_HITBOX_MAX);
WRITEN(world->map, WORLD_MAP_MAX);
WRITE(world->portalCount);
WRITEN(world->portals, WORLD_PORTAL_MAX);
FS_FileClose(file);
printf("Saved world :)\n");
printf("--- World Saved ---\n");
}
World *LoadWorld(M_Arena *arena) {
printf("loading world\n");
OS_Handle file = FS_FileOpen(S("world.sgdat"), FS_ACCESS_READ);
World *world = M_ArenaPush(arena, World);
NavMesh *navMesh = M_ArenaPush(arena, NavMesh);
FS_FileRead(file, world, sizeof(World), 0);
FS_FileRead(file, navMesh, sizeof(NavMesh), sizeof(World));
FS_FileClose(file);
world->navMesh = navMesh;
world->arena = arena;
world->player.world = world;
printf("loaded world\n");
return world;
void LoadWorld(M_Arena *arena, World *world) {
printf("--- Loading World ---\n");
M_TempScope(0, 0) {
Str8 data = FS_ReadEntireFile(temp.arena, S("world.sgdat"));
U8 *base = data.data;
// Load tile types
//
world->tileTypes = M_ArenaPush(arena, World_Tile, .count = WORLD_TILE_TYPE_MAX);
for (U32 it = 0; it < WORLD_TILE_TYPE_MAX; ++it) {
World_Tile *type = &world->tileTypes[it];
Str8 name;
name.count = *(S64 *) base;
name.data = base + 8;
base += 8;
base += name.count;
if (name.count != 0) {
// Means we likely got a vaild tag
type->tag = Str8_Copy(arena, name);
type->rotation = *(F32 *) base;
}
base += 4;
}
// Load prop types
//
world->propTypes = M_ArenaPush(arena, World_PropType, .count = WORLD_PROP_TYPE_MAX);
for (U32 it = 0; it < WORLD_PROP_TYPE_MAX; ++it) {
World_PropType *type = &world->propTypes[it];
Str8 name;
name.count = *(S64 *) base;
name.data = base + 8;
base += 8;
base += name.count;
if (name.count != 0) {
type->tag = Str8_Copy(arena, name);
type->scale = *(F32 *) base;
}
base += 4;
}
// Prop locations
{
U32 n_props = *(U32 *) base; base += 4;
World_Prop *props = (World_Prop *) base;
world->propCount = n_props;
world->props = M_ArenaPush(arena, World_Prop, .count = WORLD_PROP_MAX);
M_CopySize(world->props, props, n_props * sizeof(World_Prop));
base += (WORLD_PROP_MAX * sizeof(World_Prop));
}
// Hitboxes
//
{
U32 n_hitbox = *(U32 *) base; base += 4;
World_Hitbox *boxes = (World_Hitbox *) base;
world->hitboxCount = n_hitbox;
world->hitboxes = M_ArenaPush(arena, World_Hitbox, .count = WORLD_HITBOX_MAX);
M_CopySize(world->hitboxes, boxes, n_hitbox * sizeof(World_Hitbox));
base += (WORLD_HITBOX_MAX * sizeof(World_Hitbox));
}
// Map
//
{
U32 *__map = (U32 *) base;
world->map = M_ArenaPush(arena, U32, .count = WORLD_MAP_MAX);
M_CopySize(world->map, __map, WORLD_MAP_MAX * sizeof(U32));
base += (WORLD_MAP_MAX * sizeof(U32));
}
{
U32 n_portals = *(U32 *) base; base += 4;
World_Portal *portals = (World_Portal *) base;
world->portalCount = n_portals;
world->portals = M_ArenaPush(arena, World_Portal, .count = WORLD_PORTAL_MAX);
M_CopySize(world->portals, portals, n_portals * sizeof(World_Portal));
base += (WORLD_PORTAL_MAX * sizeof(World_Portal));
}
if (base != (data.data + data.count)) {
printf("--- OFFSET MISMATCH ---\n");
}
printf("loaded world\n");
printf(" - %d props\n", world->propCount);
printf(" - %d hitboxes\n", world->hitboxCount);
printf("--- Loaded World ---\n");
}
}
void GenerateNavMesh(M_Arena *arena, World *world) {
world->navMesh = M_ArenaPush(arena, NavMesh);
world->navMesh->nodeCount = 0;
for(int i = 0; i < WORLD_MAP_MAX; i++) {
U32 x = (i % 96);
U32 y = (i / 96);
bool skip = false;
for(U32 hi = 0; hi < world->hitboxCount; hi++) {
if(AABB_Point(world->hitboxes[hi].box, V2F((F32) x, (F32) y))) {
skip = true;
break;
}
}
if(skip) {continue;}
world->navMesh->nodes[world->navMesh->nodeCount].pos.x = cast(F32) x;
world->navMesh->nodes[world->navMesh->nodeCount].pos.y = cast(F32) y;
world->navMesh->nodes[world->navMesh->nodeCount].connectionCount = 0;
for(int nx = -1; nx < 2; nx++){
for(int ny = -1; ny < 2; ny++){
if(nx==0 && ny==0) {continue;}
if(x+nx < 0 || x+nx > 95) {
continue;
}
if(y+ny < 0 || y+ny > 49) {
continue;
}
// It's quad for loop time :D
for(U32 hi = 0; hi < world->hitboxCount; hi++) {
if(AABB_Point(world->hitboxes[hi].box, V2F((F32) (x + nx), (F32) (y + ny)))) {
skip = true;
break;
}
}
U32 nCount = world->navMesh->nodeCount;
S32 index = -1;
for (U32 ohgod = 0; ohgod < nCount; ohgod++) {
if(world->navMesh->nodes[ohgod].pos.x == nx+x&& world->navMesh->nodes[ohgod].pos.y == y+ny) {
index=ohgod;
break;
}
}
F32 cost = 20;
if(ny+nx == 2) {
cost = 40;
};
if(Str8_Equal(world->tileTypes[world->map[i]].tag, S("path_middle"), STR8_EQUAL_IGNORE_CASE)) {
cost = 1;
if(Abs(ny+nx) == 2) {
cost = 4;
};
}
if(index < 0) {continue;}
skip |= (index > (S32) nCount);
if(skip) {continue;}
world->navMesh->nodes[nCount].connections[world->navMesh->nodes[nCount].connectionCount].NodeIndex = index;
world->navMesh->nodes[nCount].connections[world->navMesh->nodes[nCount].connectionCount].Cost = cost;
world->navMesh->nodes[index].connections[world->navMesh->nodes[index].connectionCount].NodeIndex = nCount;
world->navMesh->nodes[index].connections[world->navMesh->nodes[index].connectionCount].Cost = cost;
world->navMesh->nodes[nCount].connectionCount++;
world->navMesh->nodes[index].connectionCount++;
}
}
world->navMesh->nodeCount++;
}
for(U32 i = 0; i < world->npcCount; i++) {
world->npcs[i].mode = NPC_ACTION_WAITING;
world->npcs[i].maxWaitTime = Random_F32(&world->random, 20, 140);
world->npcs[i].waitTime = 0;
world->npcs[i].currentNavNode=0;
world->npcs[i].pathIndex = 0;
}
}

View File

@@ -6,7 +6,7 @@
#define NAV_MAX_PATH 1024
#define NAV_MAX_CONNECTIONS 8
#define NAV_MAX_NODES 4096
#define NAV_MAX_NODES 4800
typedef struct NavNode NavNode;

View File

@@ -46,10 +46,15 @@ struct NPC {
U32 targetNavNode;
// How long the npc has been walking to the next index
F32 walkTimer;
// Space within you can interact with the NPC.
F32 interationRadius;
//// Knowledge
// What the NPC knows about the bandit.
NPC_LOOK banditKnowledge;
// NPC clothes
G_Outfit outfit;
// if the NPC has given info
bool infoGiven;
};
function void NPCDraw(D_Context *draw, NPC *npc);
#endif // LD_GAME_NPC_H_

45
code/game/outfit.h Normal file
View File

@@ -0,0 +1,45 @@
#if !defined(LD_GAME_OUTFIT_H)
#define LD_GAME_OUTFIT_H
#define G_OUTFIT_COMPONENT_COUNT 8
global_var Str8 __outfit_names[] = {
Sl("npc_%s_base_%d"),
Sl("npc_%s_eyes_%d"),
Sl("npc_%s_face_%d"),
Sl("npc_%s_hair_%d"),
Sl("npc_%s_hat_%d"),
Sl("npc_%s_shirt_%d"),
Sl("npc_%s_shoes_%d"),
Sl("npc_%s_trousers_%d")};
global_var U32 __outfit_counts[] = {
2, // base
3, // eyes
5, // face
4, // hair
3, // hat
2, // shirt
1, // shoes
2 // trousers
};
global_var U32 __outfit_back_counts[] = {
2, // base
0, // eyes
3, // face
4, // hair
3, // hat
2, // shirt
1, // shoes
2 // trousers
};
StaticAssert(ArraySize(__outfit_names) == ArraySize(__outfit_counts));
#define OUTFIT_IMG(dir, n) D_ImageHandle(&game->draw, Sf(temp.arena, (const char *)__outfit_names[it].data, #dir, n))
function U32* GenOutfit(G_Outfit *o, World *world, G_State *game);
#endif // LD_GAME_OUTFIT_H

View File

@@ -34,6 +34,9 @@ struct Player
U32 health;
F32 reloadTimer;
U32 *knownDetails;
U32 detailCount;
};
function void PlayerInit(G_State *game, Player *player);

File diff suppressed because it is too large Load Diff

View File

@@ -2,25 +2,32 @@
#define LD_GAME_WORLD_H_
#include "../core/math.h"
#include "./aabb.h"
#define WORLD_HITBOX_MAX 4096
#define WORLD_PROP_MAX 2048
#define WORLD_PROP_TYPE_MAX 64
#define WORLD_TILE_TYPE_MAX 64
#define WORLD_MAP_MAX 4800
#define WORLD_PORTAL_MAX 64
// Areas are which
typedef U32 World_Area;
enum World_Area
{
WORLD_AREA_OUTSIDE = (1 << 0),
WORLD_AREA_SALOON = (1 << 1),
WORLD_AREA_OUTSIDE = 1,
WORLD_AREA_SALOON = 2,
};
typedef struct World_Tile World_Tile;
struct World_Tile {
U32 tile;
double rotation;
bool collision;
Str8 tag;
F32 rotation;
};
typedef struct World_PropType World_PropType;
struct World_PropType {
U32 assetHandle;
Str8 tag;
F32 scale;
};
@@ -32,6 +39,15 @@ struct World_Prop
V2f pos;
};
typedef struct World_Portal World_Portal;
struct World_Portal
{
AABB box;
World_Area area;
};
typedef World_Portal World_Hitbox;
typedef struct World World;
#include "player.h"
#include "npc.h"
@@ -44,14 +60,20 @@ struct World {
NavMesh *navMesh;
Random random;
V2f mouseProjected;
World_Tile tileTypes[64];
U32 map[4800];
World_PropType propTypes[64];
//// Loaded from world file
World_Tile *tileTypes;
World_PropType *propTypes;
World_Prop *props;
World_Hitbox *hitboxes;
World_Portal *portals;
U32 *map;
bool showPoster;
U32 propCount;
World_Prop props[256];
U32 portalCount;
U32 hitboxCount;
AABB hitboxes[4096];
Audio_Context *audio;
U32 audio_handles[2];
//// Player
Player player;
@@ -75,7 +97,8 @@ function void UpdateNPCs(F32 delta, World *world);
function void UpdateNPC(F32 delta, NPC *npc, World *world);
function void UpdateBandit(F32 delta, Bandit *bandit, World *world);
function World *LoadWorld(M_Arena *arena);
function void SaveWorld(M_Arena *arena, World *world);
function void LoadWorld(M_Arena *arena, World *world);
function void SaveWorld(World *world);
function void GenerateNavMesh(M_Arena *arena, World *world);
#endif // LD_GAME_WORLD_H_

43
code/os/audio.h Normal file
View File

@@ -0,0 +1,43 @@
#if !defined(LD_OS_AUDIO_H_)
#define LD_OS_AUDIO_H_
typedef struct Audio_Data Audio_Data;
struct Audio_Data {
S16 *samples;
U32 n_frames;
};
typedef struct Audio_Track Audio_Track;
struct Audio_Track {
B32 playing;
Audio_Data *data;
F32 volume;
U32 n_played;
U32 next; // to play if playing, free otherwise
};
typedef struct Audio_Context Audio_Context;
struct Audio_Context {
SDL_AudioStream *stream;
U32 n_tracks;
Audio_Track tracks[16];
U32 head;
U32 free;
F32 volume;
U32 n_sounds;
Audio_Data *sounds;
};
function void Audio_Init(M_Arena *arena, Audio_Context *audio, F32 volume);
function U32 Audio_Play(Audio_Context *audio, U32 index);
function void Audio_ChangeVolume(Audio_Context *audio, U32 handle, F32 volume);
#endif // LD_OS_AUDIO_H_

View File

@@ -4,7 +4,6 @@
#include "impl/linux/core.c"
#endif
Str8 FS_ReadEntireFile(M_Arena *arena, Str8 path) {
Str8 result = { 0 };
@@ -19,3 +18,164 @@ Str8 FS_ReadEntireFile(M_Arena *arena, Str8 path) {
return result;
}
internal void SDL_SubmitAudio(void *user, SDL_AudioStream *stream, int needed, int total) {
Audio_Context *audio = cast(Audio_Context *) user;
(void) total;
M_TempScope(0, 0) {
U32 prev = 0;
U32 it = audio->head;
U32 n_samples = needed >> 1;
U32 n_frames = n_samples >> 1;
F32 *left_f32 = M_ArenaPush(temp.arena, F32, .count = n_frames);
F32 *right_f32 = M_ArenaPush(temp.arena, F32, .count = n_frames);
while (it != 0) {
Audio_Track *track = &audio->tracks[it];
U32 next = track->next;
F32 *l0 = left_f32;
F32 *r0 = right_f32;
Assert(track->playing);
U32 remain = track->data->n_frames - track->n_played;
U32 play = Min(n_frames, remain);
U32 off = track->n_played << 1; // played n frames, thus double to n samples
for (U32 f = 0; f < play; ++f) {
*l0++ += cast(F32) (track->data->samples[off + (2 * f) + 0]) * track->volume * audio->volume;
*r0++ += cast(F32) (track->data->samples[off + (2 * f) + 1]) * track->volume * audio->volume;
}
track->n_played += play;
if (track->n_played == track->data->n_frames) {
if (prev == 0) {
// Head has finished playing
//
audio->head = track->next;
track->next = audio->free;
audio->free = it;
}
else {
// We're in the middle somewhere so prev->next == it->next
//
audio->tracks[prev].next = track->next;
track->next = audio->free;
audio->free = it;
}
track->playing = false;
}
it = next;
}
F32 *l0 = left_f32;
F32 *r0 = right_f32;
S16 *interleaved = M_ArenaPush(temp.arena, S16, .count = n_samples);
S16 *i0 = interleaved;
for (U32 n = 0; n < n_frames; ++n) {
*i0++ = cast(S16) (l0[n] + 0.5f);
*i0++ = cast(S16) (r0[n] + 0.5f);
}
SDL_PutAudioStreamData(stream, interleaved, needed);
}
}
void Audio_Init(M_Arena *arena, Audio_Context *audio, F32 volume) {
SDL_AudioSpec spec = { 0 };
spec.format = SDL_AUDIO_S16LE;
spec.channels = 2;
spec.freq = 44100;
audio->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, SDL_SubmitAudio, audio);
if (audio->stream) {
M_TempScope(0, 0) {
Str8 exec = FS_SystemPath(temp.arena, FS_SYSTEM_PATH_EXE);
Str8 path = Sf(temp.arena, "%.*s/assets", Sv(exec));
FS_List files = FS_PathList(temp.arena, path);
U32 n_audio = 0;
for (FS_Entry *it = files.first; it != 0; it = it->next) {
if (Str8_EndsWith(it->basename, S("wav"))) {
n_audio += 1;
}
}
audio->n_sounds = 0;
audio->sounds = M_ArenaPush(arena, Audio_Data, .count = n_audio);
for (FS_Entry *it = files.first; it != 0; it = it->next) {
if (Str8_EndsWith(it->basename, S("wav"))) {
Audio_Data *sound = &audio->sounds[audio->n_sounds];
U8 *data;
U32 count;
SDL_AudioSpec wav;
SDL_LoadWAV((const char *) it->path.data, &wav, &data, &count);
Assert(wav.freq == 44100);
sound->samples = (S16 *) data;
sound->n_frames = (count >> 2);
audio->n_sounds += 1;
}
}
audio->n_tracks = 16;
for (U32 it = 1; it < audio->n_tracks; ++it) {
audio->tracks[it].next = it + 1;
}
audio->tracks[audio->n_tracks - 1].next = 0;
audio->volume = volume;
audio->head = 0;
audio->free = 1;
printf("--- Loaded %d sounds ---\n", audio->n_sounds);
}
}
}
U32 Audio_Play(Audio_Context *audio, U32 index) {
U32 result = 0;
if (audio->free != 0 && SDL_LockAudioStream(audio->stream)) {
result = audio->free;
Audio_Track *track = &audio->tracks[result];
audio->free = track->next;
track->playing = true;
track->data = &audio->sounds[index];
track->n_played = 0;
track->volume = 1.0f;
track->next = audio->head;
audio->head = result;
SDL_UnlockAudioStream(audio->stream);
}
return result;
}
void Audio_ChangeVolume(Audio_Context *audio, U32 handle, F32 volume) {
if (SDL_LockAudioStream(audio->stream)) {
audio->tracks[handle].volume = volume;
SDL_UnlockAudioStream(audio->stream);
}
}

View File

@@ -17,5 +17,6 @@ function void VM_Decommit(void *base, U64 size);
function void VM_Release(void *base, U64 size);
#include "filesystem.h"
#include "audio.h"
#endif // LD_OS_CORE_H_

BIN
code/world.sgdat Normal file

Binary file not shown.