diff options
Diffstat (limited to 'src/pacer.c')
-rw-r--r-- | src/pacer.c | 330 |
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; +} |