A 3d fps game made in OpenGL
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
Symmetry/src/game/enemy.c

529 lines
20 KiB

#include "enemy.h"
#include "entity.h"
#include "scene.h"
#include "game.h"
#include "transform.h"
#include "sound_source.h"
#include "../common/log.h"
#include "../common/hashmap.h"
#include "../common/parser.h"
#include "event.h"
#include "../system/platform.h"
#include "debug_vars.h"
#include "im_render.h"
#include "player.h"
#include "texture.h"
#include <string.h>
#include <math.h>
#include <assert.h>
#include <stdlib.h>
static void enemy_on_scene_loaded(struct Event* event, void* enemy_ptr);
static void enemy_update_physics_turret(struct Enemy* enemy, struct Game_State* game_state, float fixed_dt);
static void enemy_update_ai_turret(struct Enemy* enemy, struct Game_State* game_state, float dt);
static void enemy_state_set_turret(struct Enemy* enemy, int state);
void enemy_init(struct Enemy* enemy, int type)
{
struct Game_State* game_state = game_state_get();
struct Scene* scene = game_state->scene;
enemy->base.type = ET_ENEMY;
enemy->type = type;
////Muzzle
//enemy->muzzle_light_intensity_min = 1.f;
//enemy->muzzle_light_intensity_max = 5.f;
//enemy->muzzle_light_intensity_decay = 30.f;
//
//vec3 translation = { 0.f, 0.f, -1.f };
//enemy->muzzle_light = scene_light_create(scene, "Enemy_Muzzle_Light", enemy, LT_SPOT);
//transform_translate(enemy->muzzle_light, &translation, TS_LOCAL);
//enemy->muzzle_light->intensity = 0.f;
//enemy->muzzle_light->radius = 25.f;
//enemy->muzzle_light->outer_angle = 80.f;
//enemy->muzzle_light->inner_angle = 50.f;
//vec3_fill(&enemy->muzzle_light->color, 0.9f, 0.4f, 0.f);
//enemy->muzzle_light_mesh = scene_static_mesh_create(scene, "Enemy_Muzzle_Light_Mesh", enemy, "muzzle_flash.symbres", MAT_BLINN);
//transform_translate(enemy->muzzle_light_mesh, &(vec3) {0.f, 0.f, -1.f}, TS_LOCAL);
//transform_rotate(enemy->muzzle_light_mesh, &UNIT_X, 90.f, TS_LOCAL);
//transform_scale(enemy->muzzle_light_mesh, &(vec3){0.f});
//enemy->muzzle_light_mesh->model.material_params[MMP_DIFFUSE_TEX].val_int = texture_create_from_file("white.tga", TU_DIFFUSE);
//enemy->muzzle_light_mesh->model.material_params[MMP_DIFFUSE].val_float = 6.f;
//vec4_fill(&enemy->muzzle_light_mesh->model.material_params[MMP_DIFFUSE_COL].val_vec4, 0.9f, 0.4f, 0.f, 1.f);
/* Initialization specific to each enemy type */
switch(enemy->type)
{
case ENEMY_TURRET:
{
enemy->health = 100;
enemy->damage = 20;
enemy->Turret.yaw_direction_positive = true;
enemy->Turret.scan = false;
enemy->Turret.turn_speed_default = 50.f;
enemy->Turret.turn_speed_when_targetting = 100.f;
enemy->Turret.turn_speed_current = enemy->Turret.turn_speed_default;
enemy->Turret.max_yaw = 60.f;
enemy->Turret.target_yaw = 0.f;
enemy->Turret.pulsate_height = 1.5f;
enemy->Turret.pulsate_speed_scale = 0.1f;
enemy->Turret.attack_cooldown = 0.05f;
enemy->Turret.alert_cooldown = 1.f;
enemy->Turret.color_default = (vec4){ 0.f, 1.f, 1.f, 1.f };
enemy->Turret.color_alert = (vec4){ 1.f, 1.f, 0.f, 1.f };
enemy->Turret.color_attack = (vec4){ 1.f, 0.f, 0.f, 1.f };
enemy->Turret.time_elapsed_since_alert = 0.f;
enemy->Turret.time_elapsed_since_attack = 0.f;
enemy->Turret.vision_range = 15.f;
enemy->hit_chance = 4;
}
break;
default:
log_error("enemy:init", "Unsupported Enemy Type");
break;
}
struct Event_Manager* event_manager = game_state->event_manager;
event_manager_subscribe_with_subscriber(event_manager, EVT_SCENE_LOADED, &enemy_on_scene_loaded, (void*)enemy);
}
void enemy_weapon_sound_set(struct Enemy* enemy, const char* sound_filename, int type)
{
sound_source_buffer_set(game_state_get()->sound, enemy->weapon_sound, sound_filename, type);
}
void enemy_static_mesh_set(struct Enemy* enemy, const char* geometry_filename, int material_type)
{
struct Scene* scene = game_state_get()->scene;
char mesh_name[MAX_ENTITY_NAME_LEN];
memset(mesh_name, '\0', sizeof(char) * MAX_ENTITY_NAME_LEN);
snprintf(mesh_name, MAX_ENTITY_NAME_LEN, "%s_Mesh", enemy->base.name);
struct Static_Mesh* new_mesh = scene_static_mesh_create(scene, mesh_name, enemy, geometry_filename, material_type);
if(new_mesh)
{
if(enemy->mesh) scene_static_mesh_remove(scene, enemy->mesh);
enemy->mesh = new_mesh;
}
}
void enemy_update(struct Enemy* enemy, struct Scene* scene, float dt)
{
if(enemy->muzzle_light->intensity > 0.f)
{
enemy->muzzle_light->intensity -= enemy->muzzle_light_intensity_decay * dt;
if(enemy->muzzle_light->intensity < 0.f)
enemy->muzzle_light->intensity = 0.f;
}
if(enemy->muzzle_light_mesh->base.transform.scale.x > 0.f)
{
enemy->muzzle_light_mesh->base.transform.scale.x -= enemy->muzzle_light_intensity_decay * dt;
enemy->muzzle_light_mesh->base.transform.scale.y -= enemy->muzzle_light_intensity_decay * dt;
enemy->muzzle_light_mesh->base.transform.scale.z -= enemy->muzzle_light_intensity_decay * dt;
transform_update_transmat(enemy->muzzle_light_mesh);
}
// AI Update
static float enemy_update_interval = 1.f / 60.f;
static float time_elapsed_since_last_update = 0.f;
time_elapsed_since_last_update += dt;
if(time_elapsed_since_last_update < enemy_update_interval)
return;
time_elapsed_since_last_update = 0.f;
struct Game_State* game_state = game_state_get();
switch(enemy->type)
{
case ENEMY_TURRET: enemy_update_ai_turret(enemy, game_state, dt); break;
}
}
void enemy_update_physics(struct Enemy* enemy, struct Scene* scene, float fixed_dt)
{
struct Game_State* game_state = game_state_get();
switch(enemy->type)
{
case ENEMY_TURRET: enemy_update_physics_turret(enemy, game_state, fixed_dt); break;
}
}
void enemy_reset(struct Enemy* enemy)
{
enemy->type = -1;
enemy->damage = 0;
enemy->health = 0;
struct Event_Manager* event_manager = game_state_get()->event_manager;
event_manager_unsubscribe_with_subscriber(event_manager, EVT_SCENE_LOADED, &enemy_on_scene_loaded, (void*)enemy);
}
struct Enemy* enemy_read(struct Parser_Object* object, const char* name, struct Entity* parent_entity)
{
int enemy_type = -1;
struct Enemy* new_enemy = NULL;
struct Scene* scene = game_state_get()->scene;
if(hashmap_value_exists(object->data, "enemy_type")) enemy_type = hashmap_int_get(object->data, "enemy_type");
if(enemy_type != -1)
{
new_enemy = scene_enemy_create(scene, name, parent_entity, enemy_type); // Create enemy with default values then read and update from file if necessary
if(!new_enemy)
return new_enemy;
if(hashmap_value_exists(object->data, "health")) new_enemy->health = hashmap_int_get(object->data, "health");
if(hashmap_value_exists(object->data, "damage")) new_enemy->damage = hashmap_int_get(object->data, "damage");
if(hashmap_value_exists(object->data, "hit_chance")) new_enemy->hit_chance = hashmap_int_get(object->data, "hit_chance");
if(hashmap_value_exists(object->data, "muzzle_light_intensity_decay")) new_enemy->muzzle_light_intensity_decay = hashmap_float_get(object->data, "muzzle_light_intensity_decay");
if(hashmap_value_exists(object->data, "muzzle_light_intensity_min")) new_enemy->muzzle_light_intensity_min = hashmap_int_get(object->data, "muzzle_light_intensity_min");
if(hashmap_value_exists(object->data, "muzzle_light_intensity_max")) new_enemy->muzzle_light_intensity_max = hashmap_int_get(object->data, "muzzle_light_intensity_max");
switch(new_enemy->type)
{
case ENEMY_TURRET:
{
if(hashmap_value_exists(object->data, "turn_speed_default")) new_enemy->Turret.turn_speed_default = hashmap_float_get(object->data, "turn_speed_default");
if(hashmap_value_exists(object->data, "max_yaw")) new_enemy->Turret.max_yaw = hashmap_float_get(object->data, "max_yaw");
if(hashmap_value_exists(object->data, "pulsate_speed_scale")) new_enemy->Turret.pulsate_speed_scale = hashmap_float_get(object->data, "pulsate_speed_scale");
if(hashmap_value_exists(object->data, "pulsate_height")) new_enemy->Turret.pulsate_height = hashmap_float_get(object->data, "pulsate_height");
if(hashmap_value_exists(object->data, "attack_cooldown")) new_enemy->Turret.attack_cooldown = hashmap_float_get(object->data, "attack_cooldown");
if(hashmap_value_exists(object->data, "alert_cooldown")) new_enemy->Turret.alert_cooldown = hashmap_float_get(object->data, "alert_cooldown");
if(hashmap_value_exists(object->data, "vision_range")) new_enemy->Turret.vision_range = hashmap_float_get(object->data, "vision_range");
if(hashmap_value_exists(object->data, "color_default")) new_enemy->Turret.color_default = hashmap_vec4_get(object->data, "color_default");
if(hashmap_value_exists(object->data, "color_alert")) new_enemy->Turret.color_alert = hashmap_vec4_get(object->data, "color_alert");
if(hashmap_value_exists(object->data, "color_attack")) new_enemy->Turret.color_attack = hashmap_vec4_get(object->data, "color_attack");
if(hashmap_value_exists(object->data, "turn_direction_positive")) new_enemy->Turret.yaw_direction_positive = hashmap_bool_get(object->data, "turn_direction_positive");
}
break;
}
}
return new_enemy;
}
void enemy_write(struct Enemy* enemy, struct Hashmap* entity_data)
{
hashmap_int_set(entity_data, "enemy_type", enemy->type);
hashmap_int_set(entity_data, "health", enemy->health);
hashmap_int_set(entity_data, "damage", enemy->damage);
hashmap_int_set(entity_data, "hit_chance", enemy->hit_chance);
hashmap_int_set(entity_data, "muzzle_light_intensity_decay", enemy->muzzle_light_intensity_decay);
hashmap_int_set(entity_data, "muzzle_light_intensity_min", enemy->muzzle_light_intensity_min);
hashmap_int_set(entity_data, "muzzle_light_intensity_max", enemy->muzzle_light_intensity_max);
switch(enemy->type)
{
case ENEMY_TURRET:
{
hashmap_float_set(entity_data, "turn_speed_default", enemy->Turret.turn_speed_default);
hashmap_float_set(entity_data, "turn_speed_when_targetting", enemy->Turret.turn_speed_default);
hashmap_float_set(entity_data, "max_yaw", enemy->Turret.max_yaw);
hashmap_float_set(entity_data, "pulsate_speed_scale", enemy->Turret.pulsate_speed_scale);
hashmap_float_set(entity_data, "pulsate_height", enemy->Turret.pulsate_height);
hashmap_float_set(entity_data, "attack_cooldown", enemy->Turret.attack_cooldown);
hashmap_float_set(entity_data, "alert_cooldown", enemy->Turret.alert_cooldown);
hashmap_float_set(entity_data, "vision_range", enemy->Turret.vision_range);
hashmap_vec4_set(entity_data, "color_default", &enemy->Turret.color_default);
hashmap_vec4_set(entity_data, "color_alert", &enemy->Turret.color_alert);
hashmap_vec4_set(entity_data, "color_attack", &enemy->Turret.color_attack);
hashmap_bool_set(entity_data, "turn_direction_positive", enemy->Turret.yaw_direction_positive);
}
break;
}
}
void enemy_on_scene_loaded(struct Event* event, void* enemy_ptr)
{
struct Enemy* enemy = (struct Enemy*)enemy_ptr;
// Assign pointers to static_mesh and sound_source child entities
struct Entity* enemy_mesh[MAX_ENEMY_MESHES] = { NULL };
struct Entity* enemy_sound_sources[MAX_ENEMY_SOUND_SOURCES] = { NULL };
struct Entity* enemy_lights[MAX_ENEMY_LIGHTS] = { NULL };
if(entity_get_num_children_of_type(enemy, ET_STATIC_MESH, &enemy_mesh, MAX_ENEMY_MESHES) == MAX_ENEMY_MESHES)
{
enemy->muzzle_light_mesh = enemy_mesh[0];
enemy->mesh = enemy_mesh[1];
}
else
{
log_error("enemy:on_scene_load", "Could not find %d child mesh entities for enemy %s", MAX_ENEMY_MESHES, enemy->base.name);
}
if(entity_get_num_children_of_type(enemy, ET_SOUND_SOURCE, &enemy_sound_sources, MAX_ENEMY_SOUND_SOURCES) == MAX_ENEMY_SOUND_SOURCES)
{
enemy->weapon_sound = enemy_sound_sources[0];
enemy->ambient_sound = enemy_sound_sources[1];
}
else
{
log_error("enemy:on_scene_load", "Could not find %d child sound source entities for enemy %s", MAX_ENEMY_SOUND_SOURCES, enemy->base.name);
}
if(entity_get_num_children_of_type(enemy, ET_LIGHT, &enemy_lights, MAX_ENEMY_LIGHTS) == MAX_ENEMY_LIGHTS)
enemy->muzzle_light = enemy_lights[0];
else
log_error("enemy:on_scene_load", "Could not find %d child light entities for enemy %s", MAX_ENEMY_LIGHTS, enemy->base.name);
// Do other post-scene-load initialization stuff per enemy type here
switch(enemy->type)
{
case ENEMY_TURRET:
enemy_state_set_turret(enemy, TURRET_DEFAULT);
break;
}
}
void enemy_update_physics_turret(struct Enemy* enemy, struct Game_State* game_state, float fixed_dt)
{
/* Turning/Rotation */
static vec3 yaw_axis = { 0.f, 1.f, 0.f };
if(enemy->Turret.scan)
{
float current_yaw = quat_get_yaw(&enemy->base.transform.rotation);
float yaw = enemy->Turret.turn_speed_current * 1.f * fixed_dt;
if(!enemy->Turret.yaw_direction_positive)
yaw *= -1.f;
current_yaw += yaw;
if(current_yaw >= enemy->Turret.max_yaw)
{
yaw = 0.f;
enemy->Turret.yaw_direction_positive = false;
}
else if(current_yaw <= -enemy->Turret.max_yaw)
{
yaw = 0.f;
enemy->Turret.yaw_direction_positive = true;
}
if(yaw != 0.f)
transform_rotate(enemy, &yaw_axis, yaw, TS_LOCAL);
}
else if(!enemy->Turret.scan)
{
float epsilon = 0.5f;
float current_yaw = quat_get_yaw(&enemy->base.transform.rotation);
float difference = enemy->Turret.target_yaw - current_yaw;
if(fabsf(difference) > epsilon)
{
float yaw = enemy->Turret.turn_speed_current * 1.f * fixed_dt;
if(current_yaw > enemy->Turret.target_yaw)
yaw *= -1.f;
transform_rotate(enemy, &yaw_axis, yaw, TS_LOCAL);
}
}
/* Movement */
if(enemy->Turret.pulsate)
{
float ticks = (float)platform_ticks_get();
vec3 translation = { 0.f };
vec3_assign(&translation, &enemy->mesh->base.transform.position);
translation.y += sinf(TO_RADIANS(ticks * enemy->Turret.pulsate_speed_scale)) * enemy->Turret.pulsate_height * fixed_dt ;
transform_set_position(enemy->mesh, &translation);
}
}
void enemy_update_ai_turret(struct Enemy* enemy, struct Game_State* game_state, float dt)
{
struct Scene* scene = game_state->scene;
struct Ray turret_ray;
transform_get_absolute_position(enemy->mesh, &turret_ray.origin);
transform_get_forward(enemy, &turret_ray.direction);
//quat rot = { 0.f, 0.f, 0.f, 1.f };
//quat_assign(&rot, &enemy->base.transform.rotation);
//quat_axis_angle(&rot, &UNIT_X, -90);
//vec4 color = { 0.f, 0.f, 1.f, 1.f };
//im_arc(enemy->Turret.vision_range, -enemy->Turret.max_yaw, enemy->Turret.max_yaw, 32, false, turret_ray.origin, rot, color, 5);
switch(enemy->current_state)
{
case TURRET_DEFAULT:
{
im_ray(&turret_ray, enemy->Turret.vision_range, enemy->Turret.color_default, 4);
struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER);
if(player)
{
float distance = scene_entity_distance(scene, player, enemy);
if(distance <= enemy->Turret.vision_range)
{
enemy_state_set_turret(enemy, TURRET_ALERT);
}
}
}
break;
case TURRET_ALERT:
{
enemy->Turret.time_elapsed_since_alert += dt;
if(enemy->Turret.time_elapsed_since_alert >= enemy->Turret.alert_cooldown)
{
enemy_state_set_turret(enemy, TURRET_DEFAULT);
break;
}
im_ray(&turret_ray, enemy->Turret.vision_range, enemy->Turret.color_alert, 4);
struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER);
if(player)
{
float distance = scene_entity_distance(scene, player, enemy);
if(distance <= enemy->Turret.vision_range)
{
enemy_state_set_turret(enemy, TURRET_ATTACK);
}
}
}
break;
case TURRET_ACQUIRE_TARGET:
{
struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER);
if(player)
{
float distance = scene_entity_distance(scene, &scene->player, enemy);
if(distance <= enemy->Turret.vision_range)
{
enemy_state_set_turret(enemy, TURRET_ATTACK);
break;
}
}
float distance = scene_entity_distance(scene, &scene->player, enemy);
if(distance <= enemy->Turret.vision_range)
{
vec3 player_pos = { 0.f };
vec3 dir_to_player = { 0.f };
transform_get_absolute_position(&scene->player, &player_pos);
vec3_sub(&dir_to_player, &player_pos, &turret_ray.origin);
vec3_norm(&dir_to_player, &dir_to_player);
im_ray_origin_dir(turret_ray.origin, dir_to_player, 10.f, (vec4) { 0.f, 1.f, 0.f, 1.f }, 5);
float yaw_required_to_face_player = floorf(vec3_signed_angle(&dir_to_player, &turret_ray.direction, &UNIT_Y));
float current_yaw = quat_get_yaw(&enemy->base.transform.rotation);
float new_target_yaw = current_yaw - yaw_required_to_face_player;
if(fabsf(floorf(new_target_yaw)) > enemy->Turret.max_yaw)
{
enemy_state_set_turret(enemy, TURRET_ALERT);
}
else
{
float difference = fabsf(enemy->Turret.target_yaw - new_target_yaw);
if(difference > 1.f)
enemy->Turret.target_yaw = new_target_yaw;
}
}
else
{
enemy_state_set_turret(enemy, TURRET_ALERT);
}
}
break;
case TURRET_ATTACK:
{
enemy->Turret.time_elapsed_since_attack += dt;
if(enemy->Turret.time_elapsed_since_attack >= enemy->Turret.attack_cooldown)
{
im_ray(&turret_ray, enemy->Turret.vision_range, enemy->Turret.color_attack, 4);
struct Entity* player = scene_ray_intersect_closest(scene, &turret_ray, ERM_PLAYER);
if(player)
{
float distance = scene_entity_distance(scene, player, enemy);
if(distance <= enemy->Turret.vision_range)
{
enemy->Turret.time_elapsed_since_attack = 0.f;
sound_source_play(game_state->sound, enemy->weapon_sound);
enemy->muzzle_light->intensity = enemy->muzzle_light_intensity_min + rand() % enemy->muzzle_light_intensity_max;
transform_scale(enemy->muzzle_light_mesh, &(vec3){1.f, 1.f, 1.f});
int hit_chance = 4;
int roll = rand() % 10;
if(roll <= enemy->hit_chance)
{
player_apply_damage(&scene->player, enemy);
}
}
else
{
enemy_state_set_turret(enemy, TURRET_ACQUIRE_TARGET);
}
}
else
{
enemy_state_set_turret(enemy, TURRET_ACQUIRE_TARGET);
}
}
}
break;
}
}
void enemy_state_set_turret(struct Enemy* enemy, int state)
{
assert(state >= 0 && state < TURRET_STATE_MAX);
struct Model* model = &enemy->mesh->model;
enemy->current_state = state;
switch(enemy->current_state)
{
case TURRET_DEFAULT:
{
vec4_assign(&model->material_params[MMP_DIFFUSE_COL].val_vec4, &enemy->Turret.color_default);
sound_source_play(game_state_get()->sound, enemy->ambient_sound);
enemy->Turret.time_elapsed_since_alert = 0.f;
enemy->Turret.time_elapsed_since_attack = 0.f;
enemy->Turret.pulsate = true;
enemy->Turret.scan = false;
enemy->Turret.target_yaw = 0.f;
enemy->Turret.turn_speed_current = enemy->Turret.turn_speed_default;
vec3 default_position = { 0.f };
transform_set_position(enemy->mesh, &default_position);
}
break;
case TURRET_ALERT:
{
enemy->Turret.pulsate = false;
enemy->Turret.turn_speed_current = enemy->Turret.turn_speed_default;
enemy->Turret.scan = true;
enemy->Turret.target_yaw = enemy->Turret.yaw_direction_positive ? enemy->Turret.max_yaw : -enemy->Turret.max_yaw;
vec4_assign(&model->material_params[MMP_DIFFUSE_COL].val_vec4, &enemy->Turret.color_alert);
enemy->Turret.time_elapsed_since_alert = 0.f;
}
break;
case TURRET_ACQUIRE_TARGET:
{
vec4_assign(&model->material_params[MMP_DIFFUSE_COL].val_vec4, &enemy->Turret.color_attack);
enemy->Turret.scan = false;
enemy->Turret.turn_speed_current = enemy->Turret.turn_speed_when_targetting;
}
break;
case TURRET_ATTACK:
{
enemy->Turret.pulsate = false;
enemy->Turret.scan = false;
float current_yaw = quat_get_yaw(&enemy->base.transform.rotation);
enemy->Turret.target_yaw = current_yaw;
vec4_assign(&model->material_params[MMP_DIFFUSE_COL].val_vec4, &enemy->Turret.color_attack);
enemy->Turret.time_elapsed_since_attack = enemy->Turret.attack_cooldown;
}
break;
}
}
void enemy_apply_damage(struct Enemy* enemy, int damage)
{
enemy->health -= damage;
vec4_fill(&enemy->mesh->model.material_params[MMP_DIFFUSE_COL].val_vec4, 0.7f, 0.3f, 0.1f, 1.f);
if(enemy->health <= 0)
{
scene_enemy_remove(game_state_get()->scene, enemy);
}
}