Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  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 &&center_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 &&center_point = compute_center_point_on_side(vcvertptr, segp, snum);
  369.  
  370.                                         fq.p0                                           = &objp->pos;
  371.                                         fq.startseg                             = objp->segnum;
  372.                                         fq.p1                                           = &center_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.  
  1714.