summary refs log tree commit diff
path: root/src/pacer.c
diff options
context:
space:
mode:
authorzlago2024-10-28 10:27:19 +0100
committerzlago2024-10-28 10:29:08 +0100
commitf7e4de7c45ed848312690ce6cfb62ae562e8347c (patch)
tree23d4afd3af64690c789249ce7eebb30137581657 /src/pacer.c
parentee316a07cdfb01e52694edef2cc998e672e2885b (diff)
pacer enemy [wip]
Diffstat (limited to 'src/pacer.c')
-rw-r--r--src/pacer.c330
1 files changed, 330 insertions, 0 deletions
diff --git a/src/pacer.c b/src/pacer.c
new file mode 100644
index 0000000..9bbcfb6
--- /dev/null
+++ b/src/pacer.c
@@ -0,0 +1,330 @@
+#include "main.h"
+#include "entity.h"
+#include "loader.h"
+#include "tilemap.h"
+#include <stdbool.h>
+#include <math.h>
+#include "particles.h"
+#include "gun.h"
+
+#define HP 15
+#define WIDTH 8
+#define HEIGHT 10
+#define ACCELERATION 1
+#define FRICTION 2
+#define MAX_SPEED 8
+#define GRAVITY 1
+#define WALKABLE_CHECK_DISTANCE 8
+#define DETECTION_RANGE to_fixed(80)
+#define GIVE_UP_RANGE to_fixed(128)
+#define FAR_RANGE to_fixed(56)
+#define NEAR_RANGE to_fixed(40)
+#define COLLISION_DAMAGE 1
+#define COLLISION_IFRAMES 60
+
+struct pacer_ext {
+	unsigned detection_range, give_up_range;
+	signed facing;
+	struct gun primary, secondary;
+};
+
+enum {
+	PACER_NONE,
+	PACER_FALL,
+	PACER_IDLE,
+	PACER_PATROL,
+	PACER_ALERT_NEAR,
+	PACER_ALERT_APPROACH,
+	PACER_ALERT_HALT_FAR,
+	PACER_ALERT_FAR,
+	PACER_ALERT_BACK_AWAY,
+	PACER_ALERT_HALT_NEAR,
+};
+
+enum {
+	PACER_A_IDLE,
+	PACER_A_IDLE2,
+	PACER_A_REST,
+	PACER_A_WALK,
+	PACER_A_WALK2,
+	PACER_A_WALK3,
+	PACER_A_WALK4,
+};
+
+static struct anim pacer_anims[] = {
+	{PACER_A_IDLE2, {0, 0, 16, 16}, 300},
+	{PACER_A_IDLE, {16, 0, 16, 16}, 2},
+	{PACER_A_REST, {16, 0, 16, 16}, 300},
+	{PACER_A_WALK2, {0, 0, 16, 16}, 6},
+	{PACER_A_WALK3, {32, 0, 16, 16}, 6},
+	{PACER_A_WALK4, {0, 0, 16, 16}, 6},
+	{PACER_A_WALK, {48, 0, 16, 16}, 6},
+};
+
+void hvy_blaster_new(struct gun *self, int x, int y);
+void hvy_shotgun_new(struct gun *self, int x, int y);
+void hvy_repeater_new(struct gun *self, int x, int y);
+void hvy_chaingun_new(struct gun *self, int x, int y);
+void hvy_launcher_new(struct gun *self, int x, int y);
+void hvy_laser_new(struct gun *self, int x, int y);
+
+static collision_T collide(struct entity *self) {
+	return tilemap_area(tilemap, from_fixed(self->x) - WIDTH / 2, from_fixed(self->y) - HEIGHT, from_fixed(self->x) + WIDTH / 2, from_fixed(self->y));
+}
+
+static int move(struct entity *self, signed direction) {
+	int on_ground = false;
+	int const dx = direction * ACCELERATION;
+	self->velocity.x += dx;
+	// deaccel
+	if (dx == 0) {
+		if (self->velocity.x < -FRICTION) {
+			self->velocity.x += FRICTION;
+		} else if (self->velocity.x > FRICTION) {
+			self->velocity.x -= FRICTION;
+		} else {
+			self->velocity.x = 0;
+		}
+	}
+	// speed cap
+	if (self->velocity.x > MAX_SPEED) {
+		self->velocity.x = MAX_SPEED;
+	} else if (self->velocity.x < -MAX_SPEED) {
+		self->velocity.x = -MAX_SPEED;
+	}
+	// x collision
+	self->x += self->velocity.x;
+	collision_T const cx = collide(self);
+	if (collision_solid(cx)) {
+		if (self->velocity.x < 0) {
+			self->x += to_fixed(8) - ((self->x - to_fixed(WIDTH / 2)) % to_fixed(8)); // left
+		} else if (self->velocity.x == 0) {
+			//fputs("what?\n", stderr);
+		} else {
+			self->x -= ((self->x + to_fixed(WIDTH / 2)) % to_fixed(8)); // right
+		}
+		self->velocity.x = 0;
+	}
+	self->velocity.y += GRAVITY;
+	// y collision
+	self->y += self->velocity.y;
+	collision_T const cy = collide(self);
+	if (collision_solid(cy)) {
+		if (self->velocity.y < 0) {
+			self->y += to_fixed(8) - ((self->y - to_fixed(HEIGHT)) % to_fixed(8)); // up
+			self->velocity.y = 0;
+		} else if (self->velocity.y == 0) {
+			//fputs("what?\n", stderr);
+		} else {
+			self->y -= ((self->y) % to_fixed(8)); // down
+			self->velocity.y = to_fixed(1); // crazy but it works
+			on_ground = true;
+		}
+	}
+	
+	if (collision_hazard(cx | cy)) {
+		self->hurt(self, (struct damage) {1, 60});
+	}
+	
+	self->hitbox.left = from_fixed(self->x) - WIDTH / 2;
+	self->hitbox.right = from_fixed(self->x) + WIDTH / 2;
+	self->hitbox.top = from_fixed(self->y) - HEIGHT;
+	self->hitbox.bottom = from_fixed(self->y);
+	return on_ground;
+}
+
+static void anim(struct entity *self, unsigned anim) {
+	self->anim = pacer_anims[anim];
+}
+
+static inline int is_walkable(int const x, int const y) {
+	collision_T const front1 = tilemap_tile(tilemap, from_fixed(x), from_fixed(y) - 4);
+	collision_T const front2 = tilemap_tile(tilemap, from_fixed(x), from_fixed(y) - 10);
+	collision_T const floor = tilemap_tile(tilemap, from_fixed(x), from_fixed(y) + 4);
+	return !collision_solid(front1) && !collision_solid(front2) && collision_solid(floor) && !collision_hazard(floor);
+}
+
+static void pacer_free(struct entity *self) {
+	self->state = 0;
+	free(self->ext), self->ext = NULL;
+}
+
+static int pacer_update(struct entity *self) {
+	struct pacer_ext *const ext = self->ext;
+	if (self->hp <= 0) {
+		pacer_free(self);
+		entities.enemies--;
+		return 1;
+	}
+	switch (self->state) {
+		case PACER_IDLE:
+			move(self, 0);
+			ext->primary.update(&ext->primary, self, NULL);
+			ext->secondary.update(&ext->secondary, self, NULL);
+			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) < ext->detection_range) {
+				self->facing = self->x > entities.player[0].x? -1: 1;
+				anim(self, PACER_A_WALK);
+				self->state = PACER_ALERT_APPROACH;
+			}
+			break;
+		
+		case PACER_ALERT_NEAR:
+			move(self, 0);
+			self->facing = self->x > entities.player[0].x? -1: 1;
+			if (ext->primary.update(&ext->primary, self, NULL)) {
+				anim(self, PACER_A_WALK);
+				self->state = PACER_ALERT_BACK_AWAY;
+			}
+			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > ext->give_up_range) {
+				anim(self, PACER_A_REST);
+				self->state = PACER_IDLE;
+			}
+			break;
+
+		case PACER_ALERT_APPROACH:
+			move(self, self->facing);
+			if (hitbox_overlap(self->hitbox, entities.player[0].hitbox)) {
+				entities.player[0].hurt(entities.player + 0, (struct damage) {COLLISION_DAMAGE, COLLISION_IFRAMES});
+			}
+			if (abs(entities.player[0].x - self->x) < NEAR_RANGE || !is_walkable(self->x + to_fixed(self->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
+				anim(self, PACER_A_IDLE);
+				self->state = PACER_ALERT_HALT_NEAR;
+			}
+			break;
+
+		case PACER_ALERT_HALT_NEAR: // velocity affects bullets
+			move(self, 0);
+			if (self->velocity.x == 0) {
+				if (ext->primary.update(&ext->primary, self, entities.player)) {
+					anim(self, PACER_A_IDLE);
+					self->state = PACER_ALERT_NEAR;
+				}
+			}
+			break;
+
+		case PACER_ALERT_FAR:
+			move(self, 0);
+			self->facing = self->x > entities.player[0].x? -1: 1;
+			if (ext->secondary.update(&ext->secondary, self, NULL)) {
+				anim(self, PACER_A_WALK);
+				self->state = PACER_ALERT_APPROACH;
+			}
+			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > ext->give_up_range) {
+				anim(self, PACER_A_REST);
+				self->state = PACER_IDLE;
+			}
+			break;
+
+		case PACER_ALERT_BACK_AWAY:
+			move(self, -self->facing);
+			if (hitbox_overlap(self->hitbox, entities.player[0].hitbox)) {
+				entities.player[0].hurt(entities.player + 0, (struct damage) {COLLISION_DAMAGE, COLLISION_IFRAMES});
+			}
+			if (abs(entities.player[0].x - self->x) > FAR_RANGE || !is_walkable(self->x + to_fixed(-self->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
+				anim(self, PACER_A_IDLE);
+				self->state = PACER_ALERT_HALT_FAR;
+			}
+			break;
+
+		case PACER_ALERT_HALT_FAR: // velocity affects bullets
+			move(self, 0);
+			if (self->velocity.x == 0) {
+				if (ext->secondary.update(&ext->secondary, self, entities.player)) {
+					anim(self, PACER_A_IDLE);
+					self->state = PACER_ALERT_FAR;
+				}
+			}
+			break;
+	}
+	if (self->iframes > 0) {
+		self->iframes--;
+	}
+	self->anim.length--;
+	if (self->anim.length == 0) {
+		anim(self, self->anim.frame);
+	}
+	return 0;
+}
+
+static int pacer_hurt(struct entity *self, struct damage damage) {
+	if (self->iframes == 0) {
+		self->hp -= damage.damage;
+		self->iframes = damage.iframes;
+		for (int x = -1; x <= 1; x += 2) {
+			for (int y = -1; y <= 1; y += 2) {
+				struct particle *part = entities.particle + entities.particles;
+				part->x = self->x;
+				part->y = self->y;
+				part->velocity = (struct vec2) {to_fixed(x) * 1.5, to_fixed(y) * 1.5};
+				part->rect = particle_white;
+				part->acceleration = (struct vec2) {-x, -y};
+				part->hp = 24;
+				entities.particles++;
+			}
+		}
+	}
+	return 0;
+}
+
+static int pacer_draw(struct entity *self, int camX, int camY) {
+	if (!(self->iframes & 0x4)) {
+		SDL_Rect rect = self->anim.rect;
+		if (self->facing == -1) {
+			rect.y += 16;
+		}
+		SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x) - camX - 8, from_fixed(self->y) - camY - 16, 16, 16});
+	}
+	return 0;
+}
+
+struct entity *pacer_new(struct entities *entities) {
+	struct entity *self = entities->enemy + entities->enemies;
+	*self = (struct entity) {
+		.update = pacer_update,
+		.hurt = pacer_hurt,
+		.draw = pacer_draw,
+		.free = pacer_free,
+		.x = 0, .y = 0,
+		.velocity = {.x = 0, .y = 0},
+		.hitbox = {
+			0, 0, 0, 0,
+		},
+		.state = PACER_IDLE,
+		.hp = HP,
+		.timer = 0,
+		.facing = 1,
+		.faction = FACTION_ENEMY,
+		.iframes = 0,
+		.texture = NULL,
+		.ext = NULL,
+	};
+	self->ext = malloc(sizeof (struct pacer_ext));
+	*(struct pacer_ext *) self->ext = (struct pacer_ext) {
+		.detection_range = DETECTION_RANGE,
+		.give_up_range = GIVE_UP_RANGE,
+		.facing = 1,
+	};
+	struct pacer_ext *ext = self->ext;
+	hvy_repeater_new(&ext->primary, 5, -6);
+	hvy_blaster_new(&ext->secondary, 5, -6);
+	anim(self, PACER_A_REST);
+	self->texture = res_get_texture("pacer").data;
+	entities->enemies++;
+	return self;
+}
+
+int pacer_property(struct entity *const restrict self, char const *const restrict property, char const *const restrict value) {
+	struct pacer_ext *const ext = self->ext;
+	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, "detection range") == 0) {
+		ext->detection_range = to_fixed(atoi(value));
+	} else if (strcmp(property, "give up range") == 0) {
+		ext->give_up_range = to_fixed(atoi(value));
+	} else {
+		return 1;
+	}
+	return 0;
+}