#include "main.h" #include "entity.h" #include "loader.h" #include "tilemap.h" #include #include #define SIZE 4 #define ACCELERATION 1 #define FRICTION 2 #define MAX_SPEED 16 #define GRAVITY 1 #define WALKABLE_CHECK_DISTANCE 10 #define IDLE_TIME 90 #define ATTACK_DELAY 120 #define ATTACK_DELAY2 60 #define ATTACK_REPEAT 60 struct flier_ext { unsigned idle_time, attack_delay, attack_delay2, attack_repeat; signed facing_x, facing_y; struct vec2 *path; size_t current_node, path_length; bool reached_x, reached_y; }; enum { FLIER_NONE, FLIER_IDLE, FLIER_PATROL, FLIER_ALERT_IDLE, FLIER_ALERT_CHASE, FLIER_ALERT_RUN, FLIER_ALERT_ATTACK, }; enum { FLIER_A_IDLE, FLIER_A_IDLE2, FLIER_A_IDLE3, FLIER_A_IDLE4, FLIER_A_WALK, FLIER_A_WALK2, FLIER_A_WALK3, FLIER_A_WALK4, FLIER_A_ATTACK, FLIER_A_ATTACK2, }; struct anim flier_anims[] = { {FLIER_A_IDLE2, {0, 0, 16, 16}, 7}, {FLIER_A_IDLE3, {16, 0, 16, 16}, 7}, {FLIER_A_IDLE4, {32, 0, 16, 16}, 7}, {FLIER_A_IDLE, {16, 0, 16, 16}, 7}, {FLIER_A_WALK2, {0, 32, 16, 16}, 5}, {FLIER_A_WALK3, {16, 32, 16, 16}, 5}, {FLIER_A_WALK4, {32, 32, 16, 16}, 5}, {FLIER_A_WALK, {16, 32, 16, 16}, 5}, {FLIER_A_ATTACK2, {48, 0, 16, 16}, 3}, {FLIER_A_ATTACK, {48, 32, 16, 16}, 3}, }; static int bullet_update(struct projectile *self) { self->x += self->velocity.x; if (collision_solid(tilemap_tile(tilemap, from_fixed(self->x), from_fixed(self->y)))) { self->velocity.x = -self->velocity.x; self->x += self->velocity.x; } self->y += self->velocity.y; if (collision_solid(tilemap_tile(tilemap, from_fixed(self->x), from_fixed(self->y)))) { self->velocity.y = -self->velocity.y; self->y += self->velocity.y; } self->hp--; int x = from_fixed(self->x); int y = from_fixed(self->y); self->hitbox = (struct hitbox) {.left = x, .right = x, .top = y, .bottom = y}; if (hitbox_overlap(self->hitbox, entities.player[0].hitbox)) { entities.player[0].hurt(entities.player + 0, 1); return 1; } if (self->hp == 0) { return 1; self->state = 0; } return 0; } static int bullet_draw(struct projectile *self, int camX, int camY) { SDL_Rect rect = {4, 0, 4, 4}; SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x) - camX - 1, from_fixed(self->y) - camY - 1, 4, 4}); SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x - self->velocity.x) - camX - 1, from_fixed(self->y - self->velocity.y) - camY - 1, 4, 4}); return 0; } static void bullet_free(struct projectile *self) {} static collision_T collide(struct entity *self) { return tilemap_area(tilemap, from_fixed(self->x) - SIZE / 2, from_fixed(self->y) - SIZE, from_fixed(self->x) + SIZE / 2, from_fixed(self->y)); } static void move(struct entity *self, signed direction_x, signed direction_y, bool friction) { int const dx = direction_x * ACCELERATION; self->velocity.x += dx; // deaccel if (dx == 0 && friction) { 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(SIZE / 2)) % to_fixed(8)); // left } else if (self->velocity.x == 0) { //fputs("what?\n", stderr); } else { self->x -= ((self->x + to_fixed(SIZE / 2)) % to_fixed(8)); // right } self->velocity.x = -self->velocity.x; } #if 0 if (self->velocity.x < 0) { self->facing = -1; } else if (self->velocity.x > 0) { self->facing = +1; } #endif self->velocity.y += direction_y * ACCELERATION; if (direction_y == 0 && friction) { if (self->velocity.y < -FRICTION) { self->velocity.y += FRICTION; } else if (self->velocity.y > FRICTION) { self->velocity.y -= FRICTION; } else { self->velocity.y = 0; } } // speed cap if (self->velocity.y > MAX_SPEED) { self->velocity.y = MAX_SPEED; } else if (self->velocity.y < -MAX_SPEED) { self->velocity.y = -MAX_SPEED; } // 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(SIZE)) % to_fixed(8)); // up } else if (self->velocity.y == 0) { //fputs("what?\n", stderr); } else { self->y -= ((self->y) % to_fixed(8)); // down } self->velocity.y = -self->velocity.y; } if (collision_hazard(cx | cy)) { self->hurt(self, 1); } self->hitbox.left = from_fixed(self->x) - SIZE / 2; self->hitbox.right = from_fixed(self->x) + SIZE / 2; self->hitbox.top = from_fixed(self->y) - SIZE; self->hitbox.bottom = from_fixed(self->y); } static void move2(struct entity *self) { int dx = 0, dy = 0; struct flier_ext *const ext = self->ext; struct vec2 const *node = ext->path + ext->current_node; int const tx = node->x - self->x, ty = node->y - self->y; if (ext->reached_x == false) { dx = tx >= 0? 1: -1; } if (ext->reached_y == false) { dy = ty >= 0? 1: -1; } move(self, dx, dy, true); if (ext->reached_x == false) { if (dx != (node->x - self->x >= 0? 1: -1)) { ext->reached_x = true; } } if (ext->reached_y == false) { if (dy != (node->y - self->y >= 0? 1: -1)) { ext->reached_y = true; } } } static void anim(struct entity *self, unsigned anim) { self->anim = flier_anims[anim]; } static void attack(struct entity const *const self) { float const angle = atan2(entities.player[0].x - self->x, entities.player[0].y - self->y); entities.projectile[entities.projectiles] = (struct projectile) { .update = bullet_update, .draw = bullet_draw, .free = bullet_free, .x = self->x, .y = self->y - to_fixed(3), .velocity = (struct vec2) {to_fixed(sin(angle)), to_fixed(cos(angle))}, .hitbox = { 0, 0, 0, 0, }, .state = FLIER_IDLE, .hp = 300, .timer = 0, .facing = self->facing, .faction = FACTION_ENEMY, .iframes = 0, .texture = NULL, .ext = NULL, }; //anim(entities->projectile + entities->projectiles, FLIER_A_IDLE); entities.projectile[entities.projectiles].texture = res_get_texture("particles").data; entities.projectiles++; } static void flier_free(struct entity *self) { self->state = 0; free(((struct flier_ext *) self->ext)->path); free(self->ext), self->ext = NULL; } static int flier_update(struct entity *self) { struct flier_ext *const ext = self->ext; if (self->hp <= 0) { flier_free(self); entities.enemies--; return 1; } switch (self->state) { case FLIER_IDLE: move(self, 0, 0, true); self->timer--; if (self->timer == 0) { anim(self, FLIER_A_WALK); self->state = FLIER_PATROL; } if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) < to_fixed(64)) { self->state = FLIER_ALERT_IDLE; self->timer = ext->attack_delay; } break; case FLIER_PATROL: move2(self); if (ext->reached_x && ext->reached_y) { ext->current_node++; if (ext->current_node == ext->path_length) { ext->current_node = 0; } ext->reached_x = false; ext->reached_y = false; anim(self, FLIER_A_IDLE); self->state = FLIER_IDLE; self->timer = ext->idle_time; } if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) < to_fixed(64)) { anim(self, FLIER_A_IDLE); self->state = FLIER_ALERT_IDLE; self->timer = ext->attack_delay; } break; case FLIER_ALERT_IDLE: move(self, 0, 0, false); ext->facing_x = self->x > entities.player[0].x? -1: 1; ext->facing_y = self->y > entities.player[0].y? -1: 1; self->facing = ext->facing_x; if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > to_fixed(64)) { anim(self, FLIER_A_WALK); self->state = FLIER_ALERT_CHASE; } if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > to_fixed(96)) { self->state = FLIER_IDLE; } self->timer--; if (self->timer == 0) { anim(self, FLIER_A_ATTACK); self->state = FLIER_ALERT_ATTACK; self->timer = ext->attack_delay2; } break; case FLIER_ALERT_CHASE: ext->facing_x = self->x > entities.player[0].x? -1: 1; ext->facing_y = self->y > entities.player[0].y? -1: 1; move(self, ext->facing_x, ext->facing_y, true); if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) < to_fixed(64) /*|| !is_walkable(self->x + to_fixed(ext->facing * WALKABLE_CHECK_DISTANCE), self->y)*/) { anim(self, FLIER_A_IDLE); self->state = FLIER_ALERT_IDLE; self->timer = ext->attack_delay; } break; case FLIER_ALERT_ATTACK: move(self, 0, 0, false); ext->facing_x = self->x > entities.player[0].x? -1: 1; self->facing = ext->facing_x; if (self->timer > 0) { self->timer--; } if (self->timer == 0) { attack(self); anim(self, FLIER_A_IDLE); self->state = FLIER_ALERT_IDLE; self->timer = ext->attack_repeat; } 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; } break; } if (self->iframes > 0) { self->iframes--; } self->anim.length--; if (self->anim.length == 0) { anim(self, self->anim.frame); } return 0; } static int flier_init(struct entity *self) { if (((struct flier_ext *) self->ext)->path == NULL) { flier_free(self); entities.enemies--; return 1; } self->update = flier_update; return flier_update(self); } static int flier_hurt(struct entity *self, int damage) { if (self->iframes == 0) { self->hp -= damage; self->iframes = 60; 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), to_fixed(y) - 8}; part->rect = (SDL_Rect) {4, 0, 4, 4}; part->acceleration = (struct vec2) {0, 1}; part->hp = 30; entities.particles++; } } } return 0; } static int flier_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 - 12, 16, 16}); } return 0; } struct entity *flier_new(struct entities *entities) { struct entity *self = entities->enemy + entities->enemies; *self = (struct entity) { .update = flier_init, .hurt = flier_hurt, .draw = flier_draw, .free = flier_free, .x = 0, .y = 0, .velocity = {.x = 0, .y = 0}, .hitbox = { 0, 0, 0, 0, }, .state = FLIER_PATROL, .hp = 3, .timer = 0, .facing = 1, .faction = FACTION_ENEMY, .iframes = 0, .texture = NULL, .ext = NULL, }; self->ext = malloc(sizeof (struct flier_ext)); *(struct flier_ext *) self->ext = (struct flier_ext) { .idle_time = IDLE_TIME, .attack_delay = ATTACK_DELAY, .attack_delay2 = ATTACK_DELAY2, .attack_repeat = ATTACK_REPEAT, .facing_x = 1, .facing_y = 1, }; anim(self, FLIER_A_WALK); self->texture = res_get_texture("flier").data; entities->enemies++; return self; } int flier_property(struct entity *const restrict self, char const *const restrict property, char const *const restrict value) { struct flier_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, "idle time") == 0) { ext->idle_time = atoi(value); } else if (strcmp(property, "attack delay") == 0) { ext->attack_delay = atoi(value); } else if (strcmp(property, "attack delay 2") == 0) { ext->attack_delay2 = atoi(value); } else if (strcmp(property, "attack repeat") == 0) { ext->attack_repeat = atoi(value); } else if (strcmp(property, "path") == 0) { size_t spaces = 1, commas = 0; for (size_t i = 0; value[i]; i++) { if (value[i] == ' ') { spaces++; } else if (value[i] == ',') { commas++; } } if (spaces != commas) { return 1; } ext->path = malloc(spaces * sizeof (*ext->path)); ext->path_length = spaces; char *v = strdup(value), *vv = v; // stupid but it silences a warning for (size_t i = 0; i < spaces; i++) { ext->path[i].x = to_fixed(strtol(v, &v, 0)) + self->x; if (*v != ',') { return 1; } v++; ext->path[i].y = to_fixed(strtol(v, &v, 0)) + self->y; if (*v != ' ' && *v != 0) { return 1; } v++; } free(vv); self->x = ext->path[0].x; self->y = ext->path[0].y; } else { return 1; } return 0; }