Fixes funnel algorithm for various edge cases

Adds server-side navmesh v0.1.0 loading
Refactors AI_Chase code
This commit is contained in:
blubs 2023-08-02 01:27:40 -07:00
parent 66bb8a8702
commit cf0ec43631
8 changed files with 1170 additions and 339 deletions

View file

@ -1479,15 +1479,15 @@ void cl_navmesh_calc_connected_polies() {
cl_navmesh_polies[j].connected_polies_count++;
//=======================================================================================
print("Poly ");
print(ftos(i));
print(" is connected to ");
print(ftos(j));
print(" and poly ");
print(ftos(j));
print(" is connected to ");
print(ftos(i));
print(".\n");
// print("Poly ");
// print(ftos(i));
// print(" is connected to ");
// print(ftos(j));
// print(" and poly ");
// print(ftos(j));
// print(" is connected to ");
// print(ftos(i));
// print(".\n");
}
}
}
@ -1616,7 +1616,7 @@ void cl_navmesh_calc_polies_centers()
//Saves the current cl_navmesh to a file
void cl_navmesh_editor_save_navmesh() {
int v_major = 0;
int v_minor = 0;
int v_minor = 1;
int v_patch = 0;
float file;
string h;
@ -1878,10 +1878,15 @@ void cl_navmesh_editor_load_navmesh() {
fgets(file);//1
fgets(file);//2
fgets(file);//3
// Don't care about connected traversals
float n_traversals = stof(fgets(file));
for(float j = 0; j < n_traversals; j++) {
fgets(file); // discard the j-th traversal index
// If navmesh file version 0.0.0, no traversals
// FIXME - Need a better way to do this..
if(strcmp(nav_file_version, "0.0.0") != 0) {
// Don't care about connected traversals
float n_traversals = stof(fgets(file));
for(float j = 0; j < n_traversals; j++) {
fgets(file); // discard the j-th traversal index
}
}
// Load doortarget field
@ -1899,31 +1904,33 @@ void cl_navmesh_editor_load_navmesh() {
// -----------------------------------------------------------------------
// Load Traversals
// -----------------------------------------------------------------------
// TODO - Increment navmesh version count, if 0.0.0, load file without traversals
//Next line contains the number of traverals
cl_navmesh_traversal_count = stof(fgets(file));
//The next lines are each traversal
for(float i = 0; i < cl_navmesh_traversal_count; i++) {
line = fgets(file);
temp = stov(line);
cl_navmesh_traversals[i].start_pos.x = temp.x;
cl_navmesh_traversals[i].start_pos.y = temp.y;
cl_navmesh_traversals[i].start_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
cl_navmesh_traversals[i].midpoint_pos.x = temp.x;
cl_navmesh_traversals[i].midpoint_pos.y = temp.y;
cl_navmesh_traversals[i].midpoint_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
cl_navmesh_traversals[i].end_pos.x = temp.x;
cl_navmesh_traversals[i].end_pos.y = temp.y;
cl_navmesh_traversals[i].end_pos.z = temp.z;
cl_navmesh_traversals[i].angle = stof(fgets(file));
cl_navmesh_traversals[i].use_midpoint = stof(fgets(file));
// Don't care about traversal end polygon index
fgets(file);
// If navmesh file version 0.0.0, no traversals
if(strcmp(nav_file_version, "0.0.0") != 0) {
// Next line contains the number of traverals
cl_navmesh_traversal_count = stof(fgets(file));
// Next lines are each traversal
for(float i = 0; i < cl_navmesh_traversal_count; i++) {
line = fgets(file);
temp = stov(line);
cl_navmesh_traversals[i].start_pos.x = temp.x;
cl_navmesh_traversals[i].start_pos.y = temp.y;
cl_navmesh_traversals[i].start_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
cl_navmesh_traversals[i].midpoint_pos.x = temp.x;
cl_navmesh_traversals[i].midpoint_pos.y = temp.y;
cl_navmesh_traversals[i].midpoint_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
cl_navmesh_traversals[i].end_pos.x = temp.x;
cl_navmesh_traversals[i].end_pos.y = temp.y;
cl_navmesh_traversals[i].end_pos.z = temp.z;
cl_navmesh_traversals[i].angle = stof(fgets(file));
cl_navmesh_traversals[i].use_midpoint = stof(fgets(file));
// Don't care about traversal end polygon index
fgets(file);
}
}
// -----------------------------------------------------------------------
@ -2397,6 +2404,8 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
vector cur_funnel_corner = start_point;
vector cur_funnel_left = cl_test_pathfind_result->portals_left_pos[0];
vector cur_funnel_right = cl_test_pathfind_result->portals_right_pos[0];
float cur_funnel_left_portal_idx = 0;
float cur_funnel_right_portal_idx = 0;
float next_portal_left_in_funnel;
float next_portal_right_in_funnel;
@ -2415,51 +2424,30 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
// TODO < -1 outside of funnel to the left
// TODO > 1 outside of funnel to the right
float cur_portal_left_in_funnel = pathfind_point_in_funnel( cur_portal_left_pos, cur_funnel_corner, cur_funnel_left, cur_funnel_right);
float cur_portal_right_in_funnel = pathfind_point_in_funnel(cur_portal_right_pos, cur_funnel_corner, cur_funnel_left, cur_funnel_right);
print("Current portal: ");
print(ftos(i));
print(" (l: ");
print(ftos(cur_portal_left_pos.x));
print(",");
print(ftos(cur_portal_left_pos.y));
print(", r: ");
print(ftos(cur_portal_right_pos.x));
print(",");
print(ftos(cur_portal_right_pos.y));
print(", t: ");
print(ftos(cur_portal_traversal));
print(") -");
print(" In funnel? (");
print(ftos(cur_portal_left_in_funnel));
print(",");
print(ftos(cur_portal_right_in_funnel));
print(") - ");
print("Funnel: (");
print(ftos(cur_funnel_left.x));
print(",");
print(ftos(cur_funnel_left.y));
print(" -> ");
print(ftos(cur_funnel_corner.x));
print(",");
print(ftos(cur_funnel_corner.y));
print(" -> ");
print(ftos(cur_funnel_right.x));
print(",");
print(ftos(cur_funnel_right.y));
print(")\n");
print("{'portal_idx': ", ftos(i), ", 'portal': ");
print(" [(", ftos(cur_portal_left_pos.x), ",", ftos(cur_portal_left_pos.y), "), (", ftos(cur_portal_right_pos.x), ",", ftos(cur_portal_right_pos.y));
print(")], 'traversal': ", ftos(cur_portal_traversal), ", ");
print("'funnel_result': (", ftos(cur_portal_left_in_funnel), ",", ftos(cur_portal_right_in_funnel), "), ");
print("'funnel': [");
print("(", ftos(cur_funnel_left.x), ",", ftos(cur_funnel_left.y), "), ");
print("(", ftos(cur_funnel_corner.x), ",", ftos(cur_funnel_corner.y), "), ");
print("(", ftos(cur_funnel_right.x), ",", ftos(cur_funnel_right.y), ")");
print("]},\n");
// print(vtos(cur_funnel_left));
// If cur portal left point is in cur funnel, narrow the cur funnel
if(cur_portal_left_in_funnel >= -1 && cur_portal_left_in_funnel <= 1) {
cur_funnel_left = cur_portal_left_pos;
cur_funnel_left_portal_idx = i;
}
// If cur portal right point is in cur funnel, narrow the cur funnel
if(cur_portal_right_in_funnel >= -1 && cur_portal_right_in_funnel <= 1) {
cur_funnel_right = cur_portal_right_pos;
cur_funnel_right_portal_idx = i;
}
// If cur portal is completely outside (to left of) current funnel
@ -2469,12 +2457,18 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
cl_test_pathfind_result->point_path_traversals[cl_test_pathfind_result->point_path_length] = -1;
cl_test_pathfind_result->point_path_length++;
// Update funnel to start at the current funnel left endpoint, pointing to the next portal in the list
// Set next funnel to start at cur funnel left point
cur_funnel_corner = cur_funnel_left;
// Set next funnel left point to next portal left
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[i+1];
// Set next funnel right point to next portal right
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[i+1];
// Advance the funnel's left point to the next portal
cur_funnel_left_portal_idx += 1;
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[cur_funnel_left_portal_idx];
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[cur_funnel_left_portal_idx];
cur_funnel_right_portal_idx = cur_funnel_left_portal_idx;
// Restart funnel algorithm from the funnel's endpoint
i = cur_funnel_left_portal_idx - 1;
continue;
}
// If cur portal is completely outside (to right of) current funnel
else if(cur_portal_left_in_funnel > 1 && cur_portal_right_in_funnel > 1) {
@ -2483,12 +2477,18 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
cl_test_pathfind_result->point_path_traversals[cl_test_pathfind_result->point_path_length] = -1;
cl_test_pathfind_result->point_path_length++;
// Update funnel to start at the current funnel right endpoint, pointing to the next portal in the list
// Set next funnel to start at cur funnel right point
cur_funnel_corner = cur_funnel_right;
// Set next funnel left point to next portal left
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[i+1];
// Set next funnel right point to next portal right
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[i+1];
// Advance the funnel's right point to the next portal
cur_funnel_right_portal_idx += 1;
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[cur_funnel_right_portal_idx];
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[cur_funnel_right_portal_idx];
cur_funnel_left_portal_idx = cur_funnel_right_portal_idx;
// Restart funnel algorithm from the funnel's endpoint
i = cur_funnel_right_portal_idx - 1;
continue;
}
// If cur portal is a traversal
@ -2510,6 +2510,8 @@ void cl_pathfind_smooth_path(vector start_point, vector goal_point) {
cur_funnel_corner = end_pos;
cur_funnel_left = cl_test_pathfind_result->portals_left_pos[i+1];
cur_funnel_right = cl_test_pathfind_result->portals_right_pos[i+1];
cur_funnel_left_portal_idx = i+1;
cur_funnel_right_portal_idx = i+1;
}
}

View file

@ -1,36 +1,9 @@
var struct framegroup {
float start_frame;
float end_frame;
float duration;
// void(entity) start_callback; // Called at the first frame of the range
void(entity) frame_callback; // Called at every frame of the range (after start / finish callbacks)
// void(entity) end_callback; // Called at the last frame of the range
// FIXME - Increasing the size of this framegroup by one more function pointer causes chaos...
// I assume I run out of space to pass framegroups along as arguments at any point...
// It starts to write into weird memory locations, like overriding self / world... things just break down.
// TODO - How can I refactor this to work?
void(entity) think_callback; // Called at each think invocation
// -- Maybe I can get rid of start / end callbacks? idk
// I should think about how I actually plan on using these...
// TODO - Get rid of start / end callback, add fields to the AI_Chase class that denotes
// TODO - These callbacks will be executed at the same time as frame anyways.
};
// FIXME - This framegroup nonsense is causing chaos... the struct is too large apparently... what can I cut out?
framegroup make_empty_framegroup(__out framegroup fg) {
fg.start_frame = -1;
fg.end_frame = -1;
fg.duration = 1.0;
// fg.start_callback = SUB_Null;
fg.frame_callback = SUB_Null;
// fg.end_callback = SUB_Null;
fg.think_callback = SUB_Null;
}
void init_framegroup( float start_frame, float end_frame, float duration,
void(entity) frame_callback,
void(entity) think_callback,
@ -44,214 +17,157 @@ void init_framegroup( float start_frame, float end_frame, float duration,
// Constructor. Called when calling `spawn(AI_Chase);`
void() AI_Chase::AI_Chase = {
this.path_target = world;
this.path_pos = world.origin;
this.think_delta_time = 0.1; // Call `this.think();` 10x per second
this.cur_fg_idx = 0;
};
class AI_Chase : entity {
// framegroup_registry *th_reg;
entity path_target; // If specified, path towards entity
vector path_pos; // Otherwise, path towards location specified
float think_delta_time; // Delta time (seconds) between each `this.think();`
// ------------------------------
// Current Framegroup value vars
// ------------------------------
float cur_fg_start_time;
float cur_fg_start_frame;
float cur_fg_end_frame;
float cur_fg_duration;
// void() endanimfunc = SUB_Null;
// virtual void(entity) cur_fg_start_callback = SUB_Null;
virtual void(entity) cur_fg_frame_callback = SUB_Null;
// virtual void(entity) cur_fg_end_callback = SUB_Null;
virtual void(entity) cur_fg_think_callback = SUB_Null;
// ------------------------------
// Framegroup state vars
// ------------------------------
float cur_fg_idx; // Counter for checking when the framegroup has been changed
float cur_fg_frame_callback_next_frame;
float cur_fg_frame_callback_is_start_frame; // Is set to true when performing cur_fg_frame_callback for the first framegroup frame
float cur_fg_frame_callback_is_end_frame; // Is set to true when performing cur_fg_frame_callback for the final framegroup frame
// ------------------------------
// Constructor. Called when calling `spawn(AI_Chase);`
virtual void() AI_Chase = {
this.path_target = world;
this.path_pos = world.origin;
this.think_delta_time = 0.1; // Call `this.think();` 10x per second
this.cur_fg_idx = 0;
};
virtual void() think = {
if(this.cur_fg_start_time >= 0) {
float cur_framegroup_idx = this.cur_fg_idx;
float dt = time - this.cur_fg_start_time;
float lerp_frac = (dt / this.cur_fg_duration);
// Frame ranges are inclusive, do an un-clamped interpolation to include the final frame
float cur_frame = unclamped_lerp(this.cur_fg_start_frame, this.cur_fg_end_frame, lerp_frac);
// For rendering, keep frame number always within current animation
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
this.frame = clamp(cur_frame, this.cur_fg_start_frame, this.cur_fg_end_frame);
}
else {
this.frame = clamp(cur_frame, this.cur_fg_end_frame, this.cur_fg_start_frame);
}
// print("Cur frame: ");
// print(ftos(this.frame));
// print(", start_frame: ");
// print(ftos(this.cur_fg_start_frame));
// print(", end_frame: ");
// print(ftos(this.cur_fg_end_frame));
// print(", cur_frame: ");
// print(ftos(cur_frame));
// print(", next frame callback: ");
// print(ftos(this.cur_fg_frame_callback_next_frame));
// print(", duration: ");
// print(ftos(this.cur_fg_duration));
// print("\n");
// TODO - round frame to nearest frame?
// If the animation is playing forward (low frame num to high frame num)
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
if(cur_frame >= this.cur_fg_frame_callback_next_frame) {
if(cur_frame >= this.cur_fg_end_frame + 1) {
this.cur_fg_frame_callback_is_end_frame = true;
}
this.cur_fg_frame_callback(this); // FIXME - This can override and invoke new things, need to be careful
// Callback may have assigned new framegroup
if(this.cur_fg_idx == cur_framegroup_idx) {
this.cur_fg_frame_callback_is_start_frame = false;
this.cur_fg_frame_callback_is_end_frame = false;
this.cur_fg_frame_callback_next_frame = floor(cur_frame) + 1;
}
// print("\tSet next frame callback frame to: ");
// print(ftos(this.cur_fg_frame_callback_next_frame));
// print("\n");
}
}
// else the animation is playing in reverse (high frame num to low frame num)
else {
if(cur_frame <= this.cur_fg_frame_callback_next_frame) {
if(cur_frame <= this.cur_fg_end_frame - 1) {
this.cur_fg_frame_callback_is_end_frame = true;
}
this.cur_fg_frame_callback(this);
// Callback may have assigned new framegroup
if(this.cur_fg_idx == cur_fg_idx) {
this.cur_fg_frame_callback_is_start_frame = false;
this.cur_fg_frame_callback_is_end_frame = false;
this.cur_fg_frame_callback_next_frame = ceil(cur_frame) - 1;
}
}
}
// Callback may have assigned new framegroup
// if(this.cur_fg_idx == cur_fg_idx) {
this.cur_fg_think_callback(this);
// }
}
this.nextthink = time + this.think_delta_time;
};
virtual void (float duration) set_framegroup_duration = {
// If no current framegroup, stop
if(this.cur_fg_start_time < 0) {
print("Warning: Attempted to set framegroup duration without an active framegroup.\n");
return;
}
this.cur_fg_duration = duration;
// --------------------------------------------------------------------
// Adjust think_delta_time so no frames are skipped
// --------------------------------------------------------------------
float n_anim_frames = fabs(this.cur_fg_end_frame - this.cur_fg_start_frame);
float time_per_frame = duration / n_anim_frames;
// If faster than 10FPS, make thinks be called more often to not miss a frame callback
if(time_per_frame < 0.10) {
this.think_delta_time = time_per_frame * 0.5;
void() AI_Chase::think = {
if(this.cur_fg_start_time >= 0) {
float cur_framegroup_idx = this.cur_fg_idx;
float dt = time - this.cur_fg_start_time;
float lerp_frac = (dt / this.cur_fg_duration);
// Frame ranges are inclusive, do an un-clamped interpolation to include the final frame
float cur_frame = unclamped_lerp(this.cur_fg_start_frame, this.cur_fg_end_frame, lerp_frac);
// For rendering, keep frame number always within current animation
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
this.frame = clamp(cur_frame, this.cur_fg_start_frame, this.cur_fg_end_frame);
}
else {
this.think_delta_time = 0.1;
this.frame = clamp(cur_frame, this.cur_fg_end_frame, this.cur_fg_start_frame);
}
// --------------------------------------------------------------------
};
virtual void (framegroup fgroup) set_framegroup = {
// Increment framegroup counter. Reset every 100, we only use this to check when it has changed
this.cur_fg_idx = (this.cur_fg_idx + 1) % 100;
// FIXME - frame callback isn't guaranteed to be called for every frame because sometimes we skip over frames
// FIXME - I should modify the think delta time to run at 80% of the time between frames, or 0.10 seconds, whichever is greater.
// FIXME - That way, if are playing an animation really fast, it'll call think more often
this.cur_fg_start_frame = fgroup.start_frame;
this.cur_fg_end_frame = fgroup.end_frame;
this.cur_fg_frame_callback = fgroup.frame_callback;
this.cur_fg_think_callback = fgroup.think_callback;
this.set_framegroup_duration(fgroup.duration);
// Start the animation now
this.cur_fg_frame_callback_is_start_frame = true;
this.cur_fg_frame_callback_is_end_frame = false;
this.frame = this.cur_fg_start_frame;
this.cur_fg_start_time = time;
this.cur_fg_frame_callback_next_frame = this.cur_fg_start_frame;
// print("Done setting framegroup! Start frame: ");
// print(ftos(fgroup.start_frame));
// print(", End frame: ");
// print(ftos(fgroup.end_frame));
// print("Cur frame: ");
// print(ftos(this.frame));
// print(", start_frame: ");
// print(ftos(this.cur_fg_start_frame));
// print(", end_frame: ");
// print(ftos(this.cur_fg_end_frame));
// print(", cur_frame: ");
// print(ftos(cur_frame));
// print(", next frame callback: ");
// print(ftos(this.cur_fg_frame_callback_next_frame));
// print(", duration: ");
// print(ftos(this.cur_fg_duration));
// print("\n");
};
// TODO - round frame to nearest frame?
virtual void () fg_die = {};
virtual void () fg_walk = {};
virtual void () fg_attack = {};
virtual void () fg_idle = {};
virtual void (float dist) do_walk_to_goal = {
if(dist == 0) {
return;
// If the animation is playing forward (low frame num to high frame num)
if(this.cur_fg_end_frame > this.cur_fg_start_frame) {
if(cur_frame >= this.cur_fg_frame_callback_next_frame) {
if(cur_frame >= this.cur_fg_end_frame + 1) {
this.cur_fg_frame_callback_is_end_frame = true;
}
this.cur_fg_frame_callback(this); // FIXME - This can override and invoke new things, need to be careful
// Callback may have assigned new framegroup
if(this.cur_fg_idx == cur_framegroup_idx) {
this.cur_fg_frame_callback_is_start_frame = false;
this.cur_fg_frame_callback_is_end_frame = false;
this.cur_fg_frame_callback_next_frame = floor(cur_frame) + 1;
}
// print("\tSet next frame callback frame to: ");
// print(ftos(this.cur_fg_frame_callback_next_frame));
// print("\n");
}
}
vector goal_pos = path_pos;
if(this.path_target != world) {
goal_pos = this.path_target.origin;
// else the animation is playing in reverse (high frame num to low frame num)
else {
if(cur_frame <= this.cur_fg_frame_callback_next_frame) {
if(cur_frame <= this.cur_fg_end_frame - 1) {
this.cur_fg_frame_callback_is_end_frame = true;
}
this.cur_fg_frame_callback(this);
// Callback may have assigned new framegroup
if(this.cur_fg_idx == cur_fg_idx) {
this.cur_fg_frame_callback_is_start_frame = false;
this.cur_fg_frame_callback_is_end_frame = false;
this.cur_fg_frame_callback_next_frame = ceil(cur_frame) - 1;
}
}
}
// Callback may have assigned new framegroup
// if(this.cur_fg_idx == cur_fg_idx) {
this.cur_fg_think_callback(this);
// }
}
this.nextthink = time + this.think_delta_time;
};
this.ideal_yaw = vectoyaw(goal_pos - this.origin);
ChangeYaw();
vector new_velocity;
float dist_to_goal = vlen(goal_pos - this.origin);
if(dist > dist_to_goal) {
dist = dist_to_goal;
}
new_velocity = normalize(goal_pos - this.origin) * dist;
new_velocity_z = this.velocity_z;
this.velocity = new_velocity;
};
void (float duration) AI_Chase::set_framegroup_duration = {
// If no current framegroup, stop
if(this.cur_fg_start_time < 0) {
print("Warning: Attempted to set framegroup duration without an active framegroup.\n");
return;
}
this.cur_fg_duration = duration;
// --------------------------------------------------------------------
// Adjust think_delta_time so no frames are skipped
// --------------------------------------------------------------------
float n_anim_frames = fabs(this.cur_fg_end_frame - this.cur_fg_start_frame);
float time_per_frame = duration / n_anim_frames;
// If faster than 10FPS, make thinks be called more often to not miss a frame callback
if(time_per_frame < 0.10) {
this.think_delta_time = time_per_frame * 0.5;
}
else {
this.think_delta_time = 0.1;
}
// --------------------------------------------------------------------
};
void (framegroup fgroup) AI_Chase::set_framegroup = {
// Increment framegroup counter. Reset every 100, we only use this to check when it has changed
this.cur_fg_idx = (this.cur_fg_idx + 1) % 100;
// FIXME - frame callback isn't guaranteed to be called for every frame because sometimes we skip over frames
// FIXME - I should modify the think delta time to run at 80% of the time between frames, or 0.10 seconds, whichever is greater.
// FIXME - That way, if are playing an animation really fast, it'll call think more often
this.cur_fg_start_frame = fgroup.start_frame;
this.cur_fg_end_frame = fgroup.end_frame;
this.cur_fg_frame_callback = fgroup.frame_callback;
this.cur_fg_think_callback = fgroup.think_callback;
this.set_framegroup_duration(fgroup.duration);
// Start the animation now
this.cur_fg_frame_callback_is_start_frame = true;
this.cur_fg_frame_callback_is_end_frame = false;
this.frame = this.cur_fg_start_frame;
this.cur_fg_start_time = time;
this.cur_fg_frame_callback_next_frame = this.cur_fg_start_frame;
// print("Done setting framegroup! Start frame: ");
// print(ftos(fgroup.start_frame));
// print(", End frame: ");
// print(ftos(fgroup.end_frame));
// print("\n");
};
void (float dist) AI_Chase::do_walk_to_goal = {
if(dist == 0) {
return;
}
vector goal_pos = path_pos;
if(this.path_target != world) {
goal_pos = this.path_target.origin;
}
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;
this.ideal_yaw = vectoyaw(goal_pos - this.origin);
ChangeYaw();
vector new_velocity;
float dist_to_goal = vlen(goal_pos - this.origin);
if(dist > dist_to_goal) {
dist = dist_to_goal;
}
new_velocity = normalize(goal_pos - this.origin) * dist;
new_velocity_z = this.velocity_z;
this.velocity = new_velocity;
};
@ -264,6 +180,7 @@ class AI_Zombie : AI_Chase {
framegroup fg_zombie_walk_0;
framegroup fg_zombie_walk_1;
framegroup fg_zombie_walk_2;
@ -410,18 +327,17 @@ void create_framegroups() {
// death2 139 148
// In blender, it's frames 38 to 53, inclusive
// make_empty_framegroup(fg_zombie_walk_0);
init_framegroup( 38, 53, 1.0, zombie_walk_frame_callback, zombie_walk_think_callback, fg_zombie_walk_0);
init_framegroup( 53, 66, 10.0, SUB_Null, SUB_Null, fg_zombie_walk_1);
init_framegroup( 67, 82, 10.0, SUB_Null, SUB_Null, fg_zombie_walk_2);
init_framegroup( 116, 129, 10.0, SUB_Null, SUB_Null, fg_zombie_jog_0);
init_framegroup( 78, 85, 10.0, SUB_Null, SUB_Null, fg_zombie_run_0);
init_framegroup( 0, 12, 10.0, SUB_Null, SUB_Null, fg_zombie_idle_0);
init_framegroup( 86, 90, 10.0, SUB_Null, SUB_Null, fg_zombie_attack_0);
init_framegroup( 91, 96, 10.0, SUB_Null, SUB_Null, fg_zombie_attack_1);
init_framegroup( 123, 133, 10.0, SUB_Null, SUB_Null, fg_zombie_die_0);
init_framegroup( 134, 138, 10.0, SUB_Null, SUB_Null, fg_zombie_die_1);
init_framegroup( 139, 148, 10.0, SUB_Null, SUB_Null, fg_zombie_die_2);
init_framegroup( 53, 66, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_walk_1);
init_framegroup( 67, 82, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_walk_2);
init_framegroup( 116, 129, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_jog_0);
init_framegroup( 78, 85, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_run_0);
init_framegroup( 0, 12, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_idle_0);
init_framegroup( 86, 90, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_attack_0);
init_framegroup( 91, 96, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_attack_1);
init_framegroup( 123, 133, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_die_0);
init_framegroup( 134, 138, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_die_1);
init_framegroup( 139, 148, 10.0, (void(entity)) SUB_Null, (void(entity)) SUB_Null, fg_zombie_die_2);
};

View file

@ -6,6 +6,10 @@ float sv_navmesh_vert_count;
navmesh_poly sv_navmesh_polies[NAV_MAX_POLIES];
float sv_navmesh_poly_count;
// List of all navmesh traversals
navmesh_traversal sv_navmesh_traversals[NAV_MAX_TRAVERSALS];
float sv_navmesh_traversal_count;
void () Navmesh_Editor_Logic =
{
if (!cl_navmesh_edit_mode) {
@ -62,16 +66,16 @@ void () Navmesh_Editor_Logic =
};
void sv_load_navmesh_data()
{
void sv_load_navmesh_data() {
string filepath;
float file;
filepath = strcat(mappath, ".nav");
print("Loading server-side navmesh from file \"", filepath, "\"\n");
file = fopen(filepath,FILE_READ);
if(file == -1)
{
if(file == -1) {
print("Error: file \"",filepath,"\" not found.\n");
return;
}
@ -83,8 +87,7 @@ void sv_load_navmesh_data()
string line = fgets(file);
float vert_count = stof(line);
if(vert_count > sv_navmesh_verts.length)
{
if(vert_count > sv_navmesh_verts.length) {
print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , line, " > ", ftos(sv_navmesh_verts.length),").\n");
fclose(file);
return;
@ -98,8 +101,7 @@ void sv_load_navmesh_data()
//Reading all of the vertex positions
for(float i = 0; i < sv_navmesh_vert_count; i++)
{
for(float i = 0; i < sv_navmesh_vert_count; i++) {
line = fgets(file);
temp = stov(line);
sv_navmesh_verts[i].pos.x = temp.x;
@ -111,47 +113,89 @@ void sv_load_navmesh_data()
sv_navmesh_poly_count = stof(fgets(file));
//The next lines are each polygon
for(float i = 0; i < sv_navmesh_poly_count; i++)
{
//Getting vertex count
for(float i = 0; i < sv_navmesh_poly_count; i++) {
// Get vertex count
sv_navmesh_polies[i].vert_count = stof(fgets(file));
//Getting vertices
// Get vertices
sv_navmesh_polies[i].verts[0] = stof(fgets(file));
sv_navmesh_polies[i].verts[1] = stof(fgets(file));
sv_navmesh_polies[i].verts[2] = stof(fgets(file));
sv_navmesh_polies[i].verts[3] = stof(fgets(file));
//Getting polygon center
// Get polygon center
temp = stov(fgets(file));
sv_navmesh_polies[i].center.x = temp.x;
sv_navmesh_polies[i].center.y = temp.y;
sv_navmesh_polies[i].center.z = temp.z;
//Getting link count
// Get link count
sv_navmesh_polies[i].connected_polies_count = stof(fgets(file));
//Getting links
// Get links
sv_navmesh_polies[i].connected_polies[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies[3] = stof(fgets(file));
//Getting link edge left vertex
// Get link edge left vertex
sv_navmesh_polies[i].connected_polies_left_vert[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_left_vert[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_left_vert[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_left_vert[3] = stof(fgets(file));
//Getting link edge right vertex
// Get link edge right vertex
sv_navmesh_polies[i].connected_polies_right_vert[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_right_vert[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_right_vert[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_right_vert[3] = stof(fgets(file));
//Get link edge neighbor entrance edge index
// Get link edge neighbor entrance edge index
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[0] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[1] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[2] = stof(fgets(file));
sv_navmesh_polies[i].connected_polies_neighbor_edge_index[3] = stof(fgets(file));
// If navmesh file version 0.0.0, no traversals
// FIXME - Need a better way to do this..
if(strcmp(nav_file_version, "0.0.0") != 0) {
// Get connected traversals
sv_navmesh_polies[i].connected_traversals_count = stof(fgets(file));
for(float j = 0; j < sv_navmesh_polies[i].connected_traversals_count; j++) {
sv_navmesh_polies[i].connected_traversals[j] = stof(fgets(file));
}
}
//Get polygon doortarget
sv_navmesh_polies[i].doortarget = fgets(file);
//Get entrance edge
sv_navmesh_polies[i].entrance_edge = stoi(fgets(file));
}
// -----------------------------------------------------------------------
// Load Traversals
// -----------------------------------------------------------------------
// If navmesh file version 0.0.0, no traversals
// FIXME - Need a better way to do this..
if(strcmp(nav_file_version, "0.0.0") != 0) {
//Next line contains the number of traverals
sv_navmesh_traversal_count = stof(fgets(file));
//The next lines are each traversal
for(float i = 0; i < sv_navmesh_traversal_count; i++) {
line = fgets(file);
temp = stov(line);
sv_navmesh_traversals[i].start_pos.x = temp.x;
sv_navmesh_traversals[i].start_pos.y = temp.y;
sv_navmesh_traversals[i].start_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
sv_navmesh_traversals[i].midpoint_pos.x = temp.x;
sv_navmesh_traversals[i].midpoint_pos.y = temp.y;
sv_navmesh_traversals[i].midpoint_pos.z = temp.z;
line = fgets(file);
temp = stov(line);
sv_navmesh_traversals[i].end_pos.x = temp.x;
sv_navmesh_traversals[i].end_pos.y = temp.y;
sv_navmesh_traversals[i].end_pos.z = temp.z;
sv_navmesh_traversals[i].angle = stof(fgets(file));
sv_navmesh_traversals[i].use_midpoint = stof(fgets(file));
sv_navmesh_traversals[i].end_poly = stof(fgets(file));
}
}
// -----------------------------------------------------------------------
fclose(file);
}

View file

@ -0,0 +1,779 @@
/*
server/ai/pathfind_core.qc
Navmesh-based pathfinding
Copyright (C) 2021 NZ:P Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
//===========================================================================================================
//==================================== Navmesh-based Pathfinding Functions ==================================
//===========================================================================================================
//Calculates the heuristic h score value (Our best guess for how far this node is from the goal node)
float sv_pathfind_calc_h_score(float current, float goal)
{
//FIXME: we could just as easily return vlen()^2 for comparisons... (saves a sqrt operation)
return vlen(sv_navmesh_polies[goal].center - sv_navmesh_polies[current].center);
}
//Returns the polygon with the lowest f score from polygons the open set
float sv_pathfind_get_lowest_f_score(navmesh_pathfind_result* res)
{
//TODO: implement a better algorithm for finding the lowest score
float best_score = 100000;
float best_score_index = -1;
for(float i = 0; i < sv_navmesh_poly_count; i++)
{
if(res->poly_set[i] == PATHFIND_POLY_SET_OPEN)
{
if(res->poly_f_score[i] < best_score)
{
best_score = res->poly_f_score[i];
best_score_index = i;
}
}
}
return best_score_index;
}
void sv_pathfind_clear_result_data(navmesh_pathfind_result* res) {
//Technically we only need to iterate over navmesh_poly_count...
for(float i = 0; i < NAV_MAX_POLIES; i++) {
res->poly_set[i] = PATHFIND_POLY_SET_NONE;
res->poly_prev_poly[i] = -1;
res->poly_prev_traversal[i] = -1;
res->poly_g_score[i] = 0;
res->poly_f_score[i] = 0;
res->path_polygons[i] = -1;
res->path_traversals[i] = -1;
res->portals_left_pos[i].x = 0;
res->portals_left_pos[i].y = 0;
res->portals_left_pos[i].z = 0;
res->portals_right_pos[i].x = 0;
res->portals_right_pos[i].y = 0;
res->portals_right_pos[i].z = 0;
res->portals_traversal[i] = -1;
res->point_path_points[i].x = 0;
res->point_path_points[i].y = 0;
res->point_path_points[i].y = 0;
res->point_path_traversals[i] = -1;
}
for(float i = 0;i < NAV_MAX_TRAVERSALS; i++) {
res->traversal_set[i] = PATHFIND_POLY_SET_NONE;
res->traversal_prev_poly[i] = -1;
res->traversal_g_score[i] = 0;
res->traversal_f_score[i] = 0;
}
res->path_length = 0;
res->portals_length = 0;
res->point_path_length = 0;
}
//Applies a funnel algorithm to the path defined by the array res->result_node_path
// and populates pathfind result path
void sv_pathfind_smooth_path(vector start_point, vector goal_point, navmesh_pathfind_result* res) {};
// void sv_pathfind_smooth_path(vector start_point, vector goal_point, navmesh_pathfind_result* res) {
// //Evaluate portals and identify left / right portal vertices
// for(float i = 1; i < res->result_node_length; i++)
// {
// float prev_poly = res->result_node_path[i-1];
// float poly = res->result_node_path[i];
// //TODO: find link index of portal
// //Finding what index prev_poly is linked to poly
// float link_index = -1;
// for(float j = 0; j < sv_navmesh_polies[prev_poly].connected_polies_count; j++)
// {
// if(sv_navmesh_polies[prev_poly].connected_polies[j] == poly)
// {
// link_index = j;
// break;
// }
// }
// //Use that index to get left and right vertices
// res->portals_left_vert[res->portals_length] = sv_navmesh_polies[prev_poly].connected_polies_left_vert[link_index];
// res->portals_right_vert[res->portals_length++] = sv_navmesh_polies[prev_poly].connected_polies_right_vert[link_index];
// }
// //the last left edge / right edge is the goal position
// /*print("Portals: ");
// for(float i = 0; i < res->portals_length; i++)
// {
// print("[");
// print(ftos(i));
// print("] = (");
// print(ftos(res->portals_left_vert[i]));
// print(" , ");
// print(ftos(res->portals_right_vert[i]));
// print(") , ");
// }*/
// //starting at start_pos (not at start center point)
// //get vectors that point to first left edge and first right edge
// vector funnel_apex = start_point;
// float funnel_left_index = 0;
// //Index of the funnel's left vertex in the portal list
// vector funnel_left = sv_navmesh_verts[res->portals_left_vert[0]].pos;
// //Index of the funnel's right vertex in the portal list
// float funnel_right_index = 0;
// vector funnel_right = sv_navmesh_verts[res->portals_right_vert[0]].pos;
// vector next_funnel_left = '0 0 0';
// vector next_funnel_right = '0 0 0';
// vector corner;
// int portal_idx;
// float inside_right_edge;
// float inside_left_edge;
// while(1)
// {
// //print("=============Funnel iteration.==========\n");
// //Check if we have reached the end of the portals, consider the goal position as the last portal
// //If we are not at the last index of the left portal
// //Keeping track of whether or not advancing the left edge or right edge narrows the funnel
// float advanced_left = FALSE;
// float advanced_right = FALSE;
// //Consider the end goal point as the last portal
// //================ Checking left funnel edge =================
// if(funnel_left_index < res->portals_length)
// {
// //print("Trying to advance left edge.\n");
// if(funnel_left_index < res->portals_length - 1)
// {
// next_funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index+1]].pos;
// // print("Next left edge is vert: ",ftos(res->portals_left_vert[funnel_left_index+1],".\n");
// }
// else
// {
// // print("Trying next left edge as goal point.\n");
// //If funnel_left is pointing to the last portal in the array, consider the goal_point the last portal
// next_funnel_left = goal_point;
// }
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_left);
// //If the next left edge crosses the current right edge
// if(inside_right_edge < 0)
// {
// //print("next left edge crosses current right edge.\n");
// //Add funnel right point to path
// // res->result_path[res->result_length++] = funnel_right;
// corner = funnel_right;
// // ------------------------------------------------------------
// // Instead of adding the funnel's corner to the path,
// // move away from the corner by some small amount
// // ------------------------------------------------------------
// // Find the first portal along the path with this corner
// portal_idx = funnel_right_index;
// for(int i = funnel_right_index; i >= 0; i--) {
// if(res->portals_right_vert[i] == res->portals_right_vert[funnel_right_index]) {
// portal_idx = i;
// continue;
// }
// break;
// }
// // For every portal with this corner, add a point to the path
// for(int i = portal_idx; i <= funnel_right_index; i++) {
// // Find the vertex opposite this portal edge
// vector opposite_corner = sv_navmesh_verts[res->portals_left_vert[i]].pos;
// // Move 20 qu or 20% of the edge length, whichever is smaller:
// float edge_len = vlen(opposite_corner - corner);
// vector edge_dir = normalize(opposite_corner - corner);
// vector delta_corner = edge_dir * min(edge_len * 0.2, 20);
// res->result_path[res->result_length++] = corner + delta_corner;
// }
// // ------------------------------------------------------------
// // res->result_path[res->result_length++] = corner;
// // TODO - Move 10% of the way the funnel's right portal vert to the left
// //Check if we are at the end of our portal list
// if(funnel_right_index >= res->portals_length - 1)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// //Restart algorithm with the portal after the right point
// funnel_apex = funnel_right;
// funnel_left_index = funnel_right_index + 1;
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
// funnel_right_index = funnel_right_index + 1;
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
// continue;
// }
// //If the next left edge is in the funnel
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_left);
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
// {
// //print("next left edge is in the funnel.\n");
// //Advance the left edge
// funnel_left = next_funnel_left;
// funnel_left_index++;
// advanced_left = TRUE;
// //Check if the portal we just added was the goal point (will be after the list)
// if(funnel_left_index >= res->portals_length)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// }
// }
// //================ Checking right funnel edge =================
// if(funnel_right_index < res->portals_length)
// {
// //print("Trying to advance right edge:.\n");
// if(funnel_right_index < res->portals_length - 1)
// {
// next_funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index+1]].pos;
// //print("Next right edge is vert: ",ftos(res->portals_right_vert[funnel_right_index+1]),".\n");
// }
// else
// {
// //print("Trying next right edge as goal point.\n");
// //If funnel_right is pointing to the last portal in the array, consider the goal_point the last portal
// next_funnel_right = goal_point;
// }
// //If the next right edge crosses the current left edge
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_right);
// if(inside_left_edge < 0)
// {
// //print("next right edge crosses current left edge.\n");
// //Add funnel left point to path
// // res->result_path[res->result_length++] = funnel_left;
// corner = funnel_left;
// // ------------------------------------------------------------
// // Instead of adding the funnel's corner to the path,
// // move away from the corner by some small amount
// // ------------------------------------------------------------
// // Find the first portal along the path with this corner
// portal_idx = funnel_left_index;
// for(int i = funnel_left_index; i >= 0; i--) {
// if(res->portals_left_vert[i] == res->portals_left_vert[funnel_left_index]) {
// portal_idx = i;
// continue;
// }
// break;
// }
// // For every portal with this corner, add a point to the path
// for(int i = portal_idx; i <= funnel_left_index; i++) {
// // Find the vertex opposite this portal edge
// vector opposite_corner = sv_navmesh_verts[res->portals_right_vert[i]].pos;
// // Move 20 qu or 20% of the edge length, whichever is smaller:
// float edge_len = vlen(opposite_corner - corner);
// vector edge_dir = normalize(opposite_corner - corner);
// vector delta_corner = edge_dir * min(edge_len * 0.2, 20);
// res->result_path[res->result_length++] = corner + delta_corner;
// }
// // ------------------------------------------------------------
// // res->result_path[res->result_length++] = corner;
// //Check if we are at the end of our portal list
// if(funnel_left_index >= res->portals_length - 1)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// //Restart algorithm with the portal after the right point
// funnel_apex = funnel_left;
// funnel_right_index = funnel_left_index + 1;
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
// funnel_left_index = funnel_left_index + 1;
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
// continue;
// }
// //If the next right edge is in the funnel
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_right);
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
// {
// //print("next right edge is in the funnel.\n");
// //Advance the left edge
// funnel_right = next_funnel_right;
// funnel_right_index++;
// advanced_right = TRUE;
// //Check if the portal we just added was the goal point (will be after the list)
// if(funnel_right_index >= res->portals_length)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// }
// }
// if(advanced_left == FALSE && advanced_right == FALSE)
// {
// //print("Hit funnel freeze condition.\n");
// float left_vert = -1;
// float left_type = -1;
// float left_portal_index = -1;//index of the vertex in the list of portals
// float right_vert = -1;
// float right_type = -1;
// float right_portal_index = -1;//index of the vertex in the list of portals
// float crossed = 1;//Indicates the vert crossed the funnel
// float contained = 2;//Indicates a vert is in the funnel
// float last_vert = -1;//used to skip calculating the same vertex more than once
// //Find the closest vertex in the portal left with the lowest index that crosses the funnel or is in the funnel
// for(float i = funnel_left_index + 1; i < res->portals_length + 1; i++)
// {
// if(i < res->portals_length - 1)
// {
// //Don't calculate the same vertex again
// if(last_vert == res->portals_left_vert[i])
// continue;
// last_vert = res->portals_left_vert[i];
// next_funnel_left = sv_navmesh_verts[last_vert].pos;
// }
// else//consider goal pos as last portal edge
// {
// next_funnel_left = goal_point;
// }
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_left);
// //If the left vertex crosses the funnel (left vertex is outside the funnel's right edge)
// if(inside_right_edge < 0)
// {
// left_vert = res->portals_left_vert[i];
// left_type = crossed;
// break;
// }
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_left);
// //If the left vertex is within the funnel
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
// {
// left_vert = res->portals_left_vert[i];
// left_type = contained;
// left_portal_index = i;
// break;
// }
// }
// last_vert = -1;
// //Find the closest vertex in the portal right with the lowest index that crosses the funnel or is in the funnel
// for(float i = funnel_right_index + 1; i < res->portals_length; i++)
// {
// if(i < res->portals_length - 1)
// {
// //Don't calculate the same vertex again
// if(last_vert == res->portals_right_vert[i])
// continue;
// last_vert = res->portals_right_vert[i];
// next_funnel_right = sv_navmesh_verts[last_vert].pos;
// }
// else//consider goal pos as last portal edge
// {
// next_funnel_right = goal_point;
// }
// inside_left_edge = pathfind_point_is_to_left(funnel_left,funnel_apex,next_funnel_right);
// //If the right vertex crosses the funnel (right vertex is outside the funnel's left edge)
// if(inside_left_edge < 0)
// {
// right_vert = res->portals_right_vert[i];
// right_type = crossed;
// break;
// }
// inside_right_edge = pathfind_point_is_to_left(funnel_apex,funnel_right,next_funnel_right);
// //If the right vertex is within the funnel
// if(inside_left_edge >= 0 && inside_right_edge >= 0)
// {
// right_vert = res->portals_right_vert[i];
// right_type = contained;
// right_portal_index = i;
// break;
// }
// }
// //If no vertices were found, goal is reachable from here
// if(left_vert == -1 && right_vert == -1)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// float use_left;
// //If both verts were found
// //Find which vertex has a lower index
// if(left_vert != -1 && right_vert != -1)
// {
// if(left_portal_index < right_portal_index)
// {
// use_left = TRUE;
// }
// else
// {
// use_left = FALSE;
// }
// }
// //if left vert was found
// else if(left_vert != -1)
// {
// use_left = TRUE;
// }
// //else right vert was found
// else
// {
// use_left = FALSE;
// }
// if(use_left)
// {
// if(left_type == crossed)
// {
// //Place corner at current right funnel edge
// res->result_path[res->result_length++] = funnel_right;
// //Restart algorithm with the portal after the right point
// funnel_apex = funnel_right;
// //Check if portal after this portal is goal:
// funnel_right_index++;
// if(funnel_right_index >= res->portals_length)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
// funnel_left_index = funnel_right_index;
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
// continue;
// }
// else//in funnel
// {
// //If the next funnel right is the goal, we are done
// //Check if portal after this portal is goal:
// if(right_portal_index >= res->portals_length)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// //Update current left funnel edge to use this vertex
// funnel_left = next_funnel_left;
// funnel_left_index = left_portal_index;
// continue;
// }
// }
// else//use right
// {
// if(right_type == crossed)
// {
// //Place corner at current left funnel edge
// res->result_path[res->result_length++] = funnel_left;
// //Restart algorithm with the portal after the left point
// funnel_apex = funnel_left;
// //Check if portal after this portal is goal:
// funnel_left_index++;
// if(funnel_left_index >= res->portals_length)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// funnel_left = sv_navmesh_verts[res->portals_left_vert[funnel_left_index]].pos;
// funnel_right_index = funnel_left_index;
// funnel_right = sv_navmesh_verts[res->portals_right_vert[funnel_right_index]].pos;
// continue;
// }
// else//in funnel
// {
// //If the next funnel right is the goal, we are done
// //Check if portal after this portal is goal:
// if(right_portal_index >= res->portals_length)
// {
// res->result_path[res->result_length++] = goal_point;
// return;
// }
// //Update current right funnel edge to use this vertex
// funnel_right = next_funnel_right;
// funnel_right_index = right_portal_index;
// continue;
// }
// }
// }
// //print("==========================\n");
// }
// }
//Evaluates the path that is found in the pathfinding algorithm and populates an array with the nodes in the path from start to goal
void sv_pathfind_trace_path(float start, float goal, navmesh_pathfind_result* res) {}
// void sv_pathfind_trace_path(float start, float goal, navmesh_pathfind_result* res)
// {
// float current = start;
// //Count the length of the path (how many polygons the path traverses)
// current = goal;
// res->result_node_length = 1;
// do
// {
// //print("Poly ");
// //print(ftos(current));
// //print(" came from ");
// //print(ftos(res->poly_prev[current]));
// //print(".\n");
// current = res->poly_prev[current];
// res->result_node_length++;
// } while(current != start);
// //Starting at goal waypoint, add the traversed waypoints to the result path in reverse order
// current = goal;
// for(float i = 0; i < res->result_node_length; i++)
// {
// res->result_node_path[res->result_node_length - 1 - i] = current;
// current = res->poly_prev[current];
// }
// // print("Pathfind success, path length: ");
// // print(ftos(res->result_node_length));
// // print(", Path: [");
// // for(float i = 0; i < res->result_node_length; i++)
// // {
// // if(i > 0)
// // print(" , ");
// // print(ftos(res->result_node_path[i]));
// // }
// // print(" ].\n");
// }
//Accepts start polygon and goal polygon
//Returns 1 on success.
//Returns 0 on fail.
float sv_navmesh_pathfind_start(float start, float goal, vector start_pos, vector end_pos, navmesh_pathfind_result* res) {}
// float sv_navmesh_pathfind_start(float start, float goal, vector start_pos, vector end_pos, navmesh_pathfind_result* res) {
// if(start == -1) {
// //print("Error: pathfind start node invalid.\n");
// return 0;
// }
// if(goal == -1) {
// //print("Error: pathfind goal node invalid.\n");
// return 0;
// }
// if(start == goal) {
// //Calculating node path
// res->result_node_path[0] = start;
// res->result_node_length = 1;
// // print("Pathind success: trivial case (start = goal).\n");
// //Calculating vector based path (go directly to goal)
// res->result_path[0].x = end_pos.x;
// res->result_path[0].y = end_pos.y;
// res->result_path[0].z = end_pos.z;
// res->result_length = 1;
// return 1;
// }
// //Clearing previous data
// sv_pathfind_clear_result_data(res);
// //Adding start polygon to the open set
// res->poly_set[start] = PATHFIND_POLY_SET_OPEN;
// res->poly_g_score[start] = 0;
// res->poly_f_score[start] = 0 + sv_pathfind_calc_h_score(start , goal);
// //Fields that need to be set:
// //res->poly_set[NAV_MAX_POLIES];
// //res->prev_poly[NAV_MAX_POLIES];
// //res->poly_g_score[NAV_MAX_POLIES];
// //res->poly_f_score[NAV_MAX_POLIES];
// // print("Pathfind init. Start: ");
// // print(ftos(start));
// // print(" , Goal: ");
// // print(ftos(goal));
// // print(".\n");
// float current = start;
// float pathfind_success = FALSE;
// while(current != -1) {
// if(current == goal) {
// //print("Current is now goal. Breaking.\n");
// pathfind_success = TRUE;
// break;
// }
// //Add current node to the closed set
// res->poly_set[current] = PATHFIND_POLY_SET_CLOSED;
// //Add connected nodes to the open set
// for(float i = 0; i < sv_navmesh_polies[current].connected_polies_count; i++) {
// float neighbor = sv_navmesh_polies[current].connected_polies[i];
// //print("Checking poly ");
// //print(ftos(current));
// //print("'s neighbor ");
// //print(ftos(neighbor));
// //print(".\n");
// // ----------------------------------------------------------------
// // Door check
// // ----------------------------------------------------------------
// // If this polygon references a door:
// if(sv_navmesh_polies[neighbor].doortarget != "") {
// entity door;
// door = find(world, wayTarget, sv_navmesh_polies[neighbor].doortarget);
// if(door != world) {
// // If the referenced door is closed, don't consider this polygon.
// if(door.state == STATE_BOTTOM) {
// continue;
// }
// }
// }
// // ----------------------------------------------------------------
// // ----------------------------------------------------------------
// // Entrance edge
// // ----------------------------------------------------------------
// // Check if we can enter the neighbor polygon from the current polygon
// // across the edge connecting the current and neighbor polygons.
// // If entrance_edge != -1, we can only enter the polygon from the edge at index "entrance_edge"
// if(sv_navmesh_polies[neighbor].entrance_edge != -1) {
// // Check if the edge we're crossing from current to neighbor is the entrance edge
// if(sv_navmesh_polies[neighbor].entrance_edge != sv_navmesh_polies[current].connected_polies_neighbor_edge_index[i]) {
// // If it's not the entrance edge, skip this neighbor.
// // We can't walk from current to neighbor.
// continue;
// }
// }
// // ----------------------------------------------------------------
// if(res->poly_set[neighbor] != PATHFIND_POLY_SET_CLOSED) {
// //print("Neighbor is not in closed list.\n");
// //Calculate tentative f score
// //Calculate tentative g score (distance from start to current + distance from current to neighbor)
// float tentative_g_score = res->poly_g_score[current] + vlen(sv_navmesh_polies[neighbor].center - sv_navmesh_polies[current].center);
// if(res->poly_set[neighbor] != PATHFIND_POLY_SET_OPEN || tentative_g_score < res->poly_g_score[neighbor]) {
// //print("Neighbor is not in open list, or a better g score has been found.\n");
// //Adding neighbor to open set
// res->poly_set[neighbor] = PATHFIND_POLY_SET_OPEN;
// //Updating scores for neighbor node
// float tentative_f_score = tentative_g_score + sv_pathfind_calc_h_score(neighbor , goal);
// res->poly_g_score[neighbor] = tentative_g_score;
// res->poly_f_score[neighbor] = tentative_f_score;
// //Linking neighbor node to current node (for tracing path)
// res->poly_prev[neighbor] = current;
// //print("Assigning Poly ");
// //print(ftos(neighbor));
// //print(" came from ");
// //print(ftos(current));
// //print(".\n");
// }
// }
// }
// current = sv_pathfind_get_lowest_f_score(res);
// }
// //Tracing the pathfind results
// if(pathfind_success == TRUE) {
// sv_pathfind_trace_path(start,goal,res);
// sv_pathfind_smooth_path(start_pos,end_pos,res);
// return 1;
// }
// print("Pathfind fail");
// return 0;
// }
float sv_navmesh_pathfind(entity from, entity to) {}
// float sv_navmesh_pathfind(entity from, entity to) {
// // TODO ... How do I get...?
// // Every AI_Chase entity gets an integer
// // TODO - In pathfinding, each entity needs a place to store its results...
// // TODO - I should really make a MAX_AI number
// // TODO - I can have an array of pre-allocated pathfind results, and dynamically use them?
// // TODO - Or maybe I have one reserved per AI, and one supplemental one reesrved per AI...
// // FIXME - Will need to pre-allocate a certain number of AI_Chase ents and reuse them... or maybe
// // FIXME I need to pre-allocate a certain number of zombies, and dogs?
// // FIXME I wanted to subclass zombies to crawlers to override their behaviors...
// // We can perform pathfinding logic without losing our current path.
// // TODO - In pathfinding, I need a reference to the calling entity to know which traversal types this entity can use
// AI_Chase chase_ent = (AI_Chase) from;
// navmesh_pathfind_result* res = &(sv_zombie_pathfind_result[chase_ent.pathfind_result_idx]);
// float start = sv_navmesh_get_containing_poly(from.origin);
// float goal = sv_navmesh_get_containing_poly(to.origin);
// return sv_navmesh_pathfind_start(start,goal,from.origin,to.origin,res);
// }
//===========================================================================================================

View file

@ -1593,7 +1593,7 @@ float() getMaxZombies =
if (dogWave == true) {
return 2;
} else {
return MAX_ZOMB;
return MAX_ZOMBIES;
}
}

View file

@ -230,9 +230,9 @@ void() SUB_Null2 = {};
#define VEC_VIEW_OFS '0 0 32'
#ifndef HANDHELD
#define MAX_ZOMB 24
#define MAX_ZOMBIES 24
#else
#define MAX_ZOMB 12
#define MAX_ZOMBIES 12
#endif // HANDHELD
vector trace_plane_normal;
@ -477,14 +477,99 @@ string world_fog;
#ifdef PC
void() Navmesh_Editor_Logic;
void() sv_load_navmesh_data;
//One struct for temp pathfinding calculations per zombie
//navmesh_pathfind_result sv_zombie_pathfind_result[MAX_ZOMBIES];
navmesh_pathfind_result *sv_zombie_pathfind_result; // Used as primary. The one that's followed
navmesh_pathfind_result *sv_zombie_pathfind_result_aux; // Used as secondary, so we can pathfind without losing current path
#endif // PC
// //One struct for temp pathfinding calculations per zombie
// //pathfind_result sv_zombie_pathfind_result[MAX_ZOMBIES];
// pathfind_result *sv_zombie_pathfind_result;
//=================================================================
//========================== AI defs ==============================
var struct framegroup {
float start_frame;
float end_frame;
float duration;
// void(entity) start_callback; // Called at the first frame of the range
void(entity) frame_callback; // Called at every frame of the range (after start / finish callbacks)
// void(entity) end_callback; // Called at the last frame of the range
// FIXME - Increasing the size of this framegroup by one more function pointer causes chaos...
// I assume I run out of space to pass framegroups along as arguments at any point...
// It starts to write into weird memory locations, like overriding self / world... things just break down.
// TODO - How can I refactor this to work?
void(entity) think_callback; // Called at each think invocation
// -- Maybe I can get rid of start / end callbacks? idk
// I should think about how I actually plan on using these...
// TODO - Get rid of start / end callback, add fields to the AI_Chase class that denotes
// TODO - These callbacks will be executed at the same time as frame anyways.
};
class AI_Chase : entity {
// framegroup_registry *th_reg;
entity path_target; // If specified, path towards entity
vector path_pos; // Otherwise, path towards location specified
float think_delta_time; // Delta time (seconds) between each `this.think();`
// ------------------------------
// Current Framegroup value vars
// ------------------------------
float cur_fg_start_time;
float cur_fg_start_frame;
float cur_fg_end_frame;
float cur_fg_duration;
// void() endanimfunc = SUB_Null;
// virtual void(entity) cur_fg_start_callback = SUB_Null;
virtual void(entity) cur_fg_frame_callback = (void(entity)) SUB_Null;
// virtual void(entity) cur_fg_end_callback = SUB_Null;
virtual void(entity) cur_fg_think_callback = (void(entity)) SUB_Null;
// ------------------------------
// Framegroup state vars
// ------------------------------
float cur_fg_idx; // Counter for checking when the framegroup has been changed
float cur_fg_frame_callback_next_frame;
float cur_fg_frame_callback_is_start_frame; // Is set to true when performing cur_fg_frame_callback for the first framegroup frame
float cur_fg_frame_callback_is_end_frame; // Is set to true when performing cur_fg_frame_callback for the final framegroup frame
// ------------------------------
float pathfind_result_idx; // TODO - Need to increment this on instantiation
// Constructor. Called when calling `spawn(AI_Chase);`
virtual void() AI_Chase;
virtual void() think;
virtual void (float duration) set_framegroup_duration;
virtual void (framegroup fgroup) set_framegroup;
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
// 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;
};
//=================================================================
//Misc patch definitions
.string teddyremovetarget;

View file

@ -340,12 +340,17 @@ void() worldspawn =
mappath = strzone(mappath);
LoadWaypointData();
// sv_load_navmesh_data();
// TODO - Add PSP equivalent for loading navmesh
#ifdef PC
sv_load_navmesh_data();
//Allocating memory for the zombie's pathfinding data
sv_zombie_pathfind_result = memalloc(sizeof(navmesh_pathfind_result) * MAX_ZOMBIES);
sv_zombie_pathfind_result_aux = memalloc(sizeof(navmesh_pathfind_result) * MAX_ZOMBIES);
// I should technically deallocate the memory somewhere, but I've nowhere to put this, and it SHOULD be cleared automatically upon vm reset
// memfree(sv_zombie_pathfind_result);
// memfree(sv_zombie_pathfind_result_aux);
#endif // PC
// //Allocating memory for the zombie's pathfinding data
// sv_zombie_pathfind_result = memalloc(sizeof(pathfind_result) * MAX_ZOMBIES);
//I should technically deallocate the memory, but I've nowhere to put this, and it SHOULD be cleared automatically upon vm reset
//memfree(sv_zombie_pathfind_result);
//set game elements

View file

@ -147,7 +147,7 @@ struct navmesh_pathfind_result {
float portals_traversal[NAV_MAX_POLIES];
// ---------------------------------------------------------
// ---------------------------------------------------------
// Final path is a list of 3D points and traversals
// ---------------------------------------------------------