Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | pmbaty | 1 | /* |
| 2 | * Portions of this file are copyright Rebirth contributors and licensed as |
||
| 3 | * described in COPYING.txt. |
||
| 4 | * Portions of this file are copyright Parallax Software and licensed |
||
| 5 | * according to the Parallax license below. |
||
| 6 | * See COPYING.txt for license details. |
||
| 7 | |||
| 8 | THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX |
||
| 9 | SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO |
||
| 10 | END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A |
||
| 11 | ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS |
||
| 12 | IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS |
||
| 13 | SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE |
||
| 14 | FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE |
||
| 15 | CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS |
||
| 16 | AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. |
||
| 17 | COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. |
||
| 18 | */ |
||
| 19 | |||
| 20 | /* |
||
| 21 | * |
||
| 22 | * AI path forming stuff. |
||
| 23 | * |
||
| 24 | */ |
||
| 25 | |||
| 26 | #include <stdio.h> // for printf() |
||
| 27 | #include <stdlib.h> // for d_rand() and qsort() |
||
| 28 | #include <string.h> // for memset() |
||
| 29 | |||
| 30 | #include "inferno.h" |
||
| 31 | #include "console.h" |
||
| 32 | #include "3d.h" |
||
| 33 | |||
| 34 | #include "object.h" |
||
| 35 | #include "dxxerror.h" |
||
| 36 | #include "ai.h" |
||
| 37 | #include "robot.h" |
||
| 38 | #include "fvi.h" |
||
| 39 | #include "gameseg.h" |
||
| 40 | #include "physics.h" |
||
| 41 | #include "wall.h" |
||
| 42 | #if DXX_USE_EDITOR |
||
| 43 | #include "editor/editor.h" |
||
| 44 | #include "editor/esegment.h" |
||
| 45 | #endif |
||
| 46 | #include "player.h" |
||
| 47 | #include "fireball.h" |
||
| 48 | #include "game.h" |
||
| 49 | |||
| 50 | #include "compiler-range_for.h" |
||
| 51 | #include "d_enumerate.h" |
||
| 52 | |||
| 53 | // Length in segments of avoidance path |
||
| 54 | #define AVOID_SEG_LENGTH 7 |
||
| 55 | |||
| 56 | #ifdef NDEBUG |
||
| 57 | #define PATH_VALIDATION 0 |
||
| 58 | #else |
||
| 59 | #define PATH_VALIDATION 1 |
||
| 60 | #endif |
||
| 61 | |||
| 62 | namespace dsx { |
||
| 63 | static void ai_path_set_orient_and_vel(object &objp, const vms_vector &goal_point |
||
| 64 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 65 | , player_visibility_state player_visibility, const vms_vector *vec_to_player |
||
| 66 | #endif |
||
| 67 | ); |
||
| 68 | static void maybe_ai_path_garbage_collect(void); |
||
| 69 | static void ai_path_garbage_collect(void); |
||
| 70 | #if PATH_VALIDATION |
||
| 71 | static void validate_all_paths(); |
||
| 72 | static int validate_path(int, point_seg* psegs, uint_fast32_t num_points); |
||
| 73 | #endif |
||
| 74 | } |
||
| 75 | |||
| 76 | // ------------------------------------------------------------------------ |
||
| 77 | static void create_random_xlate(std::array<uint8_t, MAX_SIDES_PER_SEGMENT> &xt) |
||
| 78 | { |
||
| 79 | for (int i = 0; i<MAX_SIDES_PER_SEGMENT; i++) |
||
| 80 | xt[i] = i; |
||
| 81 | |||
| 82 | range_for (auto &i, xt) |
||
| 83 | { |
||
| 84 | uint_fast32_t j = (d_rand()*MAX_SIDES_PER_SEGMENT)/(D_RAND_MAX+1); |
||
| 85 | Assert(j < xt.size()); |
||
| 86 | using std::swap; |
||
| 87 | swap(i, xt[j]); |
||
| 88 | } |
||
| 89 | } |
||
| 90 | |||
| 91 | namespace dsx { |
||
| 92 | |||
| 93 | // ----------------------------------------------------------------------------------------------------------- |
||
| 94 | // Insert the point at the center of the side connecting two segments between the two points. |
||
| 95 | // This is messy because we must insert into the list. The simplest (and not too slow) way to do this is to start |
||
| 96 | // at the end of the list and go backwards. |
||
| 97 | static uint_fast32_t insert_center_points(segment_array &segments, point_seg *psegs, uint_fast32_t count) |
||
| 98 | { |
||
| 99 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 100 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 101 | auto &vcsegptr = segments.vcptr; |
||
| 102 | auto &vcsegptridx = segments.vcptridx; |
||
| 103 | if (count < 2) |
||
| 104 | return count; |
||
| 105 | uint_fast32_t last_point = count - 1; |
||
| 106 | for (uint_fast32_t i = last_point; i; --i) |
||
| 107 | { |
||
| 108 | psegs[2*i] = psegs[i]; |
||
| 109 | const auto &&seg1 = vcsegptr(psegs[i-1].segnum); |
||
| 110 | auto connect_side = find_connect_side(vcsegptridx(psegs[i].segnum), seg1); |
||
| 111 | Assert(connect_side != side_none); // Impossible! These two segments must be connected, they were created by create_path_points (which was created by mk!) |
||
| 112 | if (connect_side == side_none) // Try to blow past the assert, this should at least prevent a hang. |
||
| 113 | connect_side = 0; |
||
| 114 | auto &vcvertptr = Vertices.vcptr; |
||
| 115 | const auto &¢er_point = compute_center_point_on_side(vcvertptr, seg1, connect_side); |
||
| 116 | auto new_point = vm_vec_sub(psegs[i-1].point, center_point); |
||
| 117 | new_point.x /= 16; |
||
| 118 | new_point.y /= 16; |
||
| 119 | new_point.z /= 16; |
||
| 120 | vm_vec_sub(psegs[2*i-1].point, center_point, new_point); |
||
| 121 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 122 | const auto &&segp = segments.imptridx(psegs[2*i].segnum); |
||
| 123 | const auto &&temp_segnum = find_point_seg(LevelSharedSegmentState, psegs[2*i-1].point, segp); |
||
| 124 | if (temp_segnum == segment_none) { |
||
| 125 | psegs[2*i-1].point = center_point; |
||
| 126 | find_point_seg(LevelSharedSegmentState, psegs[2*i-1].point, segp); |
||
| 127 | } |
||
| 128 | #endif |
||
| 129 | |||
| 130 | psegs[2*i-1].segnum = psegs[2*i].segnum; |
||
| 131 | count++; |
||
| 132 | } |
||
| 133 | |||
| 134 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 135 | // Now, remove unnecessary center points. |
||
| 136 | // A center point is unnecessary if it is close to the line between the two adjacent points. |
||
| 137 | // MK, OPTIMIZE! Can get away with about half the math since every vector gets computed twice. |
||
| 138 | for (uint_fast32_t i = 1; i < count - 1; i += 2) |
||
| 139 | { |
||
| 140 | vms_vector temp1, temp2; |
||
| 141 | fix dot; |
||
| 142 | |||
| 143 | dot = vm_vec_dot(vm_vec_sub(temp1, psegs[i].point, psegs[i-1].point), vm_vec_sub(temp2, psegs[i+1].point, psegs[i].point)); |
||
| 144 | |||
| 145 | if (dot * 9/8 > fixmul(vm_vec_mag(temp1), vm_vec_mag(temp2))) |
||
| 146 | psegs[i].segnum = segment_none; |
||
| 147 | |||
| 148 | } |
||
| 149 | |||
| 150 | // Now, scan for points with segnum == -1 |
||
| 151 | auto predicate = [](const point_seg &p) { return p.segnum == segment_none; }; |
||
| 152 | count = std::distance(psegs, std::remove_if(psegs, psegs + count, predicate)); |
||
| 153 | #endif |
||
| 154 | return count; |
||
| 155 | } |
||
| 156 | |||
| 157 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 158 | // ----------------------------------------------------------------------------------------------------------- |
||
| 159 | // Move points halfway to outside of segment. |
||
| 160 | static void move_towards_outside(const d_level_shared_segment_state &LevelSharedSegmentState, point_seg *const psegs, const unsigned num_points, const vmobjptridx_t objp, const create_path_random_flag rand_flag) |
||
| 161 | { |
||
| 162 | int i; |
||
| 163 | std::array<point_seg, 200> new_psegs; |
||
| 164 | assert(num_points < new_psegs.size()); |
||
| 165 | |||
| 166 | auto &Segments = LevelSharedSegmentState.get_segments(); |
||
| 167 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 168 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 169 | auto &vcvertptr = Vertices.vcptr; |
||
| 170 | for (i = 1; i < num_points - 1; ++i) |
||
| 171 | { |
||
| 172 | fix segment_size; |
||
| 173 | segnum_t segnum; |
||
| 174 | vms_vector e; |
||
| 175 | int count; |
||
| 176 | const auto &&temp_segnum = find_point_seg(LevelSharedSegmentState, psegs[i].point, Segments.vcptridx(psegs[i].segnum)); |
||
| 177 | Assert(temp_segnum != segment_none); |
||
| 178 | psegs[i].segnum = temp_segnum; |
||
| 179 | segnum = psegs[i].segnum; |
||
| 180 | |||
| 181 | // I don't think we can use quick version here and this is _very_ rarely called. --MK, 07/03/95 |
||
| 182 | const auto a = vm_vec_normalized_quick(vm_vec_sub(psegs[i].point, psegs[i-1].point)); |
||
| 183 | const auto b = vm_vec_normalized_quick(vm_vec_sub(psegs[i+1].point, psegs[i].point)); |
||
| 184 | const auto c = vm_vec_sub(psegs[i+1].point, psegs[i-1].point); |
||
| 185 | if (abs(vm_vec_dot(a, b)) > 3*F1_0/4 ) { |
||
| 186 | if (abs(a.z) < F1_0/2) { |
||
| 187 | if (rand_flag != create_path_random_flag::nonrandom) |
||
| 188 | { |
||
| 189 | e.x = (d_rand()-16384)/2; |
||
| 190 | e.y = (d_rand()-16384)/2; |
||
| 191 | e.z = abs(e.x) + abs(e.y) + 1; |
||
| 192 | vm_vec_normalize_quick(e); |
||
| 193 | } else { |
||
| 194 | e.x = 0; |
||
| 195 | e.y = 0; |
||
| 196 | e.z = F1_0; |
||
| 197 | } |
||
| 198 | } else { |
||
| 199 | if (rand_flag != create_path_random_flag::nonrandom) |
||
| 200 | { |
||
| 201 | e.y = (d_rand()-16384)/2; |
||
| 202 | e.z = (d_rand()-16384)/2; |
||
| 203 | e.x = abs(e.y) + abs(e.z) + 1; |
||
| 204 | vm_vec_normalize_quick(e); |
||
| 205 | } else { |
||
| 206 | e.x = F1_0; |
||
| 207 | e.y = 0; |
||
| 208 | e.z = 0; |
||
| 209 | } |
||
| 210 | } |
||
| 211 | } else { |
||
| 212 | const auto d = vm_vec_cross(a, b); |
||
| 213 | vm_vec_cross(e, c, d); |
||
| 214 | vm_vec_normalize_quick(e); |
||
| 215 | } |
||
| 216 | |||
| 217 | if (vm_vec_mag_quick(e) < F1_0/2) |
||
| 218 | Int3(); |
||
| 219 | |||
| 220 | const shared_segment &segp = *vcsegptr(segnum); |
||
| 221 | segment_size = vm_vec_dist_quick(vcvertptr(segp.verts[0]), vcvertptr(segp.verts[6])); |
||
| 222 | if (segment_size > F1_0*40) |
||
| 223 | segment_size = F1_0*40; |
||
| 224 | |||
| 225 | auto goal_pos = vm_vec_scale_add(psegs[i].point, e, segment_size/4); |
||
| 226 | |||
| 227 | count = 3; |
||
| 228 | while (count) { |
||
| 229 | fvi_query fq; |
||
| 230 | fvi_info hit_data; |
||
| 231 | int hit_type; |
||
| 232 | |||
| 233 | fq.p0 = &psegs[i].point; |
||
| 234 | fq.startseg = psegs[i].segnum; |
||
| 235 | fq.p1 = &goal_pos; |
||
| 236 | fq.rad = objp->size; |
||
| 237 | fq.thisobjnum = objp; |
||
| 238 | fq.ignore_obj_list.first = nullptr; |
||
| 239 | fq.flags = 0; |
||
| 240 | |||
| 241 | hit_type = find_vector_intersection(fq, hit_data); |
||
| 242 | |||
| 243 | if (hit_type == HIT_NONE) |
||
| 244 | count = 0; |
||
| 245 | else { |
||
| 246 | if ((count == 3) && (hit_type == HIT_BAD_P0)) |
||
| 247 | Int3(); |
||
| 248 | goal_pos.x = (fq.p0->x + hit_data.hit_pnt.x)/2; |
||
| 249 | goal_pos.y = (fq.p0->y + hit_data.hit_pnt.y)/2; |
||
| 250 | goal_pos.z = (fq.p0->z + hit_data.hit_pnt.z)/2; |
||
| 251 | count--; |
||
| 252 | if (count == 0) { // Couldn't move towards outside, that's ok, sometimes things can't be moved. |
||
| 253 | goal_pos = psegs[i].point; |
||
| 254 | } |
||
| 255 | } |
||
| 256 | } |
||
| 257 | |||
| 258 | // Only move towards outside if remained inside segment. |
||
| 259 | const auto &&new_segnum = find_point_seg(LevelSharedSegmentState, goal_pos, Segments.vcptridx(psegs[i].segnum)); |
||
| 260 | if (new_segnum == psegs[i].segnum) { |
||
| 261 | new_psegs[i].point = goal_pos; |
||
| 262 | new_psegs[i].segnum = new_segnum; |
||
| 263 | } else { |
||
| 264 | new_psegs[i].point = psegs[i].point; |
||
| 265 | new_psegs[i].segnum = psegs[i].segnum; |
||
| 266 | } |
||
| 267 | |||
| 268 | } |
||
| 269 | |||
| 270 | for (i = 1; i < num_points - 1; ++i) |
||
| 271 | psegs[i] = new_psegs[i]; |
||
| 272 | } |
||
| 273 | #endif |
||
| 274 | |||
| 275 | |||
| 276 | // ----------------------------------------------------------------------------------------------------------- |
||
| 277 | // Create a path from objp->pos to the center of end_seg. |
||
| 278 | // Return a list of (segment_num, point_locations) at psegs |
||
| 279 | // Return number of points in *num_points. |
||
| 280 | // if max_depth == -1, then there is no maximum depth. |
||
| 281 | // If unable to create path, return -1, else return 0. |
||
| 282 | // If random_flag !0, then introduce randomness into path by looking at sides in random order. This means |
||
| 283 | // that a path between two segments won't always be the same, unless it is unique. |
||
| 284 | // If safety_flag is set, then additional points are added to "make sure" that points are reachable. I would |
||
| 285 | // like to say that it ensures that the object can move between the points, but that would require knowing what |
||
| 286 | // the object is (which isn't passed, right?) and making fvi calls (slow, right?). So, consider it the more_or_less_safe_flag. |
||
| 287 | // If end_seg == -2, then end seg will never be found and this routine will drop out due to depth (probably called by create_n_segment_path). |
||
| 288 | std::pair<create_path_result, unsigned> create_path_points(const vmobjptridx_t objp, const vcsegidx_t start_seg, icsegidx_t end_seg, point_seg_array_t::iterator psegs, const unsigned max_depth, create_path_random_flag random_flag, const create_path_safety_flag safety_flag, icsegidx_t avoid_seg) |
||
| 289 | { |
||
| 290 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 291 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 292 | auto &vmobjptr = Objects.vmptr; |
||
| 293 | #endif |
||
| 294 | segnum_t cur_seg; |
||
| 295 | int qtail = 0, qhead = 0; |
||
| 296 | int i; |
||
| 297 | std::array<seg_seg, MAX_SEGMENTS> seg_queue; |
||
| 298 | int cur_depth; |
||
| 299 | std::array<uint8_t, MAX_SIDES_PER_SEGMENT> random_xlate; |
||
| 300 | DXX_POISON_VAR(random_xlate, 0xcc); |
||
| 301 | point_seg_array_t::iterator original_psegs = psegs; |
||
| 302 | unsigned l_num_points = 0; |
||
| 303 | |||
| 304 | #if PATH_VALIDATION |
||
| 305 | validate_all_paths(); |
||
| 306 | #endif |
||
| 307 | |||
| 308 | if ((objp->type == OBJ_ROBOT) && (objp->ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM)) { |
||
| 309 | random_flag = create_path_random_flag::random; |
||
| 310 | avoid_seg = ConsoleObject->segnum; |
||
| 311 | // Int3(); |
||
| 312 | } |
||
| 313 | |||
| 314 | visited_segment_bitarray_t visited; |
||
| 315 | std::array<uint16_t, MAX_SEGMENTS> depth{}; |
||
| 316 | |||
| 317 | // If there is a segment we're not allowed to visit, mark it. |
||
| 318 | if (avoid_seg != segment_none) { |
||
| 319 | Assert(avoid_seg <= Highest_segment_index); |
||
| 320 | if ((start_seg != avoid_seg) && (end_seg != avoid_seg)) { |
||
| 321 | visited[avoid_seg] = true; |
||
| 322 | } |
||
| 323 | } |
||
| 324 | |||
| 325 | if (random_flag != create_path_random_flag::nonrandom) |
||
| 326 | create_random_xlate(random_xlate); |
||
| 327 | |||
| 328 | cur_seg = start_seg; |
||
| 329 | visited[cur_seg] = true; |
||
| 330 | cur_depth = 0; |
||
| 331 | |||
| 332 | auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); |
||
| 333 | auto &Vertices = LevelSharedVertexState.get_vertices(); |
||
| 334 | auto &vcvertptr = Vertices.vcptr; |
||
| 335 | auto &Walls = LevelUniqueWallSubsystemState.Walls; |
||
| 336 | auto &vcwallptr = Walls.vcptr; |
||
| 337 | while (cur_seg != end_seg) { |
||
| 338 | const auto &&segp = vcsegptr(cur_seg); |
||
| 339 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 340 | if (random_flag != create_path_random_flag::nonrandom) |
||
| 341 | if (d_rand() < 8192) |
||
| 342 | create_random_xlate(random_xlate); |
||
| 343 | #endif |
||
| 344 | |||
| 345 | range_for (const auto &&es, enumerate(random_xlate)) |
||
| 346 | { |
||
| 347 | const unsigned snum = (random_flag != create_path_random_flag::nonrandom) ? es.value : es.idx; |
||
| 348 | |||
| 349 | if (!IS_CHILD(segp->children[snum])) |
||
| 350 | continue; |
||
| 351 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 352 | #define AI_DOOR_OPENABLE_PLAYER_FLAGS |
||
| 353 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 354 | #define AI_DOOR_OPENABLE_PLAYER_FLAGS player_info.powerup_flags, |
||
| 355 | auto &player_info = get_local_plrobj().ctype.player_info; |
||
| 356 | #endif |
||
| 357 | if ((WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, snum) & WID_FLY_FLAG) || ai_door_is_openable(objp, AI_DOOR_OPENABLE_PLAYER_FLAGS segp, snum)) |
||
| 358 | #undef AI_DOOR_OPENABLE_PLAYER_FLAGS |
||
| 359 | { |
||
| 360 | const auto this_seg = segp->children[snum]; |
||
| 361 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 362 | Assert(this_seg != segment_none); |
||
| 363 | if (((cur_seg == avoid_seg) || (this_seg == avoid_seg)) && (ConsoleObject->segnum == avoid_seg)) { |
||
| 364 | fvi_query fq; |
||
| 365 | fvi_info hit_data; |
||
| 366 | int hit_type; |
||
| 367 | |||
| 368 | const auto &¢er_point = compute_center_point_on_side(vcvertptr, segp, snum); |
||
| 369 | |||
| 370 | fq.p0 = &objp->pos; |
||
| 371 | fq.startseg = objp->segnum; |
||
| 372 | fq.p1 = ¢er_point; |
||
| 373 | fq.rad = objp->size; |
||
| 374 | fq.thisobjnum = objp; |
||
| 375 | fq.ignore_obj_list.first = nullptr; |
||
| 376 | fq.flags = 0; |
||
| 377 | |||
| 378 | hit_type = find_vector_intersection(fq, hit_data); |
||
| 379 | if (hit_type != HIT_NONE) { |
||
| 380 | goto dont_add; |
||
| 381 | } |
||
| 382 | } |
||
| 383 | #endif |
||
| 384 | |||
| 385 | if (!visited[this_seg]) { |
||
| 386 | seg_queue[qtail].start = cur_seg; |
||
| 387 | seg_queue[qtail].end = this_seg; |
||
| 388 | visited[this_seg] = true; |
||
| 389 | depth[qtail++] = cur_depth+1; |
||
| 390 | if (depth[qtail-1] == max_depth) { |
||
| 391 | end_seg = seg_queue[qtail-1].end; |
||
| 392 | goto cpp_done1; |
||
| 393 | } // end if (depth[... |
||
| 394 | } // end if (!visited... |
||
| 395 | } // if (WALL_IS_DOORWAY(... |
||
| 396 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 397 | dont_add: ; |
||
| 398 | #endif |
||
| 399 | } |
||
| 400 | |||
| 401 | if (qtail <= 0) |
||
| 402 | break; |
||
| 403 | |||
| 404 | if (qhead >= qtail) { |
||
| 405 | // Couldn't get to goal, return a path as far as we got, which probably acceptable to the unparticular caller. |
||
| 406 | end_seg = seg_queue[qtail-1].end; |
||
| 407 | break; |
||
| 408 | } |
||
| 409 | |||
| 410 | cur_seg = seg_queue[qhead].end; |
||
| 411 | cur_depth = depth[qhead]; |
||
| 412 | qhead++; |
||
| 413 | |||
| 414 | cpp_done1: ; |
||
| 415 | } // while (cur_seg ... |
||
| 416 | |||
| 417 | if (qtail > 0) |
||
| 418 | { |
||
| 419 | // Set qtail to the segment which ends at the goal. |
||
| 420 | while (seg_queue[--qtail].end != end_seg) |
||
| 421 | if (qtail < 0) { |
||
| 422 | return std::make_pair(create_path_result::early, l_num_points); |
||
| 423 | } |
||
| 424 | } |
||
| 425 | else |
||
| 426 | qtail = -1; |
||
| 427 | |||
| 428 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 429 | #if DXX_USE_EDITOR |
||
| 430 | Selected_segs.clear(); |
||
| 431 | #endif |
||
| 432 | #endif |
||
| 433 | |||
| 434 | while (qtail >= 0) { |
||
| 435 | segnum_t parent_seg, this_seg; |
||
| 436 | |||
| 437 | this_seg = seg_queue[qtail].end; |
||
| 438 | parent_seg = seg_queue[qtail].start; |
||
| 439 | psegs->segnum = this_seg; |
||
| 440 | compute_segment_center(vcvertptr, psegs->point, vcsegptr(this_seg)); |
||
| 441 | psegs++; |
||
| 442 | l_num_points++; |
||
| 443 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 444 | #if DXX_USE_EDITOR |
||
| 445 | Selected_segs.emplace_back(this_seg); |
||
| 446 | #endif |
||
| 447 | #endif |
||
| 448 | |||
| 449 | if (parent_seg == start_seg) |
||
| 450 | break; |
||
| 451 | |||
| 452 | while (seg_queue[--qtail].end != parent_seg) |
||
| 453 | Assert(qtail >= 0); |
||
| 454 | } |
||
| 455 | |||
| 456 | psegs->segnum = start_seg; |
||
| 457 | compute_segment_center(vcvertptr, psegs->point, vcsegptr(start_seg)); |
||
| 458 | psegs++; |
||
| 459 | l_num_points++; |
||
| 460 | |||
| 461 | #if PATH_VALIDATION |
||
| 462 | validate_path(1, original_psegs, l_num_points); |
||
| 463 | #endif |
||
| 464 | |||
| 465 | // Now, reverse point_segs in place. |
||
| 466 | for (i=0; i< l_num_points/2; i++) { |
||
| 467 | point_seg temp_point_seg = *(original_psegs + i); |
||
| 468 | *(original_psegs + i) = *(original_psegs + l_num_points - i - 1); |
||
| 469 | *(original_psegs + l_num_points - i - 1) = temp_point_seg; |
||
| 470 | } |
||
| 471 | #if PATH_VALIDATION |
||
| 472 | validate_path(2, original_psegs, l_num_points); |
||
| 473 | #endif |
||
| 474 | |||
| 475 | // Now, if safety_flag set, then insert the point at the center of the side connecting two segments |
||
| 476 | // between the two points. This is messy because we must insert into the list. The simplest (and not too slow) |
||
| 477 | // way to do this is to start at the end of the list and go backwards. |
||
| 478 | if (safety_flag != create_path_safety_flag::unsafe) { |
||
| 479 | if (psegs - Point_segs + l_num_points + 2 > MAX_POINT_SEGS) { |
||
| 480 | // Ouch! Cannot insert center points in path. So return unsafe path. |
||
| 481 | ai_reset_all_paths(); |
||
| 482 | return std::make_pair(create_path_result::early, l_num_points); |
||
| 483 | } else { |
||
| 484 | l_num_points = insert_center_points(Segments, original_psegs, l_num_points); |
||
| 485 | } |
||
| 486 | } |
||
| 487 | |||
| 488 | #if PATH_VALIDATION |
||
| 489 | validate_path(3, original_psegs, l_num_points); |
||
| 490 | #endif |
||
| 491 | |||
| 492 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 493 | // -- MK, 10/30/95 -- This code causes apparent discontinuities in the path, moving a point |
||
| 494 | // into a new segment. It is not necessarily bad, but it makes it hard to track down actual |
||
| 495 | // discontinuity problems. |
||
| 496 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 497 | if (objp->type == OBJ_ROBOT) |
||
| 498 | if (Robot_info[get_robot_id(objp)].companion) |
||
| 499 | move_towards_outside(LevelSharedSegmentState, original_psegs, l_num_points, objp, create_path_random_flag::nonrandom); |
||
| 500 | #endif |
||
| 501 | |||
| 502 | #if PATH_VALIDATION |
||
| 503 | validate_path(4, original_psegs, l_num_points); |
||
| 504 | #endif |
||
| 505 | return std::make_pair(create_path_result::finished, l_num_points); |
||
| 506 | } |
||
| 507 | |||
| 508 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 509 | // ------------------------------------------------------------------------------------------------------- |
||
| 510 | // polish_path |
||
| 511 | // Takes an existing path and makes it nicer. |
||
| 512 | // Drops as many leading points as possible still maintaining direct accessibility |
||
| 513 | // from current position to first point. |
||
| 514 | // Will not shorten path to fewer than 3 points. |
||
| 515 | // Returns number of points. |
||
| 516 | // Starting position in psegs doesn't change. |
||
| 517 | // Changed, MK, 10/18/95. I think this was causing robots to get hung up on walls. |
||
| 518 | // Only drop up to the first three points. |
||
| 519 | int polish_path(const vmobjptridx_t objp, point_seg *psegs, int num_points) |
||
| 520 | { |
||
| 521 | auto &BuddyState = LevelUniqueObjectState.BuddyState; |
||
| 522 | int i, first_point=0; |
||
| 523 | |||
| 524 | if (num_points <= 4) |
||
| 525 | return num_points; |
||
| 526 | |||
| 527 | // Prevent the buddy from polishing his path twice in one tick, which can cause him to get hung up. Pretty ugly, huh? |
||
| 528 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 529 | if (Robot_info[get_robot_id(objp)].companion) |
||
| 530 | { |
||
| 531 | if (d_tick_count == BuddyState.Last_buddy_polish_path_tick) |
||
| 532 | return num_points; |
||
| 533 | else |
||
| 534 | BuddyState.Last_buddy_polish_path_tick = d_tick_count; |
||
| 535 | } |
||
| 536 | |||
| 537 | // -- MK: 10/18/95: for (i=0; i<num_points-3; i++) |
||
| 538 | for (i=0; i<2; i++) { |
||
| 539 | fvi_query fq; |
||
| 540 | fvi_info hit_data; |
||
| 541 | int hit_type; |
||
| 542 | |||
| 543 | fq.p0 = &objp->pos; |
||
| 544 | fq.startseg = objp->segnum; |
||
| 545 | fq.p1 = &psegs[i].point; |
||
| 546 | fq.rad = objp->size; |
||
| 547 | fq.thisobjnum = objp; |
||
| 548 | fq.ignore_obj_list.first = nullptr; |
||
| 549 | fq.flags = 0; |
||
| 550 | |||
| 551 | hit_type = find_vector_intersection(fq, hit_data); |
||
| 552 | |||
| 553 | if (hit_type == HIT_NONE) |
||
| 554 | first_point = i+1; |
||
| 555 | else |
||
| 556 | break; |
||
| 557 | } |
||
| 558 | |||
| 559 | if (first_point) { |
||
| 560 | // Scrunch down all the psegs. |
||
| 561 | for (i=first_point; i<num_points; i++) |
||
| 562 | psegs[i-first_point] = psegs[i]; |
||
| 563 | } |
||
| 564 | |||
| 565 | return num_points - first_point; |
||
| 566 | } |
||
| 567 | #endif |
||
| 568 | |||
| 569 | #ifndef NDEBUG |
||
| 570 | // ------------------------------------------------------------------------------------------------------- |
||
| 571 | // Make sure that there are connections between all segments on path. |
||
| 572 | // Note that if path has been optimized, connections may not be direct, so this function is useless, or worse. |
||
| 573 | // Return true if valid, else return false. |
||
| 574 | int validate_path(int, point_seg *psegs, uint_fast32_t num_points) |
||
| 575 | { |
||
| 576 | #if PATH_VALIDATION |
||
| 577 | auto curseg = psegs->segnum; |
||
| 578 | if (curseg > Highest_segment_index) |
||
| 579 | { |
||
| 580 | //Int3(); // Contact Mike: Debug trap for elusive, nasty bug. |
||
| 581 | return 0; |
||
| 582 | } |
||
| 583 | |||
| 584 | range_for (const auto &ps, unchecked_partial_range(psegs, 1u, num_points)) |
||
| 585 | { |
||
| 586 | auto nextseg = ps.segnum; |
||
| 587 | if (curseg != nextseg) { |
||
| 588 | const auto &&csegp = vcsegptr(curseg); |
||
| 589 | const auto &children = csegp->children; |
||
| 590 | if (std::find(children.begin(), children.end(), nextseg) == children.end()) |
||
| 591 | { |
||
| 592 | // Assert(sidenum != MAX_SIDES_PER_SEGMENT); // Hey, created path is not contiguous, why!? |
||
| 593 | //Int3(); |
||
| 594 | return 0; |
||
| 595 | } |
||
| 596 | curseg = nextseg; |
||
| 597 | } |
||
| 598 | } |
||
| 599 | #endif |
||
| 600 | return 1; |
||
| 601 | |||
| 602 | } |
||
| 603 | |||
| 604 | // ----------------------------------------------------------------------------------------------------------- |
||
| 605 | void validate_all_paths(void) |
||
| 606 | { |
||
| 607 | |||
| 608 | #if PATH_VALIDATION |
||
| 609 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 610 | auto &vmobjptr = Objects.vmptr; |
||
| 611 | range_for (const auto &&objp, vmobjptr) |
||
| 612 | { |
||
| 613 | auto &obj = *objp; |
||
| 614 | if (obj.type == OBJ_ROBOT) { |
||
| 615 | auto &aip = obj.ctype.ai_info; |
||
| 616 | if (obj.control_type == CT_AI) { |
||
| 617 | if ((aip.hide_index != -1) && (aip.path_length > 0)) |
||
| 618 | if (!validate_path(4, &Point_segs[aip.hide_index], aip.path_length)) |
||
| 619 | { |
||
| 620 | //Int3(); // This path is bogus! Who corrupted it! Danger! Danger! |
||
| 621 | // Contact Mike, he caused this mess. |
||
| 622 | //force_dump_ai_objects_all("Error in validate_all_paths"); |
||
| 623 | aip.path_length=0; // This allows people to resume without harm... |
||
| 624 | } |
||
| 625 | } |
||
| 626 | } |
||
| 627 | } |
||
| 628 | #endif |
||
| 629 | |||
| 630 | } |
||
| 631 | #endif |
||
| 632 | |||
| 633 | // ------------------------------------------------------------------------------------------------------- |
||
| 634 | // Creates a path from the objects current segment (objp->segnum) to the specified segment for the object to |
||
| 635 | // hide in Ai_local_info[objnum].goal_segment. |
||
| 636 | // Sets objp->ctype.ai_info.hide_index, a pointer into Point_segs, the first point_seg of the path. |
||
| 637 | // objp->ctype.ai_info.path_length, length of path |
||
| 638 | // Point_segs_free_ptr global pointer into Point_segs array |
||
| 639 | void create_path_to_segment(const vmobjptridx_t objp, const unsigned max_length, const create_path_safety_flag safety_flag, const icsegidx_t goal_segment) |
||
| 640 | { |
||
| 641 | ai_static *aip = &objp->ctype.ai_info; |
||
| 642 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
| 643 | |||
| 644 | ailp->time_player_seen = GameTime64; // Prevent from resetting path quickly. |
||
| 645 | ailp->goal_segment = goal_segment; |
||
| 646 | |||
| 647 | segnum_t start_seg; |
||
| 648 | start_seg = objp->segnum; |
||
| 649 | const auto end_seg = goal_segment; |
||
| 650 | |||
| 651 | if (end_seg == segment_none) { |
||
| 652 | ; |
||
| 653 | } else { |
||
| 654 | aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, max_length, create_path_random_flag::random, safety_flag, segment_none).second; |
||
| 655 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 656 | aip->path_length = polish_path(objp, Point_segs_free_ptr, aip->path_length); |
||
| 657 | #endif |
||
| 658 | aip->hide_index = Point_segs_free_ptr - Point_segs; |
||
| 659 | aip->cur_path_index = 0; |
||
| 660 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 661 | #ifndef NDEBUG |
||
| 662 | validate_path(6, Point_segs_free_ptr, aip->path_length); |
||
| 663 | #endif |
||
| 664 | #endif |
||
| 665 | Point_segs_free_ptr += aip->path_length; |
||
| 666 | if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) { |
||
| 667 | //Int3(); // Contact Mike: This is stupid. Should call maybe_ai_garbage_collect before the add. |
||
| 668 | //force_dump_ai_objects_all("Error in create_path_to_player"); |
||
| 669 | ai_reset_all_paths(); |
||
| 670 | return; |
||
| 671 | } |
||
| 672 | // Assert(Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 < MAX_POINT_SEGS); |
||
| 673 | aip->PATH_DIR = 1; // Initialize to moving forward. |
||
| 674 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 675 | aip->SUBMODE = AISM_GOHIDE; // This forces immediate movement. |
||
| 676 | #endif |
||
| 677 | ailp->mode = ai_mode::AIM_FOLLOW_PATH; |
||
| 678 | ailp->player_awareness_type = player_awareness_type_t::PA_NONE; // If robot too aware of player, will set mode to chase |
||
| 679 | } |
||
| 680 | maybe_ai_path_garbage_collect(); |
||
| 681 | } |
||
| 682 | |||
| 683 | // Change, 10/07/95: Used to create path to ConsoleObject->pos. Now creates path to Believed_player_pos. |
||
| 684 | void create_path_to_believed_player_segment(const vmobjptridx_t objp, const unsigned max_length, const create_path_safety_flag safety_flag) |
||
| 685 | { |
||
| 686 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 687 | const auto goal_segment = ConsoleObject->segnum; |
||
| 688 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 689 | const auto goal_segment = Believed_player_seg; |
||
| 690 | #endif |
||
| 691 | create_path_to_segment(objp, max_length, safety_flag, goal_segment); |
||
| 692 | } |
||
| 693 | |||
| 694 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 695 | void create_path_to_guidebot_player_segment(const vmobjptridx_t objp, const unsigned max_length, const create_path_safety_flag safety_flag) |
||
| 696 | { |
||
| 697 | auto &BuddyState = LevelUniqueObjectState.BuddyState; |
||
| 698 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 699 | auto &plr = get_player_controlling_guidebot(BuddyState, Players); |
||
| 700 | if (plr.objnum == object_none) |
||
| 701 | return; |
||
| 702 | auto &plrobj = *Objects.vcptr(plr.objnum); |
||
| 703 | const auto goal_segment = plrobj.segnum; |
||
| 704 | create_path_to_segment(objp, max_length, safety_flag, goal_segment); |
||
| 705 | } |
||
| 706 | // ------------------------------------------------------------------------------------------------------- |
||
| 707 | // Creates a path from the object's current segment (objp->segnum) to segment goalseg. |
||
| 708 | void create_path_to_segment(const vmobjptridx_t objp, segnum_t goalseg, const unsigned max_length, const create_path_safety_flag safety_flag) |
||
| 709 | { |
||
| 710 | ai_static *aip = &objp->ctype.ai_info; |
||
| 711 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
| 712 | |||
| 713 | ailp->time_player_seen = GameTime64; // Prevent from resetting path quickly. |
||
| 714 | ailp->goal_segment = goalseg; |
||
| 715 | |||
| 716 | segnum_t start_seg, end_seg; |
||
| 717 | start_seg = objp->segnum; |
||
| 718 | end_seg = ailp->goal_segment; |
||
| 719 | |||
| 720 | if (end_seg == segment_none) { |
||
| 721 | ; |
||
| 722 | } else { |
||
| 723 | aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, max_length, create_path_random_flag::random, safety_flag, segment_none).second; |
||
| 724 | aip->hide_index = Point_segs_free_ptr - Point_segs; |
||
| 725 | aip->cur_path_index = 0; |
||
| 726 | Point_segs_free_ptr += aip->path_length; |
||
| 727 | if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) { |
||
| 728 | ai_reset_all_paths(); |
||
| 729 | return; |
||
| 730 | } |
||
| 731 | |||
| 732 | aip->PATH_DIR = 1; // Initialize to moving forward. |
||
| 733 | // -- UNUSED! aip->SUBMODE = AISM_GOHIDE; // This forces immediate movement. |
||
| 734 | ailp->player_awareness_type = player_awareness_type_t::PA_NONE; // If robot too aware of player, will set mode to chase |
||
| 735 | } |
||
| 736 | |||
| 737 | maybe_ai_path_garbage_collect(); |
||
| 738 | |||
| 739 | } |
||
| 740 | #endif |
||
| 741 | |||
| 742 | // ------------------------------------------------------------------------------------------------------- |
||
| 743 | // Creates a path from the objects current segment (objp->segnum) to the specified segment for the object to |
||
| 744 | // hide in Ai_local_info[objnum].goal_segment |
||
| 745 | // Sets objp->ctype.ai_info.hide_index, a pointer into Point_segs, the first point_seg of the path. |
||
| 746 | // objp->ctype.ai_info.path_length, length of path |
||
| 747 | // Point_segs_free_ptr global pointer into Point_segs array |
||
| 748 | void create_path_to_station(const vmobjptridx_t objp, int max_length) |
||
| 749 | { |
||
| 750 | ai_static *aip = &objp->ctype.ai_info; |
||
| 751 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
| 752 | |||
| 753 | if (max_length == -1) |
||
| 754 | max_length = MAX_DEPTH_TO_SEARCH_FOR_PLAYER; |
||
| 755 | |||
| 756 | ailp->time_player_seen = GameTime64; // Prevent from resetting path quickly. |
||
| 757 | |||
| 758 | segnum_t start_seg, end_seg; |
||
| 759 | start_seg = objp->segnum; |
||
| 760 | end_seg = aip->hide_segment; |
||
| 761 | |||
| 762 | if (end_seg == segment_none) { |
||
| 763 | ; |
||
| 764 | } else { |
||
| 765 | aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, max_length, create_path_random_flag::random, create_path_safety_flag::safe, segment_none).second; |
||
| 766 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 767 | aip->path_length = polish_path(objp, Point_segs_free_ptr, aip->path_length); |
||
| 768 | #endif |
||
| 769 | aip->hide_index = Point_segs_free_ptr - Point_segs; |
||
| 770 | aip->cur_path_index = 0; |
||
| 771 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 772 | #ifndef NDEBUG |
||
| 773 | validate_path(7, Point_segs_free_ptr, aip->path_length); |
||
| 774 | #endif |
||
| 775 | #endif |
||
| 776 | Point_segs_free_ptr += aip->path_length; |
||
| 777 | if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) { |
||
| 778 | //Int3(); // Contact Mike: Stupid. |
||
| 779 | //force_dump_ai_objects_all("Error in create_path_to_station"); |
||
| 780 | ai_reset_all_paths(); |
||
| 781 | return; |
||
| 782 | } |
||
| 783 | // Assert(Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 < MAX_POINT_SEGS); |
||
| 784 | aip->PATH_DIR = 1; // Initialize to moving forward. |
||
| 785 | // aip->SUBMODE = AISM_GOHIDE; // This forces immediate movement. |
||
| 786 | ailp->mode = ai_mode::AIM_FOLLOW_PATH; |
||
| 787 | ailp->player_awareness_type = player_awareness_type_t::PA_NONE; |
||
| 788 | } |
||
| 789 | |||
| 790 | |||
| 791 | maybe_ai_path_garbage_collect(); |
||
| 792 | |||
| 793 | } |
||
| 794 | |||
| 795 | |||
| 796 | // ------------------------------------------------------------------------------------------------------- |
||
| 797 | // Create a path of length path_length for an object, stuffing info in ai_info field. |
||
| 798 | void create_n_segment_path(const vmobjptridx_t objp, unsigned path_length, const imsegidx_t avoid_seg) |
||
| 799 | { |
||
| 800 | ai_static *aip=&objp->ctype.ai_info; |
||
| 801 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
| 802 | |||
| 803 | const auto &&cr0 = create_path_points(objp, objp->segnum, segment_exit, Point_segs_free_ptr, path_length, create_path_random_flag::random, create_path_safety_flag::unsafe, avoid_seg); |
||
| 804 | aip->path_length = cr0.second; |
||
| 805 | if (cr0.first == create_path_result::early) |
||
| 806 | { |
||
| 807 | Point_segs_free_ptr += aip->path_length; |
||
| 808 | for (;;) |
||
| 809 | { |
||
| 810 | const auto &&crf = create_path_points(objp, objp->segnum, segment_exit, Point_segs_free_ptr, --path_length, create_path_random_flag::random, create_path_safety_flag::unsafe, segment_none); |
||
| 811 | aip->path_length = crf.second; |
||
| 812 | if (crf.first != create_path_result::early) |
||
| 813 | break; |
||
| 814 | } |
||
| 815 | assert(path_length); |
||
| 816 | } |
||
| 817 | |||
| 818 | aip->hide_index = Point_segs_free_ptr - Point_segs; |
||
| 819 | aip->cur_path_index = 0; |
||
| 820 | #if PATH_VALIDATION |
||
| 821 | validate_path(8, Point_segs_free_ptr, aip->path_length); |
||
| 822 | #endif |
||
| 823 | Point_segs_free_ptr += aip->path_length; |
||
| 824 | if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) { |
||
| 825 | //Int3(); // Contact Mike: This is curious, though not deadly. /eip++;g |
||
| 826 | //force_dump_ai_objects_all("Error in crete_n_segment_path 2"); |
||
| 827 | ai_reset_all_paths(); |
||
| 828 | } |
||
| 829 | |||
| 830 | aip->PATH_DIR = 1; // Initialize to moving forward. |
||
| 831 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 832 | aip->SUBMODE = -1; // Don't know what this means. |
||
| 833 | #endif |
||
| 834 | ailp->mode = ai_mode::AIM_FOLLOW_PATH; |
||
| 835 | |||
| 836 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 837 | // If this robot is visible (player_visibility is not available) and it's running away, move towards outside with |
||
| 838 | // randomness to prevent a stream of bots from going away down the center of a corridor. |
||
| 839 | if (player_is_visible(ailp->previous_visibility)) |
||
| 840 | { |
||
| 841 | if (aip->path_length) { |
||
| 842 | int t_num_points = aip->path_length; |
||
| 843 | move_towards_outside(LevelSharedSegmentState, &Point_segs[aip->hide_index], t_num_points, objp, create_path_random_flag::random); |
||
| 844 | aip->path_length = t_num_points; |
||
| 845 | } |
||
| 846 | } |
||
| 847 | #endif |
||
| 848 | |||
| 849 | maybe_ai_path_garbage_collect(); |
||
| 850 | |||
| 851 | } |
||
| 852 | |||
| 853 | // ------------------------------------------------------------------------------------------------------- |
||
| 854 | void create_n_segment_path_to_door(const vmobjptridx_t objp, const unsigned path_length) |
||
| 855 | { |
||
| 856 | create_n_segment_path(objp, path_length, segment_none); |
||
| 857 | } |
||
| 858 | |||
| 859 | #define Int3_if(cond) if (!cond) Int3(); |
||
| 860 | |||
| 861 | // -- too much work -- // ---------------------------------------------------------------------------------------------------------- |
||
| 862 | // -- too much work -- // Return true if the object the companion wants to kill is reachable. |
||
| 863 | // -- too much work -- int attack_kill_object(object *objp) |
||
| 864 | // -- too much work -- { |
||
| 865 | // -- too much work -- object *kill_objp; |
||
| 866 | // -- too much work -- fvi_info hit_data; |
||
| 867 | // -- too much work -- int fate; |
||
| 868 | // -- too much work -- fvi_query fq; |
||
| 869 | // -- too much work -- |
||
| 870 | // -- too much work -- if (Escort_kill_object == -1) |
||
| 871 | // -- too much work -- return 0; |
||
| 872 | // -- too much work -- |
||
| 873 | // -- too much work -- kill_objp = &Objects[Escort_kill_object]; |
||
| 874 | // -- too much work -- |
||
| 875 | // -- too much work -- fq.p0 = &objp->pos; |
||
| 876 | // -- too much work -- fq.startseg = objp->segnum; |
||
| 877 | // -- too much work -- fq.p1 = &kill_objp->pos; |
||
| 878 | // -- too much work -- fq.rad = objp->size; |
||
| 879 | // -- too much work -- fq.thisobjnum = objp-Objects; |
||
| 880 | // -- too much work -- fq.ignore_obj_list = NULL; |
||
| 881 | // -- too much work -- fq.flags = 0; |
||
| 882 | // -- too much work -- |
||
| 883 | // -- too much work -- fate = find_vector_intersection(&fq,&hit_data); |
||
| 884 | // -- too much work -- |
||
| 885 | // -- too much work -- if (fate == HIT_NONE) |
||
| 886 | // -- too much work -- return 1; |
||
| 887 | // -- too much work -- else |
||
| 888 | // -- too much work -- return 0; |
||
| 889 | // -- too much work -- } |
||
| 890 | |||
| 891 | // ------------------------------------------------------------------------------------------------------- |
||
| 892 | // Creates a path from the objects current segment (objp->segnum) to the specified segment for the object to |
||
| 893 | // hide in Ai_local_info[objnum].goal_segment. |
||
| 894 | // Sets objp->ctype.ai_info.hide_index, a pointer into Point_segs, the first point_seg of the path. |
||
| 895 | // objp->ctype.ai_info.path_length, length of path |
||
| 896 | // Point_segs_free_ptr global pointer into Point_segs array |
||
| 897 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 898 | static void create_path(const vmobjptridx_t objp) |
||
| 899 | { |
||
| 900 | ai_static *aip = &objp->ctype.ai_info; |
||
| 901 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
| 902 | segnum_t start_seg, end_seg; |
||
| 903 | |||
| 904 | start_seg = objp->segnum; |
||
| 905 | end_seg = ailp->goal_segment; |
||
| 906 | |||
| 907 | if (end_seg == segment_none) |
||
| 908 | create_n_segment_path(objp, 3, segment_none); |
||
| 909 | |||
| 910 | if (end_seg == segment_none) { |
||
| 911 | ; |
||
| 912 | } else { |
||
| 913 | aip->path_length = create_path_points(objp, start_seg, end_seg, Point_segs_free_ptr, MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none).second; |
||
| 914 | aip->hide_index = Point_segs_free_ptr - Point_segs; |
||
| 915 | aip->cur_path_index = 0; |
||
| 916 | #ifndef NDEBUG |
||
| 917 | validate_path(5, Point_segs_free_ptr, aip->path_length); |
||
| 918 | #endif |
||
| 919 | Point_segs_free_ptr += aip->path_length; |
||
| 920 | if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) { |
||
| 921 | //Int3(); // Contact Mike: This is curious, though not deadly. /eip++;g |
||
| 922 | //force_dump_ai_objects_all("Error in create_path"); |
||
| 923 | ai_reset_all_paths(); |
||
| 924 | } |
||
| 925 | aip->PATH_DIR = 1; // Initialize to moving forward. |
||
| 926 | aip->SUBMODE = AISM_HIDING; // Pretend we are hiding, so we sit here until bothered. |
||
| 927 | } |
||
| 928 | |||
| 929 | maybe_ai_path_garbage_collect(); |
||
| 930 | |||
| 931 | } |
||
| 932 | #endif |
||
| 933 | |||
| 934 | |||
| 935 | // ---------------------------------------------------------------------------------------------------------- |
||
| 936 | // Optimization: If current velocity will take robot near goal, don't change velocity |
||
| 937 | void ai_follow_path(const vmobjptridx_t objp, const player_visibility_state player_visibility, const vms_vector *const vec_to_player) |
||
| 938 | { |
||
| 939 | ai_static *aip = &objp->ctype.ai_info; |
||
| 940 | |||
| 941 | vms_vector goal_point, new_goal_point; |
||
| 942 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 943 | auto &BuddyState = LevelUniqueObjectState.BuddyState; |
||
| 944 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 945 | auto &robptr = Robot_info[get_robot_id(objp)]; |
||
| 946 | #endif |
||
| 947 | int forced_break, original_dir, original_index; |
||
| 948 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
| 949 | |||
| 950 | |||
| 951 | if ((aip->hide_index == -1) || (aip->path_length == 0)) |
||
| 952 | { |
||
| 953 | if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) { |
||
| 954 | create_n_segment_path(objp, 5, segment_none); |
||
| 955 | //--Int3_if((aip->path_length != 0)); |
||
| 956 | ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT; |
||
| 957 | } else { |
||
| 958 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 959 | create_path(objp); |
||
| 960 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 961 | create_n_segment_path(objp, 5, segment_none); |
||
| 962 | #endif |
||
| 963 | //--Int3_if((aip->path_length != 0)); |
||
| 964 | } |
||
| 965 | } |
||
| 966 | |||
| 967 | if ((aip->hide_index + aip->path_length > Point_segs_free_ptr - Point_segs) && (aip->path_length>0)) { |
||
| 968 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 969 | Int3(); // Contact Mike: Bad. Path goes into what is believed to be free space. |
||
| 970 | // This is debugging code. Figure out why garbage collection |
||
| 971 | // didn't compress this object's path information. |
||
| 972 | ai_path_garbage_collect(); |
||
| 973 | #endif |
||
| 974 | ai_reset_all_paths(); |
||
| 975 | } |
||
| 976 | |||
| 977 | if (aip->path_length < 2) { |
||
| 978 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 979 | if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) |
||
| 980 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 981 | if ((aip->behavior == ai_behavior::AIB_SNIPE) || (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT)) |
||
| 982 | #endif |
||
| 983 | { |
||
| 984 | create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum == objp->segnum ? segment_none : ConsoleObject->segnum); // Can't avoid segment player is in, robot is already in it! (That's what the -1 is for) |
||
| 985 | //--Int3_if((aip->path_length != 0)); |
||
| 986 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 987 | if (aip->behavior == ai_behavior::AIB_SNIPE) { |
||
| 988 | if (robot_is_thief(robptr)) |
||
| 989 | ailp->mode = ai_mode::AIM_THIEF_ATTACK; // It gets bashed in create_n_segment_path |
||
| 990 | else |
||
| 991 | ailp->mode = ai_mode::AIM_SNIPE_FIRE; // It gets bashed in create_n_segment_path |
||
| 992 | } else |
||
| 993 | #endif |
||
| 994 | { |
||
| 995 | ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT; // It gets bashed in create_n_segment_path |
||
| 996 | } |
||
| 997 | } |
||
| 998 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 999 | else { |
||
| 1000 | ailp->mode = ai_mode::AIM_STILL; |
||
| 1001 | } |
||
| 1002 | return; |
||
| 1003 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1004 | else if (robot_is_companion(robptr) == 0) { |
||
| 1005 | ailp->mode = ai_mode::AIM_STILL; |
||
| 1006 | aip->path_length = 0; |
||
| 1007 | return; |
||
| 1008 | } |
||
| 1009 | #endif |
||
| 1010 | } |
||
| 1011 | |||
| 1012 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1013 | Assert((aip->PATH_DIR == -1) || (aip->PATH_DIR == 1)); |
||
| 1014 | |||
| 1015 | if ((aip->SUBMODE == AISM_HIDING) && (aip->behavior == ai_behavior::AIB_HIDE)) |
||
| 1016 | return; |
||
| 1017 | #endif |
||
| 1018 | |||
| 1019 | goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point; |
||
| 1020 | auto dist_to_goal = vm_vec_dist_quick(goal_point, objp->pos); |
||
| 1021 | |||
| 1022 | // If running from player, only run until can't be seen. |
||
| 1023 | if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) { |
||
| 1024 | if (player_visibility == player_visibility_state::no_line_of_sight && ailp->player_awareness_type == player_awareness_type_t::PA_NONE) |
||
| 1025 | { |
||
| 1026 | fix vel_scale; |
||
| 1027 | |||
| 1028 | vel_scale = F1_0 - FrameTime/2; |
||
| 1029 | if (vel_scale < F1_0/2) |
||
| 1030 | vel_scale = F1_0/2; |
||
| 1031 | |||
| 1032 | vm_vec_scale(objp->mtype.phys_info.velocity, vel_scale); |
||
| 1033 | |||
| 1034 | return; |
||
| 1035 | } else |
||
| 1036 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1037 | if (!(d_tick_count ^ ((objp) & 0x07))) |
||
| 1038 | #endif |
||
| 1039 | { // Done 1/8 ticks. |
||
| 1040 | // If player on path (beyond point robot is now at), then create a new path. |
||
| 1041 | point_seg *curpsp = &Point_segs[aip->hide_index]; |
||
| 1042 | auto player_segnum = ConsoleObject->segnum; |
||
| 1043 | int i; |
||
| 1044 | |||
| 1045 | // This is probably being done every frame, which is wasteful. |
||
| 1046 | for (i=aip->cur_path_index; i<aip->path_length; i++) { |
||
| 1047 | if (curpsp[i].segnum == player_segnum) { |
||
| 1048 | create_n_segment_path(objp, AVOID_SEG_LENGTH, player_segnum != objp->segnum ? player_segnum : segment_none); |
||
| 1049 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1050 | Assert(aip->path_length != 0); |
||
| 1051 | #endif |
||
| 1052 | ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT; // It gets bashed in create_n_segment_path |
||
| 1053 | break; |
||
| 1054 | } |
||
| 1055 | } |
||
| 1056 | if (player_is_visible(player_visibility)) |
||
| 1057 | { |
||
| 1058 | ailp->player_awareness_type = player_awareness_type_t::PA_NEARBY_ROBOT_FIRED; |
||
| 1059 | ailp->player_awareness_time = F1_0; |
||
| 1060 | } |
||
| 1061 | } |
||
| 1062 | } |
||
| 1063 | |||
| 1064 | if (aip->cur_path_index < 0) { |
||
| 1065 | aip->cur_path_index = 0; |
||
| 1066 | } else if (aip->cur_path_index >= aip->path_length) { |
||
| 1067 | if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) { |
||
| 1068 | create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum); |
||
| 1069 | ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT; // It gets bashed in create_n_segment_path |
||
| 1070 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1071 | Assert(aip->path_length != 0); |
||
| 1072 | #endif |
||
| 1073 | } else { |
||
| 1074 | aip->cur_path_index = aip->path_length-1; |
||
| 1075 | } |
||
| 1076 | } |
||
| 1077 | |||
| 1078 | goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point; |
||
| 1079 | |||
| 1080 | // If near goal, pick another goal point. |
||
| 1081 | forced_break = 0; // Gets set for short paths. |
||
| 1082 | original_dir = aip->PATH_DIR; |
||
| 1083 | original_index = aip->cur_path_index; |
||
| 1084 | const vm_distance threshold_distance{fixmul(vm_vec_mag_quick(objp->mtype.phys_info.velocity), FrameTime)*2 + F1_0*2}; |
||
| 1085 | |||
| 1086 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1087 | new_goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point; |
||
| 1088 | |||
| 1089 | //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length))); |
||
| 1090 | #endif |
||
| 1091 | |||
| 1092 | while ((dist_to_goal < threshold_distance) && !forced_break) { |
||
| 1093 | |||
| 1094 | // Advance to next point on path. |
||
| 1095 | aip->cur_path_index += aip->PATH_DIR; |
||
| 1096 | |||
| 1097 | // See if next point wraps past end of path (in either direction), and if so, deal with it based on mode. |
||
| 1098 | if ((aip->cur_path_index >= aip->path_length) || (aip->cur_path_index < 0)) { |
||
| 1099 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1100 | // Buddy bot. If he's in mode to get away from player and at end of line, |
||
| 1101 | // if player visible, then make a new path, else just return. |
||
| 1102 | if (robot_is_companion(robptr)) { |
||
| 1103 | if (BuddyState.Escort_special_goal == ESCORT_GOAL_SCRAM) |
||
| 1104 | { |
||
| 1105 | if (player_is_visible(player_visibility)) |
||
| 1106 | { |
||
| 1107 | create_n_segment_path(objp, 16 + d_rand() * 16, segment_none); |
||
| 1108 | aip->path_length = polish_path(objp, &Point_segs[aip->hide_index], aip->path_length); |
||
| 1109 | Assert(aip->path_length != 0); |
||
| 1110 | ailp->mode = ai_mode::AIM_WANDER; // Special buddy mode. |
||
| 1111 | //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length))); |
||
| 1112 | return; |
||
| 1113 | } else { |
||
| 1114 | ailp->mode = ai_mode::AIM_WANDER; // Special buddy mode. |
||
| 1115 | vm_vec_zero(objp->mtype.phys_info.velocity); |
||
| 1116 | vm_vec_zero(objp->mtype.phys_info.rotvel); |
||
| 1117 | //!!Assert((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length)); |
||
| 1118 | return; |
||
| 1119 | } |
||
| 1120 | } |
||
| 1121 | } |
||
| 1122 | #endif |
||
| 1123 | |||
| 1124 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1125 | if (ailp->mode == ai_mode::AIM_HIDE) { |
||
| 1126 | ailp->mode = ai_mode::AIM_STILL; |
||
| 1127 | return; // Stay here until bonked or hit by player. |
||
| 1128 | } |
||
| 1129 | #elif defined(DXX_BUILD_DESCENT_II) |
||
| 1130 | if (aip->behavior == ai_behavior::AIB_FOLLOW) { |
||
| 1131 | create_n_segment_path(objp, 10, ConsoleObject->segnum); |
||
| 1132 | //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length))); |
||
| 1133 | } |
||
| 1134 | #endif |
||
| 1135 | else if (aip->behavior == ai_behavior::AIB_STATION) { |
||
| 1136 | create_path_to_station(objp, 15); |
||
| 1137 | if ((aip->hide_segment != Point_segs[aip->hide_index+aip->path_length-1].segnum) |
||
| 1138 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1139 | || (aip->path_length == 0) |
||
| 1140 | #endif |
||
| 1141 | ) { |
||
| 1142 | ailp->mode = ai_mode::AIM_STILL; |
||
| 1143 | } |
||
| 1144 | return; |
||
| 1145 | } else if (ailp->mode == ai_mode::AIM_FOLLOW_PATH |
||
| 1146 | #if defined(DXX_BUILD_DESCENT_I) |
||
| 1147 | && (aip->behavior != ai_behavior::AIB_FOLLOW_PATH) |
||
| 1148 | #endif |
||
| 1149 | ) { |
||
| 1150 | create_path_to_believed_player_segment(objp, 10, create_path_safety_flag::safe); |
||
| 1151 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1152 | if (aip->hide_segment != Point_segs[aip->hide_index+aip->path_length-1].segnum) { |
||
| 1153 | ailp->mode = ai_mode::AIM_STILL; |
||
| 1154 | return; |
||
| 1155 | } |
||
| 1156 | #endif |
||
| 1157 | } else if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT) { |
||
| 1158 | create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum); |
||
| 1159 | ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT; // It gets bashed in create_n_segment_path |
||
| 1160 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1161 | if (aip->path_length < 1) { |
||
| 1162 | create_n_segment_path(objp, AVOID_SEG_LENGTH, ConsoleObject->segnum); |
||
| 1163 | ailp->mode = ai_mode::AIM_RUN_FROM_OBJECT; // It gets bashed in create_n_segment_path |
||
| 1164 | if (aip->path_length < 1) { |
||
| 1165 | aip->behavior = ai_behavior::AIB_NORMAL; |
||
| 1166 | ailp->mode = ai_mode::AIM_STILL; |
||
| 1167 | return; |
||
| 1168 | } |
||
| 1169 | } |
||
| 1170 | //--Int3_if(((aip->cur_path_index >= 0) && (aip->cur_path_index < aip->path_length))); |
||
| 1171 | #endif |
||
| 1172 | } else { |
||
| 1173 | // Reached end of the line. First see if opposite end point is reachable, and if so, go there. |
||
| 1174 | // If not, turn around. |
||
| 1175 | int opposite_end_index; |
||
| 1176 | vms_vector *opposite_end_point; |
||
| 1177 | fvi_info hit_data; |
||
| 1178 | int fate; |
||
| 1179 | fvi_query fq; |
||
| 1180 | |||
| 1181 | // See which end we're nearer and look at the opposite end point. |
||
| 1182 | if (abs(aip->cur_path_index - aip->path_length) < aip->cur_path_index) { |
||
| 1183 | // Nearer to far end (ie, index not 0), so try to reach 0. |
||
| 1184 | opposite_end_index = 0; |
||
| 1185 | } else { |
||
| 1186 | // Nearer to 0 end, so try to reach far end. |
||
| 1187 | opposite_end_index = aip->path_length-1; |
||
| 1188 | } |
||
| 1189 | |||
| 1190 | opposite_end_point = &Point_segs[aip->hide_index + opposite_end_index].point; |
||
| 1191 | |||
| 1192 | fq.p0 = &objp->pos; |
||
| 1193 | fq.startseg = objp->segnum; |
||
| 1194 | fq.p1 = opposite_end_point; |
||
| 1195 | fq.rad = objp->size; |
||
| 1196 | fq.thisobjnum = objp; |
||
| 1197 | fq.ignore_obj_list.first = nullptr; |
||
| 1198 | fq.flags = 0; //what about trans walls??? |
||
| 1199 | |||
| 1200 | fate = find_vector_intersection(fq, hit_data); |
||
| 1201 | |||
| 1202 | if (fate != HIT_WALL) { |
||
| 1203 | // We can be circular! Do it! |
||
| 1204 | // Path direction is unchanged. |
||
| 1205 | aip->cur_path_index = opposite_end_index; |
||
| 1206 | } else { |
||
| 1207 | aip->PATH_DIR = -aip->PATH_DIR; |
||
| 1208 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1209 | aip->cur_path_index += aip->PATH_DIR; |
||
| 1210 | #endif |
||
| 1211 | } |
||
| 1212 | } |
||
| 1213 | break; |
||
| 1214 | } else { |
||
| 1215 | new_goal_point = Point_segs[aip->hide_index + aip->cur_path_index].point; |
||
| 1216 | goal_point = new_goal_point; |
||
| 1217 | dist_to_goal = vm_vec_dist_quick(goal_point, objp->pos); |
||
| 1218 | } |
||
| 1219 | |||
| 1220 | // If went all the way around to original point, in same direction, then get out of here! |
||
| 1221 | if ((aip->cur_path_index == original_index) && (aip->PATH_DIR == original_dir)) { |
||
| 1222 | create_path_to_believed_player_segment(objp, 3, create_path_safety_flag::safe); |
||
| 1223 | forced_break = 1; |
||
| 1224 | } |
||
| 1225 | } // end while |
||
| 1226 | |||
| 1227 | // Set velocity (objp->mtype.phys_info.velocity) and orientation (objp->orient) for this object. |
||
| 1228 | ai_path_set_orient_and_vel(objp, goal_point |
||
| 1229 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1230 | , player_visibility, vec_to_player |
||
| 1231 | #endif |
||
| 1232 | ); |
||
| 1233 | |||
| 1234 | } |
||
| 1235 | |||
| 1236 | } |
||
| 1237 | |||
| 1238 | namespace { |
||
| 1239 | |||
| 1240 | int Last_tick_garbage_collected; |
||
| 1241 | |||
| 1242 | struct obj_path { |
||
| 1243 | short path_start; |
||
| 1244 | objnum_t objnum; |
||
| 1245 | }; |
||
| 1246 | |||
| 1247 | static int path_index_compare(const void *const v1, const void *const v2) |
||
| 1248 | { |
||
| 1249 | const auto i1 = reinterpret_cast<const obj_path *>(v1); |
||
| 1250 | const auto i2 = reinterpret_cast<const obj_path *>(v2); |
||
| 1251 | if (i1->path_start < i2->path_start) |
||
| 1252 | return -1; |
||
| 1253 | else if (i1->path_start == i2->path_start) |
||
| 1254 | return 0; |
||
| 1255 | else |
||
| 1256 | return 1; |
||
| 1257 | } |
||
| 1258 | |||
| 1259 | } |
||
| 1260 | |||
| 1261 | namespace dsx { |
||
| 1262 | |||
| 1263 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1264 | // Set orientation matrix and velocity for objp based on its desire to get to a point. |
||
| 1265 | void ai_path_set_orient_and_vel(object &objp, const vms_vector &goal_point |
||
| 1266 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1267 | , const player_visibility_state player_visibility, const vms_vector *const vec_to_player |
||
| 1268 | #endif |
||
| 1269 | ) |
||
| 1270 | { |
||
| 1271 | vms_vector cur_vel = objp.mtype.phys_info.velocity; |
||
| 1272 | vms_vector cur_pos = objp.pos; |
||
| 1273 | fix speed_scale; |
||
| 1274 | fix dot; |
||
| 1275 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 1276 | auto &robptr = Robot_info[get_robot_id(objp)]; |
||
| 1277 | fix max_speed; |
||
| 1278 | |||
| 1279 | // If evading player, use highest difficulty level speed, plus something based on diff level |
||
| 1280 | const auto Difficulty_level = GameUniqueState.Difficulty_level; |
||
| 1281 | max_speed = robptr.max_speed[Difficulty_level]; |
||
| 1282 | ai_local *ailp = &objp.ctype.ai_info.ail; |
||
| 1283 | if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT |
||
| 1284 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1285 | || objp.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE |
||
| 1286 | #endif |
||
| 1287 | ) |
||
| 1288 | max_speed = max_speed*3/2; |
||
| 1289 | |||
| 1290 | auto norm_vec_to_goal = vm_vec_normalized_quick(vm_vec_sub(goal_point, cur_pos)); |
||
| 1291 | auto norm_cur_vel = vm_vec_normalized_quick(cur_vel); |
||
| 1292 | const auto norm_fvec = vm_vec_normalized_quick(objp.orient.fvec); |
||
| 1293 | |||
| 1294 | dot = vm_vec_dot(norm_vec_to_goal, norm_fvec); |
||
| 1295 | |||
| 1296 | // If very close to facing opposite desired vector, perturb vector |
||
| 1297 | if (dot < -15*F1_0/16) { |
||
| 1298 | norm_cur_vel = norm_vec_to_goal; |
||
| 1299 | } else { |
||
| 1300 | norm_cur_vel.x += norm_vec_to_goal.x/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime); |
||
| 1301 | norm_cur_vel.y += norm_vec_to_goal.y/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime); |
||
| 1302 | norm_cur_vel.z += norm_vec_to_goal.z/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime); |
||
| 1303 | } |
||
| 1304 | |||
| 1305 | vm_vec_normalize_quick(norm_cur_vel); |
||
| 1306 | |||
| 1307 | // Set speed based on this robot type's maximum allowed speed and how hard it is turning. |
||
| 1308 | // How hard it is turning is based on the dot product of (vector to goal) and (current velocity vector) |
||
| 1309 | // Note that since 3*F1_0/4 is added to dot product, it is possible for the robot to back up. |
||
| 1310 | |||
| 1311 | // Set speed and orientation. |
||
| 1312 | if (dot < 0) |
||
| 1313 | dot /= -4; |
||
| 1314 | |||
| 1315 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1316 | // If in snipe mode, can move fast even if not facing that direction. |
||
| 1317 | if (objp.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE) |
||
| 1318 | if (dot < F1_0/2) |
||
| 1319 | dot = (dot + F1_0)/2; |
||
| 1320 | #endif |
||
| 1321 | |||
| 1322 | speed_scale = fixmul(max_speed, dot); |
||
| 1323 | vm_vec_scale(norm_cur_vel, speed_scale); |
||
| 1324 | objp.mtype.phys_info.velocity = norm_cur_vel; |
||
| 1325 | |||
| 1326 | fix rate; |
||
| 1327 | if (ailp->mode == ai_mode::AIM_RUN_FROM_OBJECT |
||
| 1328 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1329 | || robot_is_companion(robptr) == 1 || objp.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE |
||
| 1330 | #endif |
||
| 1331 | ) { |
||
| 1332 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1333 | if (ailp->mode == ai_mode::AIM_SNIPE_RETREAT_BACKWARDS) { |
||
| 1334 | if (player_is_visible(player_visibility) && vec_to_player) |
||
| 1335 | norm_vec_to_goal = *vec_to_player; |
||
| 1336 | else |
||
| 1337 | vm_vec_negate(norm_vec_to_goal); |
||
| 1338 | } |
||
| 1339 | #endif |
||
| 1340 | rate = robptr.turn_time[NDL - 1] / 2; |
||
| 1341 | } else |
||
| 1342 | rate = robptr.turn_time[Difficulty_level]; |
||
| 1343 | ai_turn_towards_vector(norm_vec_to_goal, objp, rate); |
||
| 1344 | } |
||
| 1345 | |||
| 1346 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1347 | // Garbage colledion -- Free all unused records in Point_segs and compress all paths. |
||
| 1348 | void ai_path_garbage_collect() |
||
| 1349 | { |
||
| 1350 | int free_path_index = 0; |
||
| 1351 | int num_path_objects = 0; |
||
| 1352 | int objind; |
||
| 1353 | obj_path object_list[MAX_OBJECTS]; |
||
| 1354 | |||
| 1355 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1356 | auto &vcobjptridx = Objects.vcptridx; |
||
| 1357 | auto &vmobjptridx = Objects.vmptridx; |
||
| 1358 | #ifndef NDEBUG |
||
| 1359 | force_dump_ai_objects_all("***** Start ai_path_garbage_collect *****"); |
||
| 1360 | #endif |
||
| 1361 | |||
| 1362 | Last_tick_garbage_collected = d_tick_count; |
||
| 1363 | |||
| 1364 | #if PATH_VALIDATION |
||
| 1365 | validate_all_paths(); |
||
| 1366 | #endif |
||
| 1367 | // Create a list of objects which have paths of length 1 or more. |
||
| 1368 | range_for (const auto &&objp, vcobjptridx) |
||
| 1369 | { |
||
| 1370 | if ((objp->type == OBJ_ROBOT) && ((objp->control_type == CT_AI) |
||
| 1371 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1372 | || (objp->control_type == CT_MORPH) |
||
| 1373 | #endif |
||
| 1374 | )) { |
||
| 1375 | const auto &aip = objp->ctype.ai_info; |
||
| 1376 | if (aip.path_length) { |
||
| 1377 | object_list[num_path_objects].path_start = aip.hide_index; |
||
| 1378 | object_list[num_path_objects++].objnum = objp; |
||
| 1379 | } |
||
| 1380 | } |
||
| 1381 | } |
||
| 1382 | |||
| 1383 | qsort(object_list, num_path_objects, sizeof(object_list[0]), |
||
| 1384 | path_index_compare); |
||
| 1385 | |||
| 1386 | for (objind=0; objind < num_path_objects; objind++) { |
||
| 1387 | ai_static *aip; |
||
| 1388 | int i; |
||
| 1389 | int old_index; |
||
| 1390 | |||
| 1391 | auto objnum = object_list[objind].objnum; |
||
| 1392 | object &objp = vmobjptridx(objnum); |
||
| 1393 | aip = &objp.ctype.ai_info; |
||
| 1394 | old_index = aip->hide_index; |
||
| 1395 | |||
| 1396 | aip->hide_index = free_path_index; |
||
| 1397 | for (i=0; i<aip->path_length; i++) |
||
| 1398 | Point_segs[free_path_index++] = Point_segs[old_index++]; |
||
| 1399 | } |
||
| 1400 | |||
| 1401 | Point_segs_free_ptr = Point_segs.begin() + free_path_index; |
||
| 1402 | |||
| 1403 | #ifndef NDEBUG |
||
| 1404 | { |
||
| 1405 | force_dump_ai_objects_all("***** Finish ai_path_garbage_collect *****"); |
||
| 1406 | |||
| 1407 | auto &vcobjptr = Objects.vcptr; |
||
| 1408 | range_for (const auto &&objp, vcobjptr) |
||
| 1409 | { |
||
| 1410 | auto &obj = *objp; |
||
| 1411 | const auto &aip = obj.ctype.ai_info; |
||
| 1412 | |||
| 1413 | if (obj.type == OBJ_ROBOT && obj.control_type == CT_AI) |
||
| 1414 | if ((aip.hide_index + aip.path_length > Point_segs_free_ptr - Point_segs) && (aip.path_length>0)) |
||
| 1415 | Int3(); // Contact Mike: Debug trap for nasty, elusive bug. |
||
| 1416 | } |
||
| 1417 | |||
| 1418 | validate_all_paths(); |
||
| 1419 | } |
||
| 1420 | #endif |
||
| 1421 | |||
| 1422 | } |
||
| 1423 | |||
| 1424 | // ----------------------------------------------------------------------------- |
||
| 1425 | // Do garbage collection if not been done for awhile, or things getting really critical. |
||
| 1426 | void maybe_ai_path_garbage_collect(void) |
||
| 1427 | { |
||
| 1428 | if (Point_segs_free_ptr - Point_segs > MAX_POINT_SEGS - MAX_PATH_LENGTH) { |
||
| 1429 | if (Last_tick_garbage_collected+1 >= d_tick_count) { |
||
| 1430 | // This is kind of bad. Garbage collected last frame or this frame. |
||
| 1431 | // Just destroy all paths. Too bad for the robots. They are memory wasteful. |
||
| 1432 | ai_reset_all_paths(); |
||
| 1433 | } else { |
||
| 1434 | // We are really close to full, but didn't just garbage collect, so maybe this is recoverable. |
||
| 1435 | ai_path_garbage_collect(); |
||
| 1436 | } |
||
| 1437 | } else if (Point_segs_free_ptr - Point_segs > 3*MAX_POINT_SEGS/4) { |
||
| 1438 | if (Last_tick_garbage_collected + 16 < d_tick_count) { |
||
| 1439 | ai_path_garbage_collect(); |
||
| 1440 | } |
||
| 1441 | } else if (Point_segs_free_ptr - Point_segs > MAX_POINT_SEGS/2) { |
||
| 1442 | if (Last_tick_garbage_collected + 256 < d_tick_count) { |
||
| 1443 | ai_path_garbage_collect(); |
||
| 1444 | } |
||
| 1445 | } |
||
| 1446 | } |
||
| 1447 | |||
| 1448 | // ----------------------------------------------------------------------------- |
||
| 1449 | // Reset all paths. Do garbage collection. |
||
| 1450 | // Should be called at the start of each level. |
||
| 1451 | void ai_reset_all_paths(void) |
||
| 1452 | { |
||
| 1453 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1454 | auto &vmobjptr = Objects.vmptr; |
||
| 1455 | range_for (const auto &&objp, vmobjptr) |
||
| 1456 | { |
||
| 1457 | auto &obj = *objp; |
||
| 1458 | if (obj.type == OBJ_ROBOT && obj.control_type == CT_AI) |
||
| 1459 | { |
||
| 1460 | obj.ctype.ai_info.hide_index = -1; |
||
| 1461 | obj.ctype.ai_info.path_length = 0; |
||
| 1462 | } |
||
| 1463 | } |
||
| 1464 | |||
| 1465 | ai_path_garbage_collect(); |
||
| 1466 | |||
| 1467 | } |
||
| 1468 | |||
| 1469 | // --------------------------------------------------------------------------------------------------------- |
||
| 1470 | // Probably called because a robot bashed a wall, getting a bunch of retries. |
||
| 1471 | // Try to resume path. |
||
| 1472 | void attempt_to_resume_path(const vmobjptridx_t objp) |
||
| 1473 | { |
||
| 1474 | ai_static *aip = &objp->ctype.ai_info; |
||
| 1475 | int new_path_index; |
||
| 1476 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1477 | auto &Robot_info = LevelSharedRobotInfoState.Robot_info; |
||
| 1478 | #endif |
||
| 1479 | |||
| 1480 | if (aip->behavior == ai_behavior::AIB_STATION |
||
| 1481 | #if defined(DXX_BUILD_DESCENT_II) |
||
| 1482 | && Robot_info[get_robot_id(objp)].companion != 1 |
||
| 1483 | #endif |
||
| 1484 | ) |
||
| 1485 | if (d_rand() > 8192) { |
||
| 1486 | ai_local *ailp = &objp->ctype.ai_info.ail; |
||
| 1487 | |||
| 1488 | aip->hide_segment = objp->segnum; |
||
| 1489 | ailp->mode = ai_mode::AIM_STILL; |
||
| 1490 | } |
||
| 1491 | |||
| 1492 | new_path_index = aip->cur_path_index - aip->PATH_DIR; |
||
| 1493 | |||
| 1494 | if ((new_path_index >= 0) && (new_path_index < aip->path_length)) { |
||
| 1495 | aip->cur_path_index = new_path_index; |
||
| 1496 | } else { |
||
| 1497 | // At end of line and have nowhere to go. |
||
| 1498 | move_towards_segment_center(LevelSharedSegmentState, objp); |
||
| 1499 | create_path_to_station(objp, 15); |
||
| 1500 | } |
||
| 1501 | } |
||
| 1502 | |||
| 1503 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1504 | // DEBUG FUNCTIONS FOLLOW |
||
| 1505 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1506 | |||
| 1507 | #if DXX_USE_EDITOR |
||
| 1508 | |||
| 1509 | __attribute_used |
||
| 1510 | static void test_create_path_many(fvmobjptridx &vmobjptridx, fimsegptridx &imsegptridx) |
||
| 1511 | { |
||
| 1512 | std::array<point_seg, 200> point_segs; |
||
| 1513 | int i; |
||
| 1514 | |||
| 1515 | const unsigned Test_size = 1000; |
||
| 1516 | for (i=0; i<Test_size; i++) { |
||
| 1517 | Cursegp = imsegptridx(static_cast<segnum_t>((d_rand() * (Highest_segment_index + 1)) / D_RAND_MAX)); |
||
| 1518 | Markedsegp = imsegptridx(static_cast<segnum_t>((d_rand() * (Highest_segment_index + 1)) / D_RAND_MAX)); |
||
| 1519 | create_path_points(vmobjptridx(object_first), Cursegp, Markedsegp, point_segs.begin(), MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none); |
||
| 1520 | } |
||
| 1521 | } |
||
| 1522 | |||
| 1523 | __attribute_used |
||
| 1524 | static void test_create_path(fvmobjptridx &vmobjptridx) |
||
| 1525 | { |
||
| 1526 | std::array<point_seg, 200> point_segs; |
||
| 1527 | |||
| 1528 | create_path_points(vmobjptridx(object_first), Cursegp, Markedsegp, point_segs.begin(), MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none); |
||
| 1529 | } |
||
| 1530 | |||
| 1531 | // For all segments in mine, create paths to all segments in mine, print results. |
||
| 1532 | __attribute_used |
||
| 1533 | static void test_create_all_paths(fvmobjptridx &vmobjptridx, fvcsegptridx &vcsegptridx) |
||
| 1534 | { |
||
| 1535 | Point_segs_free_ptr = Point_segs.begin(); |
||
| 1536 | |||
| 1537 | range_for (const auto &&segp0, vcsegptridx) |
||
| 1538 | { |
||
| 1539 | if (segp0->segnum != segment_none) |
||
| 1540 | { |
||
| 1541 | range_for (const auto &&segp1, partial_range(vcsegptridx, static_cast<segnum_t>(segp0), vcsegptridx.count())) |
||
| 1542 | { |
||
| 1543 | if (segp1->segnum != segment_none) |
||
| 1544 | { |
||
| 1545 | create_path_points(vmobjptridx(object_first), segp0, segp1, Point_segs_free_ptr, MAX_PATH_LENGTH, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none); |
||
| 1546 | } |
||
| 1547 | } |
||
| 1548 | } |
||
| 1549 | } |
||
| 1550 | } |
||
| 1551 | |||
| 1552 | short Player_path_length=0; |
||
| 1553 | int Player_hide_index=-1; |
||
| 1554 | int Player_cur_path_index=0; |
||
| 1555 | int Player_following_path_flag=0; |
||
| 1556 | |||
| 1557 | // ------------------------------------------------------------------------------------------------------------------ |
||
| 1558 | // Set orientation matrix and velocity for objp based on its desire to get to a point. |
||
| 1559 | static void player_path_set_orient_and_vel(object &objp, const vms_vector &goal_point) |
||
| 1560 | { |
||
| 1561 | const auto &cur_vel = objp.mtype.phys_info.velocity; |
||
| 1562 | const auto &cur_pos = objp.pos; |
||
| 1563 | fix speed_scale; |
||
| 1564 | fix dot; |
||
| 1565 | |||
| 1566 | const fix max_speed = F1_0*50; |
||
| 1567 | |||
| 1568 | const auto norm_vec_to_goal = vm_vec_normalized_quick(vm_vec_sub(goal_point, cur_pos)); |
||
| 1569 | auto norm_cur_vel = vm_vec_normalized_quick(cur_vel); |
||
| 1570 | const auto &&norm_fvec = vm_vec_normalized_quick(objp.orient.fvec); |
||
| 1571 | |||
| 1572 | dot = vm_vec_dot(norm_vec_to_goal, norm_fvec); |
||
| 1573 | |||
| 1574 | // If very close to facing opposite desired vector, perturb vector |
||
| 1575 | if (dot < -15*F1_0/16) { |
||
| 1576 | norm_cur_vel = norm_vec_to_goal; |
||
| 1577 | } else { |
||
| 1578 | norm_cur_vel.x += norm_vec_to_goal.x/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime); |
||
| 1579 | norm_cur_vel.y += norm_vec_to_goal.y/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime); |
||
| 1580 | norm_cur_vel.z += norm_vec_to_goal.z/2/(static_cast<float>(DESIGNATED_GAME_FRAMETIME)/FrameTime); |
||
| 1581 | } |
||
| 1582 | |||
| 1583 | vm_vec_normalize_quick(norm_cur_vel); |
||
| 1584 | |||
| 1585 | // Set speed based on this robot type's maximum allowed speed and how hard it is turning. |
||
| 1586 | // How hard it is turning is based on the dot product of (vector to goal) and (current velocity vector) |
||
| 1587 | // Note that since 3*F1_0/4 is added to dot product, it is possible for the robot to back up. |
||
| 1588 | |||
| 1589 | // Set speed and orientation. |
||
| 1590 | if (dot < 0) |
||
| 1591 | dot /= 4; |
||
| 1592 | |||
| 1593 | speed_scale = fixmul(max_speed, dot); |
||
| 1594 | vm_vec_scale(norm_cur_vel, speed_scale); |
||
| 1595 | objp.mtype.phys_info.velocity = norm_cur_vel; |
||
| 1596 | physics_turn_towards_vector(norm_vec_to_goal, objp, F1_0); |
||
| 1597 | } |
||
| 1598 | |||
| 1599 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1600 | // Optimization: If current velocity will take robot near goal, don't change velocity |
||
| 1601 | void player_follow_path(object &objp) |
||
| 1602 | { |
||
| 1603 | vms_vector goal_point; |
||
| 1604 | int count, forced_break, original_index; |
||
| 1605 | int goal_seg; |
||
| 1606 | |||
| 1607 | if (!Player_following_path_flag) |
||
| 1608 | return; |
||
| 1609 | |||
| 1610 | if (Player_hide_index == -1) |
||
| 1611 | return; |
||
| 1612 | |||
| 1613 | if (Player_path_length < 2) |
||
| 1614 | return; |
||
| 1615 | |||
| 1616 | goal_point = Point_segs[Player_hide_index + Player_cur_path_index].point; |
||
| 1617 | goal_seg = Point_segs[Player_hide_index + Player_cur_path_index].segnum; |
||
| 1618 | Assert((goal_seg >= 0) && (goal_seg <= Highest_segment_index)); |
||
| 1619 | (void)goal_seg; |
||
| 1620 | auto dist_to_goal = vm_vec_dist_quick(goal_point, objp.pos); |
||
| 1621 | |||
| 1622 | if (Player_cur_path_index < 0) |
||
| 1623 | Player_cur_path_index = 0; |
||
| 1624 | else if (Player_cur_path_index >= Player_path_length) |
||
| 1625 | Player_cur_path_index = Player_path_length-1; |
||
| 1626 | |||
| 1627 | goal_point = Point_segs[Player_hide_index + Player_cur_path_index].point; |
||
| 1628 | |||
| 1629 | count=0; |
||
| 1630 | |||
| 1631 | // If near goal, pick another goal point. |
||
| 1632 | forced_break = 0; // Gets set for short paths. |
||
| 1633 | //original_dir = 1; |
||
| 1634 | original_index = Player_cur_path_index; |
||
| 1635 | const vm_distance threshold_distance{fixmul(vm_vec_mag_quick(objp.mtype.phys_info.velocity), FrameTime)*2 + F1_0*2}; |
||
| 1636 | |||
| 1637 | while ((dist_to_goal < threshold_distance) && !forced_break) { |
||
| 1638 | |||
| 1639 | // ----- Debug stuff ----- |
||
| 1640 | if (count++ > 20) { |
||
| 1641 | break; |
||
| 1642 | } |
||
| 1643 | |||
| 1644 | // Advance to next point on path. |
||
| 1645 | Player_cur_path_index += 1; |
||
| 1646 | |||
| 1647 | // See if next point wraps past end of path (in either direction), and if so, deal with it based on mode. |
||
| 1648 | if ((Player_cur_path_index >= Player_path_length) || (Player_cur_path_index < 0)) { |
||
| 1649 | Player_following_path_flag = 0; |
||
| 1650 | forced_break = 1; |
||
| 1651 | } |
||
| 1652 | |||
| 1653 | // If went all the way around to original point, in same direction, then get out of here! |
||
| 1654 | if (Player_cur_path_index == original_index) { |
||
| 1655 | Player_following_path_flag = 0; |
||
| 1656 | forced_break = 1; |
||
| 1657 | } |
||
| 1658 | |||
| 1659 | goal_point = Point_segs[Player_hide_index + Player_cur_path_index].point; |
||
| 1660 | dist_to_goal = vm_vec_dist_quick(goal_point, objp.pos); |
||
| 1661 | |||
| 1662 | } // end while |
||
| 1663 | |||
| 1664 | // Set velocity (objp->mtype.phys_info.velocity) and orientation (objp->orient) for this object. |
||
| 1665 | player_path_set_orient_and_vel(objp, goal_point); |
||
| 1666 | } |
||
| 1667 | |||
| 1668 | |||
| 1669 | // ------------------------------------------------------------------------------------------------------------------ |
||
| 1670 | // Create path for player from current segment to goal segment. |
||
| 1671 | static void create_player_path_to_segment(fvmobjptridx &vmobjptridx, segnum_t segnum) |
||
| 1672 | { |
||
| 1673 | const auto objp = vmobjptridx(ConsoleObject); |
||
| 1674 | Player_hide_index=-1; |
||
| 1675 | Player_cur_path_index=0; |
||
| 1676 | Player_following_path_flag=0; |
||
| 1677 | |||
| 1678 | auto &&cr = create_path_points(objp, objp->segnum, segnum, Point_segs_free_ptr, 100, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none); |
||
| 1679 | Player_path_length = cr.second; |
||
| 1680 | if (cr.first == create_path_result::early) |
||
| 1681 | con_printf(CON_DEBUG,"Unable to form path of length %i for myself", 100); |
||
| 1682 | |||
| 1683 | Player_following_path_flag = 1; |
||
| 1684 | |||
| 1685 | Player_hide_index = Point_segs_free_ptr - Point_segs; |
||
| 1686 | Player_cur_path_index = 0; |
||
| 1687 | Point_segs_free_ptr += Player_path_length; |
||
| 1688 | if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) { |
||
| 1689 | //Int3(); // Contact Mike: This is curious, though not deadly. /eip++;g |
||
| 1690 | ai_reset_all_paths(); |
||
| 1691 | } |
||
| 1692 | |||
| 1693 | } |
||
| 1694 | segnum_t Player_goal_segment = segment_none; |
||
| 1695 | |||
| 1696 | void check_create_player_path(void) |
||
| 1697 | { |
||
| 1698 | auto &Objects = LevelUniqueObjectState.Objects; |
||
| 1699 | auto &vmobjptridx = Objects.vmptridx; |
||
| 1700 | if (Player_goal_segment != segment_none) |
||
| 1701 | create_player_path_to_segment(vmobjptridx, Player_goal_segment); |
||
| 1702 | |||
| 1703 | Player_goal_segment = segment_none; |
||
| 1704 | } |
||
| 1705 | |||
| 1706 | #endif |
||
| 1707 | |||
| 1708 | } |
||
| 1709 | |||
| 1710 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1711 | // DEBUG FUNCTIONS ENDED |
||
| 1712 | // ---------------------------------------------------------------------------------------------------------- |
||
| 1713 |