CLIENT: Add FTE skel defs

SERVER: Add test IQM ledge-climb traversal
This commit is contained in:
blubs 2023-08-29 22:43:54 -07:00
parent e3dcb2aa13
commit 11004c96b5
3 changed files with 688 additions and 629 deletions

View File

@ -1179,10 +1179,28 @@ float(float modlindex, optional float useabstransforms) skel_create = #263; /* P
Allocates a new uninitiaised skeletal object, with enough bone info to animate the given model.
eg: self.skeletonobject = skel_create(self.modelindex); */
typedef struct {
int sourcemodelindex; /*frame data will be imported from this model, bones must be compatible*/
int reserved;
int firstbone;
int lastbone;
float prescale; /*0 destroys existing data, 1 retains it*/
float scale[4]; /*you'll need to do lerpfrac manually*/
int animation[4];
float animationtime[4];
/*halflife models*/
float subblend[2];
float controllers[5];
} skelblend_t;
float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone, optional float addfrac) skel_build = #264; /* Part of FTE_CSQC_SKELETONOBJECTS
Animation data (according to the entity's frame info) is pulled from the specified model and blended into the specified skeletal object.
If retainfrac is set to 0 on the first call and 1 on the others, you can blend multiple animations together according to the addfrac value. The final weight should be 1. Other values will result in scaling and/or other weirdness. You can use firstbone and lastbone to update only part of the skeletal object, to allow legs to animate separately from torso, use 0 for both arguments to specify all, as bones are 1-based. */
float(float skel, int numblends, skelblend_t *weights, int structsize) skel_build_ptr = #0:skel_build_ptr; /*
Like skel_build, but slightly simpler. */
float(float skel) skel_get_numbones = #265; /* Part of FTE_CSQC_SKELETONOBJECTS
Retrives the number of bones in the model. The valid range is 1<=bone<=numbones. */
@ -1627,6 +1645,10 @@ void(entity e, string skinfilename, optional string skindata) setcustomskin = #3
The texture is determined to be sufficient to hold the first named image, additional images can be named as extra tokens on the same line.
Use a + at the end of the line to continue reading image tokens from the next line also, the named shader must use 'map $diffuse' to read the composed texture (compatible with the defaultskin shader). */
float(string skinfilename, optional string skindata) loadcustomskin = #377;
void(entity e, float skinobj) applycustomskin = #378;
void(float skinobj) releasecustomskin = #379;
__variant*(int size) memalloc = #384; /* Part of FTE_MEMALLOC
Allocate an arbitary block of memory */

View File

@ -509,10 +509,6 @@ float() zombie_predraw = {
// Due to QC's limit of 8-args per vararg func, split this up into
// a few different sprintf calls.
string skin_str = "";
skin_str = sprintf("%sgeomset %d %d\n", skin_str, ZOMBIE_IQM_GEOMSET_BODY, )
string skin_str = "";
// Add in the geomset defs
skin_str = sprintf("%sgeomset %d %d\n", skin_str, ZOMBIE_IQM_GEOMSET_BODY, 0);
@ -522,7 +518,7 @@ float() zombie_predraw = {
skin_str = sprintf("%sgeomset %d %d\n", skin_str, ZOMBIE_IQM_GEOMSET_LEG_L, self.limbs_state & ZOMBIE_LIMB_STATE_LEG_L == 0);
skin_str = sprintf("%sgeomset %d %d\n", skin_str, ZOMBIE_IQM_GEOMSET_LEG_R, self.limbs_state & ZOMBIE_LIMB_STATE_LEG_R == 0);
skin_str = sprintf("%sgeomset %d %d\n", skin_str, ZOMBIE_IQM_GEOMSET_ARM_R, self.limbs_state & ZOMBIE_LIMB_STATE_ARM_R == 0);
// Add in the shader defs
// Add in the per-mesh shader defs
skin_str = sprintf("%sreplace ZombieBody %S\n", skin_str, shader_name);
skin_str = sprintf("%sreplace ZombieHead %S\n", skin_str, shader_name);
skin_str = sprintf("%sreplace ZombieArmL %S\n", skin_str, shader_name);

View File

@ -13,6 +13,8 @@ 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
@ -59,7 +61,6 @@ class AI_Chase : entity {
float substate;
// -----------------------------
// -----------------------------
// Traversal state vars
// -----------------------------
@ -69,12 +70,10 @@ class AI_Chase : entity {
// 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;
// // 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
// ------------------------------
@ -85,27 +84,21 @@ class AI_Chase : entity {
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;
// Constructor. Called when calling `spawn(AI_Chase);`
// virtual void() AI_Chase;
// void() AI_Chase::think = {
// ------------------------------------------------------------------------
// Animation movement integration fields / code
// ------------------------------------------------------------------------
@ -118,7 +111,357 @@ class AI_Chase : entity {
// 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 = {
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;
}
@ -137,10 +480,9 @@ class AI_Chase : entity {
};
// 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 = {
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_duration = frameduration(this.cur_anim_model_idx, this.cur_anim_framegroup);
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;
@ -158,8 +500,8 @@ class AI_Chase : entity {
if(anim_looped == false) {
break;
}
last_event_start_time += anim_duration;
last_event_end_time += anim_duration;
last_event_start_time += this.cur_anim_duration;
last_event_end_time += this.cur_anim_duration;
}
// Store current event as last event
@ -168,15 +510,14 @@ class AI_Chase : entity {
}
};
virtual float() calc_anim_movement_speed {
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_duration = frameduration(this.cur_anim_model_idx, this.cur_anim_framegroup);
// float anim_last_frametime = this.cur_anim_last_time % anim_duration;
// float anim_cur_frametime = ((time - this.cur_anim_start_time) * this.cur_anim_playback_speed) % anim_duration;
// 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);
@ -190,17 +531,17 @@ class AI_Chase : entity {
// 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 % anim_duration; // Wrap `last_frametime`
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 = anim_duration + 1;
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(anim_duration, IQM_EVENT_MOVE_SPEED, "0");
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;
@ -215,7 +556,7 @@ class AI_Chase : entity {
virtual void (float move_speed) do_walk_to_goal = {
void (float move_speed) AI_Chase::do_walk_to_goal {
if(move_speed == 0) {
return;
}
@ -248,10 +589,6 @@ class AI_Chase : entity {
this.state = AI_STATE_TRAVERSING;
this.substate = 0;
this.cur_traversal_idx = res->point_path_traversals[this.pathfind_cur_point_idx];
print("Setting state to traversing.\n");
vector traversal_end_pos = sv_navmesh_get_traversal_end_pos(res->point_path_traversals[this.pathfind_cur_point_idx]);
print("Setting origin to end position: ", vtos(traversal_end_pos), "\n");
this.origin = traversal_end_pos;
}
this.pathfind_cur_point_idx += 1;
goal_pos = res->point_path_points[this.pathfind_cur_point_idx];
@ -289,7 +626,7 @@ class AI_Chase : entity {
};
virtual float(entity to, float sendflags) send_entity_func = {
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);
@ -337,7 +674,7 @@ class AI_Chase : entity {
// Constructor. Called when calling `spawn(AI_Chase);`
virtual void() AI_Chase = {
void() AI_Chase::AI_Chase {
this.path_target = world;
// this.path_pos = world.origin;
this.cur_anim_model_idx = -1;
@ -354,7 +691,7 @@ class AI_Chase : entity {
// // TODO - Update limb states, mark as needing networking
// }
virtual void(float timestamp, int event_code, string event_data) handle_iqm_event = {
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);
@ -369,18 +706,19 @@ class AI_Chase : entity {
};
// Stop what you're doing and play this animation immediately.
virtual void(float anim_model_idx, float anim_framegroup) play_anim {
void(float anim_model_idx, float anim_framegroup) AI_Chase::play_anim {
this.cur_anim_model_idx = anim_model_idx;
this.cur_anim_framegroup = 0;
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
virtual void(float anim_model_idx, float anim_framegroup) queue_anim {
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;
@ -388,7 +726,7 @@ class AI_Chase : entity {
// The "logic tick" of the entity.
virtual void() think = {
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;
@ -402,12 +740,9 @@ class AI_Chase : entity {
skel_blend_data.animationtime[0] = cur_anim_time;
skel_build_ptr(this.skeletonindex, 1, &skel_blend_data, sizeof(skel_blend_data));
// Zero out for this frame, we're going to accumulate total distance to move here via `processmodelevents`
// this.cur_anim_move_dist = 0;
// Process all animation frame events that elapsed since the last `think` function was called
float anim_duration = frameduration(this.cur_anim_model_idx, this.cur_anim_framegroup);
float events_start_time = this.cur_anim_last_time % anim_duration;
float events_end_time = cur_anim_time % anim_duration;
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
@ -415,7 +750,7 @@ class AI_Chase : entity {
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 = anim_duration;
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;
@ -428,7 +763,7 @@ class AI_Chase : entity {
}
// 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) >= anim_duration) {
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
@ -453,13 +788,6 @@ class AI_Chase : entity {
// virtual void () fg_die = {};
// virtual void () fg_walk = {};
// virtual void () fg_attack = {};
// virtual void () fg_idle = {};
// virtual void (float dist) do_walk_to_goal;
};
// class AI_Zombie : AI_Chase {
// entity enemy; // If near, attack
@ -518,15 +846,13 @@ void zombie_think_callback() {
}
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;
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;
}
};
@ -546,6 +872,7 @@ void() test_new_ent = {
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");
@ -553,6 +880,7 @@ void() test_new_ent = {
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);
@ -589,8 +917,6 @@ void() test_new_ent = {
setmodel(zombie, "models/ai/nazi_zombie.iqm");
skelblend_t skel_blend_data;
zombie.skeletonindex = skel_create(zombie.modelindex);
// TODO - Do an initial skeleton build with the idle animation...
skel_blend_data.sourcemodelindex = zombie.skeletonindex; // FIXME - replace this with idle animation...
skel_blend_data.sourcemodelindex = zombie_anim_idle_modelindex;
skel_blend_data.firstbone = -1;
skel_blend_data.lastbone = -1;
@ -691,291 +1017,6 @@ void() test_new_ent = {
// void zombie_traversal_logic() {
// AI_Zombie zombie_ent = (AI_Zombie) 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 = (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 = 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;
// 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;
// if(zombie_ent.substate == 0) {
// zombie_ent.movetype = MOVETYPE_STEP;
// zombie_ent.play_anim(get_anim_frame_zombie_jump, get_anim_length_zombie_jump(), ANIM_STOP_TYPE_STOP);
// // zombie_ent.cur_anim_frametime = 0.08;
// anim_time = (zombie_ent.cur_anim_length - 1) * zombie_ent.cur_anim_frametime;
// zombie_ent.cur_traversal_end_time = time + anim_time - (1 * zombie_ent.cur_anim_frametime);
// // Stash anim stop-time in this variable so we can tell when to proceed: (minus three frames)
// 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;
// // FIXME - Some traversals will have a different way of getting speed...
// }
// else if(zombie_ent.substate == 1) {
// if(zombie_ent.cur_traversal_end_time <= time) {
// // Zombie jumping should be real fast,
// traversal_time = 0.12; // 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) {
// 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 = 3;
// zombie_ent.play_anim(get_anim_frame_zombie_climb, get_anim_length_zombie_climb(), ANIM_STOP_TYPE_STOP);
// anim_time = (zombie_ent.cur_anim_length - 1) * zombie_ent.cur_anim_frametime;
// zombie_ent.cur_traversal_start_time = time;
// zombie_ent.cur_traversal_end_time = time + anim_time;
// }
// }
// 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);
// zombie_ent.origin = 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(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_LOOP);
// }
// }
// }
// return;
// }
// 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);
// // }
// }