summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/disk.c1
-rw-r--r--src/entity.h2
-rw-r--r--src/flier.c9
-rw-r--r--src/hvy_guns.c12
-rw-r--r--src/incbin.h2
-rw-r--r--src/loader.c4
-rw-r--r--src/loader.h2
-rw-r--r--src/main.c100
-rw-r--r--src/main.h3
-rw-r--r--src/pacer.c2
-rw-r--r--src/particles.c2
-rw-r--r--src/particles.h2
-rw-r--r--src/player.c27
-rw-r--r--src/res/enemy.asebin914 -> 724 bytes
-rw-r--r--src/res/flier.asebin964 -> 691 bytes
-rw-r--r--src/res/icon.pngbin239 -> 211 bytes
-rw-r--r--src/res/pacer.asebin504 -> 504 bytes
-rw-r--r--src/res/padding.asebin401 -> 350 bytes
-rw-r--r--src/res/test.tmx4
-rw-r--r--src/res/tiny.tmx54
-rw-r--r--src/tilemap.c45
-rw-r--r--src/tilemap.h3
-rw-r--r--src/walker.c2
-rw-r--r--src/warp.c2
24 files changed, 188 insertions, 90 deletions
diff --git a/src/disk.c b/src/disk.c
index 084533f..7469337 100644
--- a/src/disk.c
+++ b/src/disk.c
@@ -6,6 +6,7 @@
 #include "util.h"
 #include "save.h"
 #include <string.h>
+#include <stdlib.h>
 #include <stdio.h>
 
 enum {
diff --git a/src/entity.h b/src/entity.h
index 038b62a..614d15f 100644
--- a/src/entity.h
+++ b/src/entity.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <SDL2/SDL.h>
+#include <SDL2/SDL_rect.h>
 
 #define from_fixed(a) ((a) / 16)
 #define to_fixed(a) ((a) * 16)
diff --git a/src/flier.c b/src/flier.c
index c507bd2..1fd3092 100644
--- a/src/flier.c
+++ b/src/flier.c
@@ -3,6 +3,7 @@
 #include "loader.h"
 #include "tilemap.h"
 #include <stdbool.h>
+#include <stdlib.h>
 #include <math.h>
 #include <string.h>
 #include "particles.h"
@@ -302,6 +303,7 @@ static int flier_update(struct entity *self) {
 			}
 			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > to_fixed(96)) {
 				self->state = FLIER_IDLE;
+				ext->reached_x = ext->reached_y = false; // not doing this makes them get lost and confused
 			}
 			self->timer--;
 			if (self->timer == 0) {
@@ -320,6 +322,12 @@ static int flier_update(struct entity *self) {
 				self->state = FLIER_ALERT_IDLE;
 				self->timer = ext->attack_delay;
 			}
+			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > to_fixed(96)) {
+				anim(self, FLIER_A_IDLE);
+				self->state = FLIER_IDLE;
+				self->timer = ext->idle_time;
+				ext->reached_x = ext->reached_y = false; // not doing this makes them get lost and confused
+			}
 			break;
 
 		case FLIER_ALERT_ATTACK:
@@ -339,6 +347,7 @@ static int flier_update(struct entity *self) {
 				anim(self, FLIER_A_IDLE);
 				self->state = FLIER_IDLE;
 				self->timer = ext->idle_time;
+				ext->reached_x = ext->reached_y = false; // not doing this makes them get lost and confused
 			}
 			break;
 	}
diff --git a/src/hvy_guns.c b/src/hvy_guns.c
index 1eabbe2..106cea6 100644
--- a/src/hvy_guns.c
+++ b/src/hvy_guns.c
@@ -324,8 +324,16 @@ int hvy_chaingun(struct gun *self, struct entity *parent, struct entity *target)
 		entities.projectiles++;
 		if (self->counter > HVY_CHAINGUN_MIN_RELOAD) {
 			self->counter--;
+			self->timer = self->counter;
+		} else {
+			// this allows the projectiles to keep cycling up and down
+			// without the fire rate getting all wonky
+			self->counter--;
+			self->timer = HVY_CHAINGUN_MIN_RELOAD;
+			if (self->counter <= HVY_CHAINGUN_MIN_RELOAD - 4) {
+				self->counter = HVY_CHAINGUN_MIN_RELOAD;
+			}
 		}
