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