summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/disk.c2
-rw-r--r--src/funsinit.c3
-rw-r--r--src/gun.h7
-rw-r--r--src/hvy_guns.c449
-rw-r--r--src/pacer.c330
-rw-r--r--src/res/big.tmx29
-rw-r--r--src/res/pacer.asebin0 -> 865 bytes
-rw-r--r--src/save.c1
8 files changed, 807 insertions, 14 deletions
diff --git a/src/disk.c b/src/disk.c
index 5231953..084533f 100644
--- a/src/disk.c
+++ b/src/disk.c
@@ -93,7 +93,7 @@ struct entity *save_new(struct entities *entities) {
.timer = 0,
.ext = NULL,
};
- self->anim = save_anims[SAVE_A_SPIN];
+ self->anim = save_anims[SAVE_A_IDLE];
self->texture = res_get_texture("save").data;
entities->enemies++;
return self;
diff --git a/src/funsinit.c b/src/funsinit.c
index 50469c6..a323b1a 100644
--- a/src/funsinit.c
+++ b/src/funsinit.c
@@ -8,6 +8,8 @@ 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 *pacer_new(struct entities *entities);
+int pacer_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);
@@ -16,5 +18,6 @@ void funs_init(void) {
res_push_fun(save_new, save_property, "save");
res_push_fun(walker_new, walker_property, "walker");
res_push_fun(flier_new, flier_property, "flier");
+ res_push_fun(pacer_new, pacer_property, "pacer");
res_push_fun(warp_new, warp_property, "warp");
}
diff --git a/src/gun.h b/src/gun.h
new file mode 100644
index 0000000..6dac3d7
--- /dev/null
+++ b/src/gun.h
@@ -0,0 +1,7 @@
+#pragma once
+
+struct gun {
+ int (*update)(struct gun *self, struct entity *parent, struct entity *target);
+ int timer, counter;
+ int x, y; // offset from the parents center
+};
diff --git a/src/hvy_guns.c b/src/hvy_guns.c
new file mode 100644
index 0000000..43fd1f1
--- /dev/null
+++ b/src/hvy_guns.c
@@ -0,0 +1,449 @@
+#include "loader.h"
+#include "tilemap.h"
+#include <math.h>
+#include "particles.h"
+#include "gun.h"
+
+#define POS_X (parent->x + self->x * parent->facing)
+#define POS_Y (parent->y + self->y)
+
+#define HVY_BLASTER_RELOAD 120
+
+#define HVY_SHOTGUN_RELOAD 150
+
+#define HVY_REPEATER_RELOAD 60
+#define HVY_REPEATER_ROUNDS 3
+
+#define HVY_CHAINGUN_RELOAD 30
+#define HVY_CHAINGUN_MIN_RELOAD 5
+
+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)) {
+ if (entities.player[0].hurt(entities.player + 0, (struct damage) {1, 60})) {
+ //return 1;
+ }
+ }
+ if (self->hp == 0) {
+ return 1;
+ self->state = 0;
+ }
+ return 0;
+}
+
+static int projectile_update(struct projectile *self) {
+ self->x += self->velocity.x;
+ if (collision_solid(tilemap_tile(tilemap, from_fixed(self->x), from_fixed(self->y)))) {
+ goto explod;
+ }
+
+ self->y += self->velocity.y;
+ if (collision_solid(tilemap_tile(tilemap, from_fixed(self->x), from_fixed(self->y)))) {
+ goto explod;
+ }
+
+ 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, (struct damage) {1, 60});
+ goto explod;
+ }
+ if (self->hp == 0) {
+ goto explod;
+ }
+ return 0;
+ explod:
+ for (float r = 0; r < M_PI * 2; r += M_PI / 6) {
+ struct particle *part = entities.particle + entities.particles;
+ part->x = self->x;
+ part->y = self->y;
+ part->velocity = (struct vec2) {sin(r) * 24, cos(r) * 24};
+ part->rect = particle_red;
+ part->acceleration = (struct vec2) {0, 0};
+ part->hp = 15;
+ entities.particles++;
+ }
+ return 1;
+}
+
+static int laser_update(struct projectile *self) {
+ for (int i = 0; i < 15; i++) {
+ 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;
+ }
+
+ 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)) {
+ if (entities.player[0].hurt(entities.player + 0, (struct damage) {1, 60})) {
+ return 1;
+ }
+ }
+ struct particle *part = entities.particle + entities.particles;
+ part->x = self->x;
+ part->y = self->y;
+ part->velocity = (struct vec2) {0, 0};
+ part->rect = particle_red;
+ part->acceleration = (struct vec2) {0, 0};
+ part->hp = i & 15;
+ entities.particles++;
+ }
+ self->hp -= 15;
+ if (self->hp <= 0) {
+ return 1;
+ self->state = 0;
+ }
+ return 0;
+}
+
+static int bullet_draw(struct projectile *self, int camX, int camY) {
+ SDL_Rect const *rect;
+ if (self->hp & 0x2) {
+ rect = &particle_red;
+ } else {
+ rect = &particle_white;
+ }
+ 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});
+ //SDL_RenderFillRect(renderer, &(SDL_Rect) {from_fixed(self->x) - camX - 8, from_fixed(self->y) - camY - 12, 16, 16});
+ return 0;
+}
+
+static void bullet_free(struct projectile *self) {}
+
+int hvy_blaster(struct gun *self, struct entity *parent, struct entity *target) {
+ if (self->timer > 0) {
+ self->timer--;
+ return 0;
+ }
+ if (target != NULL) {
+ entities.projectile[entities.projectiles] = (struct projectile) {
+ .update = bullet_update,
+ .draw = bullet_draw,
+ .free = bullet_free,
+ .x = POS_X, .y = POS_Y,
+ .velocity = (struct vec2) {parent->velocity.x + to_fixed(parent->facing), 1},
+ .hitbox = {
+ 0, 0, 0, 0,
+ },
+ .state = 1,
+ .hp = 300,
+ .timer = 0,
+ .facing = parent->facing,
+ .faction = parent->faction,
+ .iframes = 0,
+ .texture = NULL,
+ .ext = NULL,
+ };
+ //anim(entities->projectile + entities->projectiles, PACER_A_IDLE);
+ entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
+ entities.projectiles++;
+ self->timer = HVY_BLASTER_RELOAD;
+ }
+ return 1;
+}
+
+void hvy_blaster_new(struct gun *self, int x, int y) {
+ self->update = hvy_blaster;
+ self->timer = HVY_BLASTER_RELOAD;
+ self->counter = 0;
+ self->x = to_fixed(x);
+ self->y = to_fixed(y);
+}
+
+int hvy_shotgun(struct gun *self, struct entity *parent, struct entity *target) {
+ if (self->timer > 0) {
+ self->timer--;
+ return 0;
+ }
+ if (target != NULL) {
+ float angle = atan2(target->x - POS_X, target->y - POS_Y);
+ for (int i = -2; i <= 2; i++) {
+ entities.projectile[entities.projectiles] = (struct projectile) {
+ .update = bullet_update,
+ .draw = bullet_draw,
+ .free = bullet_free,
+ .x = POS_X, .y = POS_Y,
+ .velocity = (struct vec2) {parent->velocity.x + to_fixed(sin(angle + i * 0.1)), to_fixed(cos(angle + i * 0.1))},
+ .hitbox = {
+ 0, 0, 0, 0,
+ },
+ .state = 1,
+ .hp = 300,
+ .timer = 0,
+ .facing = parent->facing,
+ .faction = parent->faction,
+ .iframes = 0,
+ .texture = NULL,
+ .ext = NULL,
+ };
+ //anim(entities->projectile + entities->projectiles, PACER_A_IDLE);
+ entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
+ entities.projectiles++;
+ }
+ self->timer = HVY_SHOTGUN_RELOAD;
+ }
+ return 1;
+}
+
+void hvy_shotgun_new(struct gun *self, int x, int y) {
+ self->update = hvy_shotgun;
+ self->timer = HVY_SHOTGUN_RELOAD;
+ self->counter = 0;
+ self->x = to_fixed(x);
+ self->y = to_fixed(y);
+}
+
+int hvy_repeater(struct gun *self, struct entity *parent, struct entity *target) {
+ if (self->timer > 0) {
+ self->timer--;
+ return 0;
+ }
+ if (target != NULL && self->counter == 0) {
+ self->counter = HVY_REPEATER_ROUNDS;
+ }
+ if (self->counter > 0) {
+ entities.projectile[entities.projectiles] = (struct projectile) {
+ .update = bullet_update,
+ .draw = bullet_draw,
+ .free = bullet_free,
+ .x = POS_X, .y = POS_Y,
+ .velocity = (struct vec2) {parent->velocity.x + to_fixed(parent->facing), (int []) {0, 1, 0, -1}[self->counter % 4]},
+ .hitbox = {
+ 0, 0, 0, 0,
+ },
+ .state = 1,
+ .hp = 300,
+ .timer = 0,
+ .facing = parent->facing,
+ .faction = parent->faction,
+ .iframes = 0,
+ .texture = NULL,
+ .ext = NULL,
+ };
+ //anim(entities->projectile + entities->projectiles, PACER_A_IDLE);
+ entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
+ entities.projectiles++;
+ self->counter--;
+ if (self->counter > 0) {
+ self->timer = 6;
+ } else {
+ self->timer = HVY_REPEATER_RELOAD;
+ if (target != NULL) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+ return 1;
+}
+
+void hvy_repeater_new(struct gun *self, int x, int y) {
+ self->update = hvy_repeater;
+ self->timer = HVY_REPEATER_RELOAD;
+ self->counter = 0;
+ self->x = to_fixed(x);
+ self->y = to_fixed(y);
+}
+
+int hvy_chaingun(struct gun *self, struct entity *parent, struct entity *target) {
+ if (self->timer > 0) {
+ self->timer--;
+ return 0;
+ }
+ if (target != NULL) {
+ entities.projectile[entities.projectiles] = (struct projectile) {
+ .update = bullet_update,
+ .draw = bullet_draw,
+ .free = bullet_free,
+ .x = POS_X, .y = POS_Y,
+ .velocity = (struct vec2) {parent->velocity.x + to_fixed(parent->facing), (int []) {0, 1, 0, -1}[self->counter % 4]},
+ .hitbox = {
+ 0, 0, 0, 0,
+ },
+ .state = 1,
+ .hp = 300,
+ .timer = 0,
+ .facing = parent->facing,
+ .faction = parent->faction,
+ .iframes = 0,
+ .texture = NULL,
+ .ext = NULL,
+ };
+ //anim(entities->projectile + entities->projectiles, PACER_A_IDLE);
+ entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
+ entities.projectiles++;
+ if (self->counter > HVY_CHAINGUN_MIN_RELOAD) {
+ self->counter--;
+ }
+ self->timer = self->counter;
+ } else {
+ self->counter = HVY_CHAINGUN_RELOAD;
+ }
+ return 1;
+}
+
+void hvy_chaingun_new(struct gun *self, int x, int y) {
+ self->update = hvy_chaingun;
+ self->timer = HVY_CHAINGUN_RELOAD;
+ self->counter = 0;
+ self->x = to_fixed(x);
+ self->y = to_fixed(y);
+}
+
+int hvy_launcher(struct gun *self, struct entity *parent, struct entity *target) {
+ if (self->timer > 0) {
+ self->timer--;
+ return 0;
+ }
+ if (target != NULL) {
+ entities.projectile[entities.projectiles] = (struct projectile) {
+ .update = projectile_update,
+ .draw = bullet_draw,
+ .free = bullet_free,
+ .x = POS_X, .y = POS_Y,
+ .velocity = (struct vec2) {parent->velocity.x + to_fixed(parent->facing), 1},
+ .hitbox = {
+ 0, 0, 0, 0,
+ },
+ .state = 1,
+ .hp = 300,
+ .timer = 0,
+ .facing = parent->facing,
+ .faction = parent->faction,
+ .iframes = 0,
+ .texture = NULL,
+ .ext = NULL,
+ };
+ //anim(entities->projectile + entities->projectiles, PACER_A_IDLE);
+ entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
+ entities.projectiles++;
+ self->timer = HVY_BLASTER_RELOAD;
+ }
+ return 1;
+}
+
+void hvy_launcher_new(struct gun *self, int x, int y) {
+ self->update = hvy_launcher;
+ self->timer = HVY_BLASTER_RELOAD;
+ self->counter = 0;
+ self->x = to_fixed(x);
+ self->y = to_fixed(y);
+}
+
+int hvy_laser(struct gun *self, struct entity *parent, struct entity *target) {
+ if (self->counter == 0) {
+ if (self->timer > 0) {
+ self->timer--;
+ return 0;
+ }
+ if (target != NULL) {
+ float angle = atan2(target->x - POS_X, target->y - POS_Y);
+ #if 0
+ entities.projectile[entities.projectiles] = (struct projectile) {
+ .update = laser_update,
+ .draw = bullet_draw,
+ .free = bullet_free,
+ .x = POS_X, .y = POS_Y,
+ .velocity = (struct vec2) {parent->velocity.x + to_fixed(sin(angle)), to_fixed(cos(angle))},
+ .hitbox = {
+ 0, 0, 0, 0,
+ },
+ .state = 1,
+ .hp = 500,
+ .timer = 0,
+ .facing = parent->facing,
+ .faction = parent->faction,
+ .iframes = 0,
+ .texture = NULL,
+ .ext = NULL,
+ };
+ //anim(entities->projectile + entities->projectiles, PACER_A_IDLE);
+ entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
+ entities.projectiles++;
+ self->timer = HVY_BLASTER_RELOAD;
+ #endif
+ self->counter = 25;
+ self->timer = angle * 128;
+ return 0;
+ }
+ } else {
+ if (target != NULL) {
+ self->counter--;
+ float angle = self->timer / 128.0;
+ if (self->counter != 0) {
+ float r = angle + (self->counter & 1? self->counter: -self->counter) * 0.05;
+ int const x = sin(r) * 16, y = cos(r) * 16;
+ struct particle *part = entities.particle + entities.particles;
+ part->x = POS_X + x * self->counter;
+ part->y = POS_Y + y * self->counter;
+ part->velocity = (struct vec2) {-x, -y};
+ part->rect = particle_white;
+ part->acceleration = (struct vec2) {0, 0};
+ part->hp = self->counter;
+ entities.particles++;
+ return 0;
+ }
+ entities.projectile[entities.projectiles] = (struct projectile) {
+ .update = laser_update,
+ .draw = bullet_draw,
+ .free = bullet_free,
+ .x = POS_X, .y = POS_Y,
+ .velocity = (struct vec2) {parent->velocity.x + to_fixed(sin(angle)), to_fixed(cos(angle))},
+ .hitbox = {
+ 0, 0, 0, 0,
+ },
+ .state = 1,
+ .hp = 500,
+ .timer = 0,
+ .facing = parent->facing,
+ .faction = parent->faction,
+ .iframes = 0,
+ .texture = NULL,
+ .ext = NULL,
+ };
+ //anim(entities->projectile + entities->projectiles, PACER_A_IDLE);
+ entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
+ entities.projectiles++;
+ self->timer = HVY_BLASTER_RELOAD;
+ } else {
+ self->counter = 0;
+ self->timer = 0;
+ }
+ }
+ return 1;
+}
+
+void hvy_laser_new(struct gun *self, int x, int y) {
+ self->update = hvy_laser;
+ self->timer = HVY_BLASTER_RELOAD;
+ self->counter = 0;
+ self->x = to_fixed(x);
+ self->y = to_fixed(y);
+}
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;
+}
diff --git a/src/res/big.tmx b/src/res/big.tmx
index d9d35ce..8c10ff6 100644
--- a/src/res/big.tmx
+++ b/src/res/big.tmx
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="65" height="33" tilewidth="8" tileheight="8" infinite="0" backgroundcolor="#1d2b79" nextlayerid="5" nextobjectid="7">
+<map version="1.10" tiledversion="1.11.0" orientation="orthogonal" renderorder="right-down" width="65" height="33" tilewidth="8" tileheight="8" infinite="0" backgroundcolor="#1d2b79" nextlayerid="5" nextobjectid="8">
<tileset firstgid="1" source="autotiles.tsx"/>
<tileset firstgid="33" source="tileset.tsx"/>
<group id="3" name="Group Layer 1">
@@ -23,7 +23,7 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,37,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,65,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -56,16 +56,16 @@
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,13,13,13,5,0,0,0,0,0,0,0,0,0,0,9,13,13,13,13,13,13,5,0,0,0,0,0,0,0,0,0,
-0,9,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,5,9,13,13,13,13,13,13,13,13,13,13,13,13,13,13,15,8,4,12,14,13,5,9,13,13,13,13,13,13,13,15,8,4,4,4,4,12,14,13,5,0,0,0,0,0,0,0,
-0,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,12,14,15,8,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,0,3,4,12,14,15,8,4,4,4,4,4,4,4,2,0,0,0,0,3,4,4,2,0,0,0,0,0,0,0,
-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,8,4,4,4,4,4,4,4,4,4,4,4,4,4,12,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,6,0,0,0,0,0,0,0,0,0,0,0,0,0,11,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,6,0,0,0,0,0,0,0,0,0,0,0,0,0,11,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,6,0,0,0,0,0,0,0,0,0,0,0,0,0,11,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,9,5,0,0,0,0,0,0,0,0,0,9,5,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9,5,3,2,0,0,0,0,0,0,0,0,0,3,2,9,13,13,13,5,0,0,0,0,0,0,0,0,0,0,9,13,13,13,13,13,13,5,0,0,0,0,0,0,0,0,0,
+0,9,13,13,13,13,13,13,13,13,13,13,13,13,5,9,13,13,15,14,13,13,13,13,13,13,13,13,13,13,13,13,13,15,8,4,12,14,13,5,9,13,13,13,13,13,13,13,15,8,4,4,4,4,12,14,13,5,0,9,13,13,13,5,0,
+0,3,4,4,4,4,4,4,4,4,4,4,4,12,14,15,8,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,2,0,3,4,12,14,15,8,4,4,4,4,4,4,4,2,0,0,0,0,3,4,4,2,0,3,4,4,4,2,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,4,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@@ -98,11 +98,14 @@
<object id="4" name="walky" type="walker" x="408" y="96">
<point/>
</object>
- <object id="5" name="walky" type="walker" x="200" y="112">
+ <object id="5" name="pacer" type="pacer" x="200" y="112">
<point/>
</object>
<object id="6" name="disk" type="save" x="448" y="120">
<point/>
</object>
+ <object id="7" name="pacer" type="pacer" x="488" y="112">
+ <point/>
+ </object>
</objectgroup>
</map>
diff --git a/src/res/pacer.ase b/src/res/pacer.ase
new file mode 100644
index 0000000..7d8e666
--- /dev/null
+++ b/src/res/pacer.ase
Binary files differ
diff --git a/src/save.c b/src/save.c
index 0264dfa..9baf981 100644
--- a/src/save.c
+++ b/src/save.c
@@ -1,5 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include "main.h"
#include "util.h"
#include "util.h"