-		self->timer = self->counter;
 	} else {
 		self->counter = HVY_CHAINGUN_RELOAD;
 	}
@@ -335,7 +343,7 @@ int hvy_chaingun(struct gun *self, struct entity *parent, struct entity *target)
 void hvy_chaingun_new(struct gun *self, int x, int y) {
 	self->update = hvy_chaingun;
 	self->timer = HVY_CHAINGUN_RELOAD;
-	self->counter = 0;
+	self->counter = HVY_CHAINGUN_RELOAD;
 	self->x = to_fixed(x);
 	self->y = to_fixed(y);
 }
diff --git a/src/incbin.h b/src/incbin.h
index d768006..5dbd575 100644
--- a/src/incbin.h
+++ b/src/incbin.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#if defined(__APPLE__)
+#if defined(__APPLE__) || (defined(__MINGW32__) && !defined(__MINGW64__))
 extern char const game_icon[], game_icon_end[];
 #else
 extern char const _game_icon[], _game_icon_end[];
diff --git a/src/loader.c b/src/loader.c
index 54425d9..fed2a7c 100644
--- a/src/loader.c
+++ b/src/loader.c
@@ -4,13 +4,11 @@
 #include <zlib.h>
 #include <stdbool.h>
 
-#include <SDL2/SDL.h>
-
 #include "libplum.h"
 #include "zip.h"
 #include "loader.h"
 #include "util.h"
-#include "main.h"
+#include "main.h" // renderer
 #include "collision.h"
 
 static void *inflateWrapped(void *const restrict data, uint32_t const outsize);
diff --git a/src/loader.h b/src/loader.h
index 800ef0b..44f9d9f 100644
--- a/src/loader.h
+++ b/src/loader.h
@@ -2,7 +2,7 @@
 
 #include <stddef.h>
 #include "entity.h"
-#include "main.h"
+#include "main.h" // struct entities
 
 typedef char * name_T;
 
diff --git a/src/main.c b/src/main.c
index 41c239b..922ca20 100644
--- a/src/main.c
+++ b/src/main.c
@@ -4,8 +4,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <inttypes.h>
-#include <errno.h>
+#include <errno.h> // ENOENT
+#include <math.h> // used by vk2dSleep
 
 #include "input.h"
 #include "loader.h"
@@ -14,8 +14,8 @@
 
 #include "tilemap.h"
 
-#include "incbin.h"
-#include "libplum.h"
+#include "incbin.h" // game icon
+#include "libplum.h" // decoding game icon
 
 #include "main.h"
 
@@ -240,10 +240,19 @@ int game_load_level(char *level) {
 
 			game_state = STATE_FADE_OUT;
 			fade = 255;
-			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;}
+			int x, y;
+			if (tilemap->width < WINDOW_WIDTH / 8) {
+				x = -((WINDOW_WIDTH - tilemap->width * 8) / 2);
+			} else {
+				x = (entities.player[0].x / 16) - (WINDOW_WIDTH / 2);
+				if (x < 0) {x = 0;} else if (x + WINDOW_WIDTH > tilemap->width * 8) {x = tilemap->width * 8 - WINDOW_WIDTH;}
+			}
+			if (tilemap->height <= WINDOW_HEIGHT / 8) {
+				y = -((WINDOW_HEIGHT - tilemap->height * 8) / 2);
+			} else {
+				y = (entities.player[0].y / 16) - (WINDOW_HEIGHT / 2);
+				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);
@@ -264,6 +273,27 @@ void game_render_flush(SDL_Texture *framebuffer) {
 	SDL_RenderPresent(renderer);
 }
 
