summary refs log tree commit diff
diff options
context:
space:
mode:
authorzlago2024-10-23 19:17:26 +0200
committerzlago2024-10-23 19:17:26 +0200
commitb12606899c98d7fc7a120c2b79797b5c45283ad2 (patch)
treef210a037fee0f2346bae8a10d1edc1b445b4324f
parentaf6acead62498bc49065ef828e388bcd511ce54d (diff)
hacky save system
-rwxr-xr-xbuild.sh5
-rw-r--r--src/flier.c7
-rw-r--r--src/funsinit.c3
-rw-r--r--src/main.c148
-rw-r--r--src/main.h3
-rw-r--r--src/res/big.tmx5
-rw-r--r--src/res/save.asebin0 -> 394 bytes
-rw-r--r--src/res/tiny.tmx8
-rw-r--r--src/save.c110
-rw-r--r--src/tilemap.c3
-rw-r--r--src/util.c16
-rw-r--r--src/util.h5
-rw-r--r--src/walker.c30
13 files changed, 283 insertions, 60 deletions
diff --git a/build.sh b/build.sh
index 60ad14c..c85c145 100755
--- a/build.sh
+++ b/build.sh
@@ -14,7 +14,10 @@ case $1 in
 		NS=ub CFLAGS='-g -Og -fsanitize=undefined -lubsan' make
 		;;
 	emscripten)
-		NS=em EXTENSION=html CC='/usr/lib/emscripten/emcc' CFLAGS='-g2 -O1 -sUSE_SDL=2 -sUSE_ZLIB' LDFLAGS='-sASYNCIFY --embed-file out/assets.res@./assets.res' make
+		NS=em EXTENSION=js CC='/usr/lib/emscripten/emcc' CFLAGS='-g2 -O2 -sUSE_SDL=2 -sUSE_ZLIB' LDFLAGS='-sASYNCIFY --embed-file out/assets.res@./assets.res' make
+		;;
+	em-dbg)
+		NS=em-dbg EXTENSION=html CC='/usr/lib/emscripten/emcc' CFLAGS='-g2 -O0 -sUSE_SDL=2 -sUSE_ZLIB --memoryprofiler' LDFLAGS='-sASYNCIFY -lidbfs.js --embed-file out/assets.res@./assets.res' make
 		;;
 	*)
 		echo >&2 "dont know how to build '$1'"
diff --git a/src/flier.c b/src/flier.c
index bfce46a..f51a8a1 100644
--- a/src/flier.c
+++ b/src/flier.c
@@ -89,7 +89,12 @@ static int bullet_update(struct projectile *self) {
 }
 
 static int bullet_draw(struct projectile *self, int camX, int camY) {
-	SDL_Rect rect = {4, 0, 4, 4};
+	SDL_Rect rect;
+	if (self->hp & 0x2) {
+		rect = (SDL_Rect) {4, 0, 4, 4};
+	} else {
+		rect = (SDL_Rect) {12, 0, 4, 4};
+	}
 	SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x) - camX - 1, from_fixed(self->y) - camY - 1, 4, 4});
 	SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x - self->velocity.x) - camX - 1, from_fixed(self->y - self->velocity.y) - camY - 1, 4, 4});
 	return 0;
diff --git a/src/funsinit.c b/src/funsinit.c
index 8e5581a..50469c6 100644
--- a/src/funsinit.c
+++ b/src/funsinit.c
@@ -2,6 +2,8 @@
 #include "loader.h"
 #include "main.h"
 
+void *save_new(struct entities *entities);
+int save_property(void *const restrict entity, char const *const restrict property, char const *const restrict value);
 void *walker_new(struct entities *entities);
 int walker_property(void *const restrict entity, char const *const restrict property, char const *const restrict value);
 void *flier_new(struct entities *entities);
