#include "main.h" #include "entity.h" #include "loader.h" #include "tilemap.h" #include #include #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; }