+// from libertea / <https://github.com/PaoloMazzon>
+double getTime(void) {
+  static double time = -1;
+  if (time == -1)
+    time = SDL_GetPerformanceCounter();
+  return (double)(SDL_GetPerformanceCounter() - time) / SDL_GetPerformanceFrequency();
+}
+
+// also from libertea / <https://github.com/PaoloMazzon>
+void vk2dSleep(double seconds) {
+    if (seconds <= 0)
+        return;
+    double start = SDL_GetPerformanceCounter();
+    double milliseconds = floor(seconds * 1000);
+    SDL_Delay(milliseconds);
+    while ((SDL_GetPerformanceCounter() - start) / (double)SDL_GetPerformanceFrequency() < seconds) {
+        volatile int i = 0;
+        (void) i;
+    }
+}
+
 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);
@@ -349,12 +379,13 @@ int main(int const argc, char *const *const argv) {
 	#if defined(__EMSCRIPTEN__)
 	scale = 1;
 	#else
-	if (scale <= 0) { // this looks very wrong
+	if (scale <= 0) { // because we used atoi, the user can set scale to -1 or something
 		SDL_DisplayMode dm;
 		if (SDL_GetDesktopDisplayMode(0, &dm) != 0) {
-			SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "couldnt get desktop size", SDL_GetError(), NULL);
-			fprintf(stderr, "info: couldnt get desktop size %s\n", SDL_GetError());
+			SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "couldnt get desktop size", SDL_GetError(), NULL);
+			fprintf(stderr, "warn: couldnt get desktop size %s\n", SDL_GetError());
 			flags |= SDL_WINDOW_RESIZABLE;
+			scale = 3;
 		} else {
 			int x = dm.w / 2 / WINDOW_WIDTH;
 			int y = dm.h / 2 / WINDOW_HEIGHT;
@@ -366,8 +397,10 @@ int main(int const argc, char *const *const argv) {
 			if (scale == 0) {
 				scale = 1;
 			}
-			if (dm.refresh_rate != 60) {
-				SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "refresh rate", "this game currently only runs well on 60Hz displays", NULL);
+			if (dm.refresh_rate < 59) {
+				SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_WARNING, "refresh rate", "your monitor appears to have a refresh rate below 60Hz, the game will run slower\n"
+				"you can compile the game from source to disable vsync, unlocking the framerate\n"
+				"TODO: disabling vsync without a recompile", NULL);
 			}
 		}
 	}
@@ -375,12 +408,15 @@ int main(int const argc, char *const *const argv) {
 	
 	window = SDL_CreateWindow(":3", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH * scale, WINDOW_HEIGHT * scale, flags | SDL_WINDOW_HIDDEN);
 	if (window == NULL) {
+		fprintf(stderr, "failed to create the game window: %s\n", SDL_GetError());
 		goto end;
 	}
 
+	SDL_SetWindowMinimumSize(window, WINDOW_WIDTH, WINDOW_HEIGHT);
 	SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); // hack, i dont wanna deal with windows discarding render textures
 	renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_TARGETTEXTURE | SDL_RENDERER_PRESENTVSYNC);
 	if (renderer == NULL) {
+		fprintf(stderr, "failed to create a rendering context: %s\n", SDL_GetError());
 		goto end;
 	}
 	SDL_RenderSetLogicalSize(renderer, WINDOW_WIDTH, WINDOW_HEIGHT);
