quakec/source/server/ai/chase_ai.qc

1122 lines
50 KiB
C++
Raw Permalink Normal View History

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.
};
2023-02-05 05:52:50 +00:00
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,
};
2023-02-05 05:52:50 +00:00
#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
2023-02-05 05:52:50 +00:00
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;
};
2023-02-05 05:52:50 +00:00
// 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
2023-02-05 05:52:50 +00:00
// TODO - Set model? Create skeleton?
// Should this be done in constructor?
this.SendEntity = this.send_entity_func;
this.cur_anim_playback_speed = 1;
};
2023-02-05 05:52:50 +00:00
// 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;
};
2023-02-05 05:52:50 +00:00
// 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
2023-02-05 05:52:50 +00:00
// // Constructor. Called when calling `spawn(AI_Zombie);`
// // virtual void() AI_Zombie = {};
// // This should be called explicitly:
// virtual void(vector org) init;
2023-02-05 05:52:50 +00:00
// // virtual void () fg_die;
// // virtual void () fg_walk;
// // virtual void () fg_attack;
// // virtual void () fg_idle;
2023-02-05 05:52:50 +00:00
// // 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.
// // };
// };
2023-02-05 05:52:50 +00:00
// AI_Zombie.zombie_idle_frames = {1,2,3,4,5,6,7,8,9,10,11,12,13};
2023-02-05 05:52:50 +00:00
void zombie_think_callback() {
AI_Chase zombie = (AI_Chase) self;
2023-02-05 05:52:50 +00:00
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;
}
2023-02-05 05:52:50 +00:00
// 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;
2023-02-05 05:52:50 +00:00
// ----------------------------------------------------
// ----------------------------------------------------
2023-02-05 05:52:50 +00:00
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();
2023-02-05 05:52:50 +00:00
// // 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;
}
};
2023-02-05 05:52:50 +00:00
void() test_new_ent = {
makevectors(self.v_angle);
2023-02-05 05:52:50 +00:00
// 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");
}
2023-02-05 05:52:50 +00:00
AI_Chase zombie = spawn(AI_Chase);
// zombie.init() // TODO - Which args to use here?
setorigin(zombie, self.origin + v_forward * 100);
2023-02-05 05:52:50 +00:00
// 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);
// -------------–-------------–-------------–-------------–-------------–--
};
2023-02-05 05:52:50 +00:00
// 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
2023-02-05 05:52:50 +00:00
// 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
2023-02-05 05:52:50 +00:00
// // 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;
2023-02-05 05:52:50 +00:00
// }
// void() AI_Zombie::think = {
// print("we do be thinkin!\n");
// // TODO - how to call superclass think?
// }
2023-02-05 05:52:50 +00:00
// void () AI_Zombie::traverse = {
// this.traversal_idx; // Can't use pointers... but can use index!
// this.traversal_state;
// this.traversal_substate;
2023-02-05 05:52:50 +00:00
// // TODO - Check traversal type, check if this class knows how to perform it.
2023-02-05 05:52:50 +00:00
// 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
2023-02-05 05:52:50 +00:00
// 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?
2023-02-05 05:52:50 +00:00
// }
// // ...
// // On final state, clear traversal
// else {
// ent.traversal_state = -1;
// // TODO - Revert to zombie original behavior, fg_walk?
// zombie_ent.fg_walk();
// }
// };
2023-02-05 05:52:50 +00:00
// // 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
2023-02-05 05:52:50 +00:00