From 03b817a354913240b3bccd4148c9acec39933acc Mon Sep 17 00:00:00 2001 From: zlago Date: Mon, 14 Oct 2024 13:24:27 +0200 Subject: add flying enemy --- src/flier.c | 469 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/funsinit.c | 4 + src/res/flier.ase | Bin 0 -> 964 bytes utl/json2map/main.c | 49 +++++- 4 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 src/flier.c create mode 100644 src/res/flier.ase diff --git a/src/flier.c b/src/flier.c new file mode 100644 index 0000000..bfce46a --- /dev/null +++ b/src/flier.c @@ -0,0 +1,469 @@ +#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; +} diff --git a/src/funsinit.c b/src/funsinit.c index 264a653..8e5581a 100644 --- a/src/funsinit.c +++ b/src/funsinit.c @@ -4,10 +4,14 @@ void *walker_new(struct entities *entities); int walker_property(void *const restrict entity, char const *const restrict property, char const *const restrict value); +void *flier_new(struct entities *entities); +int flier_property(void *const restrict entity, char const *const restrict property, char const *const restrict value); + void *warp_new(struct entities *entities); int warp_property(void *const restrict entity, char const *const restrict property, char const *const restrict value); void funs_init(void) { res_push_fun(walker_new, walker_property, "walker"); + res_push_fun(flier_new, flier_property, "flier"); res_push_fun(warp_new, warp_property, "warp"); } diff --git a/src/res/flier.ase b/src/res/flier.ase new file mode 100644 index 0000000..741409a Binary files /dev/null and b/src/res/flier.ase differ diff --git a/utl/json2map/main.c b/utl/json2map/main.c index 9ffa9b9..beb8909 100644 --- a/utl/json2map/main.c +++ b/utl/json2map/main.c @@ -382,7 +382,7 @@ int main(int argc, char **argv) { //printf("%s:", property->valuestring); char const *const *default_props = (char const *const[]) {"x", "y", "width", "height", NULL}; - if (cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(object, "point"))) { + if (cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(object, "point")) || cJSON_GetObjectItemCaseSensitive(object, "polygon") || cJSON_GetObjectItemCaseSensitive(object, "polyline")) { default_props = (char const *const[]) {"x", "y", NULL}; } for (int i = 0; default_props[i] != NULL; i++) { @@ -407,6 +407,53 @@ int main(int argc, char **argv) { return 1; } } + cJSON const *path = cJSON_GetObjectItemCaseSensitive(object, "polygon"); + if (path) { + fputs("polygon ", stdout); + cJSON const *node; + struct blob nodes = blob_new(); + blob_append(&nodes, "path", strlen("path") + 1); + cJSON_ArrayForEach(node, path) { + char buf[64]; + sprintf(buf, "%i,%i ", cJSON_GetObjectItemCaseSensitive(node, "x")->valueint, + cJSON_GetObjectItemCaseSensitive(node, "y")->valueint); + blob_append(&nodes, buf, strlen(buf)); + } + blob_append(&entity, nodes.data, nodes.size - 1); + blob_append(&entity, &(char) {0}, 1); + fwrite(nodes.data, 1, nodes.size - 1, stdout); + blob_free(&nodes); + putchar('\n'); + } + path = cJSON_GetObjectItemCaseSensitive(object, "polyline"); + if (path) { + //fputs("polyline ", stdout); + char **node_strings = malloc(cJSON_GetArraySize(path) * sizeof (char *)); + int node_strings_index = 0; + cJSON const *node; + struct blob nodes = blob_new(); + blob_append(&nodes, "path", strlen("path") + 1); + cJSON_ArrayForEach(node, path) { + char buf[64]; + sprintf(buf, "%i,%i ", cJSON_GetObjectItemCaseSensitive(node, "x")->valueint, + cJSON_GetObjectItemCaseSensitive(node, "y")->valueint); + blob_append(&nodes, buf, strlen(buf)); + node_strings[node_strings_index++] = strdup(buf); + } + free(node_strings[--node_strings_index]); + while (node_strings_index > 1) { + node_strings_index--; + blob_append(&nodes, node_strings[node_strings_index], strlen(node_strings[node_strings_index])); + free(node_strings[node_strings_index]); + } + free(node_strings[--node_strings_index]); + free(node_strings); + blob_append(&entity, nodes.data, nodes.size - 1); + blob_append(&entity, &(char) {0}, 1); + //fwrite(nodes.data, 1, nodes.size - 1, stdout); + blob_free(&nodes); + //putchar('\n'); + } cJSON_ArrayForEach(property, cJSON_GetObjectItemCaseSensitive(object, "properties")) { char const *const name = cJSON_GetObjectItemCaseSensitive(property, "name")->valuestring; char const *const type = cJSON_GetObjectItemCaseSensitive(property, "type")->valuestring; -- cgit 1.4.1-2-gfad0