@@ -482,6 +518,8 @@ void main_loop(void) {
 	int x = 0, y = 0;
 	while (1) {
 #endif
+		double const begin_time = getTime();
+
 		input_pressed = input_held;
 		SDL_Event evt;
 		while (SDL_PollEvent(&evt)) {
@@ -551,7 +589,7 @@ void main_loop(void) {
 					i = evt.tfinger.fingerId;
 					touch.positions[i].x = evt.tfinger.x;
 					touch.positions[i].y = evt.tfinger.y;
-					printf("%" PRIu64 " %" PRIu64 " %f %f\n", evt.tfinger.touchId, evt.tfinger.fingerId, evt.tfinger.x, evt.tfinger.y);
+					//printf("%" PRIu64 " %" PRIu64 " %f %f\n", evt.tfinger.touchId, evt.tfinger.fingerId, evt.tfinger.x, evt.tfinger.y);
 					reset_touch:
 					touch.input_touch = 0;
 					for (size_t i = 0; i < touch.allocated; i++) {
@@ -654,10 +692,18 @@ void main_loop(void) {
 					}
 				}
 				
-				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;}
+				if (tilemap->width < WINDOW_WIDTH / 8) {
+					x = -((WINDOW_WIDTH - tilemap->width * 8) / 2);
+				} else {
+					x = (entities.player[0].x / 16) - (WINDOW_WIDTH / 2);
+					if (x < 0) {x = 0;} else if (x + WINDOW_WIDTH > tilemap->width * 8) {x = tilemap->width * 8 - WINDOW_WIDTH;}
+				}
+				if (tilemap->height <= WINDOW_HEIGHT / 8) {
+					y = -((WINDOW_HEIGHT - tilemap->height * 8) / 2);
+				} else {
+					y = (entities.player[0].y / 16) - (WINDOW_HEIGHT / 2);
+					if (y < 0) {y = 0;} else if (y + WINDOW_HEIGHT > tilemap->height * 8) {y = tilemap->height * 8 - WINDOW_HEIGHT;}
+				}
 				
 				game_render(framebuffer, x, y);
 				game_render_flush(framebuffer);
@@ -675,10 +721,18 @@ void main_loop(void) {
 				if (fade == 255) {
 					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;}
+						if (tilemap->width < WINDOW_WIDTH / 8) {
+							x = -((WINDOW_WIDTH - tilemap->width * 8) / 2);
+						} else {
+							x = (entities.player[0].x / 16) - (WINDOW_WIDTH / 2);
+							if (x < 0) {x = 0;} else if (x + WINDOW_WIDTH > tilemap->width * 8) {x = tilemap->width * 8 - WINDOW_WIDTH;}
+						}
+						if (tilemap->height <= WINDOW_HEIGHT / 8) {
+							y = -((WINDOW_HEIGHT - tilemap->height * 8) / 2);
+						} else {
+							y = (entities.player[0].y / 16) - (WINDOW_HEIGHT / 2);
+							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);
@@ -705,6 +759,8 @@ void main_loop(void) {
 #if defined(__EMSCRIPTEN__)
 	return;
 #else
+		double const end_time = getTime();
+		vk2dSleep(1.0/60.0 - (end_time - begin_time));
 	}
 #endif
 	
diff --git a/src/main.h b/src/main.h
index ecd8250..2766a84 100644
--- a/src/main.h
+++ b/src/main.h
@@ -1,6 +1,7 @@
 #pragma once
 
-#include <SDL2/SDL.h>
+#include <SDL2/SDL_video.h>
+#include <SDL2/SDL_render.h>
 #include "entity.h"
 extern SDL_Window *window;
 extern SDL_Renderer *renderer;
diff --git a/src/pacer.c b/src/pacer.c
index 9bbcfb6..de9a94d 100644
--- a/src/pacer.c
+++ b/src/pacer.c
@@ -3,6 +3,8 @@
 #include "loader.h"
 #include "tilemap.h"
 #include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
 #include <math.h>
 #include "particles.h"
 #include "gun.h"
diff --git a/src/particles.c b/src/particles.c
index 3655f23..4432780 100644
--- a/src/particles.c
+++ b/src/particles.c
@@ -1,4 +1,4 @@
-#include <SDL2/SDL.h>
+#include "particles.h"
 
 SDL_Rect const particle_gray = {0, 0, 4, 4};
 SDL_Rect const particle_red = {4, 0, 4, 4};
diff --git a/src/particles.h b/src/particles.h
index c841817..bcfa6fe 100644
--- a/src/particles.h
+++ b/src/particles.h
@@ -1,5 +1,5 @@
 #pragma once
-#include <SDL2/SDL.h>
+#include <SDL2/SDL_rect.h>
 
 extern SDL_Rect const particle_gray;
 extern SDL_Rect const particle_red;
diff --git a/src/player.c b/src/player.c
index 40c5f6f..a28a6c9 100644
--- a/src/player.c
+++ b/src/player.c
@@ -4,6 +4,8 @@
 #include "input.h"
 #include "tilemap.h"
 #include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
 #include <math.h>
 #include "particles.h"
 
@@ -76,10 +78,10 @@ static int slash_update(struct projectile *self) {
 	} else {
 		self->hp++;
 	}
-	int x = from_fixed(self->x) + self->facing * sin(self->hp / 2) * SLASH_REACH_X;
-	int y = from_fixed(self->y) - cos(self->hp / 2) * SLASH_REACH_Y;
-	int xx = from_fixed(self->x) + self->facing * sin(self->hp / 2 + 1) * SLASH_REACH_X;
-	int yy = from_fixed(self->y) - cos(self->hp / 2 + 1) * SLASH_REACH_Y;
+	int x = from_fixed(self->x) + self->facing * sin(self->hp / 2.0) * SLASH_REACH_X;
+	int y = from_fixed(self->y) - cos(self->hp / 2.0) * SLASH_REACH_Y;
+	int xx = from_fixed(self->x) + self->facing * sin(self->hp / 2.0 + 1) * SLASH_REACH_X;
+	int yy = from_fixed(self->y) - cos(self->hp / 2.0 + 1) * SLASH_REACH_Y;
 	self->hitbox = (struct hitbox) {.left = x - 2, .right = x + 2, .top = y - 2, .bottom = y + 2};
 	for (int i = 0, e = entities.enemies; i < 64 && e; i++) {
 		if (entities.enemy[i].state) {
@@ -116,18 +118,18 @@ static int slash_draw(struct projectile *self, int camX, int camY) {
 	int const x = from_fixed(self->x) - camX, y = from_fixed(self->y) - camY;
 	if (self->hp >= 0) {
 		SDL_Vertex vertices[4] = {
-			{.position = {x + from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2 - 1) * SLASH_REACH_X, y - cos(self->hp / 2 - 1) * SLASH_REACH_Y}, .color = {255, 191, 63, 255}},
-			{.position = {x + self->facing * sin(self->hp / 2 + 0) * SLASH_REACH_X, y - cos(self->hp / 2 + 0) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
-			{.position = {x - from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2 + 1) * SLASH_REACH_X, y - cos(self->hp / 2 + 1) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
-			{.position = {x - from_fixed(self->velocity.x * 2) + self->facing * sin(self->hp / 2 + 2) * SLASH_REACH_X, y - cos(self->hp / 2 + 2) * SLASH_REACH_Y}, .color = {255, 127, 0, 0}},
+			{.position = {x + from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2.0 - 1) * SLASH_REACH_X, y - cos(self->hp / 2.0 - 1) * SLASH_REACH_Y}, .color = {255, 191, 63, 255}},
+			{.position = {x + self->facing * sin(self->hp / 2.0 + 0) * SLASH_REACH_X, y - cos(self->hp / 2.0 + 0) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
+			{.position = {x - from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2.0 + 1) * SLASH_REACH_X, y - cos(self->hp / 2.0 + 1) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
+			{.position = {x - from_fixed(self->velocity.x * 2) + self->facing * sin(self->hp / 2.0 + 2) * SLASH_REACH_X, y - cos(self->hp / 2.0 + 2) * SLASH_REACH_Y}, .color = {255, 127, 0, 0}},
 		};
 		SDL_RenderGeometry(renderer, NULL, vertices, 4, (int []) {0, 1, 2, 0, 2, 3}, 6);
 	} else {
 		SDL_Vertex vertices[4] = {
-			{.position = {x - from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2 + 1) * SLASH_REACH_X, y - cos(self->hp / 2 + 1) * SLASH_REACH_Y}, .color = {255, 191, 63, 255}},
-			{.position = {x + + self->facing * sin(self->hp / 2 + 0) * SLASH_REACH_X, y - cos(self->hp / 2 + 0) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
-			{.position = {x + from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2 - 1) * SLASH_REACH_X, y - cos(self->hp / 2 - 1) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
-			{.position = {x + from_fixed(self->velocity.x * 2) + self->facing * sin(self->hp / 2 - 2) * SLASH_REACH_X, y - cos(self->hp / 2 - 2) * SLASH_REACH_Y}, .color = {255, 127, 0, 0}},
+			{.position = {x - from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2.0 + 1) * SLASH_REACH_X, y - cos(self->hp / 2.0 + 1) * SLASH_REACH_Y}, .color = {255, 191, 63, 255}},
+			{.position = {x + + self->facing * sin(self->hp / 2.0 + 0) * SLASH_REACH_X, y - cos(self->hp / 2.0 + 0) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
+			{.position = {x + from_fixed(self->velocity.x) + self->facing * sin(self->hp / 2.0 - 1) * SLASH_REACH_X, y - cos(self->hp / 2.0 - 1) * SLASH_REACH_Y}, .color = {255, 127, 0, 255}},
+			{.position = {x + from_fixed(self->velocity.x * 2) + self->facing * sin(self->hp / 2.0 - 2) * SLASH_REACH_X, y - cos(self->hp / 2.0 - 2) * SLASH_REACH_Y}, .color = {255, 127, 0, 0}},
 		};
 		SDL_RenderGeometry(renderer, NULL, vertices, 4, (int []) {0, 1, 2, 0, 2, 3}, 6);
 	}
@@ -332,6 +334,7 @@ static int player_update(struct entity *self) {
 				self->timer = 60;
 			}
 			if (self->timer-- == 0) {
+				anim(self, PLAYER_A_IDLE);
 				self->state = PLAYER_IDLE;
 			}
 			break;
diff --git a/src/res/enemy.ase b/src/res/enemy.ase
index aa73c16..ce1bad6 100644
--- a/src/res/enemy.ase
+++ b/src/res/enemy.ase
Binary files differdiff --git a/src/res/flier.ase b/src/res/flier.ase
index 741409a..0577788 100644
--- a/src/res/flier.ase
+++ b/src/res/flier.ase
Binary files differdiff --git a/src/res/icon.png b/src/res/icon.png
index 04d5199..1298225 100644
--- a/src/res/icon.png
+++ b/src/res/icon.png
Binary files differdiff --git a/src/res/pacer.ase b/src/res/pacer.ase
index 6e05528..a9ff0ea 100644
--- a/src/res/pacer.ase
+++ b/src/res/pacer.ase
Binary files differdiff --git a/src/res/padding.ase b/src/res/padding.ase
index 3bdd076..39f4cfc 100644
--- a/src/res/padding.ase
+++ b/src/res/padding.ase
Binary files differdiff --git a/src/res/test.tmx b/src/res/test.tmx
index 5ac87be..a449d81 100644
--- a/src/res/test.tmx
+++ b/src/res/test.tmx
@@ -43,8 +43,8 @@
   <object id="5" name="tiny" type="warp" x="0" y="96" width="8" height="40">
    <properties>
     <property name="map" type="file" value="tiny.tmx"/>
-    <property name="tox" type="int" value="96"/>
-    <property name="toy" type="int" value="64"/>
+    <property name="tox" type="int" value="48"/>
+    <property name="toy" type="int" value="48"/>
    </properties>
   </object>
   <object id="6" name="walky" type="walker" x="112" y="112">
diff --git a/src/res/tiny.tmx b/src/res/tiny.tmx
index 6d593af..ece2481 100644
--- a/src/res/tiny.tmx
+++ b/src/res/tiny.tmx
@@ -1,16 +1,16 @@
 <?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="3">
+<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="11" height="9" 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">
-  <object id="1" name="test" type="warp" x="104" y="32" width="8" height="32">
+  <object id="1" name="test" type="warp" x="64" y="16" width="8" height="32">
    <properties>
     <property name="map" type="file" value="test.tmx"/>
     <property name="tox" type="int" value="16"/>
     <property name="toy" type="int" value="128"/>
    </properties>
   </object>
-  <object id="2" name="lavender disk" type="save" x="88" y="64">
+  <object id="2" name="lavender disk" type="save" x="48" y="48">
    <properties>
     <property name="color" type="color" value="#ff9d56d0"/>
    </properties>
@@ -21,38 +21,30 @@
   <properties>
    <property name="middleground" type="bool" value="true"/>
   </properties>
-  <layer id="1" name="Tile Layer 1" width="21" height="13" offsetx="-4" offsety="-4">
+  <layer id="1" name="Tile Layer 1" width="11" height="9" offsetx="-4" offsety="-4">
    <data encoding="csv">
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,9,13,13,13,13,13,5,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,3,4,4,4,4,4,2,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,9,13,13,13,13,13,5,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,3,4,4,4,4,4,2,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,9,13,13,13,13,13,5,0,0,
+0,0,3,4,4,4,4,4,2,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,9,13,13,13,13,13,5,0,0,
+0,0,3,4,4,4,4,4,2,0,0,
+0,0,0,0,0,0,0,0,0,0,0
 </data>
   </layer>
-  <layer id="2" name="Tile Layer 2" width="21" height="13">
+  <layer id="2" name="Tile Layer 2" width="11" height="9">
    <data encoding="csv">
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,38,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,56,53,54,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,38,0,0,0,
+0,0,56,53,54,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0
 </data>
   </layer>
  </group>
diff --git a/src/tilemap.c b/src/tilemap.c
index 34e7c43..ccfe990 100644
--- a/src/tilemap.c
+++ b/src/tilemap.c
@@ -1,11 +1,11 @@
 #include <stdio.h>
+#include <stdlib.h>
 #include <stdint.h>
 #include <string.h>
-
-#include <SDL2/SDL.h>
+#include <limits.h>
 
 #include "loader.h"
-#include "main.h"
+#include "main.h" // renderer
 
 #include "tilemap.h"
 #include "collision.h"
@@ -71,17 +71,13 @@ collision_T tilemap_area(struct tilemap *tilemap, int x1, int y1, int x2, int y2
 	if (x1 < 0) {
 		x1 = 0;
 	}
-	if (x2 < 0) {
-		x2 = 0;
-	} else if (x2 >= tilemap->width) {
+	if (x2 >= (signed) tilemap->width) {
 		x2 = tilemap->width - 1;
 	}
 	if (y1 < 0) {
 		y1 = 0;
 	}
-	if (y2 < 0) { // for some reason you can bonk your head on nothing without this
-		y2 = 0;
-	} else if (y2 >= tilemap->height) {
+	if (y2 >= (signed) tilemap->height) {
 		y2 = tilemap->height - 1;
 	}
 	return tilemap_area_raw(tilemap, x1, y1, x2, y2);
@@ -90,14 +86,36 @@ collision_T tilemap_area(struct tilemap *tilemap, int x1, int y1, int x2, int y2
 void tilemap_background(struct tilemap *tilemap, int x, int y, int w, int h) {
 	SDL_SetRenderDrawColor(renderer, tilemap->backdrop.r, tilemap->backdrop.g, tilemap->backdrop.b, tilemap->backdrop.a);
 	SDL_RenderFillRect(renderer, &(SDL_Rect) {0, 0, w, h});
+	int bx = 0, by = 0, vw = w, vh = h;
+	if (x < 0) {
+		bx = -x;
+		vw = w + x * 2;
+		x = 0;
+	}
+	if (y < 0) {
+		by = -y;
+		vh = h + y * 2;
+		y = 0;
+	}
 	for (int i = 0; i < tilemap->middleground; i++) {
-		SDL_RenderCopy(renderer, tilemap->tilemaps[i], &(SDL_Rect) {x * tilemap->parallax[i].x, y * tilemap->parallax[i].y, w, h}, &(SDL_Rect) {0, 0, w, h});
+		SDL_RenderCopy(renderer, tilemap->tilemaps[i], &(SDL_Rect) {x * tilemap->parallax[i].x, y * tilemap->parallax[i].y, vw, vh}, &(SDL_Rect) {bx, by, vw, vh});
 	}
 }
 
 void tilemap_foreground(struct tilemap *tilemap, int x, int y, int w, int h) {
+	int bx = 0, by = 0, vw = w, vh = h;
+	if (x < 0) {
+		bx = -x;
+		vw = w + x * 2;
+		x = 0;
+	}
+	if (y < 0) {
+		by = -y;
+		vh = h + y * 2;
+		y = 0;
+	}
 	for (int i = tilemap->middleground; i < tilemap->layers; i++) {
-		SDL_RenderCopy(renderer, tilemap->tilemaps[i], &(SDL_Rect) {x * tilemap->parallax[i].x, y * tilemap->parallax[i].y, w, h}, &(SDL_Rect) {0, 0, w, h});
+		SDL_RenderCopy(renderer, tilemap->tilemaps[i], &(SDL_Rect) {x * tilemap->parallax[i].x, y * tilemap->parallax[i].y, vw, vh}, &(SDL_Rect) {bx, by, vw, vh});
 	}
 }
 
@@ -163,7 +181,7 @@ struct tilemap *tilemap_load(void *data, size_t size) {
 		struct blob col = res_get_collision(str);
 		
 		SDL_RenderCopy(renderer, tex, &(SDL_Rect) {0, 0, 128, height}, &(SDL_Rect) {0, y, 128, height});
-		if (col.data != NULL) { // silence -fsanitize=undefined
+		if (col.data != NULL) { // col.data is only NULL when col.size is 0 but -fsanitize=undefined doesnt know that
 			if (col.size + y * 2 > 0xf0) {
 				fprintf(stderr, "warn: '%s' overflows tile properties\n", str);
 				col.size = 0xf0 - y * 2;
@@ -213,6 +231,9 @@ struct tilemap *tilemap_load(void *data, size_t size) {
 	if (map->width * map->height * map->layers > size - sizeof (struct map)) {
 		goto fail;
 	}
+	if (map->width > INT_MAX || map->height > INT_MAX) {
+		goto fail;
+	}
 
 	tilemap->backdrop.r = map->backdrop.r;
 	tilemap->backdrop.g = map->backdrop.g;
diff --git a/src/tilemap.h b/src/tilemap.h
index 0f2bebe..4b6774a 100644
--- a/src/tilemap.h
+++ b/src/tilemap.h
@@ -21,6 +21,9 @@ extern struct tilemap {
 } *tilemap, *next_tilemap;
 
 // tilemap collision functions
+// public functions use pixel coordinates (not subpixel)
+// raw functions have no bounds checking and use tile coordinates
+// you shouldnt need to use the raw functions
 collision_T tilemap_tile_raw(struct tilemap *tilemap, int x, int y);
 collision_T tilemap_tile(struct tilemap *tilemap, int x, int y);
 
diff --git a/src/walker.c b/src/walker.c
index 6441909..b3dc680 100644
--- a/src/walker.c
+++ b/src/walker.c
@@ -3,6 +3,8 @@
 #include "loader.h"
 #include "tilemap.h"
 #include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
 #include <math.h>
 #include "particles.h"
 
diff --git a/src/warp.c b/src/warp.c
index 3556ef3..c9bb1b4 100644
--- a/src/warp.c
+++ b/src/warp.c
@@ -2,6 +2,8 @@
 #include "entity.h"
 #include "loader.h"
 #include "tilemap.h"
+#include <string.h>
+#include <stdlib.h>
 
 static int warp_update(struct warp *self) {
 	if (