fencer/game/src/Enemy.c
Sara a27acee155 feat: drag for enemy is now calculated in EnemyUpdate
instead of separately in the states
2024-01-20 12:55:17 +01:00

157 lines
4.9 KiB
C

#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;
}
}