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.  * Routines to handle collisions
  23.  *
  24.  */
  25.  
  26. #include <algorithm>
  27. #include <cstdlib>
  28. #include <stdexcept>
  29. #include <stdlib.h>
  30. #include <stdio.h>
  31. #include <type_traits>
  32.  
  33. #include "rle.h"
  34. #include "inferno.h"
  35. #include "game.h"
  36. #include "gr.h"
  37. #include "bm.h"
  38. #include "3d.h"
  39. #include "segment.h"
  40. #include "texmap.h"
  41. #include "laser.h"
  42. #include "key.h"
  43. #include "gameseg.h"
  44. #include "object.h"
  45. #include "physics.h"
  46. #include "slew.h"
  47. #include "render.h"
  48. #include "wall.h"
  49. #include "vclip.h"
  50. #include "polyobj.h"
  51. #include "fireball.h"
  52. #include "hudmsg.h"
  53. #include "laser.h"
  54. #include "dxxerror.h"
  55. #include "ai.h"
  56. #include "hostage.h"
  57. #include "fuelcen.h"
  58. #include "sounds.h"
  59. #include "robot.h"
  60. #include "weapon.h"
  61. #include "player.h"
  62. #include "gauges.h"
  63. #include "powerup.h"
  64. #include "newmenu.h"
  65. #include "scores.h"
  66. #include "effects.h"
  67. #include "textures.h"
  68. #include "multi.h"
  69. #include "cntrlcen.h"
  70. #include "newdemo.h"
  71. #include "endlevel.h"
  72. #include "multibot.h"
  73. #include "playsave.h"
  74. #include "piggy.h"
  75. #include "text.h"
  76. #include "automap.h"
  77. #include "switch.h"
  78. #include "palette.h"
  79. #include "gameseq.h"
  80. #if DXX_USE_EDITOR
  81. #include "editor/editor.h"
  82. #endif
  83. #include "collide.h"
  84. #include "escort.h"
  85.  
  86. #include "dxxsconf.h"
  87. #include "dsx-ns.h"
  88. #include "partial_range.h"
  89. #include <utility>
  90.  
  91. using std::min;
  92.  
  93. #if defined(DXX_BUILD_DESCENT_II)
  94. constexpr std::array<uint8_t, MAX_WEAPON_TYPES> Weapon_is_energy{{
  95.         1, 1, 1, 1, 1,
  96.         1, 1, 1, 0, 1,
  97.         1, 0, 1, 1, 1,
  98.         0, 1, 0, 0, 1,
  99.         1, 0, 0, 1, 1,
  100.         1, 1, 1, 0, 1,
  101.         1, 1, 0, 1, 1,
  102.         1
  103. }};
  104. #endif
  105.  
  106. #define WALL_DAMAGE_SCALE (128) // Was 32 before 8:55 am on Thursday, September 15, changed by MK, walls were hurting me more than robots!
  107. #define WALL_DAMAGE_THRESHOLD (F1_0/3)
  108. #define WALL_LOUDNESS_SCALE (20)
  109. #define FORCE_DAMAGE_THRESHOLD (F1_0/3)
  110. #define STANDARD_EXPL_DELAY (F1_0/4)
  111.  
  112. static int check_collision_delayfunc_exec()
  113. {
  114.         static fix64 last_play_time=0;
  115.         if (last_play_time + (F1_0/3) < GameTime64 || last_play_time > GameTime64)
  116.         {
  117.                 last_play_time = GameTime64;
  118.                 last_play_time -= (d_rand()/2); // add some randomness
  119.                 return 1;
  120.         }
  121.         return 0;
  122. }
  123.  
  124. //      -------------------------------------------------------------------------------------------------------------
  125. //      The only reason this routine is called (as of 10/12/94) is so Brain guys can open doors.
  126. namespace dsx {
  127. static void collide_robot_and_wall(fvcwallptr &vcwallptr, object &robot, const vmsegptridx_t hitseg, const unsigned hitwall, const vms_vector &)
  128. {
  129.         const ubyte robot_id = get_robot_id(robot);
  130. #if defined(DXX_BUILD_DESCENT_I)
  131.         if (robot_id == ROBOT_BRAIN || robot.ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM)
  132. #elif defined(DXX_BUILD_DESCENT_II)
  133.         auto &BuddyState = LevelUniqueObjectState.BuddyState;
  134.         auto &Objects = LevelUniqueObjectState.Objects;
  135.         auto &vmobjptr = Objects.vmptr;
  136.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  137.         auto &robptr = Robot_info[robot_id];
  138.         if (robot_id == ROBOT_BRAIN || robot.ctype.ai_info.behavior == ai_behavior::AIB_RUN_FROM || robot_is_companion(robptr) == 1 || robot.ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
  139. #endif
  140.         {
  141.                 const auto wall_num = hitseg->shared_segment::sides[hitwall].wall_num;
  142.                 if (wall_num != wall_none) {
  143.                         auto &w = *vcwallptr(wall_num);
  144.                         if (w.type == WALL_DOOR && w.keys == KEY_NONE && w.state == WALL_DOOR_CLOSED && !(w.flags & WALL_DOOR_LOCKED))
  145.                         {
  146.                                 wall_open_door(hitseg, hitwall);
  147.                         // -- Changed from this, 10/19/95, MK: Don't want buddy getting stranded from player
  148.                         //-- } else if ((Robot_info[robot->id].companion == 1) && (Walls[wall_num].type == WALL_DOOR) && (Walls[wall_num].keys != KEY_NONE) && (Walls[wall_num].state == WALL_DOOR_CLOSED) && !(Walls[wall_num].flags & WALL_DOOR_LOCKED)) {
  149.                         }
  150. #if defined(DXX_BUILD_DESCENT_II)
  151.                         else if (robot_is_companion(robptr) && w.type == WALL_DOOR)
  152.                         {
  153.                                 ai_local *const ailp = &robot.ctype.ai_info.ail;
  154.                                 if (ailp->mode == ai_mode::AIM_GOTO_PLAYER || BuddyState.Escort_special_goal == ESCORT_GOAL_SCRAM) {
  155.                                         if (w.keys != KEY_NONE)
  156.                                         {
  157.                                                 auto &player_info = get_local_plrobj().ctype.player_info;
  158.                                                 if (player_info.powerup_flags & static_cast<PLAYER_FLAG>(w.keys))
  159.                                                         wall_open_door(hitseg, hitwall);
  160.                                         }
  161.                                         else if (!(w.flags & WALL_DOOR_LOCKED))
  162.                                                 wall_open_door(hitseg, hitwall);
  163.                                 }
  164.                         } else if (Robot_info[get_robot_id(robot)].thief) {             //      Thief allowed to go through doors to which player has key.
  165.                                 if (w.keys != KEY_NONE)
  166.                                 {
  167.                                         auto &player_info = get_local_plrobj().ctype.player_info;
  168.                                         if (player_info.powerup_flags & static_cast<PLAYER_FLAG>(w.keys))
  169.                                                 wall_open_door(hitseg, hitwall);
  170.                                 }
  171.                         }
  172. #endif
  173.                 }
  174.         }
  175.  
  176.         return;
  177. }
  178. }
  179.  
  180. //      -------------------------------------------------------------------------------------------------------------
  181.  
  182. static int apply_damage_to_clutter(const vmobjptridx_t clutter, fix damage)
  183. {
  184.         if ( clutter->flags&OF_EXPLODING) return 0;
  185.  
  186.         if (clutter->shields < 0 ) return 0;    //clutter already dead...
  187.  
  188.         clutter->shields -= damage;
  189.  
  190.         if (clutter->shields < 0) {
  191.                 explode_object(clutter,0);
  192.                 return 1;
  193.         } else
  194.                 return 0;
  195. }
  196.  
  197. //given the specified force, apply damage from that force to an object
  198. namespace dsx {
  199. static void apply_force_damage(const vmobjptridx_t obj,fix force,const vmobjptridx_t other_obj)
  200. {
  201.         auto &Objects = LevelUniqueObjectState.Objects;
  202.         auto &vcobjptr = Objects.vcptr;
  203.         fix damage;
  204.  
  205.         if (obj->flags & (OF_EXPLODING|OF_SHOULD_BE_DEAD))
  206.                 return;         //already exploding or dead
  207.  
  208.         damage = fixdiv(force,obj->mtype.phys_info.mass) / 8;
  209.  
  210. #if defined(DXX_BUILD_DESCENT_II)
  211.         if ((other_obj->type == OBJ_PLAYER) && cheats.monsterdamage)
  212.                 damage = INT32_MAX;
  213. #endif
  214.  
  215.         if (damage < FORCE_DAMAGE_THRESHOLD)
  216.                 return;
  217.  
  218.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  219.         switch (obj->type) {
  220.  
  221.                 case OBJ_ROBOT:
  222.                 {
  223.                         auto &robptr = Robot_info[get_robot_id(obj)];
  224.                         const auto other_type = other_obj->type;
  225.                         const auto result = apply_damage_to_robot(obj, (robptr.attack_type == 1)
  226.                                 ? damage / 4
  227.                                 : damage / 2,
  228.                                 (other_type == OBJ_WEAPON)
  229.                                 ? other_obj->ctype.laser_info.parent_num
  230.                                 : static_cast<objnum_t>(other_obj));
  231.                         if (!result)
  232.                                 break;
  233.  
  234.                         const auto console = ConsoleObject;
  235.                         if ((other_type == OBJ_PLAYER && other_obj == console) ||
  236.                                 (other_type == OBJ_WEAPON && laser_parent_is_player(vcobjptr, other_obj->ctype.laser_info, *console)))
  237.                                 add_points_to_score(console->ctype.player_info, robptr.score_value);
  238.                         break;
  239.                 }
  240.  
  241.                 case OBJ_PLAYER:
  242.  
  243.                         //      If colliding with a claw type robot, do damage proportional to FrameTime because you can collide with those
  244.                         //      bots every frame since they don't move.
  245.                         if ( (other_obj->type == OBJ_ROBOT) && (Robot_info[get_robot_id(other_obj)].attack_type) )
  246.                                 damage = fixmul(damage, FrameTime*2);
  247.  
  248. #if defined(DXX_BUILD_DESCENT_II)
  249.                         //      Make trainee easier.
  250.                         if (GameUniqueState.Difficulty_level == 0)
  251.                                 damage /= 2;
  252. #endif
  253.  
  254.                         apply_damage_to_player(obj,other_obj,damage,0);
  255.                         break;
  256.  
  257.                 case OBJ_CLUTTER:
  258.  
  259.                         apply_damage_to_clutter(obj,damage);
  260.                         break;
  261.  
  262.                 case OBJ_CNTRLCEN: // Never hits! Reactor does not have MT_PHYSICS - it's stationary! So no force damage here.
  263.  
  264.                         apply_damage_to_controlcen(obj,damage, other_obj);
  265.                         break;
  266.  
  267.                 case OBJ_WEAPON:
  268.  
  269.                         break; //weapons don't take damage
  270.  
  271.                 default:
  272.  
  273.                         Int3();
  274.  
  275.         }
  276. }
  277. }
  278.  
  279. //      -----------------------------------------------------------------------------
  280. static void bump_this_object(const vmobjptridx_t objp, const vmobjptridx_t other_objp, const vms_vector &force, int damage_flag)
  281. {
  282.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  283.         if (! (objp->mtype.phys_info.flags & PF_PERSISTENT))
  284.         {
  285.                 if (objp->type == OBJ_PLAYER) {
  286.                         vms_vector force2;
  287.                         force2.x = force.x/4;
  288.                         force2.y = force.y/4;
  289.                         force2.z = force.z/4;
  290.                         phys_apply_force(objp,force2);
  291.                         if (damage_flag && (other_objp->type != OBJ_ROBOT || !robot_is_companion(Robot_info[get_robot_id(other_objp)])))
  292.                         {
  293.                                 auto force_mag = vm_vec_mag_quick(force2);
  294.                                 apply_force_damage(objp, force_mag, other_objp);
  295.                         }
  296.                 } else if ((objp->type == OBJ_ROBOT) || (objp->type == OBJ_CLUTTER) || (objp->type == OBJ_CNTRLCEN)) {
  297.                         if (!Robot_info[get_robot_id(objp)].boss_flag) {
  298.                                 vms_vector force2;
  299.                                 const auto Difficulty_level = GameUniqueState.Difficulty_level;
  300.                                 force2.x = force.x/(4 + Difficulty_level);
  301.                                 force2.y = force.y/(4 + Difficulty_level);
  302.                                 force2.z = force.z/(4 + Difficulty_level);
  303.  
  304.                                 phys_apply_force(objp, force);
  305.                                 phys_apply_rot(objp, force2);
  306.                                 if (damage_flag) {
  307.                                         auto force_mag = vm_vec_mag_quick(force);
  308.                                         apply_force_damage(objp, force_mag, other_objp);
  309.                                 }
  310.                         }
  311.                 }
  312.         }
  313. }
  314.  
  315. //      -----------------------------------------------------------------------------
  316. //deal with two objects bumping into each other.  Apply force from collision
  317. //to each robot.  The flags tells whether the objects should take damage from
  318. //the collision.
  319. static void bump_two_objects(const vmobjptridx_t obj0,const vmobjptridx_t obj1,int damage_flag)
  320. {
  321.         const vmobjptridx_t *pt;
  322.         if ((obj0->movement_type != MT_PHYSICS && (pt = &obj1, true)) ||
  323.                 (obj1->movement_type != MT_PHYSICS && (pt = &obj0, true)))
  324.         {
  325.                 object_base &t = *pt;
  326.                 Assert(t.movement_type == MT_PHYSICS);
  327.                 const auto force = vm_vec_copy_scale(t.mtype.phys_info.velocity, -t.mtype.phys_info.mass);
  328.                 phys_apply_force(t,force);
  329.                 return;
  330.         }
  331.  
  332.         auto force = vm_vec_sub(obj0->mtype.phys_info.velocity,obj1->mtype.phys_info.velocity);
  333.         vm_vec_scale2(force,2*fixmul(obj0->mtype.phys_info.mass,obj1->mtype.phys_info.mass),(obj0->mtype.phys_info.mass+obj1->mtype.phys_info.mass));
  334.  
  335.         bump_this_object(obj1, obj0, force, damage_flag);
  336.         bump_this_object(obj0, obj1, vm_vec_negated(force), damage_flag);
  337. }
  338.  
  339. void bump_one_object(object_base &obj0, const vms_vector &hit_dir, fix damage)
  340. {
  341.         const auto hit_vec = vm_vec_copy_scale(hit_dir, damage);
  342.         phys_apply_force(obj0,hit_vec);
  343. }
  344.  
  345. namespace dsx {
  346. static void collide_player_and_wall(const vmobjptridx_t playerobj, const fix hitspeed, const vmsegptridx_t hitseg, const unsigned hitwall, const vms_vector &hitpt)
  347. {
  348.         auto &Objects = LevelUniqueObjectState.Objects;
  349.         auto &vmobjptr = Objects.vmptr;
  350.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  351.         fix damage;
  352.  
  353.         if (get_player_id(playerobj) != Player_num) // Execute only for local player
  354.                 return;
  355.  
  356.         const auto tmap_num = hitseg->unique_segment::sides[hitwall].tmap_num;
  357.  
  358.         //      If this wall does damage, don't make *BONK* sound, we'll be making another sound.
  359.         if (TmapInfo[tmap_num].damage > 0)
  360.                 return;
  361.  
  362. #if defined(DXX_BUILD_DESCENT_I)
  363.         const int ForceFieldHit = 0;
  364. #elif defined(DXX_BUILD_DESCENT_II)
  365.         int ForceFieldHit=0;
  366.         if (TmapInfo[tmap_num].flags & TMI_FORCE_FIELD) {
  367.                 vms_vector force;
  368.  
  369.                 PALETTE_FLASH_ADD(0, 0, 60);    //flash blue
  370.  
  371.                 //knock player around
  372.                 force.x = 40*(d_rand() - 16384);
  373.                 force.y = 40*(d_rand() - 16384);
  374.                 force.z = 40*(d_rand() - 16384);
  375.                 phys_apply_rot(playerobj, force);
  376.  
  377.                 //make sound
  378.                 multi_digi_link_sound_to_pos(SOUND_FORCEFIELD_BOUNCE_PLAYER, hitseg, 0, hitpt, 0, f1_0);
  379.                 ForceFieldHit=1;
  380.         }
  381.         else
  382. #endif
  383.         {
  384.                 auto &player_info = get_local_plrobj().ctype.player_info;
  385.                 wall_hit_process(player_info.powerup_flags, hitseg, hitwall, 20, get_player_id(playerobj), playerobj);
  386.         }
  387.  
  388.         //      ** Damage from hitting wall **
  389.         //      If the player has less than 10% shields, don't take damage from bump
  390. #if defined(DXX_BUILD_DESCENT_I)
  391.         damage = hitspeed / WALL_DAMAGE_SCALE;
  392. #elif defined(DXX_BUILD_DESCENT_II)
  393.         // Note: Does quad damage if hit a force field - JL
  394.         damage = (hitspeed / WALL_DAMAGE_SCALE) * (ForceFieldHit*8 + 1);
  395.  
  396.         const auto tmap_num2 = hitseg->unique_segment::sides[hitwall].tmap_num2;
  397.  
  398.         //don't do wall damage and sound if hit lava or water
  399.         if ((TmapInfo[tmap_num].flags & (TMI_WATER|TMI_VOLATILE)) || (tmap_num2 && (TmapInfo[tmap_num2&0x3fff].flags & (TMI_WATER|TMI_VOLATILE))))
  400.                 damage = 0;
  401. #endif
  402.  
  403.         if (damage >= WALL_DAMAGE_THRESHOLD) {
  404.                 int     volume;
  405.                 volume = (hitspeed-(WALL_DAMAGE_SCALE*WALL_DAMAGE_THRESHOLD)) / WALL_LOUDNESS_SCALE ;
  406.  
  407.                 create_awareness_event(playerobj, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState);
  408.  
  409.                 if ( volume > F1_0 )
  410.                         volume = F1_0;
  411.                 if (volume > 0 && !ForceFieldHit) {  // uhhhgly hack
  412.                         multi_digi_link_sound_to_pos(SOUND_PLAYER_HIT_WALL, hitseg, 0, hitpt, 0, volume);
  413.                 }
  414.  
  415.                 auto &player_info = playerobj->ctype.player_info;
  416.                 if (!(player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE))
  417.                         if (playerobj->shields > f1_0*10 || ForceFieldHit)
  418.                                 apply_damage_to_player( playerobj, playerobj, damage, 0 );
  419.  
  420.                 // -- No point in doing this unless we compute a reasonable hitpt.  Currently it is just the player's position. --MK, 01/18/96
  421.                 // -- if (!(TmapInfo[Segments[hitseg].unique_segment::sides[hitwall].tmap_num].flags & TMI_FORCE_FIELD)) {
  422.                 // --   vms_vector      hitpt1;
  423.                 // --   int                     hitseg1;
  424.                 // --
  425.                 // --           vm_vec_avg(&hitpt1, hitpt, &Objects[Players[Player_num].objnum].pos);
  426.                 // --   hitseg1 = find_point_seg(&hitpt1, Objects[Players[Player_num].objnum].segnum);
  427.                 // --   if (hitseg1 != -1)
  428.                 // --           object_create_explosion( hitseg, hitpt, Weapon_info[0].impact_size, Weapon_info[0].wall_hit_vclip );
  429.                 // -- }
  430.  
  431.         }
  432.  
  433.         return;
  434. }
  435. }
  436.  
  437. static fix64    Last_volatile_scrape_time = 0;
  438. static fix64    Last_volatile_scrape_sound_time = 0;
  439.  
  440. namespace dsx {
  441.  
  442. #if defined(DXX_BUILD_DESCENT_I)
  443. static
  444. #endif
  445. //see if wall is volatile or water
  446. //if volatile, cause damage to player
  447. //returns 1=lava, 2=water
  448. volatile_wall_result check_volatile_wall(const vmobjptridx_t obj, const unique_side &side)
  449. {
  450.         Assert(obj->type==OBJ_PLAYER);
  451.  
  452.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  453.         const auto &ti = TmapInfo[side.tmap_num];
  454.         const fix d = ti.damage;
  455.         if (d > 0
  456. #if defined(DXX_BUILD_DESCENT_II)
  457.                 || (ti.flags & TMI_WATER)
  458. #endif
  459.                 )
  460.         {
  461. #if defined(DXX_BUILD_DESCENT_II)
  462.                 if (get_player_id(obj) == Player_num)
  463. #endif
  464.                 {
  465.                         if (!((GameTime64 > Last_volatile_scrape_time + DESIGNATED_GAME_FRAMETIME) || (GameTime64 < Last_volatile_scrape_time)))
  466.                                 return volatile_wall_result::none;
  467.                         Last_volatile_scrape_time = GameTime64;
  468.  
  469. #if defined(DXX_BUILD_DESCENT_II)
  470.                         if (d > 0)
  471. #endif
  472.                         {
  473.                                 fix damage = fixmul(d,((FrameTime>DESIGNATED_GAME_FRAMETIME)?FrameTime:DESIGNATED_GAME_FRAMETIME));
  474.  
  475. #if defined(DXX_BUILD_DESCENT_II)
  476.                                 if (GameUniqueState.Difficulty_level == 0)
  477.                                         damage /= 2;
  478. #endif
  479.  
  480.                                 if (!(obj->ctype.player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE))
  481.                                         apply_damage_to_player( obj, obj, damage, 0 );
  482.  
  483.                                 PALETTE_FLASH_ADD(f2i(damage*4), 0, 0); //flash red
  484.                         }
  485.  
  486.                         obj->mtype.phys_info.rotvel.x = (d_rand() - 16384)/2;
  487.                         obj->mtype.phys_info.rotvel.z = (d_rand() - 16384)/2;
  488.                 }
  489.  
  490.                 return
  491. #if defined(DXX_BUILD_DESCENT_II)
  492.                         (d <= 0)
  493.                         ? volatile_wall_result::water
  494.                         :
  495. #endif
  496.                         volatile_wall_result::lava;
  497.         }
  498.         else
  499.          {
  500.                 return volatile_wall_result::none;
  501.          }
  502. }
  503.  
  504. //this gets called when an object is scraping along the wall
  505. bool scrape_player_on_wall(const vmobjptridx_t obj, const vmsegptridx_t hitseg, const unsigned hitside, const vms_vector &hitpt)
  506. {
  507.         if (obj->type != OBJ_PLAYER || get_player_id(obj) != Player_num)
  508.                 return false;
  509.  
  510.         const auto type = check_volatile_wall(obj, hitseg->unique_segment::sides[hitside]);
  511.         if (type != volatile_wall_result::none)
  512.         {
  513.                 if ((GameTime64 > Last_volatile_scrape_sound_time + F1_0/4) || (GameTime64 < Last_volatile_scrape_sound_time)) {
  514.                         Last_volatile_scrape_sound_time = GameTime64;
  515.                         const auto sound =
  516. #if defined(DXX_BUILD_DESCENT_II)
  517.                                 (type != volatile_wall_result::lava)
  518.                                 ? SOUND_SHIP_IN_WATER
  519.                                 :
  520. #endif
  521.                                 SOUND_VOLATILE_WALL_HISS;
  522.                         multi_digi_link_sound_to_pos(sound, hitseg, 0, hitpt, 0, F1_0);
  523.                 }
  524.  
  525.                 auto hit_dir = hitseg->shared_segment::sides[hitside].normals[0];
  526.  
  527.                 vm_vec_scale_add2(hit_dir, make_random_vector(), F1_0/8);
  528.                 vm_vec_normalize_quick(hit_dir);
  529.                 bump_one_object(obj, hit_dir, F1_0*8);
  530.  
  531.                 return true;
  532.         }
  533.         return false;
  534. }
  535.  
  536. #if defined(DXX_BUILD_DESCENT_II)
  537. static int effect_parent_is_guidebot(fvcobjptr &vcobjptr, const laser_parent &laser)
  538. {
  539.         if (laser.parent_type != OBJ_ROBOT)
  540.                 return 0;
  541.         const auto &&robot = vcobjptr(laser.parent_num);
  542.         if (robot->type != OBJ_ROBOT)
  543.                 return 0;
  544.         if (robot->signature != laser.parent_signature)
  545.                 /* parent replaced, no idea what it once was */
  546.                 return 0;
  547.         const auto robot_id = get_robot_id(robot);
  548.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  549.         auto &robptr = Robot_info[robot_id];
  550.         return robot_is_companion(robptr);
  551. }
  552. #endif
  553.  
  554. //if an effect is hit, and it can blow up, then blow it up
  555. //returns true if it blew up
  556. int check_effect_blowup(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const d_vclip_array &Vclip, const vmsegptridx_t seg, unsigned side, const vms_vector &pnt, const laser_parent &blower, int force_blowup_flag, int remote)
  557. {
  558.         auto &Effects = LevelUniqueEffectsClipState.Effects;
  559.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  560.         int tm;
  561.  
  562. #if defined(DXX_BUILD_DESCENT_I)
  563.         static constexpr std::integral_constant<int, 0> force_blowup_flag{};
  564. #elif defined(DXX_BUILD_DESCENT_II)
  565.         auto &Objects = LevelUniqueObjectState.Objects;
  566.         auto &vcobjptr = Objects.vcptr;
  567.         const auto wall_num = seg->shared_segment::sides[side].wall_num;
  568.  
  569.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  570.         auto &vcwallptr = Walls.vcptr;
  571.         const auto is_trigger = wall_num != wall_none && vcwallptr(wall_num)->trigger != trigger_none;
  572.         if (is_trigger)
  573.         {
  574.                 if (force_blowup_flag || (
  575.                                 (Game_mode & GM_MULTI)
  576.         // If this wall has a trigger and the blower-upper is not the player or the buddy, abort!
  577.         // For Multiplayer perform an additional check to see if it's a local-player hit. If a remote player hits, a packet is expected (remote 1) which would be followed by MULTI_TRIGGER to ensure sync with the switch and the actual trigger.
  578.                                 ? (!(blower.parent_type == OBJ_PLAYER && (blower.parent_num == get_local_player().objnum || remote)))
  579.                                 : !(blower.parent_type == OBJ_PLAYER || effect_parent_is_guidebot(vcobjptr, blower)))
  580.                         )
  581.                         return(0);
  582.         }
  583. #endif
  584.  
  585.         if ((tm=seg->unique_segment::sides[side].tmap_num2) != 0) {
  586.                 int tmf = tm&0xc000;            //tm flags
  587.                 tm &= 0x3fff;                   //tm without flags
  588.  
  589.                 const auto ec = TmapInfo[tm].eclip_num;
  590.                 unsigned db = 0;
  591. #if defined(DXX_BUILD_DESCENT_I)
  592.                 if (ec != eclip_none && (db = Effects[ec].dest_bm_num) != ~0u && !(Effects[ec].flags & EF_ONE_SHOT))
  593. #elif defined(DXX_BUILD_DESCENT_II)
  594.                 //check if it's an animation (monitor) or casts light
  595.                 if ((ec != eclip_none && ((db = Effects[ec].dest_bm_num) != ~0u && !(Effects[ec].flags & EF_ONE_SHOT))) || (ec == eclip_none && (TmapInfo[tm].destroyed != -1)))
  596. #endif
  597.                 {
  598.                         const grs_bitmap *bm = &GameBitmaps[Textures[tm].index];
  599.                         int x=0,y=0,t;
  600.  
  601.                         PIGGY_PAGE_IN(Textures[tm]);
  602.  
  603.                         //this can be blown up...did we hit it?
  604.  
  605.                         if (!force_blowup_flag) {
  606.                                 const auto hitpoint = find_hitpoint_uv(pnt,seg,side,0); //evil: always say face zero
  607.                                 auto &u = hitpoint.u;
  608.                                 auto &v = hitpoint.v;
  609.  
  610.                                 x = static_cast<unsigned>(f2i(u*bm->bm_w)) % bm->bm_w;
  611.                                 y = static_cast<unsigned>(f2i(v*bm->bm_h)) % bm->bm_h;
  612.  
  613.                                 switch (tmf) {          //adjust for orientation of paste-on
  614.                                         case 0x0000:    break;
  615.                                         case 0x4000:    t=y; y=x; x=bm->bm_w-t-1; break;
  616.                                         case 0x8000:    y=bm->bm_h-y-1; x=bm->bm_w-x-1; break;
  617.                                         case 0xc000:    t=x; x=y; y=bm->bm_h-t-1; break;
  618.                                 }
  619.                                 bm = rle_expand_texture(*bm);
  620.                         }
  621.  
  622. #if defined(DXX_BUILD_DESCENT_I)
  623.                         if (gr_gpixel (*bm, x, y) != TRANSPARENCY_COLOR)
  624. #elif defined(DXX_BUILD_DESCENT_II)
  625.                         if (force_blowup_flag || (bm->bm_data[y*bm->bm_w+x] != TRANSPARENCY_COLOR))
  626. #endif
  627.                         {               //not trans, thus on effect
  628.                                 int vc,sound_num;
  629.  
  630. #if defined(DXX_BUILD_DESCENT_II)
  631.                                 if ((Game_mode & GM_MULTI) && Netgame.AlwaysLighting)
  632.                                         if (!(ec != eclip_none && db != -1 && !(Effects[ec].flags & EF_ONE_SHOT)))
  633.                                                 return 0;
  634.  
  635.                                 //note: this must get called before the texture changes,
  636.                                 //because we use the light value of the texture to change
  637.                                 //the static light in the segment
  638.                                 subtract_light(LevelSharedDestructibleLightState, seg, side);
  639.  
  640.                                 // we blew up something connected to a trigger. Send it to others!
  641.                                 if ((Game_mode & GM_MULTI) && is_trigger && !remote && !force_blowup_flag)
  642.                                         multi_send_effect_blowup(seg, side, pnt);
  643. #endif
  644.                                 if (Newdemo_state == ND_STATE_RECORDING)
  645.                                         newdemo_record_effect_blowup( seg, side, pnt);
  646.  
  647.                                 fix dest_size;
  648.                                 if (ec != eclip_none)
  649.                                 {
  650.                                         dest_size = Effects[ec].dest_size;
  651.                                         vc = Effects[ec].dest_vclip;
  652.                                 }
  653.                                 else
  654.                                 {
  655.                                         dest_size = i2f(20);
  656.                                         vc = 3;
  657.                                 }
  658.  
  659.                                 object_create_explosion( seg, pnt, dest_size, vc );
  660.  
  661. #if defined(DXX_BUILD_DESCENT_II)
  662.                                 if (ec != eclip_none && db != -1 && !(Effects[ec].flags & EF_ONE_SHOT))
  663. #endif
  664.                                 {
  665.  
  666.                                         if ((sound_num = Vclip[vc].sound_num) != -1)
  667.                                                 digi_link_sound_to_pos( sound_num, seg, 0, pnt,  0, F1_0 );
  668.  
  669.                                         if ((sound_num=Effects[ec].sound_num)!=-1)              //kill sound
  670.                                                 digi_kill_sound_linked_to_segment(seg,side,sound_num);
  671.  
  672.                                         if (Effects[ec].dest_eclip!=-1 && Effects[Effects[ec].dest_eclip].segnum == segment_none)
  673.                                         {
  674.                                                 int bm_num;
  675.                                                 eclip *new_ec;
  676.  
  677.                                                 new_ec = &Effects[Effects[ec].dest_eclip];
  678.                                                 bm_num = new_ec->changing_wall_texture;
  679.  
  680.                                                 new_ec->time_left = new_ec->vc.frame_time;
  681.                                                 new_ec->frame_count = 0;
  682.                                                 new_ec->segnum = seg;
  683.                                                 new_ec->sidenum = side;
  684.                                                 new_ec->flags |= EF_ONE_SHOT;
  685.                                                 new_ec->dest_bm_num = Effects[ec].dest_bm_num;
  686.  
  687.                                                 assert(bm_num != 0);
  688.                                                 auto &tmap_num2 = seg->unique_segment::sides[side].tmap_num2;
  689.                                                 assert(tmap_num2 != 0);
  690.                                                 tmap_num2 = bm_num | tmf;               //replace with destroyed
  691.  
  692.                                         }
  693.                                         else {
  694.                                                 assert(db != 0);
  695.                                                 auto &tmap_num2 = seg->unique_segment::sides[side].tmap_num2;
  696.                                                 assert(tmap_num2 != 0);
  697.                                                 tmap_num2 = db | tmf;           //replace with destroyed
  698.                                         }
  699.                                 }
  700. #if defined(DXX_BUILD_DESCENT_II)
  701.                                 else {
  702.                                         seg->unique_segment::sides[side].tmap_num2 = TmapInfo[tm].destroyed | tmf;
  703.  
  704.                                         //assume this is a light, and play light sound
  705.                                         digi_link_sound_to_pos( SOUND_LIGHT_BLOWNUP, seg, 0, pnt,  0, F1_0 );
  706.                                 }
  707. #endif
  708.  
  709.                                 return 1;               //blew up!
  710.                         }
  711.                 }
  712.         }
  713.  
  714.         return 0;               //didn't blow up
  715. }
  716. }
  717.  
  718. //these gets added to the weapon's values when the weapon hits a volitle wall
  719. #define VOLATILE_WALL_EXPL_STRENGTH i2f(10)
  720. #define VOLATILE_WALL_IMPACT_SIZE       i2f(3)
  721. #define VOLATILE_WALL_DAMAGE_FORCE      i2f(5)
  722. #define VOLATILE_WALL_DAMAGE_RADIUS     i2f(30)
  723.  
  724. // int Show_seg_and_side = 0;
  725.  
  726. namespace dsx {
  727. static window_event_result collide_weapon_and_wall(
  728. #if defined(DXX_BUILD_DESCENT_II)
  729.         const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
  730. #endif
  731.         object_array &Objects, fvmsegptridx &vmsegptridx, const vmobjptridx_t weapon, const vmsegptridx_t hitseg, const unsigned hitwall, const vms_vector &hitpt)
  732. {
  733.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  734.         auto &imobjptridx = Objects.imptridx;
  735.         auto &vcobjptr = Objects.vcptr;
  736.         auto &vmobjptr = Objects.vmptr;
  737.         int blew_up;
  738.         int playernum;
  739.         auto result = window_event_result::handled;
  740.  
  741. #if defined(DXX_BUILD_DESCENT_I)
  742.         if (weapon->mtype.phys_info.flags & PF_BOUNCE)
  743.                 return window_event_result::ignored;
  744.         auto &uhitside = hitseg->unique_segment::sides[hitwall];
  745. #elif defined(DXX_BUILD_DESCENT_II)
  746.         auto &vcobjptridx = Objects.vcptridx;
  747.         if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
  748.                 if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
  749.                         return window_event_result::ignored;
  750.  
  751.         //      If this is a guided missile and it strikes fairly directly, clear bounce flag.
  752.         if (get_weapon_id(weapon) == weapon_id_type::GUIDEDMISS_ID) {
  753.                 fix     dot;
  754.  
  755.                 dot = vm_vec_dot(weapon->orient.fvec, hitseg->shared_segment::sides[hitwall].normals[0]);
  756.                 if (dot < -F1_0/6) {
  757.                         weapon->mtype.phys_info.flags &= ~PF_BOUNCE;
  758.                 }
  759.         }
  760.  
  761.         auto &uhitside = hitseg->unique_segment::sides[hitwall];
  762.         //if an energy weapon hits a forcefield, let it bounce
  763.         if ((TmapInfo[uhitside.tmap_num].flags & TMI_FORCE_FIELD) &&
  764.                  !(weapon->type == OBJ_WEAPON && Weapon_info[get_weapon_id(weapon)].energy_usage==0)) {
  765.  
  766.                 //make sound
  767.                 multi_digi_link_sound_to_pos(SOUND_FORCEFIELD_BOUNCE_WEAPON, hitseg, 0, hitpt, 0, f1_0);
  768.                 return window_event_result::ignored;    //bail here. physics code will bounce this object
  769.         }
  770.  
  771.         #ifndef NDEBUG
  772.         if (keyd_pressed[KEY_LAPOSTRO])
  773.                 if (weapon->ctype.laser_info.parent_num == get_local_player().objnum) {
  774.                         //      MK: Real pain when you need to know a seg:side and you've got quad lasers.
  775.                         HUD_init_message(HM_DEFAULT, "Hit at segment = %hu, side = %i", static_cast<vmsegptridx_t::integral_type>(hitseg), hitwall);
  776.                         if (get_weapon_id(weapon) < 4)
  777.                                 subtract_light(LevelSharedDestructibleLightState, hitseg, hitwall);
  778.                         else if (get_weapon_id(weapon) == weapon_id_type::FLARE_ID)
  779.                                 add_light(LevelSharedDestructibleLightState, hitseg, hitwall);
  780.                 }
  781.  
  782.                 //@@#ifdef EDITOR
  783.                 //@@Cursegp = &Segments[hitseg];
  784.                 //@@Curside = hitwall;
  785.                 //@@#endif
  786.         #endif
  787. #endif
  788.  
  789.         if ((weapon->mtype.phys_info.velocity.x == 0) && (weapon->mtype.phys_info.velocity.y == 0) && (weapon->mtype.phys_info.velocity.z == 0)) {
  790.                 Int3(); //      Contact Matt: This is impossible.  A weapon with 0 velocity hit a wall, which doesn't move.
  791.                 return window_event_result::ignored;
  792.         }
  793.  
  794.         blew_up = check_effect_blowup(LevelSharedDestructibleLightState, Vclip, hitseg, hitwall, hitpt, weapon->ctype.laser_info, 0, 0);
  795.  
  796. #if defined(DXX_BUILD_DESCENT_I)
  797.         const unsigned robot_escort = 0;
  798. #elif defined(DXX_BUILD_DESCENT_II)
  799.         const unsigned robot_escort = effect_parent_is_guidebot(vcobjptr, weapon->ctype.laser_info);
  800.         if (robot_escort) {
  801.                 /* Guidebot is always associated with the host */
  802.                 playernum = 0;
  803.         }
  804.         else
  805. #endif
  806.         {
  807.                 auto &objp = *vcobjptr(weapon->ctype.laser_info.parent_num);
  808.                 if (objp.type == OBJ_PLAYER)
  809.                         playernum = get_player_id(objp);
  810.                 else
  811.                         playernum = -1;         //not a player (thus a robot)
  812.         }
  813.  
  814.         auto &plrobj = get_local_plrobj();
  815.         auto &player_info = plrobj.ctype.player_info;
  816. #if defined(DXX_BUILD_DESCENT_II)
  817.         if (blew_up) {          //could be a wall switch
  818.                 //for wall triggers, always say that the player shot it out.  This is
  819.                 //because robots can shoot out wall triggers, and so the trigger better
  820.                 //take effect
  821.                 //      NO -- Changed by MK, 10/18/95.  We don't want robots blowing puzzles.  Only player or buddy can open!
  822.                 result = check_trigger(hitseg, hitwall, plrobj, vcobjptridx(weapon->ctype.laser_info.parent_num), 1);
  823.         }
  824.  
  825.         if (get_weapon_id(weapon) == weapon_id_type::EARTHSHAKER_ID)
  826.                 smega_rock_stuff();
  827. #endif
  828.  
  829.         const auto wall_type = wall_hit_process(player_info.powerup_flags, hitseg, hitwall, weapon->shields, playernum, weapon);
  830.  
  831.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  832.         // Wall is volatile if either tmap 1 or 2 is volatile
  833.         if ((TmapInfo[uhitside.tmap_num].flags & TMI_VOLATILE) ||
  834.                 (uhitside.tmap_num2 && (TmapInfo[uhitside.tmap_num2 & 0x3fff].flags & TMI_VOLATILE))
  835.                 )
  836.         {
  837.                 weapon_info *wi = &Weapon_info[get_weapon_id(weapon)];
  838.  
  839.                 //we've hit a volatile wall
  840.  
  841.                 digi_link_sound_to_pos( SOUND_VOLATILE_WALL_HIT,hitseg, 0, hitpt, 0, F1_0 );
  842.  
  843.                 //      New by MK: If powerful badass, explode as badass, not due to lava, fixes megas being wimpy in lava.
  844.                 if (wi->damage_radius >= VOLATILE_WALL_DAMAGE_RADIUS/2) {
  845.                         explode_badass_weapon(weapon, hitpt);
  846.                 }
  847.                 else
  848.                 {
  849.                         object_create_badass_explosion( weapon, hitseg, hitpt,
  850.                                 wi->impact_size + VOLATILE_WALL_IMPACT_SIZE,
  851.                 //for most weapons, use volatile wall hit.  For mega, use its special vclip
  852.                                 (get_weapon_id(weapon) == weapon_id_type::MEGA_ID) ? wi->robot_hit_vclip : VCLIP_VOLATILE_WALL_HIT,
  853.                                 wi->strength[Difficulty_level]/4+VOLATILE_WALL_EXPL_STRENGTH,   //      diminished by mk on 12/08/94, i was doing 70 damage hitting lava on lvl 1.
  854.                                 wi->damage_radius+VOLATILE_WALL_DAMAGE_RADIUS,
  855.                                 wi->strength[Difficulty_level]/2+VOLATILE_WALL_DAMAGE_FORCE,
  856.                                 imobjptridx(weapon->ctype.laser_info.parent_num));
  857.                 }
  858.  
  859.                 weapon->flags |= OF_SHOULD_BE_DEAD;             //make flares die in lava
  860.  
  861.         }
  862. #if defined(DXX_BUILD_DESCENT_II)
  863.         else if ((TmapInfo[uhitside.tmap_num].flags & TMI_WATER) ||
  864.                         (uhitside.tmap_num2 && (TmapInfo[uhitside.tmap_num2 & 0x3fff].flags & TMI_WATER))
  865.                         )
  866.         {
  867.                 weapon_info *wi = &Weapon_info[get_weapon_id(weapon)];
  868.  
  869.                 //we've hit water
  870.  
  871.                 //      MK: 09/13/95: Badass in water is 1/2 normal intensity.
  872.                 if ( Weapon_info[get_weapon_id(weapon)].matter ) {
  873.  
  874.                         digi_link_sound_to_pos( SOUND_MISSILE_HIT_WATER,hitseg, 0, hitpt, 0, F1_0 );
  875.  
  876.                         if ( Weapon_info[get_weapon_id(weapon)].damage_radius ) {
  877.  
  878.                                 digi_link_sound_to_object(SOUND_BADASS_EXPLOSION, weapon, 0, F1_0, sound_stack::allow_stacking);
  879.  
  880.                                 //      MK: 09/13/95: Badass in water is 1/2 normal intensity.
  881.                                 object_create_badass_explosion( weapon, hitseg, hitpt,
  882.                                         wi->impact_size/2,
  883.                                         wi->robot_hit_vclip,
  884.                                         wi->strength[Difficulty_level]/4,
  885.                                         wi->damage_radius,
  886.                                         wi->strength[Difficulty_level]/2,
  887.                                         imobjptridx(weapon->ctype.laser_info.parent_num));
  888.                         }
  889.                         else
  890.                                 object_create_explosion(vmsegptridx(weapon->segnum), weapon->pos, Weapon_info[get_weapon_id(weapon)].impact_size, Weapon_info[get_weapon_id(weapon)].wall_hit_vclip);
  891.  
  892.                 } else {
  893.                         digi_link_sound_to_pos( SOUND_LASER_HIT_WATER,hitseg, 0, hitpt, 0, F1_0 );
  894.                         object_create_explosion(vmsegptridx(weapon->segnum), weapon->pos, Weapon_info[get_weapon_id(weapon)].impact_size, VCLIP_WATER_HIT);
  895.                 }
  896.  
  897.                 weapon->flags |= OF_SHOULD_BE_DEAD;             //make flares die in water
  898.  
  899.         }
  900. #endif
  901.         else {
  902.  
  903. #if defined(DXX_BUILD_DESCENT_II)
  904.                 if (weapon->mtype.phys_info.flags & PF_BOUNCE) {
  905.  
  906.                         //do special bound sound & effect
  907.  
  908.                 }
  909.                 else
  910. #endif
  911.                 {
  912.  
  913.                         //if it's not the player's weapon, or it is the player's and there
  914.                         //is no wall, and no blowing up monitor, then play sound
  915.                         if ((weapon->ctype.laser_info.parent_type != OBJ_PLAYER) ||     ((hitseg->shared_segment::sides[hitwall].wall_num == wall_none || wall_type == wall_hit_process_t::WHP_NOT_SPECIAL) && !blew_up))
  916.                                 if ((Weapon_info[get_weapon_id(weapon)].wall_hit_sound > -1 ) && (!(weapon->flags & OF_SILENT)))
  917.                                 digi_link_sound_to_pos(Weapon_info[get_weapon_id(weapon)].wall_hit_sound, vmsegptridx(weapon->segnum), 0, weapon->pos, 0, F1_0);
  918.  
  919.                         if ( Weapon_info[get_weapon_id(weapon)].wall_hit_vclip > -1 )   {
  920.                                 if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
  921. #if defined(DXX_BUILD_DESCENT_I)
  922.                                         explode_badass_weapon(weapon, weapon->pos);
  923. #elif defined(DXX_BUILD_DESCENT_II)
  924.                                         explode_badass_weapon(weapon, hitpt);
  925. #endif
  926.                                 else
  927.                                         object_create_explosion(vmsegptridx(weapon->segnum), weapon->pos, Weapon_info[get_weapon_id(weapon)].impact_size, Weapon_info[get_weapon_id(weapon)].wall_hit_vclip);
  928.                         }
  929.                 }
  930.         }
  931.  
  932.         //      If weapon fired by player or companion...
  933.         if (( weapon->ctype.laser_info.parent_type== OBJ_PLAYER ) || robot_escort) {
  934.  
  935.                 if (!(weapon->flags & OF_SILENT) && (weapon->ctype.laser_info.parent_num == get_local_player().objnum))
  936.                         create_awareness_event(weapon, player_awareness_type_t::PA_WEAPON_WALL_COLLISION, LevelUniqueRobotAwarenessState);                      // object "weapon" can attract attention to player
  937.  
  938. //      We now allow flares to open doors.
  939.                 {
  940. #if defined(DXX_BUILD_DESCENT_I)
  941.                         if (get_weapon_id(weapon) != weapon_id_type::FLARE_ID)
  942.                                 weapon->flags |= OF_SHOULD_BE_DEAD;
  943. #elif defined(DXX_BUILD_DESCENT_II)
  944.                         if (((get_weapon_id(weapon) != weapon_id_type::FLARE_ID) || (weapon->ctype.laser_info.parent_type != OBJ_PLAYER)) && !(weapon->mtype.phys_info.flags & PF_BOUNCE))
  945.                                 weapon->flags |= OF_SHOULD_BE_DEAD;
  946.  
  947.                         //don't let flares stick in force fields
  948.                         if ((get_weapon_id(weapon) == weapon_id_type::FLARE_ID) && (TmapInfo[uhitside.tmap_num].flags & TMI_FORCE_FIELD))
  949.                                 weapon->flags |= OF_SHOULD_BE_DEAD;
  950. #endif
  951.  
  952.                         if (!(weapon->flags & OF_SILENT)) {
  953.                                 switch (wall_type) {
  954.  
  955.                                         case wall_hit_process_t::WHP_NOT_SPECIAL:
  956.                                                 //should be handled above
  957.                                                 //digi_link_sound_to_pos( Weapon_info[weapon->id].wall_hit_sound, weapon->segnum, 0, &weapon->pos, 0, F1_0 );
  958.                                                 break;
  959.  
  960.                                         case wall_hit_process_t::WHP_NO_KEY:
  961.                                                 //play special hit door sound (if/when we get it)
  962.                                                 multi_digi_link_sound_to_pos(SOUND_WEAPON_HIT_DOOR, vmsegptridx(weapon->segnum), 0, weapon->pos, 0, F1_0);
  963.  
  964.                                                 break;
  965.  
  966.                                         case wall_hit_process_t::WHP_BLASTABLE:
  967.                                                 //play special blastable wall sound (if/when we get it)
  968. #if defined(DXX_BUILD_DESCENT_II)
  969.                                                 if ((Weapon_info[get_weapon_id(weapon)].wall_hit_sound > -1 ) && (!(weapon->flags & OF_SILENT)))
  970. #endif
  971.                                                         digi_link_sound_to_pos(SOUND_WEAPON_HIT_BLASTABLE, vmsegptridx(weapon->segnum), 0, weapon->pos, 0, F1_0);
  972.                                                 break;
  973.  
  974.                                         case wall_hit_process_t::WHP_DOOR:
  975.                                                 //don't play anything, since door open sound will play
  976.                                                 break;
  977.                                 }
  978.                         } // else
  979.                 } // else {
  980.                         //      if (weapon->lifeleft <= 0)
  981.                         //      weapon->flags |= OF_SHOULD_BE_DEAD;
  982.                 // }
  983.  
  984.         } else {
  985.                 // This is a robot's laser
  986. #if defined(DXX_BUILD_DESCENT_II)
  987.                 if (!(weapon->mtype.phys_info.flags & PF_BOUNCE))
  988. #endif
  989.                         weapon->flags |= OF_SHOULD_BE_DEAD;
  990.         }
  991.  
  992.         return result;
  993. }
  994. }
  995.  
  996. static void collide_debris_and_wall(const vmobjptridx_t debris, const unique_segment &hitseg, const unsigned hitwall, const vms_vector &)
  997. {
  998.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  999.         if (!PERSISTENT_DEBRIS || TmapInfo[hitseg.sides[hitwall].tmap_num].damage)
  1000.                 explode_object(debris,0);
  1001. }
  1002.  
  1003. //      -------------------------------------------------------------------------------------------------------------------
  1004. static void collide_robot_and_robot(const vmobjptridx_t robot1, const vmobjptridx_t robot2, const vms_vector &)
  1005. {
  1006.         bump_two_objects(robot1, robot2, 1);
  1007.         return;
  1008. }
  1009.  
  1010. static void collide_robot_and_controlcen(object_base &obj_robot, const object_base &obj_cc, const vms_vector &)
  1011. {
  1012.         assert(obj_cc.type == OBJ_CNTRLCEN);
  1013.         assert(obj_robot.type == OBJ_ROBOT);
  1014.         const auto &&hitvec = vm_vec_normalized(vm_vec_sub(obj_cc.pos, obj_robot.pos));
  1015.         bump_one_object(obj_robot, hitvec, 0);
  1016. }
  1017.  
  1018. namespace dsx {
  1019. static void collide_robot_and_player(const vmobjptridx_t robot, const vmobjptridx_t playerobj, const vms_vector &collision_point)
  1020. {
  1021. #if defined(DXX_BUILD_DESCENT_II)
  1022.         int     steal_attempt = 0;
  1023.  
  1024.         if (robot->flags&OF_EXPLODING)
  1025.                 return;
  1026.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1027. #endif
  1028.  
  1029.         if (get_player_id(playerobj) == Player_num) {
  1030. #if defined(DXX_BUILD_DESCENT_II)
  1031.                 auto &robptr = Robot_info[get_robot_id(robot)];
  1032.                 if (robot_is_companion(robptr)) //      Player and companion don't collide.
  1033.                         return;
  1034.                 if (robptr.kamikaze) {
  1035.                         apply_damage_to_robot(robot, robot->shields+1, playerobj);
  1036.                         if (playerobj == ConsoleObject)
  1037.                                 add_points_to_score(playerobj->ctype.player_info, robptr.score_value);
  1038.                 }
  1039.  
  1040.                 if (robot_is_thief(robptr)) {
  1041.                         static fix64 Last_thief_hit_time;
  1042.                         ai_local                *ailp = &robot->ctype.ai_info.ail;
  1043.                         if (ailp->mode == ai_mode::AIM_THIEF_ATTACK)
  1044.                         {
  1045.                                 Last_thief_hit_time = GameTime64;
  1046.                                 attempt_to_steal_item(robot, playerobj);
  1047.                                 steal_attempt = 1;
  1048.                         } else if (GameTime64 - Last_thief_hit_time < F1_0*2)
  1049.                                 return;         //      ZOUNDS!  BRILLIANT!  Thief not collide with player if not stealing!
  1050.                                                                 // NO!  VERY DUMB!  makes thief look very stupid if player hits him while cloaked! -AP
  1051.                         else
  1052.                                 Last_thief_hit_time = GameTime64;
  1053.                 }
  1054. #endif
  1055.  
  1056.                 create_awareness_event(playerobj, player_awareness_type_t::PA_PLAYER_COLLISION, LevelUniqueRobotAwarenessState);                        // object robot can attract attention to player
  1057.                 do_ai_robot_hit_attack(robot, playerobj, collision_point);
  1058.                 do_ai_robot_hit(robot, player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION);
  1059.         }
  1060.         else
  1061.         {
  1062.                 multi_robot_request_change(robot, get_player_id(playerobj));
  1063.                 return; // only controlling player should make damage otherwise we might juggle robot back and forth, killing it instantly
  1064.         }
  1065.  
  1066.         if (check_collision_delayfunc_exec())
  1067.         {
  1068.                 const auto &&player_segp = Segments.vmptridx(playerobj->segnum);
  1069.                 const auto &&collision_seg = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, collision_point, player_segp);
  1070.  
  1071. #if defined(DXX_BUILD_DESCENT_II)
  1072.                 // added this if to remove the bump sound if it's the thief.
  1073.                 // A "steal" sound was added and it was getting obscured by the bump. -AP 10/3/95
  1074.                 //      Changed by MK to make this sound unless the robot stole.
  1075.                 if ((!steal_attempt) && !Robot_info[get_robot_id(robot)].energy_drain)
  1076. #endif
  1077.                         digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, player_segp, 0, collision_point, 0, F1_0);
  1078.  
  1079.                 if (collision_seg != segment_none)
  1080.                         object_create_explosion( collision_seg, collision_point, Weapon_info[0].impact_size, Weapon_info[0].wall_hit_vclip );
  1081.         }
  1082.  
  1083.         bump_two_objects(robot, playerobj, 1);
  1084.         return;
  1085. }
  1086. }
  1087.  
  1088. // Provide a way for network message to instantly destroy the control center
  1089. // without awarding points or anything.
  1090.  
  1091. //      if controlcen == NULL, that means don't do the explosion because the control center
  1092. //      was actually in another object.
  1093. void net_destroy_controlcen(const imobjptridx_t controlcen)
  1094. {
  1095.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1096.         if (LevelUniqueControlCenterState.Control_center_destroyed != 1)
  1097.         {
  1098.                 do_controlcen_destroyed_stuff(controlcen);
  1099.  
  1100.                 if ((controlcen != object_none) && !(controlcen->flags&(OF_EXPLODING|OF_DESTROYED))) {
  1101.                         digi_link_sound_to_pos(SOUND_CONTROL_CENTER_DESTROYED, vmsegptridx(controlcen->segnum), 0, controlcen->pos, 0, F1_0);
  1102.                         explode_object(controlcen,0);
  1103.                 }
  1104.         }
  1105.  
  1106. }
  1107.  
  1108. //      -----------------------------------------------------------------------------
  1109. void apply_damage_to_controlcen(const vmobjptridx_t controlcen, fix damage, const object &who)
  1110. {
  1111.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1112.         int     whotype;
  1113.  
  1114.         //      Only allow a player to damage the control center.
  1115.         whotype = who.type;
  1116.         if (whotype != OBJ_PLAYER) {
  1117.                 return;
  1118.         }
  1119.  
  1120.         if ((Game_mode & GM_MULTI) &&
  1121.                 !(Game_mode & GM_MULTI_COOP))
  1122.         {
  1123.                 auto &player = get_local_player();
  1124.                 const auto t = i2f(player.hours_level * 3600) + player.time_level;
  1125.                 if (t < Netgame.control_invul_time)
  1126.                 {
  1127.                 if (get_player_id(who) == Player_num) {
  1128.                         const auto r = f2i(Netgame.control_invul_time - t);
  1129.                         HUD_init_message(HM_DEFAULT, "%s %d:%02d.", TXT_CNTRLCEN_INVUL, r / 60, r % 60);
  1130.                 }
  1131.                 return;
  1132.                 }
  1133.         }
  1134.  
  1135.         if (get_player_id(who) == Player_num) {
  1136.                 LevelUniqueControlCenterState.Control_center_been_hit = 1;
  1137.                 ai_do_cloak_stuff();
  1138.         }
  1139.  
  1140.         if ( controlcen->shields >= 0 )
  1141.                 controlcen->shields -= damage;
  1142.  
  1143.         if ( (controlcen->shields < 0) && !(controlcen->flags&(OF_EXPLODING|OF_DESTROYED)) ) {
  1144.                 do_controlcen_destroyed_stuff(controlcen);
  1145.  
  1146.                 if (Game_mode & GM_MULTI) {
  1147.                         if (get_player_id(who) == Player_num)
  1148.                                 add_points_to_score(ConsoleObject->ctype.player_info, CONTROL_CEN_SCORE);
  1149.                         multi_send_destroy_controlcen(controlcen, get_player_id(who) );
  1150.                 }
  1151.                 else
  1152.                         add_points_to_score(ConsoleObject->ctype.player_info, CONTROL_CEN_SCORE);
  1153.  
  1154.                 digi_link_sound_to_pos(SOUND_CONTROL_CENTER_DESTROYED, vmsegptridx(controlcen->segnum), 0, controlcen->pos, 0, F1_0);
  1155.  
  1156.                 explode_object(controlcen,0);
  1157.         }
  1158. }
  1159.  
  1160. static void collide_player_and_controlcen(const vmobjptridx_t playerobj, const vmobjptridx_t controlcen, const vms_vector &collision_point)
  1161. {
  1162.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1163.         if (get_player_id(playerobj) == Player_num) {
  1164.                 LevelUniqueControlCenterState.Control_center_been_hit = 1;
  1165.                 ai_do_cloak_stuff();                            //      In case player cloaked, make control center know where he is.
  1166.         }
  1167.  
  1168.         if (check_collision_delayfunc_exec())
  1169.                 digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, vmsegptridx(playerobj->segnum), 0, collision_point, 0, F1_0);
  1170.  
  1171.         bump_two_objects(controlcen, playerobj, 1);
  1172.  
  1173.         return;
  1174. }
  1175.  
  1176. #if defined(DXX_BUILD_DESCENT_II)
  1177. static void collide_player_and_marker(const object_base &playerobj, const vmobjptridx_t marker, const vms_vector &)
  1178. {
  1179.         if (get_player_id(playerobj)==Player_num) {
  1180.                 int drawn;
  1181.  
  1182.                 const auto marker_id = get_marker_id(marker);
  1183.                 auto &msg = MarkerState.message[marker_id];
  1184.                 if (Game_mode & GM_MULTI)
  1185.                 {
  1186.                         const auto marker_owner = get_marker_owner(Game_mode, marker_id, Netgame.max_numplayers);
  1187.                         drawn = HUD_init_message(HM_DEFAULT|HM_MAYDUPL, "MARKER %s: %s", static_cast<const char *>(vcplayerptr(marker_owner)->callsign), &msg[0]);
  1188.                 }
  1189.                 else
  1190.                 {
  1191.                         const auto displayed_marker_id = static_cast<unsigned>(marker_id) + 1;
  1192.                         if (msg[0])
  1193.                                 drawn = HUD_init_message(HM_DEFAULT|HM_MAYDUPL, "MARKER %d: %s", displayed_marker_id, &msg[0]);
  1194.                         else
  1195.                                 drawn = HUD_init_message(HM_DEFAULT|HM_MAYDUPL, "MARKER %d", displayed_marker_id);
  1196.            }
  1197.  
  1198.                 if (drawn)
  1199.                         digi_play_sample( SOUND_MARKER_HIT, F1_0 );
  1200.  
  1201.                 detect_escort_goal_accomplished(marker);
  1202.    }
  1203. }
  1204. #endif
  1205.  
  1206. //      If a persistent weapon and other object is not a weapon, weaken it, else kill it.
  1207. //      If both objects are weapons, weaken the weapon.
  1208. namespace dsx {
  1209. static void maybe_kill_weapon(object_base &weapon, const object_base &other_obj)
  1210. {
  1211.         if (is_proximity_bomb_or_player_smart_mine_or_placed_mine(get_weapon_id(weapon))) {
  1212.                 weapon.flags |= OF_SHOULD_BE_DEAD;
  1213.                 return;
  1214.         }
  1215.  
  1216. #if defined(DXX_BUILD_DESCENT_I)
  1217.         if (weapon.mtype.phys_info.flags & PF_PERSISTENT || other_obj.type == OBJ_WEAPON)
  1218. #elif defined(DXX_BUILD_DESCENT_II)
  1219.         //      Changed, 10/12/95, MK: Make weapon-weapon collisions always kill both weapons if not persistent.
  1220.         //      Reason: Otherwise you can't use proxbombs to detonate incoming homing missiles (or mega missiles).
  1221.         if (weapon.mtype.phys_info.flags & PF_PERSISTENT)
  1222. #endif
  1223.         {
  1224.                 //      Weapons do a lot of damage to weapons, other objects do much less.
  1225.                 if (!(weapon.mtype.phys_info.flags & PF_PERSISTENT)) {
  1226.                         if (other_obj.type == OBJ_WEAPON)
  1227.                                 weapon.shields -= other_obj.shields/2;
  1228.                         else
  1229.                                 weapon.shields -= other_obj.shields/4;
  1230.  
  1231.                         if (weapon.shields <= 0) {
  1232.                                 weapon.shields = 0;
  1233.                                 weapon.flags |= OF_SHOULD_BE_DEAD;
  1234.                         }
  1235.                 }
  1236.         } else
  1237.                 weapon.flags |= OF_SHOULD_BE_DEAD;
  1238.  
  1239. // --   if ((weapon->mtype.phys_info.flags & PF_PERSISTENT) || (other_obj->type == OBJ_WEAPON)) {
  1240. // --           //      Weapons do a lot of damage to weapons, other objects do much less.
  1241. // --           if (!(weapon->mtype.phys_info.flags & PF_PERSISTENT)) {
  1242. // --                   if (other_obj->type == OBJ_WEAPON)
  1243. // --                           weapon->shields -= other_obj->shields/2;
  1244. // --                   else
  1245. // --                           weapon->shields -= other_obj->shields/4;
  1246. // --
  1247. // --                   if (weapon->shields <= 0) {
  1248. // --                           weapon->shields = 0;
  1249. // --                           weapon->flags |= OF_SHOULD_BE_DEAD;
  1250. // --                   }
  1251. // --           }
  1252. // --   } else
  1253. // --           weapon->flags |= OF_SHOULD_BE_DEAD;
  1254. }
  1255. }
  1256.  
  1257. namespace dsx {
  1258. static void collide_weapon_and_controlcen(const vmobjptridx_t weapon, const vmobjptridx_t controlcen, vms_vector &collision_point)
  1259. {
  1260.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1261.         auto &Objects = LevelUniqueObjectState.Objects;
  1262.         auto &vcobjptr = Objects.vcptr;
  1263.  
  1264. #if defined(DXX_BUILD_DESCENT_I)
  1265.         fix explosion_size = ((controlcen->size/3)*3)/4;
  1266. #elif defined(DXX_BUILD_DESCENT_II)
  1267.         if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
  1268.                 if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
  1269.                         return;
  1270.  
  1271.         fix explosion_size = controlcen->size*3/20;
  1272. #endif
  1273.         if (weapon->ctype.laser_info.parent_type == OBJ_PLAYER) {
  1274.                 fix     damage = weapon->shields;
  1275.  
  1276.                 /*
  1277.                 * Check if persistent weapon already hit this object. If yes, abort.
  1278.                 * If no, add this object to hitobj_list and do it's damage.
  1279.                 */
  1280.                 if (weapon->mtype.phys_info.flags & PF_PERSISTENT)
  1281.                 {
  1282.                         damage = weapon->shields*2; // to not alter Gameplay too much, multiply damage by 2.
  1283.                         if (weapon->ctype.laser_info.test_set_hitobj(controlcen))
  1284.                                 return;
  1285.                 }
  1286.  
  1287.                 if (get_player_id(vcobjptr(weapon->ctype.laser_info.parent_num)) == Player_num)
  1288.                         LevelUniqueControlCenterState.Control_center_been_hit = 1;
  1289.  
  1290.                 if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
  1291.                 {
  1292.                         const auto obj2weapon = vm_vec_sub(collision_point, controlcen->pos);
  1293.                         const auto mag = vm_vec_mag(obj2weapon);
  1294.                         if(mag < controlcen->size && mag > 0) // FVI code does not necessarily update the collision point for object2object collisions. Do that now.
  1295.                         {
  1296.                                 vm_vec_scale_add(collision_point, controlcen->pos, obj2weapon, fixdiv(controlcen->size, mag));
  1297.                                 weapon->pos = collision_point;
  1298.                         }
  1299. #if defined(DXX_BUILD_DESCENT_I)
  1300.                         explode_badass_weapon(weapon, weapon->pos);
  1301. #elif defined(DXX_BUILD_DESCENT_II)
  1302.                         explode_badass_weapon(weapon, collision_point);
  1303. #endif
  1304.                 }
  1305.                 else
  1306.                         object_create_explosion(vmsegptridx(controlcen->segnum), collision_point, explosion_size, VCLIP_SMALL_EXPLOSION);
  1307.  
  1308.                 digi_link_sound_to_pos(SOUND_CONTROL_CENTER_HIT, vmsegptridx(controlcen->segnum), 0, collision_point, 0, F1_0);
  1309.  
  1310.                 damage = fixmul(damage, weapon->ctype.laser_info.multiplier);
  1311.  
  1312.                 apply_damage_to_controlcen(controlcen, damage, vcobjptr(weapon->ctype.laser_info.parent_num));
  1313.  
  1314.                 maybe_kill_weapon(weapon,controlcen);
  1315.         } else {        //      If robot weapon hits control center, blow it up, make it go away, but do no damage to control center.
  1316.                 object_create_explosion(vmsegptridx(controlcen->segnum), collision_point, explosion_size, VCLIP_SMALL_EXPLOSION);
  1317.                 maybe_kill_weapon(weapon,controlcen);
  1318.         }
  1319.  
  1320. }
  1321. }
  1322.  
  1323. static void collide_weapon_and_clutter(object_base &weapon, const vmobjptridx_t clutter, const vms_vector &collision_point)
  1324. {
  1325.         short exp_vclip = VCLIP_SMALL_EXPLOSION;
  1326.  
  1327.         if ( clutter->shields >= 0 )
  1328.                 clutter->shields -= weapon.shields;
  1329.  
  1330.         digi_link_sound_to_pos(SOUND_LASER_HIT_CLUTTER, vmsegptridx(weapon.segnum), 0, collision_point, 0, F1_0);
  1331.  
  1332.         object_create_explosion(vmsegptridx(clutter->segnum), collision_point, ((clutter->size/3)*3)/4, exp_vclip);
  1333.  
  1334.         if ( (clutter->shields < 0) && !(clutter->flags&(OF_EXPLODING|OF_DESTROYED)))
  1335.                 explode_object(clutter,STANDARD_EXPL_DELAY);
  1336.  
  1337.         maybe_kill_weapon(weapon,clutter);
  1338. }
  1339.  
  1340. //--mk, 121094 -- extern void spin_robot(object *robot, vms_vector *collision_point);
  1341.  
  1342. #if defined(DXX_BUILD_DESCENT_II)
  1343. namespace dsx {
  1344.  
  1345. //      ------------------------------------------------------------------------------------------------------
  1346. window_event_result do_final_boss_frame(void)
  1347. {
  1348.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1349.         auto Final_boss_countdown_time = GameUniqueState.Final_boss_countdown_time;
  1350.         if (!Final_boss_countdown_time)
  1351.                 return window_event_result::ignored;
  1352.  
  1353.         if (!LevelUniqueControlCenterState.Control_center_destroyed)
  1354.                 return window_event_result::ignored;
  1355.  
  1356.         Final_boss_countdown_time -= FrameTime;
  1357.         if (Final_boss_countdown_time > 0)
  1358.         {
  1359.                 if (Final_boss_countdown_time <= F1_0*2)
  1360.                 {
  1361.                         int flash_value = f2i(((F1_0*2)-Final_boss_countdown_time)*16); // during final 2 seconds of final boss countdown (as set by do_final_boss_hacks()), make a flash value that maxes out over that time
  1362.                         PALETTE_FLASH_SET(-flash_value,-flash_value,-flash_value); // set palette to inverted flash_value to fade to black
  1363.                 }
  1364.                 GameUniqueState.Final_boss_countdown_time = Final_boss_countdown_time;
  1365.                 return window_event_result::ignored;
  1366.         }
  1367.         /* Ensure that GameUniqueState.Final_boss_countdown_time is not 0,
  1368.          * so that the flag remains raised.  If Final_boss_countdown_time
  1369.          * were greater than 0, the function already returned.  If
  1370.          * Final_boss_countdown_time is exactly 0, write -1.  Otherwise,
  1371.          * write a slightly more negative number.  If
  1372.          * Final_boss_countdown_time is INT32_MIN, this would roll over, but
  1373.          * that would only happen if FrameTime had an absurd value.
  1374.          */
  1375.         GameUniqueState.Final_boss_countdown_time = --Final_boss_countdown_time;
  1376.  
  1377.         return start_endlevel_sequence();               //pretend we hit the exit trigger
  1378. }
  1379.  
  1380. //      ------------------------------------------------------------------------------------------------------
  1381. //      This is all the ugly stuff we do when you kill the final boss so that you don't die or something
  1382. //      which would ruin the logic of the cut sequence.
  1383. void do_final_boss_hacks(void)
  1384. {
  1385.         auto &Objects = LevelUniqueObjectState.Objects;
  1386.         auto &vmobjptr = Objects.vmptr;
  1387.         if (Player_dead_state != player_dead_state::no)
  1388.         {
  1389.                 Int3();         //      Uh-oh, player is dead.  Try to rescue him.
  1390.                 Player_dead_state = player_dead_state::no;
  1391.         }
  1392.  
  1393.         auto &plrobj = get_local_plrobj();
  1394.         auto &player_info = plrobj.ctype.player_info;
  1395.         {
  1396.                 auto &shields = plrobj.shields;
  1397.                 if (shields <= 0)
  1398.                         shields = 1;
  1399.         }
  1400.  
  1401.         //      If you're not invulnerable, get invulnerable!
  1402.         auto &pl_flags = player_info.powerup_flags;
  1403.         if (!(pl_flags & PLAYER_FLAGS_INVULNERABLE)) {
  1404.                 pl_flags |= PLAYER_FLAGS_INVULNERABLE;
  1405.                 player_info.FakingInvul = 0;
  1406.                 player_info.invulnerable_time = GameTime64;
  1407.         }
  1408.         if (!(Game_mode & GM_MULTI))
  1409.                 buddy_message("Nice job, %s!", static_cast<const char *>(get_local_player().callsign));
  1410.  
  1411.         GameUniqueState.Final_boss_countdown_time = F1_0*4; // was F1_0*2 originally. extended to F1_0*4 to play fade to black which happened after countdown ended in the original game.
  1412. }
  1413.  
  1414. }
  1415. #endif
  1416.  
  1417. //      ------------------------------------------------------------------------------------------------------
  1418. //      Return 1 if robot died, else return 0
  1419. namespace dsx {
  1420. int apply_damage_to_robot(const vmobjptridx_t robot, fix damage, objnum_t killer_objnum)
  1421. {
  1422.         if ( robot->flags&OF_EXPLODING) return 0;
  1423.  
  1424.         if (robot->shields < 0 ) return 0;      //robot already dead...
  1425.  
  1426.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1427.         auto &robptr = Robot_info[get_robot_id(robot)];
  1428. #if defined(DXX_BUILD_DESCENT_II)
  1429.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  1430.         auto &Objects = LevelUniqueObjectState.Objects;
  1431.         auto &vmobjptr = Objects.vmptr;
  1432.         if (robptr.boss_flag)
  1433.                 BossUniqueState.Boss_hit_time = GameTime64;
  1434.  
  1435.         //      Buddy invulnerable on level 24 so he can give you his important messages.  Bah.
  1436.         //      Also invulnerable if his cheat for firing weapons is in effect.
  1437.         if (robot_is_companion(robptr)) {
  1438.                 if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
  1439.                         return 0;
  1440.         }
  1441. #endif
  1442.  
  1443.         robot->shields -= damage;
  1444.  
  1445. #if defined(DXX_BUILD_DESCENT_II)
  1446.         //      Do unspeakable hacks to make sure player doesn't die after killing boss.  Or before, sort of.
  1447.         if (robptr.boss_flag)
  1448.                 if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
  1449.                         if (robot->shields < 0)
  1450.                          {
  1451.                                 if (Game_mode & GM_MULTI)
  1452.                                   {
  1453.                                          if (!multi_all_players_alive(Objects.vcptr, partial_range(Players, N_players))) // everyones gotta be alive
  1454.                                            robot->shields=1;
  1455.                                          else
  1456.                                           {
  1457.                                             multi_send_finish_game();
  1458.                                             do_final_boss_hacks();
  1459.                                           }
  1460.  
  1461.                                         }
  1462.                                 else
  1463.                                   {     // NOTE LINK TO ABOVE!!!
  1464.                                         if (get_local_plrobj().shields < 0 || Player_dead_state != player_dead_state::no)
  1465.                                                 robot->shields = 1;             //      Sorry, we can't allow you to kill the final boss after you've died.  Rough luck.
  1466.                                         else
  1467.                                                 do_final_boss_hacks();
  1468.                                   }
  1469.                           }
  1470. #endif
  1471.  
  1472.         if (robot->shields < 0) {
  1473.                 auto &plr = get_local_player();
  1474.                 plr.num_kills_level++;
  1475.                 plr.num_kills_total++;
  1476.                 if (Game_mode & GM_MULTI) {
  1477.                         if (multi_explode_robot_sub(robot))
  1478.                         {
  1479.                                 multi_send_robot_explode(robot, killer_objnum);
  1480.                                 return 1;
  1481.                         }
  1482.                         else
  1483.                                 return 0;
  1484.                 }
  1485.  
  1486.                 if (robptr.boss_flag) {
  1487.                         start_boss_death_sequence(robot);       //do_controlcen_destroyed_stuff(NULL);
  1488.                 }
  1489. #if defined(DXX_BUILD_DESCENT_II)
  1490.                 else if (robptr.death_roll) {
  1491.                         start_robot_death_sequence(robot);      //do_controlcen_destroyed_stuff(NULL);
  1492.                 }
  1493. #endif
  1494.                 else {
  1495. #if defined(DXX_BUILD_DESCENT_II)
  1496.                         if (get_robot_id(robot) == SPECIAL_REACTOR_ROBOT)
  1497.                                 special_reactor_stuff();
  1498.                         if (robptr.kamikaze)
  1499.                                 explode_object(robot,1);                //      Kamikaze, explode right away, IN YOUR FACE!
  1500.                         else
  1501. #endif
  1502.                                 explode_object(robot,STANDARD_EXPL_DELAY);
  1503.                 }
  1504.                 return 1;
  1505.         } else
  1506.                 return 0;
  1507. }
  1508.  
  1509. #if defined(DXX_BUILD_DESCENT_II)
  1510. namespace {
  1511.  
  1512. enum class boss_weapon_collision_result : uint8_t
  1513. {
  1514.         normal,
  1515.         invulnerable,
  1516.         reflect,        // implies invulnerable
  1517. };
  1518.  
  1519. }
  1520.  
  1521. static inline int Boss_invulnerable_dot()
  1522. {
  1523.         return F1_0 / 4 - i2f(GameUniqueState.Difficulty_level) / 8;
  1524. }
  1525.  
  1526. //      ------------------------------------------------------------------------------------------------------
  1527. //      Return true if damage done to boss, else return false.
  1528. static boss_weapon_collision_result do_boss_weapon_collision(const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, const vcobjptridx_t robotptridx, object &weapon, const vms_vector &collision_point)
  1529. {
  1530.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  1531.         auto &BuddyState = LevelUniqueObjectState.BuddyState;
  1532.         const object_base &robot = robotptridx;
  1533.         int     d2_boss_index;
  1534.  
  1535.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1536.         d2_boss_index = Robot_info[get_robot_id(robot)].boss_flag - BOSS_D2;
  1537.  
  1538.         Assert((d2_boss_index >= 0) && (d2_boss_index < NUM_D2_BOSSES));
  1539.  
  1540.         //      See if should spew a bot.
  1541.         if (weapon.ctype.laser_info.parent_type == OBJ_PLAYER && weapon.ctype.laser_info.parent_num == get_local_player().objnum)
  1542.                 if ((Weapon_info[get_weapon_id(weapon)].matter
  1543.                         ? Boss_spews_bots_matter
  1544.                         : Boss_spews_bots_energy)[d2_boss_index])
  1545.                 {
  1546.                         if (Boss_spew_more[d2_boss_index])
  1547.                                 if (d_rand() > 16384) {
  1548.                                         const auto &&spew = boss_spew_robot(robot, collision_point);
  1549.                                         if (spew != object_none)
  1550.                                         {
  1551.                                                 BossUniqueState.Last_gate_time = GameTime64 - GameUniqueState.Boss_gate_interval - 1;   //      Force allowing spew of another bot.
  1552.                                                 multi_send_boss_create_robot(robotptridx, spew);
  1553.                                         }
  1554.                                 }
  1555.                         const auto &&spew = boss_spew_robot(robot, collision_point);
  1556.                         if (spew != object_none)
  1557.                                 multi_send_boss_create_robot(robotptridx, spew);
  1558.                 }
  1559.  
  1560.         if (Boss_invulnerable_spot[d2_boss_index]) {
  1561.                 fix                     dot;
  1562.                 //      Boss only vulnerable in back.  See if hit there.
  1563.                 //      Note, if BOSS_INVULNERABLE_DOT is close to F1_0 (in magnitude), then should probably use non-quick version.
  1564.                 const auto tvec1 = vm_vec_normalized_quick(vm_vec_sub(collision_point, robot.pos));
  1565.                 dot = vm_vec_dot(tvec1, robot.orient.fvec);
  1566.                 if (dot > Boss_invulnerable_dot()) {
  1567.                         if (const auto &&segp = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, collision_point, Segments.vmptridx(robot.segnum)))
  1568.                                 digi_link_sound_to_pos(SOUND_WEAPON_HIT_DOOR, segp, 0, collision_point, 0, F1_0);
  1569.                         if (BuddyState.Buddy_objnum != object_none)
  1570.                         {
  1571.                                 if (BuddyState.Buddy_gave_hint_count) {
  1572.                                         if (BuddyState.Last_time_buddy_gave_hint + F1_0*20 < GameTime64) {
  1573.                                                 int     sval;
  1574.  
  1575.                                                 BuddyState.Buddy_gave_hint_count--;
  1576.                                                 BuddyState.Last_time_buddy_gave_hint = GameTime64;
  1577.                                                 sval = (d_rand()*4) >> 15;
  1578.                                                 const char *msg;
  1579.                                                 switch (sval) {
  1580.                                                         case 0:
  1581.                                                                 msg = "Hit him in the back!";
  1582.                                                                 break;
  1583.                                                         case 1:
  1584.                                                                 msg = "He's invulnerable there!";
  1585.                                                                 break;
  1586.                                                         case 2:
  1587.                                                                 msg = "Get behind him and fire!";
  1588.                                                                 break;
  1589.                                                         case 3:
  1590.                                                         default:
  1591.                                                                 msg = "Hit the glowing spot!";
  1592.                                                                 break;
  1593.                                                 }
  1594.                                                 buddy_message_str(msg);
  1595.                                         }
  1596.                                 }
  1597.                         }
  1598.  
  1599.                         //      Cause weapon to bounce.
  1600.                         if (!Weapon_info[get_weapon_id(weapon)].matter) {
  1601.                                         fix                     speed;
  1602.  
  1603.                                         auto vec_to_point = vm_vec_normalized_quick(vm_vec_sub(collision_point, robot.pos));
  1604.                                         auto weap_vec = weapon.mtype.phys_info.velocity;
  1605.                                         speed = vm_vec_normalize_quick(weap_vec);
  1606.                                         vm_vec_scale_add2(vec_to_point, weap_vec, -F1_0*2);
  1607.                                         vm_vec_scale(vec_to_point, speed/4);
  1608.                                         weapon.mtype.phys_info.velocity = vec_to_point;
  1609.                                 return boss_weapon_collision_result::reflect;
  1610.                         }
  1611.                         return boss_weapon_collision_result::invulnerable;
  1612.                 }
  1613.         }
  1614.         else if ((Weapon_info[get_weapon_id(weapon)].matter ? Boss_invulnerable_matter : Boss_invulnerable_energy)[d2_boss_index])
  1615.         {
  1616.                 if (const auto &&segp = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, collision_point, Segments.vmptridx(robot.segnum)))
  1617.                         digi_link_sound_to_pos(SOUND_WEAPON_HIT_DOOR, segp, 0, collision_point, 0, F1_0);
  1618.                 return boss_weapon_collision_result::invulnerable;
  1619.         }
  1620.         return boss_weapon_collision_result::normal;
  1621. }
  1622. #endif
  1623.  
  1624. //      ------------------------------------------------------------------------------------------------------
  1625. static void collide_robot_and_weapon(const vmobjptridx_t  robot, const vmobjptridx_t  weapon, vms_vector &collision_point)
  1626. {
  1627.         auto &BossUniqueState = LevelUniqueObjectState.BossState;
  1628.         auto &Objects = LevelUniqueObjectState.Objects;
  1629.         auto &vcobjptr = Objects.vcptr;
  1630. #if defined(DXX_BUILD_DESCENT_II)
  1631.         auto &icobjptridx = Objects.icptridx;
  1632.         if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
  1633.                 if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
  1634.                         return;
  1635. #endif
  1636.  
  1637.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1638.         auto &robptr = Robot_info[get_robot_id(robot)];
  1639.         if (robptr.boss_flag)
  1640.         {
  1641.                 BossUniqueState.Boss_hit_this_frame = 1;
  1642. #if defined(DXX_BUILD_DESCENT_II)
  1643.                 BossUniqueState.Boss_hit_time = GameTime64;
  1644. #endif
  1645.         }
  1646. #if defined(DXX_BUILD_DESCENT_II)
  1647.         const boss_weapon_collision_result damage_flag = (robptr.boss_flag >= BOSS_D2)
  1648.                 ? do_boss_weapon_collision(LevelSharedSegmentState, LevelUniqueSegmentState, robot, weapon, collision_point)
  1649.                 : boss_weapon_collision_result::normal;
  1650. #endif
  1651.  
  1652. #if defined(DXX_BUILD_DESCENT_II)
  1653.         //      Put in at request of Jasen (and Adam) because the Buddy-Bot gets in their way.
  1654.         //      MK has so much fun whacking his butt around the mine he never cared...
  1655.         if ((robot_is_companion(robptr)) && ((weapon->ctype.laser_info.parent_type != OBJ_ROBOT) && !cheats.robotskillrobots))
  1656.                 return;
  1657.  
  1658.         if (get_weapon_id(weapon) == weapon_id_type::EARTHSHAKER_ID)
  1659.                 smega_rock_stuff();
  1660. #endif
  1661.  
  1662.         /*
  1663.          * Check if persistent weapon already hit this object. If yes, abort.
  1664.          * If no, add this object to hitobj_list and do it's damage.
  1665.          */
  1666.         if (weapon->mtype.phys_info.flags & PF_PERSISTENT)
  1667.         {
  1668.                 if (weapon->ctype.laser_info.test_set_hitobj(robot))
  1669.                         return;
  1670.         }
  1671.  
  1672.         if (laser_parent_is_object(weapon->ctype.laser_info, robot))
  1673.                 return;
  1674.  
  1675. #if defined(DXX_BUILD_DESCENT_II)
  1676.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  1677.         //      Changed, 10/04/95, put out blobs based on skill level and power of weapon doing damage.
  1678.         //      Also, only a weapon hit from a player weapon causes smart blobs.
  1679.         if ((weapon->ctype.laser_info.parent_type == OBJ_PLAYER) && (robptr.energy_blobs))
  1680.                 if ((robot->shields > 0) && Weapon_is_energy[get_weapon_id(weapon)]) {
  1681.                         fix     probval;
  1682.                         int     num_blobs;
  1683.  
  1684.                         probval = (Difficulty_level+2) * min(weapon->shields, robot->shields);
  1685.                         probval = robptr.energy_blobs * probval/(NDL*32);
  1686.  
  1687.                         num_blobs = probval >> 16;
  1688.                         if (2*d_rand() < (probval & 0xffff))
  1689.                                 num_blobs++;
  1690.  
  1691.                         if (num_blobs)
  1692.                                 create_robot_smart_children(robot, num_blobs);
  1693.                 }
  1694.  
  1695.         //      Note: If weapon hits an invulnerable boss, it will still do badass damage, including to the boss,
  1696.         //      unless this is trapped elsewhere.
  1697. #endif
  1698.         weapon_info *wi = &Weapon_info[get_weapon_id(weapon)];
  1699.         if ( wi->damage_radius )
  1700.         {
  1701.                 const auto obj2weapon = vm_vec_sub(collision_point, robot->pos);
  1702.                 const auto mag = vm_vec_mag(obj2weapon);
  1703.                 if(mag < robot->size && mag > 0) // FVI code does not necessarily update the collision point for object2object collisions. Do that now.
  1704.                 {
  1705.                         vm_vec_scale_add(collision_point, robot->pos, obj2weapon, fixdiv(robot->size, mag));
  1706.                         weapon->pos = collision_point;
  1707.                 }
  1708. #if defined(DXX_BUILD_DESCENT_I)
  1709.                 explode_badass_weapon(weapon, weapon->pos);
  1710. #elif defined(DXX_BUILD_DESCENT_II)
  1711.                 if (damage_flag != boss_weapon_collision_result::normal) {                      //don't make badass sound
  1712.  
  1713.                         //this code copied from explode_badass_weapon()
  1714.  
  1715.                         object_create_badass_explosion(weapon, vmsegptridx(weapon->segnum), collision_point,
  1716.                                                         wi->impact_size,
  1717.                                                         wi->robot_hit_vclip,
  1718.                                                         wi->strength[Difficulty_level],
  1719.                                                         wi->damage_radius,wi->strength[Difficulty_level],
  1720.                                                         icobjptridx(weapon->ctype.laser_info.parent_num));
  1721.  
  1722.                 }
  1723.                 else            //normal badass explosion
  1724.                         explode_badass_weapon(weapon, collision_point);
  1725. #endif
  1726.         }
  1727.  
  1728. #if defined(DXX_BUILD_DESCENT_I)
  1729.         if ( (weapon->ctype.laser_info.parent_type==OBJ_PLAYER) && !(robot->flags & OF_EXPLODING) )
  1730. #elif defined(DXX_BUILD_DESCENT_II)
  1731.         if ( ((weapon->ctype.laser_info.parent_type==OBJ_PLAYER) || cheats.robotskillrobots) && !(robot->flags & OF_EXPLODING) )
  1732. #endif
  1733.         {
  1734.                 if (weapon->ctype.laser_info.parent_num == get_local_player().objnum) {
  1735.                         create_awareness_event(weapon, player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION, LevelUniqueRobotAwarenessState);                     // object "weapon" can attract attention to player
  1736.                         do_ai_robot_hit(robot, player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION);
  1737.                 }
  1738.                 else
  1739.                         multi_robot_request_change(robot, get_player_id(vcobjptr(weapon->ctype.laser_info.parent_num)));
  1740.  
  1741.                 std::pair<fix, int> explosion_size_and_vclip;
  1742.                 if ((robptr.exp1_vclip_num > -1 && (explosion_size_and_vclip = {(robot->size / 2 * 3) / 4, robptr.exp1_vclip_num}, true))
  1743. #if defined(DXX_BUILD_DESCENT_II)
  1744.                         || (wi->robot_hit_vclip > -1 && (explosion_size_and_vclip = {wi->impact_size, wi->robot_hit_vclip}, true))
  1745. #endif
  1746.                         )
  1747.                 {
  1748.                         const auto &&expl_obj = object_create_explosion(vmsegptridx(weapon->segnum), collision_point, explosion_size_and_vclip.first, explosion_size_and_vclip.second);
  1749.  
  1750.                 if (expl_obj != object_none)
  1751.                                 obj_attach(Objects, robot, expl_obj);
  1752.                 }
  1753.  
  1754. #if defined(DXX_BUILD_DESCENT_II)
  1755.                 if (damage_flag == boss_weapon_collision_result::normal)
  1756. #endif
  1757.                 {
  1758.                         if (robptr.exp1_sound_num > -1)
  1759.                                 digi_link_sound_to_pos(robptr.exp1_sound_num, vcsegptridx(robot->segnum), 0, collision_point, 0, F1_0);
  1760.                         fix     damage = weapon->shields;
  1761.  
  1762.                                 damage = fixmul(damage, weapon->ctype.laser_info.multiplier);
  1763.  
  1764. #if defined(DXX_BUILD_DESCENT_II)
  1765.                         //      Cut Gauss damage on bosses because it just breaks the game.  Bosses are so easy to
  1766.                         //      hit, and missing a robot is what prevents the Gauss from being game-breaking.
  1767.                         if (get_weapon_id(weapon) == weapon_id_type::GAUSS_ID)
  1768.                                 if (robptr.boss_flag)
  1769.                                         damage = damage * (2*NDL-Difficulty_level)/(2*NDL);
  1770. #endif
  1771.  
  1772.                         if (! apply_damage_to_robot(robot, damage, weapon->ctype.laser_info.parent_num))
  1773.                                 bump_two_objects(robot, weapon, 0);             //only bump if not dead. no damage from bump
  1774.                         else if (laser_parent_is_player(vcobjptr, weapon->ctype.laser_info, *ConsoleObject))
  1775.                         {
  1776.                                 add_points_to_score(ConsoleObject->ctype.player_info, robptr.score_value);
  1777.                                 detect_escort_goal_accomplished(robot);
  1778.                         }
  1779.                 }
  1780.  
  1781. #if defined(DXX_BUILD_DESCENT_II)
  1782.                 //      If Gauss Cannon, spin robot.
  1783.                 if (!robot_is_companion(robptr) && (!robptr.boss_flag) && (get_weapon_id(weapon) == weapon_id_type::GAUSS_ID))
  1784.                 {
  1785.                         ai_static       *aip = &robot->ctype.ai_info;
  1786.  
  1787.                         if (aip->SKIP_AI_COUNT * FrameTime < F1_0) {
  1788.                                 aip->SKIP_AI_COUNT++;
  1789.                                 robot->mtype.phys_info.rotthrust.x = fixmul((d_rand() - 16384), FrameTime * aip->SKIP_AI_COUNT);
  1790.                                 robot->mtype.phys_info.rotthrust.y = fixmul((d_rand() - 16384), FrameTime * aip->SKIP_AI_COUNT);
  1791.                                 robot->mtype.phys_info.rotthrust.z = fixmul((d_rand() - 16384), FrameTime * aip->SKIP_AI_COUNT);
  1792.                                 robot->mtype.phys_info.flags |= PF_USES_THRUST;
  1793.  
  1794.                         }
  1795.                 }
  1796. #endif
  1797.  
  1798.         }
  1799.  
  1800. #if defined(DXX_BUILD_DESCENT_II)
  1801.         if (damage_flag != boss_weapon_collision_result::reflect)
  1802. #endif
  1803.         maybe_kill_weapon(weapon,robot);
  1804.  
  1805.         return;
  1806. }
  1807. }
  1808.  
  1809. static void collide_hostage_and_player(const vmobjptridx_t hostage, object &player, const vms_vector &)
  1810. {
  1811.         // Give player points, etc.
  1812.         if (&player == ConsoleObject)
  1813.         {
  1814.                 detect_escort_goal_accomplished(hostage);
  1815.                 add_points_to_score(player.ctype.player_info, HOSTAGE_SCORE);
  1816.  
  1817.                 // Do effect
  1818.                 hostage_rescue();
  1819.  
  1820.                 // Remove the hostage object.
  1821.                 hostage->flags |= OF_SHOULD_BE_DEAD;
  1822.  
  1823.                 if (Game_mode & GM_MULTI)
  1824.                         multi_send_remobj(hostage);
  1825.         }
  1826.         return;
  1827. }
  1828.  
  1829. static void collide_player_and_player(const vmobjptridx_t player1, const vmobjptridx_t player2, const vms_vector &collision_point)
  1830. {
  1831.         int damage_flag = 1;
  1832.  
  1833.         if (check_collision_delayfunc_exec())
  1834.                 digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, vcsegptridx(player1->segnum), 0, collision_point, 0, F1_0);
  1835.  
  1836.         // Multi is special - as always. Clients do the bump damage locally but the remote players do their collision result (change velocity) on their own. So after our initial collision, ignore further collision damage till remote players (hopefully) react.
  1837.         if (Game_mode & GM_MULTI)
  1838.         {
  1839.                 damage_flag = 0;
  1840.                 if (!(get_player_id(player1) == Player_num || get_player_id(player2) == Player_num))
  1841.                         return;
  1842.                 auto &last_player_bump = ((get_player_id(player1) == Player_num)
  1843.                         ? player2
  1844.                         : player1)->ctype.player_info.Last_bumped_local_player;
  1845.                 if (last_player_bump + (F1_0/Netgame.PacketsPerSec) < GameTime64 || last_player_bump > GameTime64)
  1846.                 {
  1847.                         last_player_bump = GameTime64;
  1848.                         damage_flag = 1;
  1849.                 }
  1850.         }
  1851.  
  1852.         bump_two_objects(player1, player2, damage_flag);
  1853.         return;
  1854. }
  1855.  
  1856. static imobjptridx_t maybe_drop_primary_weapon_egg(const object &playerobj, int weapon_index)
  1857. {
  1858.         int weapon_flag = HAS_PRIMARY_FLAG(weapon_index);
  1859.         const auto powerup_num = Primary_weapon_to_powerup[weapon_index];
  1860.         auto &player_info = playerobj.ctype.player_info;
  1861.         if (player_info.primary_weapon_flags & weapon_flag)
  1862.                 return call_object_create_egg(playerobj, 1, powerup_num);
  1863.         else
  1864.                 return object_none;
  1865. }
  1866.  
  1867. static void maybe_drop_secondary_weapon_egg(const object_base &playerobj, int weapon_index, int count)
  1868. {
  1869.         const auto powerup_num = Secondary_weapon_to_powerup[weapon_index];
  1870.                 int max_count = min(count, 3);
  1871.                 for (int i=0; i<max_count; i++)
  1872.                         call_object_create_egg(playerobj, 1, powerup_num);
  1873. }
  1874.  
  1875. static void drop_missile_1_or_4(const object &playerobj,int missile_index)
  1876. {
  1877.         unsigned num_missiles = playerobj.ctype.player_info.secondary_ammo[missile_index];
  1878.         const auto powerup_id = Secondary_weapon_to_powerup[missile_index];
  1879.  
  1880.         if (num_missiles > 10)
  1881.                 num_missiles = 10;
  1882.  
  1883.         call_object_create_egg(playerobj, num_missiles / 4, powerup_id + 1);
  1884.         call_object_create_egg(playerobj, num_missiles % 4, powerup_id);
  1885. }
  1886.  
  1887. namespace dsx {
  1888. void drop_player_eggs(const vmobjptridx_t playerobj)
  1889. {
  1890.         if ((playerobj->type == OBJ_PLAYER) || (playerobj->type == OBJ_GHOST)) {
  1891.                 // Seed the random number generator so in net play the eggs will always
  1892.                 // drop the same way
  1893.                 if (Game_mode & GM_MULTI)
  1894.                 {
  1895.                         Net_create_loc = 0;
  1896.                         d_srand(5483L);
  1897.                 }
  1898.                 auto &player_info = playerobj->ctype.player_info;
  1899.                 auto &plr_laser_level = player_info.laser_level;
  1900.                 if (const auto GrantedItems = (Game_mode & GM_MULTI) ? Netgame.SpawnGrantedItems : 0)
  1901.                 {
  1902.                         if (const auto granted_laser_level = map_granted_flags_to_laser_level(GrantedItems))
  1903.                         {
  1904.                                 if (plr_laser_level <= granted_laser_level)
  1905.                                         /* All levels were from grant */
  1906.                                         plr_laser_level = LASER_LEVEL_1;
  1907. #if defined(DXX_BUILD_DESCENT_II)
  1908.                                 else if (granted_laser_level > MAX_LASER_LEVEL)
  1909.                                 {
  1910.                                         /* Grant gives super laser 5.
  1911.                                          * Player has super laser 6.
  1912.                                          */
  1913.                                         -- plr_laser_level;
  1914.                                 }
  1915.                                 else if (plr_laser_level > MAX_LASER_LEVEL)
  1916.                                 {
  1917.                                         /* Grant gives only regular lasers.
  1918.                                          * Player has super lasers, will drop only
  1919.                                          * super lasers.
  1920.                                          */
  1921.                                 }
  1922. #endif
  1923.                                 else
  1924.                                         plr_laser_level -= granted_laser_level;
  1925.                         }
  1926.                         if (uint16_t subtract_vulcan_ammo = map_granted_flags_to_vulcan_ammo(GrantedItems))
  1927.                         {
  1928.                                 auto &v = playerobj->ctype.player_info.vulcan_ammo;
  1929.                                 if (v < subtract_vulcan_ammo)
  1930.                                         v = 0;
  1931.                                 else
  1932.                                         v -= subtract_vulcan_ammo;
  1933.                         }
  1934.                         player_info.powerup_flags &= ~map_granted_flags_to_player_flags(GrantedItems);
  1935.                         player_info.primary_weapon_flags &= ~map_granted_flags_to_primary_weapon_flags(GrantedItems);
  1936.                 }
  1937.  
  1938.                 auto &secondary_ammo = playerobj->ctype.player_info.secondary_ammo;
  1939. #if defined(DXX_BUILD_DESCENT_II)
  1940.                 //      If the player had smart mines, maybe arm one of them.
  1941.                 const auto drop_armed_bomb = [&](uint8_t mines, weapon_id_type id) {
  1942.                         mines %= 4;
  1943.                         for (int rthresh = 30000; mines && d_rand() < rthresh; rthresh /= 2)
  1944.                 {
  1945.                         const auto randvec = make_random_vector();
  1946.                         const auto tvec = vm_vec_add(playerobj->pos, randvec);
  1947.                         const auto &&newseg = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, tvec, Segments.vmptridx(playerobj->segnum));
  1948.                         if (newseg != segment_none)
  1949.                         {
  1950.                                 -- mines;
  1951.                                 Laser_create_new(randvec, tvec, newseg, playerobj, id, 0);
  1952.                         }
  1953.                 }
  1954.                 };
  1955.                 drop_armed_bomb(secondary_ammo[SMART_MINE_INDEX], weapon_id_type::SUPERPROX_ID);
  1956.  
  1957.                 //      If the player had proximity bombs, maybe arm one of them.
  1958.                 if (Game_mode & GM_MULTI)
  1959.                         drop_armed_bomb(secondary_ammo[PROXIMITY_INDEX], weapon_id_type::PROXIMITY_ID);
  1960. #endif
  1961.  
  1962.                 //      If the player dies and he has powerful lasers, create the powerups here.
  1963.  
  1964.                 std::pair<int, int> laser_level_and_id;
  1965.                 if (
  1966. #if defined(DXX_BUILD_DESCENT_II)
  1967.                         (plr_laser_level > MAX_LASER_LEVEL && (laser_level_and_id = {plr_laser_level - MAX_LASER_LEVEL, POW_SUPER_LASER}, true)) ||
  1968. #endif
  1969.                         (plr_laser_level && (laser_level_and_id = {plr_laser_level, POW_LASER}, true)))
  1970.                         call_object_create_egg(playerobj, laser_level_and_id.first, laser_level_and_id.second);
  1971.  
  1972.                 //      Drop quad laser if appropos
  1973.                 if (player_info.powerup_flags & PLAYER_FLAGS_QUAD_LASERS)
  1974.                         call_object_create_egg(playerobj, 1, POW_QUAD_FIRE);
  1975.  
  1976.                 if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
  1977.                         call_object_create_egg(playerobj, 1, POW_CLOAK);
  1978.  
  1979. #if defined(DXX_BUILD_DESCENT_II)
  1980.                 if (player_info.powerup_flags & PLAYER_FLAGS_MAP_ALL)
  1981.                         call_object_create_egg(playerobj, 1, POW_FULL_MAP);
  1982.  
  1983.                 if (player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER)
  1984.                         call_object_create_egg(playerobj, 1, POW_AFTERBURNER);
  1985.  
  1986.                 if (player_info.powerup_flags & PLAYER_FLAGS_AMMO_RACK)
  1987.                         call_object_create_egg(playerobj, 1, POW_AMMO_RACK);
  1988.  
  1989.                 if (player_info.powerup_flags & PLAYER_FLAGS_CONVERTER)
  1990.                         call_object_create_egg(playerobj, 1, POW_CONVERTER);
  1991.  
  1992.                 if (player_info.powerup_flags & PLAYER_FLAGS_HEADLIGHT)
  1993.                         call_object_create_egg(playerobj, 1, POW_HEADLIGHT);
  1994.  
  1995.                 // drop the other enemies flag if you have it
  1996.  
  1997.                 if (game_mode_capture_flag() && (player_info.powerup_flags & PLAYER_FLAGS_FLAG))
  1998.                 {
  1999.                         call_object_create_egg(playerobj, 1, get_team(get_player_id(playerobj)) == TEAM_RED ? POW_FLAG_BLUE : POW_FLAG_RED);
  2000.                 }
  2001.  
  2002.  
  2003.                 if (game_mode_hoard())
  2004.                 {
  2005.                         // Drop hoard orbs
  2006.                         for (unsigned max_count = std::min<uint8_t>(player_info.hoard.orbs, player_info.max_hoard_orbs); max_count--;)
  2007.                                 call_object_create_egg(playerobj, 1, POW_HOARD_ORB);
  2008.                 }
  2009. #endif
  2010.  
  2011.                 //Drop the vulcan, gauss, and ammo
  2012.                 auto vulcan_ammo = playerobj->ctype.player_info.vulcan_ammo;
  2013. #if defined(DXX_BUILD_DESCENT_I)
  2014.                 const auto HAS_VULCAN_AND_GAUSS_FLAGS = HAS_VULCAN_FLAG;
  2015. #elif defined(DXX_BUILD_DESCENT_II)
  2016.                 const auto HAS_VULCAN_AND_GAUSS_FLAGS = HAS_VULCAN_FLAG | HAS_GAUSS_FLAG;
  2017.                 if ((player_info.primary_weapon_flags & HAS_VULCAN_AND_GAUSS_FLAGS) == HAS_VULCAN_AND_GAUSS_FLAGS)
  2018.                         vulcan_ammo /= 2;               //if both vulcan & gauss, each gets half
  2019. #endif
  2020.                 if (vulcan_ammo < VULCAN_AMMO_AMOUNT)
  2021.                         vulcan_ammo = VULCAN_AMMO_AMOUNT;       //make sure gun has at least as much as a powerup
  2022.                 auto objnum = maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::VULCAN_INDEX);
  2023.                 if (objnum!=object_none)
  2024.                         objnum->ctype.powerup_info.count = vulcan_ammo;
  2025. #if defined(DXX_BUILD_DESCENT_II)
  2026.                 objnum = maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::GAUSS_INDEX);
  2027.                 if (objnum!=object_none)
  2028.                         objnum->ctype.powerup_info.count = vulcan_ammo;
  2029. #endif
  2030.  
  2031.                 //      Drop the rest of the primary weapons
  2032.                 maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::SPREADFIRE_INDEX);
  2033.                 maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::PLASMA_INDEX);
  2034.                 maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::FUSION_INDEX);
  2035.  
  2036. #if defined(DXX_BUILD_DESCENT_II)
  2037.                 maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::HELIX_INDEX);
  2038.                 maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::PHOENIX_INDEX);
  2039.  
  2040.                 objnum = maybe_drop_primary_weapon_egg(playerobj, primary_weapon_index_t::OMEGA_INDEX);
  2041.                 if (objnum!=object_none)
  2042.                         objnum->ctype.powerup_info.count = (get_player_id(playerobj) == Player_num) ? playerobj->ctype.player_info.Omega_charge : MAX_OMEGA_CHARGE;
  2043. #endif
  2044.  
  2045.                 //      Drop the secondary weapons
  2046.                 //      Note, proximity weapon only comes in packets of 4.  So drop n/2, but a max of 3 (handled inside maybe_drop..)  Make sense?
  2047.  
  2048.                 maybe_drop_secondary_weapon_egg(playerobj, PROXIMITY_INDEX, secondary_ammo[PROXIMITY_INDEX] / 4);
  2049.  
  2050.                 maybe_drop_secondary_weapon_egg(playerobj, SMART_INDEX, secondary_ammo[SMART_INDEX]);
  2051.                 maybe_drop_secondary_weapon_egg(playerobj, MEGA_INDEX, secondary_ammo[MEGA_INDEX]);
  2052.  
  2053. #if defined(DXX_BUILD_DESCENT_II)
  2054.                 maybe_drop_secondary_weapon_egg(playerobj, SMART_MINE_INDEX,(secondary_ammo[SMART_MINE_INDEX])/4);
  2055.                 maybe_drop_secondary_weapon_egg(playerobj, SMISSILE5_INDEX, secondary_ammo[SMISSILE5_INDEX]);
  2056. #endif
  2057.  
  2058.                 //      Drop the player's missiles in packs of 1 and/or 4
  2059.                 drop_missile_1_or_4(playerobj,HOMING_INDEX);
  2060. #if defined(DXX_BUILD_DESCENT_II)
  2061.                 drop_missile_1_or_4(playerobj,GUIDED_INDEX);
  2062. #endif
  2063.                 drop_missile_1_or_4(playerobj,CONCUSSION_INDEX);
  2064. #if defined(DXX_BUILD_DESCENT_II)
  2065.                 drop_missile_1_or_4(playerobj,SMISSILE1_INDEX);
  2066.                 drop_missile_1_or_4(playerobj,SMISSILE4_INDEX);
  2067. #endif
  2068.  
  2069.                 //      If player has vulcan ammo, but no vulcan or gauss cannon, drop the ammo.
  2070.                 if (!(player_info.primary_weapon_flags & HAS_VULCAN_AND_GAUSS_FLAGS))
  2071.                 {
  2072.                         auto amount = player_info.vulcan_ammo;
  2073.                         if (amount > 200) {
  2074.                                 amount = 200;
  2075.                         }
  2076.                         if (amount)
  2077.                                 for (;;)
  2078.                         {
  2079.                                 call_object_create_egg(playerobj, 1, POW_VULCAN_AMMO);
  2080.                                 if (amount <= VULCAN_AMMO_AMOUNT)
  2081.                                         break;
  2082.                                 amount -= VULCAN_AMMO_AMOUNT;
  2083.                         }
  2084.                 }
  2085.  
  2086.                 //      Always drop a shield and energy powerup.
  2087.                 if (Game_mode & GM_MULTI) {
  2088.                         call_object_create_egg(playerobj, 1, POW_SHIELD_BOOST);
  2089.                         call_object_create_egg(playerobj, 1, POW_ENERGY);
  2090.                 }
  2091.         }
  2092. }
  2093. }
  2094.  
  2095. namespace dsx {
  2096.  
  2097. void apply_damage_to_player(object &playerobj, const icobjptridx_t killer, fix damage, ubyte possibly_friendly)
  2098. {
  2099. #if defined(DXX_BUILD_DESCENT_II)
  2100.         auto &BuddyState = LevelUniqueObjectState.BuddyState;
  2101. #endif
  2102.         if (Player_dead_state != player_dead_state::no)
  2103.                 return;
  2104.  
  2105.         auto &player_info = playerobj.ctype.player_info;
  2106.         if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
  2107.                 return;
  2108.  
  2109.         if (possibly_friendly && multi_maybe_disable_friendly_fire(killer))
  2110.                 return;
  2111.  
  2112.         if (Endlevel_sequence)
  2113.                 return;
  2114.  
  2115.         //for the player, the 'real' shields are maintained in the Players[]
  2116.         //array.  The shields value in the player's object are, I think, not
  2117.         //used anywhere.  This routine, however, sets the objects shields to
  2118.         //be a mirror of the value in the Player structure.
  2119.  
  2120.         if (get_player_id(playerobj) == Player_num) {           //is this the local player?
  2121.                 PALETTE_FLASH_ADD(f2i(damage)*4,-f2i(damage/2),-f2i(damage/2)); //flash red
  2122.                 if (playerobj.shields < 0)
  2123.                 {
  2124.                         /*
  2125.                          * If player is already dead (but Player_dead_state has not
  2126.                          * caught up yet), preserve the original killer data.
  2127.                          */
  2128.                         assert(playerobj.flags & OF_SHOULD_BE_DEAD);
  2129.                         return;
  2130.                 }
  2131.                 const auto shields = (playerobj.shields -= damage);
  2132.  
  2133.                 if (shields < 0)
  2134.                 {
  2135.                         playerobj.ctype.player_info.killer_objnum = killer;
  2136.                         playerobj.flags |= OF_SHOULD_BE_DEAD;
  2137.  
  2138. #if defined(DXX_BUILD_DESCENT_II)
  2139.                         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2140.                                 if (killer && killer->type == OBJ_ROBOT && robot_is_companion(Robot_info[get_robot_id(killer)]))
  2141.                                         BuddyState.Buddy_sorry_time = GameTime64;
  2142. #endif
  2143.                 }
  2144.         }
  2145. }
  2146.  
  2147. }
  2148.  
  2149. namespace dsx {
  2150. static void collide_player_and_weapon(const vmobjptridx_t playerobj, const vmobjptridx_t weapon, vms_vector &collision_point)
  2151. {
  2152.         auto &Objects = LevelUniqueObjectState.Objects;
  2153.         auto &imobjptridx = Objects.imptridx;
  2154.         fix             damage = weapon->shields;
  2155.  
  2156. #if defined(DXX_BUILD_DESCENT_II)
  2157.         if (get_weapon_id(weapon) == weapon_id_type::OMEGA_ID)
  2158.                 if (!ok_to_do_omega_damage(weapon)) // see comment in laser.c
  2159.                         return;
  2160.  
  2161.         //      Don't collide own smart mines unless direct hit.
  2162.         if (get_weapon_id(weapon) == weapon_id_type::SUPERPROX_ID)
  2163.                 if (playerobj == weapon->ctype.laser_info.parent_num)
  2164.                         if (vm_vec_dist_quick(collision_point, playerobj->pos) > playerobj->size)
  2165.                                 return;
  2166.  
  2167.         if (get_weapon_id(weapon) == weapon_id_type::EARTHSHAKER_ID)
  2168.                 smega_rock_stuff();
  2169. #endif
  2170.  
  2171.         damage = fixmul(damage, weapon->ctype.laser_info.multiplier);
  2172. #if defined(DXX_BUILD_DESCENT_II)
  2173.         if (Game_mode & GM_MULTI)
  2174.                 damage = fixmul(damage, Weapon_info[get_weapon_id(weapon)].multi_damage_scale);
  2175. #endif
  2176.  
  2177. #if 1
  2178.         /*
  2179.          * Check if persistent weapon already hit this object. If yes, abort.
  2180.          * If no, add this object to hitobj_list and do it's damage.
  2181.          */
  2182.         if (weapon->mtype.phys_info.flags & PF_PERSISTENT)
  2183.         {
  2184.                 if (weapon->ctype.laser_info.test_set_hitobj(playerobj))
  2185.                         return;
  2186.         }
  2187. #endif
  2188.  
  2189.         const auto &&player_segp = vmsegptridx(playerobj->segnum);
  2190.         if (get_player_id(playerobj) == Player_num)
  2191.         {
  2192.                 auto &player_info = playerobj->ctype.player_info;
  2193.                 multi_digi_link_sound_to_pos((player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE) ? SOUND_WEAPON_HIT_DOOR : SOUND_PLAYER_GOT_HIT, player_segp, 0, collision_point, 0, F1_0);
  2194.         }
  2195.  
  2196.         object_create_explosion(player_segp, collision_point, i2f(10)/2, VCLIP_PLAYER_HIT);
  2197.         if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
  2198.         {
  2199.                 const auto obj2weapon = vm_vec_sub(collision_point, playerobj->pos);
  2200.                 const auto mag = vm_vec_mag(obj2weapon);
  2201.                 if(mag > 0) // FVI code does not necessarily update the collision point for object2object collisions. Do that now.
  2202.                 {
  2203.                         vm_vec_scale_add(collision_point, playerobj->pos, obj2weapon, fixdiv(playerobj->size, mag));
  2204.                         weapon->pos = collision_point;
  2205.                 }
  2206. #if defined(DXX_BUILD_DESCENT_I)
  2207.                 explode_badass_weapon(weapon, weapon->pos);
  2208. #elif defined(DXX_BUILD_DESCENT_II)
  2209.                 explode_badass_weapon(weapon, collision_point);
  2210. #endif
  2211.         }
  2212.  
  2213.         maybe_kill_weapon(weapon,playerobj);
  2214.  
  2215.         bump_two_objects(playerobj, weapon, 0); //no damage from bump
  2216.  
  2217.         if ( !Weapon_info[get_weapon_id(weapon)].damage_radius ) {
  2218.                 imobjptridx_t killer = object_none;
  2219.                 if ( weapon->ctype.laser_info.parent_num != object_none )
  2220.                         killer = imobjptridx(weapon->ctype.laser_info.parent_num);
  2221.  
  2222. //              if (weapon->id == SMART_HOMING_ID)
  2223. //                      damage /= 4;
  2224.  
  2225.                         apply_damage_to_player( playerobj, killer, damage, 1);
  2226.         }
  2227.  
  2228.         //      Robots become aware of you if you get hit.
  2229.         ai_do_cloak_stuff();
  2230.  
  2231.         return;
  2232. }
  2233. }
  2234.  
  2235. //      Nasty robots are the ones that attack you by running into you and doing lots of damage.
  2236. void collide_player_and_nasty_robot(const vmobjptridx_t playerobj, const vmobjptridx_t robot, const vms_vector &collision_point)
  2237. {
  2238.         const auto &&player_segp = vmsegptridx(playerobj->segnum);
  2239.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2240.         digi_link_sound_to_pos(Robot_info[get_robot_id(robot)].claw_sound, player_segp, 0, collision_point, 0, F1_0);
  2241.         object_create_explosion(player_segp, collision_point, i2f(10)/2, VCLIP_PLAYER_HIT);
  2242.  
  2243.         bump_two_objects(playerobj, robot, 0);  //no damage from bump
  2244.  
  2245.         apply_damage_to_player(playerobj, robot, F1_0 * (GameUniqueState.Difficulty_level + 1), 0);
  2246. }
  2247.  
  2248. static vms_vector find_exit_direction(vms_vector result, const object &objp, const cscusegment segp)
  2249. {
  2250.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  2251.         auto &Vertices = LevelSharedVertexState.get_vertices();
  2252.         auto &vcvertptr = Vertices.vcptr;
  2253.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  2254.         auto &vcwallptr = Walls.vcptr;
  2255.         for (unsigned side = MAX_SIDES_PER_SEGMENT; side --;)
  2256.                 if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, side) & WID_FLY_FLAG)
  2257.                 {
  2258.                         const auto &&exit_point = compute_center_point_on_side(vcvertptr, segp, side);
  2259.                         vm_vec_add2(result, vm_vec_normalized_quick(vm_vec_sub(exit_point, objp.pos)));
  2260.                         break;
  2261.                 }
  2262.         return result;
  2263. }
  2264.  
  2265. namespace dsx {
  2266. void collide_player_and_materialization_center(const vmobjptridx_t objp)
  2267. {
  2268.         const auto &&segp = vmsegptridx(objp->segnum);
  2269.         digi_link_sound_to_pos(SOUND_PLAYER_GOT_HIT, segp, 0, objp->pos, 0, F1_0);
  2270.         object_create_explosion(segp, objp->pos, i2f(10)/2, VCLIP_PLAYER_HIT);
  2271.  
  2272.         if (get_player_id(objp) != Player_num)
  2273.                 return;
  2274.  
  2275.         auto rand_vec = make_random_vector();
  2276.         rand_vec.x /= 4;
  2277.         rand_vec.y /= 4;
  2278.         rand_vec.z /= 4;
  2279.         auto exit_dir = find_exit_direction(rand_vec, objp, segp);
  2280.         vm_vec_normalize_quick(exit_dir);
  2281.         bump_one_object(objp, exit_dir, 64*F1_0);
  2282.  
  2283. #if defined(DXX_BUILD_DESCENT_I)
  2284.         apply_damage_to_player( objp, object_none, 4*F1_0, 0);
  2285. #elif defined(DXX_BUILD_DESCENT_II)
  2286.         apply_damage_to_player( objp, objp, 4*F1_0, 0); //      Changed, MK, 2/19/96, make killer the player, so if you die in matcen, will say you killed yourself
  2287. #endif
  2288.  
  2289.         return;
  2290.  
  2291. }
  2292. }
  2293.  
  2294. void collide_robot_and_materialization_center(const vmobjptridx_t objp)
  2295. {
  2296.         const auto &&segp = vmsegptridx(objp->segnum);
  2297.         digi_link_sound_to_pos(SOUND_ROBOT_HIT, segp, 0, objp->pos, 0, F1_0);
  2298.  
  2299.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  2300.         if ( Robot_info[get_robot_id(objp)].exp1_vclip_num > -1 )
  2301.                 object_create_explosion(segp, objp->pos, (objp->size / 2 * 3) / 4, Robot_info[get_robot_id(objp)].exp1_vclip_num);
  2302.  
  2303.         auto exit_dir = find_exit_direction({}, objp, segp);
  2304.         bump_one_object(objp, exit_dir, 8*F1_0);
  2305.  
  2306.         apply_damage_to_robot( objp, F1_0, object_none);
  2307.  
  2308.         return;
  2309. }
  2310.  
  2311. void collide_live_local_player_and_powerup(const vmobjptridx_t powerup)
  2312. {
  2313.         if (do_powerup(powerup))
  2314.         {
  2315.                 powerup->flags |= OF_SHOULD_BE_DEAD;
  2316.                 if (Game_mode & GM_MULTI)
  2317.                         multi_send_remobj(powerup);
  2318.         }
  2319. }
  2320.  
  2321. static void collide_player_and_powerup(object &playerobj, const vmobjptridx_t powerup, const vms_vector &)
  2322. {
  2323.         if (!Endlevel_sequence &&
  2324.                 Player_dead_state == player_dead_state::no &&
  2325.                 get_player_id(playerobj) == Player_num)
  2326.         {
  2327.                 collide_live_local_player_and_powerup(powerup);
  2328.         }
  2329.         else if ((Game_mode & GM_MULTI_COOP) && (get_player_id(playerobj) != Player_num))
  2330.         {
  2331.                 switch (get_powerup_id(powerup)) {
  2332.                         case POW_KEY_BLUE:
  2333.                                 playerobj.ctype.player_info.powerup_flags |= PLAYER_FLAGS_BLUE_KEY;
  2334.                                 break;
  2335.                         case POW_KEY_RED:
  2336.                                 playerobj.ctype.player_info.powerup_flags |= PLAYER_FLAGS_RED_KEY;
  2337.                                 break;
  2338.                         case POW_KEY_GOLD:
  2339.                                 playerobj.ctype.player_info.powerup_flags |= PLAYER_FLAGS_GOLD_KEY;
  2340.                                 break;
  2341.                         default:
  2342.                                 break;
  2343.                 }
  2344.         }
  2345.         return;
  2346. }
  2347.  
  2348. static void collide_player_and_clutter(const vmobjptridx_t  playerobj, const vmobjptridx_t  clutter, const vms_vector &collision_point)
  2349. {
  2350.         if (check_collision_delayfunc_exec())
  2351.                 digi_link_sound_to_pos(SOUND_ROBOT_HIT_PLAYER, vcsegptridx(playerobj->segnum), 0, collision_point, 0, F1_0);
  2352.         bump_two_objects(clutter, playerobj, 1);
  2353.         return;
  2354. }
  2355.  
  2356. //      See if weapon1 creates a badass explosion.  If so, create the explosion
  2357. //      Return true if weapon does proximity (as opposed to only contact) damage when it explodes.
  2358. namespace dsx {
  2359. int maybe_detonate_weapon(const vmobjptridx_t weapon1, object &weapon2, const vms_vector &collision_point)
  2360. {
  2361.         if ( Weapon_info[get_weapon_id(weapon1)].damage_radius ) {
  2362.                 auto dist = vm_vec_dist_quick(weapon1->pos, weapon2.pos);
  2363.                 if (dist < F1_0*5) {
  2364.                         maybe_kill_weapon(weapon1,weapon2);
  2365.                         if (weapon1->flags & OF_SHOULD_BE_DEAD) {
  2366. #if defined(DXX_BUILD_DESCENT_I)
  2367.                                 explode_badass_weapon(weapon1, weapon1->pos);
  2368. #elif defined(DXX_BUILD_DESCENT_II)
  2369.                                 explode_badass_weapon(weapon1, collision_point);
  2370. #endif
  2371.                                 digi_link_sound_to_pos(Weapon_info[get_weapon_id(weapon1)].robot_hit_sound, vcsegptridx(weapon1->segnum), 0, collision_point, 0, F1_0);
  2372.                         }
  2373.                         return 1;
  2374.                 } else {
  2375.                         weapon1->lifeleft = min(static_cast<fix>(dist) / 64, F1_0);
  2376.                         return 1;
  2377.                 }
  2378.         } else
  2379.                 return 0;
  2380. }
  2381. }
  2382.  
  2383. namespace dsx {
  2384. static void collide_weapon_and_weapon(const vmobjptridx_t weapon1, const vmobjptridx_t weapon2, const vms_vector &collision_point)
  2385. {
  2386. #if defined(DXX_BUILD_DESCENT_II)
  2387.         // -- Does this look buggy??:  if (weapon1->id == PMINE_ID && weapon1->id == PMINE_ID)
  2388.         if (get_weapon_id(weapon1) == weapon_id_type::PMINE_ID && get_weapon_id(weapon2) == weapon_id_type::PMINE_ID)
  2389.                 return;         //these can't blow each other up
  2390.  
  2391.         if (get_weapon_id(weapon1) == weapon_id_type::OMEGA_ID) {
  2392.                 if (!ok_to_do_omega_damage(weapon1)) // see comment in laser.c
  2393.                         return;
  2394.         } else if (get_weapon_id(weapon2) == weapon_id_type::OMEGA_ID) {
  2395.                 if (!ok_to_do_omega_damage(weapon2)) // see comment in laser.c
  2396.                         return;
  2397.         }
  2398. #endif
  2399.  
  2400.         if ((Weapon_info[get_weapon_id(weapon1)].destroyable) || (Weapon_info[get_weapon_id(weapon2)].destroyable)) {
  2401.                 // shooting Plasma will make bombs explode one drops at the same time since hitboxes overlap. Small HACK to get around this issue. if the player moves away from the bomb at least...
  2402.                 if ((GameTime64 < weapon1->ctype.laser_info.creation_time + (F1_0/5)) && (GameTime64 < weapon2->ctype.laser_info.creation_time + (F1_0/5)) && (weapon1->ctype.laser_info.parent_num == weapon2->ctype.laser_info.parent_num))
  2403.                         return;
  2404.                 //      Bug reported by Adam Q. Pletcher on September 9, 1994, smart bomb homing missiles were toasting each other.
  2405.                 if ((get_weapon_id(weapon1) == get_weapon_id(weapon2)) && (weapon1->ctype.laser_info.parent_num == weapon2->ctype.laser_info.parent_num))
  2406.                         return;
  2407.  
  2408. #if defined(DXX_BUILD_DESCENT_I)
  2409.                 if (Weapon_info[get_weapon_id(weapon1)].destroyable)
  2410.                         if (maybe_detonate_weapon(weapon1, weapon2, collision_point))
  2411.                                 maybe_kill_weapon(weapon2,weapon1);
  2412.  
  2413.                 if (Weapon_info[get_weapon_id(weapon2)].destroyable)
  2414.                         if (maybe_detonate_weapon(weapon2, weapon1, collision_point))
  2415.                                 maybe_kill_weapon(weapon1,weapon2);
  2416. #elif defined(DXX_BUILD_DESCENT_II)
  2417.                 if (Weapon_info[get_weapon_id(weapon1)].destroyable)
  2418.                         if (maybe_detonate_weapon(weapon1, weapon2, collision_point))
  2419.                                 maybe_detonate_weapon(weapon2,weapon1, collision_point);
  2420.  
  2421.                 if (Weapon_info[get_weapon_id(weapon2)].destroyable)
  2422.                         if (maybe_detonate_weapon(weapon2, weapon1, collision_point))
  2423.                                 maybe_detonate_weapon(weapon1,weapon2, collision_point);
  2424. #endif
  2425.         }
  2426.  
  2427. }
  2428. }
  2429.  
  2430. namespace dsx {
  2431. static void collide_weapon_and_debris(const vmobjptridx_t weapon, const vmobjptridx_t debris, const vms_vector &collision_point)
  2432. {
  2433. #if defined(DXX_BUILD_DESCENT_II)
  2434.         //      Hack!  Prevent debris from causing bombs spewed at player death to detonate!
  2435.         if ((get_weapon_id(weapon) == weapon_id_type::PROXIMITY_ID) || (get_weapon_id(weapon) == weapon_id_type::SUPERPROX_ID)) {
  2436.                 if (weapon->ctype.laser_info.creation_time + F1_0/2 > GameTime64)
  2437.                         return;
  2438.         }
  2439. #endif
  2440.         if ( (weapon->ctype.laser_info.parent_type==OBJ_PLAYER) && !(debris->flags & OF_EXPLODING) )    {
  2441.                 digi_link_sound_to_pos(SOUND_ROBOT_HIT, vcsegptridx(weapon->segnum), 0, collision_point, 0, F1_0);
  2442.  
  2443.                 explode_object(debris,0);
  2444.                 if ( Weapon_info[get_weapon_id(weapon)].damage_radius )
  2445.                         explode_badass_weapon(weapon, collision_point);
  2446.                 maybe_kill_weapon(weapon,debris);
  2447.                 if (!(weapon->mtype.phys_info.flags & PF_PERSISTENT))
  2448.                         weapon->flags |= OF_SHOULD_BE_DEAD;
  2449.         }
  2450.         return;
  2451. }
  2452. }
  2453.  
  2454. #if defined(DXX_BUILD_DESCENT_I)
  2455. #define DXX_COLLISION_TABLE(NO,DO)      \
  2456.  
  2457. #elif defined(DXX_BUILD_DESCENT_II)
  2458. #define DXX_COLLISION_TABLE(NO,DO)      \
  2459.         NO##_SAME_COLLISION(OBJ_MARKER) \
  2460.         DO##_COLLISION(OBJ_PLAYER, OBJ_MARKER, collide_player_and_marker)       \
  2461.         NO##_COLLISION(OBJ_ROBOT, OBJ_MARKER)   \
  2462.         NO##_COLLISION(OBJ_HOSTAGE, OBJ_MARKER) \
  2463.         NO##_COLLISION(OBJ_WEAPON, OBJ_MARKER)  \
  2464.         NO##_COLLISION(OBJ_CAMERA, OBJ_MARKER)  \
  2465.         NO##_COLLISION(OBJ_POWERUP, OBJ_MARKER) \
  2466.         NO##_COLLISION(OBJ_DEBRIS, OBJ_MARKER)  \
  2467.  
  2468. #endif
  2469.  
  2470. #define COLLIDE_IGNORE_COLLISION(O1,O2,C)
  2471.  
  2472. #define COLLISION_TABLE(NO,DO)  \
  2473.         NO##_SAME_COLLISION( OBJ_FIREBALL)      \
  2474.         DO##_SAME_COLLISION( OBJ_ROBOT, collide_robot_and_robot )       \
  2475.         NO##_SAME_COLLISION( OBJ_HOSTAGE)       \
  2476.         DO##_SAME_COLLISION( OBJ_PLAYER, collide_player_and_player )    \
  2477.         DO##_SAME_COLLISION( OBJ_WEAPON, collide_weapon_and_weapon )    \
  2478.         NO##_SAME_COLLISION( OBJ_CAMERA)        \
  2479.         NO##_SAME_COLLISION( OBJ_POWERUP)       \
  2480.         NO##_SAME_COLLISION( OBJ_DEBRIS)        \
  2481.         DO##_COLLISION( OBJ_WALL, OBJ_ROBOT, COLLIDE_IGNORE_COLLISION)  \
  2482.         DO##_COLLISION( OBJ_WALL, OBJ_WEAPON, COLLIDE_IGNORE_COLLISION) \
  2483.         DO##_COLLISION( OBJ_WALL, OBJ_PLAYER, COLLIDE_IGNORE_COLLISION) \
  2484.         DO##_COLLISION( OBJ_WALL, OBJ_POWERUP, COLLIDE_IGNORE_COLLISION)        \
  2485.         DO##_COLLISION( OBJ_WALL, OBJ_DEBRIS, COLLIDE_IGNORE_COLLISION) \
  2486.         NO##_COLLISION( OBJ_FIREBALL, OBJ_ROBOT)        \
  2487.         NO##_COLLISION( OBJ_FIREBALL, OBJ_HOSTAGE)      \
  2488.         NO##_COLLISION( OBJ_FIREBALL, OBJ_PLAYER)       \
  2489.         NO##_COLLISION( OBJ_FIREBALL, OBJ_WEAPON)       \
  2490.         NO##_COLLISION( OBJ_FIREBALL, OBJ_CAMERA)       \
  2491.         NO##_COLLISION( OBJ_FIREBALL, OBJ_POWERUP)      \
  2492.         NO##_COLLISION( OBJ_FIREBALL, OBJ_DEBRIS)       \
  2493.         NO##_COLLISION( OBJ_ROBOT, OBJ_HOSTAGE) \
  2494.         DO##_COLLISION( OBJ_ROBOT, OBJ_PLAYER,  collide_robot_and_player )      \
  2495.         DO##_COLLISION( OBJ_ROBOT, OBJ_WEAPON,  collide_robot_and_weapon )      \
  2496.         NO##_COLLISION( OBJ_ROBOT, OBJ_CAMERA)  \
  2497.         NO##_COLLISION( OBJ_ROBOT, OBJ_POWERUP) \
  2498.         NO##_COLLISION( OBJ_ROBOT, OBJ_DEBRIS)  \
  2499.         DO##_COLLISION( OBJ_HOSTAGE, OBJ_PLAYER,  collide_hostage_and_player )  \
  2500.         DO##_COLLISION( OBJ_HOSTAGE, OBJ_WEAPON, COLLIDE_IGNORE_COLLISION)      \
  2501.         NO##_COLLISION( OBJ_HOSTAGE, OBJ_CAMERA)        \
  2502.         NO##_COLLISION( OBJ_HOSTAGE, OBJ_POWERUP)       \
  2503.         NO##_COLLISION( OBJ_HOSTAGE, OBJ_DEBRIS)        \
  2504.         DO##_COLLISION( OBJ_PLAYER, OBJ_WEAPON,  collide_player_and_weapon )    \
  2505.         NO##_COLLISION( OBJ_PLAYER, OBJ_CAMERA) \
  2506.         DO##_COLLISION( OBJ_PLAYER, OBJ_POWERUP, collide_player_and_powerup )   \
  2507.         NO##_COLLISION( OBJ_PLAYER, OBJ_DEBRIS) \
  2508.         DO##_COLLISION( OBJ_PLAYER, OBJ_CNTRLCEN, collide_player_and_controlcen )       \
  2509.         DO##_COLLISION( OBJ_PLAYER, OBJ_CLUTTER, collide_player_and_clutter )   \
  2510.         NO##_COLLISION( OBJ_WEAPON, OBJ_CAMERA) \
  2511.         NO##_COLLISION( OBJ_WEAPON, OBJ_POWERUP)        \
  2512.         DO##_COLLISION( OBJ_WEAPON, OBJ_DEBRIS,  collide_weapon_and_debris )    \
  2513.         NO##_COLLISION( OBJ_CAMERA, OBJ_POWERUP)        \
  2514.         NO##_COLLISION( OBJ_CAMERA, OBJ_DEBRIS) \
  2515.         NO##_COLLISION( OBJ_POWERUP, OBJ_DEBRIS)        \
  2516.         DO##_COLLISION( OBJ_WEAPON, OBJ_CNTRLCEN, collide_weapon_and_controlcen )       \
  2517.         DO##_COLLISION( OBJ_ROBOT, OBJ_CNTRLCEN, collide_robot_and_controlcen ) \
  2518.         DO##_COLLISION( OBJ_WEAPON, OBJ_CLUTTER, collide_weapon_and_clutter )   \
  2519.         DXX_COLLISION_TABLE(NO,DO)      \
  2520.  
  2521.  
  2522. /* DPH: Put these macros on one long line to avoid CR/LF problems on linux */
  2523. #define COLLISION_OF(a,b) (((a)<<4) + (b))
  2524.  
  2525. #define DO_COLLISION(type1,type2,collision_function)    \
  2526.         case COLLISION_OF( (type1), (type2) ):  \
  2527.                 static_assert(type1 < type2, "do " #type1 " < " #type2);        \
  2528.                 collision_function( (A), (B), collision_point );        \
  2529.                 break;
  2530. #define DO_SAME_COLLISION(type1,collision_function)     \
  2531.         case COLLISION_OF( (type1), (type1) ):  \
  2532.                 collision_function( (A), (B), collision_point );        \
  2533.                 break;
  2534.  
  2535. //these next two macros define a case that does nothing
  2536. #define NO_COLLISION(type1,type2)       \
  2537.         case COLLISION_OF( (type1), (type2) ):  \
  2538.                 static_assert(type1 < type2, "no " #type1 " < " #type2);        \
  2539.                 break;
  2540.  
  2541. #define NO_SAME_COLLISION(type1)        \
  2542.         case COLLISION_OF( (type1), (type1) ):  \
  2543.                 break;
  2544.  
  2545. template <typename T, std::size_t V>
  2546. struct assert_no_truncation
  2547. {
  2548.         static_assert(static_cast<T>(V) == V, "truncation error");
  2549. };
  2550.  
  2551. void collide_two_objects(vmobjptridx_t A, vmobjptridx_t B, vms_vector &collision_point)
  2552. {
  2553.         if (B->type < A->type)
  2554.         {
  2555.                 using std::swap;
  2556.                 swap(A, B);
  2557.         }
  2558.         uint_fast8_t at, bt;
  2559.         const char *emsg;
  2560.         if (((at = A->type) >= MAX_OBJECT_TYPES && (emsg = "illegal object type A", true)) ||
  2561.                 ((bt = B->type) >= MAX_OBJECT_TYPES && (emsg = "illegal object type B", true)))
  2562.                 throw std::runtime_error(emsg);
  2563.         uint_fast8_t collision_type = COLLISION_OF(at, bt);
  2564.         struct assert_object_type_not_truncated : std::pair<assert_no_truncation<decltype(at), MAX_OBJECT_TYPES>, assert_no_truncation<decltype(bt), MAX_OBJECT_TYPES>> {};
  2565.         struct assert_collision_of_not_truncated : assert_no_truncation<decltype(collision_type), COLLISION_OF(MAX_OBJECT_TYPES - 1, MAX_OBJECT_TYPES - 1)> {};
  2566.         switch( collision_type )        {
  2567.                 COLLISION_TABLE(NO,DO)
  2568.         default:
  2569.                 Int3(); //Error( "Unhandled collision_type in collide.c!\n" );
  2570.         }
  2571. }
  2572.  
  2573. #define ENABLE_COLLISION(type1,type2,f) \
  2574.         COLLISION_RESULT(type1,type2,RESULT_CHECK);     \
  2575.         COLLISION_RESULT(type2,type1,RESULT_CHECK);
  2576.  
  2577. #define DISABLE_COLLISION(type1,type2)  \
  2578.         COLLISION_RESULT(type1,type2,RESULT_NOTHING);   \
  2579.         COLLISION_RESULT(type2,type1,RESULT_NOTHING);
  2580.  
  2581. #define ENABLE_SAME_COLLISION(type,f)   COLLISION_RESULT(type,type,RESULT_CHECK);
  2582. #define DISABLE_SAME_COLLISION(type)    COLLISION_RESULT(type,type,RESULT_NOTHING);
  2583.  
  2584. namespace {
  2585.  
  2586. template <object_type_t A, object_type_t B>
  2587. struct collision_result_t : public std::conditional<(B < A), collision_result_t<B, A>, std::integral_constant<ubyte, RESULT_NOTHING>>::type {};
  2588.  
  2589. #define COLLISION_RESULT(type1,type2,result)    \
  2590.         template <>     \
  2591.         struct collision_result_t<type1, type2> : public std::integral_constant<ubyte, result> {}
  2592.  
  2593. COLLISION_TABLE(DISABLE, ENABLE);
  2594.  
  2595. template <std::size_t R, std::size_t... C>
  2596. static inline constexpr collision_inner_array_t collide_init(std::index_sequence<C...>)
  2597. {
  2598.         static_assert((COLLISION_OF(R, 0) < COLLISION_OF(R, sizeof...(C) - 1)), "ambiguous collision");
  2599.         static_assert((COLLISION_OF(R, sizeof...(C) - 1) < COLLISION_OF(R + 1, 0)), "ambiguous collision");
  2600.         return collision_inner_array_t{{
  2601.                 collision_result_t<static_cast<object_type_t>(R), static_cast<object_type_t>(C)>::value...
  2602.         }};
  2603. }
  2604.  
  2605. template <std::size_t... R, std::size_t... C>
  2606. static inline constexpr collision_outer_array_t collide_init(std::index_sequence<R...>, std::index_sequence<C...> c)
  2607. {
  2608.         return collision_outer_array_t{{collide_init<R>(c)...}};
  2609. }
  2610.  
  2611. }
  2612.  
  2613. namespace dsx {
  2614.  
  2615. constexpr collision_outer_array_t CollisionResult = collide_init(std::make_index_sequence<MAX_OBJECT_TYPES>(), std::make_index_sequence<MAX_OBJECT_TYPES>());
  2616.  
  2617. }
  2618.  
  2619. #undef DISABLE_COLLISION
  2620. #undef ENABLE_COLLISION
  2621.  
  2622. #define ENABLE_COLLISION(T1,T2) static_assert((!!collision_result_t<T1, T2>::value && !!collision_result_t<T2, T1>::value), #T1 " " #T2);
  2623. #define DISABLE_COLLISION(T1,T2)        static_assert((!collision_result_t<T1, T2>::value && !collision_result_t<T2, T1>::value), #T1 " " #T2);
  2624.  
  2625.         ENABLE_COLLISION( OBJ_WALL, OBJ_ROBOT );
  2626.         ENABLE_COLLISION( OBJ_WALL, OBJ_WEAPON );
  2627.         ENABLE_COLLISION( OBJ_WALL, OBJ_PLAYER  );
  2628.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_FIREBALL );
  2629.  
  2630.         ENABLE_COLLISION( OBJ_ROBOT, OBJ_ROBOT );
  2631. //      DISABLE_COLLISION( OBJ_ROBOT, OBJ_ROBOT );      //      ALERT: WARNING: HACK: MK = RESPONSIBLE! TESTING!!
  2632.         DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_HOSTAGE );
  2633.         ENABLE_COLLISION( OBJ_PLAYER, OBJ_PLAYER );
  2634.         ENABLE_COLLISION( OBJ_WEAPON, OBJ_WEAPON );
  2635.         DISABLE_COLLISION( OBJ_CAMERA, OBJ_CAMERA );
  2636.         DISABLE_COLLISION( OBJ_POWERUP, OBJ_POWERUP );
  2637.         DISABLE_COLLISION( OBJ_DEBRIS, OBJ_DEBRIS );
  2638.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_ROBOT );
  2639.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_HOSTAGE );
  2640.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_PLAYER );
  2641.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_WEAPON );
  2642.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_CAMERA );
  2643.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_POWERUP );
  2644.         DISABLE_COLLISION( OBJ_FIREBALL, OBJ_DEBRIS );
  2645.         DISABLE_COLLISION( OBJ_ROBOT, OBJ_HOSTAGE );
  2646.         ENABLE_COLLISION( OBJ_ROBOT, OBJ_PLAYER );
  2647.         ENABLE_COLLISION( OBJ_ROBOT, OBJ_WEAPON );
  2648.         DISABLE_COLLISION( OBJ_ROBOT, OBJ_CAMERA );
  2649.         DISABLE_COLLISION( OBJ_ROBOT, OBJ_POWERUP );
  2650.         DISABLE_COLLISION( OBJ_ROBOT, OBJ_DEBRIS );
  2651.         ENABLE_COLLISION( OBJ_HOSTAGE, OBJ_PLAYER );
  2652.         ENABLE_COLLISION( OBJ_HOSTAGE, OBJ_WEAPON );
  2653.         DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_CAMERA );
  2654.         DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_POWERUP );
  2655.         DISABLE_COLLISION( OBJ_HOSTAGE, OBJ_DEBRIS );
  2656.         ENABLE_COLLISION( OBJ_PLAYER, OBJ_WEAPON );
  2657.         DISABLE_COLLISION( OBJ_PLAYER, OBJ_CAMERA );
  2658.         ENABLE_COLLISION( OBJ_PLAYER, OBJ_POWERUP );
  2659.         DISABLE_COLLISION( OBJ_PLAYER, OBJ_DEBRIS );
  2660.         DISABLE_COLLISION( OBJ_WEAPON, OBJ_CAMERA );
  2661.         DISABLE_COLLISION( OBJ_WEAPON, OBJ_POWERUP );
  2662.         ENABLE_COLLISION( OBJ_WEAPON, OBJ_DEBRIS );
  2663.         DISABLE_COLLISION( OBJ_CAMERA, OBJ_POWERUP );
  2664.         DISABLE_COLLISION( OBJ_CAMERA, OBJ_DEBRIS );
  2665.         DISABLE_COLLISION( OBJ_POWERUP, OBJ_DEBRIS );
  2666.         ENABLE_COLLISION( OBJ_POWERUP, OBJ_WALL );
  2667.         ENABLE_COLLISION( OBJ_WEAPON, OBJ_CNTRLCEN )
  2668.         ENABLE_COLLISION( OBJ_WEAPON, OBJ_CLUTTER )
  2669.         ENABLE_COLLISION( OBJ_PLAYER, OBJ_CNTRLCEN )
  2670.         ENABLE_COLLISION( OBJ_ROBOT, OBJ_CNTRLCEN )
  2671.         ENABLE_COLLISION( OBJ_PLAYER, OBJ_CLUTTER )
  2672. #if defined(DXX_BUILD_DESCENT_II)
  2673.         ENABLE_COLLISION( OBJ_PLAYER, OBJ_MARKER );
  2674. #endif
  2675.         ENABLE_COLLISION( OBJ_DEBRIS, OBJ_WALL );
  2676.  
  2677. namespace dsx {
  2678.  
  2679. window_event_result collide_object_with_wall(
  2680. #if defined(DXX_BUILD_DESCENT_II)
  2681.         const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
  2682. #endif
  2683.         const vmobjptridx_t A, fix hitspeed, const vmsegptridx_t hitseg, short hitwall, const vms_vector &hitpt)
  2684. {
  2685.         auto &Objects = LevelUniqueObjectState.Objects;
  2686.  
  2687.         switch( A->type )       {
  2688.         case OBJ_NONE:
  2689.                 Error( "A object of type NONE hit a wall!\n");
  2690.                 break;
  2691.         case OBJ_PLAYER:                collide_player_and_wall(A,hitspeed,hitseg,hitwall,hitpt); break;
  2692.                 case OBJ_WEAPON:
  2693.                         return collide_weapon_and_wall(
  2694. #if defined(DXX_BUILD_DESCENT_II)
  2695.                                 LevelSharedDestructibleLightState,
  2696. #endif
  2697.                                 Objects, vmsegptridx, A, hitseg, hitwall, hitpt);
  2698.         case OBJ_DEBRIS:                collide_debris_and_wall(A,hitseg,hitwall,hitpt); break;
  2699.  
  2700.         case OBJ_FIREBALL:      break;          //collide_fireball_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
  2701.                 case OBJ_ROBOT:
  2702.                 {
  2703.                         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  2704.                         auto &vcwallptr = Walls.vcptr;
  2705.                         collide_robot_and_wall(vcwallptr, A, hitseg, hitwall, hitpt);
  2706.                         break;
  2707.                 }
  2708.         case OBJ_HOSTAGE:               break;          //collide_hostage_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
  2709.         case OBJ_CAMERA:                break;          //collide_camera_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
  2710.         case OBJ_POWERUP:               break;          //collide_powerup_and_wall(A,hitspeed,hitseg,hitwall,hitpt);
  2711.         case OBJ_GHOST:         break;  //do nothing
  2712.  
  2713.         default:
  2714.                 Error( "Unhandled object type hit wall in collide.c\n" );
  2715.         }
  2716.  
  2717.         return window_event_result::handled;    // assume handled
  2718. }
  2719.  
  2720. }
  2721.