diff --git a/source/client/navmesh_editor.qc b/source/client/navmesh_editor.qc index 8b04352..fb720a8 100644 --- a/source/client/navmesh_editor.qc +++ b/source/client/navmesh_editor.qc @@ -246,24 +246,24 @@ void cl_navmesh_draw_poly(float poly_index) { } // If polygon's entrance edge is set, draw other edges as red. - if(cl_navmesh_polies[poly_index].entrance_edge != -1) { - for(int i = 0; i < cl_navmesh_polies[poly_index].vert_count; i++) { - if(i == cl_navmesh_polies[poly_index].entrance_edge) - continue; + // if(cl_navmesh_polies[poly_index].entrance_edge != -1) { + // for(int i = 0; i < cl_navmesh_polies[poly_index].vert_count; i++) { + // if(i == cl_navmesh_polies[poly_index].entrance_edge) + // continue; - // Draw this edge as solid red - int edge_vert_a_idx = i; - int edge_vert_b_idx = (i + 1) % cl_navmesh_polies[poly_index].vert_count; - int edge_vert_a = cl_navmesh_polies[poly_index].verts[edge_vert_a_idx]; - int edge_vert_b = cl_navmesh_polies[poly_index].verts[edge_vert_b_idx]; - vector start = cl_navmesh_verts[edge_vert_a].pos; - vector end = cl_navmesh_verts[edge_vert_b].pos; + // // Draw this edge as solid red + // int edge_vert_a_idx = i; + // int edge_vert_b_idx = (i + 1) % cl_navmesh_polies[poly_index].vert_count; + // int edge_vert_a = cl_navmesh_polies[poly_index].verts[edge_vert_a_idx]; + // int edge_vert_b = cl_navmesh_polies[poly_index].verts[edge_vert_b_idx]; + // vector start = cl_navmesh_verts[edge_vert_a].pos; + // vector end = cl_navmesh_verts[edge_vert_b].pos; - vector edge_color = [0.9,0.2,0.2]; - float edge_alpha = 0.7; - cl_navmesh_draw_line(start,end,2,edge_color,edge_alpha); - } - } + // vector edge_color = [0.9,0.2,0.2]; + // float edge_alpha = 0.7; + // cl_navmesh_draw_line(start,end,2,edge_color,edge_alpha); + // } + // } } @@ -315,9 +315,9 @@ void cl_navmesh_draw_traversal(float traversal_index) { float edge_width = 0.1; if(cl_navmesh_selected_traversal != traversal_index) { - active_alpha = 0.2; + active_alpha = 0.4; inactive_alpha = 0.0; - edge_alpha = 0.1; + edge_alpha = 0.4; edge_color = '0 0.2 0.5'; active_color = '0 0.2 0.5'; } @@ -1115,7 +1115,7 @@ void cl_navmesh_make_poly() cl_navmesh_polies[cl_navmesh_poly_count].verts[i] = selected_verts[i]; } cl_navmesh_polies[cl_navmesh_poly_count].vert_count = selected_vert_count; - cl_navmesh_polies[cl_navmesh_poly_count].entrance_edge = -1; + // cl_navmesh_polies[cl_navmesh_poly_count].entrance_edge = -1; cl_navmesh_poly_count++; } @@ -1133,7 +1133,7 @@ void cl_navmesh_delete_poly_at_index(float poly_index) } // Copy down other fields: - cl_navmesh_polies[i].entrance_edge = cl_navmesh_polies[i+1].entrance_edge; + // cl_navmesh_polies[i].entrance_edge = cl_navmesh_polies[i+1].entrance_edge; cl_navmesh_polies[i].doortarget = cl_navmesh_polies[i+1].doortarget; } @@ -1143,7 +1143,7 @@ void cl_navmesh_delete_poly_at_index(float poly_index) { cl_navmesh_polies[cl_navmesh_poly_count-1].verts[j] = -1; } - cl_navmesh_polies[cl_navmesh_poly_count-1].entrance_edge = -1; + // cl_navmesh_polies[cl_navmesh_poly_count-1].entrance_edge = -1; cl_navmesh_polies[cl_navmesh_poly_count-1].doortarget = ""; cl_navmesh_poly_count--; @@ -1695,7 +1695,7 @@ void cl_navmesh_editor_save_navmesh() { // Getting polygon entrance edge index - fputs(file,itos(cl_navmesh_polies[i].entrance_edge),"\n"); + // fputs(file,itos(cl_navmesh_polies[i].entrance_edge),"\n"); } // ----------------------------------------------------------------------- @@ -1773,7 +1773,7 @@ void cl_navmesh_editor_clear_navmesh() cl_navmesh_polies[i].doortarget = ""; - cl_navmesh_polies[i].entrance_edge = -1; + // cl_navmesh_polies[i].entrance_edge = -1; } cl_navmesh_traversal_count = 0; for(float i = 0; i < NAV_MAX_TRAVERSALS; i++) { @@ -1805,19 +1805,17 @@ void cl_navmesh_editor_load_navmesh() { } - float v; - //First line contains navmesh file semver + // First line contains navmesh file semver string nav_file_version = fgets(file); print("Loading v", nav_file_version, " navmesh file \"", filepath, "\"...\n"); // TODO - Add backwards compatibility for older navmesh file versions. // Next line contains vertex count - string line = fgets(file); - float vert_count = stof(line); + float vert_count = stof(fgets(file)); if(vert_count > NAV_MAX_VERTS) { - print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , line, " > ", ftos(NAV_MAX_VERTS),").\n"); + print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , vert_count, " > ", ftos(NAV_MAX_VERTS),").\n"); fclose(file); return; } @@ -1826,20 +1824,12 @@ void cl_navmesh_editor_load_navmesh() { cl_navmesh_editor_clear_navmesh(); cl_navmesh_vert_count = vert_count; - - print("Vert count: ",line,"\n"); + print("Vert count: ",vert_count,"\n"); + - //Temp vector to assign component-wise - vector temp; - //Reading all of the vertex positions for(float i = 0; i < cl_navmesh_vert_count; i++) { - line = fgets(file); - temp = stov(line); - cl_navmesh_verts[i].pos.x = temp.x; - cl_navmesh_verts[i].pos.y = temp.y; - cl_navmesh_verts[i].pos.z = temp.z; - //TODO: if something goes wrong, clear the partially loaded navmesh + cl_navmesh_verts[i].pos = stov(fgets(file)); } //Next line contains the number of polygons @@ -1881,7 +1871,7 @@ void cl_navmesh_editor_load_navmesh() { // 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) { + if(nav_file_version != "0.0.0") { // Don't care about connected traversals float n_traversals = stof(fgets(file)); for(float j = 0; j < n_traversals; j++) { @@ -1896,8 +1886,11 @@ void cl_navmesh_editor_load_navmesh() { print("CLLOADNAVMESH - Polygon at index ",ftos(i)," has doortarget: \"", cl_navmesh_polies[i].doortarget, "\"" ); } // cl_navmesh_polies[i].doortarget = ""; // Temp fix to load old files - // Load entrance edge - cl_navmesh_polies[i].entrance_edge = stoi(fgets(file)); + // If v0.0.0, discard entrance edge (no longer used) + if(nav_file_version == "0.0.0") { + // cl_navmesh_polies[i].entrance_edge = stoi(fgets(file)); + fgets(file); + } // cl_navmesh_polies[i].entrance_edge = -1; // Temp fix to load old files } @@ -1905,27 +1898,15 @@ void cl_navmesh_editor_load_navmesh() { // Load Traversals // ----------------------------------------------------------------------- // If navmesh file version 0.0.0, no traversals - if(strcmp(nav_file_version, "0.0.0") != 0) { + if(nav_file_version != "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].start_pos = stov(fgets(file)); + cl_navmesh_traversals[i].midpoint_pos = stov(fgets(file)); + cl_navmesh_traversals[i].end_pos = stov(fgets(file)); 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 @@ -2620,6 +2601,9 @@ void cl_pathfind_trace_path(float start_poly, float goal_poly) { //Returns 1 on success. //Returns 0 on fail. float cl_navmesh_pathfind_start(float start_poly, float goal_poly, vector start_pos, vector end_pos) { + //Clearing previous data + cl_pathfind_clear_result_data(); + if(start_poly == -1) { print("Error: pathfind start node invalid.\n"); return 0; @@ -2645,9 +2629,6 @@ float cl_navmesh_pathfind_start(float start_poly, float goal_poly, vector start_ return 1; } - //Clearing previous data - cl_pathfind_clear_result_data(); - //Adding start polygon to the open set cl_test_pathfind_result->poly_set[start_poly] = PATHFIND_POLY_SET_OPEN; cl_test_pathfind_result->poly_g_score[start_poly] = 0; @@ -2693,67 +2674,6 @@ float cl_navmesh_pathfind_start(float start_poly, float goal_poly, vector start_ // NOTE - This check isn't done in the navmesh editor. // ---------------------------------------------------------------- - // // ---------------------------------------------------------------- - // // Entrance edge - // // ---------------------------------------------------------------- - // // Check if we can enter this polygon from this edge - // // If entrance_edge != -1, we can only enter the polygon from the edge at index "entrance_edge" - // if(cl_navmesh_polies[neighbor].entrance_edge != -1) { - // // print("-- Pathfind loop -- Evaluating a neighbor whose entrance_edge = ",ftos(cl_navmesh_polies[neighbor].entrance_edge),"\n"); - // // print("Current polygon:",ftos(current),"\n"); - // // print("Neighbor polygon:",ftos(neighbor),"\n"); - // // print("Neighbor index:",ftos(i),"\n"); - // // print("Current vertices: "); - // // for(float j = 0; j < cl_navmesh_polies[current].vert_count; j++) { - // // print(ftos(cl_navmesh_polies[current].verts[j]),","); - // // } - // // print("\n"); - // // print("Neighbor vertices: "); - // // for(float j = 0; j < cl_navmesh_polies[neighbor].vert_count; j++) { - // // print(ftos(cl_navmesh_polies[neighbor].verts[j]),","); - // // } - // // print("\n"); - // // print("Current left portal vertices: "); - // // for(float j = 0; j < cl_navmesh_polies[current].connected_polies_count; j++) { - // // print(ftos(cl_navmesh_polies[current].connected_polies_left_vert[j]),","); - // // } - // // print("\n"); - // // print("Current right portal vertices: "); - // // for(float j = 0; j < cl_navmesh_polies[current].connected_polies_count; j++) { - // // print(ftos(cl_navmesh_polies[current].connected_polies_right_vert[j]),","); - // // } - // // print("\n"); - // // print("neighbor left portal vertices: "); - // // for(float j = 0; j < cl_navmesh_polies[neighbor].connected_polies_count; j++) { - // // print(ftos(cl_navmesh_polies[neighbor].connected_polies_left_vert[j]),","); - // // } - // // print("\n"); - // // print("neighbor right portal vertices: "); - // // for(float j = 0; j < cl_navmesh_polies[neighbor].connected_polies_count; j++) { - // // print(ftos(cl_navmesh_polies[neighbor].connected_polies_right_vert[j]),","); - // // } - // // print("\n"); - // // print("Current neighbor edge index: "); - // // for(float j = 0; j < cl_navmesh_polies[current].connected_polies_count; j++) { - // // print(ftos(cl_navmesh_polies[current].connected_polies_neighbor_edge_index[j]),","); - // // } - // // print("\n"); - // // print("Neighbor neighbor edge index: "); - // // for(float j = 0; j < cl_navmesh_polies[neighbor].connected_polies_count; j++) { - // // print(ftos(cl_navmesh_polies[neighbor].connected_polies_neighbor_edge_index[j]),","); - // // } - // // print("\n"); - // // print("According to current polygon, the we're traversing the neighbor's edge with index: ", ftos(cl_navmesh_polies[current].connected_polies_neighbor_edge_index[i])," to enter neighbor.\n"); - - // // Check if the edge we're crossing from current to neighbor is the entrance edge - // if(cl_navmesh_polies[neighbor].entrance_edge != cl_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(cl_test_pathfind_result->poly_set[neighbor_poly] != PATHFIND_POLY_SET_CLOSED) { //print("Neighbor is not in closed list.\n"); @@ -3086,7 +3006,7 @@ void cl_register_navmesh_commands() = registercommand("yes"); registercommand("nav_load_navmesh"); registercommand("nav_toggle_poly_door"); - registercommand("nav_toggle_poly_entrance_edge"); + // registercommand("nav_toggle_poly_entrance_edge"); registercommand("nav_print_poly_door"); // --- Traversal Commands --- @@ -3121,7 +3041,8 @@ void cl_navmesh_editor_toggle_poly_door() { string editor_active_door = strcat(cvar_string("nav_editor_active_door")); // If current polygon doortarget isn't set to the editor's currently active door, set it - if(strcmp(cl_navmesh_polies[selected_polygon].doortarget, editor_active_door) != 0) { + // if(strcmp(cl_navmesh_polies[selected_polygon].doortarget, editor_active_door) != 0) { + if(cl_navmesh_polies[selected_polygon].doortarget != editor_active_door) { cl_navmesh_polies[selected_polygon].doortarget = editor_active_door; } // Otherwise, clear it. @@ -3151,23 +3072,23 @@ void cl_navmesh_editor_print_poly_door() { } -void cl_navmesh_editor_toggle_entrance_edge() { - int selected_polygon = cl_navmesh_get_selected_poly(); - if(selected_polygon == -1) { - print("Can't make door polygon. No polygon selected.\n"); - return; - } +// void cl_navmesh_editor_toggle_entrance_edge() { +// int selected_polygon = cl_navmesh_get_selected_poly(); +// if(selected_polygon == -1) { +// print("Can't make door polygon. No polygon selected.\n"); +// return; +// } - cl_navmesh_polies[selected_polygon].entrance_edge += 1; - // If entrance edge index is no longer valid, reset it to -1 - if(cl_navmesh_polies[selected_polygon].entrance_edge >= cl_navmesh_polies[selected_polygon].vert_count) { - cl_navmesh_polies[selected_polygon].entrance_edge = -1; - } +// cl_navmesh_polies[selected_polygon].entrance_edge += 1; +// // If entrance edge index is no longer valid, reset it to -1 +// if(cl_navmesh_polies[selected_polygon].entrance_edge >= cl_navmesh_polies[selected_polygon].vert_count) { +// cl_navmesh_polies[selected_polygon].entrance_edge = -1; +// } - print("Selected Polygon entrance edge set to: "); - print(itos(cl_navmesh_polies[selected_polygon].entrance_edge)); - print("\n"); -} +// print("Selected Polygon entrance edge set to: "); +// print(itos(cl_navmesh_polies[selected_polygon].entrance_edge)); +// print("\n"); +// } @@ -3579,21 +3500,21 @@ float(string cmd) cl_navmesh_console_commands = case "nav_print_poly_door": cl_navmesh_editor_print_poly_door(); return TRUE; - case "nav_toggle_poly_entrance_edge": - cl_navmesh_editor_toggle_entrance_edge(); - // TODO - Make currently selected polygon only be traversible from - // TODO one direction to another. - // TODO - // TODO - If no polygon selected, stop. - // TODO - If Quad, assume verts sorted CCW from BL - // TODO - if not one-way, make one-way poly from 01 to 23 - // TODO - if one-way from 01 to 23, make one-way from 12 to 03 - // TODO - if one-way from 12 to 03, make one-way from 23 to 01 - // TODO - if one-way from 12 to 03, make one-way from 03 to 12 - // TODO - if one-way from 03 to 12, make normal polygon - // TODO - If tri, should I support one-way? (probably not, tbh) - // TODO - return TRUE; + // case "nav_toggle_poly_entrance_edge": + // cl_navmesh_editor_toggle_entrance_edge(); + // // TODO - Make currently selected polygon only be traversible from + // // TODO one direction to another. + // // TODO + // // TODO - If no polygon selected, stop. + // // TODO - If Quad, assume verts sorted CCW from BL + // // TODO - if not one-way, make one-way poly from 01 to 23 + // // TODO - if one-way from 01 to 23, make one-way from 12 to 03 + // // TODO - if one-way from 12 to 03, make one-way from 23 to 01 + // // TODO - if one-way from 12 to 03, make one-way from 03 to 12 + // // TODO - if one-way from 03 to 12, make normal polygon + // // TODO - If tri, should I support one-way? (probably not, tbh) + // // TODO + // return TRUE; case "nav_place_traversal": cl_navmesh_editor_place_traversal(); return TRUE; diff --git a/source/server/ai/chase_ai.qc b/source/server/ai/chase_ai.qc index 6f54469..7122cc6 100644 --- a/source/server/ai/chase_ai.qc +++ b/source/server/ai/chase_ai.qc @@ -1,19 +1,24 @@ - - - -// FIXME - This framegroup nonsense is causing chaos... the struct is too large apparently... what can I cut out? - - -void init_framegroup( float start_frame, float end_frame, float duration, - void(entity) frame_callback, - void(entity) think_callback, - __out framegroup fg) { - fg.start_frame = start_frame; - fg.end_frame = end_frame; - fg.duration = duration; - fg.frame_callback = frame_callback; - fg.think_callback = think_callback; -} +DEFINE_ANIM(zombie_idle, 1,2,3,4,5,6,7,8,9,10,11,12,13); // Defines: get_anim_frame_zombie_idle, get_anim_length_zombie_idle +DEFINE_ANIM(zombie_rise, 14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37); // Defines: get_anim_frame_zombie_rise, get_anim_length_zombie_rise +DEFINE_ANIM(zombie_walk1, 38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53); // Defines: get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1 +DEFINE_ANIM(zombie_walk2, 54,55,56,57,58,59,60,61,62,63,64,65,66,67); // Defines: get_anim_frame_zombie_walk2, get_anim_length_zombie_walk2 +DEFINE_ANIM(zombie_walk3, 68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83); // Defines: get_anim_frame_zombie_walk3, get_anim_length_zombie_walk3 +DEFINE_ANIM(zombie_jog1, 84,85,86,87,88,89,90,91,92); // Defines: get_anim_frame_zombie_jog1, get_anim_length_zombie_jog1 +DEFINE_ANIM(zombie_run1, 93,94,95,96,97,98,99,100,101,102); // Defines: get_anim_frame_zombie_run1, get_anim_length_zombie_run1 +DEFINE_ANIM(zombie_attack1, 103,104,105,106,107); // Defines: get_anim_frame_zombie_attack1, get_anim_length_zombie_attack1 +DEFINE_ANIM(zombie_attack2, 108,109,110,111,112,113); // Defines: get_anim_frame_zombie_attack2, get_anim_length_zombie_attack2 +DEFINE_ANIM(zombie_window_rip_board1, 182,183,184,185,186,187,188,189,190,191,192); // Defines: get_anim_frame_zombie_window_rip_board1, get_anim_length_zombie_window_rip_board1 +DEFINE_ANIM(zombie_window_rip_board2, 192,193,194,195,196,197,198,199,200,201,202); // Defines: get_anim_frame_zombie_window_rip_board2, get_anim_length_zombie_window_rip_board2 +DEFINE_ANIM(zombie_window_attack, 202,203,204,205,206,207,208,209,210,211); // Defines: get_anim_frame_zombie_window_attack, get_anim_length_zombie_window_attack +DEFINE_ANIM(zombie_window_hop, 114,115,116,117,118,119,120,121,122,123); // Defines: get_anim_frame_zombie_window_hop, get_anim_length_zombie_window_hop +DEFINE_ANIM(zombie_die1, 124,125,126,127,128,129,130,131,132,133,134); // Defines: get_anim_frame_zombie_die1, get_anim_length_zombie_die1 +DEFINE_ANIM(zombie_die2, 135,136,137,138,139); // Defines: get_anim_frame_zombie_die2, get_anim_length_zombie_die2 +DEFINE_ANIM(zombie_die3, 140,141,142,143,144,145,146,147,148,149); // Defines: get_anim_frame_zombie_die3, get_anim_length_zombie_die3 +DEFINE_ANIM(zombie_die_wunder, 212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228); // Defines: get_anim_frame_zombie_die_wunder, get_anim_length_zombie_die_wunder +DEFINE_ANIM(zombie_fall, 150,151,152,153); // Defines: get_anim_frame_zombie_fall, get_anim_length_zombie_fall +DEFINE_ANIM(zombie_land, 154,155,156,157,158,159,160); // Defines: get_anim_frame_zombie_land, get_anim_length_zombie_land +DEFINE_ANIM(zombie_jump, 161,162,163,164,165,166,167); // Defines: get_anim_frame_zombie_jump, get_anim_length_zombie_jump +DEFINE_ANIM(zombie_climb, 168,169,170,171,172,173,174,175,176,177,178,179,180,181,182); // Defines: get_anim_frame_zombie_climb, get_anim_length_zombie_climb @@ -22,140 +27,73 @@ 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; }; 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.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"); + // ------------------------------------------------------------------------ + // Animation logic + // ------------------------------------------------------------------------ + float cur_anim_prev_frame_idx = floor(this.cur_anim_frame_idx); - // TODO - round frame to nearest frame? + // Check if we're updating the current animation being played + if(this.cur_anim_get_frame_func != SUB_Null) { + this.cur_anim_frame_idx = (time - this.cur_anim_start_time) / this.cur_anim_frametime; - // 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"); + // If at the final frame, we're done + if(this.cur_anim_frame_idx >= this.cur_anim_length) { + if(this.cur_anim_stop_type == ANIM_STOP_TYPE_LOOP) { + this.cur_anim_frame_idx = this.cur_anim_frame_idx % this.cur_anim_length; + } + else if(this.cur_anim_stop_type == ANIM_STOP_TYPE_NEXT_ANIM) { + this.play_anim( + this.next_anim_get_frame_func, + this.next_anim_length, + this.next_anim_stop_type); + this.cur_anim_frametime = this.next_anim_frametime; + // We started a new animation, play the frame callback + cur_anim_prev_frame_idx = -1; + } + // Otherwise, treat it as ANIM_STOP_TYPE_STOP + else { + this.cur_anim_frame_idx = min(this.cur_anim_frame_idx, this.cur_anim_length - 1); + this.cur_anim_get_frame_func = (float(float)) SUB_Null; + this.cur_anim_length = 0; } } - // 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; - } + + // If we still have an animation: + if(this.cur_anim_get_frame_func != SUB_Null) { + // NOTE - We can control frame interpolation using the fractional + // NOTE portion of `cur_anim_frame_idx`, in case we ever have + // NOTE better control over MDL frame interpolation. + this.cur_anim_frame_idx = floor(this.cur_anim_frame_idx); + this.frame = this.cur_anim_get_frame_func(this.cur_anim_frame_idx); + + if(floor(this.cur_anim_frame_idx) != cur_anim_prev_frame_idx) { + this.frame_callback(); } } - // Callback may have assigned new framegroup - // if(this.cur_fg_idx == cur_fg_idx) { - this.cur_fg_think_callback(this); - // } + // print("Frame_idx: ", ftos(this.cur_anim_frame_idx), " Actual frame: ", ftos(this.frame), "\n"); } + // ------------------------------------------------------------------------ + + this.nextthink = time + this.think_delta_time; -}; - -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"); + this.think_callback(); }; -// Computes the length of a vector ignoring the Z-component -float (vector ofs) vlen_xy = { - return sqrt(ofs.x * ofs.x + ofs.y * ofs.y); -} -void (float dist) AI_Chase::do_walk_to_goal = { - if(dist == 0) { + + + + + +void (float move_speed) AI_Chase::do_walk_to_goal = { + if(move_speed == 0) { return; } @@ -179,7 +117,7 @@ void (float dist) AI_Chase::do_walk_to_goal = { // TOOD - If close, continue to next one... // TODO - Also make sure we're "close enough" in Z... - if(vlen_xy(this.origin - goal_pos) < 20) { + if(vlen_xy(this.origin - goal_pos) < 5) { print("Distance from target: ", ftos(vlen_xy(this.origin - goal_pos)), ", Cur point: ", ftos(this.pathfind_cur_point_idx), "\n"); print("Current traversal: ", ftos(res->point_path_traversals[this.pathfind_cur_point_idx]),"\n"); // If this point path is a traversal, teleport to traversal end spot @@ -193,23 +131,21 @@ void (float dist) AI_Chase::do_walk_to_goal = { } } else { - dist = 0; + move_speed = 0; // TODO - Idle animation? } #endif // PC - - - this.ideal_yaw = vectoyaw(goal_pos - this.origin); ChangeYaw(); vector new_velocity; + float walk_dist = move_speed * this.think_delta_time; float dist_to_goal = vlen(goal_pos - this.origin); - if(dist > dist_to_goal) { - dist = dist_to_goal; + if(walk_dist > dist_to_goal) { + move_speed = dist_to_goal / this.think_delta_time; } - new_velocity = normalize(goal_pos - this.origin) * dist; + new_velocity = normalize(goal_pos - this.origin) * move_speed; new_velocity_z = this.velocity_z; this.velocity = new_velocity; }; @@ -225,28 +161,11 @@ void (float dist) AI_Chase::do_walk_to_goal = { -framegroup fg_zombie_walk_0; -framegroup fg_zombie_walk_1; -framegroup fg_zombie_walk_2; -framegroup fg_zombie_jog_0; -framegroup fg_zombie_run_0; -// framegroup fg_zombie_run_1; // TODO - Either add new anims, or make new run FG with different speed? -// framegroup fg_zombie_run_2; // TODO - Either add new anims, or make new run FG with different speed? -framegroup fg_zombie_idle_0; -framegroup fg_zombie_attack_0; -framegroup fg_zombie_attack_1; -framegroup fg_zombie_die_0; -framegroup fg_zombie_die_1; -framegroup fg_zombie_die_2; - - - float get_zombie_walk_is_footstep(float frame) { - frame = floor(frame); - switch(frame) { + switch(floor(frame)) { // ------------ Zombie Walk 1 -------------- case 39: // Right foot case 44: // Left foot @@ -259,49 +178,45 @@ float get_zombie_walk_is_footstep(float frame) { } float get_zombie_walk_dist_for_frame(float frame) { - frame = floor(frame); - switch(frame) { + switch(floor(frame)) { // ------------ Zombie Walk 1 -------------- - case 37: - return 8; case 38: + return 8; case 39: case 40: case 41: case 42: - return 3.5; case 43: - return 8.8; + return 3.5; case 44: - return 9; + return 8.8; case 45: + return 9; case 46: - return 4; case 47: - return 7.8; + return 4; case 48: - return 5.2; + return 7.8; case 49: - return 2.4; + return 5.2; case 50: - return 2.8; + return 2.4; case 51: - return 6.5; + return 2.8; case 52: + return 6.5; + case 53: return 7.7; default: return 0.0; } } -// Called at the first frame of the current animation -void zombie_walk_start_callback(entity ent) { - // print("start called\n"); -}; + // Called once per animation frame: -void zombie_walk_frame_callback(entity ent) { - AI_Zombie zombie_ent = (AI_Zombie) ent; +void zombie_walk_frame_callback() { + AI_Zombie zombie_ent = (AI_Zombie) self; // print("frame called\n"); // print("frame called. At frame: "); // print(ftos(ent.frame)); @@ -311,7 +226,7 @@ void zombie_walk_frame_callback(entity ent) { // // print(ftos(zombie_ent.cur_fg_frame_callback_is_end_frame)); // print("\n"); - if(get_zombie_walk_is_footstep(ent.frame)) { + if(get_zombie_walk_is_footstep(zombie_ent.frame)) { if(random() < 0.5) { sound(self, 5, "sounds/zombie/s0.wav", 1, ATTN_NORM); } @@ -320,72 +235,36 @@ void zombie_walk_frame_callback(entity ent) { } } - if(zombie_ent.cur_fg_frame_callback_is_end_frame) { - // print("At final frame, restarting walk\n"); - zombie_ent.fg_walk(); - } - - // TODO - If at final frame, call fg_walk again... - // AI_Zombie zombie_ent = (AI_Zombie) ent; - // zombie_ent.fg_walk(); - // ent.fg_walk(); + // Ideal animation velocity: + // float dist_per_frame = get_zombie_walk_dist_for_frame(zombie_ent.frame); + // float speed = dist_per_frame / zombie_ent.cur_anim_frametime; + // // Convert that to velocity at think_time: + // zombie_ent.do_walk_to_goal(speed); }; // Called once per ent.think invocation -void zombie_walk_think_callback(entity ent) { - // print("Think called\n"); +void zombie_walk_think_callback() { + print("Think called\n"); // print("Think called for frame: "); // print(ftos(ent.frame)); // print("\n"); - AI_Zombie zombie_ent = (AI_Zombie) ent; - // zombie_ent.fg_walk(); + AI_Zombie zombie_ent = (AI_Zombie) self; // "::classname" denotes the global ".string classname" field. - entity player_ent = find( world, ::classname, "player"); + entity player_ent = find( world, classname, "player"); if(player_ent != world) { zombie_ent.path_target = player_ent; - ent.enemy = player_ent; + zombie_ent.enemy = player_ent; } - - float n_anim_frames = fabs(zombie_ent.cur_fg_end_frame - zombie_ent.cur_fg_start_frame); - // float dist_per_frame = 5.23125; // qu + // // Ideal animation velocity: float dist_per_frame = get_zombie_walk_dist_for_frame(zombie_ent.frame); - float time_per_frame = zombie_ent.cur_fg_duration / n_anim_frames; - float speed = dist_per_frame / time_per_frame; + float speed = dist_per_frame / zombie_ent.cur_anim_frametime; zombie_ent.do_walk_to_goal(speed); }; -void create_framegroups() { - // walk0 37 52 - // walk1 53 66 - // walk3 67 82 - // jog0 116 129 - // run0 78 85 - // idle0 0 12 - // attack0 86 90 - // attack1 91 96 - // death0 123 133 - // death1 134 138 - // death2 139 148 - - // In blender, it's frames 38 to 53, inclusive - init_framegroup( 38, 53, 1.0, zombie_walk_frame_callback, zombie_walk_think_callback, fg_zombie_walk_0); - 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); -}; - - @@ -427,7 +306,7 @@ void(vector org) AI_Zombie::init = { // --------------------------------- this.think_delta_time = 0.1; // 10x per second this.nextthink = time + this.think_delta_time; - this.cur_fg_start_time = -1; + // this.cur_fg_start_time = -1; } @@ -437,116 +316,93 @@ void(vector org) AI_Zombie::init = { // } -void () AI_Zombie::fg_walk = { - this.set_framegroup(fg_zombie_walk_0); - this.set_framegroup_duration(fg_zombie_walk_0.duration * (1.0 + 3.0 * random())); - // print("Set anim duration to: "); - // print(ftos(this.cur_fg_duration)); - // print("\n"); -} -void () AI_Zombie::fg_die = { - // TODO - Execute -- zombie_die_0 -} -void () AI_Zombie::fg_attack = { - // TODO - Execute an attack framegroup -} -void () AI_Zombie::fg_idle = { - // TODO - Execute the idle framegroup -} +// void () AI_Zombie::traverse = { +// this.traversal_idx; // Can't use pointers... but can use index! +// this.traversal_state; +// this.traversal_substate; + +// // TODO - Check traversal type, check if this class knows how to perform it. + +// float traversal_idx = 0; +// // TODO - Once we know traversal type, the AI_Zombie class will know which think_callback to use +// // TODO - Set up a test scene on the map, set up a test traversal, have dummy zombie play the test traversal + + +// this.traversal_callback = zombie_traversal_hop_barricade_think; +// void (entity ent) zombie_hop_barricade_traveral_think = { +// AI_Zombie zombie_ent = (AI_Zombie) ent; +// // TODO - need to initialize traversal state and traversal substate +// // Initialize state +// if(ent.traversal_state < 0) { +// ent.traversal_state = 0; +// ent.traversal_substate = 0; +// } + +// // State 0 -- Start hopping, start playing anim, lerp towards +// // TODO - How to adjust animation speed scale? +// // TODO - How best to control animation from here? +// if(ent.traversal_state == 0) { +// // TODO - Somehow start playing animation? + +// } +// // ... +// // On final state, clear traversal +// else { +// ent.traversal_state = -1; +// // TODO - Revert to zombie original behavior, fg_walk? +// zombie_ent.fg_walk(); +// } +// }; + + +// // TODO - Implement think logic for hop barricade traversal +// // Each traversal has a different think function implementation... +// // When I call zombie.traverse(), I need some way of telling the zombie which barricade to traverse +// // Maybe traverse has a traversal_idx as its argument? +// // zombie_ent.traverse(traversal_idx); + +// // That tells the zombie to perform this traversal +// // And the traversal think function will be stored in the struct + + + +// #define MYFUNC(DUMMY, FN, I) int FN(void) { return I; } +// #define GENFUNCS(...) \ +// P99_FOR(, P99_NARG(__VA_ARGS__), P00_IGN, MYFUNC, __VA_ARGS__) \ +// int (*function_table)(void)[] = { __VA_ARGS__ } + +// GENFUNCS(toto, hui, gogo); + + +void() test_frame_callback = { + // print("Frame Callback: ", self.classname, "\n"); + AI_Zombie ent = self; + print("Frame Callback. Frame: ", ftos(ent.frame), " anim frame_idx: ", ftos(ent.cur_anim_frame_idx), "\n"); +} void() test_new_ent = { - create_framegroups(); - makevectors(self.v_angle); - // AI_Chase test_ent = spawn(AI_Chase); AI_Zombie zombie = spawn(AI_Zombie); zombie.init(self.origin + v_forward * 100); - zombie.fg_walk(); + // TODO - If riser, Immediately set frmae to below ground frame + zombie.frame = get_anim_frame_zombie_rise(0); + zombie.play_anim(get_anim_frame_zombie_rise, get_anim_length_zombie_rise(), ANIM_STOP_TYPE_NEXT_ANIM); + zombie.queue_anim(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_LOOP); + zombie.frame_callback = zombie_walk_frame_callback; + zombie.think_callback = zombie_walk_think_callback; + // zombie.play_anim(get_anim_frame_zombie_walk1, get_anim_length_zombie_walk1(), ANIM_STOP_TYPE_NEXT_ANIM); + // zombie.queue_anim(get_anim_frame_zombie_rise, get_anim_length_zombie_rise(), ANIM_STOP_TYPE_LOOP); - - - - - - // somefunc(); - // framegroup_registry zombie_th_reg; - // // framegroup_registry crawler_th_reg; - // // framegroup_registry dog_th_reg; - - // framegroup zombie_attack_0; - // // framegroup zombie_attack_1; - // // framegroup zombie_idle_0; - // // framegroup zombie_die_0; - // // framegroup zombie_die_1; - // // framegroup zombie_die_2; - // // framegroup zombie_walk_0; - // // framegroup zombie_walk_1; - // // framegroup zombie_walk_2; - // // framegroup zombie_jog_0; - // // framegroup zombie_run_1; - // // framegroup zombie_run_2; - // // framegroup zombie_run_3; - - - - // create_framegroup( zombie_attack_0, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_attack_1, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_idle_0, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_die_0, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_die_1, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_die_2, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_walk_0, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_walk_1, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_walk_2, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_jog_0, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_run_1, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_run_2, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - // // create_framegroup( zombie_run_3, 0, 10, 5.0, SUB_Null, SUB_Null, SUB_Null); - - - // // zombie_th_reg.fg_attack.num_framegroups = 2; - - // print(strcat("Num before:",ftos(zombie_th_reg.fg_attack.num_framegroups),"\n")); - // register_framegroup( zombie_th_reg.fg_attack, zombie_attack_0); - // print(strcat("Num after:",ftos(zombie_th_reg.fg_attack.num_framegroups),"\n")); - - - - // zombie_th_reg.fg_attack = register_framegroup( zombie_th_reg.fg_attack, zombie_attack_0); - // print(strcat("Num before:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // zombie_th_reg.fg_attack = register_framegroup( zombie_th_reg.fg_attack, zombie_attack_0); - // print(strcat("Num after:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // print(strcat("Num before:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // zombie_th_reg.fg_attack = register_framegroup( zombie_th_reg.fg_attack, zombie_attack_0); - // print(strcat("Num after:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // print(strcat("Num before:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // zombie_th_reg.fg_attack = register_framegroup( zombie_th_reg.fg_attack, zombie_attack_0); - // print(strcat("Num after:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // print(strcat("Num before:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // zombie_th_reg.fg_attack = register_framegroup( zombie_th_reg.fg_attack, zombie_attack_0); - // print(strcat("Num after:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // print(strcat("Num before:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // zombie_th_reg.fg_attack = register_framegroup( zombie_th_reg.fg_attack, zombie_attack_0); - // print(strcat("Num after:",ftos(zombie_th_reg.fg_attack.num_framegroups))); - // print(ftos(result)); - // print("\n"); - // result = register_framegroup( &(zombie_th_reg.fg_attack), &zombie_attack_1); - // print(ftos(result)); - // print("\n"); - // result = register_framegroup( &(zombie_th_reg.fg_idle), &zombie_idle_0); - // print(ftos(result)); - // print("\n"); - // result = register_framegroup( &(zombie_th_reg.fg_die), &zombie_die_0); - // print(ftos(result)); - // print("\n"); - // result = register_framegroup( &(zombie_th_reg.fg_die), &zombie_die_1); - // print(ftos(result)); - // print("\n"); + // TODO - Set frametime to something random? + zombie.cur_anim_frametime = 0.1; + // zombie.next_anim_frametime = 0.05 + 0.1 * random(); + zombie.next_anim_frametime = 0.13; + // zombie.next_anim_frametime = 0.1; }; diff --git a/source/server/ai/navmesh_core.qc b/source/server/ai/navmesh_core.qc index 1b18113..97556d1 100644 --- a/source/server/ai/navmesh_core.qc +++ b/source/server/ai/navmesh_core.qc @@ -80,15 +80,14 @@ void sv_load_navmesh_data() { return; } - //First line contains navmesh file semver + // First line contains navmesh file semver string nav_file_version = fgets(file); - //Next line contains vertex count - string line = fgets(file); - float vert_count = stof(line); + // Next line contains vertex count + float vert_count = stof(fgets(file)); if(vert_count > sv_navmesh_verts.length) { - print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , line, " > ", ftos(sv_navmesh_verts.length),").\n"); + print("Error: navmesh file \"",filepath,"\" has an invalid vert count. (" , vert_count, " > ", ftos(sv_navmesh_verts.length),").\n"); fclose(file); return; } @@ -96,21 +95,15 @@ void sv_load_navmesh_data() { //The navmesh appears to be valid (we shouldn't need to clear the current navmesh values) sv_navmesh_vert_count = vert_count; - //Temp vector to assign component-wise - vector temp; - //Reading all of the vertex positions for(float i = 0; i < sv_navmesh_vert_count; i++) { - line = fgets(file); - temp = stov(line); - sv_navmesh_verts[i].pos.x = temp.x; - sv_navmesh_verts[i].pos.y = temp.y; - sv_navmesh_verts[i].pos.z = temp.z; + sv_navmesh_verts[i].pos = stov(fgets(file)); } //Next line contains the number of polygons sv_navmesh_poly_count = stof(fgets(file)); + print("Polygon count: ", ftos(sv_navmesh_poly_count), "\n"); //The next lines are each polygon for(float i = 0; i < sv_navmesh_poly_count; i++) { @@ -122,10 +115,7 @@ void sv_load_navmesh_data() { sv_navmesh_polies[i].verts[2] = stof(fgets(file)); sv_navmesh_polies[i].verts[3] = stof(fgets(file)); // 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; + sv_navmesh_polies[i].center = stov(fgets(file)); // Get link count sv_navmesh_polies[i].connected_polies_count = stof(fgets(file)); // Get links @@ -151,7 +141,7 @@ void sv_load_navmesh_data() { // 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) { + if(nav_file_version != "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++) { @@ -160,8 +150,11 @@ void sv_load_navmesh_data() { } //Get polygon doortarget sv_navmesh_polies[i].doortarget = fgets(file); - //Get entrance edge - sv_navmesh_polies[i].entrance_edge = stoi(fgets(file)); + // If v0.0.0, throw away entrance edge (legacy feature not supported) + if(nav_file_version == "0.0.0") { + // sv_navmesh_polies[i].entrance_edge = stoi(fgets(file)); + fgets(file); + } } // ----------------------------------------------------------------------- @@ -169,27 +162,15 @@ void sv_load_navmesh_data() { // ----------------------------------------------------------------------- // 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) { + if(nav_file_version != "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].start_pos = stov(fgets(file)); + sv_navmesh_traversals[i].midpoint_pos = stov(fgets(file)); + sv_navmesh_traversals[i].end_pos = stov(fgets(file)); 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)); @@ -272,22 +253,19 @@ float sv_navmesh_dist_to_poly(vector pos, float poly_index) //Returns polygon that pos is inside of. //If we are in no polygon, returns a polygon whose edge we are sufficiently close to (might be in but are not due to a small error) //Otherwise, returns -1 -float sv_navmesh_get_containing_poly(vector pos) -{ +float sv_navmesh_get_containing_poly(vector pos) { //Get nearest polygon, and check if pos is in that polygon float closest_poly = -1; float closest_poly_dist = 1000000; //In reality, there's no need to try ALL polygons, the one we are in should be within the nearest 10 or so, but checking all to be safe - for(float i = 0; i < sv_navmesh_poly_count; i++) - { + for(float i = 0; i < sv_navmesh_poly_count; i++) { //Check if we are sufficiently close enough to polygon i in the z-axis //Current method of checking: if pos is within 30 qu of polygon z-axis bounds float poly_min_z = sv_navmesh_verts[sv_navmesh_polies[i].verts[0]].pos.z; float poly_max_z = poly_min_z; - for(float j = 0; j < sv_navmesh_polies[i].vert_count; j++) - { + for(float j = 0; j < sv_navmesh_polies[i].vert_count; j++) { float vert_z = sv_navmesh_verts[sv_navmesh_polies[i].verts[j]].pos.z; poly_min_z = min(poly_min_z, vert_z); poly_max_z = max(poly_max_z, vert_z); @@ -295,15 +273,12 @@ float sv_navmesh_get_containing_poly(vector pos) // If we NOT between [min - 30, max + 30], skip this polygon // if(fabs(sv_navmesh_polies[0].center.z - pos.z) >= 30) - if(pos.z < poly_min_z - 30 || pos.z > poly_max_z + 30) - { + if(pos.z < poly_min_z - 30 || pos.z > poly_max_z + 30) { continue; } - //Check if we are in polygon i - if(sv_navmesh_is_inside_poly(pos,i)) - { + if(sv_navmesh_is_inside_poly(pos,i)) { // NOTE - Because is_inside_poly only checks if we are in it on X & Y axes, // NOTE we may have found a polygon on a floor below us or above us. // NOTE However, this may not be an issue. A polygon on a floor below @@ -317,25 +292,17 @@ float sv_navmesh_get_containing_poly(vector pos) //If we are not in polygon i, check if we arse very close to one of its edges float dist = sv_navmesh_dist_to_poly(pos,i); - - if(dist >= 0) - { - if(dist < closest_poly_dist) - { - closest_poly = i; - closest_poly_dist = dist; - } + if(dist >= 0 && dist < closest_poly_dist) { + closest_poly = i; + closest_poly_dist = dist; } - } //============================================ - /*if(closest_poly == -1) - { + /*if(closest_poly == -1) { print("Ent pos is not in or near any polygons.\n"); } - else - { + else { print("Ent pos is near but not in poly: closest_poly.\n"); }*/ diff --git a/source/server/ai/pathfind_core.qc b/source/server/ai/pathfind_core.qc index 59dce00..d1e70e7 100644 --- a/source/server/ai/pathfind_core.qc +++ b/source/server/ai/pathfind_core.qc @@ -971,11 +971,22 @@ float sv_navmesh_pathfind_start(float start_poly, float goal_poly, vector start_ // print(ftos(neighbor_poly)); // print(".\n"); + // ---------------------------------------------------------------- // Door check // ---------------------------------------------------------------- // NOTE - If polygon's door hasn't been opened, don't consider it. - // NOTE - This check isn't done in the navmesh editor. + // If this polygon references a door: + if(sv_navmesh_polies[neighbor_poly].doortarget != "") { + entity door; + door = find(world, wayTarget, sv_navmesh_polies[neighbor_poly].doortarget); + if(door != world) { + // If the referenced door is closed, don't consider this polygon. + if(door.state == STATE_BOTTOM) { + continue; + } + } + } // ---------------------------------------------------------------- diff --git a/source/server/defs/custom.qc b/source/server/defs/custom.qc index 5408d72..875688e 100644 --- a/source/server/defs/custom.qc +++ b/source/server/defs/custom.qc @@ -485,20 +485,7 @@ navmesh_pathfind_result *sv_zombie_pathfind_result_aux; // Used as secondary, so //================================================================= //========================== 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... + // -----------–-----------–-----------–-----------–-----------–-----------–---- // Animation Definitions @@ -535,54 +522,110 @@ var struct framegroup { float get_anim_length_##ANIM_NAME## () {float frames[] = {__VA_ARGS__}; return frames.length;}; // -----------–-----------–-----------–-----------–-----------–-----------–---- - // 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. +enum anim_stop_type:float { + ANIM_STOP_TYPE_STOP, // Animation stops and freezes at final frame. + ANIM_STOP_TYPE_LOOP, // Animation starts again from the first frame. + ANIM_STOP_TYPE_NEXT_ANIM, // Another animation is played when this one finishes. }; + 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();` + + // Regardless of what animation we're playing (or not playing), we have a think callback + virtual void() think_callback = SUB_Null; + // We also have a frame callback that's called each time we reach a new animation frame. + virtual void() frame_callback = SUB_Null; + // ------------------------------ - // Current Framegroup value vars + // Animation variables // ------------------------------ - 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 + // --- Current animation vars --- + virtual float(float) cur_anim_get_frame_func = (float(float)) SUB_Null; // Gets frame number from frame index. Assign to `get_anim_frame_{ANIM_NAME}`, as generated by DEFINE_ANIM + float cur_anim_length; // The length of the current animation. Assign to `get_anim_length_{ANIM_NAME}()`, as generated by DEFINE_ANIM) + float cur_anim_frametime; // Time (in seconds) between frames, 0.1 for 10FPS + anim_stop_type cur_anim_stop_type; // ANIM_STOP_TYPE_STOP, ANIM_STOP_TYPE_LOOP, or ANIM_STOP_TYPE_NEXT_ANIM + // --- Next / Queued animation vars --- + virtual float(float) next_anim_get_frame_func = (float(float)) SUB_Null; // Gets frame number from frame index. Assign to `get_anim_frame_{ANIM_NAME}`, as generated by DEFINE_ANIM + float next_anim_length; // The length of the current animation. Assign to `get_anim_length_{ANIM_NAME}()`, as generated by DEFINE_ANIM) + float next_anim_frametime; // Time (in seconds) between frames, 0.1 for 10FPS + anim_stop_type next_anim_stop_type; // ANIM_STOP_TYPE_STOP or ANIM_STOP_TYPE_LOOP + // --- Current animation state vars --- + float cur_anim_start_time; + float cur_anim_frame_idx; // Counter from 0 to `cur_anim_n_frames` // ------------------------------ + float pathfind_result_idx; // TODO - Need to increment this on instantiation float pathfind_cur_point_idx; // 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 = {}; + // Stop what you're doing and play this animation immediately. + virtual void(float(float) anim_frame_func, float anim_length, anim_stop_type stop_type) play_anim = { + // Assign Animation Vars + this.cur_anim_get_frame_func = anim_frame_func; + this.cur_anim_length = anim_length; + this.cur_anim_stop_type = stop_type; + this.cur_anim_frametime = 0.1; // TODO - Make this a param? + this.cur_anim_frametime = 0.2; // TODO - Make this a param? + + // Reset Animation state Vars + this.cur_anim_start_time = time; + this.cur_anim_frame_idx = 0; + + // Adjust think_delta_time if needed + // If we want to play the animation faster than 10FPS, call logic ticks faster so the animation will be played + if(this.cur_anim_frametime < 0.10) { + this.think_delta_time = this.cur_anim_frametime * 0.5; + } + else { + this.think_delta_time = 0.1; + } + this.think_delta_time = this.cur_anim_frametime * 0.5; + this.think_delta_time = this.cur_anim_frametime * 0.1; + + + + // TODO - Do some math and figure out if we need to + // TODO - Thinktime should be adjusted so that + // TOOD - this.thinktime = min(0.1, this.cur_anim_frametime * 0.05); + + // TODO - Reset FPS to 10 FPS, (frametime = 0.1) + // TODO - Add a function that allows this to play slower or faster? + }; + + // Queue up another animation to play when the current one finishes + virtual void(float(float) anim_frame_func, float anim_length, anim_stop_type stop_type) queue_anim { + // This isn't allowed, we can't queue more than one animation. + // Queued animation stop type must be: ANIM_STOP_TYPE_STOP, or ANIM_STOP_TYPE_LOOP + if(stop_type == ANIM_STOP_TYPE_NEXT_ANIM) { + return; + } + // Indicate that we have a next animation queued up the next time the current animation finishes. + this.cur_anim_stop_type = ANIM_STOP_TYPE_NEXT_ANIM; + + this.next_anim_get_frame_func = anim_frame_func; + this.next_anim_length = anim_length; + this.next_anim_stop_type = stop_type; + this.next_anim_frametime = 0.1; + this.next_anim_frametime = 0.2; // FIXME - Parameterize this? + }; + + + + // virtual void () fg_die = {}; + // virtual void () fg_walk = {}; + // virtual void () fg_attack = {}; + // virtual void () fg_idle = {}; virtual void (float dist) do_walk_to_goal; }; @@ -596,12 +639,21 @@ class AI_Zombie : AI_Chase { // 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; + // virtual void () fg_die; + // virtual void () fg_walk; + // virtual void () fg_attack; + // virtual void () fg_idle; + + // static void () setup_frames = { + // // this.zombie_idle_frames = {1,2,3,4,5,6,7,8,9,10,11,12,13}; + // // this.cur_frames = zombie_idle_frames; + // // FIXME - I can't figure out a way to assign an array... + // // Even if I convert an animation to a struct, I can't do arbitrary-length arrays... + // // I also can't fill them in in this convenient syntax... it's ridiculous. + // }; }; +// AI_Zombie.zombie_idle_frames = {1,2,3,4,5,6,7,8,9,10,11,12,13}; //================================================================= diff --git a/source/server/entities/doors.qc b/source/server/entities/doors.qc index de612f7..b867905 100644 --- a/source/server/entities/doors.qc +++ b/source/server/entities/doors.qc @@ -348,7 +348,8 @@ void() door_trigger_touch = #ifdef PC if(cvar("navmesh_edit_mode")) { - if(strcmp(cvar_string("nav_editor_active_door"), self.owner.wayTarget) != 0) { + // if(strcmp(cvar_string("nav_editor_active_door"), self.owner.wayTarget) != 0) { + if(cvar_string("nav_editor_active_door") != self.owner.wayTarget) { bprint(PRINT_HIGH, "Current Door for navmesh editor set to \""); bprint(PRINT_HIGH, self.owner.wayTarget); // naiveil (FIXME) bprint(PRINT_HIGH, "\"\n"); diff --git a/source/server/utilities/math.qc b/source/server/utilities/math.qc index 23842a6..817cc42 100644 --- a/source/server/utilities/math.qc +++ b/source/server/utilities/math.qc @@ -107,6 +107,23 @@ float(float a, float b, float mix) unclamped_lerp = return (b * mix + a * ( 1 - mix ) ); } +#ifndef PC // FTE has built-ins for min / max +float(float a, float b) min = +{ + return a < b ? a : b; +} + +float(float a, float b) max = +{ + return a > b ? a : b; +} +#endif // PC + +// Computes the length of a vector ignoring the Z-component +float (vector ofs) vlen_xy = { + return vlen([ofs.x, ofs.y, 0]); +} + vector(vector a, vector b, float mix) lerpVector = { diff --git a/source/shared/navmesh_defines.qc b/source/shared/navmesh_defines.qc index e7949bb..dbdab5a 100644 --- a/source/shared/navmesh_defines.qc +++ b/source/shared/navmesh_defines.qc @@ -39,7 +39,7 @@ struct navmesh_poly { float vert_count; string doortarget; // "" or matches the .wayTarget field of a door entity. Polygon is only used when door is open (i.e. door.state == STATE_BOTTOM) // TODO - Remove this, will be replaced by traversals - int entrance_edge; // If != -1, specifies which edge index this polygon can be entered from. (0,1,2, or 3) + // int entrance_edge; // If != -1, specifies which edge index this polygon can be entered from. (0,1,2, or 3) float connected_traversals_count; // How many traversals start in this polygon float connected_traversals[NAV_MAX_POLY_TRAVERSALS]; // List of traversals that start in this polygon