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.  * Code for the control center
  23.  *
  24.  */
  25.  
  26.  
  27. #include <stdlib.h>
  28. #include <stdio.h>
  29. #if !defined(_WIN32) && !defined(macintosh)
  30. #include <unistd.h>
  31. #endif
  32. #include "pstypes.h"
  33. #include "dxxerror.h"
  34. #include "inferno.h"
  35. #include "cntrlcen.h"
  36. #include "game.h"
  37. #include "laser.h"
  38. #include "gameseq.h"
  39. #include "ai.h"
  40. #include "player.h"
  41. #include "multi.h"
  42. #include "fwd-wall.h"
  43. #include "segment.h"
  44. #include "object.h"
  45. #include "robot.h"
  46. #include "vclip.h"
  47. #include "physfs-serial.h"
  48. #include "fireball.h"
  49. #include "endlevel.h"
  50. #include "state.h"
  51. #include "args.h"
  52. #include "wall.h"
  53.  
  54. #include "compiler-range_for.h"
  55. #include "partial_range.h"
  56.  
  57. namespace dsx {
  58. std::array<reactor, MAX_REACTORS> Reactors;
  59. #if defined(DXX_BUILD_DESCENT_II)
  60. unsigned Num_reactors;
  61. #endif
  62. }
  63.  
  64. control_center_triggers ControlCenterTriggers;
  65.  
  66. namespace dsx {
  67.  
  68. static window_event_result do_countdown_frame();
  69.  
  70. //      -----------------------------------------------------------------------------
  71. //return the position & orientation of a gun on the control center object
  72. static void calc_controlcen_gun_point(reactor &r, object &obj, const uint_fast32_t gun_num)
  73. {
  74.         //instance gun position & orientation
  75.  
  76.         auto &gun_point = obj.ctype.reactor_info.gun_pos[gun_num];
  77.         auto &gun_dir = obj.ctype.reactor_info.gun_dir[gun_num];
  78.         const auto &&m = vm_transposed_matrix(obj.orient);
  79.         vm_vec_rotate(gun_point, r.gun_points[gun_num], m);
  80.         vm_vec_add2(gun_point, obj.pos);
  81.         vm_vec_rotate(gun_dir, r.gun_dirs[gun_num], m);
  82. }
  83.  
  84. void calc_controlcen_gun_point(object &obj)
  85. {
  86.         assert(obj.type == OBJ_CNTRLCEN);
  87.         assert(obj.render_type == RT_POLYOBJ);
  88.         auto &reactor = get_reactor_definition(get_reactor_id(obj));
  89.         for (uint_fast32_t i = reactor.n_guns; i--;)
  90.                 calc_controlcen_gun_point(reactor, obj, i);
  91. }
  92.  
  93. //      -----------------------------------------------------------------------------
  94. //      Look at control center guns, find best one to fire at *objp.
  95. //      Return best gun number (one whose direction dotted with vector to player is largest).
  96. //      If best gun has negative dot, return -1, meaning no gun is good.
  97. static int calc_best_gun(const unsigned num_guns, const object &objreactor, const vms_vector &objpos)
  98. {
  99.         int     i;
  100.         fix     best_dot;
  101.         int     best_gun;
  102.         auto &gun_pos = objreactor.ctype.reactor_info.gun_pos;
  103.         auto &gun_dir = objreactor.ctype.reactor_info.gun_dir;
  104.  
  105.         best_dot = -F1_0*2;
  106.         best_gun = -1;
  107.  
  108.         for (i=0; i<num_guns; i++) {
  109.                 fix                     dot;
  110.                 const auto gun_vec = vm_vec_normalized_quick(vm_vec_sub(objpos, gun_pos[i]));
  111.                 dot = vm_vec_dot(gun_dir[i], gun_vec);
  112.  
  113.                 if (dot > best_dot) {
  114.                         best_dot = dot;
  115.                         best_gun = i;
  116.                 }
  117.         }
  118.  
  119.         Assert(best_gun != -1);         // Contact Mike.  This is impossible.  Or maybe you're getting an unnormalized vector somewhere.
  120.  
  121.         if (best_dot < 0)
  122.                 return -1;
  123.         else
  124.                 return best_gun;
  125. }
  126.  
  127. }
  128.  
  129. namespace dcx {
  130. constexpr int   D1_Alan_pavlish_reactor_times[NDL] = {50, 45, 40, 35, 30};
  131. }
  132. namespace dsx {
  133. #if defined(DXX_BUILD_DESCENT_II)
  134. constexpr int   D2_Alan_pavlish_reactor_times[NDL] = {90, 60, 45, 35, 30};
  135. #endif
  136.  
  137. //      -----------------------------------------------------------------------------
  138. //      Called every frame.  If control center been destroyed, then actually do something.
  139. window_event_result do_controlcen_dead_frame()
  140. {
  141.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  142.         auto &Objects = LevelUniqueObjectState.Objects;
  143.         auto &vmobjptridx = Objects.vmptridx;
  144.         if ((Game_mode & GM_MULTI) && (get_local_player().connected != CONNECT_PLAYING)) // if out of level already there's no need for this
  145.                 return window_event_result::ignored;
  146.  
  147.         const auto Dead_controlcen_object_num = LevelUniqueControlCenterState.Dead_controlcen_object_num;
  148.         if (Dead_controlcen_object_num != object_none && LevelUniqueControlCenterState.Countdown_seconds_left > 0)
  149.                 if (d_rand() < FrameTime*4)
  150. #if defined(DXX_BUILD_DESCENT_I)
  151. #define CC_FIREBALL_SCALE       F1_0*3
  152. #elif defined(DXX_BUILD_DESCENT_II)
  153. #define CC_FIREBALL_SCALE       F1_0
  154. #endif
  155.                         create_small_fireball_on_object(vmobjptridx(Dead_controlcen_object_num), CC_FIREBALL_SCALE, 1);
  156.  
  157.         if (LevelUniqueControlCenterState.Control_center_destroyed && !Endlevel_sequence)
  158.                 return do_countdown_frame();
  159.  
  160.         return window_event_result::ignored;
  161. }
  162.  
  163. #define COUNTDOWN_VOICE_TIME fl2f(12.75)
  164.  
  165. window_event_result do_countdown_frame()
  166. {
  167.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  168.         fix     old_time;
  169.  
  170.         if (!LevelUniqueControlCenterState.Control_center_destroyed)
  171.                 return window_event_result::ignored;
  172.  
  173. #if defined(DXX_BUILD_DESCENT_II)
  174.         if (!is_D2_OEM && !is_MAC_SHARE && !is_SHAREWARE)   // get countdown in OEM and SHAREWARE only
  175.         {
  176.                 // On last level, we don't want a countdown.
  177.                 if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
  178.                 {
  179.                         if (!(Game_mode & GM_MULTI))
  180.                                 return window_event_result::ignored;
  181.                         if (Game_mode & GM_MULTI_ROBOTS)
  182.                                 return window_event_result::ignored;
  183.                 }
  184.         }
  185. #endif
  186.  
  187.         //      Control center destroyed, rock the player's ship.
  188.         if (d_tick_step)
  189.         {
  190.                 auto &rotvel = ConsoleObject->mtype.phys_info.rotvel;
  191.                 const auto get_base_disturbance = [fc = std::min(LevelUniqueControlCenterState.Countdown_seconds_left, 16)]() {
  192.                         return fixmul(d_rand() - 16384, 3 * F1_0 / 16 + (F1_0 * (16 - fc)) / 32);
  193.                 };
  194.                 fix disturb_x = get_base_disturbance(), disturb_z = get_base_disturbance();
  195.                 //      At Trainee, decrease rocking of ship by 4x.
  196.                 if (GameUniqueState.Difficulty_level == Difficulty_0)
  197.                 {
  198.                         disturb_x /= 4;
  199.                         disturb_z /= 4;
  200.                 }
  201.                 rotvel.x += disturb_x;
  202.                 rotvel.z += disturb_z;
  203.         }
  204.         //      Hook in the rumble sound effect here.
  205.  
  206.         old_time = LevelUniqueControlCenterState.Countdown_timer;
  207.         LevelUniqueControlCenterState.Countdown_timer -= FrameTime;
  208.         const auto Countdown_timer = LevelUniqueControlCenterState.Countdown_timer;
  209.         const auto Countdown_seconds_left = LevelUniqueControlCenterState.Countdown_seconds_left = f2i(Countdown_timer + F1_0*7/8);
  210.  
  211.         if (old_time > COUNTDOWN_VOICE_TIME && Countdown_timer <= COUNTDOWN_VOICE_TIME)
  212.         {
  213.                 digi_play_sample( SOUND_COUNTDOWN_13_SECS, F3_0 );
  214.         }
  215.         if (f2i(old_time + F1_0 * 7 / 8) != Countdown_seconds_left)
  216.         {
  217.                 if (Countdown_seconds_left >= 0 && Countdown_seconds_left < 10)
  218.                         digi_play_sample(SOUND_COUNTDOWN_0_SECS + Countdown_seconds_left, F3_0);
  219.                 if (Countdown_seconds_left == LevelUniqueControlCenterState.Total_countdown_time - 1)
  220.                         digi_play_sample( SOUND_COUNTDOWN_29_SECS, F3_0 );
  221.         }                                              
  222.  
  223.         if (Countdown_timer > 0) {
  224.                 fix size,old_size;
  225.                 const auto Total_countdown_time = LevelUniqueControlCenterState.Total_countdown_time;
  226.                 size = (i2f(Total_countdown_time) - Countdown_timer) / fl2f(0.65);
  227.                 old_size = (i2f(Total_countdown_time) - old_time) / fl2f(0.65);
  228.                 if (size != old_size && Countdown_seconds_left < Total_countdown_time - 5)
  229.                 {                       // Every 2 seconds!
  230.                         //@@if (Dead_controlcen_object_num != -1) {
  231.                         //@@    vms_vector vp;  //,v,c;
  232.                         //@@    compute_segment_center(&vp, &Segments[Objects[Dead_controlcen_object_num].segnum]);
  233.                         //@@    object_create_explosion( Objects[Dead_controlcen_object_num].segnum, &vp, size*10, VCLIP_SMALL_EXPLOSION);
  234.                         //@@}
  235.  
  236.                         digi_play_sample( SOUND_CONTROL_CENTER_WARNING_SIREN, F3_0 );
  237.                 }
  238.         }  else {
  239.                 int flash_value;
  240.  
  241.                 if (old_time > 0)
  242.                         digi_play_sample( SOUND_MINE_BLEW_UP, F1_0 );
  243.  
  244.                 flash_value = f2i(-Countdown_timer * (64 / 4)); // 4 seconds to total whiteness
  245.                 PALETTE_FLASH_SET(flash_value,flash_value,flash_value);
  246.  
  247.                 if (PaletteBlueAdd > 64 )       {
  248.                         gr_set_default_canvas();
  249.                         gr_clear_canvas(*grd_curcanv, BM_XRGB(31,31,31));                               //make screen all white to match palette effect
  250.                         reset_palette_add();                                                    //restore palette for death message
  251.                         //controlcen->MaxCapacity = Fuelcen_max_amount;
  252.                         //gauge_message( "Control Center Reset" );
  253.                         return DoPlayerDead();          //kill_player();
  254.                 }                                                                                                                                                              
  255.         }
  256.  
  257.         return window_event_result::handled;
  258. }
  259.  
  260. //      -----------------------------------------------------------------------------
  261. //      Called when control center gets destroyed.
  262. //      This code is common to whether control center is implicitly imbedded in a boss,
  263. //      or is an object of its own.
  264. //      if objp == NULL that means the boss was the control center and don't set Dead_controlcen_object_num
  265. void do_controlcen_destroyed_stuff(const imobjidx_t objp)
  266. {
  267.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  268.         int i;
  269.  
  270. #if defined(DXX_BUILD_DESCENT_II)
  271.         if ((Game_mode & GM_MULTI_ROBOTS) && LevelUniqueControlCenterState.Control_center_destroyed)
  272.                 return; // Don't allow resetting if control center and boss on same level
  273. #endif
  274.  
  275.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  276.         auto &vmwallptr = Walls.vmptr;
  277.         // Must toggle walls whether it is a boss or control center.
  278.         for (i=0;i<ControlCenterTriggers.num_links;i++)
  279.                 wall_toggle(vmwallptr, vmsegptridx(ControlCenterTriggers.seg[i]), ControlCenterTriggers.side[i]);
  280.  
  281.         // And start the countdown stuff.
  282.         LevelUniqueControlCenterState.Control_center_destroyed = 1;
  283.  
  284.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  285.         int Total_countdown_time;
  286. #if defined(DXX_BUILD_DESCENT_II)
  287.         // If a secret level, delete secret.sgc to indicate that we can't return to our secret level.
  288.         if (Current_level_num < 0)
  289.                 PHYSFS_delete(SECRETC_FILENAME);
  290.  
  291.         const auto Base_control_center_explosion_time = LevelSharedControlCenterState.Base_control_center_explosion_time;
  292.         if (Base_control_center_explosion_time != DEFAULT_CONTROL_CENTER_EXPLOSION_TIME)
  293.                 Total_countdown_time = Base_control_center_explosion_time + Base_control_center_explosion_time * (NDL-Difficulty_level-1)/2;
  294.         else if (!EMULATING_D1)
  295.                 Total_countdown_time = D2_Alan_pavlish_reactor_times[Difficulty_level];
  296.         else
  297. #endif
  298.                 Total_countdown_time = D1_Alan_pavlish_reactor_times[Difficulty_level];
  299.  
  300.         LevelUniqueControlCenterState.Total_countdown_time = Total_countdown_time;
  301.         LevelUniqueControlCenterState.Countdown_timer = i2f(Total_countdown_time);
  302.  
  303.         if (!LevelUniqueControlCenterState.Control_center_present || objp==object_none)
  304.                 return;
  305.  
  306.         LevelUniqueControlCenterState.Dead_controlcen_object_num = objp;
  307. }
  308.  
  309. //      -----------------------------------------------------------------------------
  310. //do whatever this thing does in a frame
  311. void do_controlcen_frame(const vmobjptridx_t obj)
  312. {
  313.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  314.         int                     best_gun_num;
  315.         auto &Objects = LevelUniqueObjectState.Objects;
  316.         auto &vmobjptr = Objects.vmptr;
  317.  
  318.         //      If a boss level, then Control_center_present will be 0.
  319.         if (!LevelUniqueControlCenterState.Control_center_present)
  320.                 return;
  321.  
  322. #ifndef NDEBUG
  323.         if (cheats.robotfiringsuspended || (Game_suspended & SUSP_ROBOTS))
  324.                 return;
  325. #else
  326.         if (cheats.robotfiringsuspended)
  327.                 return;
  328. #endif
  329.  
  330.         auto &plrobj = get_local_plrobj();
  331.         if (!(LevelUniqueControlCenterState.Control_center_been_hit || player_is_visible(LevelUniqueControlCenterState.Control_center_player_been_seen)))
  332.         {
  333.                 if (!(d_tick_count % 8)) {              //      Do every so often...
  334.                         // This is a hack.  Since the control center is not processed by
  335.                         // ai_do_frame, it doesn't know to deal with cloaked dudes.  It
  336.                         // seems to work in single-player mode because it is actually using
  337.                         // the value of Believed_player_position that was set by the last
  338.                         // person to go through ai_do_frame.  But since a no-robots game
  339.                         // never goes through ai_do_frame, I'm making it so the control
  340.                         // center can spot cloaked dudes.
  341.  
  342.                         if (Game_mode & GM_MULTI)
  343.                                 Believed_player_pos = plrobj.pos;
  344.  
  345.                         //      Hack for special control centers which are isolated and not reachable because the
  346.                         //      real control center is inside the boss.
  347.                         auto &children = vcsegptr(obj->segnum)->children;
  348.                         if (std::none_of(children.begin(), children.end(), IS_CHILD))
  349.                                 return;
  350.  
  351.                         auto vec_to_player = vm_vec_sub(ConsoleObject->pos, obj->pos);
  352.                         auto dist_to_player = vm_vec_normalize_quick(vec_to_player);
  353.                         if (dist_to_player < F1_0*200) {
  354.                                 LevelUniqueControlCenterState.Control_center_player_been_seen = player_is_visible_from_object(obj, obj->pos, 0, vec_to_player);
  355.                                 LevelUniqueControlCenterState.Frametime_until_next_fire = 0;
  356.                         }
  357.                 }                      
  358.  
  359.                 return;
  360.         }
  361.  
  362. #if defined(DXX_BUILD_DESCENT_II)
  363.         //      Periodically, make the reactor fall asleep if player not visible.
  364.         if (!EMULATING_D1 && (LevelUniqueControlCenterState.Control_center_been_hit || player_is_visible(LevelUniqueControlCenterState.Control_center_player_been_seen)))
  365.         {
  366.                 if (LevelUniqueControlCenterState.Last_time_cc_vis_check + F1_0 * 5 < GameTime64 || LevelUniqueControlCenterState.Last_time_cc_vis_check > GameTime64)
  367.                 {
  368.                         LevelUniqueControlCenterState.Last_time_cc_vis_check = GameTime64;
  369.                         fix                     dist_to_player;
  370.                         auto vec_to_player = vm_vec_sub(ConsoleObject->pos, obj->pos);
  371.                         dist_to_player = vm_vec_normalize_quick(vec_to_player);
  372.                         if (dist_to_player < F1_0*120) {
  373.                                 LevelUniqueControlCenterState.Control_center_player_been_seen = player_is_visible_from_object(obj, obj->pos, 0, vec_to_player);
  374.                                 if (!player_is_visible(LevelUniqueControlCenterState.Control_center_player_been_seen))
  375.                                         LevelUniqueControlCenterState.Control_center_been_hit = 0;
  376.                         }
  377.                 }
  378.  
  379.         }
  380. #endif
  381.  
  382.         constexpr fix Relative_frametime_cease_fire = F1_0 * 2;
  383.         auto &Frametime_since_player_died = LevelUniqueControlCenterState.Frametime_since_player_died;
  384.         if (Player_dead_state != player_dead_state::no)
  385.         {
  386.                 if (Frametime_since_player_died <= Relative_frametime_cease_fire)
  387.                         Frametime_since_player_died += FrameTime;
  388.         }
  389.         else
  390.                 Frametime_since_player_died = 0;
  391.  
  392.         if (LevelUniqueControlCenterState.Frametime_until_next_fire < 0 && !(Frametime_since_player_died > Relative_frametime_cease_fire))
  393.         {
  394.                 auto &player_info = plrobj.ctype.player_info;
  395.                 const auto &player_pos = (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) ? Believed_player_pos : ConsoleObject->pos;
  396.                 best_gun_num = calc_best_gun(
  397.                         get_reactor_definition(get_reactor_id(obj)).n_guns,
  398.                         obj,
  399.                         player_pos
  400.                 );
  401.  
  402.                 if (best_gun_num != -1) {
  403.                         fix                     delta_fire_time;
  404.  
  405.                         auto vec_to_goal = vm_vec_sub(player_pos, obj->ctype.reactor_info.gun_pos[best_gun_num]);
  406.                         auto dist_to_player = vm_vec_normalize_quick(vec_to_goal);
  407.  
  408.                         if (dist_to_player > F1_0*300)
  409.                         {
  410.                                 LevelUniqueControlCenterState.Control_center_been_hit = 0;
  411.                                 LevelUniqueControlCenterState.Control_center_player_been_seen = player_visibility_state::no_line_of_sight;
  412.                                 return;
  413.                         }
  414.        
  415.                         if (Game_mode & GM_MULTI)
  416.                                 multi_send_controlcen_fire(vec_to_goal, best_gun_num, obj);    
  417.                         Laser_create_new_easy( vec_to_goal, obj->ctype.reactor_info.gun_pos[best_gun_num], obj, weapon_id_type::CONTROLCEN_WEAPON_NUM, 1);
  418.  
  419.                         int count = 0;
  420. #if defined(DXX_BUILD_DESCENT_I)
  421.                         const unsigned scale_divisor = 4;
  422.                         if (d_rand() < 32767/4)
  423. #elif defined(DXX_BUILD_DESCENT_II)
  424.                         const unsigned scale_divisor = 6;
  425.                         int                     rand_prob;
  426.                         //      some of time, based on level, fire another thing, not directly at player, so it might hit him if he's constantly moving.
  427.                         rand_prob = F1_0/(abs(Current_level_num)/4+2);
  428.                         while ((d_rand() > rand_prob) && (count < 4))
  429. #endif
  430.                         {
  431.                                 vm_vec_scale_add2(vec_to_goal, make_random_vector(), F1_0/scale_divisor);
  432.                                 vm_vec_normalize_quick(vec_to_goal);
  433.                                 if (Game_mode & GM_MULTI)
  434.                                         multi_send_controlcen_fire(vec_to_goal, best_gun_num, obj);
  435.                                 Laser_create_new_easy( vec_to_goal, obj->ctype.reactor_info.gun_pos[best_gun_num], obj, weapon_id_type::CONTROLCEN_WEAPON_NUM, count == 0);
  436.                                 count++;
  437.                         }
  438.  
  439.                         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  440.                         delta_fire_time = (NDL - Difficulty_level) * F1_0/4;
  441. #if defined(DXX_BUILD_DESCENT_II)
  442.                         if (Difficulty_level == 0)
  443.                                 delta_fire_time += F1_0/2;
  444. #endif
  445.  
  446.                         if (Game_mode & GM_MULTI) // slow down rate of fire in multi player
  447.                                 delta_fire_time *= 2;
  448.  
  449.                         LevelUniqueControlCenterState.Frametime_until_next_fire = delta_fire_time;
  450.  
  451.                 }
  452.         } else
  453.                 LevelUniqueControlCenterState.Frametime_until_next_fire -= FrameTime;
  454. }
  455.  
  456. //      -----------------------------------------------------------------------------
  457. //      This must be called at the start of each level.
  458. //      If this level contains a boss and mode != multiplayer, don't do control center stuff.  (Ghost out control center object.)
  459. //      If this level contains a boss and mode == multiplayer, do control center stuff.
  460. void init_controlcen_for_level(void)
  461. {
  462.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  463.         imobjptr_t cntrlcen_objnum = nullptr, boss_objnum = nullptr;
  464.  
  465.         auto &Objects = LevelUniqueObjectState.Objects;
  466.         auto &vmobjptridx = Objects.vmptridx;
  467.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  468.         range_for (const auto &&objp, vmobjptridx)
  469.         {
  470.                 if (objp->type == OBJ_CNTRLCEN)
  471.                 {
  472.                         if (cntrlcen_objnum == nullptr)
  473.                                 cntrlcen_objnum = objp;
  474.                 }
  475.                 else if (objp->type == OBJ_ROBOT && (Robot_info[get_robot_id(objp)].boss_flag))
  476.                 {
  477.                         if (boss_objnum == nullptr)
  478.                                 boss_objnum = objp;
  479.                 }
  480.         }
  481.  
  482.         if (boss_objnum != nullptr && !((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_ROBOTS)))
  483.         {
  484.                 if (cntrlcen_objnum != nullptr)
  485.                 {
  486.                         auto &objp = *cntrlcen_objnum;
  487.                         objp.type = OBJ_GHOST;
  488.                         objp.control_type = CT_NONE;
  489.                         objp.render_type = RT_NONE;
  490.                         LevelUniqueControlCenterState.Control_center_present = 0;
  491.                 }
  492.         }
  493.         else if (cntrlcen_objnum != nullptr)
  494.         {
  495.                 //      Compute all gun positions.
  496.                 object &objp = cntrlcen_objnum;
  497.                 calc_controlcen_gun_point(objp);
  498.                 LevelUniqueControlCenterState.Control_center_present = 1;
  499.  
  500. #if defined(DXX_BUILD_DESCENT_I)
  501.                 const unsigned secret_level_shield_multiplier = 100;
  502. #elif defined(DXX_BUILD_DESCENT_II)
  503.                 const unsigned secret_level_shield_multiplier = 150;
  504.                 const auto Reactor_strength = LevelSharedControlCenterState.Reactor_strength;
  505.                 if (Reactor_strength != -1)
  506.                         objp.shields = i2f(Reactor_strength);
  507.                 else
  508. #endif
  509.                 {               //use old defaults
  510.                         //      Boost control center strength at higher levels.
  511.                         if (Current_level_num >= 0)
  512.                                 objp.shields = F1_0*200 + (F1_0*200/4) * Current_level_num;
  513.                         else
  514.                                 objp.shields = F1_0*200 - Current_level_num*F1_0*secret_level_shield_multiplier;
  515.                 }
  516.         }
  517.  
  518.         //      Say the control center has not yet been hit.
  519.         LevelUniqueControlCenterState.Control_center_been_hit = 0;
  520.         LevelUniqueControlCenterState.Control_center_player_been_seen = player_visibility_state::no_line_of_sight;
  521.         LevelUniqueControlCenterState.Frametime_until_next_fire = 0;
  522.         LevelUniqueControlCenterState.Dead_controlcen_object_num = object_none;
  523. }
  524.  
  525. #if defined(DXX_BUILD_DESCENT_II)
  526. void special_reactor_stuff()
  527. {
  528.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  529.         if (LevelUniqueControlCenterState.Control_center_destroyed) {
  530.                 const auto Base_control_center_explosion_time = LevelSharedControlCenterState.Base_control_center_explosion_time;
  531.                 LevelUniqueControlCenterState.Countdown_timer += i2f(Base_control_center_explosion_time + (NDL - 1 - GameUniqueState.Difficulty_level) * Base_control_center_explosion_time / (NDL - 1));
  532.                 LevelUniqueControlCenterState.Total_countdown_time = f2i(LevelUniqueControlCenterState.Countdown_timer) + 2;    //      Will prevent "Self destruct sequence activated" message from replaying.
  533.         }
  534. }
  535.  
  536. /*
  537.  * reads n reactor structs from a PHYSFS_File
  538.  */
  539. void reactor_read_n(PHYSFS_File *fp, partial_range_t<reactor *> r)
  540. {
  541.         range_for (auto &i, r)
  542.         {
  543.                 i.model_num = PHYSFSX_readInt(fp);
  544.                 i.n_guns = PHYSFSX_readInt(fp);
  545.                 range_for (auto &j, i.gun_points)
  546.                         PHYSFSX_readVector(fp, j);
  547.                 range_for (auto &j, i.gun_dirs)
  548.                         PHYSFSX_readVector(fp, j);
  549.         }
  550. }
  551. #endif
  552. }
  553.  
  554. DEFINE_SERIAL_UDT_TO_MESSAGE(control_center_triggers, cct, (cct.num_links, cct.seg, cct.side));
  555. ASSERT_SERIAL_UDT_MESSAGE_SIZE(control_center_triggers, 42);
  556.  
  557. /*
  558.  * reads n control_center_triggers structs from a PHYSFS_File and swaps if specified
  559.  */
  560. void control_center_triggers_read(control_center_triggers *cct, PHYSFS_File *fp)
  561. {
  562.         PHYSFSX_serialize_read(fp, *cct);
  563. }
  564.  
  565. void control_center_triggers_write(const control_center_triggers *cct, PHYSFS_File *fp)
  566. {
  567.         PHYSFSX_serialize_write(fp, *cct);
  568. }
  569.