summaryrefslogtreecommitdiff
path: root/src/pacer.c
diff options
context:
space:
mode:
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;
+}