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.  * Autonomous Individual movement.
  23.  *
  24.  */
  25.  
  26. #include <algorithm>
  27. #include <cstdlib>
  28. #include <stdio.h>
  29. #include <time.h>
  30.  
  31. #include "inferno.h"
  32. #include "game.h"
  33. #include "console.h"
  34. #include "3d.h"
  35.  
  36. #include "object.h"
  37. #include "render.h"
  38. #include "dxxerror.h"
  39. #include "ai.h"
  40. #include "escort.h"
  41. #include "laser.h"
  42. #include "fvi.h"
  43. #include "physfsx.h"
  44. #include "physfs-serial.h"
  45. #include "robot.h"
  46. #include "bm.h"
  47. #include "weapon.h"
  48. #include "physics.h"
  49. #include "collide.h"
  50. #include "player.h"
  51. #include "wall.h"
  52. #include "vclip.h"
  53. #include "fireball.h"
  54. #include "morph.h"
  55. #include "effects.h"
  56. #include "timer.h"
  57. #include "sounds.h"
  58. #include "gameseg.h"
  59. #include "cntrlcen.h"
  60. #include "multibot.h"
  61. #include "multi.h"
  62. #include "gameseq.h"
  63. #include "key.h"
  64. #include "powerup.h"
  65. #include "gauges.h"
  66. #include "text.h"
  67. #include "args.h"
  68. #include "fuelcen.h"
  69. #include "controls.h"
  70. #include "kconfig.h"
  71.  
  72. #if DXX_USE_EDITOR
  73. #include "editor/editor.h"
  74. #include "editor/esegment.h"
  75. #include "editor/kdefs.h"
  76. #endif
  77.  
  78. //added 05/17/99 Matt Mueller
  79. #include "u_mem.h"
  80. //end addition -MM
  81.  
  82. #include "compiler-range_for.h"
  83. #include "segiter.h"
  84. #include "d_enumerate.h"
  85. #include "d_range.h"
  86. #include <utility>
  87.  
  88. using std::min;
  89.  
  90. #define AI_TURN_SCALE   1
  91. #define BABY_SPIDER_ID  14
  92.  
  93. namespace dsx {
  94. static void init_boss_segments(const segment_array &segments, const object &boss_objnum, d_level_shared_boss_state::special_segment_array_t &a, int size_check, int one_wall_hack);
  95. static void ai_multi_send_robot_position(object &objnum, int force);
  96.  
  97. #if defined(DXX_BUILD_DESCENT_I)
  98. #define BOSS_DEATH_SOUND_DURATION       0x2ae14         //      2.68 seconds
  99.  
  100. constexpr d_level_shared_boss_state::D1_Boss_cloak_interval d_level_shared_boss_state::Boss_cloak_interval;
  101. constexpr d_level_shared_boss_state::D1_Boss_teleport_interval d_level_shared_boss_state::Boss_teleport_interval;
  102.  
  103. #elif defined(DXX_BUILD_DESCENT_II)
  104. #define FIRE_AT_NEARBY_PLAYER_THRESHOLD (F1_0*40)
  105.  
  106. #define FIRE_K  8               //      Controls average accuracy of robot firing.  Smaller numbers make firing worse.  Being power of 2 doesn't matter.
  107.  
  108. // ====================================================================================================================
  109.  
  110. #define MIN_LEAD_SPEED          (F1_0*4)
  111. #define MAX_LEAD_DISTANCE       (F1_0*200)
  112. #define LEAD_RANGE                      (F1_0/2)
  113.  
  114. constexpr std::array<std::array<int, 3>, NUM_D2_BOSSES> Spew_bots{{
  115.         {{38, 40, -1}},
  116.         {{37, -1, -1}},
  117.         {{43, 57, -1}},
  118.         {{26, 27, 58}},
  119.         {{59, 58, 54}},
  120.         {{60, 61, 54}},
  121.  
  122.         {{69, 29, 24}},
  123.         {{72, 60, 73}}
  124. }};
  125.  
  126. constexpr std::array<int, NUM_D2_BOSSES> Max_spew_bots{{2, 1, 2, 3, 3, 3, 3, 3}};
  127. static fix Dist_to_last_fired_upon_player_pos;
  128. #endif
  129. }
  130.  
  131. namespace dcx {
  132. constexpr std::integral_constant<int, F1_0 * 8> CHASE_TIME_LENGTH{};
  133. constexpr std::integral_constant<int, F1_0> Robot_sound_volume{};
  134. enum {
  135.         Flinch_scale = 4,
  136.         Attack_scale = 24,
  137. };
  138. #define ANIM_RATE               (F1_0/16)
  139. #define DELTA_ANG_SCALE 16
  140.  
  141. constexpr std::array<int8_t, 8> Mike_to_matt_xlate{{
  142.         AS_REST, AS_REST, AS_ALERT, AS_ALERT, AS_FLINCH, AS_FIRE, AS_RECOIL, AS_REST
  143. }};
  144.  
  145. #define OVERALL_AGITATION_MAX   100
  146.  
  147. #define         MAX_AI_CLOAK_INFO       8       //      Must be a power of 2!
  148.  
  149. #define BOSS_CLOAK_DURATION     Boss_cloak_duration
  150. #define BOSS_DEATH_DURATION     (F1_0*6)
  151. //      Amount of time since the current robot was last processed for things such as movement.
  152. //      It is not valid to use FrameTime because robots do not get moved every frame.
  153.  
  154. // ---------- John: These variables must be saved as part of gamesave. --------
  155. static int             Overall_agitation;
  156. point_seg_array_t       Point_segs;
  157. point_seg_array_t::iterator       Point_segs_free_ptr;
  158. static std::array<ai_cloak_info, MAX_AI_CLOAK_INFO>   Ai_cloak_info;
  159.  
  160. // ------ John: End of variables which must be saved as part of gamesave. -----
  161.  
  162. //      0       mech
  163. //      1       green claw
  164. //      2       spider
  165. //      3       josh
  166. //      4       violet
  167. //      5       cloak vulcan
  168. //      6       cloak mech
  169. //      7       brain
  170. //      8       onearm
  171. //      9       plasma
  172. //      10      toaster
  173. //      11      bird
  174. //      12      missile bird
  175. //      13      polyhedron
  176. //      14      baby spider
  177. //      15      mini boss
  178. //      16      super mech
  179. //      17      shareware boss
  180. //      18      cloak-green     ; note, gating in this guy benefits player, cloak objects
  181. //      19      vulcan
  182. //      20      toad
  183. //      21      4-claw
  184. //      22      quad-laser
  185. // 23 super boss
  186.  
  187. // byte Super_boss_gate_list[] = {0, 1, 2, 9, 11, 16, 18, 19, 21, 22, 0, 9, 9, 16, 16, 18, 19, 19, 22, 22};
  188. constexpr std::array<int8_t, 21> Super_boss_gate_list{{
  189.         0, 1, 8, 9, 10, 11, 12, 15, 16, 18, 19, 20, 22, 0, 8, 11, 19, 20, 8, 20, 8
  190. }};
  191. }
  192. #define MAX_GATE_INDEX  (Super_boss_gate_list.size())
  193.  
  194. #if defined(DXX_BUILD_DESCENT_II)
  195. namespace dsx {
  196.  
  197.  
  198. // ------ John: End of variables which must be saved as part of gamesave. -----
  199.  
  200. vms_vector      Last_fired_upon_player_pos;
  201.  
  202.  
  203. // -- ubyte Boss_cloaks[NUM_D2_BOSSES]              = {1,1,1,1,1,1};      // Set byte if this boss can cloak
  204.  
  205. const boss_flags_t Boss_teleports{{1,1,1,1,1,1, 1,1}}; // Set byte if this boss can teleport
  206. const boss_flags_t Boss_spew_more{{0,1,0,0,0,0, 0,0}}; // If set, 50% of time, spew two bots.
  207. const boss_flags_t Boss_spews_bots_energy{{1,1,0,1,0,1, 1,1}}; // Set byte if boss spews bots when hit by energy weapon.
  208. const boss_flags_t Boss_spews_bots_matter{{0,0,1,1,1,1, 0,1}}; // Set byte if boss spews bots when hit by matter weapon.
  209. const boss_flags_t Boss_invulnerable_energy{{0,0,1,1,0,0, 0,0}}; // Set byte if boss is invulnerable to energy weapons.
  210. const boss_flags_t Boss_invulnerable_matter{{0,0,0,0,1,1, 1,0}}; // Set byte if boss is invulnerable to matter weapons.
  211. const boss_flags_t Boss_invulnerable_spot{{0,0,0,0,0,1, 0,1}}; // Set byte if boss is invulnerable in all but a certain spot.  (Dot product fvec|vec_to_collision < BOSS_INVULNERABLE_DOT)
  212.  
  213. segnum_t             Believed_player_seg;
  214. }
  215. #endif
  216.  
  217. namespace dcx {
  218.  
  219. namespace {
  220.  
  221. struct robot_to_player_visibility_state
  222. {
  223.         vms_vector vec_to_player;
  224.         player_visibility_state visibility;
  225.         uint8_t initialized = 0;
  226. };
  227.  
  228. struct awareness_t : std::array<player_awareness_type_t, MAX_SEGMENTS>
  229. {
  230. };
  231.  
  232. }
  233.  
  234. }
  235.  
  236. static int ai_evaded;
  237.  
  238. // These globals are set by a call to find_vector_intersection, which is a slow routine,
  239. // so we don't want to call it again (for this object) unless we have to.
  240. static vms_vector  Hit_pos;
  241. static int         Hit_type;
  242. static fvi_info    Hit_data;
  243.  
  244. namespace dcx {
  245. vms_vector      Believed_player_pos;
  246.  
  247. static bool silly_animation_angle(fixang vms_angvec::*const a, const vms_angvec &jp, const vms_angvec &pobjp, const int flinch_attack_scale, vms_angvec &goal_angles, vms_angvec &delta_angles)
  248. {
  249.         const fix delta_angle = jp.*a - pobjp.*a;
  250.         if (!delta_angle)
  251.                 return false;
  252.         goal_angles.*a = jp.*a;
  253.         const fix delta_anim_rate = (delta_angle >= F1_0/2)
  254.                 ? -ANIM_RATE
  255.                 : (delta_angle >= 0)
  256.                         ? ANIM_RATE
  257.                         : (delta_angle >= -F1_0/2)
  258.                                 ? -ANIM_RATE
  259.                                 : ANIM_RATE
  260.         ;
  261.         const fix delta_2 = (flinch_attack_scale != 1)
  262.                 ? delta_anim_rate * flinch_attack_scale
  263.                 : delta_anim_rate;
  264.         delta_angles.*a = delta_2 / DELTA_ANG_SCALE;            // complete revolutions per second
  265.         return true;
  266. }
  267.  
  268. static void frame_animation_angle(fixang vms_angvec::*const a, const fix frametime, const vms_angvec &deltaang, const vms_angvec &goalang, vms_angvec &curang)
  269. {
  270.         fix delta_to_goal = goalang.*a - curang.*a;
  271.         if (delta_to_goal > 32767)
  272.                 delta_to_goal = delta_to_goal - 65536;
  273.         else if (delta_to_goal < -32767)
  274.                 delta_to_goal = 65536 + delta_to_goal;
  275.         if (delta_to_goal)
  276.         {
  277.                 // Due to deltaang.*a being usually small values and frametime getting smaller with higher FPS, the usual use of fixmul will have scaled_delta_angle result in 0 way too often and long, making the robot animation run circles around itself. So multiply deltaang by DELTA_ANG_SCALE when passed to fixmul.
  278.                 const fix scaled_delta_angle = fixmul(deltaang.*a * DELTA_ANG_SCALE, frametime); // fixmul(deltaang.*a, frametime) * DELTA_ANG_SCALE;
  279.                 auto &ca = curang.*a;
  280.                 if (abs(delta_to_goal) < abs(scaled_delta_angle))
  281.                         ca = goalang.*a;
  282.                 else
  283.                         ca += scaled_delta_angle;
  284.         }
  285. }
  286.  
  287. static void move_toward_vector_component_assign(fix vms_vector::*const p, const vms_vector &vec_goal, const fix frametime32, vms_vector &velocity)
  288. {
  289.         velocity.*p = (velocity.*p / 2) + fixmul(vec_goal.*p, frametime32);
  290. }
  291.  
  292. static void move_toward_vector_component_add(fix vms_vector::*const p, const vms_vector &vec_goal, const fix frametime64, const fix difficulty_scale, vms_vector &velocity)
  293. {
  294.         velocity.*p += fixmul(vec_goal.*p, frametime64) * difficulty_scale / 4;
  295. }
  296.  
  297. }
  298.  
  299. #define AIS_MAX 8
  300. #define AIE_MAX 4
  301.  
  302. #ifndef NDEBUG
  303. #if PARALLAX
  304. #if defined(DXX_BUILD_DESCENT_I)
  305. // Index into this array with ailp->mode
  306. constexpr char mode_text[][16] = {
  307.         "STILL",
  308.         "WANDER",
  309.         "FOL_PATH",
  310.         "CHASE_OBJ",
  311.         "RUN_FROM",
  312.         "HIDE",
  313.         "FOL_PATH2",
  314.         "OPEN_DOOR",
  315. };
  316.  
  317. //      Index into this array with aip->behavior
  318. constexpr std::array<char[9], 6> behavior_text{
  319.         "STILL   ",
  320.         "NORMAL  ",
  321.         "HIDE    ",
  322.         "RUN_FROM",
  323.         "FOLPATH ",
  324.         "STATION "
  325. };
  326. #endif
  327. #endif
  328. #endif
  329.  
  330. // Current state indicates where the robot current is, or has just done.
  331. // Transition table between states for an AI object.
  332. // First dimension is trigger event.
  333. // Second dimension is current state.
  334. // Third dimension is goal state.
  335. // Result is new goal state.
  336. // ERR_ means something impossible has happened.
  337. constexpr int8_t Ai_transition_table[AI_MAX_EVENT][AI_MAX_STATE][AI_MAX_STATE] = {
  338.         {
  339.                 // Event = AIE_FIRE, a nearby object fired
  340.                 // none     rest      srch      lock      flin      fire      reco        // CURRENT is rows, GOAL is columns
  341.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // none
  342.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // rest
  343.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // search
  344.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO }, // lock
  345.                 { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO }, // flinch
  346.                 { AIS_ERR_, AIS_FIRE, AIS_FIRE, AIS_FIRE, AIS_FLIN, AIS_FIRE, AIS_RECO }, // fire
  347.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE }  // recoil
  348.         },
  349.  
  350.         // Event = AIE_HITT, a nearby object was hit (or a wall was hit)
  351.         {
  352.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  353.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  354.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  355.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  356.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FLIN},
  357.                 { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO},
  358.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE}
  359.         },
  360.  
  361.         // Event = AIE_COLL, player collided with robot
  362.         {
  363.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  364.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  365.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  366.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_RECO},
  367.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_LOCK, AIS_FLIN, AIS_FLIN},
  368.                 { AIS_ERR_, AIS_REST, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FIRE, AIS_RECO},
  369.                 { AIS_ERR_, AIS_LOCK, AIS_LOCK, AIS_LOCK, AIS_FLIN, AIS_FIRE, AIS_FIRE}
  370.         },
  371.  
  372.         // Event = AIE_HURT, player hurt robot (by firing at and hitting it)
  373.         // Note, this doesn't necessarily mean the robot JUST got hit, only that that is the most recent thing that happened.
  374.         {
  375.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
  376.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
  377.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
  378.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
  379.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
  380.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN},
  381.                 { AIS_ERR_, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN, AIS_FLIN}
  382.         }
  383. };
  384.  
  385. namespace dsx {
  386.  
  387. weapon_id_type get_robot_weapon(const robot_info &ri, const unsigned gun_num)
  388. {
  389. #if defined(DXX_BUILD_DESCENT_I)
  390.         (void)gun_num;
  391. #elif defined(DXX_BUILD_DESCENT_II)
  392.         if (ri.weapon_type2 != weapon_none && !gun_num)
  393.                 return ri.weapon_type2;
  394. #endif
  395.         return ri.weapon_type;
  396. }
  397.  
  398. static int ready_to_fire_weapon1(const ai_local &ailp, fix threshold)
  399. {
  400.         return ailp.next_fire <= threshold;
  401. }
  402.  
  403. static int ready_to_fire_weapon2(const robot_info &robptr, const ai_local &ailp, fix threshold)
  404. {
  405. #if defined(DXX_BUILD_DESCENT_I)
  406.         (void)robptr;
  407.         (void)ailp;
  408.         (void)threshold;
  409.         return 0;
  410. #elif defined(DXX_BUILD_DESCENT_II)
  411.         if (robptr.weapon_type2 == weapon_none)
  412.                 return 0;
  413.         return ailp.next_fire2 <= threshold;
  414. #endif
  415. }
  416.  
  417. // ----------------------------------------------------------------------------
  418. // Return firing status.
  419. // If ready to fire a weapon, return true, else return false.
  420. // Ready to fire a weapon if next_fire <= 0 or next_fire2 <= 0.
  421. static int ready_to_fire_any_weapon(const robot_info &robptr, const ai_local &ailp, fix threshold)
  422. {
  423.         return ready_to_fire_weapon1(ailp, threshold) || ready_to_fire_weapon2(robptr, ailp, threshold);
  424. }
  425.  
  426. // ---------------------------------------------------------------------------------------------------------------------
  427. //      Given a behavior, set initial mode.
  428. ai_mode ai_behavior_to_mode(ai_behavior behavior)
  429. {
  430.         switch (behavior) {
  431.                 case ai_behavior::AIB_STILL:
  432.                         return ai_mode::AIM_STILL;
  433.                 case ai_behavior::AIB_NORMAL:
  434.                         return ai_mode::AIM_CHASE_OBJECT;
  435.                 case ai_behavior::AIB_RUN_FROM:
  436.                         return ai_mode::AIM_RUN_FROM_OBJECT;
  437.                 case ai_behavior::AIB_STATION:
  438.                         return ai_mode::AIM_STILL;
  439. #if defined(DXX_BUILD_DESCENT_I)
  440.                 case ai_behavior::AIB_HIDE:
  441.                         return ai_mode::AIM_HIDE;
  442.                 case ai_behavior::AIB_FOLLOW_PATH:
  443.                         return ai_mode::AIM_FOLLOW_PATH;
  444. #elif defined(DXX_BUILD_DESCENT_II)
  445.                 case ai_behavior::AIB_BEHIND:
  446.                         return ai_mode::AIM_BEHIND;
  447.                 case ai_behavior::AIB_SNIPE:
  448.                         return ai_mode::AIM_STILL;      //      Changed, 09/13/95, MK, snipers are still until they see you or are hit.
  449.                 case ai_behavior::AIB_FOLLOW:
  450.                         return ai_mode::AIM_FOLLOW_PATH;
  451. #endif
  452.                 default:        Int3(); //      Contact Mike: Error, illegal behavior type
  453.         }
  454.  
  455.         return ai_mode::AIM_STILL;
  456. }
  457.  
  458. // ---------------------------------------------------------------------------------------------------------------------
  459. //      Call every time the player starts a new ship.
  460. void ai_init_boss_for_ship(void)
  461. {
  462. #if defined(DXX_BUILD_DESCENT_II)
  463.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  464.         BossUniqueState.Boss_hit_time = -F1_0*10;
  465. #endif
  466. }
  467.  
  468. static void boss_init_all_segments(const segment_array &Segments, const object &boss_objnum)
  469. {
  470.         auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
  471.         auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
  472.         if (!Boss_teleport_segs.empty())
  473.                 return; // already have boss segs
  474.  
  475.         init_boss_segments(Segments, boss_objnum, Boss_gate_segs, 0, 0);
  476.        
  477.         init_boss_segments(Segments, boss_objnum, Boss_teleport_segs, 1, 0);
  478. #if defined(DXX_BUILD_DESCENT_II)
  479.         if (Boss_teleport_segs.size() < 2)
  480.                 init_boss_segments(Segments, boss_objnum, Boss_teleport_segs, 1, 1);
  481. #endif
  482. }
  483.  
  484. // ---------------------------------------------------------------------------------------------------------------------
  485. //      initial_mode == -1 means leave mode unchanged.
  486. void init_ai_object(const vmobjptridx_t objp, ai_behavior behavior, const imsegidx_t hide_segment)
  487. {
  488.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  489.         ai_static       *const aip = &objp->ctype.ai_info;
  490.         ai_local                *const ailp = &aip->ail;
  491.  
  492.         *ailp = {};
  493.  
  494.         if (static_cast<unsigned>(behavior) == 0) {
  495.                 behavior = ai_behavior::AIB_NORMAL;
  496.                 aip->behavior = behavior;
  497.         }
  498.  
  499.         //      mode is now set from the Robot dialog, so this should get overwritten.
  500.         ailp->mode = ai_mode::AIM_STILL;
  501.  
  502.         ailp->previous_visibility = player_visibility_state::no_line_of_sight;
  503.  
  504.         {
  505.                 aip->behavior = behavior;
  506.                 ailp->mode = ai_behavior_to_mode(aip->behavior);
  507.         }
  508.  
  509.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  510.         auto &robptr = Robot_info[get_robot_id(objp)];
  511. #if defined(DXX_BUILD_DESCENT_II)
  512.         if (robot_is_companion(robptr)) {
  513.                 auto &BuddyState = LevelUniqueObjectState.BuddyState;
  514.                 BuddyState.Buddy_objnum = objp;
  515.                 ailp->mode = ai_mode::AIM_GOTO_PLAYER;
  516.         }
  517.  
  518.         if (robot_is_thief(robptr)) {
  519.                 aip->behavior = ai_behavior::AIB_SNIPE;
  520.                 ailp->mode = ai_mode::AIM_THIEF_WAIT;
  521.         }
  522.  
  523.         if (robptr.attack_type) {
  524.                 aip->behavior = ai_behavior::AIB_NORMAL;
  525.                 ailp->mode = ai_behavior_to_mode(aip->behavior);
  526.         }
  527. #endif
  528.  
  529.         // This is astonishingly stupid!  This routine gets called by matcens! KILL KILL KILL!!! Point_segs_free_ptr = Point_segs;
  530.  
  531.         objp->mtype.phys_info.velocity = {};
  532.         ailp->player_awareness_time = 0;
  533.         ailp->player_awareness_type = player_awareness_type_t::PA_NONE;
  534.         aip->GOAL_STATE = AIS_SRCH;
  535.         aip->CURRENT_STATE = AIS_REST;
  536.         ailp->time_player_seen = GameTime64;
  537.         ailp->next_misc_sound_time = GameTime64;
  538.         ailp->time_player_sound_attacked = GameTime64;
  539.  
  540.         aip->hide_segment = hide_segment;
  541.         ailp->goal_segment = hide_segment;
  542.         aip->hide_index = -1;                   // This means the path has not yet been created.
  543.         aip->cur_path_index = 0;
  544.  
  545.         aip->SKIP_AI_COUNT = 0;
  546.  
  547.         if (robptr.cloak_type == RI_CLOAKED_ALWAYS)
  548.                 aip->CLOAKED = 1;
  549.         else
  550.                 aip->CLOAKED = 0;
  551.  
  552.         objp->mtype.phys_info.flags |= (PF_BOUNCE | PF_TURNROLL);
  553.        
  554.         aip->REMOTE_OWNER = -1;
  555.  
  556. #if defined(DXX_BUILD_DESCENT_II)
  557.         aip->dying_sound_playing = 0;
  558.         aip->dying_start_time = 0;
  559. #endif
  560.         aip->danger_laser_num = object_none;
  561.  
  562.         if (robptr.boss_flag
  563. #if DXX_USE_EDITOR
  564.                 && !EditorWindow
  565. #endif
  566.                 )
  567.         {
  568.                 BossUniqueState = {};
  569.                 boss_init_all_segments(Segments, objp);
  570.         }
  571. }
  572.  
  573. // ---------------------------------------------------------------------------------------------------------------------
  574. void init_ai_objects(void)
  575. {
  576.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  577.         auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
  578.         auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
  579.         auto &Objects = LevelUniqueObjectState.Objects;
  580.         auto &vmobjptridx = Objects.vmptridx;
  581.         Point_segs_free_ptr = Point_segs.begin();
  582.         Boss_gate_segs.clear();
  583.         Boss_teleport_segs.clear();
  584.  
  585.         range_for (const auto &&o, vmobjptridx)
  586.         {
  587.                 auto &obj = *o;
  588.                 if (obj.type == OBJ_ROBOT && obj.control_type == CT_AI)
  589.                         init_ai_object(o, obj.ctype.ai_info.behavior, obj.ctype.ai_info.hide_segment);
  590.         }
  591.  
  592.         BossUniqueState.Boss_dying_sound_playing = 0;
  593.         BossUniqueState.Boss_dying = 0;
  594.  
  595.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  596. #define D1_Boss_gate_interval   F1_0*5 - Difficulty_level*F1_0/2
  597. #if defined(DXX_BUILD_DESCENT_I)
  598.         GameUniqueState.Boss_gate_interval = D1_Boss_gate_interval;
  599. #elif defined(DXX_BUILD_DESCENT_II)
  600.         ai_do_cloak_stuff();
  601.  
  602.         init_buddy_for_level();
  603.  
  604.         if (EMULATING_D1)
  605.         {
  606.                 LevelSharedBossState.Boss_cloak_interval = d_level_shared_boss_state::D1_Boss_cloak_interval::value;
  607.                 LevelSharedBossState.Boss_teleport_interval = d_level_shared_boss_state::D1_Boss_teleport_interval::value;
  608.                 GameUniqueState.Boss_gate_interval = D1_Boss_gate_interval;
  609.         }
  610.         else
  611.         {
  612.                 GameUniqueState.Boss_gate_interval = F1_0*4 - Difficulty_level*i2f(2)/3;
  613.                 if (Current_level_num == Last_level)
  614.                 {
  615.                 LevelSharedBossState.Boss_teleport_interval = F1_0*10;
  616.                 LevelSharedBossState.Boss_cloak_interval = F1_0*15;                                     //      Time between cloaks
  617.                 } else
  618.                 {
  619.                 LevelSharedBossState.Boss_teleport_interval = F1_0*7;
  620.                 LevelSharedBossState.Boss_cloak_interval = F1_0*10;                                     //      Time between cloaks
  621.                 }
  622.         }
  623. #endif
  624. #undef D1_Boss_gate_interval
  625. }
  626.  
  627. //-------------------------------------------------------------------------------------------
  628. void ai_turn_towards_vector(const vms_vector &goal_vector, object_base &objp, fix rate)
  629. {
  630.         //      Not all robots can turn, eg, SPECIAL_REACTOR_ROBOT
  631.         if (rate == 0)
  632.                 return;
  633.  
  634.         if (objp.type == OBJ_ROBOT && (get_robot_id(objp) == BABY_SPIDER_ID)) {
  635.                 physics_turn_towards_vector(goal_vector, objp, rate);
  636.                 return;
  637.         }
  638.  
  639.         auto new_fvec = goal_vector;
  640.  
  641.         const fix dot = vm_vec_dot(goal_vector, objp.orient.fvec);
  642.  
  643.         if (dot < (F1_0 - FrameTime/2)) {
  644.                 fix     new_scale = fixdiv(FrameTime * AI_TURN_SCALE, rate);
  645.                 vm_vec_scale(new_fvec, new_scale);
  646.                 vm_vec_add2(new_fvec, objp.orient.fvec);
  647.                 auto mag = vm_vec_normalize_quick(new_fvec);
  648.                 if (mag < F1_0/256) {
  649.                         new_fvec = goal_vector;         //      if degenerate vector, go right to goal
  650.                 }
  651.         }
  652.  
  653. #if defined(DXX_BUILD_DESCENT_II)
  654.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  655.         if (const auto Seismic_tremor_magnitude = LevelUniqueSeismicState.Seismic_tremor_magnitude)
  656.         {
  657.                 fix                     scale;
  658.                 scale = fixdiv(2*Seismic_tremor_magnitude, Robot_info[get_robot_id(objp)].mass);
  659.                 vm_vec_scale_add2(new_fvec, make_random_vector(), scale);
  660.         }
  661. #endif
  662.  
  663.         vm_vector_2_matrix(objp.orient, new_fvec, nullptr, &objp.orient.rvec);
  664. }
  665.  
  666. #if defined(DXX_BUILD_DESCENT_I)
  667. static void ai_turn_randomly(const vms_vector &vec_to_player, object_base &obj, const fix rate, const player_visibility_state previous_visibility)
  668. {
  669.         vms_vector      curvec;
  670.  
  671.         //      Random turning looks too stupid, so 1/4 of time, cheat.
  672.         if (player_is_visible(previous_visibility))
  673.                 if (d_rand() > 0x7400) {
  674.                         ai_turn_towards_vector(vec_to_player, obj, rate);
  675.                         return;
  676.                 }
  677. //--debug--     if (d_rand() > 0x6000)
  678. //--debug--             Prevented_turns++;
  679.  
  680.         curvec = obj.mtype.phys_info.rotvel;
  681.  
  682.         curvec.y += F1_0/64;
  683.  
  684.         curvec.x += curvec.y/6;
  685.         curvec.y += curvec.z/4;
  686.         curvec.z += curvec.x/10;
  687.  
  688.         if (abs(curvec.x) > F1_0/8) curvec.x /= 4;
  689.         if (abs(curvec.y) > F1_0/8) curvec.y /= 4;
  690.         if (abs(curvec.z) > F1_0/8) curvec.z /= 4;
  691.  
  692.         obj.mtype.phys_info.rotvel = curvec;
  693.  
  694. }
  695. #endif
  696.  
  697. //      Overall_agitation affects:
  698. //              Widens field of view.  Field of view is in range 0..1 (specified in bitmaps.tbl as N/360 degrees).
  699. //                      Overall_agitation/128 subtracted from field of view, making robots see wider.
  700. //              Increases distance to which robot will search to create path to player by Overall_agitation/8 segments.
  701. //              Decreases wait between fire times by Overall_agitation/64 seconds.
  702.  
  703.  
  704. // --------------------------------------------------------------------------------------------------------------------
  705. //      Returns:
  706. //              0               Player is not visible from object, obstruction or something.
  707. //              1               Player is visible, but not in field of view.
  708. //              2               Player is visible and in field of view.
  709. //      Note: Uses Believed_player_pos as player's position for cloak effect.
  710. //      NOTE: Will destructively modify *pos if *pos is outside the mine.
  711. player_visibility_state player_is_visible_from_object(const vmobjptridx_t objp, vms_vector &pos, const fix field_of_view, const vms_vector &vec_to_player)
  712. {
  713.         fix                     dot;
  714.         fvi_query       fq;
  715.  
  716. #if defined(DXX_BUILD_DESCENT_II)
  717.         //      Assume that robot's gun tip is in same segment as robot's center.
  718.         if (objp->control_type == CT_AI)
  719.                 objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_GUNSEG;
  720. #endif
  721.  
  722.         fq.p0                                           = &pos;
  723.         if ((pos.x != objp->pos.x) || (pos.y != objp->pos.y) || (pos.z != objp->pos.z)) {
  724.                 auto &Segments = LevelSharedSegmentState.get_segments();
  725.                 const auto &&segnum = find_point_seg(LevelSharedSegmentState, pos, Segments.vcptridx(objp->segnum));
  726.                 if (segnum == segment_none) {
  727.                         fq.startseg = objp->segnum;
  728.                         pos = objp->pos;
  729.                         move_towards_segment_center(LevelSharedSegmentState, objp);
  730.                 } else
  731.                 {
  732. #if defined(DXX_BUILD_DESCENT_II)
  733.                         if (segnum != objp->segnum) {
  734.                                 if (objp->control_type == CT_AI)
  735.                                         objp->ctype.ai_info.SUB_FLAGS |= SUB_FLAGS_GUNSEG;
  736.                         }
  737. #endif
  738.                         fq.startseg = segnum;
  739.                 }
  740.         } else
  741.                 fq.startseg                     = objp->segnum;
  742.         fq.p1                                           = &Believed_player_pos;
  743.         fq.rad                                  = F1_0/4;
  744.         fq.thisobjnum                   = objp;
  745.         fq.ignore_obj_list.first = nullptr;
  746.         fq.flags                                        = FQ_TRANSWALL; // -- Why were we checking objects? | FQ_CHECK_OBJS;            //what about trans walls???
  747.  
  748.         Hit_type = find_vector_intersection(fq, Hit_data);
  749.  
  750.         Hit_pos = Hit_data.hit_pnt;
  751.  
  752.         if (Hit_type == HIT_NONE)
  753.         {
  754.                 dot = vm_vec_dot(vec_to_player, objp->orient.fvec);
  755.                 if (dot > field_of_view - (Overall_agitation << 9)) {
  756.                         return player_visibility_state::visible_and_in_field_of_view;
  757.                 } else {
  758.                         return player_visibility_state::visible_not_in_field_of_view;
  759.                 }
  760.         } else {
  761.                 return player_visibility_state::no_line_of_sight;
  762.         }
  763. }
  764.  
  765. // ------------------------------------------------------------------------------------------------------------------
  766. //      Return 1 if animates, else return 0
  767. static int do_silly_animation(object &objp)
  768. {
  769.         int                             robot_type, gun_num, robot_state;
  770.         polyobj_info    *const pobj_info = &objp.rtype.pobj_info;
  771.         auto &aip = objp.ctype.ai_info;
  772.         int                             num_guns, at_goal;
  773.         int                             attack_type;
  774.         int                             flinch_attack_scale = 1;
  775.  
  776.         robot_type = get_robot_id(objp);
  777.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  778.         auto &Robot_joints = LevelSharedRobotJointState.Robot_joints;
  779.         auto &robptr = Robot_info[robot_type];
  780.         num_guns = robptr.n_guns;
  781.         attack_type = robptr.attack_type;
  782.  
  783.         if (num_guns == 0) {
  784.                 return 0;
  785.         }
  786.  
  787.         //      This is a hack.  All positions should be based on goal_state, not GOAL_STATE.
  788.         robot_state = Mike_to_matt_xlate[aip.GOAL_STATE];
  789.         // previous_robot_state = Mike_to_matt_xlate[aip->CURRENT_STATE];
  790.  
  791.         if (attack_type) // && ((robot_state == AS_FIRE) || (robot_state == AS_RECOIL)))
  792.                 flinch_attack_scale = Attack_scale;
  793.         else if ((robot_state == AS_FLINCH) || (robot_state == AS_RECOIL))
  794.                 flinch_attack_scale = Flinch_scale;
  795.  
  796.         at_goal = 1;
  797.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  798.         for (gun_num=0; gun_num <= num_guns; gun_num++) {
  799.                 const auto &&ras = robot_get_anim_state(Robot_info, Robot_joints, robot_type, gun_num, robot_state);
  800.  
  801.                 auto &ail = aip.ail;
  802.                 range_for (auto &jr, ras)
  803.                 {
  804.                         unsigned jointnum = jr.jointnum;
  805.                         auto &jp = jr.angles;
  806.                         vms_angvec      *pobjp = &pobj_info->anim_angles[jointnum];
  807.  
  808.                         if (jointnum >= Polygon_models[objp.rtype.pobj_info.model_num].n_models) {
  809.                                 Int3();         // Contact Mike: incompatible data, illegal jointnum, problem in pof file?
  810.                                 continue;
  811.                         }
  812.                         auto &goal_angles = ail.goal_angles[jointnum];
  813.                         auto &delta_angles = ail.delta_angles[jointnum];
  814.                         const auto animate_p = silly_animation_angle(&vms_angvec::p, jp, *pobjp, flinch_attack_scale, goal_angles, delta_angles);
  815.                         const auto animate_b = silly_animation_angle(&vms_angvec::b, jp, *pobjp, flinch_attack_scale, goal_angles, delta_angles);
  816.                         const auto animate_h = silly_animation_angle(&vms_angvec::h, jp, *pobjp, flinch_attack_scale, goal_angles, delta_angles);
  817.                         if (gun_num == 0)
  818.                         {
  819.                                 if (animate_p || animate_b || animate_h)
  820.                                         at_goal = 0;
  821.                         }
  822.                 }
  823.  
  824.                 if (at_goal) {
  825.                         //ai_static     *aip = &objp->ctype.ai_info;
  826.                         ail.achieved_state[gun_num] = ail.goal_state[gun_num];
  827.                         if (ail.achieved_state[gun_num] == AIS_RECO)
  828.                                 ail.goal_state[gun_num] = AIS_FIRE;
  829.                         else if (ail.achieved_state[gun_num] == AIS_FLIN)
  830.                                 ail.goal_state[gun_num] = AIS_LOCK;
  831.                 }
  832.         }
  833.  
  834.         if (at_goal == 1) //num_guns)
  835.                 aip.CURRENT_STATE = aip.GOAL_STATE;
  836.  
  837.         return 1;
  838. }
  839.  
  840. //      ------------------------------------------------------------------------------------------
  841. //      Move all sub-objects in an object towards their goals.
  842. //      Current orientation of object is at:    pobj_info.anim_angles
  843. //      Goal orientation of object is at:               ai_info.goal_angles
  844. //      Delta orientation of object is at:              ai_info.delta_angles
  845. static void ai_frame_animation(object &objp)
  846. {
  847.         int     joint;
  848.         auto &pobj_info = objp.rtype.pobj_info;
  849.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  850.         const auto num_joints = Polygon_models[pobj_info.model_num].n_models;
  851.         const auto &ail = objp.ctype.ai_info.ail;
  852.         for (joint=1; joint<num_joints; joint++) {
  853.                 auto &curang = pobj_info.anim_angles[joint];
  854.                 auto &goalang = ail.goal_angles[joint];
  855.                 auto &deltaang = ail.delta_angles[joint];
  856.  
  857.                 const fix frametime = FrameTime;
  858.                 frame_animation_angle(&vms_angvec::p, frametime, deltaang, goalang, curang);
  859.                 frame_animation_angle(&vms_angvec::b, frametime, deltaang, goalang, curang);
  860.                 frame_animation_angle(&vms_angvec::h, frametime, deltaang, goalang, curang);
  861.         }
  862. }
  863.  
  864. // ----------------------------------------------------------------------------------
  865. static void set_next_fire_time(object &objp, ai_local &ailp, const robot_info &robptr, const unsigned gun_num)
  866. {
  867.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  868. #if defined(DXX_BUILD_DESCENT_I)
  869.         (void)objp;
  870.         (void)gun_num;
  871.         ailp.rapidfire_count++;
  872.  
  873.         if (ailp.rapidfire_count < robptr.rapidfire_count[Difficulty_level]) {
  874.                 ailp.next_fire = min(F1_0/8, robptr.firing_wait[Difficulty_level]/2);
  875.         } else {
  876.                 ailp.rapidfire_count = 0;
  877.                 ailp.next_fire = robptr.firing_wait[Difficulty_level];
  878.         }
  879. #elif defined(DXX_BUILD_DESCENT_II)
  880.         //      For guys in snipe mode, they have a 50% shot of getting this shot in free.
  881.         if ((gun_num != 0) || (robptr.weapon_type2 == weapon_none))
  882.                 if ((objp.ctype.ai_info.behavior != ai_behavior::AIB_SNIPE) || (d_rand() > 16384))
  883.                         ailp.rapidfire_count++;
  884.  
  885.         //      Old way, 10/15/95: Continuous rapidfire if rapidfire_count set.
  886. // --   if (((robptr.weapon_type2 == -1) || (gun_num != 0)) && (ailp->rapidfire_count < robptr.rapidfire_count[Difficulty_level])) {
  887. // --           ailp->next_fire = min(F1_0/8, robptr.firing_wait[Difficulty_level]/2);
  888. // --   } else {
  889. // --           if ((robptr.weapon_type2 == -1) || (gun_num != 0)) {
  890. // --                   ailp->rapidfire_count = 0;
  891. // --                   ailp->next_fire = robptr.firing_wait[Difficulty_level];
  892. // --           } else
  893. // --                   ailp->next_fire2 = robptr.firing_wait2[Difficulty_level];
  894. // --   }
  895.  
  896.         if ((gun_num != 0 || robptr.weapon_type2 == weapon_none) && ailp.rapidfire_count < robptr.rapidfire_count[Difficulty_level])
  897.         {
  898.                 ailp.next_fire = min(F1_0/8, robptr.firing_wait[Difficulty_level]/2);
  899.         } else {
  900.                 if ((robptr.weapon_type2 == weapon_none) || (gun_num != 0)) {
  901.                         ailp.next_fire = robptr.firing_wait[Difficulty_level];
  902.                         if (ailp.rapidfire_count >= robptr.rapidfire_count[Difficulty_level])
  903.                                 ailp.rapidfire_count = 0;
  904.                 } else
  905.                         ailp.next_fire2 = robptr.firing_wait2[Difficulty_level];
  906.         }
  907. #endif
  908. }
  909.  
  910. // ----------------------------------------------------------------------------------
  911. //      When some robots collide with the player, they attack.
  912. //      If player is cloaked, then robot probably didn't actually collide, deal with that here.
  913. void do_ai_robot_hit_attack(const vmobjptridx_t robot, const vmobjptridx_t playerobj, const vms_vector &collision_point)
  914. {
  915.         ai_local &ailp = robot->ctype.ai_info.ail;
  916.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  917.         auto &robptr = Robot_info[get_robot_id(robot)];
  918.  
  919. //#ifndef NDEBUG
  920.         if (cheats.robotfiringsuspended)
  921.                 return;
  922. //#endif
  923.  
  924.         //      If player is dead, stop firing.
  925.         object &plrobj = *playerobj;
  926.         if (plrobj.type == OBJ_GHOST)
  927.                 return;
  928.  
  929.         if (robptr.attack_type == 1) {
  930.                 if (ready_to_fire_weapon1(ailp, 0)) {
  931.                         auto &player_info = plrobj.ctype.player_info;
  932.                         if (!(player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
  933.                                 if (vm_vec_dist_quick(plrobj.pos, robot->pos) < robot->size + plrobj.size + F1_0*2)
  934.                                 {
  935.                                         collide_player_and_nasty_robot( playerobj, robot, collision_point );
  936. #if defined(DXX_BUILD_DESCENT_II)
  937.                                         auto &energy = player_info.energy;
  938.                                         if (robptr.energy_drain && energy) {
  939.                                                 energy -= robptr.energy_drain * F1_0;
  940.                                                 if (energy < 0)
  941.                                                         energy = 0;
  942.                                         }
  943. #endif
  944.                                 }
  945.  
  946.                         robot->ctype.ai_info.GOAL_STATE = AIS_RECO;
  947.                         set_next_fire_time(robot, ailp, robptr, 1);     //      1 = gun_num: 0 is special (uses next_fire2)
  948.                 }
  949.         }
  950.  
  951. }
  952.  
  953. #if defined(DXX_BUILD_DESCENT_II)
  954. // --------------------------------------------------------------------------------------------------------------------
  955. //      Computes point at which projectile fired by robot can hit player given positions, player vel, elapsed time
  956. static fix compute_lead_component(fix player_pos, fix robot_pos, fix player_vel, fix elapsed_time)
  957. {
  958.         return fixdiv(player_pos - robot_pos, elapsed_time) + player_vel;
  959. }
  960.  
  961. static void compute_lead_component(fix vms_vector::*const m, vms_vector &out, const vms_vector &believed_player_pos, const vms_vector &fire_point, const vms_vector &velocity, const fix projected_time)
  962. {
  963.         out.*m = compute_lead_component(believed_player_pos.*m, fire_point.*m, velocity.*m, projected_time);
  964. }
  965.  
  966. // --------------------------------------------------------------------------------------------------------------------
  967. //      Lead the player, returning point to fire at in fire_point.
  968. //      Rules:
  969. //              Player not cloaked
  970. //              Player must be moving at a speed >= MIN_LEAD_SPEED
  971. //              Player not farther away than MAX_LEAD_DISTANCE
  972. //              dot(vector_to_player, player_direction) must be in -LEAD_RANGE..LEAD_RANGE
  973. //              if firing a matter weapon, less leading, based on skill level.
  974. static int lead_player(const object_base &objp, const vms_vector &fire_point, const vms_vector &believed_player_pos, int gun_num, vms_vector &fire_vec)
  975. {
  976.         const auto &plrobj = *ConsoleObject;
  977.         if (plrobj.ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
  978.                 return 0;
  979.  
  980.         const auto &velocity = plrobj.mtype.phys_info.velocity;
  981.         auto player_movement_dir = velocity;
  982.         const fix player_speed = vm_vec_normalize_quick(player_movement_dir);
  983.  
  984.         if (player_speed < MIN_LEAD_SPEED)
  985.                 return 0;
  986.  
  987.         auto vec_to_player = vm_vec_sub(believed_player_pos, fire_point);
  988.         const fix dist_to_player = vm_vec_normalize_quick(vec_to_player);
  989.         if (dist_to_player > MAX_LEAD_DISTANCE)
  990.                 return 0;
  991.  
  992.         const fix dot = vm_vec_dot(vec_to_player, player_movement_dir);
  993.  
  994.         if ((dot < -LEAD_RANGE) || (dot > LEAD_RANGE))
  995.                 return 0;
  996.  
  997.         //      Looks like it might be worth trying to lead the player.
  998.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  999.         const auto weapon_type = get_robot_weapon(Robot_info[get_robot_id(objp)], gun_num);
  1000.  
  1001.         const weapon_info *const wptr = &Weapon_info[weapon_type];
  1002.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1003.         fix max_weapon_speed = wptr->speed[Difficulty_level];
  1004.         if (max_weapon_speed < F1_0)
  1005.                 return 0;
  1006.  
  1007.         //      Matter weapons:
  1008.         //      At Rookie or Trainee, don't lead at all.
  1009.         //      At higher skill levels, don't lead as well.  Accomplish this by screwing up max_weapon_speed.
  1010.         if (wptr->matter)
  1011.         {
  1012.                 if (Difficulty_level <= 1)
  1013.                         return 0;
  1014.                 else
  1015.                         max_weapon_speed *= (NDL-Difficulty_level);
  1016.         }
  1017.  
  1018.         const fix projected_time = fixdiv(dist_to_player, max_weapon_speed);
  1019.  
  1020.         compute_lead_component(&vms_vector::x, fire_vec, believed_player_pos, fire_point, velocity, projected_time);
  1021.         compute_lead_component(&vms_vector::y, fire_vec, believed_player_pos, fire_point, velocity, projected_time);
  1022.         compute_lead_component(&vms_vector::z, fire_vec, believed_player_pos, fire_point, velocity, projected_time);
  1023.  
  1024.         vm_vec_normalize_quick(fire_vec);
  1025.  
  1026.         Assert(vm_vec_dot(fire_vec, objp.orient.fvec) < 3*F1_0/2);
  1027.  
  1028.         //      Make sure not firing at especially strange angle.  If so, try to correct.  If still bad, give up after one try.
  1029.         if (vm_vec_dot(fire_vec, objp.orient.fvec) < F1_0/2) {
  1030.                 vm_vec_add2(fire_vec, vec_to_player);
  1031.                 vm_vec_scale(fire_vec, F1_0/2);
  1032.                 if (vm_vec_dot(fire_vec, objp.orient.fvec) < F1_0/2) {
  1033.                         return 0;
  1034.                 }
  1035.         }
  1036.  
  1037.         return 1;
  1038. }
  1039. #endif
  1040.  
  1041. // --------------------------------------------------------------------------------------------------------------------
  1042. //      Note: Parameter vec_to_player is only passed now because guns which aren't on the forward vector from the
  1043. //      center of the robot will not fire right at the player.  We need to aim the guns at the player.  Barring that, we cheat.
  1044. //      When this routine is complete, the parameter vec_to_player should not be necessary.
  1045. static void ai_fire_laser_at_player(const d_level_shared_segment_state &LevelSharedSegmentState, const vmobjptridx_t obj, const player_info &player_info, const vms_vector &fire_point, const int gun_num
  1046. #if defined(DXX_BUILD_DESCENT_II)
  1047.                                                                         , const vms_vector &believed_player_pos
  1048. #endif
  1049.                                                                         )
  1050. {
  1051.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  1052.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1053.         const auto powerup_flags = player_info.powerup_flags;
  1054.         ai_local &ailp = obj->ctype.ai_info.ail;
  1055.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1056.         auto &robptr = Robot_info[get_robot_id(obj)];
  1057.         vms_vector      fire_vec;
  1058.         vms_vector      bpp_diff;
  1059.  
  1060.         Assert(robptr.attack_type == 0);        //      We should never be coming here for the green guy, as he has no laser!
  1061.  
  1062.         if (cheats.robotfiringsuspended)
  1063.                 return;
  1064.  
  1065.         if (obj->control_type == CT_MORPH)
  1066.                 return;
  1067.  
  1068.         //      If player is exploded, stop firing.
  1069.         if (Player_dead_state == player_dead_state::exploded)
  1070.                 return;
  1071.  
  1072. #if defined(DXX_BUILD_DESCENT_II)
  1073.         //      If this robot is only awake because a camera woke it up, don't fire.
  1074.         if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE)
  1075.                 return;
  1076.  
  1077.         if (obj->ctype.ai_info.dying_start_time)
  1078.                 return;         //      No firing while in death roll.
  1079.  
  1080.         //      Don't let the boss fire while in death roll.  Sorry, this is the easiest way to do this.
  1081.         //      If you try to key the boss off obj->ctype.ai_info.dying_start_time, it will hose the endlevel stuff.
  1082.         if (BossUniqueState.Boss_dying_start_time && Robot_info[get_robot_id(obj)].boss_flag)
  1083.                 return;
  1084. #endif
  1085.  
  1086.         //      If player is cloaked, maybe don't fire based on how long cloaked and randomness.
  1087.         if (powerup_flags & PLAYER_FLAGS_CLOAKED) {
  1088.                 fix64   cloak_time = Ai_cloak_info[static_cast<imobjptridx_t::index_type>(obj) % MAX_AI_CLOAK_INFO].last_time;
  1089.  
  1090.                 if (GameTime64 - cloak_time > CLOAK_TIME_MAX/4)
  1091.                         if (d_rand() > fixdiv(GameTime64 - cloak_time, CLOAK_TIME_MAX)/2) {
  1092.                                 set_next_fire_time(obj, ailp, robptr, gun_num);
  1093.                                 return;
  1094.                         }
  1095.         }
  1096.  
  1097. #if defined(DXX_BUILD_DESCENT_I)
  1098.         (void)LevelSharedSegmentState;
  1099.         //      Set position to fire at based on difficulty level.
  1100.         bpp_diff.x = Believed_player_pos.x + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
  1101.         bpp_diff.y = Believed_player_pos.y + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
  1102.         bpp_diff.z = Believed_player_pos.z + (d_rand()-16384) * (NDL-Difficulty_level-1) * 4;
  1103.  
  1104.         //      Half the time fire at the player, half the time lead the player.
  1105.         if (d_rand() > 16384) {
  1106.  
  1107.                 vm_vec_normalized_dir_quick(fire_vec, bpp_diff, fire_point);
  1108.  
  1109.         } else {
  1110.                 // If player is not moving, fire right at him!
  1111.                 //      Note: If the robot fires in the direction of its forward vector, this is bad because the weapon does not
  1112.                 //      come out from the center of the robot; it comes out from the side.  So it is common for the weapon to miss
  1113.                 //      its target.  Ideally, we want to point the guns at the player.  For now, just fire right at the player.
  1114.                 {
  1115.                         vm_vec_normalized_dir_quick(fire_vec, bpp_diff, fire_point);
  1116.                 // Player is moving.  Determine where the player will be at the end of the next frame if he doesn't change his
  1117.                 //      behavior.  Fire at exactly that point.  This isn't exactly what you want because it will probably take the laser
  1118.                 //      a different amount of time to get there, since it will probably be a different distance from the player.
  1119.                 //      So, that's why we write games, instead of guiding missiles...
  1120.                 }
  1121.         }
  1122. #elif defined(DXX_BUILD_DESCENT_II)
  1123.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1124.         auto &vcwallptr = Walls.vcptr;
  1125.         //      Handle problem of a robot firing through a wall because its gun tip is on the other
  1126.         //      side of the wall than the robot's center.  For speed reasons, we normally only compute
  1127.         //      the vector from the gun point to the player.  But we need to know whether the gun point
  1128.         //      is separated from the robot's center by a wall.  If so, don't fire!
  1129.         if (obj->ctype.ai_info.SUB_FLAGS & SUB_FLAGS_GUNSEG) {
  1130.                 //      Well, the gun point is in a different segment than the robot's center.
  1131.                 //      This is almost always ok, but it is not ok if something solid is in between.
  1132.                 //      See if these segments are connected, which should almost always be the case.
  1133.                 auto &Segments = LevelSharedSegmentState.get_segments();
  1134.                 const auto &&csegp = Segments.vcptridx(obj->segnum);
  1135.                 const auto &&gun_segnum = find_point_seg(LevelSharedSegmentState, fire_point, csegp);
  1136.                 const auto conn_side = find_connect_side(gun_segnum, csegp);
  1137.                 if (conn_side != side_none)
  1138.                 {
  1139.                         //      They are connected via conn_side in segment obj->segnum.
  1140.                         //      See if they are unobstructed.
  1141.                         if (!(WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, csegp, conn_side) & WID_FLY_FLAG))
  1142.                         {
  1143.                                 //      Can't fly through, so don't let this bot fire through!
  1144.                                 return;
  1145.                         }
  1146.                 } else {
  1147.                         //      Well, they are not directly connected, so use find_vector_intersection to see if they are unobstructed.
  1148.                         fvi_query       fq;
  1149.                         fvi_info                hit_data;
  1150.                         int                     fate;
  1151.  
  1152.                         fq.startseg                             = obj->segnum;
  1153.                         fq.p0                                           = &obj->pos;
  1154.                         fq.p1                                           = &fire_point;
  1155.                         fq.rad                                  = 0;
  1156.                         fq.thisobjnum                   = obj;
  1157.                         fq.ignore_obj_list.first = nullptr;
  1158.                         fq.flags                                        = FQ_TRANSWALL;
  1159.  
  1160.                         fate = find_vector_intersection(fq, hit_data);
  1161.                         if (fate != HIT_NONE) {
  1162.                                 Int3();         //      This bot's gun is poking through a wall, so don't fire.
  1163.                                 move_towards_segment_center(LevelSharedSegmentState, obj);              //      And decrease chances it will happen again.
  1164.                                 return;
  1165.                         }
  1166.                 }
  1167.         }
  1168.  
  1169.         //      Set position to fire at based on difficulty level and robot's aiming ability
  1170.         fix aim = FIRE_K*F1_0 - (FIRE_K-1)*(robptr.aim << 8);   //      F1_0 in bitmaps.tbl = same as used to be.  Worst is 50% more error.
  1171.  
  1172.         //      Robots aim more poorly during seismic disturbance.
  1173.         if (const auto Seismic_tremor_magnitude = LevelUniqueSeismicState.Seismic_tremor_magnitude)
  1174.         {
  1175.                 fix     temp;
  1176.                 temp = F1_0 - abs(Seismic_tremor_magnitude);
  1177.                 if (temp < F1_0/2)
  1178.                         temp = F1_0/2;
  1179.  
  1180.                 aim = fixmul(aim, temp);
  1181.         }
  1182.  
  1183.         //      Lead the player half the time.
  1184.         //      Note that when leading the player, aim is perfect.  This is probably acceptable since leading is so hacked in.
  1185.         //      Problem is all robots will lead equally badly.
  1186.         if (d_rand() < 16384) {
  1187.                 if (lead_player(obj, fire_point, believed_player_pos, gun_num, fire_vec))               //      Stuff direction to fire at in fire_point.
  1188.                         goto player_led;
  1189.         }
  1190.  
  1191.         {
  1192.         fix dot = 0;
  1193.         unsigned count = 0;                     //      Don't want to sit in this loop forever...
  1194.         while ((count < 4) && (dot < F1_0/4)) {
  1195.                 bpp_diff.x = believed_player_pos.x + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
  1196.                 bpp_diff.y = believed_player_pos.y + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
  1197.                 bpp_diff.z = believed_player_pos.z + fixmul((d_rand()-16384) * (NDL-Difficulty_level-1) * 4, aim);
  1198.  
  1199.                 vm_vec_normalized_dir_quick(fire_vec, bpp_diff, fire_point);
  1200.                 dot = vm_vec_dot(obj->orient.fvec, fire_vec);
  1201.                 count++;
  1202.         }
  1203.         }
  1204. player_led: ;
  1205. #endif
  1206.  
  1207.         const auto weapon_type = get_robot_weapon(robptr, gun_num);
  1208.  
  1209.         Laser_create_new_easy( fire_vec, fire_point, obj, weapon_type, 1);
  1210.  
  1211.         if (Game_mode & GM_MULTI)
  1212.         {
  1213.                 ai_multi_send_robot_position(obj, -1);
  1214.                 multi_send_robot_fire(obj, obj->ctype.ai_info.CURRENT_GUN, fire_vec);
  1215.         }
  1216.  
  1217.         create_awareness_event(obj, player_awareness_type_t::PA_NEARBY_ROBOT_FIRED, LevelUniqueRobotAwarenessState);
  1218.  
  1219.         set_next_fire_time(obj, ailp, robptr, gun_num);
  1220.  
  1221.         //      If the boss fired, allow him to teleport very soon (right after firing, cool!), pending other factors.
  1222.         if (robptr.boss_flag == BOSS_D1 || robptr.boss_flag == BOSS_SUPER)
  1223.                 BossUniqueState.Last_teleport_time -= LevelSharedBossState.Boss_teleport_interval / 2;
  1224. }
  1225.  
  1226. // --------------------------------------------------------------------------------------------------------------------
  1227. //      vec_goal must be normalized, or close to it.
  1228. //      if dot_based set, then speed is based on direction of movement relative to heading
  1229. static void move_towards_vector(object_base &objp, const vms_vector &vec_goal, int dot_based)
  1230. {
  1231.         auto &velocity = objp.mtype.phys_info.velocity;
  1232.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1233.         auto &robptr = Robot_info[get_robot_id(objp)];
  1234.  
  1235.         //      Trying to move towards player.  If forward vector much different than velocity vector,
  1236.         //      bash velocity vector twice as much towards player as usual.
  1237.  
  1238.         const auto vel = vm_vec_normalized_quick(velocity);
  1239.         fix dot = vm_vec_dot(vel, objp.orient.fvec);
  1240.  
  1241. #if defined(DXX_BUILD_DESCENT_I)
  1242.         dot_based = 1;
  1243. #elif defined(DXX_BUILD_DESCENT_II)
  1244.         if (robot_is_thief(robptr))
  1245.                 dot = (F1_0+dot)/2;
  1246. #endif
  1247.  
  1248.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1249.         if (dot_based && (dot < 3*F1_0/4)) {
  1250.                 //      This funny code is supposed to slow down the robot and move his velocity towards his direction
  1251.                 //      more quickly than the general code
  1252.                 const fix frametime32 = FrameTime * 32;
  1253.                 move_toward_vector_component_assign(&vms_vector::x, vec_goal, frametime32, velocity);
  1254.                 move_toward_vector_component_assign(&vms_vector::y, vec_goal, frametime32, velocity);
  1255.                 move_toward_vector_component_assign(&vms_vector::z, vec_goal, frametime32, velocity);
  1256.         } else {
  1257.                 const fix frametime64 = FrameTime * 64;
  1258.                 const fix difficulty_scale = Difficulty_level + 5;
  1259.                 move_toward_vector_component_add(&vms_vector::x, vec_goal, frametime64, difficulty_scale, velocity);
  1260.                 move_toward_vector_component_add(&vms_vector::y, vec_goal, frametime64, difficulty_scale, velocity);
  1261.                 move_toward_vector_component_add(&vms_vector::z, vec_goal, frametime64, difficulty_scale, velocity);
  1262.         }
  1263.  
  1264.         const auto speed = vm_vec_mag_quick(velocity);
  1265.         fix max_speed = robptr.max_speed[Difficulty_level];
  1266.  
  1267.         //      Green guy attacks twice as fast as he moves away.
  1268. #if defined(DXX_BUILD_DESCENT_I)
  1269.         if (robptr.attack_type == 1)
  1270. #elif defined(DXX_BUILD_DESCENT_II)
  1271.         if (robptr.attack_type == 1 || robot_is_thief(robptr) || robptr.kamikaze)
  1272. #endif
  1273.                 max_speed *= 2;
  1274.  
  1275.         if (speed > max_speed) {
  1276.                 velocity.x = (velocity.x * 3) / 4;
  1277.                 velocity.y = (velocity.y * 3) / 4;
  1278.                 velocity.z = (velocity.z * 3) / 4;
  1279.         }
  1280. }
  1281.  
  1282. // --------------------------------------------------------------------------------------------------------------------
  1283. #if defined(DXX_BUILD_DESCENT_I)
  1284. static
  1285. #endif
  1286. void move_towards_player(object &objp, const vms_vector &vec_to_player)
  1287. //      vec_to_player must be normalized, or close to it.
  1288. {
  1289.         move_towards_vector(objp, vec_to_player, 1);
  1290. }
  1291.  
  1292. // --------------------------------------------------------------------------------------------------------------------
  1293. //      I am ashamed of this: fast_flag == -1 means normal slide about.  fast_flag = 0 means no evasion.
  1294. static void move_around_player(const vmobjptridx_t objp, const player_flags powerup_flags, const vms_vector &vec_to_player, int fast_flag)
  1295. {
  1296.         physics_info    *pptr = &objp->mtype.phys_info;
  1297.         vms_vector              evade_vector;
  1298.  
  1299.         if (fast_flag == 0)
  1300.                 return;
  1301.  
  1302.         const unsigned dir = ((objp) ^ ((d_tick_count + 3*(objp)) >> 5)) & 3;
  1303.         switch (dir) {
  1304.                 case 0:
  1305.                         evade_vector.x = vec_to_player.z;
  1306.                         evade_vector.y = vec_to_player.y;
  1307.                         evade_vector.z = -vec_to_player.x;
  1308.                         break;
  1309.                 case 1:
  1310.                         evade_vector.x = -vec_to_player.z;
  1311.                         evade_vector.y = vec_to_player.y;
  1312.                         evade_vector.z = vec_to_player.x;
  1313.                         break;
  1314.                 case 2:
  1315.                         evade_vector.x = -vec_to_player.y;
  1316.                         evade_vector.y = vec_to_player.x;
  1317.                         evade_vector.z = vec_to_player.z;
  1318.                         break;
  1319.                 case 3:
  1320.                         evade_vector.x = vec_to_player.y;
  1321.                         evade_vector.y = -vec_to_player.x;
  1322.                         evade_vector.z = vec_to_player.z;
  1323.                         break;
  1324.                 default:
  1325.                         throw std::runtime_error("move_around_player: bad case");
  1326.         }
  1327.         const auto frametime32 = FrameTime * 32;
  1328.         evade_vector.x = fixmul(evade_vector.x, frametime32);
  1329.         evade_vector.y = fixmul(evade_vector.y, frametime32);
  1330.         evade_vector.z = fixmul(evade_vector.z, frametime32);
  1331.  
  1332.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1333.         auto &robptr = Robot_info[get_robot_id(objp)];
  1334.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1335.         //      Note: -1 means normal circling about the player.  > 0 means fast evasion.
  1336.         if (fast_flag > 0) {
  1337.                 fix     dot;
  1338.  
  1339.                 //      Only take evasive action if looking at player.
  1340.                 //      Evasion speed is scaled by percentage of shields left so wounded robots evade less effectively.
  1341.  
  1342.                 dot = vm_vec_dot(vec_to_player, objp->orient.fvec);
  1343.                 if (dot > robptr.field_of_view[Difficulty_level] && !(powerup_flags & PLAYER_FLAGS_CLOAKED)) {
  1344.                         fix     damage_scale;
  1345.  
  1346.                         if (!robptr.strength)
  1347.                                 damage_scale = F1_0;
  1348.                         else
  1349.                                 damage_scale = fixdiv(objp->shields, robptr.strength);
  1350.                         if (damage_scale > F1_0)
  1351.                                 damage_scale = F1_0;            //      Just in case...
  1352.                         else if (damage_scale < 0)
  1353.                                 damage_scale = 0;                       //      Just in case...
  1354.  
  1355.                         vm_vec_scale(evade_vector, i2f(fast_flag) + damage_scale);
  1356.                 }
  1357.         }
  1358.  
  1359.         pptr->velocity.x += evade_vector.x;
  1360.         pptr->velocity.y += evade_vector.y;
  1361.         pptr->velocity.z += evade_vector.z;
  1362.  
  1363.         const auto speed = vm_vec_mag_quick(pptr->velocity);
  1364.         if (speed > robptr.max_speed[Difficulty_level]) {
  1365.                 pptr->velocity.x = (pptr->velocity.x*3)/4;
  1366.                 pptr->velocity.y = (pptr->velocity.y*3)/4;
  1367.                 pptr->velocity.z = (pptr->velocity.z*3)/4;
  1368.         }
  1369. }
  1370.  
  1371. // --------------------------------------------------------------------------------------------------------------------
  1372. static void move_away_from_player(const vmobjptridx_t objp, const vms_vector &vec_to_player, int attack_type)
  1373. {
  1374.         physics_info    *pptr = &objp->mtype.phys_info;
  1375.  
  1376.         const auto frametime = FrameTime;
  1377.         pptr->velocity.x -= fixmul(vec_to_player.x, frametime*16);
  1378.         pptr->velocity.y -= fixmul(vec_to_player.y, frametime*16);
  1379.         pptr->velocity.z -= fixmul(vec_to_player.z, frametime*16);
  1380.  
  1381.         if (attack_type) {
  1382.                 //      Get value in 0..3 to choose evasion direction.
  1383.                 const int objref = objp ^ ((d_tick_count + 3 * objp) >> 5);
  1384.                 vm_vec_scale_add2(pptr->velocity, (objref & 2) ? objp->orient.rvec : objp->orient.uvec, ((objref & 1) ? -frametime : frametime) << 5);
  1385.         }
  1386.  
  1387.  
  1388.         auto speed = vm_vec_mag_quick(pptr->velocity);
  1389.  
  1390.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1391.         auto &robptr = Robot_info[get_robot_id(objp)];
  1392.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1393.         if (speed > robptr.max_speed[Difficulty_level])
  1394.         {
  1395.                 pptr->velocity.x = (pptr->velocity.x*3)/4;
  1396.                 pptr->velocity.y = (pptr->velocity.y*3)/4;
  1397.                 pptr->velocity.z = (pptr->velocity.z*3)/4;
  1398.         }
  1399.  
  1400. }
  1401.  
  1402. // --------------------------------------------------------------------------------------------------------------------
  1403. //      Move towards, away_from or around player.
  1404. //      Also deals with evasion.
  1405. //      If the flag evade_only is set, then only allowed to evade, not allowed to move otherwise (must have mode == AIM_STILL).
  1406. static void ai_move_relative_to_player(const vmobjptridx_t objp, ai_local &ailp, const fix dist_to_player, const fix circle_distance, const int evade_only, const robot_to_player_visibility_state &player_visibility, const player_info &player_info)
  1407. {
  1408.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1409.         auto &vec_to_player = player_visibility.vec_to_player;
  1410.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1411.         auto &robptr = Robot_info[get_robot_id(objp)];
  1412.  
  1413.         //      See if should take avoidance.
  1414.  
  1415.         // New way, green guys don't evade:     if ((robptr.attack_type == 0) && (objp->ctype.ai_info.danger_laser_num != -1))
  1416.         if (objp->ctype.ai_info.danger_laser_num != object_none) {
  1417.                 const auto &&dobjp = objp.absolute_sibling(objp->ctype.ai_info.danger_laser_num);
  1418.  
  1419.                 if ((dobjp->type == OBJ_WEAPON) && (dobjp->signature == objp->ctype.ai_info.danger_laser_signature)) {
  1420.                         vms_vector      laser_fvec;
  1421.  
  1422.                         const fix field_of_view = robptr.field_of_view[Difficulty_level];
  1423.  
  1424.                         auto vec_to_laser = vm_vec_sub(dobjp->pos, objp->pos);
  1425.                         auto dist_to_laser = vm_vec_normalize_quick(vec_to_laser);
  1426.                         const fix dot = vm_vec_dot(vec_to_laser, objp->orient.fvec);
  1427.  
  1428.                         if (dot > field_of_view || robot_is_companion(robptr))
  1429.                         {
  1430.                                 fix                     laser_robot_dot;
  1431.  
  1432.                                 //      The laser is seen by the robot, see if it might hit the robot.
  1433.                                 //      Get the laser's direction.  If it's a polyobj, it can be gotten cheaply from the orientation matrix.
  1434.                                 if (dobjp->render_type == RT_POLYOBJ)
  1435.                                         laser_fvec = dobjp->orient.fvec;
  1436.                                 else {          //      Not a polyobj, get velocity and normalize.
  1437.                                         laser_fvec = vm_vec_normalized_quick(dobjp->mtype.phys_info.velocity);  //dobjp->orient.fvec;
  1438.                                 }
  1439.                                 const auto laser_vec_to_robot = vm_vec_normalized_quick(vm_vec_sub(objp->pos, dobjp->pos));
  1440.                                 laser_robot_dot = vm_vec_dot(laser_fvec, laser_vec_to_robot);
  1441.  
  1442.                                 if ((laser_robot_dot > F1_0*7/8) && (dist_to_laser < F1_0*80)) {
  1443.                                         int     evade_speed;
  1444.  
  1445.                                         ai_evaded = 1;
  1446.                                         evade_speed = robptr.evade_speed[Difficulty_level];
  1447.                                         move_around_player(objp, player_info.powerup_flags, vec_to_player, evade_speed);
  1448.                                 }
  1449.                         }
  1450.                         return;
  1451.                 }
  1452.         }
  1453.  
  1454.         //      If only allowed to do evade code, then done.
  1455.         //      Hmm, perhaps brilliant insight.  If want claw-type guys to keep coming, don't return here after evasion.
  1456.         if (!robptr.attack_type && !robot_is_thief(robptr) && evade_only)
  1457.                 return;
  1458.  
  1459.         //      If we fall out of above, then no object to be avoided.
  1460.         objp->ctype.ai_info.danger_laser_num = object_none;
  1461.  
  1462.         //      Green guy selects move around/towards/away based on firing time, not distance.
  1463.         if (robptr.attack_type == 1) {
  1464.                 if ((!ready_to_fire_weapon1(ailp, robptr.firing_wait[Difficulty_level]/4) && dist_to_player < F1_0*30) ||
  1465.                         Player_dead_state != player_dead_state::no)
  1466.                 {
  1467.                         //      1/4 of time, move around player, 3/4 of time, move away from player
  1468.                         if (d_rand() < 8192) {
  1469.                                 move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
  1470.                         } else {
  1471.                                 move_away_from_player(objp, vec_to_player, 1);
  1472.                         }
  1473.                 } else {
  1474.                         move_towards_player(objp, vec_to_player);
  1475.                 }
  1476.         }
  1477.         else if (robot_is_thief(robptr))
  1478.         {
  1479.                 move_towards_player(objp, vec_to_player);
  1480.         }
  1481.         else {
  1482. #if defined(DXX_BUILD_DESCENT_I)
  1483.                 if (dist_to_player < circle_distance)
  1484.                         move_away_from_player(objp, vec_to_player, 0);
  1485.                 else if (dist_to_player < circle_distance*2)
  1486.                         move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
  1487.                 else
  1488.                         move_towards_player(objp, vec_to_player);
  1489. #elif defined(DXX_BUILD_DESCENT_II)
  1490.                 int     objval = ((objp) & 0x0f) ^ 0x0a;
  1491.  
  1492.                 //      Changes here by MK, 12/29/95.  Trying to get rid of endless circling around bots in a large room.
  1493.                 if (robptr.kamikaze) {
  1494.                         move_towards_player(objp, vec_to_player);
  1495.                 } else if (dist_to_player < circle_distance)
  1496.                         move_away_from_player(objp, vec_to_player, 0);
  1497.                 else if ((dist_to_player < (3+objval)*circle_distance/2) && !ready_to_fire_weapon1(ailp, -F1_0)) {
  1498.                         move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
  1499.                 } else {
  1500.                         if (ready_to_fire_weapon1(ailp, -(F1_0 + (objval << 12))) && player_is_visible(player_visibility.visibility))
  1501.                         {
  1502.                                 //      Usually move away, but sometimes move around player.
  1503.                                 if ((((GameTime64 >> 18) & 0x0f) ^ objval) > 4) {
  1504.                                         move_away_from_player(objp, vec_to_player, 0);
  1505.                                 } else {
  1506.                                         move_around_player(objp, player_info.powerup_flags, vec_to_player, -1);
  1507.                                 }
  1508.                         } else
  1509.                                 move_towards_player(objp, vec_to_player);
  1510.                 }
  1511. #endif
  1512.         }
  1513. }
  1514.  
  1515. }
  1516.  
  1517. namespace dcx {
  1518.  
  1519. // --------------------------------------------------------------------------------------------------------------------
  1520. //      Compute a somewhat random, normalized vector.
  1521. void make_random_vector(vms_vector &vec)
  1522. {
  1523.         vec.x = (d_rand() - 16384) | 1; // make sure we don't create null vector
  1524.         vec.y = d_rand() - 16384;
  1525.         vec.z = d_rand() - 16384;
  1526.         vm_vec_normalize_quick(vec);
  1527. }
  1528.  
  1529. }
  1530.  
  1531. namespace dsx {
  1532.  
  1533. //      -------------------------------------------------------------------------------------------------------------------
  1534. static void do_firing_stuff(object &obj, const player_flags powerup_flags, const robot_to_player_visibility_state &player_visibility)
  1535. {
  1536. #if defined(DXX_BUILD_DESCENT_I)
  1537.         if (player_is_visible(player_visibility.visibility))
  1538. #elif defined(DXX_BUILD_DESCENT_II)
  1539.         if (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD || player_is_visible(player_visibility.visibility))
  1540. #endif
  1541.         {
  1542.                 //      Now, if in robot's field of view, lock onto player
  1543.                 const fix dot = vm_vec_dot(obj.orient.fvec, player_visibility.vec_to_player);
  1544.                 if ((dot >= 7*F1_0/8) || (powerup_flags & PLAYER_FLAGS_CLOAKED)) {
  1545.                         ai_static *const aip = &obj.ctype.ai_info;
  1546.                         ai_local *const ailp = &obj.ctype.ai_info.ail;
  1547.  
  1548.                         switch (aip->GOAL_STATE) {
  1549.                                 case AIS_NONE:
  1550.                                 case AIS_REST:
  1551.                                 case AIS_SRCH:
  1552.                                 case AIS_LOCK:
  1553.                                         aip->GOAL_STATE = AIS_FIRE;
  1554.                                         if (ailp->player_awareness_type <= player_awareness_type_t::PA_NEARBY_ROBOT_FIRED) {
  1555.                                                 ailp->player_awareness_type = player_awareness_type_t::PA_NEARBY_ROBOT_FIRED;
  1556.                                                 ailp->player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
  1557.                                         }
  1558.                                         break;
  1559.                         }
  1560.                 } else if (dot >= F1_0/2) {
  1561.                         ai_static *const aip = &obj.ctype.ai_info;
  1562.                         switch (aip->GOAL_STATE) {
  1563.                                 case AIS_NONE:
  1564.                                 case AIS_REST:
  1565.                                 case AIS_SRCH:
  1566.                                         aip->GOAL_STATE = AIS_LOCK;
  1567.                                         break;
  1568.                         }
  1569.                 }
  1570.         }
  1571. }
  1572.  
  1573. // --------------------------------------------------------------------------------------------------------------------
  1574. //      If a hiding robot gets bumped or hit, he decides to find another hiding place.
  1575. void do_ai_robot_hit(const vmobjptridx_t objp, player_awareness_type_t type)
  1576. {
  1577.         if (objp->control_type == CT_AI) {
  1578.                 if (type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION || type == player_awareness_type_t::PA_PLAYER_COLLISION)
  1579.                         switch (objp->ctype.ai_info.behavior) {
  1580. #if defined(DXX_BUILD_DESCENT_I)
  1581.                                 case ai_behavior::AIB_HIDE:
  1582.                                         objp->ctype.ai_info.SUBMODE = AISM_GOHIDE;
  1583.                                         break;
  1584.                                 case ai_behavior::AIB_STILL:
  1585.                                         // objp->ctype.ai_info.ail.mode = ai_mode::AIM_CHASE_OBJECT; // NOTE: Should be triggered but causes unwanted movements with bosses. I leave this here for future reference.
  1586.                                         break;
  1587.                                 case ai_behavior::AIB_FOLLOW_PATH:
  1588. #elif defined(DXX_BUILD_DESCENT_II)
  1589.                                 case ai_behavior::AIB_STILL:
  1590.                                 {
  1591.                                         int     r;
  1592.  
  1593.                                         r = d_rand();
  1594.                                         //      1/8 time, charge player, 1/4 time create path, rest of time, do nothing
  1595.                                         ai_local                *ailp = &objp->ctype.ai_info.ail;
  1596.                                         if (r < 4096) {
  1597.                                                 create_path_to_believed_player_segment(objp, 10, create_path_safety_flag::safe);
  1598.                                                 objp->ctype.ai_info.behavior = ai_behavior::AIB_STATION;
  1599.                                                 objp->ctype.ai_info.hide_segment = objp->segnum;
  1600.                                                 ailp->mode = ai_mode::AIM_CHASE_OBJECT;
  1601.                                         } else if (r < 4096+8192) {
  1602.                                                 create_n_segment_path(objp, d_rand()/8192 + 2, segment_none);
  1603.                                                 ailp->mode = ai_mode::AIM_FOLLOW_PATH;
  1604.                                         }
  1605.                                         break;
  1606.                                 }
  1607.                                 case ai_behavior::AIB_BEHIND:
  1608.                                 case ai_behavior::AIB_SNIPE:
  1609.                                 case ai_behavior::AIB_FOLLOW:
  1610. #endif
  1611.                                 case ai_behavior::AIB_NORMAL:
  1612.                                 case ai_behavior::AIB_RUN_FROM:
  1613.                                 case ai_behavior::AIB_STATION:
  1614.                                         break;
  1615.                         }
  1616.         }
  1617. }
  1618.  
  1619. // --------------------------------------------------------------------------------------------------------------------
  1620. //      Note: This function could be optimized.  Surely player_is_visible_from_object would benefit from the
  1621. //      information of a normalized vec_to_player.
  1622. //      Return player visibility:
  1623. //              0               not visible
  1624. //              1               visible, but robot not looking at player (ie, on an unobstructed vector)
  1625. //              2               visible and in robot's field of view
  1626. //              -1              player is cloaked
  1627. //      If the player is cloaked, set vec_to_player based on time player cloaked and last uncloaked position.
  1628. //      Updates ailp->previous_visibility if player is not cloaked, in which case the previous visibility is left unchanged
  1629. //      and is copied to player_visibility
  1630.  
  1631. static void compute_vis_and_vec(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const player_info &player_info, vms_vector &pos, ai_local &ailp, robot_to_player_visibility_state &player_visibility, const robot_info &robptr)
  1632. {
  1633.         if (player_visibility.initialized)
  1634.                 return;
  1635.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1636.         const auto powerup_flags = player_info.powerup_flags;
  1637.                 if (powerup_flags & PLAYER_FLAGS_CLOAKED)
  1638.                 {
  1639.                         const unsigned cloak_index = (objp) % MAX_AI_CLOAK_INFO;
  1640.                         const fix delta_time = GameTime64 - Ai_cloak_info[cloak_index].last_time;
  1641.                         if (delta_time > F1_0*2) {
  1642.                                 Ai_cloak_info[cloak_index].last_time = GameTime64;
  1643.                                 vm_vec_scale_add2(Ai_cloak_info[cloak_index].last_position, make_random_vector(), 8 * delta_time);
  1644.                         }
  1645.  
  1646.                         const auto dist = vm_vec_normalized_dir_quick(player_visibility.vec_to_player, Ai_cloak_info[cloak_index].last_position, pos);
  1647.                         player_visibility.visibility = player_is_visible_from_object(objp, pos, robptr.field_of_view[Difficulty_level], player_visibility.vec_to_player);
  1648.                         if (ailp.next_misc_sound_time < GameTime64 && ready_to_fire_any_weapon(robptr, ailp, F1_0) && dist < F1_0 * 20)
  1649.                         {
  1650.                                 ailp.next_misc_sound_time = GameTime64 + (d_rand() + F1_0) * (7 - Difficulty_level) / 1;
  1651.                                 digi_link_sound_to_pos(robptr.see_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
  1652.                         }
  1653.                 } else {
  1654.                         //      Compute expensive stuff -- vec_to_player and player_visibility
  1655.                         vm_vec_normalized_dir_quick(player_visibility.vec_to_player, Believed_player_pos, pos);
  1656.                         if (player_visibility.vec_to_player.x == 0 && player_visibility.vec_to_player.y == 0 && player_visibility.vec_to_player.z == 0)
  1657.                         {
  1658.                                 player_visibility.vec_to_player.x = F1_0;
  1659.                         }
  1660.                         player_visibility.visibility = player_is_visible_from_object(objp, pos, robptr.field_of_view[Difficulty_level], player_visibility.vec_to_player);
  1661.  
  1662.                         //      This horrible code added by MK in desperation on 12/13/94 to make robots wake up as soon as they
  1663.                         //      see you without killing frame rate.
  1664.                         {
  1665.                                 ai_static       *aip = &objp->ctype.ai_info;
  1666.                         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view && ailp.previous_visibility != player_visibility_state::visible_and_in_field_of_view)
  1667.                                 if ((aip->GOAL_STATE == AIS_REST) || (aip->CURRENT_STATE == AIS_REST)) {
  1668.                                         aip->GOAL_STATE = AIS_FIRE;
  1669.                                         aip->CURRENT_STATE = AIS_FIRE;
  1670.                                 }
  1671.                         }
  1672.  
  1673. #if defined(DXX_BUILD_DESCENT_I)
  1674.                         if (Player_dead_state != player_dead_state::exploded)
  1675. #endif
  1676.                         if (ailp.previous_visibility != player_visibility.visibility && player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  1677.                         {
  1678.                                 if (ailp.previous_visibility == player_visibility_state::no_line_of_sight)
  1679.                                 {
  1680.                                         if (ailp.time_player_seen + F1_0/2 < GameTime64)
  1681.                                         {
  1682.                                                 digi_link_sound_to_pos(robptr.see_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
  1683.                                                 ailp.time_player_sound_attacked = GameTime64;
  1684.                                                 ailp.next_misc_sound_time = GameTime64 + F1_0 + d_rand()*4;
  1685.                                         }
  1686.                                 }
  1687.                                 else if (ailp.time_player_sound_attacked + F1_0/4 < GameTime64)
  1688.                                 {
  1689.                                         digi_link_sound_to_pos(robptr.attack_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
  1690.                                         ailp.time_player_sound_attacked = GameTime64;
  1691.                                 }
  1692.                         }
  1693.  
  1694.                         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view && ailp.next_misc_sound_time < GameTime64)
  1695.                         {
  1696.                                 ailp.next_misc_sound_time = GameTime64 + (d_rand() + F1_0) * (7 - Difficulty_level) / 2;
  1697.                                 digi_link_sound_to_pos(robptr.attack_sound, vmsegptridx(objp->segnum), 0, pos, 0 , Robot_sound_volume);
  1698.                         }
  1699.                         ailp.previous_visibility = player_visibility.visibility;
  1700.                 }
  1701.  
  1702. #if defined(DXX_BUILD_DESCENT_II)
  1703.                 //      @mk, 09/21/95: If player view is not obstructed and awareness is at least as high as a nearby collision,
  1704.                 //      act is if robot is looking at player.
  1705.                 if (ailp.player_awareness_type >= player_awareness_type_t::PA_NEARBY_ROBOT_FIRED)
  1706.                         if (player_visibility.visibility == player_visibility_state::visible_not_in_field_of_view)
  1707.                                 player_visibility.visibility = player_visibility_state::visible_and_in_field_of_view;
  1708. #endif
  1709.  
  1710.                 if (player_is_visible(player_visibility.visibility))
  1711.                 {
  1712.                         ailp.time_player_seen = GameTime64;
  1713.                 }
  1714.         player_visibility.initialized = 1;
  1715. }
  1716.  
  1717. #if defined(DXX_BUILD_DESCENT_II)
  1718. static void compute_buddy_vis_vec(const vmobjptridx_t buddy_obj, const vms_vector &buddy_pos, robot_to_player_visibility_state &player_visibility, const robot_info &robptr)
  1719. {
  1720.         if (player_visibility.initialized)
  1721.                 return;
  1722.         auto &BuddyState = LevelUniqueObjectState.BuddyState;
  1723.         auto &Objects = LevelUniqueObjectState.Objects;
  1724.         auto &plr = get_player_controlling_guidebot(BuddyState, Players);
  1725.         if (plr.objnum == object_none)
  1726.         {
  1727.                 player_visibility.vec_to_player = {};
  1728.                 player_visibility.visibility = player_visibility_state::no_line_of_sight;
  1729.                 player_visibility.initialized = 1;
  1730.                 return;
  1731.         }
  1732.         auto &plrobj = *Objects.vcptr(plr.objnum);
  1733.         /* Buddy ignores cloaking */
  1734.         vm_vec_normalized_dir_quick(player_visibility.vec_to_player, plrobj.pos, buddy_pos);
  1735.         if (player_visibility.vec_to_player.x == 0 && player_visibility.vec_to_player.y == 0 && player_visibility.vec_to_player.z == 0)
  1736.                 player_visibility.vec_to_player.x = F1_0;
  1737.  
  1738.         fvi_query fq;
  1739.         fq.p0 = &buddy_pos;
  1740.         fq.startseg = buddy_obj->segnum;
  1741.         fq.p1 = &plrobj.pos;
  1742.         fq.rad = F1_0/4;
  1743.         fq.thisobjnum = buddy_obj;
  1744.         fq.ignore_obj_list.first = nullptr;
  1745.         fq.flags = FQ_TRANSWALL;
  1746.         fvi_info hit_data;
  1747.         const auto hit_type = find_vector_intersection(fq, hit_data);
  1748.  
  1749.         auto &ailp = buddy_obj->ctype.ai_info.ail;
  1750.         player_visibility.visibility = (hit_type == HIT_NONE)
  1751.                 ? ((ailp.time_player_seen = GameTime64, vm_vec_dot(player_visibility.vec_to_player, buddy_obj->orient.fvec) > robptr.field_of_view[GameUniqueState.Difficulty_level])
  1752.                    ? player_visibility_state::visible_and_in_field_of_view
  1753.                    : player_visibility_state::visible_not_in_field_of_view
  1754.                 )
  1755.                 : player_visibility_state::no_line_of_sight;
  1756.         ailp.previous_visibility = player_visibility.visibility;
  1757.         player_visibility.initialized = 1;
  1758. }
  1759. #endif
  1760.  
  1761. // --------------------------------------------------------------------------------------------------------------------
  1762. //      Move object one object radii from current position towards segment center.
  1763. //      If segment center is nearer than 2 radii, move it to center.
  1764. void move_towards_segment_center(const d_level_shared_segment_state &LevelSharedSegmentState, object_base &objp)
  1765. {
  1766. /* ZICO's change of 20081103:
  1767.    Make move to segment center smoother by using move_towards vector.
  1768.    Bot's should not jump around and maybe even intersect with each other!
  1769.    In case it breaks something what I do not see, yet, old code is still there. */
  1770.         const auto segnum = objp.segnum;
  1771.         vms_vector      vec_to_center;
  1772.  
  1773.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1774.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1775.         auto &SSegments = LevelSharedSegmentState.get_segments();
  1776.         const auto &&segment_center = compute_segment_center(Vertices.vcptr, SSegments.vcptr(segnum));
  1777.         vm_vec_normalized_dir_quick(vec_to_center, segment_center, objp.pos);
  1778.         move_towards_vector(objp, vec_to_center, 1);
  1779. }
  1780.  
  1781. //      -----------------------------------------------------------------------------------------------------------
  1782. //      Return true if door can be flown through by a suitable type robot.
  1783. //      Brains, avoid robots, companions can open doors.
  1784. //      objp == NULL means treat as buddy.
  1785. int ai_door_is_openable(
  1786.         const vmobjptr_t objp,
  1787. #if defined(DXX_BUILD_DESCENT_II)
  1788.         const player_flags powerup_flags,
  1789. #endif
  1790.         const shared_segment &segp, const unsigned sidenum)
  1791. {
  1792.         if (!IS_CHILD(segp.children[sidenum]))
  1793.                 return 0;               //trap -2 (exit side)
  1794.  
  1795.         const auto wall_num = segp.sides[sidenum].wall_num;
  1796.  
  1797.         if (wall_num == wall_none)              //if there's no door at all...
  1798.                 return 0;                               //..then say it can't be opened
  1799.  
  1800.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1801.         auto &vcwallptr = Walls.vcptr;
  1802.         auto &wall = *vcwallptr(wall_num);
  1803.         //      The mighty console object can open all doors (for purposes of determining paths).
  1804.         if (objp == ConsoleObject) {
  1805.                 const auto wt = wall.type;
  1806.                 if (wt == WALL_DOOR)
  1807.                 {
  1808.                         static_assert(WALL_DOOR != 0, "WALL_DOOR must be nonzero for this shortcut to work properly.");
  1809.                         return wt;
  1810.                 }
  1811.         }
  1812.  
  1813. #if defined(DXX_BUILD_DESCENT_I)
  1814.         if ((get_robot_id(objp) == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM))
  1815.         {
  1816.  
  1817.                 if (wall_num != wall_none)
  1818.                 {
  1819.                         const auto wt = wall.type;
  1820.                         if (wt == WALL_DOOR && wall.keys == KEY_NONE && !(wall.flags & WALL_DOOR_LOCKED))
  1821.                         {
  1822.                                 static_assert(WALL_DOOR != 0, "WALL_DOOR must be nonzero for this shortcut to work properly.");
  1823.                                 return wt;
  1824.                         }
  1825.                 }
  1826.         }
  1827. #elif defined(DXX_BUILD_DESCENT_II)
  1828.         auto &WallAnims = GameSharedState.WallAnims;
  1829.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1830.         if (Robot_info[get_robot_id(objp)].companion)
  1831.         {
  1832.                 const auto wt = wall.type;
  1833.                 if (wall.flags & WALL_BUDDY_PROOF) {
  1834.                         if (wt == WALL_DOOR && wall.state == WALL_DOOR_CLOSED)
  1835.                                 return 0;
  1836.                         else if (wt == WALL_CLOSED)
  1837.                                 return 0;
  1838.                         else if (wt == WALL_ILLUSION && !(wall.flags & WALL_ILLUSION_OFF))
  1839.                                 return 0;
  1840.                 }
  1841.                 switch (const auto wall_keys = wall.keys)
  1842.                 {
  1843.                                 case KEY_BLUE:
  1844.                                 case KEY_GOLD:
  1845.                                 case KEY_RED:
  1846.                                 {
  1847.                                         return powerup_flags & static_cast<PLAYER_FLAG>(wall_keys);
  1848.                                 }
  1849.                         default:
  1850.                                 break;
  1851.                 }
  1852.  
  1853.                 if (wt != WALL_DOOR && wt != WALL_CLOSED)
  1854.                         return 1;
  1855.  
  1856.                 //      If Buddy is returning to player, don't let him think he can get through triggered doors.
  1857.                 //      It's only valid to think that if the player is going to get him through.  But if he's
  1858.                 //      going to the player, the player is probably on the opposite side.
  1859.                 const ai_mode ailp_mode = objp->ctype.ai_info.ail.mode;
  1860.  
  1861.                 // -- if (Buddy_got_stuck) {
  1862.                 if (ailp_mode == ai_mode::AIM_GOTO_PLAYER) {
  1863.                         if (wt == WALL_BLASTABLE && wall.state != WALL_BLASTED)
  1864.                                 return 0;
  1865.                         if (wt == WALL_CLOSED)
  1866.                                 return 0;
  1867.                         if (wt == WALL_DOOR) {
  1868.                                 if ((wall.flags & WALL_DOOR_LOCKED) && (wall.state == WALL_DOOR_CLOSED))
  1869.                                         return 0;
  1870.                         }
  1871.                 }
  1872.                 // -- }
  1873.  
  1874.                 if ((ailp_mode != ai_mode::AIM_GOTO_PLAYER) && (wall.controlling_trigger != -1)) {
  1875.                         const auto clip_num = wall.clip_num;
  1876.                         if (clip_num == -1)
  1877.                                 return clip_num;
  1878.                         else if (WallAnims[clip_num].flags & WCF_HIDDEN) {
  1879.                                 static_assert(WALL_DOOR_CLOSED == 0, "WALL_DOOR_CLOSED must be zero for this shortcut to work properly.");
  1880.                                 return wall.state;
  1881.                         } else
  1882.                                 return 1;
  1883.                 }
  1884.  
  1885.                 if (wt == WALL_DOOR)  {
  1886.                                 const auto clip_num = wall.clip_num;
  1887.  
  1888.                                 if (clip_num == -1)
  1889.                                         return clip_num;
  1890.                                 //      Buddy allowed to go through secret doors to get to player.
  1891.                                 else if ((ailp_mode != ai_mode::AIM_GOTO_PLAYER) && (WallAnims[clip_num].flags & WCF_HIDDEN)) {
  1892.                                         static_assert(WALL_DOOR_CLOSED == 0, "WALL_DOOR_CLOSED must be zero for this shortcut to work properly.");
  1893.                                         return wall.state;
  1894.                                 } else
  1895.                                         return 1;
  1896.                 }
  1897.         } else if ((get_robot_id(objp) == ROBOT_BRAIN) || (objp->ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM) || (objp->ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)) {
  1898.                 if (wall_num != wall_none)
  1899.                 {
  1900.                         const auto wt = wall.type;
  1901.                         if (wt == WALL_DOOR && (wall.keys == KEY_NONE) && !(wall.flags & WALL_DOOR_LOCKED))
  1902.                         {
  1903.                                 static_assert(WALL_DOOR != 0, "WALL_DOOR must be nonzero for this shortcut to work properly.");
  1904.                                 return wt;
  1905.                         }
  1906.                         else if (wall.keys != KEY_NONE) {       //      Allow bots to open doors to which player has keys.
  1907.                                 return powerup_flags & static_cast<PLAYER_FLAG>(wall.keys);
  1908.                         }
  1909.                 }
  1910.         }
  1911. #endif
  1912.         return 0;
  1913. }
  1914.  
  1915. //      -----------------------------------------------------------------------------------------------------------
  1916. //      Return side of openable door in segment, if any.  If none, return side_none.
  1917. static unsigned openable_doors_in_segment(fvcwallptr &vcwallptr, const shared_segment &segp)
  1918. {
  1919. #if defined(DXX_BUILD_DESCENT_II)
  1920.         auto &WallAnims = GameSharedState.WallAnims;
  1921. #endif
  1922.         range_for (const auto &&es, enumerate(segp.sides))
  1923.         {
  1924.                 const auto wall_num = es.value.wall_num;
  1925.                 if (wall_num != wall_none)
  1926.                 {
  1927.                         auto &w = *vcwallptr(wall_num);
  1928.                         if (w.type != WALL_DOOR)
  1929.                                 continue;
  1930.                         if (w.keys != KEY_NONE)
  1931.                                 continue;
  1932.                         if (w.state != WALL_DOOR_CLOSED)
  1933.                                 continue;
  1934.                         if (w.flags & WALL_DOOR_LOCKED)
  1935.                                 continue;
  1936. #if defined(DXX_BUILD_DESCENT_II)
  1937.                         if (WallAnims[w.clip_num].flags & WCF_HIDDEN)
  1938.                                 continue;
  1939. #endif
  1940.                         return es.idx;
  1941.                 }
  1942.         }
  1943.         return side_none;
  1944. }
  1945.  
  1946. // --------------------------------------------------------------------------------------------------------------------
  1947. //      Return true if placing an object of size size at pos *pos intersects a (player or robot or control center) in segment *segp.
  1948. static int check_object_object_intersection(const vms_vector &pos, fix size, const unique_segment &segp)
  1949. {
  1950.         auto &Objects = LevelUniqueObjectState.Objects;
  1951.         auto &vcobjptridx = Objects.vcptridx;
  1952.         //      If this would intersect with another object (only check those in this segment), then try to move.
  1953.         range_for (const object_base &curobj, objects_in(segp, vcobjptridx, vcsegptr))
  1954.         {
  1955.                 if (curobj.type == OBJ_PLAYER || curobj.type == OBJ_ROBOT || curobj.type == OBJ_CNTRLCEN)
  1956.                 {
  1957.                         if (vm_vec_dist_quick(pos, curobj.pos) < size + curobj.size)
  1958.                                 return 1;
  1959.                 }
  1960.         }
  1961.         return 0;
  1962. }
  1963.  
  1964. // --------------------------------------------------------------------------------------------------------------------
  1965. //      Return objnum if object created, else return -1.
  1966. //      If pos == NULL, pick random spot in segment.
  1967. static imobjptridx_t create_gated_robot(const d_vclip_array &Vclip, fvcobjptr &vcobjptr, const vmsegptridx_t segp, const unsigned object_id, const vms_vector *const pos)
  1968. {
  1969.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  1970.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1971.         auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
  1972.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1973.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1974.         const auto Gate_interval = GameUniqueState.Boss_gate_interval;
  1975. #if defined(DXX_BUILD_DESCENT_I)
  1976.         const unsigned maximum_gated_robots = 2*Difficulty_level + 3;
  1977. #elif defined(DXX_BUILD_DESCENT_II)
  1978.         if (GameTime64 - BossUniqueState.Last_gate_time < Gate_interval)
  1979.                 return object_none;
  1980.         const unsigned maximum_gated_robots = 2*Difficulty_level + 6;
  1981. #endif
  1982.  
  1983.         unsigned count = 0;
  1984.         range_for (const auto &&objp, vcobjptr)
  1985.         {
  1986.                 auto &obj = *objp;
  1987.                 if (obj.type == OBJ_ROBOT)
  1988.                         if (obj.matcen_creator == BOSS_GATE_MATCEN_NUM)
  1989.                                 count++;
  1990.         }
  1991.  
  1992.         if (count > maximum_gated_robots)
  1993.         {
  1994.                 BossUniqueState.Last_gate_time = GameTime64 - 3*Gate_interval/4;
  1995.                 return object_none;
  1996.         }
  1997.  
  1998.         auto &vcvertptr = Vertices.vcptr;
  1999.         const auto object_pos = pos ? *pos : pick_random_point_in_seg(vcvertptr, segp);
  2000.  
  2001.         //      See if legal to place object here.  If not, move about in segment and try again.
  2002.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2003.         auto &robptr = Robot_info[object_id];
  2004.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  2005.         const fix objsize = Polygon_models[robptr.model_num].rad;
  2006.         if (check_object_object_intersection(object_pos, objsize, segp)) {
  2007.                 BossUniqueState.Last_gate_time = GameTime64 - 3*Gate_interval/4;
  2008.                 return object_none;
  2009.         }
  2010.  
  2011. #if defined(DXX_BUILD_DESCENT_I)
  2012.         const ai_behavior default_behavior = (object_id == 10) //       This is a toaster guy!
  2013.         ? ai_behavior::AIB_RUN_FROM
  2014.         : ai_behavior::AIB_NORMAL;
  2015. #elif defined(DXX_BUILD_DESCENT_II)
  2016.         const ai_behavior default_behavior = robptr.behavior;
  2017. #endif
  2018.         auto objp = robot_create(object_id, segp, object_pos, &vmd_identity_matrix, objsize, default_behavior);
  2019.  
  2020.         if ( objp == object_none ) {
  2021.                 BossUniqueState.Last_gate_time = GameTime64 - 3*Gate_interval/4;
  2022.                 return object_none;
  2023.         }
  2024.  
  2025.         //Set polygon-object-specific data
  2026.  
  2027.         objp->rtype.pobj_info.model_num = robptr.model_num;
  2028.         objp->rtype.pobj_info.subobj_flags = 0;
  2029.  
  2030.         //set Physics info
  2031.  
  2032.         objp->mtype.phys_info.mass = robptr.mass;
  2033.         objp->mtype.phys_info.drag = robptr.drag;
  2034.  
  2035.         objp->mtype.phys_info.flags |= (PF_LEVELLING);
  2036.  
  2037.         objp->shields = robptr.strength;
  2038.         objp->matcen_creator = BOSS_GATE_MATCEN_NUM;    //      flag this robot as having been created by the boss.
  2039.  
  2040. #if defined(DXX_BUILD_DESCENT_II)
  2041.         objp->lifeleft = F1_0*30;       //      Gated in robots only live 30 seconds.
  2042. #endif
  2043.  
  2044.         object_create_explosion(segp, object_pos, i2f(10), VCLIP_MORPHING_ROBOT );
  2045.         digi_link_sound_to_pos( Vclip[VCLIP_MORPHING_ROBOT].sound_num, segp, 0, object_pos, 0 , F1_0);
  2046.         morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, objp);
  2047.  
  2048.         BossUniqueState.Last_gate_time = GameTime64;
  2049.         ++LevelUniqueObjectState.accumulated_robots;
  2050.         ++GameUniqueState.accumulated_robots;
  2051.  
  2052.         return objp;
  2053. }
  2054.  
  2055. // --------------------------------------------------------------------------------------------------------------------
  2056. //      Make object objp gate in a robot.
  2057. //      The process of him bringing in a robot takes one second.
  2058. //      Then a robot appears somewhere near the player.
  2059. //      Return objnum if robot successfully created, else return -1
  2060. imobjptridx_t gate_in_robot(const unsigned type, const vmsegptridx_t segnum)
  2061. {
  2062.         auto &Objects = LevelUniqueObjectState.Objects;
  2063.         auto &vcobjptr = Objects.vcptr;
  2064.         return create_gated_robot(Vclip, vcobjptr, segnum, type, nullptr);
  2065. }
  2066.  
  2067. static imobjptridx_t gate_in_robot(fvmsegptridx &vmsegptridx, int type)
  2068. {
  2069.         auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
  2070.         auto segnum = Boss_gate_segs[(d_rand() * Boss_gate_segs.size()) >> 15];
  2071.         return gate_in_robot(type, vmsegptridx(segnum));
  2072. }
  2073.  
  2074. }
  2075.  
  2076. namespace dcx {
  2077.  
  2078. static const shared_segment *boss_intersects_wall(fvcvertptr &vcvertptr, const object_base &boss_objp, const vcsegptridx_t segp)
  2079. {
  2080.         const auto size = boss_objp.size;
  2081.         const auto &&segcenter = compute_segment_center(vcvertptr, segp);
  2082.         auto pos = segcenter;
  2083.         for (uint_fast32_t posnum = 0;;)
  2084.         {
  2085.                 const auto seg = sphere_intersects_wall(vcvertptr, pos, segp, size).seg;
  2086.                 if (!seg)
  2087.                         return seg;
  2088.                 if (posnum == segp->verts.size())
  2089.                         return seg;
  2090.                 auto &vertex_pos = *vcvertptr(segp->verts[posnum ++]);
  2091.                 vm_vec_avg(pos, vertex_pos, segcenter);
  2092.         }
  2093. }
  2094.  
  2095. }
  2096.  
  2097. namespace dsx {
  2098.  
  2099. #if defined(DXX_BUILD_DESCENT_II)
  2100. // --------------------------------------------------------------------------------------------------------------------
  2101. //      Create a Buddy bot.
  2102. //      This automatically happens when you bring up the Buddy menu in a debug version.
  2103. //      It is available as a cheat in a non-debug (release) version.
  2104. void create_buddy_bot(void)
  2105. {
  2106.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  2107.         auto &Vertices = LevelSharedVertexState.get_vertices();
  2108.         const auto &&range = enumerate(partial_const_range(LevelSharedRobotInfoState.Robot_info, LevelSharedRobotInfoState.N_robot_types));
  2109.         const auto &&predicate = [](const auto &ev) {
  2110.                 const robot_info &robptr = ev.value;
  2111.                 return robptr.companion;
  2112.         };
  2113.         const auto &&it = std::find_if(range.begin(), range.end(), predicate);
  2114.         if (!(it != range.end()))
  2115.                 return;
  2116.         const auto &&segp = vmsegptridx(ConsoleObject->segnum);
  2117.         auto &vcvertptr = Vertices.vcptr;
  2118.         const auto &&object_pos = compute_segment_center(vcvertptr, segp);
  2119.         create_morph_robot(segp, object_pos, (*it).idx);
  2120. }
  2121. #endif
  2122.  
  2123. // --------------------------------------------------------------------------------------------------------------------
  2124. //      Create list of segments boss is allowed to teleport to at imsegptr.
  2125. //      Set *num_segs.
  2126. //      Boss is allowed to teleport to segments he fits in (calls object_intersects_wall) and
  2127. //      he can reach from his initial position (calls find_connected_distance).
  2128. //      If size_check is set, then only add segment if boss can fit in it, else any segment is legal.
  2129. //      one_wall_hack added by MK, 10/13/95: A mega-hack!  Set to !0 to ignore the
  2130. static void init_boss_segments(const segment_array &segments, const object &boss_objp, d_level_shared_boss_state::special_segment_array_t &a, const int size_check, int one_wall_hack)
  2131. {
  2132.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  2133.         auto &Vertices = LevelSharedVertexState.get_vertices();
  2134.         constexpr unsigned QUEUE_SIZE = 256;
  2135.         auto &vcsegptridx = segments.vcptridx;
  2136.         auto &vmsegptr = segments.vmptr;
  2137. #if defined(DXX_BUILD_DESCENT_I)
  2138.         one_wall_hack = 0;
  2139. #endif
  2140.  
  2141.         a.clear();
  2142. #if DXX_USE_EDITOR
  2143.         Selected_segs.clear();
  2144. #endif
  2145.  
  2146.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  2147.         auto &vcwallptr = Walls.vcptr;
  2148.         {
  2149.                 int                     head, tail;
  2150.                 std::array<segnum_t, QUEUE_SIZE> seg_queue;
  2151.  
  2152.                 const auto original_boss_seg = boss_objp.segnum;
  2153.                 head = 0;
  2154.                 tail = 0;
  2155.                 seg_queue[head++] = original_boss_seg;
  2156.                 auto &vcvertptr = Vertices.vcptr;
  2157.  
  2158.                 if (!size_check || !boss_intersects_wall(vcvertptr, boss_objp, vcsegptridx(original_boss_seg)))
  2159.                 {
  2160.                         a.emplace_back(original_boss_seg);
  2161. #if DXX_USE_EDITOR
  2162.                         Selected_segs.emplace_back(original_boss_seg);
  2163. #endif
  2164.                 }
  2165.  
  2166.                 visited_segment_bitarray_t visited;
  2167.  
  2168.                 while (tail != head) {
  2169.                         const cscusegment segp = *vmsegptr(seg_queue[tail++]);
  2170.  
  2171.                         tail &= QUEUE_SIZE-1;
  2172.  
  2173.                         range_for (const auto &&es, enumerate(segp.s.children))
  2174.                         {
  2175.                                 const uint_fast32_t sidenum = es.idx;
  2176.                                 const auto w = WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum);
  2177.                                 if ((w & WID_FLY_FLAG) || one_wall_hack)
  2178.                                 {
  2179.                                         const auto csegnum = es.value;
  2180. #if defined(DXX_BUILD_DESCENT_II)
  2181.                                         //      If we get here and w == WID_WALL, then we want to process through this wall, else not.
  2182.                                         if (IS_CHILD(csegnum)) {
  2183.                                                 if (one_wall_hack)
  2184.                                                         one_wall_hack--;
  2185.                                         } else
  2186.                                                 continue;
  2187. #endif
  2188.  
  2189.                                         if (auto &&v = visited[csegnum])
  2190.                                         {
  2191.                                         }
  2192.                                         else
  2193.                                         {
  2194.                                                 v = true;
  2195.                                                 seg_queue[head++] = csegnum;
  2196.                                                 head &= QUEUE_SIZE-1;
  2197.                                                 if (head > tail) {
  2198.                                                         if (head == tail + QUEUE_SIZE-1)
  2199.                                                                 Int3(); //      queue overflow.  Make it bigger!
  2200.                                                 } else
  2201.                                                         if (head+QUEUE_SIZE == tail + QUEUE_SIZE-1)
  2202.                                                                 Int3(); //      queue overflow.  Make it bigger!
  2203.        
  2204.                                                 if (!size_check || !boss_intersects_wall(vcvertptr, boss_objp, vcsegptridx(csegnum))) {
  2205.                                                         a.emplace_back(csegnum);
  2206. #if DXX_USE_EDITOR
  2207.                                                         Selected_segs.emplace_back(csegnum);
  2208.                                                         #endif
  2209.                                                         if (a.size() >= a.max_size())
  2210.                                                         {
  2211.                                                                 tail = head;
  2212.                                                                 break;
  2213.                                                         }
  2214.                                                 }
  2215.                                         }
  2216.                                 }
  2217.                         }
  2218.                 }
  2219.                 // Last resort - add original seg even if boss doesn't fit in it
  2220.                 if (a.empty())
  2221.                         a.emplace_back(original_boss_seg);
  2222.         }
  2223. }
  2224.  
  2225. // --------------------------------------------------------------------------------------------------------------------
  2226. static void teleport_boss(const d_vclip_array &Vclip, fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const vms_vector &target_pos)
  2227. {
  2228.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  2229.         auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
  2230.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  2231.         segnum_t                        rand_segnum;
  2232.         int                     rand_index;
  2233.         assert(!Boss_teleport_segs.empty());
  2234.         auto &Objects = LevelUniqueObjectState.Objects;
  2235.         auto &vmobjptr = Objects.vmptr;
  2236.  
  2237.         //      Pick a random segment from the list of boss-teleportable-to segments.
  2238.         rand_index = (d_rand() * Boss_teleport_segs.size()) >> 15;
  2239.         rand_segnum = Boss_teleport_segs[rand_index];
  2240.         Assert(rand_segnum <= Highest_segment_index);
  2241.  
  2242.         if (Game_mode & GM_MULTI)
  2243.                 multi_send_boss_teleport(objp, rand_segnum);
  2244.  
  2245.         const auto &&rand_segp = vmsegptridx(rand_segnum);
  2246.         auto &Vertices = LevelSharedVertexState.get_vertices();
  2247.         auto &vcvertptr = Vertices.vcptr;
  2248.         compute_segment_center(vcvertptr, objp->pos, rand_segp);
  2249.         obj_relink(vmobjptr, vmsegptr, objp, rand_segp);
  2250.  
  2251.         BossUniqueState.Last_teleport_time = GameTime64;
  2252.  
  2253.         //      make boss point right at player
  2254.         const auto boss_dir = vm_vec_sub(target_pos, objp->pos);
  2255.         vm_vector_2_matrix(objp->orient, boss_dir, nullptr, nullptr);
  2256.  
  2257.         digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, rand_segp, 0, objp->pos, 0, F1_0);
  2258.         digi_kill_sound_linked_to_object( objp);
  2259.  
  2260.         //      After a teleport, boss can fire right away.
  2261.         ai_local                *ailp = &objp->ctype.ai_info.ail;
  2262.         ailp->next_fire = 0;
  2263. #if defined(DXX_BUILD_DESCENT_II)
  2264.         ailp->next_fire2 = 0;
  2265. #endif
  2266.         boss_link_see_sound(objp);
  2267.  
  2268. }
  2269.  
  2270. //      ----------------------------------------------------------------------
  2271. void start_boss_death_sequence(object &objp)
  2272. {
  2273.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  2274.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2275.         auto &robptr = Robot_info[get_robot_id(objp)];
  2276.         if (robptr.boss_flag)
  2277.         {
  2278.                 BossUniqueState.Boss_dying = 1;
  2279.                 BossUniqueState.Boss_dying_start_time = GameTime64;
  2280.         }
  2281.  
  2282. }
  2283.  
  2284. //      ----------------------------------------------------------------------------------------------------------
  2285. #if defined(DXX_BUILD_DESCENT_I)
  2286. static void do_boss_dying_frame(const vmobjptridx_t objp)
  2287. {
  2288.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  2289.         const auto time_boss_dying = GameTime64 - BossUniqueState.Boss_dying_start_time;
  2290.         {
  2291.                 auto &rotvel = objp->mtype.phys_info.rotvel;
  2292.                 rotvel.x = time_boss_dying / 9;
  2293.                 rotvel.y = time_boss_dying / 5;
  2294.                 rotvel.z = time_boss_dying / 7;
  2295.         }
  2296.  
  2297.         if (BossUniqueState.Boss_dying_start_time + BOSS_DEATH_DURATION - BOSS_DEATH_SOUND_DURATION < GameTime64) {
  2298.                 if (!BossUniqueState.Boss_dying_sound_playing)
  2299.                 {
  2300.                         BossUniqueState.Boss_dying_sound_playing = 1;
  2301.                         digi_link_sound_to_object2(SOUND_BOSS_SHARE_DIE, objp, 0, F1_0*4, sound_stack::allow_stacking, vm_distance{F1_0*1024}); //      F1_0*512 means play twice as loud
  2302.                 } else if (d_rand() < FrameTime*16)
  2303.                         create_small_fireball_on_object(objp, (F1_0 + d_rand()) * 8, 0);
  2304.         } else if (d_rand() < FrameTime*8)
  2305.                 create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * 8, 1);
  2306.  
  2307.         if (BossUniqueState.Boss_dying_start_time + BOSS_DEATH_DURATION < GameTime64 || GameTime64+(F1_0*2) < BossUniqueState.Boss_dying_start_time)
  2308.         {
  2309.                 BossUniqueState.Boss_dying_start_time = GameTime64; // make sure following only happens one time!
  2310.                 do_controlcen_destroyed_stuff(object_none);
  2311.                 explode_object(objp, F1_0/4);
  2312.                 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp, 0, F2_0, sound_stack::allow_stacking, vm_distance{F1_0*512});
  2313.         }
  2314. }
  2315.  
  2316. static int do_any_robot_dying_frame(const vmobjptridx_t)
  2317. {
  2318.         return 0;
  2319. }
  2320. #elif defined(DXX_BUILD_DESCENT_II)
  2321. //      objp points at a boss.  He was presumably just hit and he's supposed to create a bot at the hit location *pos.
  2322. imobjptridx_t boss_spew_robot(const object_base &objp, const vms_vector &pos)
  2323. {
  2324.         auto &Objects = LevelUniqueObjectState.Objects;
  2325.         auto &vcobjptr = Objects.vcptr;
  2326.         int             boss_index;
  2327.  
  2328.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2329.         boss_index = Robot_info[get_robot_id(objp)].boss_flag - BOSS_D2;
  2330.  
  2331.         Assert((boss_index >= 0) && (boss_index < NUM_D2_BOSSES));
  2332.  
  2333.         auto &Segments = LevelUniqueSegmentState.get_segments();
  2334.         const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, pos, Segments.vmptridx(objp.segnum));
  2335.         if (segnum == segment_none) {
  2336.                 return object_none;
  2337.         }      
  2338.  
  2339.         const auto &&newobjp = create_gated_robot(Vclip, vcobjptr, segnum, Spew_bots[boss_index][(Max_spew_bots[boss_index] * d_rand()) >> 15], &pos);
  2340.  
  2341.         //      Make spewed robot come tumbling out as if blasted by a flash missile.
  2342.         if (newobjp != object_none) {
  2343.                 int             force_val;
  2344.  
  2345.                 force_val = F1_0/FrameTime;
  2346.  
  2347.                 if (force_val) {
  2348.                         newobjp->ctype.ai_info.SKIP_AI_COUNT += force_val;
  2349.                         newobjp->mtype.phys_info.rotthrust.x = ((d_rand() - 16384) * force_val)/16;
  2350.                         newobjp->mtype.phys_info.rotthrust.y = ((d_rand() - 16384) * force_val)/16;
  2351.                         newobjp->mtype.phys_info.rotthrust.z = ((d_rand() - 16384) * force_val)/16;
  2352.                         newobjp->mtype.phys_info.flags |= PF_USES_THRUST;
  2353.  
  2354.                         //      Now, give a big initial velocity to get moving away from boss.
  2355.                         vm_vec_sub(newobjp->mtype.phys_info.velocity, pos, objp.pos);
  2356.                         vm_vec_normalize_quick(newobjp->mtype.phys_info.velocity);
  2357.                         vm_vec_scale(newobjp->mtype.phys_info.velocity, F1_0*128);
  2358.                 }
  2359.         }
  2360.  
  2361.         return newobjp;
  2362. }
  2363.  
  2364. //      ----------------------------------------------------------------------
  2365. void start_robot_death_sequence(object &obj)
  2366. {
  2367.         auto &ai_info = obj.ctype.ai_info;
  2368.         ai_info.dying_start_time = GameTime64;
  2369.         ai_info.dying_sound_playing = 0;
  2370.         ai_info.SKIP_AI_COUNT = 0;
  2371. }
  2372.  
  2373. //      ----------------------------------------------------------------------
  2374. //      General purpose robot-dies-with-death-roll-and-groan code.
  2375. //      Return true if object just died.
  2376. //      scale: F1_0*4 for boss, much smaller for much smaller guys
  2377. static int do_robot_dying_frame(const vmobjptridx_t objp, fix64 start_time, fix roll_duration, sbyte *dying_sound_playing, int death_sound, fix expl_scale, fix sound_scale)
  2378. {
  2379.         fix     sound_duration;
  2380.  
  2381.         if (!roll_duration)
  2382.                 roll_duration = F1_0/4;
  2383.  
  2384.         objp->mtype.phys_info.rotvel.x = (GameTime64 - start_time)/9;
  2385.         objp->mtype.phys_info.rotvel.y = (GameTime64 - start_time)/5;
  2386.         objp->mtype.phys_info.rotvel.z = (GameTime64 - start_time)/7;
  2387.  
  2388.         if (const auto SndDigiSampleRate = GameArg.SndDigiSampleRate)
  2389.                 sound_duration = fixdiv(GameSounds[digi_xlat_sound(death_sound)].length, SndDigiSampleRate);
  2390.         else
  2391.                 sound_duration = F1_0;
  2392.  
  2393.         if (start_time + roll_duration - sound_duration < GameTime64) {
  2394.                 if (!*dying_sound_playing) {
  2395.                         *dying_sound_playing = 1;
  2396.                         digi_link_sound_to_object2(death_sound, objp, 0, sound_scale, sound_stack::allow_stacking, vm_distance{sound_scale*256});       //      F1_0*512 means play twice as loud
  2397.                 } else if (d_rand() < FrameTime*16)
  2398.                         create_small_fireball_on_object(objp, (F1_0 + d_rand()) * (16 * expl_scale/F1_0)/8, 0);
  2399.         } else if (d_rand() < FrameTime*8)
  2400.                 create_small_fireball_on_object(objp, (F1_0/2 + d_rand()) * (16 * expl_scale/F1_0)/8, 1);
  2401.  
  2402.         if (start_time + roll_duration < GameTime64 || GameTime64+(F1_0*2) < start_time)
  2403.                 return 1;
  2404.         else
  2405.                 return 0;
  2406. }
  2407.  
  2408. //      ----------------------------------------------------------------------
  2409. static void do_boss_dying_frame(const vmobjptridx_t objp)
  2410. {
  2411.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  2412.         int     rval;
  2413.  
  2414.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2415.         rval = do_robot_dying_frame(objp, BossUniqueState.Boss_dying_start_time, BOSS_DEATH_DURATION, &BossUniqueState.Boss_dying_sound_playing, Robot_info[get_robot_id(objp)].deathroll_sound, F1_0*4, F1_0*4);
  2416.  
  2417.         if (rval)
  2418.         {
  2419.                 BossUniqueState.Boss_dying_start_time = GameTime64; // make sure following only happens one time!
  2420.                 do_controlcen_destroyed_stuff(object_none);
  2421.                 explode_object(objp, F1_0/4);
  2422.                 digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp, 0, F2_0, sound_stack::allow_stacking, vm_distance{F1_0*512});
  2423.         }
  2424. }
  2425.  
  2426. //      ----------------------------------------------------------------------
  2427. static int do_any_robot_dying_frame(const vmobjptridx_t objp)
  2428. {
  2429.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2430.         if (objp->ctype.ai_info.dying_start_time) {
  2431.                 int     rval, death_roll;
  2432.  
  2433.                 death_roll = Robot_info[get_robot_id(objp)].death_roll;
  2434.                 rval = do_robot_dying_frame(objp, objp->ctype.ai_info.dying_start_time, min(death_roll/2+1,6)*F1_0, &objp->ctype.ai_info.dying_sound_playing, Robot_info[get_robot_id(objp)].deathroll_sound, death_roll*F1_0/8, death_roll*F1_0/2);
  2435.  
  2436.                 if (rval) {
  2437.                         objp->ctype.ai_info.dying_start_time = GameTime64; // make sure following only happens one time!
  2438.                         explode_object(objp, F1_0/4);
  2439.                         digi_link_sound_to_object2(SOUND_BADASS_EXPLOSION, objp, 0, F2_0, sound_stack::allow_stacking, vm_distance{F1_0*512});
  2440.                         if (Current_level_num < 0)
  2441.                         {
  2442.                                 const auto id = get_robot_id(objp);
  2443.                                 if (Robot_info[id].thief)
  2444.                                         recreate_thief(id);
  2445.                         }
  2446.                 }
  2447.  
  2448.                 return 1;
  2449.         }
  2450.  
  2451.         return 0;
  2452. }
  2453. #endif
  2454.  
  2455. // --------------------------------------------------------------------------------------------------------------------
  2456. //      Called for an AI object if it is fairly aware of the player.
  2457. //      awareness_level is in 0..100.  Larger numbers indicate greater awareness (eg, 99 if firing at player).
  2458. //      In a given frame, might not get called for an object, or might be called more than once.
  2459. //      The fact that this routine is not called for a given object does not mean that object is not interested in the player.
  2460. //      Objects are moved by physics, so they can move even if not interested in a player.  However, if their velocity or
  2461. //      orientation is changing, this routine will be called.
  2462. //      Return value:
  2463. //              0       this player IS NOT allowed to move this robot.
  2464. //              1       this player IS allowed to move this robot.
  2465. int ai_multiplayer_awareness(const vmobjptridx_t objp, int awareness_level)
  2466. {
  2467.         int     rval=1;
  2468.  
  2469.         if (Game_mode & GM_MULTI) {
  2470.                 if (awareness_level == 0)
  2471.                         return 0;
  2472.                 rval = multi_can_move_robot(objp, awareness_level);
  2473.         }
  2474.  
  2475.         return rval;
  2476. }
  2477.  
  2478. }
  2479.  
  2480. #ifndef NDEBUG
  2481. namespace {
  2482. fix     Prev_boss_shields = -1;
  2483. }
  2484. #endif
  2485.  
  2486. namespace dsx {
  2487.  
  2488. #if defined(DXX_BUILD_DESCENT_I)
  2489. #define do_d1_boss_stuff(FS,FO,PV)      do_d1_boss_stuff(FS,FO)
  2490. #endif
  2491.  
  2492. // --------------------------------------------------------------------------------------------------------------------
  2493. //      Do special stuff for a boss.
  2494. static void do_d1_boss_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const player_visibility_state player_visibility)
  2495. {
  2496.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  2497.         auto &Objects = LevelUniqueObjectState.Objects;
  2498.         auto &vmobjptr = Objects.vmptr;
  2499. #ifndef NDEBUG
  2500.         if (objp->shields != Prev_boss_shields) {
  2501.                 Prev_boss_shields = objp->shields;
  2502.         }
  2503. #endif
  2504.  
  2505. #if defined(DXX_BUILD_DESCENT_II)
  2506.         if (!EMULATING_D1 && !player_is_visible(player_visibility) && !BossUniqueState.Boss_hit_this_frame)
  2507.                 return;
  2508. #endif
  2509.  
  2510.         if (!BossUniqueState.Boss_dying) {
  2511.                 const auto Boss_cloak_start_time = BossUniqueState.Boss_cloak_start_time;
  2512.                 if (objp->ctype.ai_info.CLOAKED == 1) {
  2513.                         if (GameTime64 - Boss_cloak_start_time > Boss_cloak_duration / 3 &&
  2514.                                 (Boss_cloak_start_time + Boss_cloak_duration) - GameTime64 > Boss_cloak_duration / 3 &&
  2515.                                 GameTime64 - BossUniqueState.Last_teleport_time > LevelSharedBossState.Boss_teleport_interval)
  2516.                         {
  2517.                                 if (ai_multiplayer_awareness(objp, 98))
  2518.                                         teleport_boss(Vclip, vmsegptridx, objp, get_local_plrobj().pos);
  2519.                         } else if (BossUniqueState.Boss_hit_this_frame) {
  2520.                                 BossUniqueState.Boss_hit_this_frame = 0;
  2521.                                 BossUniqueState.Last_teleport_time -= LevelSharedBossState.Boss_teleport_interval / 4;
  2522.                         }
  2523.  
  2524.                         if (GameTime64 > (Boss_cloak_start_time + Boss_cloak_duration) ||
  2525.                                 GameTime64 < Boss_cloak_start_time)
  2526.                                 objp->ctype.ai_info.CLOAKED = 0;
  2527.                 } else {
  2528.                         if (BossUniqueState.Boss_hit_this_frame ||
  2529.                                 GameTime64 - (Boss_cloak_start_time + Boss_cloak_duration) > LevelSharedBossState.Boss_cloak_interval)
  2530.                         {
  2531.                                 if (ai_multiplayer_awareness(objp, 95))
  2532.                                 {
  2533.                                         BossUniqueState.Boss_hit_this_frame = 0;
  2534.                                         BossUniqueState.Boss_cloak_start_time = GameTime64;
  2535.                                         objp->ctype.ai_info.CLOAKED = 1;
  2536.                                         if (Game_mode & GM_MULTI)
  2537.                                                 multi_send_boss_cloak(objp);
  2538.                                 }
  2539.                         }
  2540.                 }
  2541.         } else
  2542.                 do_boss_dying_frame(objp);
  2543.  
  2544. }
  2545.  
  2546. #define BOSS_TO_PLAYER_GATE_DISTANCE    (F1_0*150)
  2547.  
  2548.  
  2549. // --------------------------------------------------------------------------------------------------------------------
  2550. //      Do special stuff for a boss.
  2551. static void do_super_boss_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const fix dist_to_player, const player_visibility_state player_visibility)
  2552. {
  2553.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  2554.         static int eclip_state = 0;
  2555.         do_d1_boss_stuff(vmsegptridx, objp, player_visibility);
  2556.  
  2557.         // Only master player can cause gating to occur.
  2558.         if ((Game_mode & GM_MULTI) && !multi_i_am_master())
  2559.                 return;
  2560.  
  2561.         if (dist_to_player < BOSS_TO_PLAYER_GATE_DISTANCE || player_is_visible(player_visibility) || (Game_mode & GM_MULTI)) {
  2562.                 const auto Gate_interval = GameUniqueState.Boss_gate_interval;
  2563.                 if (GameTime64 - BossUniqueState.Last_gate_time > Gate_interval/2) {
  2564.                         restart_effect(ECLIP_NUM_BOSS);
  2565.                         if (eclip_state == 0) {
  2566.                                 multi_send_boss_start_gate(objp);
  2567.                                 eclip_state = 1;
  2568.                         }
  2569.                 }
  2570.                 else {
  2571.                         stop_effect(ECLIP_NUM_BOSS);
  2572.                         if (eclip_state == 1) {
  2573.                                 multi_send_boss_stop_gate(objp);
  2574.                                 eclip_state = 0;
  2575.                         }
  2576.                 }
  2577.  
  2578.                 if (GameTime64 - BossUniqueState.Last_gate_time > Gate_interval)
  2579.                         if (ai_multiplayer_awareness(objp, 99)) {
  2580.                                 uint_fast32_t randtype = (d_rand() * MAX_GATE_INDEX) >> 15;
  2581.  
  2582.                                 Assert(randtype < MAX_GATE_INDEX);
  2583.                                 randtype = Super_boss_gate_list[randtype];
  2584.  
  2585.                                 const auto &&rtval = gate_in_robot(vmsegptridx, randtype);
  2586.                                 if (rtval != object_none && (Game_mode & GM_MULTI))
  2587.                                 {
  2588.                                         multi_send_boss_create_robot(objp, rtval);
  2589.                                 }
  2590.                         }      
  2591.         }
  2592. }
  2593.  
  2594. #if defined(DXX_BUILD_DESCENT_II)
  2595. static void do_d2_boss_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const player_visibility_state player_visibility)
  2596. {
  2597.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  2598.         auto &Objects = LevelUniqueObjectState.Objects;
  2599.         auto &vmobjptr = Objects.vmptr;
  2600.         int     boss_id, boss_index;
  2601.  
  2602.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2603.         boss_id = Robot_info[get_robot_id(objp)].boss_flag;
  2604.  
  2605.         Assert((boss_id >= BOSS_D2) && (boss_id < BOSS_D2 + NUM_D2_BOSSES));
  2606.  
  2607.         boss_index = boss_id - BOSS_D2;
  2608.  
  2609. #ifndef NDEBUG
  2610.         if (objp->shields != Prev_boss_shields) {
  2611.                 Prev_boss_shields = objp->shields;
  2612.         }
  2613. #endif
  2614.  
  2615.         //      @mk, 10/13/95:  Reason:
  2616.         //              Level 4 boss behind locked door.  But he's allowed to teleport out of there.  So he
  2617.         //              teleports out of there right away, and blasts player right after first door.
  2618.         if (!player_is_visible(player_visibility) && GameTime64 - BossUniqueState.Boss_hit_time > F1_0 * 2)
  2619.                 return;
  2620.  
  2621.         if (!BossUniqueState.Boss_dying && Boss_teleports[boss_index]) {
  2622.                 const auto Boss_cloak_start_time = BossUniqueState.Boss_cloak_start_time;
  2623.                 if (objp->ctype.ai_info.CLOAKED == 1) {
  2624.                         BossUniqueState.Boss_hit_time = GameTime64;     //      Keep the cloak:teleport process going.
  2625.                         if (GameTime64 - Boss_cloak_start_time > Boss_cloak_duration / 3 &&
  2626.                                 (Boss_cloak_start_time + Boss_cloak_duration) - GameTime64 > Boss_cloak_duration / 3 &&
  2627.                                 GameTime64 - BossUniqueState.Last_teleport_time > LevelSharedBossState.Boss_teleport_interval)
  2628.                         {
  2629.                                 if (ai_multiplayer_awareness(objp, 98))
  2630.                                         teleport_boss(Vclip, vmsegptridx, objp, get_local_plrobj().pos);
  2631.                         } else if (GameTime64 - BossUniqueState.Boss_hit_time > F1_0*2) {
  2632.                                 BossUniqueState.Last_teleport_time -= LevelSharedBossState.Boss_teleport_interval / 4;
  2633.                         }
  2634.  
  2635.                         if (GameTime64 > (Boss_cloak_start_time + Boss_cloak_duration) ||
  2636.                                 GameTime64 < Boss_cloak_start_time)
  2637.                                 objp->ctype.ai_info.CLOAKED = 0;
  2638.                 } else if (GameTime64 - (Boss_cloak_start_time + Boss_cloak_duration) > LevelSharedBossState.Boss_cloak_interval ||
  2639.                                 GameTime64 - (Boss_cloak_start_time + Boss_cloak_duration) < -Boss_cloak_duration) {
  2640.                         if (ai_multiplayer_awareness(objp, 95)) {
  2641.                                 BossUniqueState.Boss_cloak_start_time = GameTime64;
  2642.                                 objp->ctype.ai_info.CLOAKED = 1;
  2643.                                 if (Game_mode & GM_MULTI)
  2644.                                         multi_send_boss_cloak(objp);
  2645.                         }
  2646.                 }
  2647.         }
  2648.  
  2649. }
  2650. #endif
  2651.  
  2652.  
  2653. static void ai_multi_send_robot_position(object &obj, int force)
  2654. {
  2655.         if (Game_mode & GM_MULTI)
  2656.         {
  2657.                 multi_send_robot_position(obj, force != -1);
  2658.         }
  2659.         return;
  2660. }
  2661.  
  2662. // --------------------------------------------------------------------------------------------------------------------
  2663. //      Returns true if this object should be allowed to fire at the player.
  2664. static int maybe_ai_do_actual_firing_stuff(object &obj)
  2665. {
  2666.         if (Game_mode & GM_MULTI)
  2667.         {
  2668.                 auto &aip = obj.ctype.ai_info;
  2669.                 if (aip.GOAL_STATE != AIS_FLIN && get_robot_id(obj) != ROBOT_BRAIN)
  2670.                 {
  2671.                         const auto s = aip.CURRENT_STATE;
  2672.                         if (s == AIS_FIRE)
  2673.                         {
  2674.                                 static_assert(AIS_FIRE != 0, "AIS_FIRE must be nonzero for this shortcut to work properly.");
  2675.                                 return s;
  2676.                         }
  2677.                 }
  2678.         }
  2679.  
  2680.         return 0;
  2681. }
  2682.  
  2683. #if defined(DXX_BUILD_DESCENT_I)
  2684. static void ai_do_actual_firing_stuff(fvmobjptridx &vmobjptridx, const vmobjptridx_t obj, ai_static *const aip, ai_local &ailp, const robot_info &robptr, const fix dist_to_player, const vms_vector &gun_point, const robot_to_player_visibility_state &player_visibility, const int object_animates, const player_info &player_info, int)
  2685. {
  2686.         auto &vec_to_player = player_visibility.vec_to_player;
  2687.         fix     dot;
  2688.  
  2689.         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  2690.         {
  2691.                 //      Changed by mk, 01/04/94, onearm would take about 9 seconds until he can fire at you.
  2692.                 // if (((!object_animates) || (ailp->achieved_state[aip->CURRENT_GUN] == AIS_FIRE)) && (ailp->next_fire <= 0))
  2693.                 if (!object_animates || ready_to_fire_any_weapon(robptr, ailp, 0)) {
  2694.                         dot = vm_vec_dot(obj->orient.fvec, vec_to_player);
  2695.                         if (dot >= 7*F1_0/8) {
  2696.  
  2697.                                 if (aip->CURRENT_GUN < robptr.n_guns) {
  2698.                                         if (robptr.attack_type == 1) {
  2699.                                                 if (Player_dead_state != player_dead_state::exploded &&
  2700.                                                         dist_to_player < obj->size + ConsoleObject->size + F1_0*2)
  2701.                                                 {
  2702.                                                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
  2703.                                                                 return;
  2704.                                                         do_ai_robot_hit_attack(obj, vmobjptridx(ConsoleObject), obj->pos);
  2705.                                                 } else {
  2706.                                                         return;
  2707.                                                 }
  2708.                                         } else {
  2709.                                                 if ((gun_point.x == 0) && (gun_point.y == 0) && (gun_point.z == 0)) {
  2710.                                                         ;
  2711.                                                 } else {
  2712.                                                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
  2713.                                                                 return;
  2714.                                                         ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0);
  2715.                                                 }
  2716.                                         }
  2717.  
  2718.                                         //      Wants to fire, so should go into chase mode, probably.
  2719.                                         if (aip->behavior != ai_behavior::AIB_RUN_FROM && aip->behavior != ai_behavior::AIB_STILL && aip->behavior != ai_behavior::AIB_FOLLOW_PATH && (ailp.mode == ai_mode::AIM_FOLLOW_PATH || ailp.mode == ai_mode::AIM_STILL))
  2720.                                                 ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  2721.                                 }
  2722.  
  2723.                                 aip->GOAL_STATE = AIS_RECO;
  2724.                                 ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
  2725.  
  2726.                                 // Switch to next gun for next fire.
  2727.                                 aip->CURRENT_GUN++;
  2728.                                 if (aip->CURRENT_GUN >= robptr.n_guns)
  2729.                                         aip->CURRENT_GUN = 0;
  2730.                         }
  2731.                 }
  2732.         } else if (Weapon_info[robptr.weapon_type].homing_flag) {
  2733.                 //      Robots which fire homing weapons might fire even if they don't have a bead on the player.
  2734.                 if ((!object_animates || ailp.achieved_state[aip->CURRENT_GUN] == AIS_FIRE)
  2735.                         && ready_to_fire_weapon1(ailp, 0)
  2736.                         && (vm_vec_dist_quick(Hit_pos, obj->pos) > F1_0*40)) {
  2737.                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
  2738.                                 return;
  2739.                         ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0);
  2740.  
  2741.                         aip->GOAL_STATE = AIS_RECO;
  2742.                         ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
  2743.  
  2744.                         // Switch to next gun for next fire.
  2745.                         aip->CURRENT_GUN++;
  2746.                         if (aip->CURRENT_GUN >= robptr.n_guns)
  2747.                                 aip->CURRENT_GUN = 0;
  2748.                 } else {
  2749.                         // Switch to next gun for next fire.
  2750.                         aip->CURRENT_GUN++;
  2751.                         if (aip->CURRENT_GUN >= robptr.n_guns)
  2752.                                 aip->CURRENT_GUN = 0;
  2753.                 }
  2754.         }
  2755. }
  2756. #elif defined(DXX_BUILD_DESCENT_II)
  2757.  
  2758. // --------------------------------------------------------------------------------------------------------------------
  2759. //      If fire_anyway, fire even if player is not visible.  We're firing near where we believe him to be.  Perhaps he's
  2760. //      lurking behind a corner.
  2761. static void ai_do_actual_firing_stuff(fvmobjptridx &vmobjptridx, const vmobjptridx_t obj, ai_static *const aip, ai_local &ailp, const robot_info &robptr, const fix dist_to_player, vms_vector &gun_point, const robot_to_player_visibility_state &player_visibility, const int object_animates, const player_info &player_info, const int gun_num)
  2762. {
  2763.         auto &vec_to_player = player_visibility.vec_to_player;
  2764.         fix     dot;
  2765.  
  2766.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2767.         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view || Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)
  2768.         {
  2769.                 vms_vector      fire_pos;
  2770.  
  2771.                 fire_pos = Believed_player_pos;
  2772.  
  2773.                 //      Hack: If visibility not == 2, we're here because we're firing at a nearby player.
  2774.                 //      So, fire at Last_fired_upon_player_pos instead of the player position.
  2775.                 if (!robptr.attack_type && player_visibility.visibility != player_visibility_state::visible_and_in_field_of_view)
  2776.                         fire_pos = Last_fired_upon_player_pos;
  2777.  
  2778.                 //      Changed by mk, 01/04/95, onearm would take about 9 seconds until he can fire at you.
  2779.                 //      Above comment corrected.  Date changed from 1994, to 1995.  Should fix some very subtle bugs, as well as not cause me to wonder, in the future, why I was writing AI code for onearm ten months before he existed.
  2780.                 if (!object_animates || ready_to_fire_any_weapon(robptr, ailp, 0)) {
  2781.                         dot = vm_vec_dot(obj->orient.fvec, vec_to_player);
  2782.                         if ((dot >= 7*F1_0/8) || ((dot > F1_0/4) &&  robptr.boss_flag)) {
  2783.  
  2784.                                 if (gun_num < robptr.n_guns) {
  2785.                                         if (robptr.attack_type == 1) {
  2786.                                                 if (Player_dead_state != player_dead_state::exploded &&
  2787.                                                         dist_to_player < obj->size + ConsoleObject->size + F1_0*2)
  2788.                                                 {
  2789.                                                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
  2790.                                                                 return;
  2791.                                                         do_ai_robot_hit_attack(obj, vmobjptridx(ConsoleObject), obj->pos);
  2792.                                                 } else {
  2793.                                                         return;
  2794.                                                 }
  2795.                                         } else {
  2796.                                                 if ((gun_point.x == 0) && (gun_point.y == 0) && (gun_point.z == 0)) {
  2797.                                                         ;
  2798.                                                 } else {
  2799.                                                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
  2800.                                                                 return;
  2801.                                                         //      New, multi-weapon-type system, 06/05/95 (life is slipping away...)
  2802.                                                         if (ready_to_fire_weapon1(ailp, 0)) {
  2803.                                                                 ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, gun_num, fire_pos);
  2804.                                                                 Last_fired_upon_player_pos = fire_pos;
  2805.                                                         }
  2806.                                                         if (gun_num != 0) {
  2807.                                                                 if (ready_to_fire_weapon2(robptr, ailp, 0)) {
  2808.                                                                         calc_gun_point(gun_point, obj, 0);
  2809.                                                                         ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0, fire_pos);
  2810.                                                                         Last_fired_upon_player_pos = fire_pos;
  2811.                                                                 }
  2812.                                                         }
  2813.                                                 }
  2814.                                         }
  2815.  
  2816.                                         //      Wants to fire, so should go into chase mode, probably.
  2817.                                         if ( (aip->behavior != ai_behavior::AIB_RUN_FROM)
  2818.                                                  && (aip->behavior != ai_behavior::AIB_STILL)
  2819.                                                  && (aip->behavior != ai_behavior::AIB_SNIPE)
  2820.                                                  && (aip->behavior != ai_behavior::AIB_FOLLOW)
  2821.                                                  && (!robptr.attack_type)
  2822.                                                  && (ailp.mode == ai_mode::AIM_FOLLOW_PATH || ailp.mode == ai_mode::AIM_STILL))
  2823.                                                 ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  2824.                                 }
  2825.  
  2826.                                 aip->GOAL_STATE = AIS_RECO;
  2827.                                 ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
  2828.  
  2829.                                 // Switch to next gun for next fire.  If has 2 gun types, select gun #1, if exists.
  2830.                                 aip->CURRENT_GUN++;
  2831.                                 if (aip->CURRENT_GUN >= robptr.n_guns)
  2832.                                 {
  2833.                                         if ((robptr.n_guns == 1) || (robptr.weapon_type2 == weapon_none))
  2834.                                                 aip->CURRENT_GUN = 0;
  2835.                                         else
  2836.                                                 aip->CURRENT_GUN = 1;
  2837.                                 }
  2838.                         }
  2839.                 }
  2840.         }
  2841.         else if ((!robptr.attack_type && Weapon_info[Robot_info[get_robot_id(obj)].weapon_type].homing_flag) || ((Robot_info[get_robot_id(obj)].weapon_type2 != weapon_none && Weapon_info[Robot_info[get_robot_id(obj)].weapon_type2].homing_flag)))
  2842.         {
  2843.                 //      Robots which fire homing weapons might fire even if they don't have a bead on the player.
  2844.                 if ((!object_animates || ailp.achieved_state[aip->CURRENT_GUN] == AIS_FIRE)
  2845.                         && (((ready_to_fire_weapon1(ailp, 0)) && (aip->CURRENT_GUN != 0)) || ((ready_to_fire_weapon2(robptr, ailp, 0)) && (aip->CURRENT_GUN == 0)))
  2846.                          && (vm_vec_dist_quick(Hit_pos, obj->pos) > F1_0*40)) {
  2847.                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
  2848.                                 return;
  2849.                         ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, gun_num, Believed_player_pos);
  2850.  
  2851.                         aip->GOAL_STATE = AIS_RECO;
  2852.                         ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
  2853.                 }
  2854.                         // Switch to next gun for next fire.
  2855.                         aip->CURRENT_GUN++;
  2856.                         if (aip->CURRENT_GUN >= robptr.n_guns)
  2857.                                 aip->CURRENT_GUN = 0;
  2858.         } else {
  2859.  
  2860.  
  2861.         //      ---------------------------------------------------------------
  2862.  
  2863.                 vms_vector      vec_to_last_pos;
  2864.  
  2865.                 if (d_rand()/2 < fixmul(FrameTime, (GameUniqueState.Difficulty_level << 12) + 0x4000)) {
  2866.                 if ((!object_animates || ready_to_fire_any_weapon(robptr, ailp, 0)) && (Dist_to_last_fired_upon_player_pos < FIRE_AT_NEARBY_PLAYER_THRESHOLD)) {
  2867.                         vm_vec_normalized_dir_quick(vec_to_last_pos, Believed_player_pos, obj->pos);
  2868.                         dot = vm_vec_dot(obj->orient.fvec, vec_to_last_pos);
  2869.                         if (dot >= 7*F1_0/8) {
  2870.  
  2871.                                 if (aip->CURRENT_GUN < robptr.n_guns) {
  2872.                                         if (robptr.attack_type == 1) {
  2873.                                                 if (Player_dead_state != player_dead_state::exploded &&
  2874.                                                         dist_to_player < obj->size + ConsoleObject->size + F1_0*2)
  2875.                                                 {
  2876.                                                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION-2))
  2877.                                                                 return;
  2878.                                                         do_ai_robot_hit_attack(obj, vmobjptridx(ConsoleObject), obj->pos);
  2879.                                                 } else {
  2880.                                                         return;
  2881.                                                 }
  2882.                                         } else {
  2883.                                                 if ((gun_point.x == 0) && (gun_point.y == 0) && (gun_point.z == 0)) {
  2884.                                                         ;
  2885.                                                 } else {
  2886.                                                         if (!ai_multiplayer_awareness(obj, ROBOT_FIRE_AGITATION))
  2887.                                                                 return;
  2888.                                                         //      New, multi-weapon-type system, 06/05/95 (life is slipping away...)
  2889.                                                         if (ready_to_fire_weapon1(ailp, 0))
  2890.                                                         {
  2891.                                                                 ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, gun_num, Last_fired_upon_player_pos);
  2892.                                                         }
  2893.                                                         if (gun_num != 0) {
  2894.  
  2895.                                                                 if (ready_to_fire_weapon2(robptr, ailp, 0)) {
  2896.                                                                         calc_gun_point(gun_point, obj, 0);
  2897.                                                                         ai_fire_laser_at_player(LevelSharedSegmentState, obj, player_info, gun_point, 0, Last_fired_upon_player_pos);
  2898.                                                                 }
  2899.                                                         }
  2900.                                                 }
  2901.                                         }
  2902.  
  2903.                                         //      Wants to fire, so should go into chase mode, probably.
  2904.                                         if (aip->behavior != ai_behavior::AIB_RUN_FROM && aip->behavior != ai_behavior::AIB_STILL && aip->behavior != ai_behavior::AIB_SNIPE && aip->behavior != ai_behavior::AIB_FOLLOW && (ailp.mode == ai_mode::AIM_FOLLOW_PATH || ailp.mode == ai_mode::AIM_STILL))
  2905.                                                 ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  2906.                                 }
  2907.                                 aip->GOAL_STATE = AIS_RECO;
  2908.                                 ailp.goal_state[aip->CURRENT_GUN] = AIS_RECO;
  2909.  
  2910.                                 // Switch to next gun for next fire.
  2911.                                 aip->CURRENT_GUN++;
  2912.                                 if (aip->CURRENT_GUN >= robptr.n_guns)
  2913.                                 {
  2914.                                         if (robptr.n_guns == 1)
  2915.                                                 aip->CURRENT_GUN = 0;
  2916.                                         else
  2917.                                                 aip->CURRENT_GUN = 1;
  2918.                                 }
  2919.                         }
  2920.                 }
  2921.                 }
  2922.  
  2923.  
  2924.         //      ---------------------------------------------------------------
  2925.         }
  2926. }
  2927.  
  2928. // ----------------------------------------------------------------------------
  2929. void init_ai_frame(const player_flags powerup_flags)
  2930. {
  2931.         Dist_to_last_fired_upon_player_pos = vm_vec_dist_quick(Last_fired_upon_player_pos, Believed_player_pos);
  2932.  
  2933.         if (!(powerup_flags & PLAYER_FLAGS_CLOAKED) ||
  2934.                 (powerup_flags & PLAYER_FLAGS_HEADLIGHT_ON) ||
  2935.                 (Afterburner_charge && Controls.state.afterburner && (powerup_flags & PLAYER_FLAGS_AFTERBURNER)))
  2936.         {
  2937.                 ai_do_cloak_stuff();
  2938.         }
  2939. }
  2940.  
  2941. // ----------------------------------------------------------------------------
  2942. // Make a robot near the player snipe.
  2943. #define MNRS_SEG_MAX    70
  2944. static void make_nearby_robot_snipe(fvmsegptr &vmsegptr, const vmobjptr_t robot, const player_flags powerup_flags)
  2945. {
  2946.         auto &Objects = LevelUniqueObjectState.Objects;
  2947.         auto &vmobjptridx = Objects.vmptridx;
  2948.         std::array<segnum_t, MNRS_SEG_MAX> bfs_list;
  2949.         /* Passing powerup_flags here seems wrong.  Sniping robots do not
  2950.          * open doors, so they should not care what doors the player can
  2951.          * open.  However, passing powerup_flags here maintains the
  2952.          * semantics that past versions used.
  2953.          */
  2954.         const auto bfs_length = create_bfs_list(robot, ConsoleObject->segnum, powerup_flags, bfs_list);
  2955.  
  2956.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2957.         range_for (auto &i, partial_const_range(bfs_list, bfs_length)) {
  2958.                 range_for (object &objp, objects_in(vmsegptr(i), vmobjptridx, vmsegptr))
  2959.                 {
  2960.                         object &obj = objp;
  2961.                         if (obj.type != OBJ_ROBOT)
  2962.                                 continue;
  2963.                         if (obj.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
  2964.                                 continue;
  2965.                         if (obj.ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM)
  2966.                                 continue;
  2967.                         const auto rid = get_robot_id(obj);
  2968.                         if (rid == ROBOT_BRAIN)
  2969.                                 continue;
  2970.                         auto &robptr = Robot_info[rid];
  2971.                         if (!robptr.boss_flag && !robot_is_companion(robptr))
  2972.                         {
  2973.                                         obj.ctype.ai_info.behavior = ai_behavior::AIB_SNIPE;
  2974.                                         obj.ctype.ai_info.ail.mode = ai_mode::AIM_SNIPE_ATTACK;
  2975.                                         return;
  2976.                         }
  2977.                 }
  2978.         }
  2979. }
  2980.  
  2981. const object *Ai_last_missile_camera;
  2982.  
  2983. static int openable_door_on_near_path(fvcsegptr &vcsegptr, fvcwallptr &vcwallptr, const object &obj, const ai_static &aip)
  2984. {
  2985.         const auto path_length = aip.path_length;
  2986.         if (path_length < 1)
  2987.                 return 0;
  2988.         if (const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(obj.segnum)) - side_none)
  2989.                 return r;
  2990.         if (path_length < 2)
  2991.                 return 0;
  2992.         size_t idx;
  2993.         idx = aip.hide_index + aip.cur_path_index + aip.PATH_DIR;
  2994.         /* Hack: Point_segs[idx].segnum should never be none here, but
  2995.          * sometimes the guidebot has a none.  Guard against the bogus none
  2996.          * until someone can track down why the guidebot does this.
  2997.          */
  2998.         if (idx < Point_segs.size() && Point_segs[idx].segnum != segment_none)
  2999.         {
  3000.                 if (const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(Point_segs[idx].segnum)) - side_none)
  3001.                         return r;
  3002.         }
  3003.         if (path_length < 3)
  3004.                 return 0;
  3005.         idx = aip.hide_index + aip.cur_path_index + 2*aip.PATH_DIR;
  3006.         if (idx < Point_segs.size() && Point_segs[idx].segnum != segment_none)
  3007.         {
  3008.                 if (const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(Point_segs[idx].segnum)) - side_none)
  3009.                         return r;
  3010.         }
  3011.         return 0;
  3012. }
  3013.  
  3014. static unsigned guidebot_should_fire_flare(fvcobjptr &vcobjptr, fvcsegptr &vcsegptr, fvcwallptr &vcwallptr, d_unique_buddy_state &BuddyState, const robot_info &robptr, const object &buddy_obj)
  3015. {
  3016.         auto &ais = buddy_obj.ctype.ai_info;
  3017.         auto &ail = ais.ail;
  3018.         if (const auto r = ready_to_fire_any_weapon(robptr, ail, 0))
  3019.         {
  3020.         }
  3021.         else
  3022.                 return r;
  3023.         if (const auto r = openable_door_on_near_path(vcsegptr, vcwallptr, buddy_obj, ais))
  3024.                 return r;
  3025.         if (ail.mode != ai_mode::AIM_GOTO_PLAYER)
  3026.                 return 0;
  3027.         auto &plr = get_player_controlling_guidebot(BuddyState, Players);
  3028.         if (plr.objnum == object_none)
  3029.                 /* should never happen */
  3030.                 return 0;
  3031.         auto &plrobj = *vcobjptr(plr.objnum);
  3032.         if (plrobj.type != OBJ_PLAYER)
  3033.                 return 0;
  3034.         vms_vector vec_to_controller;
  3035.         const auto dist_to_controller = vm_vec_normalized_dir_quick(vec_to_controller, plrobj.pos, buddy_obj.pos);
  3036.         if (dist_to_controller >= 3 * MIN_ESCORT_DISTANCE / 2)
  3037.                 return 0;
  3038.         if (vm_vec_dot(plrobj.orient.fvec, vec_to_controller) <= -F1_0 / 4)
  3039.                 return 0;
  3040.         return 1;
  3041. }
  3042. #endif
  3043.  
  3044. #ifdef NDEBUG
  3045. static bool is_break_object(vcobjidx_t)
  3046. {
  3047.         return false;
  3048. }
  3049. #else
  3050. __attribute_used
  3051. static objnum_t Break_on_object = object_none;
  3052.  
  3053. static bool is_break_object(const vcobjidx_t robot)
  3054. {
  3055.         return Break_on_object == robot;
  3056. }
  3057. #endif
  3058.  
  3059. static bool skip_ai_for_time_splice(const vcobjptridx_t robot, const robot_info &robptr, const vm_distance &dist_to_player)
  3060. {
  3061.         if (unlikely(is_break_object(robot)))
  3062.                 // don't time slice if we're interested in this object.
  3063.                 return false;
  3064.         const auto &aip = robot->ctype.ai_info;
  3065.         const auto &ailp = aip.ail;
  3066. #if defined(DXX_BUILD_DESCENT_I)
  3067.         (void)robptr;
  3068.         if (static_cast<uint8_t>(ailp.player_awareness_type) < static_cast<uint8_t>(player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION) - 1)
  3069.         { // If robot got hit, he gets to attack player always!
  3070.                 {
  3071.                         if ((dist_to_player > F1_0*250) && (ailp.time_since_processed <= F1_0*2))
  3072.                                 return true;
  3073.                         else if (!((aip.behavior == ai_behavior::AIB_STATION) && (ailp.mode == ai_mode::AIM_FOLLOW_PATH) && (aip.hide_segment != robot->segnum))) {
  3074.                                 if ((dist_to_player > F1_0*150) && (ailp.time_since_processed <= F1_0))
  3075.                                         return true;
  3076.                                 else if ((dist_to_player > F1_0*100) && (ailp.time_since_processed <= F1_0/2))
  3077.                                         return true;
  3078.                         }
  3079.                 }
  3080.         }
  3081. #elif defined(DXX_BUILD_DESCENT_II)
  3082.         if (!((aip.behavior == ai_behavior::AIB_SNIPE) && (ailp.mode != ai_mode::AIM_SNIPE_WAIT)) && !robot_is_companion(robptr) && !robot_is_thief(robptr) && static_cast<uint8_t>(ailp.player_awareness_type) < static_cast<uint8_t>(player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION) - 1)
  3083.         { // If robot got hit, he gets to attack player always!
  3084.                 {
  3085.                         if ((aip.behavior == ai_behavior::AIB_STATION) && (ailp.mode == ai_mode::AIM_FOLLOW_PATH) && (aip.hide_segment != robot->segnum)) {
  3086.                                 if (dist_to_player > F1_0*250)  // station guys not at home always processed until 250 units away.
  3087.                                         return true;
  3088.                         }
  3089.                         else if (!player_is_visible(ailp.previous_visibility) && ((dist_to_player >> 7) > ailp.time_since_processed))
  3090.                         {  // 128 units away (6.4 segments) processed after 1 second.
  3091.                                 return true;
  3092.                         }
  3093.                 }
  3094.         }
  3095. #endif
  3096.         return false;
  3097. }
  3098.  
  3099. // --------------------------------------------------------------------------------------------------------------------
  3100. void do_ai_frame(const vmobjptridx_t obj)
  3101. {
  3102.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  3103.         auto &Vertices = LevelSharedVertexState.get_vertices();
  3104.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  3105.         const objnum_t &objnum = obj;
  3106.         ai_static       *const aip = &obj->ctype.ai_info;
  3107.         ai_local &ailp = obj->ctype.ai_info.ail;
  3108.         int                     obj_ref;
  3109.         int                     object_animates;
  3110.         int                     new_goal_state;
  3111.         vms_vector      gun_point;
  3112.         vms_vector      vis_vec_pos;
  3113.         auto &Objects = LevelUniqueObjectState.Objects;
  3114.         auto &vmobjptr = Objects.vmptr;
  3115.         auto &vmobjptridx = Objects.vmptridx;
  3116.  
  3117. #if defined(DXX_BUILD_DESCENT_II)
  3118.         auto &BuddyState = LevelUniqueObjectState.BuddyState;
  3119.         auto &vcobjptr = Objects.vcptr;
  3120.         ailp.next_action_time -= FrameTime;
  3121. #endif
  3122.  
  3123.         if (aip->SKIP_AI_COUNT) {
  3124.                 aip->SKIP_AI_COUNT--;
  3125. #if defined(DXX_BUILD_DESCENT_II)
  3126.                 if (obj->mtype.phys_info.flags & PF_USES_THRUST) {
  3127.                         obj->mtype.phys_info.rotthrust.x = (obj->mtype.phys_info.rotthrust.x * 15)/16;
  3128.                         obj->mtype.phys_info.rotthrust.y = (obj->mtype.phys_info.rotthrust.y * 15)/16;
  3129.                         obj->mtype.phys_info.rotthrust.z = (obj->mtype.phys_info.rotthrust.z * 15)/16;
  3130.                         if (!aip->SKIP_AI_COUNT)
  3131.                                 obj->mtype.phys_info.flags &= ~PF_USES_THRUST;
  3132.                 }
  3133. #endif
  3134.                 return;
  3135.         }
  3136.  
  3137.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  3138. #if defined(DXX_BUILD_DESCENT_II)
  3139.         auto &Station = LevelUniqueFuelcenterState.Station;
  3140. #endif
  3141.         auto &robptr = Robot_info[get_robot_id(obj)];
  3142.         Assert(robptr.always_0xabcd == 0xabcd);
  3143.  
  3144.         if (do_any_robot_dying_frame(obj))
  3145.                 return;
  3146.  
  3147.         // Kind of a hack.  If a robot is flinching, but it is time for it to fire, unflinch it.
  3148.         // Else, you can turn a big nasty robot into a wimp by firing flares at it.
  3149.         // This also allows the player to see the cool flinch effect for mechs without unbalancing the game.
  3150.         if ((aip->GOAL_STATE == AIS_FLIN) && ready_to_fire_any_weapon(robptr, ailp, 0)) {
  3151.                 aip->GOAL_STATE = AIS_FIRE;
  3152.         }
  3153.  
  3154. #ifndef NDEBUG
  3155.         if (aip->behavior == ai_behavior::AIB_RUN_FROM && ailp.mode != ai_mode::AIM_RUN_FROM_OBJECT)
  3156.                 Int3(); // This is peculiar.  Behavior is run from, but mode is not.  Contact Mike.
  3157.  
  3158.         if (Break_on_object != object_none)
  3159.                 if ((obj) == Break_on_object)
  3160.                         Int3(); // Contact Mike: This is a debug break
  3161. #endif
  3162.  
  3163.         //Assert((aip->behavior >= MIN_BEHAVIOR) && (aip->behavior <= MAX_BEHAVIOR));
  3164.         switch (aip->behavior)
  3165.         {
  3166.                 case ai_behavior::AIB_STILL:
  3167.                 case ai_behavior::AIB_NORMAL:
  3168.                 case ai_behavior::AIB_RUN_FROM:
  3169.                 case ai_behavior::AIB_STATION:
  3170. #if defined(DXX_BUILD_DESCENT_I)
  3171.                 case ai_behavior::AIB_HIDE:
  3172.                 case ai_behavior::AIB_FOLLOW_PATH:
  3173. #elif defined(DXX_BUILD_DESCENT_II)
  3174.                 case ai_behavior::AIB_BEHIND:
  3175.                 case ai_behavior::AIB_SNIPE:
  3176.                 case ai_behavior::AIB_FOLLOW:
  3177. #endif
  3178.                         break;
  3179.                 default:
  3180.                         aip->behavior = ai_behavior::AIB_NORMAL;
  3181.                         break;
  3182.         }
  3183.  
  3184.         Assert(obj->segnum != segment_none);
  3185.         assert(get_robot_id(obj) < LevelSharedRobotInfoState.N_robot_types);
  3186.  
  3187.         obj_ref = objnum ^ d_tick_count;
  3188.  
  3189.         if (!ready_to_fire_weapon1(ailp, -F1_0*8))
  3190.                 ailp.next_fire -= FrameTime;
  3191.  
  3192. #if defined(DXX_BUILD_DESCENT_I)
  3193.         Believed_player_pos = Ai_cloak_info[objnum & (MAX_AI_CLOAK_INFO-1)].last_position;
  3194. #elif defined(DXX_BUILD_DESCENT_II)
  3195.         if (robptr.weapon_type2 != weapon_none) {
  3196.                 if (!ready_to_fire_weapon2(robptr, ailp, -F1_0*8))
  3197.                         ailp.next_fire2 -= FrameTime;
  3198.         } else
  3199.                 ailp.next_fire2 = F1_0*8;
  3200. #endif
  3201.  
  3202.         if (ailp.time_since_processed < F1_0*256)
  3203.                 ailp.time_since_processed += FrameTime;
  3204.  
  3205.         const auto previous_visibility = ailp.previous_visibility;    //  Must get this before we toast the master copy!
  3206.  
  3207.         auto &plrobj = get_local_plrobj();
  3208.         auto &player_info = plrobj.ctype.player_info;
  3209.         robot_to_player_visibility_state player_visibility;
  3210.         auto &vec_to_player = player_visibility.vec_to_player;
  3211. #if defined(DXX_BUILD_DESCENT_I)
  3212.         if (!(player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
  3213.                 Believed_player_pos = ConsoleObject->pos;
  3214. #elif defined(DXX_BUILD_DESCENT_II)
  3215.         // If only awake because of a camera, make that the believed player position.
  3216.         if ((aip->SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE) && Ai_last_missile_camera)
  3217.                 Believed_player_pos = Ai_last_missile_camera->pos;
  3218.         else {
  3219.                 if (cheats.robotskillrobots) {
  3220.                         vis_vec_pos = obj->pos;
  3221.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3222.                         if (player_is_visible(player_visibility.visibility))
  3223.                         {
  3224.                                 icobjptr_t min_obj = nullptr;
  3225.                                 fix min_dist = F1_0*200, cur_dist;
  3226.  
  3227.                                 range_for (const auto &&objp, vcobjptr)
  3228.                                 {
  3229.                                         if (objp->type == OBJ_ROBOT && objp != obj)
  3230.                                         {
  3231.                                                 cur_dist = vm_vec_dist_quick(obj->pos, objp->pos);
  3232.                                                 if (cur_dist < F1_0*100)
  3233.                                                         if (object_to_object_visibility(obj, objp, FQ_TRANSWALL))
  3234.                                                                 if (cur_dist < min_dist) {
  3235.                                                                         min_obj = objp;
  3236.                                                                         min_dist = cur_dist;
  3237.                                                                 }
  3238.                                         }
  3239.                                 }
  3240.  
  3241.                                 if (min_obj != nullptr)
  3242.                                 {
  3243.                                         Believed_player_pos = min_obj->pos;
  3244.                                         Believed_player_seg = min_obj->segnum;
  3245.                                         vm_vec_normalized_dir_quick(vec_to_player, Believed_player_pos, obj->pos);
  3246.                                 } else
  3247.                                         goto _exit_cheat;
  3248.                         } else
  3249.                                 goto _exit_cheat;
  3250.                 } else {
  3251. _exit_cheat:
  3252.                         DXX_MAKE_MEM_UNDEFINED(&player_visibility, sizeof(player_visibility));
  3253.                         player_visibility.initialized = 0;
  3254.                         if (!(player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
  3255.                                 Believed_player_pos = ConsoleObject->pos;
  3256.                         else
  3257.                                 Believed_player_pos = Ai_cloak_info[objnum & (MAX_AI_CLOAK_INFO-1)].last_position;
  3258.                 }
  3259.         }
  3260. #endif
  3261.         auto dist_to_player = vm_vec_dist_quick(Believed_player_pos, obj->pos);
  3262.  
  3263.         // If this robot can fire, compute visibility from gun position.
  3264.         // Don't want to compute visibility twice, as it is expensive.  (So is call to calc_gun_point).
  3265. #if defined(DXX_BUILD_DESCENT_I)
  3266.         if (ready_to_fire_any_weapon(robptr, ailp, 0) && (dist_to_player < F1_0*200) && (robptr.n_guns) && !(robptr.attack_type))
  3267. #elif defined(DXX_BUILD_DESCENT_II)
  3268.         if ((player_is_visible(previous_visibility) || !(obj_ref & 3)) && ready_to_fire_any_weapon(robptr, ailp, 0) && (dist_to_player < F1_0*200) && (robptr.n_guns) && !(robptr.attack_type))
  3269. #endif
  3270.         {
  3271.                 // Since we passed ready_to_fire_any_weapon(), either next_fire or next_fire2 <= 0.  calc_gun_point from relevant one.
  3272.                 // If both are <= 0, we will deal with the mess in ai_do_actual_firing_stuff
  3273.                 const auto gun_num =
  3274. #if defined(DXX_BUILD_DESCENT_II)
  3275.                         (!ready_to_fire_weapon1(ailp, 0))
  3276.                         ? 0
  3277.                         :
  3278. #endif
  3279.                         aip->CURRENT_GUN;
  3280.                         calc_gun_point(gun_point, obj, gun_num);
  3281.                 vis_vec_pos = gun_point;
  3282.         } else {
  3283.                 vis_vec_pos = obj->pos;
  3284.                 vm_vec_zero(gun_point);
  3285.         }
  3286.  
  3287.         switch (const auto boss_flag = robptr.boss_flag) {
  3288.                 case 0:
  3289.                         break;
  3290.                        
  3291.                 case BOSS_SUPER:
  3292.                         if (aip->GOAL_STATE == AIS_FLIN)
  3293.                                 aip->GOAL_STATE = AIS_FIRE;
  3294.                         if (aip->CURRENT_STATE == AIS_FLIN)
  3295.                                 aip->CURRENT_STATE = AIS_FIRE;
  3296.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3297.                        
  3298.                         {
  3299.                                 auto pv = player_visibility.visibility;
  3300.                         auto dtp = dist_to_player/4;
  3301.                        
  3302.                         // If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to.
  3303.                         if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) {
  3304.                                 pv = player_visibility_state::no_line_of_sight;
  3305.                                 dtp = vm_vec_dist_quick(ConsoleObject->pos, obj->pos)/4;
  3306.                         }
  3307.                        
  3308.                         do_super_boss_stuff(vmsegptridx, obj, dtp, pv);
  3309.                 }
  3310.                         break;
  3311.                        
  3312.                 default:
  3313. #if defined(DXX_BUILD_DESCENT_I)
  3314.                         (void)boss_flag;
  3315.                         Int3(); //      Bogus boss flag value.
  3316.                         break;
  3317. #endif
  3318.                 case BOSS_D1:
  3319.                 {
  3320.                         if (aip->GOAL_STATE == AIS_FLIN)
  3321.                                 aip->GOAL_STATE = AIS_FIRE;
  3322.                         if (aip->CURRENT_STATE == AIS_FLIN)
  3323.                                 aip->CURRENT_STATE = AIS_FIRE;
  3324.                        
  3325.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3326.                        
  3327. #if defined(DXX_BUILD_DESCENT_II)
  3328.                         auto pv = player_visibility.visibility;
  3329.                         // If player cloaked, visibility is screwed up and superboss will gate in robots when not supposed to.
  3330.                         if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) {
  3331.                                 pv = player_visibility_state::no_line_of_sight;
  3332.                         }
  3333.  
  3334.                         if (boss_flag != BOSS_D1)
  3335.                         {
  3336.                                 do_d2_boss_stuff(vmsegptridx, obj, pv);
  3337.                                 break;
  3338.                         }
  3339. #endif
  3340.                         do_d1_boss_stuff(vmsegptridx, obj, pv);
  3341.                 }
  3342.                         break;
  3343.         }
  3344.        
  3345.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  3346.         // Occasionally make non-still robots make a path to the player.  Based on agitation and distance from player.
  3347. #if defined(DXX_BUILD_DESCENT_I)
  3348.         if ((aip->behavior != ai_behavior::AIB_RUN_FROM) && (aip->behavior != ai_behavior::AIB_STILL) && !(Game_mode & GM_MULTI))
  3349. #elif defined(DXX_BUILD_DESCENT_II)
  3350.         if ((aip->behavior != ai_behavior::AIB_SNIPE) && (aip->behavior != ai_behavior::AIB_RUN_FROM) && (aip->behavior != ai_behavior::AIB_STILL) && !(Game_mode & GM_MULTI) && (robot_is_companion(robptr) != 1) && (robot_is_thief(robptr) != 1))
  3351. #endif
  3352.                 if (Overall_agitation > 70) {
  3353.                         if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/4)) {
  3354.                                 if (d_rand() * (Overall_agitation - 40) > F1_0*5) {
  3355.                                         create_path_to_believed_player_segment(obj, 4 + Overall_agitation/8 + Difficulty_level, create_path_safety_flag::safe);
  3356.                                         return;
  3357.                                 }
  3358.                         }
  3359.                 }
  3360.  
  3361.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  3362.         // If retry count not 0, then add it into consecutive_retries.
  3363.         // If it is 0, cut down consecutive_retries.
  3364.         // This is largely a hack to speed up physics and deal with stupid
  3365.         // AI.  This is low level communication between systems of a sort
  3366.         // that should not be done.
  3367.         if (ailp.retry_count && !(Game_mode & GM_MULTI))
  3368.         {
  3369.                 ailp.consecutive_retries += ailp.retry_count;
  3370.                 ailp.retry_count = 0;
  3371.                 if (ailp.consecutive_retries > 3)
  3372.                 {
  3373.                         switch (ailp.mode) {
  3374. #if defined(DXX_BUILD_DESCENT_II)
  3375.                                 case ai_mode::AIM_GOTO_PLAYER:
  3376.                                         move_towards_segment_center(LevelSharedSegmentState, obj);
  3377.                                         create_path_to_guidebot_player_segment(obj, 100, create_path_safety_flag::safe);
  3378.                                         break;
  3379.                                 case ai_mode::AIM_GOTO_OBJECT:
  3380.                                         BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
  3381.                                         break;
  3382. #endif
  3383.                                 case ai_mode::AIM_CHASE_OBJECT:
  3384.                                         create_path_to_believed_player_segment(obj, 4 + Overall_agitation/8 + Difficulty_level, create_path_safety_flag::safe);
  3385.                                         break;
  3386.                                 case ai_mode::AIM_STILL:
  3387. #if defined(DXX_BUILD_DESCENT_I)
  3388.                                         if (!((aip->behavior == ai_behavior::AIB_STILL) || (aip->behavior == ai_behavior::AIB_STATION)))        //      Behavior is still, so don't follow path.
  3389. #elif defined(DXX_BUILD_DESCENT_II)
  3390.                                         if (robptr.attack_type)
  3391.                                                 move_towards_segment_center(LevelSharedSegmentState, obj);
  3392.                                         else if (!((aip->behavior == ai_behavior::AIB_STILL) || (aip->behavior == ai_behavior::AIB_STATION) || (aip->behavior == ai_behavior::AIB_FOLLOW)))    // Behavior is still, so don't follow path.
  3393. #endif
  3394.                                                 attempt_to_resume_path(obj);
  3395.                                         break;
  3396.                                 case ai_mode::AIM_FOLLOW_PATH:
  3397.                                         if (Game_mode & GM_MULTI)
  3398.                                                 ailp.mode = ai_mode::AIM_STILL;
  3399.                                         else
  3400.                                                 attempt_to_resume_path(obj);
  3401.                                         break;
  3402.                                 case ai_mode::AIM_RUN_FROM_OBJECT:
  3403.                                         move_towards_segment_center(LevelSharedSegmentState, obj);
  3404.                                         obj->mtype.phys_info.velocity = {};
  3405.                                         create_n_segment_path(obj, 5, segment_none);
  3406.                                         ailp.mode = ai_mode::AIM_RUN_FROM_OBJECT;
  3407.                                         break;
  3408. #if defined(DXX_BUILD_DESCENT_I)
  3409.                                 case ai_mode::AIM_HIDE:
  3410.                                         move_towards_segment_center(LevelSharedSegmentState, obj);
  3411.                                         obj->mtype.phys_info.velocity = {};
  3412.                                         if (Overall_agitation > (50 - Difficulty_level*4))
  3413.                                                 create_path_to_believed_player_segment(obj, 4 + Overall_agitation/8, create_path_safety_flag::safe);
  3414.                                         else {
  3415.                                                 create_n_segment_path(obj, 5, segment_none);
  3416.                                         }
  3417.                                         break;
  3418. #elif defined(DXX_BUILD_DESCENT_II)
  3419.                                 case ai_mode::AIM_BEHIND:
  3420.                                         move_towards_segment_center(LevelSharedSegmentState, obj);
  3421.                                         obj->mtype.phys_info.velocity = {};
  3422.                                         break;
  3423.                                 case ai_mode::AIM_SNIPE_ATTACK:
  3424.                                 case ai_mode::AIM_SNIPE_FIRE:
  3425.                                 case ai_mode::AIM_SNIPE_RETREAT:
  3426.                                 case ai_mode::AIM_SNIPE_RETREAT_BACKWARDS:
  3427.                                 case ai_mode::AIM_SNIPE_WAIT:
  3428.                                 case ai_mode::AIM_THIEF_ATTACK:
  3429.                                 case ai_mode::AIM_THIEF_RETREAT:
  3430.                                 case ai_mode::AIM_THIEF_WAIT:
  3431.                                         break;
  3432. #endif
  3433.                                 case ai_mode::AIM_OPEN_DOOR:
  3434.                                         create_n_segment_path_to_door(obj, 5);
  3435.                                         break;
  3436.                                 case ai_mode::AIM_FOLLOW_PATH_2:
  3437.                                         Int3(); // Should never happen!
  3438.                                         break;
  3439.                                 case ai_mode::AIM_WANDER:
  3440.                                         break;
  3441.                         }
  3442.                         ailp.consecutive_retries = 0;
  3443.                 }
  3444.         } else
  3445.                 ailp.consecutive_retries /= 2;
  3446.  
  3447.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  3448.         // If in materialization center, exit
  3449.         if (!(Game_mode & GM_MULTI))
  3450.         {
  3451.                 const shared_segment &seg = *vcsegptr(obj->segnum);
  3452.                 if (seg.special == SEGMENT_IS_ROBOTMAKER)
  3453.                 {
  3454. #if defined(DXX_BUILD_DESCENT_II)
  3455.                         if (Station[seg.station_idx].Enabled)
  3456. #endif
  3457.                 {
  3458.                         ai_follow_path(obj, player_visibility_state::visible_not_in_field_of_view, nullptr);    // 1 = player is visible, which might be a lie, but it works.
  3459.                         return;
  3460.                 }
  3461.                 }
  3462.         }
  3463.  
  3464.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  3465.         // Decrease player awareness due to the passage of time.
  3466.         if (ailp.player_awareness_type != player_awareness_type_t::PA_NONE)
  3467.         {
  3468.                 if (ailp.player_awareness_time > 0)
  3469.                 {
  3470.                         ailp.player_awareness_time -= FrameTime;
  3471.                         if (ailp.player_awareness_time <= 0)
  3472.                         {
  3473.                                 ailp.player_awareness_time = F1_0*2;   //new: 11/05/94
  3474.                                 ailp.player_awareness_type = static_cast<player_awareness_type_t>(static_cast<unsigned>(ailp.player_awareness_type) - 1);          //new: 11/05/94
  3475.                         }
  3476.                 } else {
  3477.                         ailp.player_awareness_time = F1_0*2;
  3478.                         ailp.player_awareness_type = static_cast<player_awareness_type_t>(static_cast<unsigned>(ailp.player_awareness_type) - 1);
  3479.                         //aip->GOAL_STATE = AIS_REST;
  3480.                 }
  3481.         } else
  3482.                 aip->GOAL_STATE = AIS_REST;                     //new: 12/13/94
  3483.  
  3484.  
  3485.         if (Player_dead_state != player_dead_state::no &&
  3486.                 ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
  3487.                 if ((dist_to_player < F1_0*200) && (d_rand() < FrameTime/8)) {
  3488.                         if ((aip->behavior != ai_behavior::AIB_STILL) && (aip->behavior != ai_behavior::AIB_RUN_FROM)) {
  3489.                                 if (!ai_multiplayer_awareness(obj, 30))
  3490.                                         return;
  3491.                                 ai_multi_send_robot_position(obj, -1);
  3492.  
  3493.                                 if (!(ailp.mode == ai_mode::AIM_FOLLOW_PATH && aip->cur_path_index < aip->path_length - 1))
  3494. #if defined(DXX_BUILD_DESCENT_II)
  3495.                                         if ((aip->behavior != ai_behavior::AIB_SNIPE) && (aip->behavior != ai_behavior::AIB_RUN_FROM))
  3496. #endif
  3497.                                         {
  3498.                                                 if (dist_to_player < F1_0*30)
  3499.                                                         create_n_segment_path(obj, 5, segment_none);
  3500.                                                 else
  3501.                                                         create_path_to_believed_player_segment(obj, 20, create_path_safety_flag::safe);
  3502.                                         }
  3503.                         }
  3504.                 }
  3505.  
  3506.         //      Make sure that if this guy got hit or bumped, then he's chasing player.
  3507.         if (ailp.player_awareness_type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION || ailp.player_awareness_type >= player_awareness_type_t::PA_PLAYER_COLLISION)
  3508. #if defined(DXX_BUILD_DESCENT_I)
  3509.         {
  3510.                 if ((aip->behavior != ai_behavior::AIB_STILL) && (aip->behavior != ai_behavior::AIB_FOLLOW_PATH) && (aip->behavior != ai_behavior::AIB_RUN_FROM) && (get_robot_id(obj) != ROBOT_BRAIN))
  3511.                         ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  3512.         }
  3513. #elif defined(DXX_BUILD_DESCENT_II)
  3514.         {
  3515.                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3516.                 if (player_visibility.visibility == player_visibility_state::visible_not_in_field_of_view) // Only increase visibility if unobstructed, else claw guys attack through doors.
  3517.                         player_visibility.visibility = player_visibility_state::visible_and_in_field_of_view;
  3518.         } else if (((obj_ref&3) == 0) && !player_is_visible(previous_visibility) && (dist_to_player < F1_0*100)) {
  3519.                 fix sval, rval;
  3520.  
  3521.                 rval = d_rand();
  3522.                 sval = (dist_to_player * (static_cast<int>(Difficulty_level) + 1)) / 64;
  3523.  
  3524.                 if ((fixmul(rval, sval) < FrameTime) || (player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT_ON)) {
  3525.                         ailp.player_awareness_type = player_awareness_type_t::PA_PLAYER_COLLISION;
  3526.                         ailp.player_awareness_time = F1_0*3;
  3527.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3528.                         if (player_visibility.visibility == player_visibility_state::visible_not_in_field_of_view)
  3529.                                 player_visibility.visibility = player_visibility_state::visible_and_in_field_of_view;
  3530.                 }
  3531.         }
  3532. #endif
  3533.  
  3534.         //      - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -
  3535.         if ((aip->GOAL_STATE == AIS_FLIN) && (aip->CURRENT_STATE == AIS_FLIN))
  3536.                 aip->GOAL_STATE = AIS_LOCK;
  3537.  
  3538.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  3539.         // Note: Should only do these two function calls for objects which animate
  3540.         if (dist_to_player < F1_0*100) { // && !(Game_mode & GM_MULTI))
  3541.                 object_animates = do_silly_animation(obj);
  3542.                 if (object_animates)
  3543.                         ai_frame_animation(obj);
  3544.         } else {
  3545.                 // If Object is supposed to animate, but we don't let it animate due to distance, then
  3546.                 // we must change its state, else it will never update.
  3547.                 aip->CURRENT_STATE = aip->GOAL_STATE;
  3548.                 object_animates = 0;        // If we're not doing the animation, then should pretend it doesn't animate.
  3549.         }
  3550.  
  3551.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  3552.         // Time-slice, don't process all the time, purely an efficiency hack.
  3553.         // Guys whose behavior is station and are not at their hide segment get processed anyway.
  3554.         if (skip_ai_for_time_splice(obj, robptr, dist_to_player))
  3555.                 return;
  3556.  
  3557.         // Reset time since processed, but skew objects so not everything
  3558.         // processed synchronously, else we get fast frames with the
  3559.         // occasional very slow frame.
  3560.         // AI_proc_time = ailp->time_since_processed;
  3561.         ailp.time_since_processed = - ((objnum & 0x03) * FrameTime ) / 2;
  3562.  
  3563.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  3564.         //      Perform special ability
  3565.         switch (get_robot_id(obj)) {
  3566.                 case ROBOT_BRAIN:
  3567.                         // Robots function nicely if behavior is Station.  This
  3568.                         // means they won't move until they can see the player, at
  3569.                         // which time they will start wandering about opening doors.
  3570.                         if (ConsoleObject->segnum == obj->segnum) {
  3571.                                 if (!ai_multiplayer_awareness(obj, 97))
  3572.                                         return;
  3573.                                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3574.                                 move_away_from_player(obj, vec_to_player, 0);
  3575.                                 ai_multi_send_robot_position(obj, -1);
  3576.                         }
  3577.                         else if (ailp.mode != ai_mode::AIM_STILL)
  3578.                         {
  3579.                                 auto &Walls = LevelUniqueWallSubsystemState.Walls;
  3580.                                 auto &vcwallptr = Walls.vcptr;
  3581.                                 const auto r = openable_doors_in_segment(vcwallptr, vcsegptr(obj->segnum));
  3582.                                 if (r != side_none)
  3583.                                 {
  3584.                                         ailp.mode = ai_mode::AIM_OPEN_DOOR;
  3585.                                         aip->GOALSIDE = r;
  3586.                                 }
  3587.                                 else if (ailp.mode != ai_mode::AIM_FOLLOW_PATH)
  3588.                                 {
  3589.                                         if (!ai_multiplayer_awareness(obj, 50))
  3590.                                                 return;
  3591.                                         create_n_segment_path_to_door(obj, 8+Difficulty_level);     // third parameter is avoid_seg, -1 means avoid nothing.
  3592.                                         ai_multi_send_robot_position(obj, -1);
  3593.                                 }
  3594.  
  3595. #if defined(DXX_BUILD_DESCENT_II)
  3596.                                 if (ailp.next_action_time < 0)
  3597.                                 {
  3598.                                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3599.                                         if (player_is_visible(player_visibility.visibility))
  3600.                                         {
  3601.                                                 const auto powerup_flags = player_info.powerup_flags;
  3602.                                                 make_nearby_robot_snipe(vmsegptr, obj, powerup_flags);
  3603.                                                 ailp.next_action_time = (NDL - Difficulty_level) * 2*F1_0;
  3604.                                         }
  3605.                                 }
  3606. #endif
  3607.                         } else {
  3608.                                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3609.                                 if (player_is_visible(player_visibility.visibility))
  3610.                                 {
  3611.                                         if (!ai_multiplayer_awareness(obj, 50))
  3612.                                                 return;
  3613.                                         create_n_segment_path_to_door(obj, 8+Difficulty_level);     // third parameter is avoid_seg, -1 means avoid nothing.
  3614.                                         ai_multi_send_robot_position(obj, -1);
  3615.                                 }
  3616.                         }
  3617.                         break;
  3618.                 default:
  3619.                         break;
  3620.         }
  3621.  
  3622. #if defined(DXX_BUILD_DESCENT_II)
  3623.         if (aip->behavior == ai_behavior::AIB_SNIPE) {
  3624.                 if ((Game_mode & GM_MULTI) && !robot_is_thief(robptr)) {
  3625.                         aip->behavior = ai_behavior::AIB_NORMAL;
  3626.                         ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  3627.                         return;
  3628.                 }
  3629.  
  3630.                 if (!(obj_ref & 3) || player_is_visible(previous_visibility))
  3631.                 {
  3632.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3633.  
  3634.                         // If this sniper is in still mode, if he was hit or can see player, switch to snipe mode.
  3635.                         if (ailp.mode == ai_mode::AIM_STILL)
  3636.                                 if (player_is_visible(player_visibility.visibility) || ailp.player_awareness_type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
  3637.                                         ailp.mode = ai_mode::AIM_SNIPE_ATTACK;
  3638.  
  3639.                         if (!robot_is_thief(robptr) && ailp.mode != ai_mode::AIM_STILL)
  3640.                                 do_snipe_frame(obj, dist_to_player, player_visibility.visibility, vec_to_player);
  3641.                 } else if (!robot_is_thief(robptr) && !robot_is_companion(robptr))
  3642.                         return;
  3643.         }
  3644.  
  3645.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  3646.         auto &vcwallptr = Walls.vcptr;
  3647.         // More special ability stuff, but based on a property of a robot, not its ID.
  3648.         if (robot_is_companion(robptr)) {
  3649.                 compute_buddy_vis_vec(obj, obj->pos, player_visibility, robptr);
  3650.                 auto &player_controlling_guidebot = get_player_controlling_guidebot(BuddyState, Players);
  3651.                 if (player_controlling_guidebot.objnum != object_none)
  3652.                 {
  3653.                         auto &plrobj_controlling_guidebot = *Objects.vcptr(player_controlling_guidebot.objnum);
  3654.                         do_escort_frame(obj, plrobj_controlling_guidebot, player_visibility.visibility);
  3655.                 }
  3656.  
  3657.                 if (obj->ctype.ai_info.danger_laser_num != object_none) {
  3658.                         auto &dobjp = *vcobjptr(obj->ctype.ai_info.danger_laser_num);
  3659.                         if ((dobjp.type == OBJ_WEAPON) && (dobjp.signature == obj->ctype.ai_info.danger_laser_signature))
  3660.                         {
  3661.                                 fix circle_distance;
  3662.                                 circle_distance = robptr.circle_distance[Difficulty_level] + ConsoleObject->size;
  3663.                                 ai_move_relative_to_player(obj, ailp, dist_to_player, circle_distance, 1, player_visibility, player_info);
  3664.                         }
  3665.                 }
  3666.  
  3667.                 if (guidebot_should_fire_flare(vcobjptr, vcsegptr, vcwallptr, BuddyState, robptr, *obj))
  3668.                 {
  3669.                                 Laser_create_new_easy( obj->orient.fvec, obj->pos, obj, weapon_id_type::FLARE_ID, 1);
  3670.                                 ailp.next_fire = F1_0/2;
  3671.                                 if (!BuddyState.Buddy_allowed_to_talk) // If buddy not talking, make him fire flares less often.
  3672.                                         ailp.next_fire += d_rand()*4;
  3673.                 }
  3674.         }
  3675.  
  3676.         if (robot_is_thief(robptr)) {
  3677.  
  3678.                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3679.                 do_thief_frame(obj, dist_to_player, player_visibility.visibility, vec_to_player);
  3680.  
  3681.                 if (ready_to_fire_any_weapon(robptr, ailp, 0)) {
  3682.                         if (openable_door_on_near_path(vmsegptr, vcwallptr, *obj, *aip))
  3683.                         {
  3684.                                 // @mk, 05/08/95: Firing flare from center of object, this is dumb...
  3685.                                 Laser_create_new_easy( obj->orient.fvec, obj->pos, obj, weapon_id_type::FLARE_ID, 1);
  3686.                                 ailp.next_fire = F1_0/2;
  3687.                                 if (LevelUniqueObjectState.ThiefState.Stolen_item_index == 0)     // If never stolen an item, fire flares less often (bad: Stolen_item_index wraps, but big deal)
  3688.                                         ailp.next_fire += d_rand()*4;
  3689.                         }
  3690.                 }
  3691.         }
  3692. #endif
  3693.  
  3694.         switch (ailp.mode)
  3695.         {
  3696.                 case ai_mode::AIM_CHASE_OBJECT: {        // chasing player, sort of, chase if far, back off if close, circle in between
  3697.                         fix circle_distance;
  3698.  
  3699.                         circle_distance = robptr.circle_distance[Difficulty_level] + ConsoleObject->size;
  3700.                         // Green guy doesn't get his circle distance boosted, else he might never attack.
  3701.                         if (robptr.attack_type != 1)
  3702.                                 circle_distance += (objnum&0xf) * F1_0/2;
  3703.  
  3704.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3705.  
  3706.                         // @mk, 12/27/94, structure here was strange.  Would do both clauses of what are now this if/then/else.  Used to be if/then, if/then.
  3707.                         if (player_visibility.visibility != player_visibility_state::visible_and_in_field_of_view && previous_visibility == player_visibility_state::visible_and_in_field_of_view)
  3708.                         { // this is redundant: mk, 01/15/95: && (ailp->mode == ai_mode::AIM_CHASE_OBJECT))
  3709.                                 if (!ai_multiplayer_awareness(obj, 53)) {
  3710.                                         if (maybe_ai_do_actual_firing_stuff(obj))
  3711.                                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  3712.                                         return;
  3713.                                 }
  3714.                                 create_path_to_believed_player_segment(obj, 8, create_path_safety_flag::safe);
  3715.                                 ai_multi_send_robot_position(obj, -1);
  3716.                         } else if (!player_is_visible(player_visibility.visibility) && dist_to_player > F1_0 * 80 && !(Game_mode & GM_MULTI))
  3717.                         {
  3718.                                 // If pretty far from the player, player cannot be seen
  3719.                                 // (obstructed) and in chase mode, switch to follow path mode.
  3720.                                 // This has one desirable benefit of avoiding physics retries.
  3721.                                 if (aip->behavior == ai_behavior::AIB_STATION) {
  3722.                                         ailp.goal_segment = aip->hide_segment;
  3723.                                         create_path_to_station(obj, 15);
  3724.                                 } // -- this looks like a dumb thing to do...robots following paths far away from you! else create_n_segment_path(obj, 5, -1);
  3725. #if defined(DXX_BUILD_DESCENT_I)
  3726.                                 else
  3727.                                         create_n_segment_path(obj, 5, segment_none);
  3728. #endif
  3729.                                 break;
  3730.                         }
  3731.  
  3732.                         if ((aip->CURRENT_STATE == AIS_REST) && (aip->GOAL_STATE == AIS_REST)) {
  3733.                                 const auto pv = player_visibility.visibility;
  3734.                                 if (player_is_visible(pv))
  3735.                                 {
  3736.                                         const auto upv = static_cast<unsigned>(pv);
  3737.                                         if (d_rand() < FrameTime * upv)
  3738.                                                 if (dist_to_player/256 < static_cast<fix>(d_rand() * upv))
  3739.                                                 {
  3740.                                                         aip->GOAL_STATE = AIS_SRCH;
  3741.                                                         aip->CURRENT_STATE = AIS_SRCH;
  3742.                                                 }
  3743.                                 }
  3744.                         }
  3745.  
  3746.                         if (GameTime64 - ailp.time_player_seen > CHASE_TIME_LENGTH)
  3747.                         {
  3748.  
  3749.                                 if (Game_mode & GM_MULTI)
  3750.                                 {
  3751.                                         if (!player_is_visible(player_visibility.visibility) && dist_to_player > F1_0 * 70)
  3752.                                         {
  3753.                                                 ailp.mode = ai_mode::AIM_STILL;
  3754.                                                 return;
  3755.                                         }
  3756.                                 }
  3757.  
  3758.                                 if (!ai_multiplayer_awareness(obj, 64)) {
  3759.                                         if (maybe_ai_do_actual_firing_stuff(obj))
  3760.                                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  3761.                                         return;
  3762.                                 }
  3763. #if defined(DXX_BUILD_DESCENT_I)
  3764.                                 create_path_to_believed_player_segment(obj, 10, create_path_safety_flag::safe);
  3765.                                 ai_multi_send_robot_position(obj, -1);
  3766. #endif
  3767.                         } else if ((aip->CURRENT_STATE != AIS_REST) && (aip->GOAL_STATE != AIS_REST)) {
  3768.                                 if (!ai_multiplayer_awareness(obj, 70)) {
  3769.                                         if (maybe_ai_do_actual_firing_stuff(obj))
  3770.                                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  3771.                                         return;
  3772.                                 }
  3773.                                 ai_move_relative_to_player(obj, ailp, dist_to_player, circle_distance, 0, player_visibility, player_info);
  3774.  
  3775.                                 if ((obj_ref & 1) && ((aip->GOAL_STATE == AIS_SRCH) || (aip->GOAL_STATE == AIS_LOCK))) {
  3776.                                         if (player_is_visible(player_visibility.visibility))
  3777.                                                 ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  3778. #if defined(DXX_BUILD_DESCENT_I)
  3779.                                         else
  3780.                                                 ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
  3781. #endif
  3782.                                 }
  3783.  
  3784.                                 ai_multi_send_robot_position(obj, ai_evaded ? (ai_evaded = 0, 1) : -1);
  3785.  
  3786.                                 do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
  3787.                         }
  3788.                         break;
  3789.                 }
  3790.  
  3791.                 case ai_mode::AIM_RUN_FROM_OBJECT:
  3792.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3793.  
  3794.                         {
  3795.                                 const auto pv = player_visibility.visibility;
  3796.                                 const auto player_is_in_line_of_sight = player_is_visible(pv);
  3797.                                 if (player_is_in_line_of_sight)
  3798.                                 {
  3799.                                 if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
  3800.                                         ailp.player_awareness_type = player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION;
  3801.                                 }
  3802.  
  3803.                         // If in multiplayer, only do if player visible.  If not multiplayer, do always.
  3804.                                 if (!(Game_mode & GM_MULTI) || player_is_in_line_of_sight)
  3805.                                 if (ai_multiplayer_awareness(obj, 75)) {
  3806.                                         ai_follow_path(obj, player_visibility.visibility, &vec_to_player);
  3807.                                         ai_multi_send_robot_position(obj, -1);
  3808.                                 }
  3809.  
  3810.                         if (aip->GOAL_STATE != AIS_FLIN)
  3811.                                 aip->GOAL_STATE = AIS_LOCK;
  3812.                         else if (aip->CURRENT_STATE == AIS_FLIN)
  3813.                                 aip->GOAL_STATE = AIS_LOCK;
  3814.  
  3815.                         // Bad to let run_from robot fire at player because it
  3816.                         // will cause a war in which it turns towards the player
  3817.                         // to fire and then towards its goal to move.
  3818.                         // do_firing_stuff(obj, player_visibility, &vec_to_player);
  3819.                         // Instead, do this:
  3820.                         // (Note, only drop if player is visible.  This prevents
  3821.                         // the bombs from being a giveaway, and also ensures that
  3822.                         // the robot is moving while it is dropping.  Also means
  3823.                         // fewer will be dropped.)
  3824.                         if (ready_to_fire_weapon1(ailp, 0) && player_is_in_line_of_sight)
  3825.                         {
  3826.                                 if (!ai_multiplayer_awareness(obj, 75))
  3827.                                         return;
  3828.  
  3829.                                 const auto fire_vec = vm_vec_negated(obj->orient.fvec);
  3830.                                 const auto fire_pos = vm_vec_add(obj->pos, fire_vec);
  3831.  
  3832. #if defined(DXX_BUILD_DESCENT_I)
  3833.                                 ailp.next_fire = F1_0*5;                //      Drop a proximity bomb every 5 seconds.
  3834.                                 const auto weapon_id =
  3835. #elif defined(DXX_BUILD_DESCENT_II)
  3836.                                 ailp.next_fire = (F1_0/2)*(NDL+5 - Difficulty_level);      // Drop a proximity bomb every 5 seconds.
  3837.                                 const auto weapon_id = (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
  3838.                                         ? weapon_id_type::ROBOT_SUPERPROX_ID
  3839.                                         :
  3840. #endif
  3841.                                         weapon_id_type::PROXIMITY_ID;
  3842.                                 Laser_create_new_easy(fire_vec, fire_pos, obj, weapon_id, 1);
  3843.  
  3844.                                 if (Game_mode & GM_MULTI)
  3845.                                 {
  3846.                                         ai_multi_send_robot_position(obj, -1);
  3847.                                         const int gun_num =
  3848. #if defined(DXX_BUILD_DESCENT_II)
  3849.                                                 (aip->SUB_FLAGS & SUB_FLAGS_SPROX)
  3850.                                                 ? -2
  3851.                                                 :
  3852. #endif
  3853.                                         -1;
  3854.                                         multi_send_robot_fire(obj, gun_num, fire_vec);
  3855.                                 }
  3856.                         }
  3857.                         }
  3858.                         break;
  3859.  
  3860. #if defined(DXX_BUILD_DESCENT_II)
  3861.                 case ai_mode::AIM_GOTO_PLAYER:
  3862.                 case ai_mode::AIM_GOTO_OBJECT:
  3863.                         ai_follow_path(obj, player_visibility_state::visible_and_in_field_of_view, &vec_to_player);    // Follows path as if player can see robot.
  3864.                         ai_multi_send_robot_position(obj, -1);
  3865.                         break;
  3866. #endif
  3867.  
  3868.                 case ai_mode::AIM_FOLLOW_PATH: {
  3869.                         int anger_level = 65;
  3870.  
  3871.                         if (aip->behavior == ai_behavior::AIB_STATION)
  3872.                         {
  3873.                                 const std::size_t idx = aip->hide_index + aip->path_length - 1;
  3874.                                 if (idx < Point_segs.size() && Point_segs[idx].segnum == aip->hide_segment)
  3875.                                 {
  3876.                                         anger_level = 64;
  3877.                                 }
  3878.                         }
  3879.  
  3880.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3881.  
  3882.                         if (!ai_multiplayer_awareness(obj, anger_level)) {
  3883.                                 if (maybe_ai_do_actual_firing_stuff(obj)) {
  3884.                                         ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  3885.                                 }
  3886.                                 return;
  3887.                         }
  3888.  
  3889.                         ai_follow_path(obj, player_visibility.visibility, &vec_to_player);
  3890.  
  3891.                         if (aip->GOAL_STATE != AIS_FLIN)
  3892.                                 aip->GOAL_STATE = AIS_LOCK;
  3893.                         else if (aip->CURRENT_STATE == AIS_FLIN)
  3894.                                 aip->GOAL_STATE = AIS_LOCK;
  3895.  
  3896. #if defined(DXX_BUILD_DESCENT_I)
  3897.                         if ((aip->behavior != ai_behavior::AIB_FOLLOW_PATH) && (aip->behavior != ai_behavior::AIB_RUN_FROM))
  3898.                                 do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
  3899.  
  3900.                         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view && aip->behavior != ai_behavior::AIB_FOLLOW_PATH && aip->behavior != ai_behavior::AIB_RUN_FROM && get_robot_id(obj) != ROBOT_BRAIN)
  3901.                         {
  3902.                                 if (robptr.attack_type == 0)
  3903.                                         ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  3904.                         }
  3905.                         else if (!player_is_visible(player_visibility.visibility)
  3906.                                 && (aip->behavior == ai_behavior::AIB_NORMAL)
  3907.                                 && (ailp.mode == ai_mode::AIM_FOLLOW_PATH)
  3908.                                 && (aip->behavior != ai_behavior::AIB_RUN_FROM))
  3909.                         {
  3910.                                 ailp.mode = ai_mode::AIM_STILL;
  3911.                                 aip->hide_index = -1;
  3912.                                 aip->path_length = 0;
  3913.                         }
  3914. #elif defined(DXX_BUILD_DESCENT_II)
  3915.                         if (aip->behavior != ai_behavior::AIB_RUN_FROM)
  3916.                                 do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
  3917.  
  3918.                         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view &&
  3919.                                 aip->behavior != ai_behavior::AIB_SNIPE &&
  3920.                                 aip->behavior != ai_behavior::AIB_FOLLOW &&
  3921.                                 aip->behavior != ai_behavior::AIB_RUN_FROM &&
  3922.                                 get_robot_id(obj) != ROBOT_BRAIN &&
  3923.                                 robot_is_companion(robptr) != 1 &&
  3924.                                 robot_is_thief(robptr) != 1)
  3925.                         {
  3926.                                 if (robptr.attack_type == 0)
  3927.                                         ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  3928.                                 // This should not just be distance based, but also time-since-player-seen based.
  3929.                         }
  3930.                         else if ((dist_to_player > F1_0 * (20 * (2 * static_cast<int>(Difficulty_level) + robptr.pursuit)))
  3931.                                 && (GameTime64 - ailp.time_player_seen > (F1_0/2*(Difficulty_level + robptr.pursuit)))
  3932.                                 && !player_is_visible(player_visibility.visibility)
  3933.                                 && (aip->behavior == ai_behavior::AIB_NORMAL)
  3934.                                 && (ailp.mode == ai_mode::AIM_FOLLOW_PATH))
  3935.                         {
  3936.                                 ailp.mode = ai_mode::AIM_STILL;
  3937.                                 aip->hide_index = -1;
  3938.                                 aip->path_length = 0;
  3939.                         }
  3940. #endif
  3941.  
  3942.                         ai_multi_send_robot_position(obj, -1);
  3943.  
  3944.                         break;
  3945.                 }
  3946.  
  3947. #if defined(DXX_BUILD_DESCENT_I)
  3948.                 case ai_mode::AIM_HIDE:
  3949. #elif defined(DXX_BUILD_DESCENT_II)
  3950.                 case ai_mode::AIM_BEHIND:
  3951. #endif
  3952.                         if (!ai_multiplayer_awareness(obj, 71)) {
  3953.                                 if (maybe_ai_do_actual_firing_stuff(obj)) {
  3954.                                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3955.                                         ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  3956.                                 }
  3957.                                 return;
  3958.                         }
  3959.  
  3960.                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  3961.  
  3962. #if defined(DXX_BUILD_DESCENT_I)
  3963.                         ai_follow_path(obj, player_visibility.visibility, nullptr);
  3964. #elif defined(DXX_BUILD_DESCENT_II)
  3965.                         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  3966.                         {
  3967.                                 // Get behind the player.
  3968.                                 // Method:
  3969.                                 // If vec_to_player dot player_rear_vector > 0, behind is goal.
  3970.                                 // Else choose goal with larger dot from left, right.
  3971.                                 vms_vector  goal_vector;
  3972.                                 fix         dot;
  3973.  
  3974.                                 dot = vm_vec_dot(ConsoleObject->orient.fvec, vec_to_player);
  3975.                                 if (dot > 0) {          // Remember, we're interested in the rear vector dot being < 0.
  3976.                                         goal_vector = vm_vec_negated(ConsoleObject->orient.fvec);
  3977.                                 } else {
  3978.                                         auto &orient = ConsoleObject->orient;
  3979.                                         constexpr unsigned choice_count = 15;
  3980.                                         const unsigned selector = (ConsoleObject->id ^ obj.get_unchecked_index() ^ ConsoleObject->segnum ^ obj->segnum) % (choice_count + 1);
  3981.                                         if (selector == 0)
  3982.                                                 goal_vector = orient.rvec;
  3983.                                         else if (selector == choice_count)
  3984.                                                 goal_vector = orient.uvec;
  3985.                                         else
  3986.                                         {
  3987.                                                 vm_vec_scale_add2(goal_vector, orient.rvec, (choice_count - selector) * F1_0);
  3988.                                                 vm_vec_copy_scale(goal_vector, orient.uvec, selector * F1_0);
  3989.                                                 vm_vec_normalize_quick(goal_vector);
  3990.                                         }
  3991.                                         if (vm_vec_dot(goal_vector, vec_to_player) > 0)
  3992.                                                 vm_vec_negate(goal_vector);
  3993.                                 }
  3994.  
  3995.                                 vm_vec_scale(goal_vector, 2*(ConsoleObject->size + obj->size + (((objnum*4 + d_tick_count) & 63) << 12)));
  3996.                                 auto goal_point = vm_vec_add(ConsoleObject->pos, goal_vector);
  3997.                                 vm_vec_scale_add2(goal_point, make_random_vector(), F1_0*8);
  3998.                                 const auto vec_to_goal = vm_vec_normalized_quick(vm_vec_sub(goal_point, obj->pos));
  3999.                                 move_towards_vector(obj, vec_to_goal, 0);
  4000.                                 ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4001.                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  4002.                         }
  4003. #endif
  4004.  
  4005.                         if (aip->GOAL_STATE != AIS_FLIN)
  4006.                                 aip->GOAL_STATE = AIS_LOCK;
  4007.                         else if (aip->CURRENT_STATE == AIS_FLIN)
  4008.                                 aip->GOAL_STATE = AIS_LOCK;
  4009.  
  4010.                         ai_multi_send_robot_position(obj, -1);
  4011.                         break;
  4012.  
  4013.                 case ai_mode::AIM_STILL:
  4014.                         if ((dist_to_player < F1_0 * 120 + static_cast<int>(Difficulty_level) * F1_0 * 20) || (static_cast<unsigned>(ailp.player_awareness_type) >= static_cast<unsigned>(player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION) - 1))
  4015.                         {
  4016.                                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4017.  
  4018.                                 // turn towards vector if visible this time or last time, or rand
  4019.                                 // new!
  4020. #if defined(DXX_BUILD_DESCENT_I)
  4021.                                 if (player_is_visible(player_visibility.visibility) || player_is_visible(previous_visibility) || ((d_rand() > 0x4000) && !(Game_mode & GM_MULTI)))
  4022. #elif defined(DXX_BUILD_DESCENT_II)
  4023.                                 if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view || previous_visibility == player_visibility_state::visible_and_in_field_of_view) // -- MK, 06/09/95:  || ((d_rand() > 0x4000) && !(Game_mode & GM_MULTI)))
  4024. #endif
  4025.                                 {
  4026.                                         if (!ai_multiplayer_awareness(obj, 71)) {
  4027.                                                 if (maybe_ai_do_actual_firing_stuff(obj))
  4028.                                                         ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  4029.                                                 return;
  4030.                                         }
  4031.                                         ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4032.                                         ai_multi_send_robot_position(obj, -1);
  4033.                                 }
  4034.  
  4035.                                 do_firing_stuff(obj, player_info.powerup_flags, player_visibility);
  4036. #if defined(DXX_BUILD_DESCENT_I)
  4037.                                 if (player_is_visible(player_visibility.visibility)) // Change, MK, 01/03/94 for Multiplayer reasons.  If robots can't see you (even with eyes on back of head), then don't do evasion.
  4038. #elif defined(DXX_BUILD_DESCENT_II)
  4039.                                 if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view) // Changed @mk, 09/21/95: Require that they be looking to evade.  Change, MK, 01/03/95 for Multiplayer reasons.  If robots can't see you (even with eyes on back of head), then don't do evasion.
  4040. #endif
  4041.                                 {
  4042.                                         if (robptr.attack_type == 1) {
  4043.                                                 aip->behavior = ai_behavior::AIB_NORMAL;
  4044.                                                 if (!ai_multiplayer_awareness(obj, 80)) {
  4045.                                                         if (maybe_ai_do_actual_firing_stuff(obj))
  4046.                                                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  4047.                                                         return;
  4048.                                                 }
  4049.                                                 ai_move_relative_to_player(obj, ailp, dist_to_player, 0, 0, player_visibility, player_info);
  4050.                                                 ai_multi_send_robot_position(obj, ai_evaded ? (ai_evaded = 0, 1) : -1);
  4051.                                         } else {
  4052.                                                 // Robots in hover mode are allowed to evade at half normal speed.
  4053.                                                 if (!ai_multiplayer_awareness(obj, 81)) {
  4054.                                                         if (maybe_ai_do_actual_firing_stuff(obj))
  4055.                                                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  4056.                                                         return;
  4057.                                                 }
  4058.                                                 ai_move_relative_to_player(obj, ailp, dist_to_player, 0, 1, player_visibility, player_info);
  4059.                                                 if (ai_evaded) {
  4060.                                                         ai_multi_send_robot_position(obj, -1);
  4061.                                                         ai_evaded = 0;
  4062.                                                 }
  4063.                                                 else
  4064.                                                         ai_multi_send_robot_position(obj, -1);
  4065.                                         }
  4066.                                 } else if ((obj->segnum != aip->hide_segment) && (dist_to_player > F1_0*80) && (!(Game_mode & GM_MULTI))) {
  4067.                                         // If pretty far from the player, player cannot be
  4068.                                         // seen (obstructed) and in chase mode, switch to
  4069.                                         // follow path mode.
  4070.                                         // This has one desirable benefit of avoiding physics retries.
  4071.                                         if (aip->behavior == ai_behavior::AIB_STATION) {
  4072.                                                 ailp.goal_segment = aip->hide_segment;
  4073.                                                 create_path_to_station(obj, 15);
  4074.                                         }
  4075.                                         break;
  4076.                                 }
  4077.                         }
  4078.  
  4079.                         break;
  4080.                 case ai_mode::AIM_OPEN_DOOR: {       // trying to open a door.
  4081.                         Assert(get_robot_id(obj) == ROBOT_BRAIN);     // Make sure this guy is allowed to be in this mode.
  4082.  
  4083.                         if (!ai_multiplayer_awareness(obj, 62))
  4084.                                 return;
  4085.                         auto &vcvertptr = Vertices.vcptr;
  4086.                         const auto &&center_point = compute_center_point_on_side(vcvertptr, vcsegptr(obj->segnum), aip->GOALSIDE);
  4087.                         const auto goal_vector = vm_vec_normalized_quick(vm_vec_sub(center_point, obj->pos));
  4088.                         ai_turn_towards_vector(goal_vector, obj, robptr.turn_time[Difficulty_level]);
  4089.                         move_towards_vector(obj, goal_vector, 0);
  4090.                         ai_multi_send_robot_position(obj, -1);
  4091.  
  4092.                         break;
  4093.                 }
  4094.  
  4095. #if defined(DXX_BUILD_DESCENT_II)
  4096.                 case ai_mode::AIM_SNIPE_WAIT:
  4097.                         break;
  4098.                 case ai_mode::AIM_SNIPE_RETREAT:
  4099.                         // -- if (ai_multiplayer_awareness(obj, 53))
  4100.                         // --   if (ailp->next_fire < -F1_0)
  4101.                         // --           ai_do_actual_firing_stuff(obj, aip, ailp, robptr, &vec_to_player, dist_to_player, &gun_point, player_visibility, object_animates, aip->CURRENT_GUN);
  4102.                         break;
  4103.                 case ai_mode::AIM_SNIPE_RETREAT_BACKWARDS:
  4104.                 case ai_mode::AIM_SNIPE_ATTACK:
  4105.                 case ai_mode::AIM_SNIPE_FIRE:
  4106.                         if (ai_multiplayer_awareness(obj, 53)) {
  4107.                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  4108.                                 if (robot_is_thief(robptr))
  4109.                                         ai_move_relative_to_player(obj, ailp, dist_to_player, 0, 0, player_visibility, player_info);
  4110.                                 break;
  4111.                         }
  4112.                         break;
  4113.  
  4114.                 case ai_mode::AIM_THIEF_WAIT:
  4115.                 case ai_mode::AIM_THIEF_ATTACK:
  4116.                 case ai_mode::AIM_THIEF_RETREAT:
  4117.                 case ai_mode::AIM_WANDER:    // Used for Buddy Bot
  4118.                         break;
  4119. #endif
  4120.  
  4121.                 default:
  4122.                         ailp.mode = ai_mode::AIM_CHASE_OBJECT;
  4123.                         break;
  4124.         }       // end: switch (ailp->mode)
  4125.  
  4126.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  4127.         // If the robot can see you, increase his awareness of you.
  4128.         // This prevents the problem of a robot looking right at you but doing nothing.
  4129.         // Assert(player_visibility != -1); // Means it didn't get initialized!
  4130.         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4131.         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  4132.         {
  4133. #if defined(DXX_BUILD_DESCENT_I)
  4134.                 if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
  4135.                         ailp.player_awareness_type = player_awareness_type_t::PA_PLAYER_COLLISION;
  4136. #elif defined(DXX_BUILD_DESCENT_II)
  4137.         if (aip->behavior != ai_behavior::AIB_FOLLOW && !robot_is_thief(robptr))
  4138.         {
  4139.                 if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE && (aip->SUB_FLAGS & SUB_FLAGS_CAMERA_AWAKE))
  4140.                         aip->SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
  4141.                 else if (ailp.player_awareness_type == player_awareness_type_t::PA_NONE)
  4142.                         ailp.player_awareness_type = player_awareness_type_t::PA_PLAYER_COLLISION;
  4143.         }
  4144. #endif
  4145.         }
  4146.  
  4147.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  4148.         if (!object_animates) {
  4149.                 aip->CURRENT_STATE = aip->GOAL_STATE;
  4150.         }
  4151.  
  4152.         assert(static_cast<unsigned>(ailp.player_awareness_type) <= AIE_MAX);
  4153.         Assert(aip->CURRENT_STATE < AIS_MAX);
  4154.         Assert(aip->GOAL_STATE < AIS_MAX);
  4155.  
  4156.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  4157.         if (ailp.player_awareness_type != player_awareness_type_t::PA_NONE) {
  4158.                 new_goal_state = Ai_transition_table[static_cast<unsigned>(ailp.player_awareness_type) - 1][aip->CURRENT_STATE][aip->GOAL_STATE];
  4159.                 if (ailp.player_awareness_type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
  4160.                 {
  4161.                         // Decrease awareness, else this robot will flinch every frame.
  4162.                         ailp.player_awareness_type = static_cast<player_awareness_type_t>(static_cast<unsigned>(ailp.player_awareness_type) - 1);
  4163.                         ailp.player_awareness_time = F1_0*3;
  4164.                 }
  4165.  
  4166.                 if (new_goal_state == AIS_ERR_)
  4167.                         new_goal_state = AIS_REST;
  4168.  
  4169.                 if (aip->CURRENT_STATE == AIS_NONE)
  4170.                         aip->CURRENT_STATE = AIS_REST;
  4171.  
  4172.                 aip->GOAL_STATE = new_goal_state;
  4173.  
  4174.         }
  4175.  
  4176.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  4177.         // If new state = fire, then set all gun states to fire.
  4178.         if (aip->GOAL_STATE == AIS_FIRE)
  4179.         {
  4180.                 uint_fast32_t num_guns = robptr.n_guns;
  4181.                 for (uint_fast32_t i=0; i<num_guns; i++)
  4182.                         ailp.goal_state[i] = AIS_FIRE;
  4183.         }
  4184.  
  4185.         // - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  - -  -
  4186.         // Hack by mk on 01/04/94, if a guy hasn't animated to the firing state, but his next_fire says ok to fire, bash him there
  4187.         if (ready_to_fire_any_weapon(robptr, ailp, 0) && (aip->GOAL_STATE == AIS_FIRE))
  4188.                 aip->CURRENT_STATE = AIS_FIRE;
  4189.  
  4190.         if ((aip->GOAL_STATE != AIS_FLIN)  && (get_robot_id(obj) != ROBOT_BRAIN)) {
  4191.                 switch (aip->CURRENT_STATE) {
  4192.                         case AIS_NONE:
  4193.                                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4194.  
  4195.                                 {
  4196.                                 const fix dot = vm_vec_dot(obj->orient.fvec, vec_to_player);
  4197.                                 if (dot >= F1_0/2)
  4198.                                         if (aip->GOAL_STATE == AIS_REST)
  4199.                                                 aip->GOAL_STATE = AIS_SRCH;
  4200.                                 }
  4201.                                 break;
  4202.                         case AIS_REST:
  4203.                                 if (aip->GOAL_STATE == AIS_REST) {
  4204.                                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4205.                                         if (ready_to_fire_any_weapon(robptr, ailp, 0) && player_is_visible(player_visibility.visibility))
  4206.                                         {
  4207.                                                 aip->GOAL_STATE = AIS_FIRE;
  4208.                                         }
  4209.                                 }
  4210.                                 break;
  4211.                         case AIS_SRCH:
  4212.                                 if (!ai_multiplayer_awareness(obj, 60))
  4213.                                         return;
  4214.  
  4215.                                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4216.  
  4217. #if defined(DXX_BUILD_DESCENT_I)
  4218.                                 if (player_is_visible(player_visibility.visibility))
  4219.                                 {
  4220.                                         ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4221.                                         ai_multi_send_robot_position(obj, -1);
  4222.                                 } else if (!(Game_mode & GM_MULTI))
  4223.                                         ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
  4224. #elif defined(DXX_BUILD_DESCENT_II)
  4225.                                 if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  4226.                                 {
  4227.                                         ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4228.                                         ai_multi_send_robot_position(obj, -1);
  4229.                                 }
  4230. #endif
  4231.                                 break;
  4232.                         case AIS_LOCK:
  4233.                                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4234.  
  4235.                                 if (!(Game_mode & GM_MULTI) || player_is_visible(player_visibility.visibility))
  4236.                                 {
  4237.                                         if (!ai_multiplayer_awareness(obj, 68))
  4238.                                                 return;
  4239.  
  4240. #if defined(DXX_BUILD_DESCENT_I)
  4241.                                         if (player_is_visible(player_visibility.visibility))
  4242.                                         {
  4243.                                                 ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4244.                                                 ai_multi_send_robot_position(obj, -1);
  4245.                                         }
  4246.                                         else if (!(Game_mode & GM_MULTI))
  4247.                                                 ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
  4248. #elif defined(DXX_BUILD_DESCENT_II)
  4249.                                         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  4250.                                         {   // @mk, 09/21/95, require that they be looking towards you to turn towards you.
  4251.                                                 ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4252.                                                 ai_multi_send_robot_position(obj, -1);
  4253.                                         }
  4254. #endif
  4255.                                 }
  4256.                                 break;
  4257.                         case AIS_FIRE:
  4258.                                 compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4259.  
  4260. #if defined(DXX_BUILD_DESCENT_I)
  4261.                                 if (player_is_visible(player_visibility.visibility))
  4262.                                 {
  4263.                                         if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1)))
  4264.                                         {
  4265.                                                 if (Game_mode & GM_MULTI) {
  4266.                                                         ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, 0);
  4267.                                                         return;
  4268.                                                 }
  4269.                                         }
  4270.                                         ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4271.                                         ai_multi_send_robot_position(obj, -1);
  4272.                                 } else if (!(Game_mode & GM_MULTI)) {
  4273.                                         ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
  4274.                                 }
  4275. #elif defined(DXX_BUILD_DESCENT_II)
  4276.                                 if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  4277.                                 {
  4278.                                         if (!ai_multiplayer_awareness(obj, (ROBOT_FIRE_AGITATION-1))) {
  4279.                                                 if (Game_mode & GM_MULTI) {
  4280.                                                         ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  4281.                                                         return;
  4282.                                                 }
  4283.                                         }
  4284.                                         ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4285.                                         ai_multi_send_robot_position(obj, -1);
  4286.                                 }
  4287. #endif
  4288.  
  4289.                                 // Fire at player, if appropriate.
  4290.                                 ai_do_actual_firing_stuff(vmobjptridx, obj, aip, ailp, robptr, dist_to_player, gun_point, player_visibility, object_animates, player_info, aip->CURRENT_GUN);
  4291.  
  4292.                                 break;
  4293.                         case AIS_RECO:
  4294.                                 if (!(obj_ref & 3)) {
  4295.                                         compute_vis_and_vec(vmsegptridx, obj, player_info, vis_vec_pos, ailp, player_visibility, robptr);
  4296. #if defined(DXX_BUILD_DESCENT_I)
  4297.                                         if (player_is_visible(player_visibility.visibility))
  4298.                                         {
  4299.                                                 if (!ai_multiplayer_awareness(obj, 69))
  4300.                                                         return;
  4301.                                                 ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4302.                                                 ai_multi_send_robot_position(obj, -1);
  4303.                                         }
  4304.                                         else if (!(Game_mode & GM_MULTI)) {
  4305.                                                 ai_turn_randomly(vec_to_player, obj, robptr.turn_time[Difficulty_level], previous_visibility);
  4306.                                         }
  4307. #elif defined(DXX_BUILD_DESCENT_II)
  4308.                                         if (player_visibility.visibility == player_visibility_state::visible_and_in_field_of_view)
  4309.                                         {
  4310.                                                 if (!ai_multiplayer_awareness(obj, 69))
  4311.                                                         return;
  4312.                                                 ai_turn_towards_vector(vec_to_player, obj, robptr.turn_time[Difficulty_level]);
  4313.                                                 ai_multi_send_robot_position(obj, -1);
  4314.                                         }
  4315. #endif
  4316.                                 }
  4317.                                 break;
  4318.                         case AIS_FLIN:
  4319.                                 break;
  4320.                         default:
  4321.                                 aip->GOAL_STATE = AIS_REST;
  4322.                                 aip->CURRENT_STATE = AIS_REST;
  4323.                                 break;
  4324.                 }
  4325.         } // end of: if (aip->GOAL_STATE != AIS_FLIN)
  4326.  
  4327.         // Switch to next gun for next fire.
  4328.         if (!player_is_visible(player_visibility.visibility))
  4329.         {
  4330.                 aip->CURRENT_GUN++;
  4331.                 if (aip->CURRENT_GUN >= robptr.n_guns)
  4332.                 {
  4333. #if defined(DXX_BUILD_DESCENT_II)
  4334.                         if (!((robptr.n_guns == 1) || (robptr.weapon_type2 == weapon_none)))  // Two weapon types hack.
  4335.                                 aip->CURRENT_GUN = 1;
  4336.                         else
  4337. #endif
  4338.                                 aip->CURRENT_GUN = 0;
  4339.                 }
  4340.         }
  4341.  
  4342. }
  4343.  
  4344. // ----------------------------------------------------------------------------
  4345. void ai_do_cloak_stuff(void)
  4346. {
  4347.         range_for (auto &i, Ai_cloak_info) {
  4348.                 i.last_time = GameTime64;
  4349. #if defined(DXX_BUILD_DESCENT_II)
  4350.                 i.last_segment = ConsoleObject->segnum;
  4351. #endif
  4352.                 i.last_position = ConsoleObject->pos;
  4353.         }
  4354.  
  4355.         // Make work for control centers.
  4356.         Believed_player_pos = Ai_cloak_info[0].last_position;
  4357. #if defined(DXX_BUILD_DESCENT_II)
  4358.         Believed_player_seg = Ai_cloak_info[0].last_segment;
  4359. #endif
  4360. }
  4361.  
  4362. // --------------------------------------------------------------------------------------------------------------------
  4363. //      Call this each time the player starts a new ship.
  4364. void init_ai_for_ship()
  4365. {
  4366.         ai_do_cloak_stuff();
  4367. }
  4368.  
  4369. // ----------------------------------------------------------------------------
  4370. // Returns false if awareness is considered too puny to add, else returns true.
  4371. static int add_awareness_event(const object_base &objp, player_awareness_type_t type, d_level_unique_robot_awareness_state &awareness)
  4372. {
  4373.         // If player cloaked and hit a robot, then increase awareness
  4374.         if (type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION ||
  4375.                 type == player_awareness_type_t::PA_WEAPON_WALL_COLLISION ||
  4376.                 type == player_awareness_type_t::PA_PLAYER_COLLISION)
  4377.                 ai_do_cloak_stuff();
  4378.  
  4379.         if (awareness.Num_awareness_events < awareness.Awareness_events.size())
  4380.         {
  4381.                 if (type == player_awareness_type_t::PA_WEAPON_WALL_COLLISION ||
  4382.                         type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
  4383.                         if (objp.type == OBJ_WEAPON && get_weapon_id(objp) == weapon_id_type::VULCAN_ID)
  4384.                                 if (d_rand() > 3276)
  4385.                                         return 0;       // For vulcan cannon, only about 1/10 actually cause awareness
  4386.  
  4387.                 auto &e = awareness.Awareness_events[awareness.Num_awareness_events++];
  4388.                 e.segnum = objp.segnum;
  4389.                 e.pos = objp.pos;
  4390.                 e.type = type;
  4391.         } else {
  4392.                 //Int3();   // Hey -- Overflowed Awareness_events, make more or something
  4393.                 // This just gets ignored, so you can just
  4394.                 // continue.
  4395.         }
  4396.         return 1;
  4397.  
  4398. }
  4399.  
  4400. // ----------------------------------------------------------------------------------
  4401. // Robots will become aware of the player based on something that occurred.
  4402. // The object (probably player or weapon) which created the awareness is objp.
  4403. void create_awareness_event(object &objp, player_awareness_type_t type, d_level_unique_robot_awareness_state &LevelUniqueRobotAwarenessState)
  4404. {
  4405.         // If not in multiplayer, or in multiplayer with robots, do this, else unnecessary!
  4406.         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
  4407.         {
  4408.                 if (add_awareness_event(objp, type, LevelUniqueRobotAwarenessState))
  4409.                 {
  4410.                         if (((d_rand() * (static_cast<unsigned>(type) + 4)) >> 15) > 4)
  4411.                                 Overall_agitation++;
  4412.                         if (Overall_agitation > OVERALL_AGITATION_MAX)
  4413.                                 Overall_agitation = OVERALL_AGITATION_MAX;
  4414.                 }
  4415.         }
  4416. }
  4417.  
  4418. // ----------------------------------------------------------------------------------
  4419. static void pae_aux(const vcsegptridx_t segnum, const player_awareness_type_t type, const int level, awareness_t &New_awareness)
  4420. {
  4421.         if (New_awareness[segnum] < type)
  4422.                 New_awareness[segnum] = type;
  4423.  
  4424.         // Process children.
  4425. #if defined(DXX_BUILD_DESCENT_I)
  4426.         if (level <= 4)
  4427. #elif defined(DXX_BUILD_DESCENT_II)
  4428.         if (level <= 3)
  4429. #endif
  4430.         {
  4431.                 const auto subtype = (type == player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION)
  4432.                         ? player_awareness_type_t::PA_PLAYER_COLLISION
  4433.                         : type;
  4434.                 const auto sublevel = level + 1;
  4435.                 range_for (auto &j, segnum->children)
  4436.                         if (IS_CHILD(j))
  4437.                                 pae_aux(segnum.absolute_sibling(j), subtype, sublevel, New_awareness);
  4438.         }
  4439. }
  4440.  
  4441.  
  4442. // ----------------------------------------------------------------------------------
  4443. static void process_awareness_events(fvcsegptridx &vcsegptridx, d_level_unique_robot_awareness_state &LevelUniqueRobotAwarenessState, awareness_t &New_awareness)
  4444. {
  4445.         const auto Num_awareness_events = std::exchange(LevelUniqueRobotAwarenessState.Num_awareness_events, 0);
  4446.         if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
  4447.         {
  4448.                 New_awareness.fill(player_awareness_type_t::PA_NONE);
  4449.                 range_for (auto &i, partial_const_range(LevelUniqueRobotAwarenessState.Awareness_events, Num_awareness_events))
  4450.                         pae_aux(vcsegptridx(i.segnum), i.type, 1, New_awareness);
  4451.         }
  4452. }
  4453.  
  4454. // ----------------------------------------------------------------------------------
  4455. static void set_player_awareness_all(fvmobjptr &vmobjptr, fvcsegptridx &vcsegptridx, d_level_unique_robot_awareness_state &LevelUniqueRobotAwarenessState)
  4456. {
  4457.         awareness_t New_awareness;
  4458.  
  4459.         process_awareness_events(vcsegptridx, LevelUniqueRobotAwarenessState, New_awareness);
  4460.  
  4461.         range_for (const auto &&objp, vmobjptr)
  4462.         {
  4463.                 if (objp->type == OBJ_ROBOT && objp->control_type == CT_AI)
  4464.                 {
  4465.                         auto &ailp = objp->ctype.ai_info.ail;
  4466.                         if (New_awareness[objp->segnum] > ailp.player_awareness_type) {
  4467.                                 ailp.player_awareness_type = New_awareness[objp->segnum];
  4468.                                 ailp.player_awareness_time = PLAYER_AWARENESS_INITIAL_TIME;
  4469.  
  4470. #if defined(DXX_BUILD_DESCENT_II)
  4471.                         // Clear the bit that says this robot is only awake because a camera woke it up.
  4472.                                 objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
  4473. #endif
  4474.                         }
  4475.                 }
  4476.         }
  4477. }
  4478.  
  4479. }
  4480.  
  4481. #ifndef NDEBUG
  4482. #if PARALLAX
  4483. int Ai_dump_enable = 0;
  4484.  
  4485. FILE *Ai_dump_file = NULL;
  4486.  
  4487. char Ai_error_message[128] = "";
  4488.  
  4489. // ----------------------------------------------------------------------------------
  4490. namespace dsx {
  4491. static void dump_ai_objects_all()
  4492. {
  4493. #if defined(DXX_BUILD_DESCENT_I)
  4494.         int     total=0;
  4495.         time_t  time_of_day;
  4496.  
  4497.         time_of_day = time(NULL);
  4498.  
  4499.         if (!Ai_dump_enable)
  4500.                 return;
  4501.  
  4502.         if (Ai_dump_file == NULL)
  4503.                 Ai_dump_file = fopen("ai.out","a+t");
  4504.  
  4505.         fprintf(Ai_dump_file, "\nnum: seg distance __mode__ behav.    [velx vely velz] (Tick = %i)\n", d_tick_count);
  4506.         fprintf(Ai_dump_file, "Date & Time = %s\n", ctime(&time_of_day));
  4507.  
  4508.         if (Ai_error_message[0])
  4509.                 fprintf(Ai_dump_file, "Error message: %s\n", Ai_error_message);
  4510.  
  4511.         range_for (const auto &&objp, vcobjptridx)
  4512.         {
  4513.                 ai_static       *aip = &objp->ctype.ai_info;
  4514.                 ai_local                *ailp = &objp->ctype.ai_info.ail;
  4515.                 fix                     dist_to_player;
  4516.  
  4517.                 dist_to_player = vm_vec_dist(objp->pos, ConsoleObject->pos);
  4518.  
  4519.                 if (objp->control_type == CT_AI) {
  4520.                         fprintf(Ai_dump_file, "%3i: %3i %8.3f %8s %8s [%3i %4i]\n",
  4521.                                 static_cast<uint16_t>(objp), objp->segnum, f2fl(dist_to_player), mode_text[ailp->mode], behavior_text[aip->behavior-0x80], aip->hide_index, aip->path_length);
  4522.                         if (aip->path_length)
  4523.                                 total += aip->path_length;
  4524.                 }
  4525.         }
  4526.  
  4527.         fprintf(Ai_dump_file, "Total path length = %4i\n", total);
  4528. #endif
  4529. }
  4530. }
  4531.  
  4532. void force_dump_ai_objects_all(const char *msg)
  4533. {
  4534.         int tsave;
  4535.  
  4536.         tsave = Ai_dump_enable;
  4537.  
  4538.         Ai_dump_enable = 1;
  4539.  
  4540.         sprintf(Ai_error_message, "%s\n", msg);
  4541.         dump_ai_objects_all();
  4542.         Ai_error_message[0] = 0;
  4543.  
  4544.         Ai_dump_enable = tsave;
  4545. }
  4546.  
  4547. // ----------------------------------------------------------------------------------
  4548. #else
  4549. static inline void dump_ai_objects_all()
  4550. {
  4551. }
  4552. #endif
  4553. #endif
  4554.  
  4555. namespace dsx {
  4556.  
  4557. // ----------------------------------------------------------------------------------
  4558. // Do things which need to get done for all AI objects each frame.
  4559. // This includes:
  4560. //  Setting player_awareness (a fix, time in seconds which object is aware of player)
  4561. void do_ai_frame_all(void)
  4562. {
  4563.         auto &Objects = LevelUniqueObjectState.Objects;
  4564.         auto &vmobjptr = Objects.vmptr;
  4565. #ifndef NDEBUG
  4566.         dump_ai_objects_all();
  4567. #endif
  4568.  
  4569.         set_player_awareness_all(vmobjptr, vcsegptridx, LevelUniqueRobotAwarenessState);
  4570.  
  4571. #if defined(DXX_BUILD_DESCENT_II)
  4572.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  4573.         auto &vmobjptridx = Objects.vmptridx;
  4574.         if (Ai_last_missile_camera)
  4575.         {
  4576.                 // Clear if supposed misisle camera is not a weapon, or just every so often, just in case.
  4577.                 if (((d_tick_count & 0x0f) == 0) || (Ai_last_missile_camera->type != OBJ_WEAPON)) {
  4578.                         Ai_last_missile_camera = nullptr;
  4579.                         range_for (const auto &&objp, vmobjptr)
  4580.                         {
  4581.                                 if (objp->type == OBJ_ROBOT)
  4582.                                         objp->ctype.ai_info.SUB_FLAGS &= ~SUB_FLAGS_CAMERA_AWAKE;
  4583.                         }
  4584.                 }
  4585.         }
  4586.  
  4587.         // (Moved here from do_d2_boss_stuff() because that only gets called if robot aware of player.)
  4588.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  4589.         if (BossUniqueState.Boss_dying) {
  4590.                 range_for (const auto &&objp, vmobjptridx)
  4591.                 {
  4592.                         if (objp->type == OBJ_ROBOT)
  4593.                                 if (Robot_info[get_robot_id(objp)].boss_flag)
  4594.                                         do_boss_dying_frame(objp);
  4595.                 }
  4596.         }
  4597. #endif
  4598. }
  4599.  
  4600.  
  4601. // Initializations to be performed for all robots for a new level.
  4602. void init_robots_for_level(void)
  4603. {
  4604.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  4605.         BossUniqueState.Boss_dying_start_time = 0;
  4606.         Overall_agitation = 0;
  4607. #if defined(DXX_BUILD_DESCENT_II)
  4608.         GameUniqueState.Final_boss_countdown_time = 0;
  4609.         Ai_last_missile_camera = nullptr;
  4610. #endif
  4611. }
  4612.  
  4613. // Following functions convert ai_local/ai_cloak_info to ai_local/ai_cloak_info_rw to be written to/read from Savegames. Convertin back is not done here - reading is done specifically together with swapping (if necessary). These structs differ in terms of timer values (fix/fix64). as we reset GameTime64 for writing so it can fit into fix it's not necessary to increment savegame version. But if we once store something else into object which might be useful after restoring, it might be handy to increment Savegame version and actually store these new infos.
  4614. static void state_ai_local_to_ai_local_rw(const ai_local *ail, ai_local_rw *ail_rw)
  4615. {
  4616.         int i = 0;
  4617.  
  4618.         ail_rw->player_awareness_type      = static_cast<int8_t>(ail->player_awareness_type);
  4619.         ail_rw->retry_count                = ail->retry_count;
  4620.         ail_rw->consecutive_retries        = ail->consecutive_retries;
  4621.         ail_rw->mode                       = static_cast<uint8_t>(ail->mode);
  4622.         ail_rw->previous_visibility        = static_cast<int8_t>(ail->previous_visibility);
  4623.         ail_rw->rapidfire_count            = ail->rapidfire_count;
  4624.         ail_rw->goal_segment               = ail->goal_segment;
  4625. #if defined(DXX_BUILD_DESCENT_I)
  4626.         ail_rw->last_see_time              = 0;
  4627.         ail_rw->last_attack_time           = 0;
  4628. #elif defined(DXX_BUILD_DESCENT_II)
  4629.         ail_rw->next_fire2                 = ail->next_fire2;
  4630. #endif
  4631.         ail_rw->next_action_time           = ail->next_action_time;
  4632.         ail_rw->next_fire                  = ail->next_fire;
  4633.         ail_rw->player_awareness_time      = ail->player_awareness_time;
  4634.         if (ail->time_player_seen - GameTime64 < F1_0*(-18000))
  4635.                 ail_rw->time_player_seen = F1_0*(-18000);
  4636.         else
  4637.                 ail_rw->time_player_seen = ail->time_player_seen - GameTime64;
  4638.         if (ail->time_player_sound_attacked - GameTime64 < F1_0*(-18000))
  4639.                 ail_rw->time_player_sound_attacked = F1_0*(-18000);
  4640.         else
  4641.                 ail_rw->time_player_sound_attacked = ail->time_player_sound_attacked - GameTime64;
  4642.         ail_rw->time_player_sound_attacked = ail->time_player_sound_attacked;
  4643.         ail_rw->next_misc_sound_time       = ail->next_misc_sound_time - GameTime64;
  4644.         ail_rw->time_since_processed       = ail->time_since_processed;
  4645.         for (i = 0; i < MAX_SUBMODELS; i++)
  4646.         {
  4647.                 ail_rw->goal_angles[i].p   = ail->goal_angles[i].p;
  4648.                 ail_rw->goal_angles[i].b   = ail->goal_angles[i].b;
  4649.                 ail_rw->goal_angles[i].h   = ail->goal_angles[i].h;
  4650.                 ail_rw->delta_angles[i].p  = ail->delta_angles[i].p;
  4651.                 ail_rw->delta_angles[i].b  = ail->delta_angles[i].b;
  4652.                 ail_rw->delta_angles[i].h  = ail->delta_angles[i].h;
  4653.                 ail_rw->goal_state[i]      = ail->goal_state[i];
  4654.                 ail_rw->achieved_state[i]  = ail->achieved_state[i];
  4655.         }
  4656. }
  4657.  
  4658. static void state_ai_cloak_info_to_ai_cloak_info_rw(ai_cloak_info *aic, ai_cloak_info_rw *aic_rw)
  4659. {
  4660.         if (aic->last_time - GameTime64 < F1_0*(-18000))
  4661.                 aic_rw->last_time = F1_0*(-18000);
  4662.         else
  4663.                 aic_rw->last_time = aic->last_time - GameTime64;
  4664. #if defined(DXX_BUILD_DESCENT_II)
  4665.         aic_rw->last_segment    = aic->last_segment;
  4666. #endif
  4667.         aic_rw->last_position.x = aic->last_position.x;
  4668.         aic_rw->last_position.y = aic->last_position.y;
  4669.         aic_rw->last_position.z = aic->last_position.z;
  4670. }
  4671.  
  4672. }
  4673.  
  4674. namespace dcx {
  4675.  
  4676. DEFINE_SERIAL_VMS_VECTOR_TO_MESSAGE();
  4677. DEFINE_SERIAL_UDT_TO_MESSAGE(point_seg, p, (p.segnum, serial::pad<2>(), p.point));
  4678. ASSERT_SERIAL_UDT_MESSAGE_SIZE(point_seg, 16);
  4679.  
  4680. DEFINE_SERIAL_MUTABLE_UDT_TO_MESSAGE(point_seg_array_t, p, (static_cast<std::array<point_seg, MAX_POINT_SEGS> &>(p)));
  4681. DEFINE_SERIAL_CONST_UDT_TO_MESSAGE(point_seg_array_t, p, (static_cast<const std::array<point_seg, MAX_POINT_SEGS> &>(p)));
  4682.  
  4683. }
  4684.  
  4685. namespace dsx {
  4686.  
  4687. int ai_save_state(PHYSFS_File *fp)
  4688. {
  4689.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  4690. #if defined(DXX_BUILD_DESCENT_II)
  4691.         auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
  4692.         auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
  4693.         auto &BuddyState = LevelUniqueObjectState.BuddyState;
  4694. #endif
  4695.         auto &Objects = LevelUniqueObjectState.Objects;
  4696.         fix tmptime32 = 0;
  4697.  
  4698.         const int Ai_initialized = 0;
  4699.         PHYSFS_write(fp, &Ai_initialized, sizeof(int), 1);
  4700.         PHYSFS_write(fp, &Overall_agitation, sizeof(int), 1);
  4701.         {
  4702.                 ai_local_rw zero{};
  4703.                 range_for (const auto &i, Objects)
  4704.                 {
  4705.                         ai_local_rw ail_rw;
  4706.                         PHYSFS_write(fp, i.type == OBJ_ROBOT ? (state_ai_local_to_ai_local_rw(&i.ctype.ai_info.ail, &ail_rw), &ail_rw) : &zero, sizeof(ail_rw), 1);
  4707.                 }
  4708.         }
  4709.         PHYSFSX_serialize_write(fp, Point_segs);
  4710.         //PHYSFS_write(fp, Ai_cloak_info, sizeof(ai_cloak_info) * MAX_AI_CLOAK_INFO, 1);
  4711.         range_for (auto &i, Ai_cloak_info)
  4712.         {
  4713.                 ai_cloak_info_rw aic_rw;
  4714.                 state_ai_cloak_info_to_ai_cloak_info_rw(&i, &aic_rw);
  4715.                 PHYSFS_write(fp, &aic_rw, sizeof(aic_rw), 1);
  4716.         }
  4717.         {
  4718.                 const auto Boss_cloak_start_time = BossUniqueState.Boss_cloak_start_time;
  4719.         if (Boss_cloak_start_time - GameTime64 < F1_0*(-18000))
  4720.                 tmptime32 = F1_0*(-18000);
  4721.         else
  4722.                 tmptime32 = Boss_cloak_start_time - GameTime64;
  4723.         PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
  4724.         if ((Boss_cloak_start_time + Boss_cloak_duration) - GameTime64 < F1_0*(-18000))
  4725.                 tmptime32 = F1_0*(-18000);
  4726.         else
  4727.                 tmptime32 = (Boss_cloak_start_time + Boss_cloak_duration) - GameTime64;
  4728.         PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
  4729.         }
  4730.         {
  4731.                 const auto Last_teleport_time = BossUniqueState.Last_teleport_time;
  4732.         if (Last_teleport_time - GameTime64 < F1_0*(-18000))
  4733.                 tmptime32 = F1_0*(-18000);
  4734.         else
  4735.                 tmptime32 = Last_teleport_time - GameTime64;
  4736.         PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
  4737.         }
  4738.         {
  4739.                 const fix Boss_teleport_interval = LevelSharedBossState.Boss_teleport_interval;
  4740.                 PHYSFS_write(fp, &Boss_teleport_interval, sizeof(fix), 1);
  4741.                 const fix Boss_cloak_interval = LevelSharedBossState.Boss_cloak_interval;
  4742.                 PHYSFS_write(fp, &Boss_cloak_interval, sizeof(fix), 1);
  4743.         }
  4744.         PHYSFS_write(fp, &Boss_cloak_duration, sizeof(fix), 1);
  4745.         if (BossUniqueState.Last_gate_time - GameTime64 < F1_0*(-18000))
  4746.                 tmptime32 = F1_0*(-18000);
  4747.         else
  4748.                 tmptime32 = BossUniqueState.Last_gate_time - GameTime64;
  4749.         PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
  4750.         PHYSFS_write(fp, &GameUniqueState.Boss_gate_interval, sizeof(fix), 1);
  4751.         {
  4752.                 const auto Boss_dying_start_time = BossUniqueState.Boss_dying_start_time;
  4753.         if (Boss_dying_start_time == 0) // if Boss not dead, yet we expect this to be 0, so do not convert!
  4754.         {
  4755.                 tmptime32 = 0;
  4756.         }
  4757.         else
  4758.         {
  4759.                 if (Boss_dying_start_time - GameTime64 < F1_0*(-18000))
  4760.                         tmptime32 = F1_0*(-18000);
  4761.                 else
  4762.                         tmptime32 = Boss_dying_start_time - GameTime64;
  4763.                 if (tmptime32 == 0) // now if our converted value went 0 we should do something against it
  4764.                         tmptime32 = -1;
  4765.         }
  4766.         PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
  4767.         }
  4768.         {
  4769.         const int boss_dying = BossUniqueState.Boss_dying;
  4770.         PHYSFS_write(fp, &boss_dying, sizeof(int), 1);
  4771.         }
  4772.         const int boss_dying_sound_playing = BossUniqueState.Boss_dying_sound_playing;
  4773.         PHYSFS_write(fp, &boss_dying_sound_playing, sizeof(int), 1);
  4774. #if defined(DXX_BUILD_DESCENT_I)
  4775.         const int boss_hit_this_frame = BossUniqueState.Boss_hit_this_frame;
  4776.         PHYSFS_write(fp, &boss_hit_this_frame, sizeof(int), 1);
  4777.         const int Boss_been_hit = 0;
  4778.         PHYSFS_write(fp, &Boss_been_hit, sizeof(int), 1);
  4779. #elif defined(DXX_BUILD_DESCENT_II)
  4780.         if (BossUniqueState.Boss_hit_time - GameTime64 < F1_0*(-18000))
  4781.                 tmptime32 = F1_0*(-18000);
  4782.         else
  4783.                 tmptime32 = BossUniqueState.Boss_hit_time - GameTime64;
  4784.         PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
  4785.         PHYSFS_writeSLE32(fp, -1);
  4786.         if (BuddyState.Escort_last_path_created - GameTime64 < F1_0*(-18000))
  4787.                 tmptime32 = F1_0*(-18000);
  4788.         else
  4789.                 tmptime32 = BuddyState.Escort_last_path_created - GameTime64;
  4790.         PHYSFS_write(fp, &tmptime32, sizeof(fix), 1);
  4791.         {
  4792.                 const uint32_t Escort_goal_object = BuddyState.Escort_goal_object;
  4793.         PHYSFS_write(fp, &Escort_goal_object, sizeof(Escort_goal_object), 1);
  4794.         }
  4795.         {
  4796.                 const uint32_t Escort_special_goal = BuddyState.Escort_special_goal;
  4797.         PHYSFS_write(fp, &Escort_special_goal, sizeof(Escort_special_goal), 1);
  4798.         }
  4799.         {
  4800.                 const int egi = BuddyState.Escort_goal_reachable == d_unique_buddy_state::Escort_goal_reachability::unreachable ? -2 : BuddyState.Escort_goal_objidx.get_unchecked_index();
  4801.                 PHYSFS_write(fp, &egi, sizeof(int), 1);
  4802.         }
  4803.         {
  4804.                 auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
  4805.                 PHYSFS_write(fp, &Stolen_items, sizeof(Stolen_items[0]) * Stolen_items.size(), 1);
  4806.         }
  4807.  
  4808.         {
  4809.                 int temp;
  4810.                 temp = Point_segs_free_ptr - Point_segs;
  4811.                 PHYSFS_write(fp, &temp, sizeof(int), 1);
  4812.         }
  4813.  
  4814.         unsigned Num_boss_teleport_segs = Boss_teleport_segs.size();
  4815.         PHYSFS_write(fp, &Num_boss_teleport_segs, sizeof(Num_boss_teleport_segs), 1);
  4816.         unsigned Num_boss_gate_segs = Boss_gate_segs.size();
  4817.         PHYSFS_write(fp, &Num_boss_gate_segs, sizeof(Num_boss_gate_segs), 1);
  4818.  
  4819.         if (Num_boss_gate_segs)
  4820.                 PHYSFS_write(fp, &Boss_gate_segs[0], sizeof(Boss_gate_segs[0]), Num_boss_gate_segs);
  4821.  
  4822.         if (Num_boss_teleport_segs)
  4823.                 PHYSFS_write(fp, &Boss_teleport_segs[0], sizeof(Boss_teleport_segs[0]), Num_boss_teleport_segs);
  4824. #endif
  4825.  
  4826.         return 1;
  4827. }
  4828.  
  4829. }
  4830.  
  4831. namespace dcx {
  4832.  
  4833. static void PHYSFSX_readAngleVecX(PHYSFS_File *file, vms_angvec &v, int swap)
  4834. {
  4835.         v.p = PHYSFSX_readSXE16(file, swap);
  4836.         v.b = PHYSFSX_readSXE16(file, swap);
  4837.         v.h = PHYSFSX_readSXE16(file, swap);
  4838. }
  4839.  
  4840. }
  4841.  
  4842. namespace dsx {
  4843.  
  4844. static void ai_local_read_swap(ai_local *ail, int swap, PHYSFS_File *fp)
  4845. {
  4846.         {
  4847.                 fix tmptime32 = 0;
  4848.  
  4849. #if defined(DXX_BUILD_DESCENT_I)
  4850.                 ail->player_awareness_type = static_cast<player_awareness_type_t>(PHYSFSX_readByte(fp));
  4851.                 ail->retry_count = PHYSFSX_readByte(fp);
  4852.                 ail->consecutive_retries = PHYSFSX_readByte(fp);
  4853.                 ail->mode = static_cast<ai_mode>(PHYSFSX_readByte(fp));
  4854.                 ail->previous_visibility = static_cast<player_visibility_state>(PHYSFSX_readByte(fp));
  4855.                 ail->rapidfire_count = PHYSFSX_readByte(fp);
  4856.                 ail->goal_segment = PHYSFSX_readSXE16(fp, swap);
  4857.                 PHYSFSX_readSXE32(fp, swap);
  4858.                 PHYSFSX_readSXE32(fp, swap);
  4859.                 ail->next_action_time = PHYSFSX_readSXE32(fp, swap);
  4860.                 ail->next_fire = PHYSFSX_readSXE32(fp, swap);
  4861. #elif defined(DXX_BUILD_DESCENT_II)
  4862.                 ail->player_awareness_type = static_cast<player_awareness_type_t>(PHYSFSX_readSXE32(fp, swap));
  4863.                 ail->retry_count = PHYSFSX_readSXE32(fp, swap);
  4864.                 ail->consecutive_retries = PHYSFSX_readSXE32(fp, swap);
  4865.                 ail->mode = static_cast<ai_mode>(PHYSFSX_readSXE32(fp, swap));
  4866.                 ail->previous_visibility = static_cast<player_visibility_state>(PHYSFSX_readSXE32(fp, swap));
  4867.                 ail->rapidfire_count = PHYSFSX_readSXE32(fp, swap);
  4868.                 ail->goal_segment = PHYSFSX_readSXE32(fp, swap);
  4869.                 ail->next_action_time = PHYSFSX_readSXE32(fp, swap);
  4870.                 ail->next_fire = PHYSFSX_readSXE32(fp, swap);
  4871.                 ail->next_fire2 = PHYSFSX_readSXE32(fp, swap);
  4872. #endif
  4873.                 ail->player_awareness_time = PHYSFSX_readSXE32(fp, swap);
  4874.                 tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4875.                 ail->time_player_seen = static_cast<fix64>(tmptime32);
  4876.                 tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4877.                 ail->time_player_sound_attacked = static_cast<fix64>(tmptime32);
  4878.                 tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4879.                 ail->next_misc_sound_time = static_cast<fix64>(tmptime32);
  4880.                 ail->time_since_processed = PHYSFSX_readSXE32(fp, swap);
  4881.                
  4882.                 range_for (auto &j, ail->goal_angles)
  4883.                         PHYSFSX_readAngleVecX(fp, j, swap);
  4884.                 range_for (auto &j, ail->delta_angles)
  4885.                         PHYSFSX_readAngleVecX(fp, j, swap);
  4886.                 range_for (auto &j, ail->goal_state)
  4887.                         j = PHYSFSX_readByte(fp);
  4888.                 range_for (auto &j, ail->achieved_state)
  4889.                         j = PHYSFSX_readByte(fp);
  4890.         }
  4891. }
  4892.  
  4893. }
  4894.  
  4895. namespace dcx {
  4896.  
  4897. static void PHYSFSX_readVectorX(PHYSFS_File *file, vms_vector &v, int swap)
  4898. {
  4899.         v.x = PHYSFSX_readSXE32(file, swap);
  4900.         v.y = PHYSFSX_readSXE32(file, swap);
  4901.         v.z = PHYSFSX_readSXE32(file, swap);
  4902. }
  4903.  
  4904. }
  4905.  
  4906. namespace dsx {
  4907.  
  4908. static void ai_cloak_info_read_n_swap(ai_cloak_info *ci, int n, int swap, PHYSFS_File *fp)
  4909. {
  4910.         int i;
  4911.         fix tmptime32 = 0;
  4912.        
  4913.         for (i = 0; i < n; i++, ci++)
  4914.         {
  4915.                 tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4916.                 ci->last_time = static_cast<fix64>(tmptime32);
  4917. #if defined(DXX_BUILD_DESCENT_II)
  4918.                 ci->last_segment = PHYSFSX_readSXE32(fp, swap);
  4919. #endif
  4920.                 PHYSFSX_readVectorX(fp, ci->last_position, swap);
  4921.         }
  4922. }
  4923.  
  4924. int ai_restore_state(PHYSFS_File *fp, int version, int swap)
  4925. {
  4926.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  4927. #if defined(DXX_BUILD_DESCENT_II)
  4928.         auto &Boss_gate_segs = LevelSharedBossState.Gate_segs;
  4929.         auto &Boss_teleport_segs = LevelSharedBossState.Teleport_segs;
  4930.         auto &BuddyState = LevelUniqueObjectState.BuddyState;
  4931. #endif
  4932.         auto &Objects = LevelUniqueObjectState.Objects;
  4933.         auto &vmobjptridx = Objects.vmptridx;
  4934.         fix tmptime32 = 0;
  4935.  
  4936.         PHYSFSX_readSXE32(fp, swap);
  4937.         Overall_agitation = PHYSFSX_readSXE32(fp, swap);
  4938.         range_for (object &obj, Objects)
  4939.         {
  4940.                 ai_local discard;
  4941.                 ai_local_read_swap(obj.type == OBJ_ROBOT ? &obj.ctype.ai_info.ail : &discard, swap, fp);
  4942.         }
  4943.         PHYSFSX_serialize_read(fp, Point_segs);
  4944.         ai_cloak_info_read_n_swap(Ai_cloak_info.data(), Ai_cloak_info.size(), swap, fp);
  4945.         tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4946.         BossUniqueState.Boss_cloak_start_time = static_cast<fix64>(tmptime32);
  4947.         tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4948.         tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4949.         BossUniqueState.Last_teleport_time = static_cast<fix64>(tmptime32);
  4950.  
  4951.         // If boss teleported, set the looping 'see' sound -kreatordxx
  4952.         // Also make sure any bosses that were generated/released during the game have teleport segs
  4953.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  4954. #if DXX_USE_EDITOR
  4955.         if (!EditorWindow)
  4956. #endif
  4957.                 range_for (const auto &&o, vmobjptridx)
  4958.                 {
  4959.                         if (o->type == OBJ_ROBOT)
  4960.                         {
  4961.                                 auto boss_id = Robot_info[get_robot_id(o)].boss_flag;
  4962.                                 if (boss_id
  4963. #if defined(DXX_BUILD_DESCENT_II)
  4964.                                 && (boss_id == BOSS_D1 || boss_id == BOSS_D2 || (boss_id >= BOSS_D2 && Boss_teleports[boss_id - BOSS_D2]))
  4965. #endif
  4966.                                 )
  4967.                                 {
  4968.                                         const auto Last_teleport_time = BossUniqueState.Last_teleport_time;
  4969.                                         if (Last_teleport_time != 0 && Last_teleport_time != BossUniqueState.Boss_cloak_start_time)
  4970.                                                 boss_link_see_sound(o);
  4971.                                         boss_init_all_segments(Segments, o);
  4972.                                 }
  4973.                         }
  4974.                 }
  4975.        
  4976. #if defined(DXX_BUILD_DESCENT_II)
  4977.         LevelSharedBossState.Boss_teleport_interval =
  4978. #endif
  4979.                 PHYSFSX_readSXE32(fp, swap);
  4980. #if defined(DXX_BUILD_DESCENT_II)
  4981.         LevelSharedBossState.Boss_cloak_interval =
  4982. #endif
  4983.                 PHYSFSX_readSXE32(fp, swap);
  4984.         PHYSFSX_readSXE32(fp, swap);
  4985.         tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4986.         BossUniqueState.Last_gate_time = static_cast<fix64>(tmptime32);
  4987.         GameUniqueState.Boss_gate_interval = PHYSFSX_readSXE32(fp, swap);
  4988.         tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4989.         BossUniqueState.Boss_dying_start_time = static_cast<fix64>(tmptime32);
  4990.         BossUniqueState.Boss_dying = PHYSFSX_readSXE32(fp, swap);
  4991.         BossUniqueState.Boss_dying_sound_playing = PHYSFSX_readSXE32(fp, swap);
  4992. #if defined(DXX_BUILD_DESCENT_I)
  4993.         (void)version;
  4994.         BossUniqueState.Boss_hit_this_frame = PHYSFSX_readSXE32(fp, swap);
  4995.         PHYSFSX_readSXE32(fp, swap);
  4996. #elif defined(DXX_BUILD_DESCENT_II)
  4997.         tmptime32 = PHYSFSX_readSXE32(fp, swap);
  4998.         BossUniqueState.Boss_hit_time = static_cast<fix64>(tmptime32);
  4999.  
  5000.         if (version >= 8) {
  5001.                 PHYSFSX_readSXE32(fp, swap);
  5002.                 tmptime32 = PHYSFSX_readSXE32(fp, swap);
  5003.                 BuddyState.Escort_last_path_created = static_cast<fix64>(tmptime32);
  5004.                 BuddyState.Escort_goal_object = static_cast<escort_goal_t>(PHYSFSX_readSXE32(fp, swap));
  5005.                 BuddyState.Escort_special_goal = static_cast<escort_goal_t>(PHYSFSX_readSXE32(fp, swap));
  5006.                 const int egi = PHYSFSX_readSXE32(fp, swap);
  5007.                 if (static_cast<unsigned>(egi) < Objects.size())
  5008.                 {
  5009.                         BuddyState.Escort_goal_objidx = egi;
  5010.                         BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::reachable;
  5011.                 }
  5012.                 else
  5013.                 {
  5014.                         BuddyState.Escort_goal_objidx = object_none;
  5015.                         BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::unreachable;
  5016.                 }
  5017.                 {
  5018.                         auto &Stolen_items = LevelUniqueObjectState.ThiefState.Stolen_items;
  5019.                         PHYSFS_read(fp, &Stolen_items, sizeof(Stolen_items[0]) * Stolen_items.size(), 1);
  5020.                 }
  5021.         } else {
  5022.                 BuddyState.Escort_last_path_created = 0;
  5023.                 BuddyState.Escort_goal_object = ESCORT_GOAL_UNSPECIFIED;
  5024.                 BuddyState.Escort_special_goal = ESCORT_GOAL_UNSPECIFIED;
  5025.                 BuddyState.Escort_goal_objidx = object_none;
  5026.                 BuddyState.Escort_goal_reachable = d_unique_buddy_state::Escort_goal_reachability::unreachable;
  5027.  
  5028.                 LevelUniqueObjectState.ThiefState.Stolen_items.fill(255);
  5029.         }
  5030.  
  5031.         if (version >= 15) {
  5032.                 unsigned temp;
  5033.                 temp = PHYSFSX_readSXE32(fp, swap);
  5034.                 if (temp > Point_segs.size())
  5035.                         throw std::out_of_range("too many points");
  5036.                 Point_segs_free_ptr = Point_segs.begin() + temp;
  5037.         } else
  5038.                 ai_reset_all_paths();
  5039.  
  5040.         if (version >= 21) {
  5041.                 unsigned Num_boss_teleport_segs = PHYSFSX_readSXE32(fp, swap);
  5042.                 unsigned Num_boss_gate_segs = PHYSFSX_readSXE32(fp, swap);
  5043.  
  5044.                 Boss_gate_segs.clear();
  5045.                 for (unsigned i = 0; i < Num_boss_gate_segs; i++)
  5046.                         Boss_gate_segs.emplace_back(PHYSFSX_readSXE16(fp, swap));
  5047.  
  5048.                 Boss_teleport_segs.clear();
  5049.                 for (unsigned i = 0; i < Num_boss_teleport_segs; i++)
  5050.                         Boss_teleport_segs.emplace_back(PHYSFSX_readSXE16(fp, swap));
  5051.         }
  5052. #endif
  5053.  
  5054.         return 1;
  5055. }
  5056.  
  5057. }
  5058.