summaryrefslogtreecommitdiff
path: root/world.c
diff options
context:
space:
mode:
authorcitrons <citrons@mondecitronne.com>2023-10-11 23:58:12 -0500
committercitrons <citrons@mondecitronne.com>2023-10-11 23:58:12 -0500
commitab32d51871248e0fbf6758a425eda2256712d519 (patch)
treeba48813a3a75f79eea5c867496d77fdb8a3251db /world.c
initial commit
Diffstat (limited to 'world.c')
-rw-r--r--world.c306
1 files changed, 306 insertions, 0 deletions
diff --git a/world.c b/world.c
new file mode 100644
index 0000000..8e22470
--- /dev/null
+++ b/world.c
@@ -0,0 +1,306 @@
+#include <SDL.h>
+
+#include "world.h"
+#include "random.h"
+#include "generator.h"
+#include "save.h"
+
+SDL_Color tile_colors[] = {
+ [TILE_RED] = {0xFF, 0x00, 0x00, 0xFF},
+ [TILE_GREEN] = {0x00, 0xFF, 0x00, 0xFF},
+ [TILE_BLUE] = {0x00, 0x00, 0xFF, 0xFF},
+ [TILE_LIGHT] = {0xFF, 0xFF, 0xFF, 0xFF},
+ [TILE_EMPTY] = {0x00, 0x00, 0x00, 0xFF},
+ [TILE_WALL] = {0x50, 0x50, 0x50, 0xFF},
+};
+
+chunk *get_chunk(world *w, SDL_Point chunk_pos) {
+ for (int i = 0; i < MAX_CHUNKS; i++) {
+ chunk *c = &w->chunks[i];
+ if (!c->loaded) continue;
+ if (c->pos.x == chunk_pos.x && c->pos.y == chunk_pos.y) {
+ c->active = SDL_TRUE;
+ return c;
+ }
+ }
+ return NULL;
+}
+
+SDL_bool chunk_exists(world *w, SDL_Point chunk_pos) {
+ if (get_chunk(w, chunk_pos)) return SDL_TRUE;
+ if (is_chunk_saved(w, chunk_pos)) return SDL_TRUE;
+ return SDL_FALSE;
+}
+
+static void unload_chunk(world *w, chunk *c) {
+ save_chunk(w, c);
+ c->active = SDL_FALSE;
+ c->loaded = SDL_FALSE;
+}
+
+static void deactivate_chunk(chunk *c) {
+ c->active = SDL_FALSE;
+}
+
+chunk *load_chunk(world *w, SDL_Point chunk_pos) {
+ chunk *c = get_chunk(w, chunk_pos);
+ if (c) return c;
+
+ static int last_loaded = 0;
+ for (int i = 0; i < MAX_CHUNKS; i++) {
+ chunk *wc = &w->chunks[(last_loaded + i) % MAX_CHUNKS];
+ if (!wc->active) {
+ last_loaded = i;
+ c = wc;
+ break;
+ }
+ }
+ if (!c) return NULL;
+
+ if (c->loaded) unload_chunk(w, c);
+
+ c->pos = chunk_pos;
+ c->active = SDL_TRUE;
+ c->dirty = SDL_TRUE;
+ c->loaded = SDL_TRUE;
+ if (!load_saved_chunk(w, c))
+ generate_chunk(w, c);
+
+ return c;
+}
+
+SDL_Texture *render_chunk(SDL_Renderer *rend, chunk *c) {
+ if (!c->tex) {
+ c->tex = SDL_CreateTexture(rend,
+ SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING,
+ CHUNK_DIM, CHUNK_DIM);
+ SDL_assert(c->tex);
+ c->dirty = SDL_TRUE;
+ }
+ if (c->dirty) {
+ int pitch;
+ void *out;
+ SDL_assert(SDL_LockTexture(c->tex, NULL, &out, &pitch) >= 0);
+ SDL_Color *pixels = out;
+
+ for (int i = 0; i < CHUNK_DIM * CHUNK_DIM; i++)
+ pixels[i] = tile_colors[c->tiles[i]];
+
+ SDL_UnlockTexture(c->tex);
+ c->dirty = SDL_FALSE;
+ }
+ return c->tex;
+}
+
+tile get_tile(world *w, SDL_Point world_pos) {
+ chunk *c = load_chunk(w, chunk_pos_at(world_pos));
+ SDL_assert(c);
+ return c->tiles[tile_index(pos_in_chunk(world_pos))];
+}
+
+void set_tile(world *w, SDL_Point world_pos, tile t) {
+ chunk *c = load_chunk(w, chunk_pos_at(world_pos));
+ SDL_assert(c);
+ c->tiles[tile_index(pos_in_chunk(world_pos))] = t;
+ c->dirty = SDL_TRUE;
+}
+
+SDL_bool is_solid(tile t) {
+ return t == TILE_WALL;
+}
+
+SDL_bool player_grounded(world *w) {
+ if (SDL_TICKS_PASSED(SDL_GetTicks(), w->player.stamina_time))
+ return SDL_FALSE;
+ SDL_bool grounded = SDL_FALSE;
+ for (int y = w->player.pos.y; y < w->player.pos.y + 2; y++) {
+ for (int x = w->player.pos.x - 1; x < w->player.pos.x + 2; x++)
+ grounded = grounded || is_solid(get_tile(w, (SDL_Point) {x, y}));
+ }
+ return grounded;
+}
+
+void player_walk(world *w, int x, int y) {
+ if (!SDL_TICKS_PASSED(SDL_GetTicks(), w->player.walk_wait))
+ return;
+
+ if (x != 0 || y != 0) {
+ SDL_Point new_pos = w->player.pos;
+ new_pos.x += x;
+ if (player_grounded(w))
+ new_pos.y += y;
+ if (is_solid(get_tile(w, new_pos))) {
+ if (y == 0 && player_grounded(w)) {
+ new_pos = w->player.pos;
+ new_pos.y -= 1;
+ if (!is_solid(get_tile(w, new_pos)))
+ w->player.pos = new_pos;
+ }
+ } else w->player.pos = new_pos;
+ w->player.walk_wait = SDL_GetTicks() + PLAYER_WALK_DELAY;
+ }
+}
+
+void player_collect(world *w, SDL_Point pos) {
+ tile t = get_tile(w, pos);
+ if (t < MAX_COLLECTIBLE) {
+ w->player.scores[t] += 1;
+ set_tile(w, pos, TILE_EMPTY);
+ }
+}
+
+void player_place(world *w, int x, int y) {
+ if (!player_grounded(w)) return;
+ if (!SDL_TICKS_PASSED(SDL_GetTicks(), w->player.place_wait)) return;
+ if (w->player.scores[TILE_LIGHT] < 1) return;
+
+ w->player.scores[TILE_LIGHT]--;
+ w->player.place_wait = SDL_GetTicks() + PLAYER_PLACE_DELAY;
+
+ SDL_Point place = {w->player.pos.x + x, w->player.pos.y + y};
+ if (!is_solid(get_tile(w, place))) {
+ player_collect(w, place);
+ set_tile(w, place, TILE_WALL);
+ } else {
+ SDL_Point push = {w->player.pos.x - x, w->player.pos.y - y};
+ if (!is_solid(get_tile(w, push))) {
+ set_tile(w, w->player.pos, TILE_WALL);
+ w->player.pos = push;
+ }
+ }
+}
+
+
+void player_destroy(world *w, int x, int y) {
+ if (!SDL_TICKS_PASSED(SDL_GetTicks(), w->player.place_wait)) return;
+
+ SDL_Point destroy = {w->player.pos.x + x, w->player.pos.y + y};
+ tile t = get_tile(w, destroy);
+
+ if (t != TILE_EMPTY) {
+ if (t < MAX_COLLECTIBLE) {
+ player_collect(w, destroy);
+ } else {
+ if (w->player.scores[TILE_LIGHT] < 1) return;
+ w->player.scores[TILE_LIGHT]--;
+
+ set_tile(w, destroy, TILE_EMPTY);
+ w->player.place_wait = SDL_GetTicks() + PLAYER_PLACE_DELAY;
+ }
+ }
+}
+
+void draw_world(world *w, SDL_Window *win, SDL_Renderer *rend) {
+ int sw, sh;
+ SDL_GetWindowSize(win, &sw, &sh);
+
+ double view_scale;
+ if (sh < sw)
+ view_scale = (double) sh / (double) VIEW_DIM;
+ else
+ view_scale = (double) sw / (double) VIEW_DIM;
+
+ double view_width = (double) sw / view_scale;
+ double view_height = (double) sh / view_scale;
+ double view_x = w->view_pos.x - view_width / 2;
+ double view_y = w->view_pos.y - view_height / 2;
+
+ SDL_Rect view_rect = {view_x, view_y, view_width + 1, view_height + 1};
+ for (int i = 0; i < MAX_CHUNKS; i++) {
+ chunk *c = &w->chunks[i];
+ if (c->active) {
+ SDL_Rect chunk_rect = {
+ c->pos.x * CHUNK_DIM, c->pos.y * CHUNK_DIM,
+ CHUNK_DIM, CHUNK_DIM,
+ };
+ if (!SDL_HasIntersection(&view_rect, &chunk_rect))
+ deactivate_chunk(c);
+ }
+ }
+
+ SDL_Point min = chunk_pos_at((SDL_Point) {view_x, view_y});
+ SDL_Point max = chunk_pos_at(
+ (SDL_Point) {view_x + view_width, view_y + view_height});
+ for (int y = min.y; y <= max.y; y++) {
+ for (int x = min.x; x <= max.x; x++) {
+ chunk *c = load_chunk(w, (SDL_Point) {x, y});
+ if (!c) continue;
+ SDL_Texture *tex = render_chunk(rend, c);
+
+ int world_x = x * CHUNK_DIM;
+ int world_y = y * CHUNK_DIM;
+ int draw_x = ((double) world_x - view_x) * view_scale;
+ int draw_y = ((double) world_y - view_y) * view_scale;
+ SDL_Rect draw_rect = {
+ draw_x, draw_y,
+ (double) CHUNK_DIM * view_scale + 0.5,
+ (double) CHUNK_DIM * view_scale + 0.5,
+ };
+ SDL_RenderCopy(rend, tex, NULL, &draw_rect);
+ }
+ }
+
+ int player_x = (w->player.pos.x - view_x) * view_scale;
+ int player_y = (w->player.pos.y - view_y) * view_scale;
+ SDL_Rect player_rect = {
+ player_x, player_y, view_scale + 0.5, view_scale + 0.5,
+ };
+ SDL_SetRenderDrawColor(rend, 0xFF, 0x00, 0xFF, 0xFF);
+ SDL_RenderFillRect(rend, &player_rect);
+}
+
+void tick_world(world *w) {
+ get_rand(); // shuffle RNG
+
+ player_collect(w, w->player.pos);
+ int to_combine = SDL_min(w->player.scores[TILE_RED],
+ SDL_min(w->player.scores[TILE_GREEN], w->player.scores[TILE_BLUE]));
+ w->player.scores[TILE_LIGHT] += to_combine;
+ w->player.scores[TILE_RED] -= to_combine;
+ w->player.scores[TILE_GREEN] -= to_combine;
+ w->player.scores[TILE_BLUE] -= to_combine;
+
+ tile below = get_tile(w,
+ (SDL_Point) {w->player.pos.x, w->player.pos.y + 1});
+ if (is_solid(below))
+ w->player.stamina_time = SDL_GetTicks() + PLAYER_STAMINA;
+
+ if (!player_grounded(w)) {
+ if (SDL_TICKS_PASSED(SDL_GetTicks(), w->player.gravity_time)) {
+ SDL_Point new_pos = {w->player.pos.x, w->player.pos.y + 1};
+ if (!is_solid(get_tile(w, new_pos)))
+ w->player.pos = new_pos;
+ w->player.gravity_time = SDL_GetTicks() + PLAYER_GRAVITY;
+ }
+ }
+
+ if (player_grounded(w)) {
+ if (w->view_pos.x < w->player.pos.x)
+ w->view_pos.x += 1;
+ else if (w->view_pos.x > w->player.pos.x)
+ w->view_pos.x -= 1;
+ if (w->view_pos.y < w->player.pos.y)
+ w->view_pos.y += 1;
+ else if (w->view_pos.y > w->player.pos.y)
+ w->view_pos.y -= 1;
+ } else {
+ if (w->view_pos.x < w->player.pos.x - VIEW_DIM / 3)
+ w->view_pos.x += 1;
+ else if (w->view_pos.x > w->player.pos.x + VIEW_DIM / 3)
+ w->view_pos.x -= 1;
+ if (w->view_pos.y < w->player.pos.y - VIEW_DIM / 3)
+ w->view_pos.y += 1;
+ else if (w->view_pos.y > w->player.pos.y + VIEW_DIM / 3)
+ w->view_pos.y -= 1;
+ }
+
+ if (SDL_TICKS_PASSED(SDL_GetTicks(), w->save_time)) {
+ save_world(w);
+ w->save_time = SDL_GetTicks() + SAVE_INTERVAL;
+ }
+}
+
+void init_world(world *w) {
+ load_world(w);
+ w->view_pos = w->player.pos;
+}