mirror of
https://github.com/nzp-team/quakec.git
synced 2025-04-15 14:30:59 +00:00
1121 lines
50 KiB
C++
1121 lines
50 KiB
C++
enum anim_stop_type : float {
|
||
ANIM_STOP_TYPE_STOP, // Animation stops and freezes at final frame.
|
||
ANIM_STOP_TYPE_LOOP, // Animation starts again from the first frame.
|
||
ANIM_STOP_TYPE_NEXT_ANIM, // Another animation is played when this one finishes.
|
||
};
|
||
|
||
|
||
|
||
float zombie_anim_idle_modelindex;
|
||
float zombie_anim_rise_modelindex;
|
||
float zombie_anim_walk1_modelindex;
|
||
float zombie_anim_walk2_modelindex;
|
||
float zombie_anim_walk3_modelindex;
|
||
float zombie_anim_jog1_modelindex;
|
||
float zombie_anim_run1_modelindex;
|
||
float zombie_anim_climbledge_modelindex;
|
||
|
||
|
||
#define AI_STATE_PATHING 0
|
||
#define AI_STATE_TRAVERSING 1
|
||
|
||
|
||
enum IQM_EVENT:float {
|
||
IQM_EVENT_SOUND = 1,
|
||
IQM_EVENT_MOVE_SPEED = 2,
|
||
IQM_EVENT_ZOMBIE_FOOTSTEP = 3,
|
||
};
|
||
|
||
|
||
|
||
#define ZOMBIE_LIMB_STATE_HEAD 1
|
||
#define ZOMBIE_LIMB_STATE_ARM_L 2
|
||
#define ZOMBIE_LIMB_STATE_ARM_R 4
|
||
#define ZOMBIE_LIMB_STATE_LEG_L 8
|
||
#define ZOMBIE_LIMB_STATE_LEG_R 16
|
||
|
||
|
||
void() play_zombie_footstep {
|
||
if(random() < 0.5) {
|
||
sound(self, 5, "sounds/zombie/s0.wav", 1, ATTN_NORM);
|
||
}
|
||
else {
|
||
sound(self, 5, "sounds/zombie/s1.wav", 1, ATTN_NORM);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
// AI_Chase entity defines a generic entity type that knows how to navigate
|
||
// the map using the map's navmesh
|
||
// AI_Chase entities are designed to use Skeletal IQM models
|
||
class AI_Chase : entity {
|
||
// -----------------------------
|
||
// AI state vars
|
||
// -----------------------------
|
||
entity path_target; // If specified, path towards entity
|
||
entity enemy;
|
||
vector path_pos; // Otherwise, path towards location specified
|
||
float state;
|
||
float substate;
|
||
// -----------------------------
|
||
|
||
// -----------------------------
|
||
// Traversal state vars
|
||
// -----------------------------
|
||
float cur_traversal_idx;
|
||
float cur_traversal_end_time;
|
||
float cur_traversal_start_time;
|
||
|
||
// Think callback executed every logic tick, independent of current animation
|
||
virtual void() think_callback = SUB_Null;
|
||
// // We also have a frame callback that's called each time a new animation frame is reached
|
||
// virtual void() frame_callback = SUB_Null;
|
||
|
||
float think_delta_time; // Time (in seconds) between entity's ".think()" invocations
|
||
// ------------------------------
|
||
// Animation variables
|
||
// ------------------------------
|
||
// --- Current animation vars ---
|
||
float cur_anim_model_idx;
|
||
float cur_anim_framegroup;
|
||
float cur_anim_start_time; // Time at which the animation was started
|
||
float cur_anim_last_time; // Time into the animation at which the last `think` was invoked
|
||
float cur_anim_playback_speed; // 1.0 = Normal speed, 2.0 = Twice as fast, 0.5 = Half-speed, etc.
|
||
// float cur_anim_move_dist;
|
||
// Cached derived vars:
|
||
float cur_anim_duration; // Length (in seconds) of current animation at 1.0 playback speed
|
||
|
||
// --- Next / Queued animation vars ---
|
||
float next_anim_model_idx;
|
||
float next_anim_framegroup;
|
||
float next_anim_playback_speed;
|
||
|
||
float pathfind_result_idx; // TODO - Need to increment this on instantiation
|
||
float pathfind_cur_point_idx;
|
||
|
||
// TODO - Move this to AI_Zombie subclass?
|
||
float limbs_state;
|
||
|
||
|
||
// ------------------------------------------------------------------------
|
||
// Animation movement integration fields / code
|
||
// ------------------------------------------------------------------------
|
||
float cur_anim_move_dist; // Accumulates `dist` for all events overlapping region
|
||
float cur_anim_move_region_start_time; // Start time of search region
|
||
float cur_anim_move_region_end_time; // End time of search region
|
||
float cur_anim_move_last_event_dist; // `dist` value for the last event processed
|
||
float cur_anim_move_last_event_start; // Event start time for the last event processed
|
||
|
||
|
||
// Returns the fraction of an event that overlaps with the saerch region
|
||
// The search region is defined over: [this.cur_anim_move_region_start_time, this.cur_anim_move_region_end_time]
|
||
virtual float(float event_start_time, float event_end_time) anim_move_get_event_intersection;
|
||
|
||
// Callback for built-in `processmodelevents` to integrate all animation movement between two timestamps
|
||
virtual void(float event_timestamp, int event_code, string event_data) anim_move_accumulate_iqm_event_movement;
|
||
virtual float() calc_anim_movement_speed;
|
||
// ------------------------------------------------------------------------
|
||
|
||
virtual void (float move_speed) do_walk_to_goal;
|
||
virtual float(entity to, float sendflags) send_entity_func;
|
||
|
||
// Constructor. Called when calling `spawn(AI_Chase);`
|
||
virtual void() AI_Chase;
|
||
|
||
// void() take_damage = {
|
||
// // TODO - Update limb states, mark as needing networking
|
||
// }
|
||
|
||
virtual void(float timestamp, int event_code, string event_data) handle_iqm_event;
|
||
|
||
// Stop what you're doing and play this animation immediately.
|
||
virtual void(float anim_model_idx, float anim_framegroup) play_anim;
|
||
|
||
// Queue up another animation to play when the current one finishes
|
||
virtual void(float anim_model_idx, float anim_framegroup) queue_anim;
|
||
|
||
// The "logic tick" of the entity.
|
||
virtual void() think;
|
||
|
||
|
||
// virtual void () fg_die = {};
|
||
// virtual void () fg_walk = {};
|
||
// virtual void () fg_attack = {};
|
||
// virtual void () fg_idle = {};
|
||
// virtual void (float dist) do_walk_to_goal;
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
|
||
void zombie_traversal_logic() {
|
||
// AI_Zombie zombie_ent = (AI_Zombie) self;
|
||
AI_Chase zombie_ent = (AI_Chase) self;
|
||
|
||
float traversal_idx = zombie_ent.cur_traversal_idx;
|
||
vector start_pos = sv_navmesh_traversals[traversal_idx].start_pos;
|
||
vector midpoint_pos;
|
||
vector end_pos = sv_navmesh_get_traversal_end_pos(traversal_idx);
|
||
|
||
|
||
string traversal_type;
|
||
traversal_type = "ledge";
|
||
// traversal_type = "leap";
|
||
// traversal_type = "jump_gap";
|
||
// traversal_type = "hop_barricade";
|
||
// traversal_type = "hop_fence";
|
||
// traversal_type = "window";
|
||
// traversal_type = "teleport";
|
||
|
||
// Jump up logic
|
||
if(traversal_type == "ledge") {
|
||
// Adjust zombie angle to its smallest representation
|
||
zombie_ent.angles.y = ((zombie_ent.angles.y + 180) % 360) - 180;
|
||
// Apply smallest delta angle
|
||
float delta_angle = sv_navmesh_traversals[traversal_idx].angle - zombie_ent.angles.y;
|
||
delta_angle = ((delta_angle + 180) % 360) - 180;
|
||
zombie_ent.angles.y += 0.5 * delta_angle;
|
||
|
||
// Jump up traversal consists of the following substates:
|
||
// 0: Start traversal, play jump up anim
|
||
// 1: Wait for jump up anim to complete
|
||
// 2: Move zombie up to ledge
|
||
// 3: Play zombie get up animation
|
||
//
|
||
// Check height of ledge we're climbing:
|
||
float traversal_height = end_pos.z - start_pos.z;
|
||
float traversal_time;
|
||
float lerp_frac;
|
||
float anim_time;
|
||
vector ledge_pos;
|
||
|
||
|
||
|
||
// Fall down
|
||
if(traversal_height < 0 ) {
|
||
if(zombie_ent.substate == 0) {
|
||
zombie_ent.movetype = MOVETYPE_STEP;
|
||
// zombie_ent.play_anim(get_anim_frame_zombie_fall, get_anim_length_zombie_fall(), ANIM_STOP_TYPE_STOP);
|
||
// zombie_ent.queue_anim(get_anim_frame_zombie_fall_loop, get_anim_length_zombie_fall_loop(), ANIM_STOP_TYPE_LOOP);
|
||
traversal_time = min(-traversal_height * (0.35 / 100.0), 2.0);
|
||
zombie_ent.cur_traversal_start_time = time;
|
||
zombie_ent.cur_traversal_end_time = time + traversal_time;
|
||
zombie_ent.substate = 1;
|
||
}
|
||
else if(zombie_ent.substate == 1) {
|
||
lerp_frac = (time - zombie_ent.cur_traversal_start_time) / (zombie_ent.cur_traversal_end_time - zombie_ent.cur_traversal_start_time);
|
||
zombie_ent.origin = lerpVector(start_pos, end_pos, lerp_frac * lerp_frac);
|
||
if(lerp_frac >= 1.0) {
|
||
// zombie_ent.play_anim(get_anim_frame_zombie_land, get_anim_length_zombie_land(), ANIM_STOP_TYPE_NEXT_ANIM);
|
||
// zombie_ent.cur_anim_frametime = 0.05;
|
||
// zombie_ent.queue_anim(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_LOOP);
|
||
zombie_ent.state = AI_STATE_PATHING;
|
||
zombie_ent.movetype = MOVETYPE_WALK;
|
||
}
|
||
}
|
||
}
|
||
// Short ledge
|
||
// else if(traversal_height < 98) {
|
||
// makevectors([0, sv_navmesh_traversals[traversal_idx].angle, 0]);
|
||
// ledge_pos = end_pos - '0 0 72' - v_forward * 21;
|
||
|
||
// if(zombie_ent.substate == 0) {
|
||
// zombie_ent.movetype = MOVETYPE_STEP;
|
||
// // If short jump up, play short jump / short climb anim
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_jump_low, get_anim_length_zombie_jump_low(), ANIM_STOP_TYPE_STOP);
|
||
// anim_time = 1;
|
||
// // anim_time = (zombie_ent.cur_anim_length - 1) * zombie_ent.cur_anim_frametime;
|
||
// // zombie_ent.cur_traversal_end_time = time + anim_time - (2 * zombie_ent.cur_anim_frametime);
|
||
// zombie_ent.cur_traversal_end_time = time + anim_time;
|
||
// // zombie_ent.cur_traversal_end_time = 0;
|
||
// // Stash anim stop-time in this variable so we can tell when to proceed: (minus three frames)
|
||
|
||
// zombie_ent.cur_traversal_start_time = time;
|
||
// zombie_ent.cur_traversal_end_time = time + 0.3;
|
||
// zombie_ent.substate = 1;
|
||
// }
|
||
// if(zombie_ent.substate == 1) {
|
||
// lerp_frac = (time - zombie_ent.cur_traversal_start_time) / (zombie_ent.cur_traversal_end_time - zombie_ent.cur_traversal_start_time);
|
||
// zombie_ent.origin = lerpVector(start_pos, ledge_pos, lerp_frac);
|
||
|
||
// if(lerp_frac >= 1) {
|
||
// zombie_ent.substate = 2;
|
||
// // If short jump up, play short climb anim
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_climb_low, get_anim_length_zombie_climb_low(), ANIM_STOP_TYPE_STOP);
|
||
// // anim_time = (zombie_ent.cur_anim_length - 1) * zombie_ent.cur_anim_frametime;
|
||
// anim_time = 1;
|
||
// zombie_ent.cur_traversal_start_time = time;
|
||
// zombie_ent.cur_traversal_end_time = time + anim_time;
|
||
// }
|
||
// }
|
||
// else if(zombie_ent.substate == 2) {
|
||
// lerp_frac = (time - zombie_ent.cur_traversal_start_time) / (zombie_ent.cur_traversal_end_time - zombie_ent.cur_traversal_start_time);
|
||
// start_pos = end_pos - '0 0 72' - v_forward * 21;
|
||
// zombie_ent.origin = lerpVector(start_pos, end_pos, lerp_frac);
|
||
// if(lerp_frac >= 1.0) {
|
||
// zombie_ent.state = AI_STATE_PATHING;
|
||
// zombie_ent.movetype = MOVETYPE_WALK;
|
||
// // FIXME - Need a better way to revert to walking
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_LOOP);
|
||
// }
|
||
// }
|
||
// }
|
||
// Tall ledge
|
||
else {
|
||
makevectors([0, sv_navmesh_traversals[traversal_idx].angle, 0]);
|
||
// ledge_pos = end_pos - '0 0 98' - v_forward * 28;
|
||
ledge_pos = end_pos - '0 0 94' - v_forward * 28;
|
||
|
||
if(zombie_ent.substate == 0) {
|
||
zombie_ent.movetype = MOVETYPE_STEP;
|
||
zombie_ent.play_anim(zombie_anim_climbledge_modelindex, 0);
|
||
// Wait until jump animation is near completion to start moving zombie
|
||
zombie_ent.cur_traversal_end_time = time + zombie_ent.cur_anim_duration - 0.3;
|
||
zombie_ent.substate = 1;
|
||
|
||
// zombie_ent.cur_anim_get_frame_func = (float(float)) SUB_Null;
|
||
// Figure out how fast to move the zombie
|
||
// float traversal_length = vlen(end_pos - start_pos);
|
||
// zombie_ent.cur_traversal_start_time = time;
|
||
}
|
||
else if(zombie_ent.substate == 1) {
|
||
if(zombie_ent.cur_traversal_end_time <= time) {
|
||
// Zombie moves from bottom to top of ledge in fixed time:
|
||
traversal_time = 0.15; // seconds
|
||
// TODO - Should we determine how fast the zombie moves based on traversal distance?
|
||
// TODO Otherwise zombie will always jump up in 0.5 seconds regardless of ledge height
|
||
zombie_ent.cur_traversal_start_time = time;
|
||
zombie_ent.cur_traversal_end_time = time + traversal_time;
|
||
zombie_ent.substate = 2;
|
||
}
|
||
}
|
||
else if(zombie_ent.substate == 2) {
|
||
// Move zombie up to ledge
|
||
lerp_frac = (time - zombie_ent.cur_traversal_start_time) / (zombie_ent.cur_traversal_end_time - zombie_ent.cur_traversal_start_time);
|
||
setorigin(zombie_ent, lerpVector(start_pos, ledge_pos, lerp_frac));
|
||
|
||
// Once at ledge, play climb animation:
|
||
if(lerp_frac >= 1) {
|
||
zombie_ent.substate = 3;
|
||
zombie_ent.play_anim(zombie_anim_climbledge_modelindex, 1);
|
||
zombie_ent.cur_traversal_start_time = time;
|
||
// Wait until animation near completion to advance
|
||
zombie_ent.cur_traversal_end_time = time + (zombie_ent.cur_anim_duration - 0.2);
|
||
}
|
||
}
|
||
else if(zombie_ent.substate == 3) {
|
||
lerp_frac = (time - zombie_ent.cur_traversal_start_time) / (zombie_ent.cur_traversal_end_time - zombie_ent.cur_traversal_start_time);
|
||
setorigin(zombie_ent, lerpVector(ledge_pos, end_pos, lerp_frac));
|
||
|
||
if(lerp_frac >= 1.0) {
|
||
zombie_ent.state = AI_STATE_PATHING;
|
||
zombie_ent.movetype = MOVETYPE_WALK;
|
||
// FIXME - Need a better way to revert to walking
|
||
zombie_ent.play_anim(zombie_anim_walk1_modelindex, 0);
|
||
}
|
||
}
|
||
}
|
||
// Mark to send origin / angles to clients
|
||
zombie_ent.SendFlags |= 1;
|
||
|
||
return;
|
||
}
|
||
// TODO - Implement leap
|
||
// else if(traversal_type == "leap") {
|
||
// midpoint_pos = sv_navmesh_get_traversal_midpoint_pos(traversal_idx);
|
||
|
||
// // Adjust zombie angle to its smallest representation
|
||
// zombie_ent.angles.y = ((zombie_ent.angles.y + 180) % 360) - 180;
|
||
// // Apply smallest delta angle
|
||
// float delta_angle = sv_navmesh_traversals[traversal_idx].angle - zombie_ent.angles.y;
|
||
// delta_angle = ((delta_angle + 180) % 360) - 180;
|
||
// zombie_ent.angles.y += 0.5 * delta_angle;
|
||
|
||
// // Leap traversal consists of the following substates:
|
||
// // 0: Play leap animation (frames 218-233)
|
||
// // 1: Wait for frame 233
|
||
// // 2: Move zombie across arc to end pos, ends at frame 241
|
||
// // 3: Wait at endpos for frame 247
|
||
|
||
// // TODO - Adjust traversal speed?
|
||
// // TODO - Break up leap start / leap-mid-air / leap land anims?
|
||
// // float traversal_length =
|
||
|
||
// float lerp_frac;
|
||
|
||
// if(zombie_ent.substate == 0) {
|
||
// zombie_ent.movetype = MOVETYPE_STEP;
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_leap_jump, get_anim_length_zombie_leap_jump(), ANIM_STOP_TYPE_STOP);
|
||
// zombie_ent.substate = 1;
|
||
// // Advance to next substate (1->2) after 5 frames
|
||
// zombie_ent.cur_traversal_start_time = time;
|
||
// zombie_ent.cur_traversal_end_time = time + (5 * zombie_ent.cur_anim_frametime);
|
||
// }
|
||
// else if(zombie_ent.substate == 1) {
|
||
// if(zombie_ent.cur_traversal_end_time <= time) {
|
||
// zombie_ent.substate = 2;
|
||
// // Advance to next substate (2->3) after 8 frames
|
||
// zombie_ent.cur_traversal_start_time = time;
|
||
// zombie_ent.cur_traversal_end_time = time + (8 * zombie_ent.cur_anim_frametime);
|
||
// }
|
||
// }
|
||
// else if(zombie_ent.substate == 2) {
|
||
// lerp_frac = (time - zombie_ent.cur_traversal_start_time) / (zombie_ent.cur_traversal_end_time - zombie_ent.cur_traversal_start_time);
|
||
// zombie_ent.origin = lerp_vector_bezier(start_pos, midpoint_pos, end_pos, lerp_frac);
|
||
|
||
// if(lerp_frac >= 1) {
|
||
// zombie_ent.substate = 3;
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_leap_land, get_anim_length_zombie_leap_land(), ANIM_STOP_TYPE_STOP);
|
||
// float anim_time = (zombie_ent.cur_anim_length - 1) * zombie_ent.cur_anim_frametime;
|
||
// // Finish traversal at the end of the land animation
|
||
// zombie_ent.cur_traversal_start_time = time;
|
||
// zombie_ent.cur_traversal_end_time = time + anim_time;
|
||
|
||
// }
|
||
// }
|
||
// else if(zombie_ent.substate == 3) {
|
||
// if(zombie_ent.cur_traversal_end_time <= time) {
|
||
// zombie_ent.state = AI_STATE_PATHING;
|
||
// zombie_ent.movetype = MOVETYPE_WALK;
|
||
// // FIXME - Need a better way to revert to walking
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_LOOP);
|
||
// }
|
||
// }
|
||
// return;
|
||
// }
|
||
|
||
|
||
|
||
// // Starting traversal
|
||
// if(zombie_ent.substate == 0) {
|
||
// zombie_ent.velocity = '0 0 0';
|
||
// // zombie_ent.cur_anim_get_frame_func = (float(float)) SUB_Null;
|
||
// // Start zombie animation:
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_window_hop, get_anim_length_zombie_window_hop(), ANIM_STOP_TYPE_STOP);
|
||
// // zombie_ent.play_anim(get_anim_frame_zombie_jump_climb, get_anim_length_zombie_jump_climb(), ANIM_STOP_TYPE_STOP);
|
||
// // zombie_ent.cur_anim_get_frame_func = (float(float)) SUB_Null;
|
||
// // Figure out how fast to move the zombie
|
||
// float traversal_length = vlen(end_pos - start_pos);
|
||
// float anim_time = (zombie_ent.cur_anim_length - 1) * zombie_ent.cur_anim_frametime;
|
||
|
||
// zombie_ent.cur_traversal_start_time = time;
|
||
// // FIXME - Some traversals will have a different way of getting speed...
|
||
// zombie_ent.cur_traversal_end_time = time + anim_time;
|
||
// zombie_ent.substate = 1;
|
||
// zombie_ent.movetype = MOVETYPE_STEP;
|
||
// zombie_ent.angles.y = sv_navmesh_traversals[traversal_idx].angle;
|
||
// }
|
||
// // Moving zombie across traversal
|
||
// if(zombie_ent.substate == 1) {
|
||
// float lerp_frac = (time - zombie_ent.cur_traversal_start_time) / (zombie_ent.cur_traversal_end_time - zombie_ent.cur_traversal_start_time);
|
||
|
||
// if(lerp_frac > 1.0) {
|
||
// zombie_ent.state = AI_STATE_PATHING;
|
||
// zombie_ent.movetype = MOVETYPE_WALK;
|
||
|
||
// // TODO - How to tell zombie to play walk anim again?
|
||
// // FIXME - This ain't right
|
||
// zombie_ent.play_anim(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_LOOP);
|
||
// }
|
||
|
||
// // If the traversal uses the midpoint, lerp across midpoint
|
||
// if(sv_navmesh_traversals[zombie_ent.cur_traversal_idx].use_midpoint) {
|
||
// print("Current lerpfrac: ", ftos(lerp_frac), "\n");
|
||
// midpoint_pos = sv_navmesh_get_traversal_midpoint_pos(traversal_idx);
|
||
// // Lerp from start to midpoint
|
||
// if(lerp_frac < 0.5) {
|
||
// zombie_ent.origin = lerpVector(start_pos, midpoint_pos, lerp_frac * 2.0);
|
||
// }
|
||
// // Lerp from midpoint to end
|
||
// else {
|
||
// zombie_ent.origin = lerpVector(midpoint_pos, end_pos, (lerp_frac - 0.5) * 2.0);
|
||
// }
|
||
// }
|
||
// // Otherwise, lerp from start to end
|
||
// else {
|
||
// zombie_ent.origin = lerpVector(start_pos, end_pos, lerp_frac);
|
||
// }
|
||
|
||
|
||
|
||
// // TODO - Moving
|
||
// // vector start_pos = sv_navmesh_traversals[traversal_idx].start_pos;
|
||
// // vector end_pos = sv_navmesh_get_traversal_end_pos(traversal_idx);
|
||
// }
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// Returns the fraction of an event that overlaps with the saerch region
|
||
// The search region is defined over: [this.cur_anim_move_region_start_time, this.cur_anim_move_region_end_time]
|
||
float(float event_start_time, float event_end_time) AI_Chase::anim_move_get_event_intersection {
|
||
if(event_end_time < this.cur_anim_move_region_start_time) {
|
||
return 0;
|
||
}
|
||
if(event_start_time > this.cur_anim_move_region_end_time) {
|
||
return 0;
|
||
}
|
||
// At this point we know there's overlap.
|
||
// Compute intersection of search region and event region
|
||
float overlap_start = clamp(this.cur_anim_move_region_start_time, event_start_time, event_end_time);
|
||
float overlap_end = clamp(this.cur_anim_move_region_end_time, event_start_time, event_end_time);
|
||
// Compute interpolation factors at the start and stop of the intersection
|
||
float overlap_start_lerp_frac = (overlap_start - event_start_time) / (event_end_time - event_start_time);
|
||
float overlap_end_lerp_frac = (overlap_end - event_start_time) / (event_end_time - event_start_time);
|
||
float overlap = overlap_end_lerp_frac - overlap_start_lerp_frac;
|
||
return overlap;
|
||
};
|
||
|
||
// Callback for built-in `processmodelevents` to integrate all animation movement between two timestamps
|
||
void(float event_timestamp, int event_code, string event_data) AI_Chase::anim_move_accumulate_iqm_event_movement {
|
||
if(event_code == IQM_EVENT_MOVE_SPEED) {
|
||
float event_dist = stof(event_data);
|
||
float anim_looped = true; // FIXME - Assumes all anims are looped. Is there any way to tell whether an animation is looped?
|
||
|
||
float last_event_start_time = this.cur_anim_move_last_event_start;
|
||
float last_event_end_time = event_timestamp;
|
||
|
||
// On the first iteration, last_event_timestamp is -1
|
||
// No edge cases here!
|
||
while(true) {
|
||
// If `last_event` falls completely after search region, stop
|
||
if(last_event_start_time > this.cur_anim_move_region_end_time) {
|
||
break;
|
||
}
|
||
this.cur_anim_move_dist += this.cur_anim_move_last_event_dist * anim_move_get_event_intersection(last_event_start_time, last_event_end_time);
|
||
// If the animation isn't looped, only consider the first iteration
|
||
if(anim_looped == false) {
|
||
break;
|
||
}
|
||
last_event_start_time += this.cur_anim_duration;
|
||
last_event_end_time += this.cur_anim_duration;
|
||
}
|
||
|
||
// Store current event as last event
|
||
this.cur_anim_move_last_event_dist = event_dist;
|
||
this.cur_anim_move_last_event_start = event_timestamp;
|
||
}
|
||
};
|
||
|
||
float() AI_Chase::calc_anim_movement_speed {
|
||
float walk_dist = 0;
|
||
// Given current animation state, read through the IQM events to determine exactly how much to move at this logic tick
|
||
// float prev_time = time - ent.think_delta_time;
|
||
|
||
|
||
// float anim_last_frametime = this.cur_anim_last_time % this.cur_anim_duration;
|
||
// float anim_cur_frametime = ((time - this.cur_anim_start_time) * this.cur_anim_playback_speed) % this.cur_anim_duration;
|
||
float anim_last_frametime = this.cur_anim_last_time;
|
||
float anim_cur_frametime = ((time - this.cur_anim_start_time) * this.cur_anim_playback_speed);
|
||
|
||
|
||
// print("Current anim frametimes: ", ftos(anim_last_frametime), " -> ", ftos(anim_cur_frametime), "\n");
|
||
|
||
this.cur_anim_move_dist = 0;
|
||
this.cur_anim_move_last_event_dist = 0;
|
||
this.cur_anim_move_last_event_start = -1;
|
||
// We know anim_last_frametime < anim_cur_frametime
|
||
// Wrap anim_last_frametime so it falls within [0, anim_duration]
|
||
// Then make anim_cur_frametime relative to that (not wrapped)
|
||
this.cur_anim_move_region_end_time = anim_cur_frametime - anim_last_frametime; // First set it to delta
|
||
this.cur_anim_move_region_start_time = anim_last_frametime % this.cur_anim_duration; // Wrap `last_frametime`
|
||
this.cur_anim_move_region_end_time += this.cur_anim_move_region_start_time; // Add `start_time` back in to get region end relative to region start
|
||
|
||
// Search through the entire animation:
|
||
float region_start = 0;
|
||
float region_end = this.cur_anim_duration + 1;
|
||
// print("Accumulating moves over: (", ftos(region_start), ",", ftos(region_end), "), Search range: (", ftos(this.cur_anim_move_region_start_time));
|
||
// print(",", ftos(this.cur_anim_move_region_end_time),").\n");
|
||
processmodelevents(this.cur_anim_model_idx, this.cur_anim_framegroup, region_start, region_end, this.anim_move_accumulate_iqm_event_movement);
|
||
// Call one more time so the final event has a chance to be deposited
|
||
this.anim_move_accumulate_iqm_event_movement(this.cur_anim_duration, IQM_EVENT_MOVE_SPEED, "0");
|
||
// print("\tFinal Result: ",ftos(this.cur_anim_move_dist),"\n");
|
||
walk_dist = this.cur_anim_move_dist;
|
||
|
||
|
||
// OKAY - We now have exactly how much the zombie should have moved between the last two logic ticks...
|
||
// To get speed, we simply divide by think_delta_time, no?
|
||
float movement_speed = walk_dist / this.think_delta_time;
|
||
return movement_speed;
|
||
};
|
||
// ------------------------------------------------------------------------
|
||
|
||
|
||
|
||
|
||
void (float move_speed) AI_Chase::do_walk_to_goal {
|
||
if(move_speed == 0) {
|
||
return;
|
||
}
|
||
// move_speed=0;
|
||
|
||
vector goal_pos = path_pos;
|
||
if(this.path_target != world) {
|
||
goal_pos = this.path_target.origin;
|
||
}
|
||
|
||
// TODO - For PSP, call engine-side function that gets us next walk point
|
||
#ifdef PC
|
||
navmesh_pathfind_result* res = &(sv_zombie_pathfind_result[this.pathfind_result_idx]);
|
||
float start_poly = sv_navmesh_get_containing_poly(this.origin);
|
||
float goal_poly = sv_navmesh_get_containing_poly(goal_pos);
|
||
float pathfind_success = sv_navmesh_pathfind_start(start_poly, goal_poly, this.origin, goal_pos, res);
|
||
this.pathfind_cur_point_idx = 0;
|
||
|
||
if(pathfind_success) {
|
||
goal_pos = res->point_path_points[this.pathfind_cur_point_idx];
|
||
|
||
// TOOD - If close, continue to next one...
|
||
// TODO - Also make sure we're "close enough" in Z...
|
||
if(vlen_xy(this.origin - goal_pos) < 5) {
|
||
print("Distance from target: ", ftos(vlen_xy(this.origin - goal_pos)), ", Cur point: ", ftos(this.pathfind_cur_point_idx), "\n");
|
||
print("Current traversal: ", ftos(res->point_path_traversals[this.pathfind_cur_point_idx]),"\n");
|
||
// If this point path is a traversal, teleport to traversal end spot
|
||
|
||
if(res->point_path_traversals[this.pathfind_cur_point_idx] != -1) {
|
||
this.state = AI_STATE_TRAVERSING;
|
||
this.substate = 0;
|
||
this.cur_traversal_idx = res->point_path_traversals[this.pathfind_cur_point_idx];
|
||
}
|
||
this.pathfind_cur_point_idx += 1;
|
||
goal_pos = res->point_path_points[this.pathfind_cur_point_idx];
|
||
}
|
||
}
|
||
else {
|
||
move_speed = 0;
|
||
// TODO - Idle animation?
|
||
}
|
||
#endif // PC
|
||
|
||
this.ideal_yaw = vectoyaw(goal_pos - this.origin);
|
||
|
||
// ChangeYaw();
|
||
// Apply smallest delta angle
|
||
float delta_angle = this.ideal_yaw - this.angles.y;
|
||
delta_angle = ((delta_angle + 180) % 360) - 180;
|
||
this.angles.y += 0.3 * delta_angle;
|
||
|
||
vector new_velocity;
|
||
// float walk_dist = move_speed * this.think_delta_time;
|
||
// float dist_to_goal = vlen(goal_pos - this.origin);
|
||
// if(walk_dist > dist_to_goal) {
|
||
// // TODO - If zombie vel is high enough, and think is too infrequent, we
|
||
// // TODO can overshoot. If this condition is true, we will have hit
|
||
// // TODO the goal position.
|
||
// // TOOD - Mark that the zombie _should_ have gotten to its goal by the next think invocation
|
||
// // Force the zombie to be relocated to the position?
|
||
// move_speed = dist_to_goal / this.think_delta_time;
|
||
// }
|
||
new_velocity = normalize(goal_pos - this.origin) * move_speed;
|
||
new_velocity_z = this.velocity_z;
|
||
this.velocity = new_velocity;
|
||
this.SendFlags |= 1;
|
||
};
|
||
|
||
|
||
float(entity to, float sendflags) AI_Chase::send_entity_func {
|
||
WriteByte(MSG_ENTITY, ENT_TYPE_ZOMBIE);
|
||
// Write bytflags indicating payload contents
|
||
WriteByte(MSG_ENTITY, sendflags);
|
||
// Send ent location (Required by CSQC)
|
||
if(sendflags & 1) {
|
||
WriteCoord( MSG_ENTITY, self.origin_x);
|
||
WriteCoord( MSG_ENTITY, self.origin_y);
|
||
WriteCoord( MSG_ENTITY, self.origin_z);
|
||
WriteCoord( MSG_ENTITY, self.angles_x);
|
||
WriteCoord( MSG_ENTITY, self.angles_y);
|
||
WriteCoord( MSG_ENTITY, self.angles_z);
|
||
WriteShort( MSG_ENTITY, self.velocity_x);
|
||
WriteShort( MSG_ENTITY, self.velocity_y);
|
||
WriteShort( MSG_ENTITY, self.velocity_z);
|
||
WriteFloat( MSG_ENTITY, self.flags); // Flags, important for physics
|
||
}
|
||
// Send animation state variables
|
||
if(sendflags & 2) {
|
||
WriteByte( MSG_ENTITY, self.cur_anim_model_idx);
|
||
WriteByte( MSG_ENTITY, self.cur_anim_framegroup);
|
||
WriteFloat( MSG_ENTITY, self.cur_anim_start_time);
|
||
WriteFloat( MSG_ENTITY, self.cur_anim_playback_speed);
|
||
}
|
||
// Send skin number
|
||
if(sendflags & 4) {
|
||
WriteByte( MSG_ENTITY, self.skin);
|
||
}
|
||
// Send limb state
|
||
if(sendflags & 8) {
|
||
WriteByte( MSG_ENTITY, self.limbs_state);
|
||
}
|
||
// Send skeleton index
|
||
if(sendflags & 64) {
|
||
WriteByte( MSG_ENTITY, self.skeletonindex);
|
||
// print("SSQC skeleton index: ", ftos(self.skeletonindex), "\n");
|
||
}
|
||
// Send model index
|
||
if(sendflags & 128) {
|
||
WriteByte( MSG_ENTITY, self.modelindex);
|
||
}
|
||
// TODO - Send limb state / texture info
|
||
return TRUE;
|
||
};
|
||
|
||
|
||
|
||
// Constructor. Called when calling `spawn(AI_Chase);`
|
||
void() AI_Chase::AI_Chase {
|
||
this.path_target = world;
|
||
// this.path_pos = world.origin;
|
||
this.cur_anim_model_idx = -1;
|
||
this.next_anim_model_idx = -1;
|
||
this.think_delta_time = 0.1; // Call `this.think();` 10x per second
|
||
|
||
// TODO - Set model? Create skeleton?
|
||
// Should this be done in constructor?
|
||
this.SendEntity = this.send_entity_func;
|
||
this.cur_anim_playback_speed = 1;
|
||
};
|
||
|
||
// void() take_damage = {
|
||
// // TODO - Update limb states, mark as needing networking
|
||
// }
|
||
|
||
void(float timestamp, int event_code, string event_data) AI_Chase::handle_iqm_event {
|
||
switch(event_code) {
|
||
// case IQM_EVENT_MOVE_SPEED: // FRAME MOVEMENT DIST
|
||
// this.cur_anim_move_dist += stof(event_data);
|
||
// print("Handling event! move dist: ", ftos(this.cur_anim_move_dist), "\n");
|
||
// break;
|
||
case IQM_EVENT_ZOMBIE_FOOTSTEP:
|
||
play_zombie_footstep(); // TODO - Should this be done via reference to sound file?
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
};
|
||
|
||
// Stop what you're doing and play this animation immediately.
|
||
void(float anim_model_idx, float anim_framegroup) AI_Chase::play_anim {
|
||
this.cur_anim_model_idx = anim_model_idx;
|
||
this.cur_anim_framegroup = anim_framegroup;
|
||
this.cur_anim_start_time = time;
|
||
this.cur_anim_last_time = 0;
|
||
this.cur_anim_playback_speed = 1; // Reset value
|
||
this.cur_anim_duration = frameduration(this.cur_anim_model_idx, this.cur_anim_framegroup);
|
||
// Indicate that we need to broadcast the current anim frame info:
|
||
self.SendFlags |= 2; // Indicate that we should update client-side animations
|
||
};
|
||
|
||
// Queue up another animation to play when the current one finishes
|
||
void(float anim_model_idx, float anim_framegroup) AI_Chase::queue_anim {
|
||
this.next_anim_model_idx = anim_model_idx;
|
||
this.next_anim_framegroup = anim_framegroup;
|
||
this.next_anim_playback_speed = 1.0;
|
||
};
|
||
|
||
|
||
// The "logic tick" of the entity.
|
||
void() AI_Chase::think {
|
||
if(this.cur_anim_model_idx >= 0) {
|
||
// print("Think!, ", ftos(cur_anim_time));
|
||
float cur_anim_time = (time - this.cur_anim_start_time) * this.cur_anim_playback_speed;
|
||
// Update entity skeleton to have the latest pose
|
||
skelblend_t skel_blend_data;
|
||
skel_blend_data.firstbone = -1;
|
||
skel_blend_data.lastbone = -1;
|
||
skel_blend_data.scale[0] = 1;
|
||
skel_blend_data.sourcemodelindex = this.cur_anim_model_idx;
|
||
skel_blend_data.animation[0] = this.cur_anim_framegroup;
|
||
skel_blend_data.animationtime[0] = cur_anim_time;
|
||
skel_build_ptr(this.skeletonindex, 1, &skel_blend_data, sizeof(skel_blend_data));
|
||
|
||
// Process all animation frame events that elapsed since the last `think` function was called
|
||
float events_start_time = this.cur_anim_last_time % this.cur_anim_duration;
|
||
float events_end_time = cur_anim_time % this.cur_anim_duration;
|
||
|
||
|
||
// If the animation was looped, and start time is near the anim end, while end time is near the anim start
|
||
// Run the events elapsed at the end of the anim and start of the anim thus far
|
||
if(events_end_time < events_start_time) {
|
||
// Process elapsed events at the end of the animation
|
||
float segment_start = events_start_time;
|
||
float segment_end = this.cur_anim_duration;
|
||
processmodelevents(this.cur_anim_model_idx, this.cur_anim_framegroup, segment_start, segment_end, this.handle_iqm_event);
|
||
// Process elapsed events at the start of the animation
|
||
segment_start = 0;
|
||
segment_end = events_end_time;
|
||
processmodelevents(this.cur_anim_model_idx, this.cur_anim_framegroup, segment_start, segment_end, this.handle_iqm_event);
|
||
}
|
||
// Otherwise just process the elapsed events
|
||
else {
|
||
processmodelevents(this.cur_anim_model_idx, this.cur_anim_framegroup, events_start_time, events_end_time, this.handle_iqm_event);
|
||
}
|
||
|
||
// If we have a queued animation, check if this animation is over (or is very close to being over)
|
||
if(this.next_anim_model_idx >= 0 && (cur_anim_time + this.think_delta_time * this.cur_anim_playback_speed) >= this.cur_anim_duration) {
|
||
this.play_anim( this.next_anim_model_idx, this.next_anim_framegroup);
|
||
this.cur_anim_playback_speed = this.next_anim_playback_speed;
|
||
this.SendFlags |= 2; // Indicate that we should update client-side animations
|
||
// Reset queued anim fields
|
||
this.next_anim_playback_speed = 1.0;
|
||
this.next_anim_model_idx = -1;
|
||
this.next_anim_framegroup = 0;
|
||
}
|
||
}
|
||
|
||
this.nextthink = time + this.think_delta_time;
|
||
this.think_callback();
|
||
|
||
|
||
if(this.cur_anim_model_idx >= 0) {
|
||
// Recalculate cur_anim_time, in case another animation was triggered
|
||
this.cur_anim_last_time = (time - this.cur_anim_start_time) * this.cur_anim_playback_speed;
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// class AI_Zombie : AI_Chase {
|
||
// entity enemy; // If near, attack
|
||
|
||
// // Constructor. Called when calling `spawn(AI_Zombie);`
|
||
// // virtual void() AI_Zombie = {};
|
||
// // This should be called explicitly:
|
||
// virtual void(vector org) init;
|
||
|
||
// // virtual void () fg_die;
|
||
// // virtual void () fg_walk;
|
||
// // virtual void () fg_attack;
|
||
// // virtual void () fg_idle;
|
||
|
||
// // static void () setup_frames = {
|
||
// // // this.zombie_idle_frames = {1,2,3,4,5,6,7,8,9,10,11,12,13};
|
||
// // // this.cur_frames = zombie_idle_frames;
|
||
// // // FIXME - I can't figure out a way to assign an array...
|
||
// // // Even if I convert an animation to a struct, I can't do arbitrary-length arrays...
|
||
// // // I also can't fill them in in this convenient syntax... it's ridiculous.
|
||
// // };
|
||
// };
|
||
|
||
// AI_Zombie.zombie_idle_frames = {1,2,3,4,5,6,7,8,9,10,11,12,13};
|
||
|
||
|
||
|
||
|
||
void zombie_think_callback() {
|
||
AI_Chase zombie = (AI_Chase) self;
|
||
|
||
if(zombie.state == AI_STATE_PATHING) {
|
||
// "::classname" denotes the global ".string classname" field.
|
||
entity player_ent = find( world, classname, "player");
|
||
if(player_ent != world) {
|
||
zombie.path_target = player_ent;
|
||
zombie.enemy = player_ent;
|
||
}
|
||
|
||
// TEMP FIXME:
|
||
// zombie.limbs_state = 0;
|
||
// if(random() < 0.9) zombie.limbs_state |= ZOMBIE_LIMB_STATE_HEAD;
|
||
// if(random() < 0.9) zombie.limbs_state |= ZOMBIE_LIMB_STATE_ARM_L;
|
||
// if(random() < 0.9) zombie.limbs_state |= ZOMBIE_LIMB_STATE_ARM_R;
|
||
// if(random() < 0.9) zombie.limbs_state |= ZOMBIE_LIMB_STATE_LEG_L;
|
||
// if(random() < 0.9) zombie.limbs_state |= ZOMBIE_LIMB_STATE_LEG_R;
|
||
// zombie.SendFlags |= 8;
|
||
|
||
|
||
// ----------------------------------------------------
|
||
// ----------------------------------------------------
|
||
|
||
float walk_speed = zombie.calc_anim_movement_speed();
|
||
// print("Walk speed: ", ftos(walk_speed), "\n");
|
||
zombie.do_walk_to_goal(walk_speed);
|
||
}
|
||
else if(zombie.state == AI_STATE_TRAVERSING) {
|
||
// TODO - traversal logic needs to be redone...
|
||
zombie_traversal_logic();
|
||
|
||
// // FIXME - For now, teleport to end of traversal:
|
||
// float traversal_idx = zombie.cur_traversal_idx;
|
||
// vector end_pos = sv_navmesh_get_traversal_end_pos(traversal_idx);
|
||
// setorigin(zombie, end_pos);
|
||
// zombie.state = AI_STATE_PATHING;
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
void() test_new_ent = {
|
||
makevectors(self.v_angle);
|
||
|
||
// Precache all zombie animations:
|
||
if(zombie_anim_walk1_modelindex == 0) {
|
||
precache_model("models/ai/zombie_anim_idle.iqm");
|
||
precache_model("models/ai/zombie_anim_rise.iqm");
|
||
precache_model("models/ai/zombie_anim_walk1.iqm");
|
||
precache_model("models/ai/zombie_anim_walk2.iqm");
|
||
precache_model("models/ai/zombie_anim_walk3.iqm");
|
||
precache_model("models/ai/zombie_anim_jog1.iqm");
|
||
precache_model("models/ai/zombie_anim_run1.iqm");
|
||
precache_model("models/ai/zombie_anim_climbledge.iqm");
|
||
zombie_anim_idle_modelindex = getmodelindex("models/ai/zombie_anim_idle.iqm");
|
||
zombie_anim_rise_modelindex = getmodelindex("models/ai/zombie_anim_rise.iqm");
|
||
zombie_anim_walk1_modelindex = getmodelindex("models/ai/zombie_anim_walk1.iqm");
|
||
zombie_anim_walk2_modelindex = getmodelindex("models/ai/zombie_anim_walk2.iqm");
|
||
zombie_anim_walk3_modelindex = getmodelindex("models/ai/zombie_anim_walk3.iqm");
|
||
zombie_anim_jog1_modelindex = getmodelindex("models/ai/zombie_anim_jog1.iqm");
|
||
zombie_anim_run1_modelindex = getmodelindex("models/ai/zombie_anim_run1.iqm");
|
||
zombie_anim_climbledge_modelindex = getmodelindex("models/ai/zombie_anim_climbledge.iqm");
|
||
}
|
||
|
||
AI_Chase zombie = spawn(AI_Chase);
|
||
// zombie.init() // TODO - Which args to use here?
|
||
setorigin(zombie, self.origin + v_forward * 100);
|
||
|
||
// TODO - Set zombie skin...
|
||
if(random() < 0.5) {
|
||
zombie.skin = 0;
|
||
}
|
||
else {
|
||
zombie.skin = 1;
|
||
}
|
||
|
||
// ------------------------------------------------------------------------
|
||
zombie.dimension_solid |= HITBOX_DIM_ZOMBIES;
|
||
zombie.movetype = MOVETYPE_WALK;
|
||
zombie.solid = SOLID_SLIDEBOX;
|
||
setsize(zombie, VEC_HULL_MIN, VEC_HULL_MAX);
|
||
zombie.owner = world;
|
||
zombie.classname = "ai_zombie";
|
||
zombie.gravity = 1.0;
|
||
zombie.takedamage = DAMAGE_YES;
|
||
zombie.flags = zombie.flags | FL_PARTIALGROUND | FL_MONSTER;
|
||
zombie.health = 999999;
|
||
// ------------------------------------------------------------------------
|
||
zombie.limbs_state |= ZOMBIE_LIMB_STATE_HEAD;
|
||
zombie.limbs_state |= ZOMBIE_LIMB_STATE_ARM_L;
|
||
zombie.limbs_state |= ZOMBIE_LIMB_STATE_ARM_R;
|
||
zombie.limbs_state |= ZOMBIE_LIMB_STATE_LEG_L;
|
||
zombie.limbs_state |= ZOMBIE_LIMB_STATE_LEG_R;
|
||
// ------------------------------------------------------------------------
|
||
precache_model("models/ai/nazi_zombie.iqm");
|
||
setmodel(zombie, "models/ai/nazi_zombie.iqm");
|
||
skelblend_t skel_blend_data;
|
||
zombie.skeletonindex = skel_create(zombie.modelindex);
|
||
skel_blend_data.sourcemodelindex = zombie_anim_idle_modelindex;
|
||
skel_blend_data.firstbone = -1;
|
||
skel_blend_data.lastbone = -1;
|
||
skel_blend_data.scale[0] = 1;
|
||
skel_blend_data.animationtime[0] = 0;
|
||
skel_build_ptr(zombie.skeletonindex, 1, &skel_blend_data, sizeof(skel_blend_data));
|
||
// ------------------------------------------------------------------------
|
||
zombie.play_anim(zombie_anim_rise_modelindex, 0);
|
||
zombie.queue_anim(zombie_anim_walk1_modelindex, 0);
|
||
|
||
// zombie.play_anim(zombie_anim_walk2_modelindex, 0);
|
||
// zombie.play_anim(zombie_anim_walk3_modelindex, 0);
|
||
// zombie.play_anim(zombie_anim_jog1_modelindex, 0);
|
||
// zombie.play_anim(zombie_anim_run1_modelindex, 0);
|
||
// zombie.cur_anim_playback_speed = 1.0;
|
||
// zombie.cur_anim_playback_speed = 0.8; // Good for run-anim
|
||
// zombie.cur_anim_playback_speed = 0.8 + random() * 0.4;
|
||
// zombie.cur_anim_playback_speed = 0.2 + random() * 0.8;
|
||
// zombie.cur_anim_playback_speed = 0.2 + random() * 2.0;
|
||
zombie.state = AI_STATE_PATHING;
|
||
zombie.nextthink = time + zombie.think_delta_time;
|
||
zombie.think_callback = zombie_think_callback;
|
||
|
||
|
||
// -------------–-------------–-------------–-------------–-------------–--
|
||
// MDL-based
|
||
// -------------–-------------–-------------–-------------–-------------–--
|
||
// zombie.init(self.origin + v_forward * 100);
|
||
// zombie.frame = get_anim_frame_zombie_rise(0);
|
||
// zombie.play_anim(get_anim_frame_zombie_rise, get_anim_length_zombie_rise(), ANIM_STOP_TYPE_NEXT_ANIM);
|
||
// zombie.queue_anim(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_LOOP);
|
||
// zombie.think_callback = zombie_think_callback;
|
||
// zombie.state = AI_STATE_PATHING;
|
||
// // zombie.play_anim(get_anim_frame_zombie_rise, get_anim_length_zombie_rise(), ANIM_STOP_TYPE_LOOP);
|
||
// -------------–-------------–-------------–-------------–-------------–--
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// DEFINE_ANIM(zombie_idle, 1,2,3,4,5,6,7,8,9,10,11,12,13); // Defines: get_anim_frame_zombie_idle, get_anim_length_zombie_idle
|
||
// DEFINE_ANIM(zombie_rise, 14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37); // Defines: get_anim_frame_zombie_rise, get_anim_length_zombie_rise
|
||
// // DEFINE_ANIM(zombie_rise, 114,115,116,117,118,119,175,176,177,178,179,180,181); // Defines: get_anim_frame_zombie_rise, get_anim_length_zombie_rise
|
||
// // DEFINE_ANIM(zombie_rise, 161,162,163,164,165,166,171,172,173,174,175,176,177,178,179,180,181);
|
||
// DEFINE_ANIM(zombie_walk1, 38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53); // Defines: get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1
|
||
// DEFINE_ANIM(zombie_walk2, 54,55,56,57,58,59,60,61,62,63,64,65,66,67); // Defines: get_anim_frame_zombie_walk2, get_anim_length_zombie_walk2
|
||
// DEFINE_ANIM(zombie_walk3, 68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83); // Defines: get_anim_frame_zombie_walk3, get_anim_length_zombie_walk3
|
||
// DEFINE_ANIM(zombie_jog1, 84,85,86,87,88,89,90,91,92); // Defines: get_anim_frame_zombie_jog1, get_anim_length_zombie_jog1
|
||
// DEFINE_ANIM(zombie_run1, 93,94,95,96,97,98,99,100,101,102); // Defines: get_anim_frame_zombie_run1, get_anim_length_zombie_run1
|
||
// DEFINE_ANIM(zombie_attack1, 103,104,105,106,107); // Defines: get_anim_frame_zombie_attack1, get_anim_length_zombie_attack1
|
||
// DEFINE_ANIM(zombie_attack2, 108,109,110,111,112,113); // Defines: get_anim_frame_zombie_attack2, get_anim_length_zombie_attack2
|
||
// DEFINE_ANIM(zombie_window_rip_board1, 181,182,183,184,185,186,187,188,189,190,191); // Defines: get_anim_frame_zombie_window_rip_board1, get_anim_length_zombie_window_rip_board1
|
||
// DEFINE_ANIM(zombie_window_rip_board2, 191,192,193,194,195,196,197,198,199,200,201); // Defines: get_anim_frame_zombie_window_rip_board2, get_anim_length_zombie_window_rip_board2
|
||
// DEFINE_ANIM(zombie_window_attack, 201,202,203,204,205,206,207,208,209,210); // Defines: get_anim_frame_zombie_window_attack, get_anim_length_zombie_window_attack
|
||
// DEFINE_ANIM(zombie_window_hop, 114,115,116,117,118,119,120,121,122,123); // Defines: get_anim_frame_zombie_window_hop, get_anim_length_zombie_window_hop
|
||
// DEFINE_ANIM(zombie_die1, 124,125,126,127,128,129,130,131,132,133,134); // Defines: get_anim_frame_zombie_die1, get_anim_length_zombie_die1
|
||
// DEFINE_ANIM(zombie_die2, 135,136,137,138,139); // Defines: get_anim_frame_zombie_die2, get_anim_length_zombie_die2
|
||
// DEFINE_ANIM(zombie_die3, 140,141,142,143,144,145,146,147,148,149); // Defines: get_anim_frame_zombie_die3, get_anim_length_zombie_die3
|
||
// DEFINE_ANIM(zombie_die_wunder, 211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227); // Defines: get_anim_frame_zombie_die_wunder, get_anim_length_zombie_die_wunder
|
||
// DEFINE_ANIM(zombie_fall, 150,151,152,153); // Defines: get_anim_frame_zombie_fall, get_anim_length_zombie_fall
|
||
// DEFINE_ANIM(zombie_land, 154,155,156,157,158,159,159); // Defines: get_anim_frame_zombie_land, get_anim_length_zombie_land
|
||
// DEFINE_ANIM(zombie_jump, 160,161,162,163,164,165,166); // Defines: get_anim_frame_zombie_jump, get_anim_length_zombie_jump
|
||
// DEFINE_ANIM(zombie_climb, 167,168,169,170,171,172,173,174,175,176,177,178,179,180); // Defines: get_anim_frame_zombie_climb, get_anim_length_zombie_climb
|
||
// DEFINE_ANIM(zombie_jump_low, 163,164,165); // Defines: get_anim_frame_zombie_jump_low, get_anim_length_zombie_jump_low
|
||
// DEFINE_ANIM(zombie_climb_low, 170,171,172,173,174,175,176,177,178,179,180); // Defines: get_anim_frame_zombie_climb_low, get_anim_length_zombie_climb_low
|
||
// DEFINE_ANIM(zombie_fall_loop, 151,152,153,152); // Defines: get_anim_frame_zombie_fall_loop, get_anim_length_zombie_fall_loop
|
||
// DEFINE_ANIM(zombie_leap, 228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247); // Defines: get_anim_frame_zombie_leap, get_anim_length_zombie_leap
|
||
// DEFINE_ANIM(zombie_leap_jump, 228,229,230,231,232,233,234,235,236,237,238,239,240,241); // Defines: get_anim_frame_zombie_leap_jump, get_anim_length_zombie_leap_jump
|
||
// DEFINE_ANIM(zombie_leap_land, 242,243,244,245,246,247); // Defines: get_anim_frame_zombie_leap_land, get_anim_length_zombie_leap_land
|
||
// DEFINE_ANIM(zombie_fence_jump, 228,229,230,231,232,233,234,235,248,249,250,116,117,118,119,120,121,251,152,153); // Defines: get_anim_frame_zombie_fence, get_anim_length_zombie_fence
|
||
// DEFINE_ANIM(zombie_fence_land, 154,155,156,157,158,159); // Defines: get_anim_frame_zombie_fence_land, get_anim_length_zombie_fence_land
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// void(vector org) AI_Zombie::init = {
|
||
// setmodel(this, "models/ai/zfull.mdl");
|
||
// // ---------------------------------
|
||
// // Usual zombie setup stuff
|
||
// // ---------------------------------
|
||
// this.solid = SOLID_CORPSE;
|
||
// #ifdef PC
|
||
// this.dimension_solid = HITBOX_DIM_ZOMBIES;
|
||
// #endif // PC
|
||
|
||
// // this.movetype = MOVETYPE_STEP;
|
||
// this.movetype = MOVETYPE_WALK;
|
||
// setsize (this, '-8 -8 -32', '8 8 30');
|
||
// this.origin = org;
|
||
// setorigin(this, this.origin);
|
||
// this.classname = "ai_zombie";
|
||
// this.gravity = 1.0;
|
||
// this.takedamage = DAMAGE_YES;
|
||
// this.flags = this.flags | FL_PARTIALGROUND | FL_MONSTER;
|
||
// this.health = 999999;
|
||
// // SetUpHitBoxes(self);
|
||
// // ---------------------------------
|
||
// this.yaw_speed = 20;
|
||
// this.cur_anim_frametime = 0.1;
|
||
// // ---------------------------------
|
||
// // this.think_delta_time = 0.1; // 10x per second
|
||
// // this.nextthink = time + this.think_delta_time;
|
||
// this.nextthink = time + this.cur_anim_frametime;
|
||
// // this.cur_fg_start_time = -1;
|
||
// }
|
||
|
||
|
||
// void() AI_Zombie::think = {
|
||
// print("we do be thinkin!\n");
|
||
// // TODO - how to call superclass think?
|
||
// }
|
||
|
||
|
||
|
||
|
||
// void () AI_Zombie::traverse = {
|
||
// this.traversal_idx; // Can't use pointers... but can use index!
|
||
// this.traversal_state;
|
||
// this.traversal_substate;
|
||
|
||
// // TODO - Check traversal type, check if this class knows how to perform it.
|
||
|
||
// float traversal_idx = 0;
|
||
// // TODO - Once we know traversal type, the AI_Zombie class will know which think_callback to use
|
||
// // TODO - Set up a test scene on the map, set up a test traversal, have dummy zombie play the test traversal
|
||
|
||
|
||
// this.traversal_callback = zombie_traversal_hop_barricade_think;
|
||
// void (entity ent) zombie_hop_barricade_traveral_think = {
|
||
// AI_Zombie zombie_ent = (AI_Zombie) ent;
|
||
// // TODO - need to initialize traversal state and traversal substate
|
||
// // Initialize state
|
||
// if(ent.traversal_state < 0) {
|
||
// ent.traversal_state = 0;
|
||
// ent.traversal_substate = 0;
|
||
// }
|
||
|
||
// // State 0 -- Start hopping, start playing anim, lerp towards
|
||
// // TODO - How to adjust animation speed scale?
|
||
// // TODO - How best to control animation from here?
|
||
// if(ent.traversal_state == 0) {
|
||
// // TODO - Somehow start playing animation?
|
||
|
||
// }
|
||
// // ...
|
||
// // On final state, clear traversal
|
||
// else {
|
||
// ent.traversal_state = -1;
|
||
// // TODO - Revert to zombie original behavior, fg_walk?
|
||
// zombie_ent.fg_walk();
|
||
// }
|
||
// };
|
||
|
||
|
||
// // TODO - Implement think logic for hop barricade traversal
|
||
// // Each traversal has a different think function implementation...
|
||
// // When I call zombie.traverse(), I need some way of telling the zombie which barricade to traverse
|
||
// // Maybe traverse has a traversal_idx as its argument?
|
||
// // zombie_ent.traverse(traversal_idx);
|
||
|
||
// // That tells the zombie to perform this traversal
|
||
// // And the traversal think function will be stored in the struct
|
||
|