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.  * This will contain the laser code
  23.  *
  24.  */
  25.  
  26. #include <stdlib.h>
  27. #include <stdio.h>
  28. #include <time.h>
  29.  
  30. #include "inferno.h"
  31. #include "game.h"
  32. #include "bm.h"
  33. #include "object.h"
  34. #include "laser.h"
  35. #include "args.h"
  36. #include "segment.h"
  37. #include "fvi.h"
  38. #include "segpoint.h"
  39. #include "dxxerror.h"
  40. #include "key.h"
  41. #include "texmap.h"
  42. #include "gameseg.h"
  43. #include "textures.h"
  44. #include "render.h"
  45. #include "vclip.h"
  46. #include "fireball.h"
  47. #include "polyobj.h"
  48. #include "robot.h"
  49. #include "weapon.h"
  50. #include "newdemo.h"
  51. #include "timer.h"
  52. #include "player.h"
  53. #include "sounds.h"
  54. #include "ai.h"
  55. #include "powerup.h"
  56. #include "multi.h"
  57. #include "physics.h"
  58. #include "multi.h"
  59. #include "fwd-wall.h"
  60. #include "playsave.h"
  61.  
  62. #include "compiler-range_for.h"
  63. #include "partial_range.h"
  64.  
  65. #ifdef NEWHOMER
  66. #define HOMING_TRACKABLE_DOT_FRAME_TIME HOMING_TURN_TIME
  67. static ubyte d_homer_tick_step = 0;
  68. static fix d_homer_tick_count = 0;
  69. #else
  70. #define HOMING_TRACKABLE_DOT_FRAME_TIME FrameTime
  71. #endif
  72.  
  73. static int Muzzle_queue_index;
  74.  
  75. namespace dsx {
  76. static imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2);
  77. static imobjptridx_t find_homing_object(const vms_vector &curpos, vmobjptridx_t tracker);
  78.  
  79. //---------------------------------------------------------------------------------
  80. // Called by render code.... determines if the laser is from a robot or the
  81. // player and calls the appropriate routine.
  82.  
  83. void Laser_render(grs_canvas &canvas, const object_base &obj)
  84. {
  85.         auto &wi = Weapon_info[get_weapon_id(obj)];
  86.         switch(wi.render_type)
  87.         {
  88.         case WEAPON_RENDER_LASER:
  89.                 Int3(); // Not supported anymore!
  90.                                         //Laser_draw_one(obj-Objects, Weapon_info[obj->id].bitmap );
  91.                 break;
  92.         case WEAPON_RENDER_BLOB:
  93.                 draw_object_blob(canvas, obj, wi.bitmap);
  94.                 break;
  95.         case WEAPON_RENDER_POLYMODEL:
  96.                 break;
  97.         case WEAPON_RENDER_VCLIP:
  98.                 Int3(); //      Oops, not supported, type added by mk on 09/09/94, but not for lasers...
  99.                 DXX_BOOST_FALLTHROUGH;
  100.         default:
  101.                 Error( "Invalid weapon render type in Laser_render\n" );
  102.         }
  103. }
  104.  
  105. //---------------------------------------------------------------------------------
  106. // Draws a texture-mapped laser bolt
  107.  
  108. //void Laser_draw_one( int objnum, grs_bitmap * bmp )
  109. //{
  110. //      int t1, t2, t3;
  111. //      g3s_point p1, p2;
  112. //      object *obj;
  113. //      vms_vector start_pos,end_pos;
  114. //
  115. //      obj = &Objects[objnum];
  116. //
  117. //      start_pos = obj->pos;
  118. //      vm_vec_scale_add(&end_pos,&start_pos,&obj->orient.fvec,-Laser_length);
  119. //
  120. //      g3_rotate_point(&p1,&start_pos);
  121. //      g3_rotate_point(&p2,&end_pos);
  122. //
  123. //      t1 = Lighting_on;
  124. //      t2 = Interpolation_method;
  125. //      t3 = Transparency_on;
  126. //
  127. //      Lighting_on  = 0;
  128. //      //Interpolation_method = 3;     // Full perspective
  129. //      Interpolation_method = 1;       // Linear
  130. //      Transparency_on = 1;
  131. //
  132. //      //gr_setcolor( gr_getcolor(31,15,0));
  133. //      //g3_draw_line_ptrs(p1,p2);
  134. //      //g3_draw_rod(p1,0x2000,p2,0x2000);
  135. //      //g3_draw_rod(p1,Laser_width,p2,Laser_width);
  136. //      g3_draw_rod_tmap(bmp,&p2,Laser_width,&p1,Laser_width,0);
  137. //      Lighting_on = t1;
  138. //      Interpolation_method = t2;
  139. //      Transparency_on = t3;
  140. //
  141. //}
  142.  
  143. static bool ignore_proximity_weapon(const object &o)
  144. {
  145.         if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(o)))
  146.                 return false;
  147. #if defined(DXX_BUILD_DESCENT_I)
  148.         return GameTime64 > o.ctype.laser_info.creation_time + F1_0*2;
  149. #elif defined(DXX_BUILD_DESCENT_II)
  150.         return GameTime64 > o.ctype.laser_info.creation_time + F1_0*4;
  151. #endif
  152. }
  153.  
  154. #if defined(DXX_BUILD_DESCENT_I)
  155. static bool ignore_phoenix_weapon(const object &)
  156. {
  157.         return false;
  158. }
  159.  
  160. static bool ignore_guided_missile_weapon(const object &)
  161. {
  162.         return false;
  163. }
  164. #elif defined(DXX_BUILD_DESCENT_II)
  165. static bool ignore_phoenix_weapon(const object &o)
  166. {
  167.         return get_weapon_id(o) == weapon_id_type::PHOENIX_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0/4;
  168. }
  169.  
  170. static bool ignore_guided_missile_weapon(const object &o)
  171. {
  172.         return get_weapon_id(o) == weapon_id_type::GUIDEDMISS_ID && GameTime64 > o.ctype.laser_info.creation_time + F1_0*2;
  173. }
  174. #endif
  175.  
  176. //      Changed by MK on 09/07/94
  177. //      I want you to be able to blow up your own bombs.
  178. //      AND...Your proximity bombs can blow you up if they're 2.0 seconds or more old.
  179. //      Changed by MK on 06/06/95: Now must be 4.0 seconds old.  Much valid Net-complaining.
  180. bool laser_are_related(const vcobjptridx_t o1, const vcobjptridx_t o2)
  181. {
  182.         // See if o2 is the parent of o1
  183.         if (o1->type == OBJ_WEAPON)
  184.                 if (laser_parent_is_object(o1->ctype.laser_info, o2))
  185.                 {
  186.                         //      o1 is a weapon, o2 is the parent of 1, so if o1 is PROXIMITY_BOMB and o2 is player, they are related only if o1 < 2.0 seconds old
  187.                         if (ignore_proximity_weapon(o1) || ignore_guided_missile_weapon(o1) || ignore_phoenix_weapon(o1))
  188.                         {
  189.                                 return 0;
  190.                         } else
  191.                                 return 1;
  192.                 }
  193.  
  194.         // See if o1 is the parent of o2
  195.         if (o2->type == OBJ_WEAPON)
  196.         {
  197.                 if (laser_parent_is_object(o2->ctype.laser_info, o1))
  198.                 {
  199. #if defined(DXX_BUILD_DESCENT_II)
  200.                         //      o2 is a weapon, o1 is the parent of 2, so if o2 is PROXIMITY_BOMB and o1 is player, they are related only if o1 < 2.0 seconds old
  201.                         if (ignore_proximity_weapon(o2) || ignore_guided_missile_weapon(o2) || ignore_phoenix_weapon(o2))
  202.                         {
  203.                                 return 0;
  204.                         } else
  205. #endif
  206.                                 return 1;
  207.                 }
  208.         }
  209.  
  210.         // They must both be weapons
  211.         if (o1->type != OBJ_WEAPON || o2->type != OBJ_WEAPON)
  212.                 return 0;
  213.  
  214.         //      Here is the 09/07/94 change -- Siblings must be identical, others can hurt each other
  215.         // See if they're siblings...
  216.         //      MK: 06/08/95, Don't allow prox bombs to detonate for 3/4 second.  Else too likely to get toasted by your own bomb if hit by opponent.
  217.         const auto o1id = get_weapon_id(o1);
  218.         const auto o2id = get_weapon_id(o2);
  219.         auto &o1li = o1->ctype.laser_info;
  220.         auto &o2li = o2->ctype.laser_info;
  221.         if (o1li.parent_num == o2li.parent_num && o1li.parent_signature == o2li.parent_signature)
  222.         {
  223.                 if (is_proximity_bomb_or_player_smart_mine(o1id) || is_proximity_bomb_or_player_smart_mine(o2id))
  224.                 {
  225.                         //      If neither is older than 1/2 second, then can't blow up!
  226. #if defined(DXX_BUILD_DESCENT_II)
  227.                         if (!(GameTime64 > o1li.creation_time + F1_0/2 || GameTime64 > o2li.creation_time + F1_0/2))
  228.                                 return 1;
  229.                         else
  230. #endif
  231.                                 return 0;
  232.                 } else
  233.                         return 1;
  234.         }
  235.  
  236. #if defined(DXX_BUILD_DESCENT_II)
  237.         //      Anything can cause a collision with a robot super prox mine.
  238.         if (!(
  239.                 o1id == weapon_id_type::ROBOT_SUPERPROX_ID || o2id == weapon_id_type::ROBOT_SUPERPROX_ID ||
  240.                 o1id == weapon_id_type::PROXIMITY_ID || o2id == weapon_id_type::PROXIMITY_ID ||
  241.                 o1id == weapon_id_type::SUPERPROX_ID || o2id == weapon_id_type::SUPERPROX_ID ||
  242.                 o1id == weapon_id_type::PMINE_ID || o2id == weapon_id_type::PMINE_ID
  243.         ))
  244.                 return 1;
  245. #endif
  246.         return 0;
  247. }
  248.  
  249. }
  250.  
  251. namespace dcx {
  252.  
  253. constexpr vm_distance MAX_SMART_DISTANCE(F1_0*150);
  254. constexpr vm_distance_squared MAX_SMART_DISTANCE_SQUARED = MAX_SMART_DISTANCE * MAX_SMART_DISTANCE;
  255. static void do_muzzle_stuff(segnum_t segnum, const vms_vector &pos)
  256. {
  257.         auto &m = Muzzle_data[Muzzle_queue_index];
  258.         Muzzle_queue_index++;
  259.         if (Muzzle_queue_index >= MUZZLE_QUEUE_MAX)
  260.                 Muzzle_queue_index = 0;
  261.         m.segnum = segnum;
  262.         m.pos = pos;
  263.         m.create_time = timer_query();
  264. }
  265.  
  266. __attribute_noreturn
  267. static void report_invalid_weapon_render_type(const int weapon_type, const unsigned render_type)
  268. {
  269.         char buf[96];
  270.         snprintf(buf, sizeof(buf), "invalid weapon render type %u on weapon %i", render_type, weapon_type);
  271.         throw std::runtime_error(buf);
  272. }
  273.  
  274. }
  275.  
  276. namespace dsx {
  277.  
  278. //creates a weapon object
  279. static imobjptridx_t create_weapon_object(int weapon_type,const vmsegptridx_t segnum, const vms_vector &position)
  280. {
  281.         render_type_t rtype;
  282.         fix laser_radius = -1;
  283.  
  284.         switch( Weapon_info[weapon_type].render_type )  {
  285.  
  286.                 case WEAPON_RENDER_BLOB:
  287.                         rtype = RT_LASER;                       // Render as a laser even if blob (see render code above for explanation)
  288.                         laser_radius = Weapon_info[weapon_type].blob_size;
  289.                         break;
  290.                 case WEAPON_RENDER_POLYMODEL:
  291.                         laser_radius = 0;       //      Filled in below.
  292.                         rtype = RT_POLYOBJ;
  293.                         break;
  294.                 case WEAPON_RENDER_LASER:
  295.                         Int3();         // Not supported anymore
  296.                         return object_none;
  297.                 case WEAPON_RENDER_NONE:
  298.                         rtype = RT_NONE;
  299.                         laser_radius = F1_0;
  300.                         break;
  301.                 case WEAPON_RENDER_VCLIP:
  302.                         rtype = RT_WEAPON_VCLIP;
  303.                         laser_radius = Weapon_info[weapon_type].blob_size;
  304.                         break;
  305.                 default:
  306.                         report_invalid_weapon_render_type(weapon_type, Weapon_info[weapon_type].render_type);
  307.         }
  308.  
  309.         Assert(laser_radius != -1);
  310.  
  311.         auto &&obj = obj_create( OBJ_WEAPON, weapon_type, segnum, position, NULL, laser_radius, CT_WEAPON, MT_PHYSICS, rtype);
  312.         if (obj == object_none)
  313.                 return object_none;
  314.  
  315.         if (Weapon_info[weapon_type].render_type == WEAPON_RENDER_POLYMODEL) {
  316.                 auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  317.                 obj->rtype.pobj_info.model_num = Weapon_info[get_weapon_id(obj)].model_num;
  318.                 obj->size = fixdiv(Polygon_models[obj->rtype.pobj_info.model_num].rad,Weapon_info[get_weapon_id(obj)].po_len_to_width_ratio);
  319.         }
  320.  
  321.         obj->mtype.phys_info.mass = Weapon_info[weapon_type].mass;
  322.         obj->mtype.phys_info.drag = Weapon_info[weapon_type].drag;
  323.         vm_vec_zero(obj->mtype.phys_info.thrust);
  324.  
  325.         if (Weapon_info[weapon_type].bounce==1)
  326.                 obj->mtype.phys_info.flags |= PF_BOUNCE;
  327.  
  328. #if defined(DXX_BUILD_DESCENT_II)
  329.         if (Weapon_info[weapon_type].bounce==2 || cheats.bouncyfire)
  330.                 obj->mtype.phys_info.flags |= PF_BOUNCE+PF_BOUNCES_TWICE;
  331. #endif
  332.  
  333.  
  334.         return obj;
  335. }
  336.  
  337. #if defined(DXX_BUILD_DESCENT_II)
  338. //      -------------------------------------------------------------------------------------------------------------------------------
  339. //      ***** HEY ARTISTS!! *****
  340. //      Here are the constants you're looking for! --MK
  341.  
  342. //      Change the following constants to affect the look of the omega cannon.
  343. //      Changing these constants will not affect the damage done.
  344. //      WARNING: If you change DESIRED_OMEGA_DIST and MAX_OMEGA_BLOBS, you don't merely change the look of the cannon,
  345. //      you change its range.  If you decrease DESIRED_OMEGA_DIST, you decrease how far the gun can fire.
  346. constexpr std::integral_constant<fix, F1_0/20> OMEGA_BASE_TIME{}; // How many blobs per second!! No FPS-based blob creation anymore, no FPS-based damage anymore!
  347. constexpr std::integral_constant<unsigned, 3> MIN_OMEGA_BLOBS{};                                //      No matter how close the obstruction, at this many blobs created.
  348. constexpr std::integral_constant<fix, F1_0*3> MIN_OMEGA_DIST{};         //      At least this distance between blobs, unless doing so would violate MIN_OMEGA_BLOBS
  349. constexpr std::integral_constant<fix, F1_0*5> DESIRED_OMEGA_DIST{};             //      This is the desired distance between blobs.  For distances > MIN_OMEGA_BLOBS*DESIRED_OMEGA_DIST, but not very large, this will apply.
  350. constexpr std::integral_constant<unsigned, 16> MAX_OMEGA_BLOBS{};                               //      No matter how far away the obstruction, this is the maximum number of blobs.
  351. constexpr vm_distance MAX_OMEGA_DIST{MAX_OMEGA_BLOBS * DESIRED_OMEGA_DIST};             //      Maximum extent of lightning blobs.
  352. constexpr vm_distance_squared MAX_OMEGA_DIST_SQUARED{MAX_OMEGA_DIST * MAX_OMEGA_DIST};
  353.  
  354. //      Additionally, several constants which apply to homing objects in general control the behavior of the Omega Cannon.
  355. //      They are defined in laser.h.  They are copied here for reference.  These values are valid on 1/10/96:
  356. //      If you want the Omega Cannon view cone to be different than the Homing Missile viewcone, contact MK to make the change.
  357. //      (Unless you are a programmer, in which case, do it yourself!)
  358. #define OMEGA_MIN_TRACKABLE_DOT                 (15*F1_0/16)            //      Larger values mean narrower cone.  F1_0 means damn near impossible.  0 means 180 degree field of view.
  359. constexpr vm_distance OMEGA_MAX_TRACKABLE_DIST = MAX_OMEGA_DIST; //     An object must be at least this close to be tracked.
  360.  
  361. //      Note, you don't need to change these constants.  You can control damage and energy consumption by changing the
  362. //      usual bitmaps.tbl parameters.
  363. #define OMEGA_DAMAGE_SCALE                      32                              //      Controls how much damage is done.  This gets multiplied by the damage specified in bitmaps.tbl in the $WEAPON line.
  364. #define OMEGA_ENERGY_CONSUMPTION        16                              //      Controls how much energy is consumed.  This gets multiplied by the energy parameter from bitmaps.tbl.
  365. //      -------------------------------------------------------------------------------------------------------------------------------
  366.  
  367. // Delete omega blobs further away than MAX_OMEGA_DIST
  368. // Since last omega blob has VERY high velocity it's impossible to ensure a constant travel distance on varying FPS. So delete if they exceed their maximum distance.
  369. static int omega_cleanup(fvcobjptr &vcobjptr, const vmobjptridx_t weapon)
  370. {
  371.         if (weapon->type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID)
  372.                 return 0;
  373.         auto &weapon_laser_info = weapon->ctype.laser_info;
  374.         auto &obj = *vcobjptr(weapon_laser_info.parent_num);
  375.         if (laser_parent_is_matching_signature(weapon_laser_info, obj))
  376.                 if (vm_vec_dist2(weapon->pos, obj.pos) > MAX_OMEGA_DIST_SQUARED)
  377.                 {
  378.                         obj_delete(LevelUniqueObjectState, Segments, weapon);
  379.                         return 1;
  380.                 }
  381.  
  382.         return 0;
  383. }
  384.  
  385. // Return true if ok to do Omega damage. For Multiplayer games. See comment for omega_cleanup()
  386. int ok_to_do_omega_damage(const object &weapon)
  387. {
  388.         auto &Objects = LevelUniqueObjectState.Objects;
  389.         auto &vcobjptr = Objects.vcptr;
  390.         if (weapon.type != OBJ_WEAPON || get_weapon_id(weapon) != weapon_id_type::OMEGA_ID)
  391.                 return 1;
  392.         if (!(Game_mode & GM_MULTI))
  393.                 return 1;
  394.         auto &weapon_laser_info = weapon.ctype.laser_info;
  395.         auto &obj = *vcobjptr(weapon_laser_info.parent_num);
  396.         if (laser_parent_is_matching_signature(weapon_laser_info, obj))
  397.                 if (vm_vec_dist2(obj.pos, weapon.pos) > MAX_OMEGA_DIST_SQUARED)
  398.                         return 0;
  399.  
  400.         return 1;
  401. }
  402.  
  403. // ---------------------------------------------------------------------------------
  404. static void create_omega_blobs(const imsegptridx_t firing_segnum, const vms_vector &firing_pos, const vms_vector &goal_pos, const vmobjptridx_t parent_objp)
  405. {
  406.         imobjptridx_t  last_created_objnum = object_none;
  407.         fix             dist_to_goal = 0, omega_blob_dist = 0;
  408.         std::array<fix, MAX_OMEGA_BLOBS> perturb_array{};
  409.  
  410.         auto vec_to_goal = vm_vec_sub(goal_pos, firing_pos);
  411.         dist_to_goal = vm_vec_normalize_quick(vec_to_goal);
  412.  
  413.         unsigned num_omega_blobs = 0;
  414.         if (dist_to_goal < MIN_OMEGA_BLOBS * MIN_OMEGA_DIST) {
  415.                 omega_blob_dist = MIN_OMEGA_DIST;
  416.                 num_omega_blobs = dist_to_goal/omega_blob_dist;
  417.                 if (num_omega_blobs == 0)
  418.                         num_omega_blobs = 1;
  419.         } else {
  420.                 omega_blob_dist = DESIRED_OMEGA_DIST;
  421.                 num_omega_blobs = dist_to_goal / omega_blob_dist;
  422.                 if (num_omega_blobs > MAX_OMEGA_BLOBS) {
  423.                         num_omega_blobs = MAX_OMEGA_BLOBS;
  424.                         omega_blob_dist = dist_to_goal / num_omega_blobs;
  425.                 } else if (num_omega_blobs < MIN_OMEGA_BLOBS) {
  426.                         num_omega_blobs = MIN_OMEGA_BLOBS;
  427.                         omega_blob_dist = dist_to_goal / num_omega_blobs;
  428.                 }
  429.         }
  430.  
  431.         vms_vector omega_delta_vector, blob_pos;
  432.         omega_delta_vector = vec_to_goal;
  433.         vm_vec_scale(omega_delta_vector, omega_blob_dist);
  434.  
  435.         //      Now, create all the blobs
  436.         blob_pos = firing_pos;
  437.         auto last_segnum = firing_segnum;
  438.  
  439.         //      If nearby, don't perturb vector.  If not nearby, start halfway out.
  440.         if (dist_to_goal < MIN_OMEGA_DIST*4) {
  441.         } else {
  442.                 vm_vec_scale_add2(blob_pos, omega_delta_vector, F1_0/2);        //      Put first blob half way out.
  443.                 for (int i=0; i<num_omega_blobs/2; i++) {
  444.                         perturb_array[i] = F1_0*i + F1_0/4;
  445.                         perturb_array[num_omega_blobs-1-i] = F1_0*i;
  446.                 }
  447.         }
  448.  
  449.         //      Create random perturbation vector, but favor _not_ going up in player's reference.
  450.         auto perturb_vec = make_random_vector();
  451.         vm_vec_scale_add2(perturb_vec, parent_objp->orient.uvec, -F1_0/2);
  452.  
  453.         Doing_lighting_hack_flag = 1;   //      Ugly, but prevents blobs which are probably outside the mine from killing framerate.
  454.  
  455.         const auto &Difficulty_level = GameUniqueState.Difficulty_level;
  456.         for (int i=0; i<num_omega_blobs; i++) {
  457.                 //      This will put the last blob right at the destination object, causing damage.
  458.                 if (i == num_omega_blobs-1)
  459.                         vm_vec_scale_add2(blob_pos, omega_delta_vector, 15*F1_0/32);    //      Move last blob another (almost) half section
  460.  
  461.                 //      Every so often, re-perturb blobs
  462.                 if ((i % 4) == 3) {
  463.                         vm_vec_scale_add2(perturb_vec, make_random_vector(), F1_0/4);
  464.                 }
  465.  
  466.                 const auto temp_pos = vm_vec_scale_add(blob_pos, perturb_vec, perturb_array[i]);
  467.  
  468.                 const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, temp_pos, last_segnum);
  469.                 if (segnum != segment_none) {
  470.                         last_segnum = segnum;
  471.                         auto objp = obj_create(OBJ_WEAPON, weapon_id_type::OMEGA_ID, segnum, temp_pos, NULL, 0, CT_WEAPON, MT_PHYSICS, RT_WEAPON_VCLIP );
  472.                         if (objp == object_none)
  473.                                 break;
  474.  
  475.                         last_created_objnum = objp;
  476.  
  477.                         objp->lifeleft = OMEGA_BASE_TIME+(d_rand()/8); // add little randomness so the lighting effect becomes a little more interesting
  478.                         objp->mtype.phys_info.velocity = vec_to_goal;
  479.  
  480.                         //      Only make the last one move fast, else multiple blobs might collide with target.
  481.                         vm_vec_scale(objp->mtype.phys_info.velocity, F1_0*4);
  482.  
  483.                         const auto &weapon_info = Weapon_info[get_weapon_id(objp)];
  484.                         objp->size = weapon_info.blob_size;
  485.  
  486.                         objp->shields = fixmul(OMEGA_DAMAGE_SCALE*OMEGA_BASE_TIME, weapon_info.strength[Difficulty_level]);
  487.  
  488.                         objp->ctype.laser_info.parent_type                      = parent_objp->type;
  489.                         objp->ctype.laser_info.parent_signature = parent_objp->signature;
  490.                         objp->ctype.laser_info.parent_num                       = parent_objp;
  491.                         objp->movement_type = MT_NONE;  //      Only last one moves, that will get bashed below.
  492.  
  493.                 }
  494.                 vm_vec_add2(blob_pos, omega_delta_vector);
  495.         }
  496.  
  497.         //      Make last one move faster, but it's already moving at speed = F1_0*4.
  498.         if (last_created_objnum != object_none) {
  499.                 vm_vec_scale(last_created_objnum->mtype.phys_info.velocity, Weapon_info[weapon_id_type::OMEGA_ID].speed[Difficulty_level]/4);
  500.                 last_created_objnum->movement_type = MT_PHYSICS;
  501.         }
  502.  
  503.         Doing_lighting_hack_flag = 0;
  504. }
  505.  
  506. #define MIN_OMEGA_CHARGE        (MAX_OMEGA_CHARGE/8)
  507. #define OMEGA_CHARGE_SCALE      4                       //      FrameTime / OMEGA_CHARGE_SCALE added to Omega_charge every frame.
  508.  
  509. #define OMEGA_CHARGE_SCALE      4
  510.  
  511. fix get_omega_energy_consumption(const fix delta_charge)
  512. {
  513.         const fix energy_used = fixmul(F1_0 * 190 / 17, delta_charge);
  514.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  515.         return Difficulty_level < 2
  516.                 ? fixmul(energy_used, i2f(Difficulty_level + 2) / 4)
  517.                 : energy_used;
  518. }
  519.  
  520. // ---------------------------------------------------------------------------------
  521. //      Call this every frame to recharge the Omega Cannon.
  522. void omega_charge_frame(player_info &player_info)
  523. {
  524.         if (!(player_info.primary_weapon_flags & HAS_PRIMARY_FLAG(primary_weapon_index_t::OMEGA_INDEX)))
  525.                 return;
  526.         auto &Omega_charge = player_info.Omega_charge;
  527.         if (Omega_charge >= MAX_OMEGA_CHARGE)
  528.                 return;
  529.  
  530.         if (Player_dead_state != player_dead_state::no)
  531.                 return;
  532.  
  533.         //      Don't charge while firing. Wait 1/3 second after firing before recharging
  534.         auto &Omega_recharge_delay = player_info.Omega_recharge_delay;
  535.         if (Omega_recharge_delay)
  536.         {
  537.                 if (Omega_recharge_delay > FrameTime)
  538.                 {
  539.                         Omega_recharge_delay -= FrameTime;
  540.                         return;
  541.                 }
  542.                 Omega_recharge_delay = 0;
  543.         }
  544.  
  545.         if (auto &energy = player_info.energy)
  546.         {
  547.                 const auto old_omega_charge = Omega_charge;
  548.                 Omega_charge += FrameTime/OMEGA_CHARGE_SCALE;
  549.                 if (Omega_charge > MAX_OMEGA_CHARGE)
  550.                         Omega_charge = MAX_OMEGA_CHARGE;
  551.  
  552.                 const auto energy_used = get_omega_energy_consumption(Omega_charge - old_omega_charge);
  553.                 energy -= energy_used;
  554.                 if (energy < 0)
  555.                         energy = 0;
  556.         }
  557.  
  558.  
  559. }
  560.  
  561. // -- fix       Last_omega_muzzle_flash_time;
  562.  
  563. // ---------------------------------------------------------------------------------
  564. //      *objp is the object firing the omega cannon
  565. //      *pos is the location from which the omega bolt starts
  566. static void do_omega_stuff(fvmsegptridx &vmsegptridx, const vmobjptridx_t parent_objp, const vms_vector &firing_pos, const vmobjptridx_t weapon_objp)
  567. {
  568.         vms_vector      goal_pos;
  569.         if (parent_objp->type == OBJ_PLAYER && get_player_id(parent_objp) == Player_num)
  570.         {
  571.                 //      If charge >= min, or (some charge and zero energy), allow to fire.
  572.                 auto &player_info = parent_objp->ctype.player_info;
  573.                 auto &Omega_charge = player_info.Omega_charge;
  574.                 if (!((Omega_charge >= MIN_OMEGA_CHARGE) || (Omega_charge && !player_info.energy))) {
  575.                         obj_delete(LevelUniqueObjectState, Segments, weapon_objp);
  576.                         return;
  577.                 }
  578.  
  579.                 Omega_charge -= OMEGA_BASE_TIME;
  580.                 if (Omega_charge < 0)
  581.                         Omega_charge = 0;
  582.  
  583.                 player_info.Omega_recharge_delay = F1_0 / 3;
  584.         }
  585.  
  586.         weapon_objp->ctype.laser_info.parent_type = parent_objp->type;
  587.         weapon_objp->ctype.laser_info.parent_num = parent_objp.get_unchecked_index();
  588.         weapon_objp->ctype.laser_info.parent_signature = parent_objp->signature;
  589.  
  590.         const auto &&lock_objnum = find_homing_object(firing_pos, weapon_objp);
  591.  
  592.         const auto &&firing_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, firing_pos, Segments.vmptridx(parent_objp->segnum));
  593.  
  594.         //      Play sound.
  595.         {
  596.                 const auto flash_sound = Weapon_info[get_weapon_id(weapon_objp)].flash_sound;
  597.         if ( parent_objp == Viewer )
  598.                 digi_play_sample(flash_sound, F1_0);
  599.         else
  600.                 digi_link_sound_to_pos(flash_sound, vmsegptridx(weapon_objp->segnum), 0, weapon_objp->pos, 0, F1_0);
  601.         }
  602.  
  603.         // -- if ((Last_omega_muzzle_flash_time + F1_0/4 < GameTime) || (Last_omega_muzzle_flash_time > GameTime)) {
  604.         // --   do_muzzle_stuff(firing_segnum, firing_pos);
  605.         // --   Last_omega_muzzle_flash_time = GameTime;
  606.         // -- }
  607.  
  608.         //      Delete the original object.  Its only purpose in life was to determine which object to home in on.
  609.         obj_delete(LevelUniqueObjectState, Segments, weapon_objp);
  610.  
  611.         //      If couldn't lock on anything, fire straight ahead.
  612.         if (lock_objnum == object_none) {
  613.                 fvi_query       fq;
  614.                 fvi_info                hit_data;
  615.                 int                     fate;
  616.                 const auto &&perturbed_fvec = vm_vec_scale_add(parent_objp->orient.fvec, make_random_vector(), F1_0/16);
  617.                 vm_vec_scale_add(goal_pos, firing_pos, perturbed_fvec, MAX_OMEGA_DIST);
  618.                 fq.startseg = firing_segnum;
  619.                 if (fq.startseg == segment_none) {
  620.                         return;
  621.                 }
  622.                 fq.p0                                           = &firing_pos;
  623.                 fq.p1                                           = &goal_pos;
  624.                 fq.rad                                  = 0;
  625.                 fq.thisobjnum                   = parent_objp;
  626.                 fq.ignore_obj_list.first = nullptr;
  627.                 fq.flags                                        = FQ_IGNORE_POWERUPS | FQ_TRANSPOINT | FQ_CHECK_OBJS;           //what about trans walls???
  628.  
  629.                 fate = find_vector_intersection(fq, hit_data);
  630.                 if (fate != HIT_NONE) {
  631.                         Assert(hit_data.hit_seg != segment_none);               //      How can this be?  We went from inside the mine to outside without hitting anything?
  632.                         goal_pos = hit_data.hit_pnt;
  633.                 }
  634.         } else
  635.                 goal_pos = lock_objnum->pos;
  636.  
  637.         //      This is where we create a pile of omega blobs!
  638.         create_omega_blobs(firing_segnum, firing_pos, goal_pos, parent_objp);
  639.  
  640. }
  641.  
  642. static int is_laser_weapon_type(const weapon_id_type weapon_type)
  643. {
  644.         return weapon_type == weapon_id_type::LASER_ID_L1 ||
  645.                 weapon_type == weapon_id_type::LASER_ID_L2 ||
  646.                 weapon_type == weapon_id_type::LASER_ID_L3 ||
  647.                 weapon_type == weapon_id_type::LASER_ID_L4 ||
  648.                 weapon_type == weapon_id_type::LASER_ID_L5 ||
  649.                 weapon_type == weapon_id_type::LASER_ID_L6;
  650. }
  651. #endif
  652.  
  653. // ---------------------------------------------------------------------------------
  654. // Initializes a laser after Fire is pressed
  655. //      Returns object number.
  656. imobjptridx_t Laser_create_new(const vms_vector &direction, const vms_vector &position, const vmsegptridx_t segnum, const vmobjptridx_t parent, weapon_id_type weapon_type, int make_sound )
  657. {
  658.         auto &Objects = LevelUniqueObjectState.Objects;
  659.         auto &vmobjptr = Objects.vmptr;
  660.         fix parent_speed, weapon_speed;
  661.         fix volume;
  662.         fix laser_length=0;
  663.  
  664.         if (weapon_type >= N_weapon_types)
  665.         {
  666.                 con_printf(CON_URGENT, DXX_STRINGIZE_FL(__FILE__, __LINE__, "invalid weapon id %u fired by parent %hu (type %u) in segment %hu"), weapon_type, parent.get_unchecked_index(), parent->type, segnum.get_unchecked_index());
  667.                 weapon_type = weapon_id_type::LASER_ID_L1;
  668.         }
  669.  
  670.         //      Don't let homing blobs make muzzle flash.
  671.         if (parent->type == OBJ_ROBOT)
  672.                 do_muzzle_stuff(segnum, position);
  673.  
  674.         const imobjptridx_t obj = create_weapon_object(weapon_type,segnum,position);
  675.  
  676.         if (obj == object_none)
  677.         {
  678.                 return object_none;
  679.         }
  680.         const auto &weapon_info = Weapon_info[weapon_type];
  681.  
  682. #if defined(DXX_BUILD_DESCENT_II)
  683.         //      Do the special Omega Cannon stuff.  Then return on account of everything that follows does
  684.         //      not apply to the Omega Cannon.
  685.         if (weapon_type == weapon_id_type::OMEGA_ID) {
  686.                 // Create orientation matrix for tracking purposes.
  687.                 vm_vector_2_matrix( obj->orient, direction, &parent->orient.uvec ,nullptr);
  688.  
  689.                 if (parent != Viewer && parent->type != OBJ_WEAPON) {
  690.                         // Muzzle flash
  691.                         if (weapon_info.flash_vclip > -1 )
  692.                                 object_create_muzzle_flash(vmsegptridx(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip);
  693.                 }
  694.  
  695.                 do_omega_stuff(vmsegptridx, parent, position, obj);
  696.  
  697.                 return obj;
  698.         }
  699. #endif
  700.  
  701.         if (parent->type == OBJ_PLAYER) {
  702.                 if (weapon_type == weapon_id_type::FUSION_ID) {
  703.                         int     fusion_scale;
  704. #if defined(DXX_BUILD_DESCENT_I)
  705.                         if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
  706.                                 fusion_scale = 2;
  707.                         else
  708. #endif
  709.                                 fusion_scale = 4;
  710.  
  711.                         auto &player_info = parent->ctype.player_info;
  712.                         const auto Fusion_charge = player_info.Fusion_charge;
  713.                         if (Fusion_charge <= 0)
  714.                                 obj->ctype.laser_info.multiplier = F1_0;
  715.                         else if (Fusion_charge <= F1_0*fusion_scale)
  716.                                 obj->ctype.laser_info.multiplier = F1_0 + Fusion_charge/2;
  717.                         else
  718.                                 obj->ctype.laser_info.multiplier = F1_0*fusion_scale;
  719.  
  720. #if defined(DXX_BUILD_DESCENT_I)
  721.                         //      Fusion damage was boosted by mk on 3/27 (for reg 1.1 release), but we only want it to apply to single player games.
  722.                         if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP))
  723.                                 obj->ctype.laser_info.multiplier /= 2;
  724. #endif
  725.                 }
  726. #if defined(DXX_BUILD_DESCENT_II)
  727.                 else if (!EMULATING_D1 && is_laser_weapon_type(weapon_type) && (parent->ctype.player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS))
  728.                         obj->ctype.laser_info.multiplier = F1_0*3/4;
  729.                 else if (weapon_type == weapon_id_type::GUIDEDMISS_ID) {
  730.                         if (parent==get_local_player().objnum) {
  731.                                 LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(obj, Player_num);
  732.                                 if (Newdemo_state==ND_STATE_RECORDING)
  733.                                         newdemo_record_guided_start();
  734.                         }
  735.                 }
  736. #endif
  737.         }
  738.  
  739.         //      Make children of smart bomb bounce so if they hit a wall right away, they
  740.         //      won't detonate.  The frame interval code will clear this bit after 1/2 second.
  741. #if defined(DXX_BUILD_DESCENT_I)
  742.         if ((weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID))
  743. #elif defined(DXX_BUILD_DESCENT_II)
  744.         if ((weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID) || (weapon_type == weapon_id_type::SMART_MINE_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID) || (weapon_type == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID) || (weapon_type == weapon_id_type::EARTHSHAKER_MEGA_ID))
  745. #endif
  746.                 obj->mtype.phys_info.flags |= PF_BOUNCE;
  747.  
  748.         if (weapon_info.render_type == WEAPON_RENDER_POLYMODEL)
  749.         {
  750.                 auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  751.                 laser_length = Polygon_models[obj->rtype.pobj_info.model_num].rad * 2;
  752.         }
  753.  
  754.         if (weapon_type == weapon_id_type::FLARE_ID)
  755.                 obj->mtype.phys_info.flags |= PF_STICK;         //this obj sticks to walls
  756.  
  757.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  758.         obj->shields = weapon_info.strength[Difficulty_level];
  759.  
  760.         // Fill in laser-specific data
  761.  
  762.         obj->lifeleft                                                   = weapon_info.lifetime;
  763.         obj->ctype.laser_info.parent_type               = parent->type;
  764.         obj->ctype.laser_info.parent_signature = parent->signature;
  765.         obj->ctype.laser_info.parent_num                        = parent;
  766.  
  767.         //      Assign parent type to highest level creator.  This propagates parent type down from
  768.         //      the original creator through weapons which create children of their own (ie, smart missile)
  769.         if (parent->type == OBJ_WEAPON) {
  770.                 auto highest_parent = parent;
  771.                 int     count;
  772.  
  773.                 count = 0;
  774.                 while ((count++ < 10) && (highest_parent->type == OBJ_WEAPON)) {
  775.                         const auto next_parent = highest_parent->ctype.laser_info.parent_num;
  776.                         const auto &&parent_objp = parent.absolute_sibling(next_parent);
  777.                         if (!laser_parent_is_object(highest_parent->ctype.laser_info, parent_objp))
  778.                                 break;  //      Probably means parent was killed.  Just continue.
  779.  
  780.                         if (next_parent == highest_parent) {
  781.                                 Int3(); //      Hmm, object is parent of itself.  This would seem to be bad, no?
  782.                                 break;
  783.                         }
  784.  
  785.                         highest_parent = parent_objp;
  786.  
  787.                         obj->ctype.laser_info.parent_num                        = highest_parent;
  788.                         obj->ctype.laser_info.parent_type = highest_parent->type;
  789.                         obj->ctype.laser_info.parent_signature = highest_parent->signature;
  790.                 }
  791.         }
  792.  
  793.         // Create orientation matrix so we can look from this pov
  794.         //      Homing missiles also need an orientation matrix so they know if they can make a turn.
  795.         if ((weapon_info.homing_flag && (obj->ctype.laser_info.track_goal = object_none, true)) || obj->render_type == RT_POLYOBJ)
  796.                 vm_vector_2_matrix(obj->orient, direction, &parent->orient.uvec, nullptr);
  797.  
  798.         if (( parent != Viewer ) && (parent->type != OBJ_WEAPON))       {
  799.                 // Muzzle flash
  800.                 if (weapon_info.flash_vclip > -1 )
  801.                         object_create_muzzle_flash(segnum.absolute_sibling(obj->segnum), obj->pos, weapon_info.flash_size, weapon_info.flash_vclip);
  802.         }
  803.  
  804.         volume = F1_0;
  805.         if (weapon_info.flash_sound > -1)
  806.         {
  807.                 if (make_sound) {
  808.                         if (parent == Viewer)
  809.                         {
  810.                                 if (weapon_type == weapon_id_type::VULCAN_ID)   // Make your own vulcan gun  1/2 as loud.
  811.                                         volume = F1_0 / 2;
  812.                                 digi_play_sample(weapon_info.flash_sound, volume);
  813.                         } else {
  814.                                 digi_link_sound_to_pos(weapon_info.flash_sound, segnum.absolute_sibling(obj->segnum), 0, obj->pos, 0, volume);
  815.                         }
  816.                 }
  817.         }
  818.  
  819.         //      Fire the laser from the gun tip so that the back end of the laser bolt is at the gun tip.
  820.         // Move 1 frame, so that the end-tip of the laser is touching the gun barrel.
  821.         // This also jitters the laser a bit so that it doesn't alias.
  822.         //      Don't do for weapons created by weapons.
  823. #if defined(DXX_BUILD_DESCENT_I)
  824.         if (parent->type != OBJ_WEAPON && weapon_info.render_type != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID)
  825. #elif defined(DXX_BUILD_DESCENT_II)
  826.         if (parent->type == OBJ_PLAYER && weapon_info.render_type != WEAPON_RENDER_NONE && weapon_type != weapon_id_type::FLARE_ID)
  827. #endif
  828.         {
  829.                 const auto end_pos = vm_vec_scale_add(obj->pos, direction, (laser_length/2) );
  830.                 const auto &&end_segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, end_pos, Segments.vmptridx(obj->segnum));
  831.                 if (end_segnum != obj->segnum) {
  832.                         if (end_segnum != segment_none) {
  833.                                 obj->pos = end_pos;
  834.                                 obj_relink(vmobjptr, vmsegptr, obj, end_segnum);
  835.                         }
  836.                 } else
  837.                         obj->pos = end_pos;
  838.         }
  839.  
  840.         //      Here's where to fix the problem with objects which are moving backwards imparting higher velocity to their weaponfire.
  841.         //      Find out if moving backwards.
  842.         if (is_proximity_bomb_or_player_smart_mine(weapon_type)) {
  843.                 parent_speed = vm_vec_mag_quick(parent->mtype.phys_info.velocity);
  844.                 if (vm_vec_dot(parent->mtype.phys_info.velocity, parent->orient.fvec) < 0)
  845.                         parent_speed = -parent_speed;
  846.         } else
  847.                 parent_speed = 0;
  848.  
  849.         weapon_speed = weapon_info.speed[Difficulty_level];
  850. #if defined(DXX_BUILD_DESCENT_II)
  851.         if (weapon_info.speedvar != 128)
  852.         {
  853.                 fix     randval;
  854.  
  855.                 //      Get a scale factor between speedvar% and 1.0.
  856.                 randval = F1_0 - ((d_rand() * weapon_info.speedvar) >> 6);
  857.                 weapon_speed = fixmul(weapon_speed, randval);
  858.         }
  859. #endif
  860.  
  861.         //      Ugly hack (too bad we're on a deadline), for homing missiles dropped by smart bomb, start them out slower.
  862. #if defined(DXX_BUILD_DESCENT_I)
  863.         if (weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID)
  864. #elif defined(DXX_BUILD_DESCENT_II)
  865.         if (weapon_type == weapon_id_type::PLAYER_SMART_HOMING_ID || weapon_type == weapon_id_type::SMART_MINE_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_HOMING_ID || weapon_type == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID || weapon_type == weapon_id_type::EARTHSHAKER_MEGA_ID)
  866. #endif
  867.                 weapon_speed /= 4;
  868.  
  869.         if (weapon_info.thrust)
  870.                 weapon_speed /= 2;
  871.  
  872.         vm_vec_copy_scale(obj->mtype.phys_info.velocity, direction, weapon_speed + parent_speed );
  873.  
  874.         //      Set thrust
  875.         if (weapon_info.thrust)
  876.         {
  877.                 obj->mtype.phys_info.thrust = obj->mtype.phys_info.velocity;
  878.                 vm_vec_scale(obj->mtype.phys_info.thrust, fixdiv(weapon_info.thrust, weapon_speed+parent_speed));
  879.         }
  880.  
  881.         if (obj->type == OBJ_WEAPON && weapon_type == weapon_id_type::FLARE_ID)
  882.                 obj->lifeleft += (d_rand()-16384) << 2;         //      add in -2..2 seconds
  883.  
  884.         return obj;
  885. }
  886.  
  887. //      -----------------------------------------------------------------------------------------------------------
  888. //      Calls Laser_create_new, but takes care of the segment and point computation for you.
  889. imobjptridx_t Laser_create_new_easy(const vms_vector &direction, const vms_vector &position, const vmobjptridx_t parent, weapon_id_type weapon_type, int make_sound )
  890. {
  891.         fvi_query       fq;
  892.         fvi_info                hit_data;
  893.         int                     fate;
  894.  
  895.         //      Find segment containing laser fire position.  If the robot is straddling a segment, the position from
  896.         //      which it fires may be in a different segment, which is bad news for find_vector_intersection.  So, cast
  897.         //      a ray from the object center (whose segment we know) to the laser position.  Then, in the call to Laser_create_new
  898.         //      use the data returned from this call to find_vector_intersection.
  899.         //      Note that while find_vector_intersection is pretty slow, it is not terribly slow if the destination point is
  900.         //      in the same segment as the source point.
  901.  
  902.         fq.p0                                           = &parent->pos;
  903.         fq.startseg                             = parent->segnum;
  904.         fq.p1                                           = &position;
  905.         fq.rad                                  = 0;
  906.         fq.thisobjnum                   = parent;
  907.         fq.ignore_obj_list.first = nullptr;
  908.         fq.flags                                        = FQ_TRANSWALL | FQ_CHECK_OBJS;         //what about trans walls???
  909.  
  910.         fate = find_vector_intersection(fq, hit_data);
  911.         if (fate != HIT_NONE  || hit_data.hit_seg==segment_none) {
  912.                 return object_none;
  913.         }
  914.  
  915.         return Laser_create_new(direction, hit_data.hit_pnt, vmsegptridx(hit_data.hit_seg), parent, weapon_type, make_sound);
  916. }
  917.  
  918. }
  919.  
  920. namespace dcx {
  921.  
  922. std::array<muzzle_info, MUZZLE_QUEUE_MAX> Muzzle_data;
  923.  
  924. static fix get_weapon_energy_usage_with_difficulty(const weapon_info &wi, const Difficulty_level_type Difficulty_level)
  925. {
  926.         const auto energy_usage = wi.energy_usage;
  927.         if (Difficulty_level < 2)
  928.                 return fixmul(energy_usage, i2f(Difficulty_level + 2) / 4);
  929.         return energy_usage;
  930. }
  931.  
  932. }
  933.  
  934. namespace d1x {
  935.  
  936. static fix get_scaled_min_trackable_dot()
  937. {
  938.         const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME;
  939.         if (curFT <= F1_0 / 16)
  940.                 return (3 * (F1_0 - HOMING_MIN_TRACKABLE_DOT) / 4 + HOMING_MIN_TRACKABLE_DOT);
  941.         else if (curFT < F1_0 / 4)
  942.                 return (fixmul(F1_0 - HOMING_MIN_TRACKABLE_DOT, F1_0 - 4 * curFT) + HOMING_MIN_TRACKABLE_DOT);
  943.         else
  944.                 return (HOMING_MIN_TRACKABLE_DOT);
  945. }
  946.  
  947. }
  948.  
  949. namespace dsx {
  950.  
  951. //      -----------------------------------------------------------------------------------------------------------
  952. //      Determine if two objects are on a line of sight.  If so, return true, else return false.
  953. //      Calls fvi.
  954. int object_to_object_visibility(const vcobjptridx_t obj1, const object_base &obj2, int trans_type)
  955. {
  956.         fvi_query       fq;
  957.         fvi_info                hit_data;
  958.  
  959.         fq.p0                                           = &obj1->pos;
  960.         fq.startseg                             = obj1->segnum;
  961.         fq.p1                                           = &obj2.pos;
  962.         fq.rad                                  = 0x10;
  963.         fq.thisobjnum                   = obj1;
  964.         fq.ignore_obj_list.first = nullptr;
  965.         fq.flags                                        = trans_type;
  966.  
  967.         switch(const auto fate = find_vector_intersection(fq, hit_data))
  968.         {
  969.                 case HIT_NONE:
  970.                         return 1;
  971.                 case HIT_WALL:
  972.                         return 0;
  973.                 default:
  974.                         con_printf(CON_VERBOSE, "object_to_object_visibility: fate=%u for object %hu{%hu/%i,%i,%i} to {%i,%i,%i}", fate, static_cast<vcobjptridx_t::integral_type>(obj1), obj1->segnum, obj1->pos.x, obj1->pos.y, obj1->pos.z, obj2.pos.x, obj2.pos.y, obj2.pos.z);
  975.                 // Int3();              //      Contact Mike: Oops, what happened?  What is fate?
  976.                                                 // 2 = hit object (impossible), 3 = bad starting point (bad)
  977.                         break;
  978.         }
  979.         return 0;
  980. }
  981.  
  982. #if defined(DXX_BUILD_DESCENT_II)
  983. static fix get_scaled_min_trackable_dot()
  984. {
  985.         if (EMULATING_D1)
  986.                 return ::d1x::get_scaled_min_trackable_dot();
  987.         const fix curFT = HOMING_TRACKABLE_DOT_FRAME_TIME;
  988.         if (curFT <= F1_0/64)
  989.                 return (HOMING_MIN_TRACKABLE_DOT);
  990.         else if (curFT < F1_0/32)
  991.                 return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - 2*curFT);
  992.         else if (curFT < F1_0/4)
  993.                 return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/16 - curFT);
  994.         else
  995.                 return (HOMING_MIN_TRACKABLE_DOT + F1_0/64 - F1_0/8);
  996. }
  997. #endif
  998.  
  999. //      -----------------------------------------------------------------------------------------------------------
  1000. //      Return true if weapon *tracker is able to track object Objects[track_goal], else return false.
  1001. //      In order for the object to be trackable, it must be within a reasonable turning radius for the missile
  1002. //      and it must not be obstructed by a wall.
  1003. static int object_is_trackable(const imobjptridx_t objp, const vmobjptridx_t tracker, fix *dot)
  1004. {
  1005.         if (objp == object_none)
  1006.                 return 0;
  1007.         if (Game_mode & GM_MULTI_COOP)
  1008.                 return 0;
  1009.         //      Don't track player if he's cloaked.
  1010.         if ((objp == get_local_player().objnum) && (objp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED))
  1011.                 return 0;
  1012. #if defined(DXX_BUILD_DESCENT_II)
  1013.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1014. #endif
  1015.  
  1016.         //      Can't track AI object if he's cloaked.
  1017.         if (objp->type == OBJ_ROBOT) {
  1018.                 if (objp->ctype.ai_info.CLOAKED)
  1019.                         return 0;
  1020. #if defined(DXX_BUILD_DESCENT_II)
  1021.                 //      Your missiles don't track your escort.
  1022.                 if (Robot_info[get_robot_id(objp)].companion)
  1023.                         if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER)
  1024.                                 return 0;
  1025. #endif
  1026.         }
  1027.         auto vector_to_goal = vm_vec_normalized_quick(vm_vec_sub(objp->pos, tracker->pos));
  1028.         *dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec);
  1029.  
  1030. #if defined(DXX_BUILD_DESCENT_II)
  1031.         if ((*dot < get_scaled_min_trackable_dot()) && (*dot > F1_0*9/10)) {
  1032.                 vm_vec_normalize(vector_to_goal);
  1033.                 *dot = vm_vec_dot(vector_to_goal, tracker->orient.fvec);
  1034.         }
  1035. #endif
  1036.  
  1037.         if (*dot >= get_scaled_min_trackable_dot()) {
  1038.                 int     rval;
  1039.                 //      dot is in legal range, now see if object is visible
  1040.                 rval =  object_to_object_visibility(tracker, objp, FQ_TRANSWALL);
  1041.                 return rval;
  1042.         } else {
  1043.                 return 0;
  1044.         }
  1045. }
  1046.  
  1047. //      --------------------------------------------------------------------------------------------
  1048. static imobjptridx_t call_find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker)
  1049. {
  1050.         if (Game_mode & GM_MULTI) {
  1051.                 if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER) {
  1052.                         //      It's fired by a player, so if robots present, track robot, else track player.
  1053.                         if (Game_mode & GM_MULTI_COOP)
  1054.                                 return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1);
  1055.                         else
  1056.                                 return find_homing_object_complete( curpos, tracker, OBJ_PLAYER, OBJ_ROBOT);
  1057.                 } else {
  1058.                         int     goal2_type = -1;
  1059. #if defined(DXX_BUILD_DESCENT_II)
  1060.                         if (cheats.robotskillrobots)
  1061.                                 goal2_type = OBJ_ROBOT;
  1062. #endif
  1063.                         Assert(tracker->ctype.laser_info.parent_type == OBJ_ROBOT);
  1064.                         return find_homing_object_complete(curpos, tracker, OBJ_PLAYER, goal2_type);
  1065.                 }
  1066.         } else
  1067.                 return find_homing_object_complete( curpos, tracker, OBJ_ROBOT, -1);
  1068. }
  1069.  
  1070. //      --------------------------------------------------------------------------------------------
  1071. //      Find object to home in on.
  1072. //      Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance.
  1073. static imobjptridx_t find_homing_object(const vms_vector &curpos, const vmobjptridx_t tracker)
  1074. {
  1075.         //      Contact Mike: This is a bad and stupid thing.  Who called this routine with an illegal laser type??
  1076. #ifndef NDEBUG
  1077.         const auto tracker_id = get_weapon_id(tracker);
  1078. #if defined(DXX_BUILD_DESCENT_II)
  1079.         if (tracker_id != weapon_id_type::OMEGA_ID)
  1080. #endif
  1081.                 assert(Weapon_info[tracker_id].homing_flag);
  1082. #endif
  1083.  
  1084.         //      Find an object to track based on game mode (eg, whether in network play) and who fired it.
  1085.  
  1086.                 return call_find_homing_object_complete(curpos, tracker);
  1087. }
  1088.  
  1089. //      --------------------------------------------------------------------------------------------
  1090. //      Find object to home in on.
  1091. //      Scan list of objects rendered last frame, find one that satisfies function of nearness to center and distance.
  1092. //      Can track two kinds of objects.  If you are only interested in one type, set track_obj_type2 to NULL
  1093. //      Always track proximity bombs.  --MK, 06/14/95
  1094. //      Make homing objects not track parent's prox bombs.
  1095. imobjptridx_t find_homing_object_complete(const vms_vector &curpos, const vmobjptridx_t tracker, int track_obj_type1, int track_obj_type2)
  1096. {
  1097.         auto &Objects = LevelUniqueObjectState.Objects;
  1098.         auto &vcobjptr = Objects.vcptr;
  1099.         auto &vmobjptridx = Objects.vmptridx;
  1100.         fix     max_dot = -F1_0*2;
  1101.  
  1102.         const auto tracker_id = get_weapon_id(tracker);
  1103. #if defined(DXX_BUILD_DESCENT_II)
  1104.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1105.         if (tracker_id != weapon_id_type::OMEGA_ID)
  1106.         //      Contact Mike: This is a bad and stupid thing.  Who called this routine with an illegal laser type??
  1107. #endif
  1108.         {
  1109.                 if (!Weapon_info[tracker_id].homing_flag)
  1110.                         throw std::logic_error("tracking without homing_flag");
  1111.         }
  1112.  
  1113.         const fix64 HOMING_MAX_TRACKABLE_DIST = F1_0*250;
  1114.         vm_distance_squared max_trackable_dist{HOMING_MAX_TRACKABLE_DIST * HOMING_MAX_TRACKABLE_DIST};
  1115.         fix min_trackable_dot = HOMING_MIN_TRACKABLE_DOT;
  1116.  
  1117. #if defined(DXX_BUILD_DESCENT_II)
  1118.         if (tracker_id == weapon_id_type::OMEGA_ID)
  1119.         {
  1120.                 max_trackable_dist = OMEGA_MAX_TRACKABLE_DIST * OMEGA_MAX_TRACKABLE_DIST;
  1121.                 min_trackable_dot = OMEGA_MIN_TRACKABLE_DOT;
  1122.         }
  1123. #endif
  1124.  
  1125.         imobjptridx_t   best_objnum = object_none;
  1126.         range_for (const auto &&curobjp, vmobjptridx)
  1127.         {
  1128.                 int                     is_proximity = 0;
  1129.                 fix                     dot;
  1130.  
  1131.                 if ((curobjp->type != track_obj_type1) && (curobjp->type != track_obj_type2))
  1132.                 {
  1133. #if defined(DXX_BUILD_DESCENT_II)
  1134.                         if ((curobjp->type == OBJ_WEAPON) && (is_proximity_bomb_or_player_smart_mine(get_weapon_id(curobjp)))) {
  1135.                                 auto &cur_laser_info = curobjp->ctype.laser_info;
  1136.                                 auto &tracker_laser_info = tracker->ctype.laser_info;
  1137.                                 if (cur_laser_info.parent_num != tracker_laser_info.parent_num || cur_laser_info.parent_signature != tracker_laser_info.parent_signature)
  1138.                                         is_proximity = 1;
  1139.                                 else
  1140.                                         continue;
  1141.                         } else
  1142. #endif
  1143.                                 continue;
  1144.                 }
  1145.  
  1146.                 if (curobjp == tracker->ctype.laser_info.parent_num) // Don't track shooter
  1147.                         continue;
  1148.  
  1149.                 //      Don't track cloaked players.
  1150.                 if (curobjp->type == OBJ_PLAYER)
  1151.                 {
  1152.                         if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
  1153.                                 continue;
  1154.                         // Don't track teammates in team games
  1155.                         if (Game_mode & GM_TEAM)
  1156.                         {
  1157.                                 const auto &&objparent = vcobjptr(tracker->ctype.laser_info.parent_num);
  1158.                                 if (objparent->type == OBJ_PLAYER && get_team(get_player_id(curobjp)) == get_team(get_player_id(objparent)))
  1159.                                         continue;
  1160.                         }
  1161.                 }
  1162.  
  1163.                 //      Can't track AI object if he's cloaked.
  1164.                 if (curobjp->type == OBJ_ROBOT) {
  1165.                         if (curobjp->ctype.ai_info.CLOAKED)
  1166.                                 continue;
  1167.  
  1168. #if defined(DXX_BUILD_DESCENT_II)
  1169.                         //      Your missiles don't track your escort.
  1170.                         if (Robot_info[get_robot_id(curobjp)].companion)
  1171.                                 if (tracker->ctype.laser_info.parent_type == OBJ_PLAYER)
  1172.                                         continue;
  1173. #endif
  1174.                 }
  1175.  
  1176.                 auto vec_to_curobj = vm_vec_sub(curobjp->pos, curpos);
  1177.                 auto dist = vm_vec_mag2(vec_to_curobj);
  1178.  
  1179.                 if (dist < max_trackable_dist) {
  1180.                         vm_vec_normalize(vec_to_curobj);
  1181.                         dot = vm_vec_dot(vec_to_curobj, tracker->orient.fvec);
  1182.                         if (is_proximity)
  1183.                                 dot = ((dot << 3) + dot) >> 3;          //      I suspect Watcom would be too stupid to figure out the obvious...
  1184.  
  1185.                         if (dot > min_trackable_dot) {
  1186.                                 if (dot > max_dot) {
  1187.                                         if (object_to_object_visibility(tracker, curobjp, FQ_TRANSWALL)) {
  1188.                                                 max_dot = dot;
  1189.                                                 best_objnum = curobjp;
  1190.                                         }
  1191.                                 }
  1192.                         }
  1193.                 }
  1194.  
  1195.         }
  1196.         return best_objnum;
  1197. }
  1198.  
  1199. #ifdef NEWHOMER
  1200. // Similar to calc_d_tick but made just for the homers.
  1201. // Causes d_homer_tick_step to be true in intervals dictated by HOMING_TURN_TIME
  1202. // and increments d_homer_tick_count accordingly
  1203. void calc_d_homer_tick()
  1204. {
  1205.         auto &Objects = LevelUniqueObjectState.Objects;
  1206.         auto &vmobjptr = Objects.vmptr;
  1207.         static fix timer = 0;
  1208.         auto t = timer + FrameTime;
  1209.         d_homer_tick_step = t >= HOMING_TURN_TIME;
  1210.         if (d_homer_tick_step)
  1211.         {
  1212.                 d_homer_tick_count++;
  1213.                 if (d_homer_tick_count > F1_0)
  1214.                         d_homer_tick_count = 0;
  1215.                 t -= HOMING_TURN_TIME;
  1216.         // Don't let slowdowns have a lasting impact; allow you to build up at most 3 frames worth
  1217.                 if (t > HOMING_TURN_TIME*3)
  1218.                         t = HOMING_TURN_TIME*3;
  1219.  
  1220.                 get_local_plrobj().ctype.player_info.homing_object_dist = -1; // Assume not being tracked.  Laser_do_weapon_sequence modifies this. Let's do this here since the homers do not track every frame, we may not want to reset this ever frame.
  1221.         }
  1222.         timer = t;
  1223. }
  1224. #endif
  1225.  
  1226. //      ------------------------------------------------------------------------------------------------------------
  1227. //      See if legal to keep tracking currently tracked object.  If not, see if another object is trackable.  If not, return -1,
  1228. //      else return object number of tracking object.
  1229. //      Computes and returns a fairly precise dot product.
  1230. static imobjptridx_t track_track_goal(fvcobjptr &vcobjptr, const imobjptridx_t track_goal, const vmobjptridx_t tracker, fix *dot, fix tick_count)
  1231. {
  1232. #if defined(DXX_BUILD_DESCENT_I)
  1233.         if (object_is_trackable(track_goal, tracker, dot))
  1234. #elif defined(DXX_BUILD_DESCENT_II)
  1235.         //      Every 8 frames for each object, scan all objects.
  1236.         if (object_is_trackable(track_goal, tracker, dot) && (((tracker ^ tick_count) % 8) != 0))
  1237. #endif
  1238.         {
  1239.                 return track_goal;
  1240.         } else if (((tracker ^ tick_count) % 4) == 0)
  1241.         {
  1242.                 int     goal_type, goal2_type;
  1243.                 //      If player fired missile, then search for an object, if not, then give up.
  1244.                 if (vcobjptr(tracker->ctype.laser_info.parent_num)->type == OBJ_PLAYER)
  1245.                 {
  1246.  
  1247.                         if (track_goal == object_none)
  1248.                         {
  1249.                                 if (Game_mode & GM_MULTI)
  1250.                                 {
  1251.                                         if (Game_mode & GM_MULTI_COOP)
  1252.                                                 goal_type = OBJ_ROBOT, goal2_type = -1;
  1253.                                         else
  1254.                                         {
  1255.                                                 goal_type = OBJ_PLAYER;
  1256.                                                 goal2_type = (Game_mode & GM_MULTI_ROBOTS)
  1257.                                                         ? OBJ_ROBOT     //      Not cooperative, if robots, track either robot or player
  1258.                                                         : -1;           //      Not cooperative and no robots, track only a player
  1259.                                         }
  1260.                                 }
  1261.                                 else
  1262.                                         goal_type = OBJ_PLAYER, goal2_type = OBJ_ROBOT;
  1263.                         }
  1264.                         else
  1265.                         {
  1266.                                 goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type;
  1267.                                 if ((goal_type == OBJ_PLAYER) || (goal_type == OBJ_ROBOT))
  1268.                                         goal2_type = -1;
  1269.                                 else
  1270.                                         return object_none;
  1271.                         }
  1272.                 }
  1273.                 else {
  1274.                         goal2_type = -1;
  1275.  
  1276. #if defined(DXX_BUILD_DESCENT_II)
  1277.                         if (cheats.robotskillrobots)
  1278.                                 goal2_type = OBJ_ROBOT;
  1279. #endif
  1280.  
  1281.                         if (track_goal == object_none)
  1282.                                 goal_type = OBJ_PLAYER;
  1283.                         else {
  1284.                                 goal_type = vcobjptr(tracker->ctype.laser_info.track_goal)->type;
  1285.                                 assert(goal_type != OBJ_GHOST);
  1286.                         }
  1287.                 }
  1288.                 return find_homing_object_complete(tracker->pos, tracker, goal_type, goal2_type);
  1289.         }
  1290.  
  1291.         return object_none;
  1292. }
  1293.  
  1294. //-------------- Initializes a laser after Fire is pressed -----------------
  1295.  
  1296. static imobjptridx_t Laser_player_fire_spread_delay(fvmsegptridx &vmsegptridx, const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const fix spreadr, const fix spreadu, const fix delay_time, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track)
  1297. {
  1298.         int                     Fate;
  1299.         vms_vector      LaserDir;
  1300.         fvi_query       fq;
  1301.         fvi_info                hit_data;
  1302.         vms_vector      *pnt;
  1303.  
  1304. #if defined(DXX_BUILD_DESCENT_II)
  1305.         create_awareness_event(obj, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState);
  1306. #endif
  1307.  
  1308.         // Find the initial position of the laser
  1309.         pnt = &Player_ship->gun_points[gun_num];
  1310.  
  1311.         vms_matrix m = vm_transposed_matrix(obj->orient);
  1312.         const auto gun_point = vm_vec_rotate(*pnt,m);
  1313.  
  1314.         auto LaserPos = vm_vec_add(obj->pos,gun_point);
  1315.  
  1316.         //      If supposed to fire at a delayed time (delay_time), then move this point backwards.
  1317.         if (delay_time)
  1318.                 vm_vec_scale_add2(LaserPos, shot_orientation, -fixmul(delay_time, Weapon_info[laser_type].speed[GameUniqueState.Difficulty_level]));
  1319.  
  1320. //      do_muzzle_stuff(obj, &Pos);
  1321.  
  1322.         //--------------- Find LaserPos and LaserSeg ------------------
  1323.         fq.p0                                           = &obj->pos;
  1324.         fq.startseg                             = obj->segnum;
  1325.         fq.p1                                           = &LaserPos;
  1326.         fq.rad                                  = 0x10;
  1327.         fq.thisobjnum                   = obj;
  1328.         fq.ignore_obj_list.first = nullptr;
  1329. #if defined(DXX_BUILD_DESCENT_I)
  1330.         fq.flags                                        = FQ_CHECK_OBJS;
  1331. #elif defined(DXX_BUILD_DESCENT_II)
  1332.         fq.flags                                        = FQ_CHECK_OBJS | FQ_IGNORE_POWERUPS;
  1333. #endif
  1334.  
  1335.         Fate = find_vector_intersection(fq, hit_data);
  1336.  
  1337.         auto LaserSeg = hit_data.hit_seg;
  1338.  
  1339.         if (LaserSeg == segment_none)           //some sort of annoying error
  1340.                 return object_none;
  1341.  
  1342.         //SORT OF HACK... IF ABOVE WAS CORRECT THIS WOULDNT BE NECESSARY.
  1343.         if ( vm_vec_dist_quick(LaserPos, obj->pos) > 0x50000 )
  1344.                 return object_none;
  1345.  
  1346.         if (Fate==HIT_WALL) {
  1347.                 return object_none;
  1348.         }
  1349.  
  1350.         if (Fate==HIT_OBJECT) {
  1351. //              if ( Objects[hit_data.hit_object].type == OBJ_ROBOT )
  1352. //                      Objects[hit_data.hit_object].flags |= OF_SHOULD_BE_DEAD;
  1353. //              if ( Objects[hit_data.hit_object].type != OBJ_POWERUP )
  1354. //                      return;
  1355.         //as of 12/6/94, we don't care if the laser is stuck in an object. We
  1356.         //just fire away normally
  1357.         }
  1358.  
  1359.         //      Now, make laser spread out.
  1360.         LaserDir = shot_orientation;
  1361.         if ((spreadr != 0) || (spreadu != 0)) {
  1362.                 vm_vec_scale_add2(LaserDir, obj->orient.rvec, spreadr);
  1363.                 vm_vec_scale_add2(LaserDir, obj->orient.uvec, spreadu);
  1364.         }
  1365.  
  1366.         const auto &&objnum = Laser_create_new(LaserDir, LaserPos, vmsegptridx(LaserSeg), obj, laser_type, make_sound);
  1367.  
  1368.         if (objnum == object_none)
  1369.                 return object_none;
  1370.  
  1371. #if defined(DXX_BUILD_DESCENT_II)
  1372.         //      Omega cannon is a hack, not surprisingly.  Don't want to do the rest of this stuff.
  1373.         if (laser_type == weapon_id_type::OMEGA_ID)
  1374.                 return objnum;
  1375.  
  1376.         if (laser_type == weapon_id_type::GUIDEDMISS_ID && Multi_is_guided) {
  1377.                 LevelUniqueObjectState.Guided_missile.set_player_active_guided_missile(objnum, get_player_id(obj));
  1378.         }
  1379.  
  1380.         Multi_is_guided=0;
  1381.  
  1382.         if (laser_type == weapon_id_type::CONCUSSION_ID ||
  1383.                          laser_type == weapon_id_type::HOMING_ID ||
  1384.                          laser_type == weapon_id_type::SMART_ID ||
  1385.                          laser_type == weapon_id_type::MEGA_ID ||
  1386.                          laser_type == weapon_id_type::FLASH_ID ||
  1387.                          //laser_type == GUIDEDMISS_ID ||
  1388.                          //laser_type == SUPERPROX_ID ||
  1389.                          laser_type == weapon_id_type::MERCURY_ID ||
  1390.                          laser_type == weapon_id_type::EARTHSHAKER_ID)
  1391.         {
  1392.                 const auto need_new_missile_viewer = [obj]{
  1393.                         if (!Missile_viewer)
  1394.                                 return true;
  1395.                         if (Missile_viewer->type != OBJ_WEAPON)
  1396.                                 return true;
  1397.                         if (Missile_viewer->signature != Missile_viewer_sig)
  1398.                                 return true;
  1399.                         if (get_player_id(obj) == Player_num && Missile_viewer->ctype.laser_info.parent_num != get_local_player().objnum)
  1400.                                 /* New missile fired-by local player &&
  1401.                                  * currently viewing missile not-fired-by local player
  1402.                                  */
  1403.                                 return true;
  1404.                         return false;
  1405.                 };
  1406.                 const auto can_view_missile = [obj]{
  1407.                         const auto obj_id = get_player_id(obj);
  1408.                         if (obj_id == Player_num)
  1409.                                 return true;
  1410.                         if (PlayerCfg.MissileViewEnabled != MissileViewMode::EnabledSelfAndAllies)
  1411.                                 return false;
  1412.                         {
  1413.                                 if (Game_mode & GM_MULTI_COOP)
  1414.                                         return true;
  1415.                                 if (Game_mode & GM_TEAM)
  1416.                                         return get_team(Player_num) == get_team(obj_id);
  1417.                         }
  1418.                         return false;
  1419.                 };
  1420.                 if (need_new_missile_viewer() && can_view_missile())
  1421.                 {
  1422.                         Missile_viewer = objnum;
  1423.                         Missile_viewer_sig = objnum->signature;
  1424.                 }
  1425.         }
  1426. #endif
  1427.  
  1428.         //      If this weapon is supposed to be silent, set that bit!
  1429.         if (!make_sound)
  1430.                 objnum->flags |= OF_SILENT;
  1431.  
  1432.         //      If the object firing the laser is the player, then indicate the laser object so robots can dodge.
  1433.         //      New by MK on 6/8/95, don't let robots evade proximity bombs, thereby decreasing uselessness of bombs.
  1434.         if (obj == ConsoleObject)
  1435. #if defined(DXX_BUILD_DESCENT_II)
  1436.                 if (!is_proximity_bomb_or_player_smart_mine(get_weapon_id(objnum)))
  1437. #endif
  1438.                 Player_fired_laser_this_frame = objnum;
  1439.  
  1440.         if (Weapon_info[laser_type].homing_flag) {
  1441.                 if (obj == ConsoleObject)
  1442.                 {
  1443.                         objnum->ctype.laser_info.track_goal = find_homing_object(LaserPos, objnum);
  1444.                 }
  1445.                 else // Some other player shot the homing thing
  1446.                 {
  1447.                         Assert(Game_mode & GM_MULTI);
  1448.                         objnum->ctype.laser_info.track_goal = Network_laser_track;
  1449.                 }
  1450.         }
  1451.  
  1452.         return objnum;
  1453. }
  1454.  
  1455. //      -----------------------------------------------------------------------------------------------------------
  1456. static imobjptridx_t Laser_player_fire_spread(const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const fix spreadr, const fix spreadu, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track)
  1457. {
  1458.         return Laser_player_fire_spread_delay(vmsegptridx, obj, laser_type, gun_num, spreadr, spreadu, 0, make_sound, shot_orientation, Network_laser_track);
  1459. }
  1460.  
  1461.  
  1462. //      -----------------------------------------------------------------------------------------------------------
  1463. imobjptridx_t Laser_player_fire(const vmobjptridx_t obj, const weapon_id_type laser_type, const int gun_num, const int make_sound, const vms_vector &shot_orientation, const icobjidx_t Network_laser_track)
  1464. {
  1465.         return Laser_player_fire_spread(obj, laser_type, gun_num, 0, 0, make_sound, shot_orientation, Network_laser_track);
  1466. }
  1467.  
  1468. //      -----------------------------------------------------------------------------------------------------------
  1469. void Flare_create(const vmobjptridx_t obj)
  1470. {
  1471.         auto &Objects = LevelUniqueObjectState.Objects;
  1472.         auto &vmobjptr = Objects.vmptr;
  1473.  
  1474.         const auto energy_usage = get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_id_type::FLARE_ID], GameUniqueState.Difficulty_level);
  1475.  
  1476. //      MK, 11/04/95: Allowed to fire flare even if no energy.
  1477. // --   if (Players[Player_num].energy >= energy_usage)
  1478.         auto &player_info = get_local_plrobj().ctype.player_info;
  1479.         auto &energy = player_info.energy;
  1480. #if defined(DXX_BUILD_DESCENT_I)
  1481.         if (energy > 0)
  1482. #endif
  1483.         {
  1484.                 const auto &&flare = Laser_player_fire(obj, weapon_id_type::FLARE_ID, 6, 1, get_local_plrobj().orient.fvec, object_none);
  1485.                 if (flare == object_none)
  1486.                         return;
  1487.                 energy -= energy_usage;
  1488.  
  1489.                 if (energy <= 0)
  1490.                 {
  1491.                         energy = 0;
  1492. #if defined(DXX_BUILD_DESCENT_I)
  1493.                         auto_select_primary_weapon(player_info);
  1494. #endif
  1495.                 }
  1496.  
  1497.                 if (Game_mode & GM_MULTI)
  1498.                         multi_send_fire(FLARE_ADJUST, 0, 0, 1, object_none, object_none);
  1499.         }
  1500.  
  1501. }
  1502.  
  1503. #if defined(DXX_BUILD_DESCENT_I)
  1504. #define HOMING_MISSILE_SCALE    8
  1505. #elif defined(DXX_BUILD_DESCENT_II)
  1506. #define HOMING_MISSILE_SCALE    16
  1507.  
  1508. static bool is_active_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const vcobjptridx_t obj)
  1509. {
  1510.         if (obj->ctype.laser_info.parent_type != OBJ_PLAYER)
  1511.                 return false;
  1512.         auto &vcobjptr = LevelUniqueObjectState.get_objects().vcptr;
  1513.         auto &parent_obj = *vcobjptr(obj->ctype.laser_info.parent_num);
  1514.         if (parent_obj.type != OBJ_PLAYER)
  1515.                 return false;
  1516.         const auto pnum = get_player_id(parent_obj);
  1517.         return LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(pnum) == obj;
  1518. }
  1519. #endif
  1520.  
  1521. //--------------------------------------------------------------------
  1522. //      Set object *objp's orientation to (or towards if I'm ambitious) its velocity.
  1523. static void homing_missile_turn_towards_velocity(object_base &objp, const vms_vector &norm_vel, fix ft)
  1524. {
  1525.         auto new_fvec = norm_vel;
  1526.         vm_vec_scale(new_fvec, ft * HOMING_MISSILE_SCALE);
  1527.         vm_vec_add2(new_fvec, objp.orient.fvec);
  1528.         vm_vec_normalize_quick(new_fvec);
  1529.  
  1530. //      if ((norm_vel->x == 0) && (norm_vel->y == 0) && (norm_vel->z == 0))
  1531. //              return;
  1532.  
  1533.         vm_vector_2_matrix(objp.orient, new_fvec, nullptr, nullptr);
  1534. }
  1535.  
  1536.  
  1537. //-------------------------------------------------------------------------------------------
  1538. //sequence this laser object for this _frame_ (underscores added here to aid MK in his searching!)
  1539. void Laser_do_weapon_sequence(const vmobjptridx_t obj)
  1540. {
  1541.         auto &Objects = LevelUniqueObjectState.Objects;
  1542.         auto &imobjptridx = Objects.imptridx;
  1543.         auto &vcobjptr = Objects.vcptr;
  1544.         auto &vmobjptr = Objects.vmptr;
  1545.         Assert(obj->control_type == CT_WEAPON);
  1546.  
  1547.         if (obj->lifeleft < 0 ) {               // We died of old age
  1548.                 obj->flags |= OF_SHOULD_BE_DEAD;
  1549.                 if ( Weapon_info[get_weapon_id(obj)].damage_radius )
  1550.                         explode_badass_weapon(obj, obj->pos);
  1551.                 return;
  1552.         }
  1553.  
  1554. #if defined(DXX_BUILD_DESCENT_II)
  1555.         if (omega_cleanup(vcobjptr, obj))
  1556.                 return;
  1557. #endif
  1558.  
  1559.         //delete weapons that are not moving
  1560.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1561.         if (    !((d_tick_count ^ obj->signature.get()) & 3) &&
  1562.                         (get_weapon_id(obj) != weapon_id_type::FLARE_ID) &&
  1563.                         (Weapon_info[get_weapon_id(obj)].speed[Difficulty_level] > 0) &&
  1564.                         (vm_vec_mag_quick(obj->mtype.phys_info.velocity) < F2_0)) {
  1565.                 obj_delete(LevelUniqueObjectState, Segments, obj);
  1566.                 return;
  1567.         }
  1568.  
  1569.         if ( get_weapon_id(obj) == weapon_id_type::FUSION_ID ) {                //always set fusion weapon to max vel
  1570.  
  1571.                 vm_vec_normalize_quick(obj->mtype.phys_info.velocity);
  1572.  
  1573.                 vm_vec_scale(obj->mtype.phys_info.velocity, Weapon_info[get_weapon_id(obj)].speed[Difficulty_level]);
  1574.         }
  1575.  
  1576.         //      For homing missiles, turn towards target. (unless it's the guided missile)
  1577. #if defined(DXX_BUILD_DESCENT_I)
  1578.         if (Weapon_info[get_weapon_id(obj)].homing_flag)
  1579. #elif defined(DXX_BUILD_DESCENT_II)
  1580.         if (Weapon_info[get_weapon_id(obj)].homing_flag && !is_active_guided_missile(LevelUniqueObjectState, obj))
  1581. #endif
  1582.         {
  1583.                 vms_vector              vector_to_object, temp_vec;
  1584.                 fix                             dot=F1_0;
  1585.                 fix                             speed, max_speed;
  1586.  
  1587.                 //      For first 125ms of life, missile flies straight.
  1588.                 if (obj->ctype.laser_info.creation_time + HOMING_FLY_STRAIGHT_TIME < GameTime64)
  1589.                 {
  1590.  
  1591.                         //      If it's time to do tracking, then it's time to grow up, stop bouncing and start exploding!.
  1592. #if defined(DXX_BUILD_DESCENT_I)
  1593.                         if ((get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::PLAYER_SMART_HOMING_ID))
  1594. #elif defined(DXX_BUILD_DESCENT_II)
  1595.                         if ((get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_MINE_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::ROBOT_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::SMART_MINE_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::PLAYER_SMART_HOMING_ID) || (get_weapon_id(obj) == weapon_id_type::EARTHSHAKER_MEGA_ID))
  1596. #endif
  1597.                         {
  1598.                                 obj->mtype.phys_info.flags &= ~PF_BOUNCE;
  1599.                         }
  1600.  
  1601. #ifdef NEWHOMER
  1602.                         if (d_homer_tick_step)
  1603.                         {
  1604.                                 const auto &&track_goal = track_track_goal(vcobjptr, imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_homer_tick_count);
  1605.  
  1606.                                 if (track_goal == get_local_player().objnum) {
  1607.                                         fix     dist_to_player;
  1608.  
  1609.                                         dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos);
  1610.                                         auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist;
  1611.                                         if (dist_to_player < homing_object_dist || homing_object_dist < 0)
  1612.                                                 homing_object_dist = dist_to_player;
  1613.                                 }
  1614.  
  1615.                                 if (track_goal != object_none)
  1616.                                 {
  1617.                                         vm_vec_sub(vector_to_object, track_goal->pos, obj->pos);
  1618.                                         vm_vec_normalize_quick(vector_to_object);
  1619.                                         temp_vec = obj->mtype.phys_info.velocity;
  1620.                                         speed = vm_vec_normalize_quick(temp_vec);
  1621.                                         max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level];
  1622.                                         if (speed+F1_0 < max_speed) {
  1623.                                                 speed += fixmul(max_speed, HOMING_TURN_TIME/2);
  1624.                                                 if (speed > max_speed)
  1625.                                                         speed = max_speed;
  1626.                                         }
  1627. #if defined(DXX_BUILD_DESCENT_I)
  1628.                                         dot = vm_vec_dot(temp_vec, vector_to_object);
  1629. #endif
  1630.                                         vm_vec_add2(temp_vec, vector_to_object);
  1631.                                         //      The boss' smart children track better...
  1632.                                         if (Weapon_info[get_weapon_id(obj)].render_type != WEAPON_RENDER_POLYMODEL)
  1633.                                                 vm_vec_add2(temp_vec, vector_to_object);
  1634.                                         vm_vec_normalize_quick(temp_vec);
  1635. #if defined(DXX_BUILD_DESCENT_I)
  1636.                                         vm_vec_scale(temp_vec, speed);
  1637.                                         obj->mtype.phys_info.velocity = temp_vec;
  1638. #elif defined(DXX_BUILD_DESCENT_II)
  1639.                                         obj->mtype.phys_info.velocity = temp_vec;
  1640.                                         vm_vec_scale(obj->mtype.phys_info.velocity, speed);
  1641. #endif
  1642.  
  1643.                                         //      Subtract off life proportional to amount turned.
  1644.                                         //      For hardest turn, it will lose 2 seconds per second.
  1645.                                         {
  1646.                                                 fix     lifelost, absdot;
  1647.  
  1648.                                                 absdot = abs(F1_0 - dot);
  1649. #if defined(DXX_BUILD_DESCENT_I)
  1650.                                                 if (absdot > F1_0/8) {
  1651.                                                         if (absdot > F1_0/4)
  1652.                                                                 absdot = F1_0/4;
  1653.                                                         lifelost = fixmul(absdot*16, HOMING_TURN_TIME);
  1654.                                                         obj->lifeleft -= lifelost;
  1655.                                                 }
  1656. #elif defined(DXX_BUILD_DESCENT_II)
  1657.                                                 lifelost = fixmul(absdot*32, HOMING_TURN_TIME);
  1658.                                                 obj->lifeleft -= lifelost;
  1659. #endif
  1660.                                         }
  1661.  
  1662.                                         //      Only polygon objects have visible orientation, so only they should turn.
  1663.                                         if (Weapon_info[get_weapon_id(obj)].render_type == WEAPON_RENDER_POLYMODEL)
  1664.                                                 homing_missile_turn_towards_velocity(obj, temp_vec, HOMING_TURN_TIME);          //      temp_vec is normalized velocity.
  1665.                                 }
  1666.                         }
  1667. #else // old FPS-dependent homers - NOTE: I know this is very redundant but I want to keep the historical code 100% preserved to compare against potential changes in the above.
  1668.                         //      Make sure the object we are tracking is still trackable.
  1669.                         const auto &&track_goal = track_track_goalvcobjptr, (imobjptridx(obj->ctype.laser_info.track_goal), obj, &dot, d_tick_count);
  1670.  
  1671.                         if (track_goal == get_local_player().objnum) {
  1672.                                 fix     dist_to_player;
  1673.  
  1674.                                 dist_to_player = vm_vec_dist_quick(obj->pos, track_goal->pos);
  1675.                                 auto &homing_object_dist = get_local_plrobj().ctype.player_info.homing_object_dist;
  1676.                                 if (dist_to_player < homing_object_dist || homing_object_dist < 0)
  1677.                                         homing_object_dist = dist_to_player;
  1678.                         }
  1679.  
  1680.                         if (track_goal != object_none)
  1681.                         {
  1682.                                 vm_vec_sub(vector_to_object, track_goal->pos, obj->pos);
  1683.                                 vm_vec_normalize_quick(vector_to_object);
  1684.                                 temp_vec = obj->mtype.phys_info.velocity;
  1685.                                 speed = vm_vec_normalize_quick(temp_vec);
  1686.                                 max_speed = Weapon_info[get_weapon_id(obj)].speed[Difficulty_level];
  1687.                                 if (speed+F1_0 < max_speed) {
  1688.                                         speed += fixmul(max_speed, FrameTime/2);
  1689.                                         if (speed > max_speed)
  1690.                                                 speed = max_speed;
  1691.                                 }
  1692. #if defined(DXX_BUILD_DESCENT_I)
  1693.                                 dot = vm_vec_dot(temp_vec, vector_to_object);
  1694. #endif
  1695.                                 vm_vec_add2(temp_vec, vector_to_object);
  1696.                                 //      The boss' smart children track better...
  1697.                                 if (Weapon_info[get_weapon_id(obj)].render_type != WEAPON_RENDER_POLYMODEL)
  1698.                                         vm_vec_add2(temp_vec, vector_to_object);
  1699.                                 vm_vec_normalize_quick(temp_vec);
  1700. #if defined(DXX_BUILD_DESCENT_I)
  1701.                                 vm_vec_scale(temp_vec, speed);
  1702.                                 obj->mtype.phys_info.velocity = temp_vec;
  1703. #elif defined(DXX_BUILD_DESCENT_II)
  1704.                                 obj->mtype.phys_info.velocity = temp_vec;
  1705.                                 vm_vec_scale(obj->mtype.phys_info.velocity, speed);
  1706. #endif
  1707.  
  1708.                                 //      Subtract off life proportional to amount turned.
  1709.                                 //      For hardest turn, it will lose 2 seconds per second.
  1710.                                 {
  1711.                                         fix     lifelost, absdot;
  1712.  
  1713.                                         absdot = abs(F1_0 - dot);
  1714. #if defined(DXX_BUILD_DESCENT_I)
  1715.                                         if (absdot > F1_0/8) {
  1716.                                                 if (absdot > F1_0/4)
  1717.                                                         absdot = F1_0/4;
  1718.                                                 lifelost = fixmul(absdot*16, FrameTime);
  1719.                                                 obj->lifeleft -= lifelost;
  1720.                                         }
  1721. #elif defined(DXX_BUILD_DESCENT_II)
  1722.                                         lifelost = fixmul(absdot*32, FrameTime);
  1723.                                         obj->lifeleft -= lifelost;
  1724. #endif
  1725.                                 }
  1726.  
  1727.                                 //      Only polygon objects have visible orientation, so only they should turn.
  1728.                                 if (Weapon_info[get_weapon_id(obj)].render_type == WEAPON_RENDER_POLYMODEL)
  1729.                                         homing_missile_turn_towards_velocity(obj, temp_vec, FrameTime);         //      temp_vec is normalized velocity.
  1730.                         }
  1731. #endif
  1732.                 }
  1733.         }
  1734.  
  1735.         //      Make sure weapon is not moving faster than allowed speed.
  1736.         const auto &weapon_info = Weapon_info[get_weapon_id(obj)];
  1737. #if defined(DXX_BUILD_DESCENT_I)
  1738.         if (weapon_info.thrust != 0)
  1739. #endif
  1740.         {
  1741.                 fix     weapon_speed;
  1742.  
  1743.                 weapon_speed = vm_vec_mag_quick(obj->mtype.phys_info.velocity);
  1744.                 if (weapon_speed > weapon_info.speed[Difficulty_level]) {
  1745.                         //      Only slow down if not allowed to move.  Makes sense, huh?  Allows proxbombs to get moved by physics force. --MK, 2/13/96
  1746. #if defined(DXX_BUILD_DESCENT_II)
  1747.                         if (weapon_info.speed[Difficulty_level])
  1748. #endif
  1749.                         {
  1750.                                 fix     scale_factor;
  1751.  
  1752.                                 scale_factor = fixdiv(Weapon_info[get_weapon_id(obj)].speed[Difficulty_level], weapon_speed);
  1753.                                 vm_vec_scale(obj->mtype.phys_info.velocity, scale_factor);
  1754.                         }
  1755.                 }
  1756.         }
  1757. }
  1758.  
  1759. }
  1760.  
  1761. namespace dcx {
  1762.  
  1763. constexpr std::integral_constant<unsigned, 30> MAX_OBJDISTS{};
  1764.  
  1765. static inline int sufficient_energy(int energy_used, fix energy)
  1766. {
  1767.         return !energy_used || (energy >= energy_used);
  1768. }
  1769.  
  1770. static inline int sufficient_ammo(int ammo_used, int uses_vulcan_ammo, ushort vulcan_ammo)
  1771. {
  1772.         return !ammo_used || (!uses_vulcan_ammo || vulcan_ammo >= ammo_used);
  1773. }
  1774.  
  1775. }
  1776.  
  1777. namespace dsx {
  1778.  
  1779. //      --------------------------------------------------------------------------------------------------
  1780. // Assumption: This is only called by the actual console player, not for network players
  1781.  
  1782. void do_laser_firing_player(object &plrobj)
  1783. {
  1784.         auto &Objects = LevelUniqueObjectState.Objects;
  1785.         auto &vmobjptridx = Objects.vmptridx;
  1786.         int             ammo_used;
  1787.         int             rval = 0;
  1788.         int             nfires = 1;
  1789.  
  1790.         if (Player_dead_state != player_dead_state::no)
  1791.                 return;
  1792.  
  1793.         auto &player_info = plrobj.ctype.player_info;
  1794.         const auto Primary_weapon = player_info.Primary_weapon;
  1795.         const auto weapon_index = Primary_weapon_to_weapon_info[Primary_weapon];
  1796.  
  1797.         ammo_used = Weapon_info[weapon_index].ammo_usage;
  1798.  
  1799.         const auto uses_vulcan_ammo = weapon_index_uses_vulcan_ammo(Primary_weapon);
  1800.  
  1801.         auto &pl_energy = player_info.energy;
  1802.         const auto base_energy_used =
  1803. #if defined(DXX_BUILD_DESCENT_II)
  1804.                 (Primary_weapon == primary_weapon_index_t::OMEGA_INDEX)
  1805.                 ? 0     //      Omega consumes energy when recharging, not when firing.
  1806.                 :
  1807. #endif
  1808.                 get_weapon_energy_usage_with_difficulty(Weapon_info[weapon_index], GameUniqueState.Difficulty_level);
  1809.  
  1810.         const auto energy_used =
  1811. #if defined(DXX_BUILD_DESCENT_II)
  1812.         //      MK, 01/26/96, Helix use 2x energy in multiplayer.  bitmaps.tbl parm should have been reduced for single player.
  1813.                 (weapon_index == weapon_id_type::HELIX_ID && (Game_mode & GM_MULTI))
  1814.                 ? base_energy_used * 2
  1815.                 :
  1816. #endif
  1817.                 base_energy_used;
  1818.  
  1819.         if      (!(sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo)))
  1820.                 auto_select_primary_weapon(player_info);                //      Make sure the player can fire from this weapon.
  1821.  
  1822.         auto &Next_laser_fire_time = player_info.Next_laser_fire_time;
  1823.         while (Next_laser_fire_time <= GameTime64) {
  1824.                 if      (sufficient_energy(energy_used, pl_energy) && sufficient_ammo(ammo_used, uses_vulcan_ammo, player_info.vulcan_ammo)) {
  1825.                         int     laser_level, flags, fire_frame_overhead = 0;
  1826.  
  1827.                         if (GameTime64 - Next_laser_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that.
  1828.                                 fire_frame_overhead = GameTime64 - Next_laser_fire_time;
  1829.  
  1830.                         laser_level = player_info.laser_level;
  1831.  
  1832.                         flags = 0;
  1833.  
  1834.                         switch (Primary_weapon)
  1835.                         {
  1836.                                 case primary_weapon_index_t::LASER_INDEX:
  1837.                                         if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
  1838.                                                 flags |= LASER_QUAD;
  1839.                                         break;
  1840.                                 case primary_weapon_index_t::SPREADFIRE_INDEX:
  1841.                                         flags |= (player_info.Spreadfire_toggle ^= LASER_SPREADFIRE_TOGGLED) & LASER_SPREADFIRE_TOGGLED;
  1842.                                         break;
  1843.                                 case primary_weapon_index_t::VULCAN_INDEX:
  1844.                                 case primary_weapon_index_t::PLASMA_INDEX:
  1845.                                 case primary_weapon_index_t::FUSION_INDEX:
  1846.                                 default:
  1847.                                         break;
  1848. #if defined(DXX_BUILD_DESCENT_II)
  1849.                                 case primary_weapon_index_t::HELIX_INDEX:
  1850.                                         flags |= (player_info.Helix_orientation++ & LASER_HELIX_MASK);
  1851.                                         break;
  1852.                                 case primary_weapon_index_t::SUPER_LASER_INDEX:
  1853.                                 case primary_weapon_index_t::GAUSS_INDEX:
  1854.                                 case primary_weapon_index_t::PHOENIX_INDEX:
  1855.                                 case primary_weapon_index_t::OMEGA_INDEX:
  1856.                                         break;
  1857. #endif
  1858.                         }
  1859.  
  1860.                         const auto shot_fired = do_laser_firing(vmobjptridx(get_local_player().objnum), Primary_weapon, laser_level, flags, nfires, plrobj.orient.fvec, object_none);
  1861.                         rval += shot_fired;
  1862.  
  1863.                         if (!shot_fired)
  1864.                                 break;
  1865.                         Next_laser_fire_time = GameTime64 - fire_frame_overhead + (unlikely(cheats.rapidfire)
  1866.                                 ? (F1_0 / 25)
  1867.                                 : (
  1868. #if defined(DXX_BUILD_DESCENT_II)
  1869.                                         weapon_index == weapon_id_type::OMEGA_ID
  1870.                                         ? OMEGA_BASE_TIME
  1871.                                         :
  1872. #endif
  1873.                                         Weapon_info[weapon_index].fire_wait
  1874.                                 )
  1875.                         );
  1876.  
  1877.                         pl_energy -= (energy_used * rval) / Weapon_info[weapon_index].fire_count;
  1878.                         if (pl_energy < 0)
  1879.                                 pl_energy = 0;
  1880.  
  1881.                         if (uses_vulcan_ammo) {
  1882.                                 auto &v = player_info.vulcan_ammo;
  1883.                                 if (v < ammo_used)
  1884.                                         v = 0;
  1885.                                 else
  1886.                                         v -= ammo_used;
  1887.                                 maybe_drop_net_powerup(POW_VULCAN_AMMO, 1, 0);
  1888.                         }
  1889.                 } else {
  1890. #if defined(DXX_BUILD_DESCENT_II)
  1891.                         Next_laser_fire_time = GameTime64;      //      Prevents shots-to-fire from building up.
  1892. #endif
  1893.                         break;  //      Couldn't fire weapon, so abort.
  1894.                 }
  1895.         }
  1896.         auto_select_primary_weapon(player_info);                //      Make sure the player can fire from this weapon.
  1897. }
  1898.  
  1899. //      --------------------------------------------------------------------------------------------------
  1900. //      Object "objnum" fires weapon "weapon_num" of level "level".  (Right now (9/24/94) level is used only for type 0 laser.
  1901. //      Flags are the player flags.  For network mode, set to 0.
  1902. //      It is assumed that this is a player object (as in multiplayer), and therefore the gun positions are known.
  1903. //      Returns number of times a weapon was fired.  This is typically 1, but might be more for low frame rates.
  1904. //      More than one shot is fired with a pseudo-delay so that players on slow machines can fire (for themselves
  1905. //      or other players) often enough for things like the vulcan cannon.
  1906. int do_laser_firing(vmobjptridx_t objp, int weapon_num, int level, int flags, int nfires, vms_vector shot_orientation, const icobjidx_t Network_laser_track)
  1907. {
  1908.         switch (weapon_num) {
  1909.                 case primary_weapon_index_t::LASER_INDEX: {
  1910.                         weapon_id_type weapon_type;
  1911.  
  1912.                         switch(level)
  1913.                         {
  1914.                                 case LASER_LEVEL_1:
  1915.                                         weapon_type = weapon_id_type::LASER_ID_L1;
  1916.                                         break;
  1917.                                 case LASER_LEVEL_2:
  1918.                                         weapon_type = weapon_id_type::LASER_ID_L2;
  1919.                                         break;
  1920.                                 case LASER_LEVEL_3:
  1921.                                         weapon_type = weapon_id_type::LASER_ID_L3;
  1922.                                         break;
  1923.                                 case LASER_LEVEL_4:
  1924.                                         weapon_type = weapon_id_type::LASER_ID_L4;
  1925.                                         break;
  1926. #if defined(DXX_BUILD_DESCENT_II)
  1927.                                 case LASER_LEVEL_5:
  1928.                                         weapon_type = weapon_id_type::LASER_ID_L5;
  1929.                                         break;
  1930.                                 case LASER_LEVEL_6:
  1931.                                         weapon_type = weapon_id_type::LASER_ID_L6;
  1932.                                         break;
  1933. #endif
  1934.                                 default:
  1935.                                         Assert(0);
  1936.                                         return nfires;
  1937.                         }
  1938.                         Laser_player_fire(objp, weapon_type, 0, 1, shot_orientation, object_none);
  1939.                         Laser_player_fire(objp, weapon_type, 1, 0, shot_orientation, object_none);
  1940.  
  1941.                         if (flags & LASER_QUAD) {
  1942.                                 //      hideous system to make quad laser 1.5x powerful as normal laser, make every other quad laser bolt harmless
  1943.                                 Laser_player_fire(objp, weapon_type, 2, 0, shot_orientation, object_none);
  1944.                                 Laser_player_fire(objp, weapon_type, 3, 0, shot_orientation, object_none);
  1945.                         }
  1946.                         break;
  1947.                 }
  1948.                 case primary_weapon_index_t::VULCAN_INDEX: {
  1949.                         //      Only make sound for 1/4 of vulcan bullets.
  1950.                         int     make_sound = 1;
  1951.                         //if (d_rand() > 24576)
  1952.                         //      make_sound = 1;
  1953.                         Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, make_sound, shot_orientation, object_none);
  1954.                         if (nfires > 1) {
  1955.                                 Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, 0, shot_orientation, object_none);
  1956.                                 if (nfires > 2) {
  1957.                                         Laser_player_fire_spread(objp, weapon_id_type::VULCAN_ID, 6, d_rand()/8 - 32767/16, d_rand()/8 - 32767/16, 0, shot_orientation, object_none);
  1958.                                 }
  1959.                         }
  1960.                         break;
  1961.                 }
  1962.                 case primary_weapon_index_t::SPREADFIRE_INDEX:
  1963.                         {
  1964.                                 fix spreadr0, spreadu0, spreadr1, spreadu1;
  1965.                                 if (flags & LASER_SPREADFIRE_TOGGLED)
  1966.                                         spreadr0 = F1_0 / 16, spreadr1 = -F1_0 / 16, spreadu0 = spreadu1 = 0;
  1967.                                 else
  1968.                                         spreadu0 = F1_0 / 16, spreadu1 = -F1_0 / 16, spreadr0 = spreadr1 = 0;
  1969.                                 Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, spreadr0, spreadu0, 0, shot_orientation, object_none);
  1970.                                 Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, spreadr1, spreadu1, 0, shot_orientation, object_none);
  1971.                                 Laser_player_fire_spread(objp, weapon_id_type::SPREADFIRE_ID, 6, 0, 0, 1, shot_orientation, object_none);
  1972.                         }
  1973.                         break;
  1974.  
  1975.                 case primary_weapon_index_t::PLASMA_INDEX:
  1976.                         Laser_player_fire(objp, weapon_id_type::PLASMA_ID, 0, 1, shot_orientation, object_none);
  1977.                         Laser_player_fire(objp, weapon_id_type::PLASMA_ID, 1, 0, shot_orientation, object_none);
  1978.                         if (nfires > 1) {
  1979.                                 Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PLASMA_ID, 0, 0, 0, FrameTime/2, 1, shot_orientation, object_none);
  1980.                                 Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PLASMA_ID, 1, 0, 0, FrameTime/2, 0, shot_orientation, object_none);
  1981.                         }
  1982.                         break;
  1983.  
  1984.                 case primary_weapon_index_t::FUSION_INDEX: {
  1985.                         vms_vector      force_vec;
  1986.  
  1987.                         Laser_player_fire(objp, weapon_id_type::FUSION_ID, 0, 1, shot_orientation, object_none);
  1988.                         Laser_player_fire(objp, weapon_id_type::FUSION_ID, 1, 1, shot_orientation, object_none);
  1989.  
  1990.                         assert(objp->type == OBJ_PLAYER);
  1991.                         auto &Fusion_charge = objp->ctype.player_info.Fusion_charge;
  1992.                         flags = static_cast<int8_t>(Fusion_charge >> 12);
  1993.  
  1994.                         Fusion_charge = 0;
  1995.  
  1996.                         force_vec.x = -(objp->orient.fvec.x << 7);
  1997.                         force_vec.y = -(objp->orient.fvec.y << 7);
  1998.                         force_vec.z = -(objp->orient.fvec.z << 7);
  1999.                         phys_apply_force(objp, force_vec);
  2000.  
  2001.                         force_vec.x = (force_vec.x >> 4) + d_rand() - 16384;
  2002.                         force_vec.y = (force_vec.y >> 4) + d_rand() - 16384;
  2003.                         force_vec.z = (force_vec.z >> 4) + d_rand() - 16384;
  2004.                         phys_apply_rot(objp, force_vec);
  2005.  
  2006.                 }
  2007.                         break;
  2008. #if defined(DXX_BUILD_DESCENT_II)
  2009.                 case primary_weapon_index_t::GAUSS_INDEX: {
  2010.                         //      Only make sound for 1/4 of vulcan bullets.
  2011.                         int     make_sound = 1;
  2012.                         //if (d_rand() > 24576)
  2013.                         //      make_sound = 1;
  2014.  
  2015.                         Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, make_sound, shot_orientation, object_none);
  2016.                         if (nfires > 1) {
  2017.                                 Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, 0, shot_orientation, object_none);
  2018.                                 if (nfires > 2) {
  2019.                                         Laser_player_fire_spread(objp, weapon_id_type::GAUSS_ID, 6, (d_rand()/8 - 32767/16)/5, (d_rand()/8 - 32767/16)/5, 0, shot_orientation, object_none);
  2020.                                 }
  2021.                         }
  2022.                         break;
  2023.                 }
  2024.                 case primary_weapon_index_t::HELIX_INDEX: {
  2025.                         fix spreadr,spreadu;
  2026.                         static const std::array<std::pair<fix, fix>, 8> spread{{
  2027.                                 { F1_0/16, 0},                  // Vertical
  2028.                                 { F1_0/17, F1_0/42},    //  22.5 degrees
  2029.                                 { F1_0/22, F1_0/22},    //  45   degrees
  2030.                                 { F1_0/42, F1_0/17},    //  67.5 degrees
  2031.                                 {               0, F1_0/16},    //  90   degrees
  2032.                                 {-F1_0/42, F1_0/17},    // 112.5 degrees
  2033.                                 {-F1_0/22, F1_0/22},    // 135   degrees
  2034.                                 {-F1_0/17, F1_0/42},    // 157.5 degrees
  2035.                         }};
  2036.                         const unsigned helix_orient = flags & LASER_HELIX_MASK;
  2037.                         if (helix_orient < spread.size())
  2038.                         {
  2039.                                 auto &s = spread[helix_orient];
  2040.                                 spreadr = s.first;
  2041.                                 spreadu = s.second;
  2042.                         }
  2043.                         else
  2044.                                 break;
  2045.                         Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6,  0,  0, 1, shot_orientation, object_none);
  2046.                         Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6,  spreadr,  spreadu, 0, shot_orientation, object_none);
  2047.                         Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, -spreadr, -spreadu, 0, shot_orientation, object_none);
  2048.                         Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6,  spreadr*2,  spreadu*2, 0, shot_orientation, object_none);
  2049.                         Laser_player_fire_spread(objp, weapon_id_type::HELIX_ID, 6, -spreadr*2, -spreadu*2, 0, shot_orientation, object_none);
  2050.                         break;
  2051.                 }
  2052.  
  2053.                 case primary_weapon_index_t::PHOENIX_INDEX:
  2054.                         Laser_player_fire(objp, weapon_id_type::PHOENIX_ID, 0, 1, shot_orientation, object_none);
  2055.                         Laser_player_fire(objp, weapon_id_type::PHOENIX_ID, 1, 0, shot_orientation, object_none);
  2056.                         if (nfires > 1) {
  2057.                                 Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PHOENIX_ID, 0, 0, 0, FrameTime/2, 1, shot_orientation, object_none);
  2058.                                 Laser_player_fire_spread_delay(vmsegptridx, objp, weapon_id_type::PHOENIX_ID, 1, 0, 0, FrameTime/2, 0, shot_orientation, object_none);
  2059.                         }
  2060.                         break;
  2061.  
  2062.                 case primary_weapon_index_t::OMEGA_INDEX:
  2063.                         Laser_player_fire(objp, weapon_id_type::OMEGA_ID, 1, 1, shot_orientation, Network_laser_track);
  2064.                         break;
  2065.  
  2066.                 case primary_weapon_index_t::SUPER_LASER_INDEX:
  2067. #endif
  2068.                 default:
  2069.                         Int3(); //      Contact Yuan: Unknown Primary weapon type, setting to 0.
  2070.         }
  2071.  
  2072.         // Set values to be recognized during comunication phase, if we are the
  2073.         //  one shooting
  2074.         if ((Game_mode & GM_MULTI) && objp == get_local_player().objnum)
  2075.                 multi_send_fire(weapon_num, level, flags, nfires, Network_laser_track, object_none);
  2076.  
  2077.         return nfires;
  2078. }
  2079.  
  2080. }
  2081.  
  2082. namespace dcx {
  2083.  
  2084. uint_fast8_t laser_info::test_set_hitobj(const vcobjidx_t o)
  2085. {
  2086.         if (const auto r = test_hitobj(o))
  2087.                 return r;
  2088.         const std::size_t values_size = hitobj_values.size();
  2089.         assert(hitobj_count <= values_size);
  2090.         assert(hitobj_pos < values_size);
  2091.         hitobj_values[hitobj_pos++] = o;
  2092.         if (unlikely(hitobj_pos == values_size))
  2093.                 hitobj_pos = 0;
  2094.         if (likely(hitobj_count != values_size))
  2095.                 ++ hitobj_count;
  2096.         return false;
  2097. }
  2098.  
  2099. uint_fast8_t laser_info::test_hitobj(const vcobjidx_t o) const
  2100. {
  2101.         /* Search backward so that the highest added element is the first
  2102.          * one considered.  This preserves most of the benefit of tracking
  2103.          * the most recent hit separately, without storing it separately or
  2104.          * requiring expensive shift operations as new elements are added.
  2105.          *
  2106.          * This efficiency hack becomes ineffective (but not incorrect) if
  2107.          * the list wraps.  Wrapping should be very rare, and even a full
  2108.          * search is relatively cheap, so it is not worth complicating the
  2109.          * code to ensure that elements are always searched in
  2110.          * most-recently-added order.
  2111.          */
  2112.         const auto &&r = partial_range(hitobj_values, hitobj_count).reversed();
  2113.         const auto &&e = r.end();
  2114.         return std::find(r.begin(), e, o) != e;
  2115. }
  2116.  
  2117. }
  2118.  
  2119. namespace dsx {
  2120.  
  2121. //      -------------------------------------------------------------------------------------------
  2122. //      if goal_obj == -1, then create random vector
  2123. static imobjptridx_t create_homing_missile(fvmsegptridx &vmsegptridx, const vmobjptridx_t objp, const imobjptridx_t goal_obj, weapon_id_type objtype, int make_sound)
  2124. {
  2125.         vms_vector      vector_to_goal;
  2126.         //vms_vector    goal_pos;
  2127.  
  2128.         if (goal_obj == object_none) {
  2129.                 make_random_vector(vector_to_goal);
  2130.         } else {
  2131.                 vm_vec_normalized_dir_quick(vector_to_goal, goal_obj->pos, objp->pos);
  2132.                 vm_vec_scale_add2(vector_to_goal, make_random_vector(), F1_0/4);
  2133.                 vm_vec_normalize_quick(vector_to_goal);
  2134.         }
  2135.  
  2136.         //      Create a vector towards the goal, then add some noise to it.
  2137.         const auto &&objnum = Laser_create_new(vector_to_goal, objp->pos, vmsegptridx(objp->segnum), objp, objtype, make_sound);
  2138.         if (objnum != object_none)
  2139.         {
  2140.         // Fixed to make sure the right person gets credit for the kill
  2141.  
  2142. //      Objects[objnum].ctype.laser_info.parent_num = objp->ctype.laser_info.parent_num;
  2143. //      Objects[objnum].ctype.laser_info.parent_type = objp->ctype.laser_info.parent_type;
  2144. //      Objects[objnum].ctype.laser_info.parent_signature = objp->ctype.laser_info.parent_signature;
  2145.         objnum->ctype.laser_info.track_goal = goal_obj;
  2146.         }
  2147.         return objnum;
  2148. }
  2149.  
  2150. namespace {
  2151.  
  2152. struct miniparent
  2153. {
  2154.         short type;
  2155.         objnum_t num;
  2156. };
  2157.  
  2158. }
  2159.  
  2160. //-----------------------------------------------------------------------------
  2161. // Create the children of a smart bomb, which is a bunch of homing missiles.
  2162. static void create_smart_children(object_array &Objects, const vmobjptridx_t objp, const uint_fast32_t num_smart_children, const miniparent parent)
  2163. {
  2164.         auto &vcobjptridx = Objects.vcptridx;
  2165.         auto &vcobjptr = Objects.vcptr;
  2166.         unsigned numobjs = 0;
  2167.         weapon_id_type blob_id;
  2168.  
  2169.         std::array<objnum_t, MAX_OBJDISTS> objlist;
  2170. #if defined(DXX_BUILD_DESCENT_II)
  2171.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2172. #endif
  2173.         {
  2174.                 if (Game_mode & GM_MULTI)
  2175.                         d_srand(8321L);
  2176.  
  2177.                 range_for (const auto &&curobjp, vcobjptridx)
  2178.                 {
  2179.                         if (((curobjp->type == OBJ_ROBOT && !curobjp->ctype.ai_info.CLOAKED) || curobjp->type == OBJ_PLAYER) && curobjp != parent.num)
  2180.                         {
  2181.                                 if (curobjp->type == OBJ_PLAYER)
  2182.                                 {
  2183.                                         if (parent.type == OBJ_PLAYER && (Game_mode & GM_MULTI_COOP))
  2184.                                                 continue;
  2185.                                         if ((Game_mode & GM_TEAM) && get_team(get_player_id(curobjp)) == get_team(get_player_id(vcobjptr(parent.num))))
  2186.                                                 continue;
  2187.                                         if (curobjp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
  2188.                                                 continue;
  2189.                                 }
  2190.  
  2191.                                 //      Robot blobs can't track robots.
  2192.                                 if (curobjp->type == OBJ_ROBOT) {
  2193.                                         if (parent.type == OBJ_ROBOT)
  2194.                                                 continue;
  2195.  
  2196. #if defined(DXX_BUILD_DESCENT_II)
  2197.                                         //      Your shots won't track the buddy.
  2198.                                         if (parent.type == OBJ_PLAYER)
  2199.                                                 if (Robot_info[get_robot_id(curobjp)].companion)
  2200.                                                         continue;
  2201. #endif
  2202.                                 }
  2203.  
  2204.                                 const auto &&dist = vm_vec_dist2(objp->pos, curobjp->pos);
  2205.                                 if (dist < MAX_SMART_DISTANCE_SQUARED)
  2206.                                 {
  2207.                                         int oovis;
  2208.  
  2209.                                         oovis = object_to_object_visibility(objp, curobjp, FQ_TRANSWALL);
  2210.  
  2211.                                         if (oovis) {
  2212.                                                 objlist[numobjs] = curobjp;
  2213.                                                 numobjs++;
  2214.                                                 if (numobjs >= MAX_OBJDISTS) {
  2215.                                                         numobjs = MAX_OBJDISTS;
  2216.                                                         break;
  2217.                                                 }
  2218.                                         }
  2219.                                 }
  2220.                         }
  2221.                 }
  2222.  
  2223.                 //      Get type of weapon for child from parent.
  2224. #if defined(DXX_BUILD_DESCENT_I)
  2225.                 if (parent.type == OBJ_PLAYER)
  2226.                 {
  2227.                         blob_id = weapon_id_type::PLAYER_SMART_HOMING_ID;
  2228.                 } else {
  2229.                         blob_id = ((N_weapon_types<weapon_id_type::ROBOT_SMART_HOMING_ID)?(weapon_id_type::PLAYER_SMART_HOMING_ID):(weapon_id_type::ROBOT_SMART_HOMING_ID)); // NOTE: Shareware & reg 1.0 do not have their own Smart structure for bots. It was introduced in 1.4 to make Smart blobs from lvl 7 boss easier to dodge. So if we do not have this type, revert to player's Smart behaviour..,
  2230.                 }
  2231. #elif defined(DXX_BUILD_DESCENT_II)
  2232.                 if (objp->type == OBJ_WEAPON) {
  2233.                         blob_id = Weapon_info[get_weapon_id(objp)].children;
  2234.                         Assert(blob_id != weapon_none);         //      Hmm, missing data in bitmaps.tbl.  Need "children=NN" parameter.
  2235.                 } else {
  2236.                         Assert(objp->type == OBJ_ROBOT);
  2237.                         blob_id = weapon_id_type::ROBOT_SMART_HOMING_ID;
  2238.                 }
  2239. #endif
  2240.  
  2241.                 objnum_t last_sel_objnum = object_none;
  2242.                 const auto get_random_different_object = [&]{
  2243.                         for (;;)
  2244.                         {
  2245.                                 const auto r = objlist[(d_rand() * numobjs) >> 15];
  2246.                                 if (last_sel_objnum != r)
  2247.                                         return last_sel_objnum = r;
  2248.                         }
  2249.                 };
  2250.                 for (auto i = num_smart_children; i--;)
  2251.                 {
  2252.                         const auto &&sel_objnum = numobjs
  2253.                                 ? Objects.imptridx((numobjs == 1)
  2254.                                         ? objlist[0]
  2255.                                         : get_random_different_object())
  2256.                                 : object_none;
  2257.                         create_homing_missile(vmsegptridx, objp, sel_objnum, blob_id, (i==0)?1:0);
  2258.                 }
  2259.         }
  2260. }
  2261.  
  2262. void create_weapon_smart_children(const vmobjptridx_t objp)
  2263. {
  2264.         auto &Objects = LevelUniqueObjectState.Objects;
  2265.         const auto wid = get_weapon_id(objp);
  2266. #if defined(DXX_BUILD_DESCENT_I)
  2267.         if (wid != weapon_id_type::SMART_ID)
  2268. #elif defined(DXX_BUILD_DESCENT_II)
  2269.         if (Weapon_info[wid].children == weapon_id_type::unspecified)
  2270. #endif
  2271.                 return;
  2272. #if defined(DXX_BUILD_DESCENT_II)
  2273.         if (wid == weapon_id_type::EARTHSHAKER_ID)
  2274.                 blast_nearby_glass(objp, Weapon_info[weapon_id_type::EARTHSHAKER_ID].strength[GameUniqueState.Difficulty_level]);
  2275. #endif
  2276.         create_smart_children(Objects, objp, NUM_SMART_CHILDREN, {objp->ctype.laser_info.parent_type, objp->ctype.laser_info.parent_num});
  2277. }
  2278.  
  2279. #if defined(DXX_BUILD_DESCENT_II)
  2280.  
  2281. void create_robot_smart_children(const vmobjptridx_t objp, const uint_fast32_t num_smart_children)
  2282. {
  2283.         auto &Objects = LevelUniqueObjectState.Objects;
  2284.         create_smart_children(Objects, objp, num_smart_children, {OBJ_ROBOT, objp});
  2285. }
  2286.  
  2287. //give up control of the guided missile
  2288. void release_guided_missile(d_level_unique_object_state &LevelUniqueObjectState, const unsigned player_num)
  2289. {
  2290.         if (player_num == Player_num)
  2291.          {
  2292.                 const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, player_num);
  2293.                 if (gimobj == nullptr)
  2294.                         return;
  2295.  
  2296.                 Missile_viewer = gimobj;
  2297.                 if (Game_mode & GM_MULTI)
  2298.                         multi_send_guided_info(gimobj, 1);
  2299.                 if (Newdemo_state==ND_STATE_RECORDING)
  2300.                  newdemo_record_guided_end();
  2301.          }
  2302.         LevelUniqueObjectState.Guided_missile.clear_player_active_guided_missile(player_num);
  2303. }
  2304. #endif
  2305.  
  2306. //      -------------------------------------------------------------------------------------------
  2307. //changed on 31/3/10 by kreatordxx to distinguish between drop bomb and secondary fire
  2308. void do_missile_firing(int drop_bomb)
  2309. {
  2310.         auto &Objects = LevelUniqueObjectState.Objects;
  2311.         auto &vmobjptr = Objects.vmptr;
  2312.         auto &vmobjptridx = Objects.vmptridx;
  2313.         int gun_flag=0;
  2314.         fix fire_frame_overhead = 0;
  2315.  
  2316.         auto &plrobj = get_local_plrobj();
  2317.         const auto bomb = which_bomb();
  2318.         auto &player_info = plrobj.ctype.player_info;
  2319.         const auto weapon = drop_bomb ? bomb : player_info.Secondary_weapon;
  2320.         assert(weapon < MAX_SECONDARY_WEAPONS);
  2321.         auto &Next_missile_fire_time = player_info.Next_missile_fire_time;
  2322.         if (GameTime64 - Next_missile_fire_time <= FrameTime) // if firing is prolonged by FrameTime overhead, let's try to fix that.
  2323.                 fire_frame_overhead = GameTime64 - Next_missile_fire_time;
  2324.  
  2325. #if defined(DXX_BUILD_DESCENT_II)
  2326.         const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num);
  2327.         if (gimobj != nullptr)
  2328.         {
  2329.                 release_guided_missile(LevelUniqueObjectState, Player_num);
  2330.                 Next_missile_fire_time = GameTime64 + Weapon_info[Secondary_weapon_to_weapon_info[weapon]].fire_wait - fire_frame_overhead;
  2331.                 return;
  2332.         }
  2333. #endif
  2334.  
  2335.         if (Player_dead_state != player_dead_state::no)
  2336.                 return;
  2337.         if (auto &secondary_weapon_ammo = player_info.secondary_ammo[weapon])
  2338.         {
  2339.  
  2340.                 int weapon_gun;
  2341.  
  2342.                 const auto weapon_index = Secondary_weapon_to_weapon_info[weapon];
  2343.  
  2344.                 if (!cheats.rapidfire)
  2345.                         Next_missile_fire_time = GameTime64 + Weapon_info[weapon_index].fire_wait - fire_frame_overhead;
  2346.                 else
  2347.                         Next_missile_fire_time = GameTime64 + (F1_0/25) - fire_frame_overhead;
  2348.  
  2349.                 weapon_gun = Secondary_weapon_to_gun_num[weapon];
  2350.  
  2351.                 if (weapon_gun==4) {            //alternate left/right
  2352.                         auto &Missile_gun = get_local_plrobj().ctype.player_info.missile_gun;
  2353.                         weapon_gun += (gun_flag = (Missile_gun & 1));
  2354.                         Missile_gun++;
  2355.                 }
  2356.  
  2357.                 const auto &&objnum = Laser_player_fire(vmobjptridx(ConsoleObject), weapon_index, weapon_gun, 1, get_local_plrobj().orient.fvec, object_none);
  2358.                 if (objnum != object_none)
  2359.                         -- secondary_weapon_ammo;
  2360.  
  2361.                 if (weapon != CONCUSSION_INDEX)
  2362.                         maybe_drop_net_powerup(Secondary_weapon_to_powerup[weapon], 1, 0);
  2363.  
  2364. #if defined(DXX_BUILD_DESCENT_I)
  2365.                 if (weapon == MEGA_INDEX)
  2366. #elif defined(DXX_BUILD_DESCENT_II)
  2367.                 if (weapon == MEGA_INDEX || weapon == SMISSILE5_INDEX)
  2368. #endif
  2369.                 {
  2370.                         vms_vector force_vec;
  2371.  
  2372.                         const auto &&console = vmobjptr(ConsoleObject);
  2373.                         force_vec.x = -(ConsoleObject->orient.fvec.x << 7);
  2374.                         force_vec.y = -(ConsoleObject->orient.fvec.y << 7);
  2375.                         force_vec.z = -(ConsoleObject->orient.fvec.z << 7);
  2376.                         phys_apply_force(console, force_vec);
  2377.  
  2378.                         force_vec.x = (force_vec.x >> 4) + d_rand() - 16384;
  2379.                         force_vec.y = (force_vec.y >> 4) + d_rand() - 16384;
  2380.                         force_vec.z = (force_vec.z >> 4) + d_rand() - 16384;
  2381.                         phys_apply_rot(console, force_vec);
  2382.                 }
  2383.  
  2384.                 if (Game_mode & GM_MULTI)
  2385.                 {
  2386.                         multi_send_fire(weapon+MISSILE_ADJUST, 0, gun_flag, 1, objnum == object_none ? object_none : objnum->ctype.laser_info.track_goal, weapon_index_is_player_bomb(weapon) ? objnum : object_none);
  2387.                 }
  2388.  
  2389.                 // don't autoselect if dropping prox and prox not current weapon
  2390.                 if (!drop_bomb || player_info.Secondary_weapon == bomb)
  2391.                         auto_select_secondary_weapon(player_info);              //select next missile, if this one out of ammo
  2392.         }
  2393. }
  2394. }
  2395.