#include "Enemy.h" #include "Layers.h" #include "debug.h" #include "game_world.h" #include "physics.h" #include "physics_world.h" #include "program.h" #include "sprite.h" START_REFLECT(Enemy) REFLECT_TYPECLASS(Enemy, Transformable) REFLECT_TYPECLASS(Enemy, Drop) REFLECT_TYPECLASS(Enemy, PhysicsEntity) REFLECT_TYPECLASS(Enemy, BehaviourEntity) REFLECT_TYPECLASS(Enemy, Damagable) END_REFLECT(Enemy) impl_Transformable_for(Enemy, EnemyGetTransform ) impl_Drop_for(Enemy, EnemyDestroy ) impl_BehaviourEntity_for(Enemy, EnemyStart, EnemyUpdate, EnemyDraw, EnemyGetDepth ) impl_PhysicsEntity_for(Enemy, EnemyGetRigidBody, EnemyOnCollision, EnemyOnOverlap ) impl_Damagable_for(Enemy, EnemyDamage ) Enemy* MakeEnemy() { Enemy* self = malloc(sizeof(Enemy)); ASSERT_RETURN(self != NULL, NULL, "Failed to allocate Enemy"); *self = (Enemy){ .transform = IdentityTransform, .behaviour = NULL, .rigidbody = NULL, .collider = NULL, .hitbox = NULL, .sprite = sprite_new_empty(), .facing = 1, .stun_time = 0.f, .idleAnim = NULL, .walkAnim = NULL, .hurtAnim = NULL, .currentAnimation = NULL, .health = 15, }; self->rigidbody = rigidbody_make(Enemy_as_PhysicsEntity(self)); // collider used for physics and movement self->collider = collider_new(Enemy_as_PhysicsEntity(self), shape_new_square(MakeVector(0.2f, 0.05f)), 0, PHYSICS_LAYER_DEFAULT, PHYSICS_LAYER_DEFAULT); // hitbox used to detect damage self->hitbox = collider_new(Enemy_as_PhysicsEntity(self), shape_new((Vector[]){ MakeVector(-0.1f, -0.9f), MakeVector( 0.1f, -0.9f), MakeVector( 0.1f, 0.0f), MakeVector(-0.1f, 0.0f), }, 4), 1, PHYSICS_LAYER_COMBAT, 0x0); // does not query sprite_set_origin(self->sprite, MakeVector(0.45f, 0.925f)); // load and configure animations self->idleAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Idle.png", IVectorFrom(512)), 1.5f, LoopMode_Loop); self->walkAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Walk.png", IVectorFrom(512)), 1.5f, LoopMode_Loop); self->hurtAnim = animation_sprite_new(self->sprite, spritesheet_load("assets/Player_Hurt.png", IVectorFrom(512)), 5.f, LoopMode_Stop); return self; } Enemy* SpawnEnemy(Vector location, const State* entryState) { Enemy* self = MakeEnemy(); self->behaviour = state_machine_init(self, entryState); // spawn at location rigidbody_get_transform(self->rigidbody)->position = location; // register into game world game_world_add_entity(Enemy_as_BehaviourEntity(self)); physics_world_add_entity(Enemy_as_PhysicsEntity(self)); return self; } void EnemyStart(Enemy* self) {} void EnemyUpdate(Enemy* self, float deltaTime) { // apply drag Vector velocity = vmovetowardsf(rigidbody_get_velocity(self->rigidbody), ZeroVector, 0.1f); rigidbody_set_velocity(self->rigidbody, velocity); // update state machine state_machine_update(self->behaviour, deltaTime); } void EnemyDraw(Enemy* self) { sprite_flip_horizontal(self->sprite, self->facing == -1); animation_sprite_draw(self->currentAnimation, &self->transform); } void EnemyDestroy(Enemy* self) { state_machine_destroy(self->behaviour); // unregister *before* deallocating physics components physics_world_remove_entity(Enemy_as_PhysicsEntity(self)); collider_destroy(self->collider); rigidbody_destroy(self->rigidbody); sprite_destroy(self->sprite); free(self); } void EnemyOnCollision(Enemy* self, Collision collision) {} void EnemyOnOverlap(Enemy* self, Collider* other) {} Transform* EnemyGetTransform(Enemy* self) { return &self->transform; } RigidBody* EnemyGetRigidBody(Enemy* self) { return self->rigidbody; } int EnemyDamage(Enemy* self, DamageEventData* data) { // being stunned gives iframes if(self->stun_time > game_time()) return 0; // subtract damage self->health -= data->damageAmount; // die if health drops below zero if(self->health <= 0) { // TODO: swap to death state instead game_world_destroy_entity(Enemy_as_BehaviourEntity(self)); return 1; } else { // store the most recent damage event self->last_damage = *data; // get stunned self->stun_time = game_time() + data->stun; // calculate which direction the damage is coming from const float direction = (data->origin.x - self->transform.position.x) >= 0.0f ? 1.0f : -1.0f; // face the direction damage is coming from self->facing = direction; // add knockback according to damage data in the calculated direction rigidbody_add_impulse(self->rigidbody, MakeVector(-direction * data->knockback, 0), 0); return 0; } }