200 lines
5.3 KiB
JavaScript
200 lines
5.3 KiB
JavaScript
|
|
|
||
|
|
class entity_enemy_t extends entity_t {
|
||
|
|
_init(patrol_dir) {
|
||
|
|
|
||
|
|
// Animations
|
||
|
|
this._ANIMS =[
|
||
|
|
[1, [0]], // 0: Idle
|
||
|
|
[0.40, [1,2,3,4]], // 1: Walk
|
||
|
|
[0.20, [1,2,3,4]], // 2: Run
|
||
|
|
[0.25, [0,5,5,5]], // 3: Attack prepare
|
||
|
|
[0.25, [5,0,0,0]], // 4: Attack
|
||
|
|
];
|
||
|
|
|
||
|
|
// State definitions
|
||
|
|
// [0: anim_index, 1: speed, 2: next_state_update, 3: next_state]
|
||
|
|
this._STATE_IDLE = [0, 0, 0.1];
|
||
|
|
this._STATE_PATROL = [1, 0.5, 0.5];
|
||
|
|
this._STATE_FOLLOW = [2, 1, 0.3];
|
||
|
|
this._STATE_ATTACK_RECOVER = [0, 0, 0.1, this._STATE_FOLLOW];
|
||
|
|
this._STATE_ATTACK_EXEC = [4, 0, 0.4, this._STATE_ATTACK_RECOVER];
|
||
|
|
this._STATE_ATTACK_PREPARE = [3, 0, 0.4, this._STATE_ATTACK_EXEC];
|
||
|
|
this._STATE_ATTACK_AIM = [0, 0, 0.1, this._STATE_ATTACK_PREPARE];
|
||
|
|
this._STATE_EVADE = [2, 1, 0.8, this._STATE_ATTACK_AIM];
|
||
|
|
|
||
|
|
this.s = vec3(12,28,12);
|
||
|
|
|
||
|
|
this._step_height = 17;
|
||
|
|
this._speed = 196;
|
||
|
|
|
||
|
|
this._target_yaw = this._yaw;
|
||
|
|
this._state_update_at = 0;
|
||
|
|
this._attack_distance = 800;
|
||
|
|
this._evade_distance = 96;
|
||
|
|
this._attack_chance = 0.65;
|
||
|
|
this._keep_off_ledges = 1;
|
||
|
|
this._turn_bias = 1;
|
||
|
|
|
||
|
|
this._check_against = ENTITY_GROUP_PLAYER;
|
||
|
|
|
||
|
|
game_entities_enemies.push(this);
|
||
|
|
|
||
|
|
// If patrol_dir is non-zero it determines the partrol direction in
|
||
|
|
// increments of 90°. Otherwise we just idle.
|
||
|
|
if (patrol_dir) {
|
||
|
|
this._set_state(this._STATE_PATROL);
|
||
|
|
this._target_yaw = (Math.PI/2) * patrol_dir;
|
||
|
|
this._anim_time = Math.random();
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
this._set_state(this._STATE_IDLE);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
_set_state(state) {
|
||
|
|
this._state = state;
|
||
|
|
this._anim = this._ANIMS[state[0]];
|
||
|
|
this._anim_time = 0;
|
||
|
|
this._state_update_at = game_time + state[2] + state[2]/4 * Math.random();
|
||
|
|
}
|
||
|
|
|
||
|
|
_update() {
|
||
|
|
// Is it time for a state update?
|
||
|
|
if (this._state_update_at < game_time) {
|
||
|
|
|
||
|
|
// Choose a new turning bias for FOLLOW/EVADE when we hit a wall
|
||
|
|
this._turn_bias = Math.random() > 0.5 ? 0.5 : -0.5;
|
||
|
|
|
||
|
|
let distance_to_player = vec3_dist(this.p, game_entity_player.p),
|
||
|
|
angle_to_player = vec3_2d_angle(this.p, game_entity_player.p);
|
||
|
|
|
||
|
|
if (this._state[3]) {
|
||
|
|
this._set_state(this._state[3]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try to minimize distance to the player
|
||
|
|
if (this._state == this._STATE_FOLLOW) {
|
||
|
|
|
||
|
|
// Do we have a line of sight?
|
||
|
|
if (!map_trace(this.p, game_entity_player.p)) {
|
||
|
|
this._target_yaw = angle_to_player;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Are we close enough to attack?
|
||
|
|
if (distance_to_player < this._attack_distance) {
|
||
|
|
|
||
|
|
// Are we too close? Evade!
|
||
|
|
if (
|
||
|
|
distance_to_player < this._evade_distance ||
|
||
|
|
Math.random() > this._attack_chance
|
||
|
|
) {
|
||
|
|
this._set_state(this._STATE_EVADE);
|
||
|
|
this._target_yaw += Math.PI/2 + Math.random() * Math.PI;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Just the right distance to attack!
|
||
|
|
else {
|
||
|
|
this._set_state(this._STATE_ATTACK_AIM);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// We just attacked; just keep looking at the player 0_o
|
||
|
|
if (this._state == this._STATE_ATTACK_RECOVER) {
|
||
|
|
this._target_yaw = angle_to_player;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wake up from patroling or idlyng if we have a line of sight
|
||
|
|
// and are near enough
|
||
|
|
if (this._state == this._STATE_PATROL || this._state == this._STATE_IDLE) {
|
||
|
|
if (
|
||
|
|
distance_to_player < 700 &&
|
||
|
|
!map_trace(this.p, game_entity_player.p)
|
||
|
|
) {
|
||
|
|
this._set_state(this._STATE_ATTACK_AIM);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Aiming - reorient the entity towards the player, check
|
||
|
|
// if we have a line of sight
|
||
|
|
if (this._state == this._STATE_ATTACK_AIM) {
|
||
|
|
this._target_yaw = angle_to_player;
|
||
|
|
|
||
|
|
// No line of sight? Randomly shuffle around :/
|
||
|
|
if (map_trace(this.p, game_entity_player.p)) {
|
||
|
|
this._set_state(this._STATE_EVADE);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Execute the attack!
|
||
|
|
if (this._state == this._STATE_ATTACK_EXEC) {
|
||
|
|
this._attack();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Rotate to desired angle
|
||
|
|
this._yaw += anglemod(this._target_yaw - this._yaw) * 0.1;
|
||
|
|
|
||
|
|
|
||
|
|
// Move along the yaw direction with the current speed (which might be 0)
|
||
|
|
if (this._on_ground) {
|
||
|
|
this.v = vec3_rotate_y(vec3(0, this.v.y, this._state[1] * this._speed), this._target_yaw);
|
||
|
|
}
|
||
|
|
|
||
|
|
this._update_physics();
|
||
|
|
this._draw_model();
|
||
|
|
}
|
||
|
|
|
||
|
|
_spawn_projectile(type, speed, yaw_offset, pitch_offset) {
|
||
|
|
let projectile = game_spawn(type, this.p);
|
||
|
|
projectile._check_against = ENTITY_GROUP_PLAYER;
|
||
|
|
projectile._yaw = this._yaw + Math.PI/2;
|
||
|
|
|
||
|
|
projectile.v = vec3_rotate_yaw_pitch(
|
||
|
|
vec3(0, 0, speed),
|
||
|
|
this._yaw + yaw_offset,
|
||
|
|
Math.atan2(
|
||
|
|
this.p.y-game_entity_player.p.y,
|
||
|
|
vec3_dist(this.p, game_entity_player.p)
|
||
|
|
) + pitch_offset
|
||
|
|
);
|
||
|
|
return projectile;
|
||
|
|
}
|
||
|
|
|
||
|
|
_receive_damage(from, amount) {
|
||
|
|
super._receive_damage(from, amount);
|
||
|
|
this._play_sound(sfx_enemy_hit);
|
||
|
|
|
||
|
|
// Wake up if we're idle or patrolling
|
||
|
|
if (this._state == this._STATE_IDLE || this._state == this._STATE_PATROL) {
|
||
|
|
this._target_yaw = vec3_2d_angle(this.p, game_entity_player.p);
|
||
|
|
this._set_state(this._STATE_FOLLOW);
|
||
|
|
}
|
||
|
|
|
||
|
|
this._spawn_particles(2, 200, model_blood, 18, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
_kill() {
|
||
|
|
super._kill();
|
||
|
|
for (let m of model_gib_pieces) {
|
||
|
|
this._spawn_particles(2, 300, m, 18, 1);
|
||
|
|
}
|
||
|
|
this._play_sound(sfx_enemy_gib);
|
||
|
|
game_entities_enemies = game_entities_enemies.filter(e => e != this);
|
||
|
|
}
|
||
|
|
|
||
|
|
_did_collide(axis) {
|
||
|
|
if (axis == 1) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we hit a wall/ledge while patrolling just turn around 180
|
||
|
|
if (this._state == this._STATE_PATROL) {
|
||
|
|
this._target_yaw += Math.PI;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
this._target_yaw += this._turn_bias;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|