@@ -11,6 +13,7 @@ void *warp_new(struct entities *entities);
 int warp_property(void *const restrict entity, char const *const restrict property, char const *const restrict value);
 
 void funs_init(void) {
+	res_push_fun(save_new, save_property, "save");
 	res_push_fun(walker_new, walker_property, "walker");
 	res_push_fun(flier_new, flier_property, "flier");
 	res_push_fun(warp_new, warp_property, "warp");
diff --git a/src/main.c b/src/main.c
index 345f6fb..7afd152 100644
--- a/src/main.c
+++ b/src/main.c
@@ -4,6 +4,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <inttypes.h>
+#include <errno.h>
 
 #include "input.h"
 #include "loader.h"
@@ -21,7 +23,8 @@
 SDL_Window *window = NULL;
 SDL_Renderer *renderer = NULL;
 enum game_state game_state = STATE_PLAYING;
-char *game_next_level;
+char *game_level, *game_next_level;
+char *save_file_name;
 
 static void *particle_tex = NULL;
 
@@ -192,12 +195,9 @@ int game_update(void) {
 void game_render(SDL_Texture *framebuffer, int x, int y) {
 	SDL_SetRenderTarget(renderer, framebuffer);
 	tilemap_background(tilemap, x, y, WINDOW_WIDTH, WINDOW_HEIGHT);
-	entities.player[0].draw(entities.player, x, y);
-	for (int i = 0, e = 0; i < 64 && e < entities.enemies; i++) {
-		if (entities.enemy[i].state) {
-			e++;
-			entities.enemy[i].draw(entities.enemy + i, x, y);
-		}
+	for (int i = 0; i < entities.particles; i++) {
+		struct particle *self = entities.particle + i;
+		SDL_RenderCopy(renderer, particle_tex, &self->rect, &(SDL_Rect) {from_fixed(self->x) - x, from_fixed(self->y) - y, self->rect.w, self->rect.h});
 	}
 	for (int i = 0, e = 0; i < 64 && e < entities.projectiles; i++) {
 		if (entities.projectile[i].state) {
@@ -205,11 +205,50 @@ void game_render(SDL_Texture *framebuffer, int x, int y) {
 			entities.projectile[i].draw(entities.projectile + i, x, y);
 		}
 	}
-	for (int i = 0; i < entities.particles; i++) {
-		struct particle *self = entities.particle + i;
-		SDL_RenderCopy(renderer, particle_tex, &self->rect, &(SDL_Rect) {from_fixed(self->x) - x, from_fixed(self->y) - y, self->rect.w, self->rect.h});
+	for (int i = 0, e = 0; i < 64 && e < entities.enemies; i++) {
+		if (entities.enemy[i].state) {
+			e++;
+			entities.enemy[i].draw(entities.enemy + i, x, y);
+		}
 	}
+	entities.player[0].draw(entities.player, x, y);
 	tilemap_foreground(tilemap, x, y, WINDOW_WIDTH, WINDOW_HEIGHT);
+	// hacky hp render
+	if (entities.player[0].hp) {
+		SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
+		for (int i = 0; i < entities.player[0].hp; i++) {
+			SDL_RenderFillRect(renderer, &(SDL_Rect) {1, 1 + i * 5, 4, 4});
+		}
+	}
+}
+
+int game_load_level(char *level) {
+	struct blob blob = res_get_map(level);
+	next_tilemap = tilemap_load(blob.data, blob.size);
+	if (next_tilemap != NULL) {
+		if (entities_load(&next_entities, blob.data, blob.size, next_tilemap->input_bytes)) {
+			tilemap_free(next_tilemap);
+		} else {
+			tilemap_free(tilemap);
+			tilemap = next_tilemap;
+			game_level = level;
+			entities_free(&entities);
+			memcpy(&entities, &next_entities, sizeof (entities));
+
+			game_state = STATE_FADE_OUT;
+			int x = (entities.player[0].x / 16) - (WINDOW_WIDTH / 2);
+			int y = (entities.player[0].y / 16) - (WINDOW_HEIGHT / 2);
+			if (x < 0) {x = 0;} else if (x + WINDOW_WIDTH > tilemap->width * 8) {x = tilemap->width * 8 - WINDOW_WIDTH;}
+			if (y < 0) {y = 0;} else if (y + WINDOW_HEIGHT > tilemap->height * 8) {y = tilemap->height * 8 - WINDOW_HEIGHT;}
+			
+			game_render(framebuffer, x, y);
+			SDL_SetRenderTarget(renderer, NULL);
+			SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
+			SDL_RenderClear(renderer);
+			return 0;
+		}
+	}
+	return 1;
 }
 
 void game_render_flush(SDL_Texture *framebuffer) {
@@ -222,6 +261,9 @@ void game_render_flush(SDL_Texture *framebuffer) {
 }
 
 int main(int const argc, char *const *const argv) {
+	save_file_name = SDL_GetPrefPath("sylvie", "game");
+	save_file_name = SDL_realloc(save_file_name, strlen(save_file_name) + strlen("save.sav") + 1);
+	strcat(save_file_name, "save.sav");
 	struct option opts[] = {
 		{"help",       0, NULL, 'h'},
 		{"scale",      1, NULL, 's'},
@@ -364,6 +406,44 @@ int main(int const argc, char *const *const argv) {
 		
 		particle_tex = res_get_texture("particles").data;
 
+		player_new(&next_entities);
+		FILE *file = fopen(save_file_name, "rb");
+		if (file == NULL) {
+			if (errno != ENOENT) {
+				perror(save_file_name);
+				goto end;
+			}
+			player_property(next_entities.player, "x", "40");
+			player_property(next_entities.player, "y", "64");
+			game_load_level("untitled");
+		} else {
+			size_t filesize, len;
+			char *filedata = util_loadFile(file, &filesize); // hack: we leak a tiny bit of memory here
+			fclose(file);
+			char *level = filedata;
+			len = strnlen(filedata, filesize);
+			if (len == filesize) {
+				fputs("invalid save format\n", stderr);
+				goto end;
+			}
+			filedata += len + 1;
+			len = strnlen(filedata, filesize);
+			if (len == filesize) {
+				fputs("invalid save format\n", stderr);
+				goto end;
+			}
+			player_property(next_entities.player, "x", filedata);
+			filedata += len + 1;
+			len = strnlen(filedata, filesize);
+			if (len == filesize) {
+				fputs("invalid save format\n", stderr);
+				goto end;
+			}
+			player_property(next_entities.player, "y", filedata);
+			level = realloc(level, strlen(level) + 1);
+			game_load_level(level);
+		}
+		#if 0
 		struct blob blob = res_get_map("untitled");
 		next_tilemap = tilemap_load(blob.data, blob.size);
 		if (next_tilemap == NULL) {
@@ -376,6 +456,7 @@ int main(int const argc, char *const *const argv) {
 			goto end;
 		}
 		tilemap = next_tilemap;
+		#endif
 	}
 
 	/* unsigned error; // identical variable is declared higher up */
@@ -397,10 +478,6 @@ int main(int const argc, char *const *const argv) {
 
 	SDL_ShowWindow(window);
 	
-	player_new(&next_entities);
-	player_property(next_entities.player, "x", "40");
-	player_property(next_entities.player, "y", "64");
-	memcpy(&entities, &next_entities, sizeof (entities));
 #if defined(__EMSCRIPTEN__)
 	emscripten_set_main_loop(main_loop, 60, 0); // TODO: how do i query the framerate if i set it to 0?
 	return 0;
@@ -413,11 +490,11 @@ int main(int const argc, char *const *const argv) {
 	return 0;
 }
 
-int x = 0, y = 0, fade = 0;
+int x = 0, y = 0, fade = 255;
 
 void main_loop(void) {
 #else
-	int x = 0, y = 0, fade = 0;
+	int x = 0, y = 0, fade = 255;
 	while (1) {
 #endif
 		input_pressed = input_held;
@@ -432,10 +509,12 @@ void main_loop(void) {
 					if (evt.key.repeat)
 						break;
 					
+					#if !defined(__EMSCRIPTEN__)
 					// check for ^Q and exit if pressed
 					if (evt.key.state == SDL_PRESSED && evt.key.keysym.sym == SDLK_q && evt.key.keysym.mod & KMOD_CTRL) {
 						goto end;
 					}
+					#endif
 					
 					//static_assert(INPUT_LENGTH <= sizeof(input_held) * CHAR_BIT); // if this trips up, scope creep happened
 					for (unsigned key = 0, bit = 1; key < INPUT_LENGTH; key++, bit <<= 1) {
@@ -473,8 +552,12 @@ void main_loop(void) {
 				case SDL_FINGERDOWN:
 					i = evt.tfinger.fingerId;
 					if (i >= touch.allocated) {
+						size_t const start = touch.allocated;
 						touch.allocated = i + 1;
 						touch.positions = realloc(touch.positions, sizeof (struct touch_vec) * touch.allocated);
+						for (size_t index = start; index < i - 1; index++) {
+							touch.positions[index].active = 0;
+						}
 					}
 					touch.positions[i].active = 1;
 				case SDL_FINGERMOTION:
@@ -588,29 +671,18 @@ void main_loop(void) {
 				SDL_RenderFillRect(renderer, &(SDL_Rect) {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT});
 				SDL_RenderPresent(renderer);
 				if (fade == 255) {
-					struct blob blob = res_get_map(game_next_level);
-					next_tilemap = tilemap_load(blob.data, blob.size);
-					if (next_tilemap != NULL) {
-						if (entities_load(&next_entities, blob.data, blob.size, next_tilemap->input_bytes)) {
-							tilemap_free(next_tilemap);
-						} else {
-							tilemap_free(tilemap);
-							tilemap = next_tilemap;
-							entities_free(&entities);
-							memcpy(&entities, &next_entities, sizeof (entities));
-						}
+					if (game_load_level(game_next_level)) {
+						game_state = STATE_FADE_OUT;
+						x = (entities.player[0].x / 16) - (WINDOW_WIDTH / 2);
+						y = (entities.player[0].y / 16) - (WINDOW_HEIGHT / 2);
+						if (x < 0) {x = 0;} else if (x + WINDOW_WIDTH > tilemap->width * 8) {x = tilemap->width * 8 - WINDOW_WIDTH;}
+						if (y < 0) {y = 0;} else if (y + WINDOW_HEIGHT > tilemap->height * 8) {y = tilemap->height * 8 - WINDOW_HEIGHT;}
+						
+						game_render(framebuffer, x, y);
+						SDL_SetRenderTarget(renderer, NULL);
+						SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
+						SDL_RenderClear(renderer);
 					}
-					game_state = STATE_FADE_OUT;
-
-					x = (entities.player[0].x / 16) - (WINDOW_WIDTH / 2);
-					y = (entities.player[0].y / 16) - (WINDOW_HEIGHT / 2);
-					if (x < 0) {x = 0;} else if (x + WINDOW_WIDTH > tilemap->width * 8) {x = tilemap->width * 8 - WINDOW_WIDTH;}
-					if (y < 0) {y = 0;} else if (y + WINDOW_HEIGHT > tilemap->height * 8) {y = tilemap->height * 8 - WINDOW_HEIGHT;}
-					
-					game_render(framebuffer, x, y);
-					SDL_SetRenderTarget(renderer, NULL);
-					SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
-					SDL_RenderClear(renderer);
 				}
 				break;
 			
diff --git a/src/main.h b/src/main.h
index ef214bb..88e1c13 100644
--- a/src/main.h
+++ b/src/main.h
@@ -25,7 +25,8 @@ extern enum game_state {
 	STATE_FADE_OUT,
 } game_state;
 
-extern char *game_next_level;
+extern char *game_level, *game_next_level;
+extern char *save_file_name;
 
 void entities_free(struct entities *entities);
 int entities_load(struct entities *entities, char *data, size_t size, size_t input_bytes);
diff --git a/src/res/big.tmx b/src/res/big.tmx
index 7aef7bd..d9d35ce 100644
--- a/src/res/big.tmx
+++ b/src/res/big.tmx
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="65" height="33" tilewidth="8" tileheight="8" infinite="0" backgroundcolor="#1d2b79" nextlayerid="5" nextobjectid="6">
+<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="65" height="33" tilewidth="8" tileheight="8" infinite="0" backgroundcolor="#1d2b79" nextlayerid="5" nextobjectid="7">
  <tileset firstgid="1" source="autotiles.tsx"/>
  <tileset firstgid="33" source="tileset.tsx"/>
  <group id="3" name="Group Layer 1">
@@ -101,5 +101,8 @@
   <object id="5" name="walky" type="walker" x="200" y="112">
    <point/>
   </object>
+  <object id="6" name="disk" type="save" x="448" y="120">
+   <point/>
+  </object>
  </objectgroup>
 </map>
diff --git a/src/res/save.ase b/src/res/save.ase
new file mode 100644
index 0000000..8932b1b
--- /dev/null
+++ b/src/res/save.ase
Binary files differdiff --git a/src/res/tiny.tmx b/src/res/tiny.tmx
index 71953f8..6d593af 100644
--- a/src/res/tiny.tmx
+++ b/src/res/tiny.tmx
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="21" height="13" tilewidth="8" tileheight="8" infinite="0" nextlayerid="5" nextobjectid="2">
+<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="21" height="13" tilewidth="8" tileheight="8" infinite="0" nextlayerid="5" nextobjectid="3">
  <tileset firstgid="1" source="autotiles.tsx"/>
  <tileset firstgid="33" source="tileset.tsx"/>
  <objectgroup id="4" name="Object Layer 1">
@@ -10,6 +10,12 @@
     <property name="toy" type="int" value="128"/>
    </properties>
   </object>
+  <object id="2" name="lavender disk" type="save" x="88" y="64">
+   <properties>
+    <property name="color" type="color" value="#ff9d56d0"/>
+   </properties>
+   <point/>
+  </object>
  </objectgroup>
  <group id="3" name="Group Layer 1">
   <properties>
diff --git a/src/save.c b/src/save.c
new file mode 100644
index 0000000..577ec61
--- /dev/null
+++ b/src/save.c
@@ -0,0 +1,110 @@
+#include "main.h"
+#include "entity.h"
+#include "loader.h"
+#include "tilemap.h"
+#include "input.h"
+#include "util.h"
+#include <string.h>
+#include <stdio.h>
+
+enum {
+	SAVE_A_IDLE,
+	SAVE_A_SPIN,
+	SAVE_A_SPIN2,
+	SAVE_A_SPIN3,
+};
+
+struct anim save_anims[] = {
+	{SAVE_A_IDLE, {0, 0, 16, 16}, 300},
+	{SAVE_A_SPIN2, {16, 0, 16, 16}, 5},
+	{SAVE_A_SPIN3, {32, 0, 16, 16}, 5},
+	{SAVE_A_IDLE, {16, 0, 16, 16}, 5},
+};
+
+static void save_free(struct entity *self) {
+	self->state = 0;
+	free(self->ext), self->ext = NULL;
+}
+
+static int save_update(struct entity *self) {
+	if (self->timer > 0) {
+		self->timer--;
+	}
+	if (
+		self->hitbox.left < entities.player[0].hitbox.right &&
+		self->hitbox.right > entities.player[0].hitbox.left &&
+		self->hitbox.top < entities.player[0].hitbox.bottom &&
+		self->hitbox.bottom > entities.player[0].hitbox.top &&
+		input_s(input_pressed) && self->timer == 0
+	) {
+		printf("%s %u %u\n", game_level, from_fixed(entities.player[0].x), from_fixed(entities.player[0].y));
+		self->timer = 20;
+		self->anim = save_anims[SAVE_A_SPIN];
+		FILE *file = fopen(save_file_name, "wb");
+		fwrite(game_level, 1, strlen(game_level) + 1, file);
+		char buf[12] = {0};
+		snprintf(buf, sizeof (buf) - 1, "%i", from_fixed(entities.player[0].x));
+		fwrite(buf, 1, strlen(buf) + 1, file);
+		snprintf(buf, sizeof (buf) - 1, "%i", from_fixed(entities.player[0].y));
+		fwrite(buf, 1, strlen(buf) + 1, file);
+		fclose(file);
+	}
+	self->anim.length--;
+	if (self->anim.length == 0) {
+		self->anim = save_anims[self->anim.frame];
+	}
+	return 0;
+}
+
+static int save_hurt(struct entity *self, int damage) {
+	return 0;
+}
+
+static int save_draw(struct entity *self, int camX, int camY) {
+	SDL_Rect rect = self->anim.rect;
+	SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x) - camX - 8, from_fixed(self->y) - camY - 16, 16, 16});
+	if (self->ext != NULL) {
+		rect.y += 16;
+		struct color const *const color = self->ext;
+		SDL_SetTextureColorMod(self->texture, color->r, color->g, color->b);
+		SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x) - camX - 8, from_fixed(self->y) - camY - 16, 16, 16});
+		SDL_SetTextureColorMod(self->texture, 255, 255, 255);
+	}
+	return 0;
+}
+
+struct entity *save_new(struct entities *entities) {
+	struct entity *self = entities->enemy + entities->enemies;
+	*self = (struct entity) {
+		.update = save_update,
+		.hurt = save_hurt,
+		.draw = save_draw,
+		.free = save_free,
+		.x = 0, .y = 0,
+		.state = 1,
+		.timer = 0,
+		.ext = NULL,
+	};
+	self->anim = save_anims[SAVE_A_SPIN];
+	self->texture = res_get_texture("save").data;
+	entities->enemies++;
+	return self;
+}
+
+int save_property(struct entity *const restrict self, char const *const restrict property, char const *const restrict value) {
+	if (strcmp(property, "x") == 0) {
+		self->x = to_fixed(atoi(value));
+	} else if (strcmp(property, "y") == 0) {
+		self->y = to_fixed(atoi(value));
+	} else if (strcmp(property, "color") == 0) {
+		free(self->ext);
+		self->ext = malloc(sizeof (struct color));
+		if (util_stringToColor(self->ext, value)) {
+			return 1;
+		}
+	} else {
+		return 1;
+	}
+	self->hitbox = (struct hitbox) {.left = from_fixed(self->x) - 8, .top = from_fixed(self->y) - 16, .right = from_fixed(self->x) + 8, .bottom = from_fixed(self->y)};
+	return 0;
+}
diff --git a/src/tilemap.c b/src/tilemap.c
index e7ee1d4..34e7c43 100644
--- a/src/tilemap.c
+++ b/src/tilemap.c
@@ -102,6 +102,9 @@ void tilemap_foreground(struct tilemap *tilemap, int x, int y, int w, int h) {
 }
 
 void tilemap_free(struct tilemap *tilemap) {
+	if (tilemap == NULL) {
+		return;
+	}
 	SDL_DestroyTexture(tilemap->tileset);
 	SDL_DestroyTexture(tilemap->wang_tileset);
 	free(tilemap->parallax);
diff --git a/src/util.c b/src/util.c
index 21aeb22..1f7a546 100644
--- a/src/util.c
+++ b/src/util.c
@@ -65,3 +65,19 @@ char *util_executableRelativePath(char const *const path, char const *const exec
 	memcpy(filePath + dirLength, path, fileLength);
 	return filePath;
 }
+
+int util_stringToColor(struct color *color, char const *const str) {
+	if (str[0] != '#' || strnlen(str, 10) != 9) {
+		return 1;
+	}
+	char *out;
+	unsigned long colorint = strtoul(str + 1, &out, 16);
+	if (out != str + 9) {
+		return 1;
+	}
+	color->r = colorint >> 24;
+	color->g = colorint >> 16;
+	color->b = colorint >> 8;
+	color->a = colorint >> 0;
+	return 0;
+}
diff --git a/src/util.h b/src/util.h
index 2a9f689..3aafc66 100644
--- a/src/util.h
+++ b/src/util.h
@@ -1,4 +1,9 @@
 #pragma once
 
+struct color {
+	unsigned char r, g, b, a;
+};
+
 void *util_loadFile(FILE *const restrict file, size_t *const restrict outsize);
 char *util_executableRelativePath(char const *const path, char const *const execPath, size_t dirLength); // allocated on the heap
+int util_stringToColor(struct color *color, char const *const str);
diff --git a/src/walker.c b/src/walker.c
index fbb80d4..49595ae 100644
--- a/src/walker.c
+++ b/src/walker.c
@@ -82,16 +82,6 @@ static int bullet_update(struct projectile *self) {
 	int x = from_fixed(self->x);
 	int y = from_fixed(self->y);
 	self->hitbox = (struct hitbox) {.left = x, .right = x, .top = y, .bottom = y};
-	#if 0
-		struct particle *part = entities.particle + entities.particles;
-		part->x = self->x + to_fixed(self->facing >= 0? sin(self->hp): -sin(self->hp)) * 6;
-		part->y = self->y - to_fixed(cos(self->hp)) * 4;
-		part->velocity = (struct vec2) {0, 0};
-		part->rect = (SDL_Rect) {0, 0, 4, 4};
-		part->acceleration = (struct vec2) {0, 0};
-		part->hp = 5;
-		entities.particles++;
-	#endif
 	if (hitbox_overlap(self->hitbox, entities.player[0].hitbox)) {
 		entities.player[0].hurt(entities.player + 0, 1);
 		return 1;
@@ -104,7 +94,12 @@ static int bullet_update(struct projectile *self) {
 }
 
 static int bullet_draw(struct projectile *self, int camX, int camY) {
-	SDL_Rect rect = {4, 0, 4, 4};
+	SDL_Rect rect;
+	if (self->hp & 0x2) {
+		rect = (SDL_Rect) {4, 0, 4, 4};
+	} else {
+		rect = (SDL_Rect) {12, 0, 4, 4};
+	}
 	SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x) - camX - 1, from_fixed(self->y) - camY - 1, 4, 4});
 	SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x - self->velocity.x) - camX - 1, from_fixed(self->y - self->velocity.y) - camY - 1, 4, 4});
 	//SDL_RenderFillRect(renderer, &(SDL_Rect) {from_fixed(self->x) - camX - 8, from_fixed(self->y) - camY - 12, 16, 16});
@@ -277,6 +272,12 @@ static int walker_update(struct entity *self) {
 			move(self, 0);
 			ext->facing = self->x > entities.player[0].x? -1: 1;
 			self->facing = ext->facing;
+			self->timer--;
+			if (self->timer == 0) {
+				anim(self, WALKER_A_ATTACK);
+				self->state = WALKER_ALERT_ATTACK;
+				self->timer = ext->attack_delay2;
+			}
 			if (abs(entities.player[0].x - self->x) > CHASE_RANGE) {
 				if (is_walkable(self->x + to_fixed(ext->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
 					anim(self, WALKER_A_WALK);
@@ -294,12 +295,7 @@ static int walker_update(struct entity *self) {
 			}
 			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > ext->give_up_range) {
 				self->state = WALKER_IDLE;
-			}
-			self->timer--;
-			if (self->timer == 0) {
-				anim(self, WALKER_A_ATTACK);
-				self->state = WALKER_ALERT_ATTACK;
-				self->timer = ext->attack_delay2;
+				self->timer = ext->idle_time;
 			}
 			break;