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.  * Functions for refueling centers.
  23.  *
  24.  */
  25.  
  26. #include <stdio.h>
  27. #include <stdlib.h>
  28. #include <math.h>
  29. #include <string.h>
  30.  
  31. #include "fuelcen.h"
  32. #include "gameseg.h"
  33. #include "game.h"               // For FrameTime
  34. #include "dxxerror.h"
  35. #include "gauges.h"
  36. #include "vclip.h"
  37. #include "fireball.h"
  38. #include "robot.h"
  39. #include "powerup.h"
  40.  
  41. #include "sounds.h"
  42. #include "morph.h"
  43. #include "3d.h"
  44. #include "physfs-serial.h"
  45. #include "bm.h"
  46. #include "polyobj.h"
  47. #include "ai.h"
  48. #include "gamemine.h"
  49. #include "gamesave.h"
  50. #include "player.h"
  51. #include "collide.h"
  52. #include "laser.h"
  53. #include "multi.h"
  54. #include "multibot.h"
  55. #include "escort.h"
  56. #include "compiler-poison.h"
  57. #include "d_enumerate.h"
  58. #include "compiler-range_for.h"
  59. #include "partial_range.h"
  60. #include "segiter.h"
  61.  
  62. // The max number of fuel stations per mine.
  63.  
  64. namespace dcx {
  65. constexpr fix Fuelcen_give_amount = i2f(25);
  66. constexpr fix Fuelcen_max_amount = i2f(100);
  67.  
  68. // Every time a robot is created in the morphing code, it decreases capacity of the morpher
  69. // by this amount... when capacity gets to 0, no more morphers...
  70. constexpr fix EnergyToCreateOneRobot = i2f(1);
  71.  
  72. static int Num_extry_robots = 15;
  73. }
  74. namespace dsx {
  75. #if DXX_USE_EDITOR
  76. const char      Special_names[MAX_CENTER_TYPES][11] = {
  77.         "NOTHING   ",
  78.         "FUELCEN   ",
  79.         "REPAIRCEN ",
  80.         "CONTROLCEN",
  81.         "ROBOTMAKER",
  82. #if defined(DXX_BUILD_DESCENT_II)
  83.         "GOAL_RED",
  84.         "GOAL_BLUE",
  85. #endif
  86. };
  87. #endif
  88.  
  89. //------------------------------------------------------------
  90. // Resets all fuel center info
  91. void fuelcen_reset()
  92. {
  93.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  94.         auto &Station = LevelUniqueFuelcenterState.Station;
  95.         DXX_MAKE_MEM_UNDEFINED(Station.begin(), Station.end());
  96.         DXX_MAKE_MEM_UNDEFINED(RobotCenters.begin(), RobotCenters.end());
  97.         LevelSharedRobotcenterState.Num_robot_centers = 0;
  98.         LevelUniqueFuelcenterState.Num_fuelcenters = 0;
  99.         range_for (shared_segment &i, Segments)
  100.                 i.special = SEGMENT_IS_NOTHING;
  101. }
  102.  
  103. #ifndef NDEBUG          //this is sometimes called by people from the debugger
  104. static void reset_all_robot_centers() __attribute_used;
  105. static void reset_all_robot_centers()
  106. {
  107.         // Remove all materialization centers
  108.         range_for (shared_segment &i, partial_range(Segments, LevelSharedSegmentState.Num_segments))
  109.                 if (i.special == SEGMENT_IS_ROBOTMAKER)
  110.                 {
  111.                         i.special = SEGMENT_IS_NOTHING;
  112.                         i.matcen_num = -1;
  113.                 }
  114. }
  115. #endif
  116.  
  117. //------------------------------------------------------------
  118. // Turns a segment into a fully charged up fuel center...
  119. void fuelcen_create(const vmsegptridx_t segp)
  120. {
  121.         auto &Station = LevelUniqueFuelcenterState.Station;
  122.         int     station_type;
  123.  
  124.         station_type = segp->special;
  125.  
  126.         switch( station_type )  {
  127.         case SEGMENT_IS_NOTHING:
  128. #if defined(DXX_BUILD_DESCENT_II)
  129.         case SEGMENT_IS_GOAL_BLUE:
  130.         case SEGMENT_IS_GOAL_RED:
  131. #endif
  132.                 return;
  133.         case SEGMENT_IS_FUELCEN:
  134.         case SEGMENT_IS_REPAIRCEN:
  135.         case SEGMENT_IS_CONTROLCEN:
  136.         case SEGMENT_IS_ROBOTMAKER:
  137.                 break;
  138.         default:
  139.                 Error( "Invalid station type %d in fuelcen.c\n", station_type );
  140.         }
  141.  
  142.         const auto next_fuelcenter_idx = LevelUniqueFuelcenterState.Num_fuelcenters++;
  143.         segp->station_idx = next_fuelcenter_idx;
  144.         auto &station = Station.at(next_fuelcenter_idx);
  145.         station.Type = station_type;
  146.         station.Capacity = Fuelcen_max_amount;
  147.         station.segnum = segp;
  148.         station.Timer = -1;
  149.         station.Flag = 0;
  150. }
  151.  
  152. //------------------------------------------------------------
  153. // Adds a matcen that already is a special type into the Station array.
  154. // This function is separate from other fuelcens because we don't want values reset.
  155. static void matcen_create(const vmsegptridx_t segp)
  156. {
  157.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  158.         auto &Station = LevelUniqueFuelcenterState.Station;
  159.         int     station_type = segp->special;
  160.  
  161.         Assert(station_type == SEGMENT_IS_ROBOTMAKER);
  162.  
  163.         const auto next_fuelcenter_idx = LevelUniqueFuelcenterState.Num_fuelcenters++;
  164.         segp->station_idx = next_fuelcenter_idx;
  165.         auto &station = Station.at(next_fuelcenter_idx);
  166.  
  167.         station.Type = station_type;
  168.         station.Capacity = i2f(GameUniqueState.Difficulty_level + 3);
  169.         station.segnum = segp;
  170.         station.Timer = -1;
  171.         station.Flag = 0;
  172.  
  173.         const auto next_robot_center_idx = LevelSharedRobotcenterState.Num_robot_centers++;
  174.         segp->matcen_num = next_robot_center_idx;
  175.         auto &robotcenter = RobotCenters[next_robot_center_idx];
  176.         robotcenter.fuelcen_num = next_fuelcenter_idx;
  177.         robotcenter.segnum = segp;
  178.         robotcenter.fuelcen_num = next_fuelcenter_idx;
  179. }
  180.  
  181. //------------------------------------------------------------
  182. // Adds a segment that already is a special type into the Station array.
  183. void fuelcen_activate(const vmsegptridx_t segp)
  184. {
  185.         if (segp->special == SEGMENT_IS_ROBOTMAKER)
  186.                 matcen_create( segp);
  187.         else
  188.                 fuelcen_create( segp);
  189. }
  190.  
  191. //      The lower this number is, the more quickly the center can be re-triggered.
  192. //      If it's too low, it can mean all the robots won't be put out, but for about 5
  193. //      robots, that's not real likely.
  194. #define MATCEN_LIFE (i2f(30-2*Difficulty_level))
  195.  
  196. //------------------------------------------------------------
  197. //      Trigger (enable) the materialization center in segment segnum
  198. void trigger_matcen(const vmsegptridx_t segp)
  199. {
  200.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  201.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  202.         auto &Station = LevelUniqueFuelcenterState.Station;
  203.         auto &Vertices = LevelSharedVertexState.get_vertices();
  204.         FuelCenter      *robotcen;
  205.  
  206.         Assert(segp->special == SEGMENT_IS_ROBOTMAKER);
  207.         assert(segp->matcen_num < LevelUniqueFuelcenterState.Num_fuelcenters);
  208.         Assert((segp->matcen_num >= 0) && (segp->matcen_num <= Highest_segment_index));
  209.  
  210.         robotcen = &Station[RobotCenters[segp->matcen_num].fuelcen_num];
  211.  
  212.         if (robotcen->Enabled == 1)
  213.                 return;
  214.  
  215.         if (!robotcen->Lives)
  216.                 return;
  217.  
  218.         const auto Difficulty_level = GameUniqueState.Difficulty_level;
  219. #if defined(DXX_BUILD_DESCENT_II)
  220.         //      MK: 11/18/95, At insane, matcens work forever!
  221.         if (Difficulty_level+1 < NDL)
  222. #endif
  223.                 robotcen->Lives--;
  224.  
  225.         robotcen->Timer = F1_0*1000;    //      Make sure the first robot gets emitted right away.
  226.         robotcen->Enabled = 1;                  //      Say this center is enabled, it can create robots.
  227.         robotcen->Capacity = i2f(Difficulty_level + 3);
  228.         robotcen->Disable_time = MATCEN_LIFE;
  229.  
  230.         //      Create a bright object in the segment.
  231.         auto &vcvertptr = Vertices.vcptr;
  232.         auto &&pos = compute_segment_center(vcvertptr, segp);
  233.         const auto &&delta = vm_vec_sub(vcvertptr(segp->verts[0]), pos);
  234.         vm_vec_scale_add2(pos, delta, F1_0/2);
  235.         auto objnum = obj_create( OBJ_LIGHT, 0, segp, pos, NULL, 0, CT_LIGHT, MT_NONE, RT_NONE );
  236.         if (objnum != object_none) {
  237.                 objnum->lifeleft = MATCEN_LIFE;
  238.                 objnum->ctype.light_info.intensity = i2f(8);    //      Light cast by a fuelcen.
  239.         } else {
  240.                 Int3();
  241.         }
  242. }
  243.  
  244. #if DXX_USE_EDITOR
  245. //------------------------------------------------------------
  246. // Takes away a segment's fuel center properties.
  247. //      Deletes the segment point entry in the FuelCenter list.
  248. void fuelcen_delete(const vmsegptr_t segp)
  249. {
  250.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  251.         auto &Station = LevelUniqueFuelcenterState.Station;
  252.         auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters;
  253. Restart: ;
  254.         segp->special = 0;
  255.  
  256.         for (uint_fast32_t i = 0; i < Num_fuelcenters; i++ )    {
  257.                 FuelCenter &fi = Station[i];
  258.                 if (vmsegptr(fi.segnum) == segp)
  259.                 {
  260.  
  261.                         auto &Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers;
  262.                         // If Robot maker is deleted, fix Segments and RobotCenters.
  263.                         if (fi.Type == SEGMENT_IS_ROBOTMAKER) {
  264.                                 if (!Num_robot_centers)
  265.                                 {
  266.                                         con_printf(CON_URGENT, "%s:%u: error: Num_robot_centers=0 while deleting robot maker", __FILE__, __LINE__);
  267.                                         return;
  268.                                 }
  269.                                 const auto &&range = partial_range(RobotCenters, static_cast<unsigned>(segp->matcen_num), Num_robot_centers--);
  270.  
  271.                                 std::move(std::next(range.begin()), range.end(), range.begin());
  272.                                 range_for (auto &fj, partial_const_range(Station, Num_fuelcenters))
  273.                                 {
  274.                                         if ( fj.Type == SEGMENT_IS_ROBOTMAKER )
  275.                                                 if ( Segments[fj.segnum].matcen_num > segp->matcen_num )
  276.                                                         Segments[fj.segnum].matcen_num--;
  277.                                 }
  278.                         }
  279.  
  280.                         //fix RobotCenters so they point to correct fuelcenter
  281.                         range_for (auto &j, partial_range(RobotCenters, Num_robot_centers))
  282.                                 if (j.fuelcen_num > i)          //this robotcenter's fuelcen is changing
  283.                                         j.fuelcen_num--;
  284.                         Assert(Num_fuelcenters > 0);
  285.                         Num_fuelcenters--;
  286.                         for (uint_fast32_t j = i; j < Num_fuelcenters; j++ )    {
  287.                                 Station[j] = Station[j+1];
  288.                                 Segments[Station[j].segnum].station_idx = j;
  289.                         }
  290.                         goto Restart;
  291.                 }
  292.         }
  293.         LevelUniqueFuelcenterState.Num_fuelcenters = Num_fuelcenters;
  294. }
  295. #endif
  296.  
  297. #define ROBOT_GEN_TIME (i2f(5))
  298.  
  299. imobjptridx_t create_morph_robot(const vmsegptridx_t segp, const vms_vector &object_pos, const unsigned object_id)
  300. {
  301.         ai_behavior default_behavior;
  302.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  303. #if defined(DXX_BUILD_DESCENT_I)
  304.         default_behavior = ai_behavior::AIB_NORMAL;
  305.         if (object_id == 10)                                            //      This is a toaster guy!
  306.                 default_behavior = ai_behavior::AIB_RUN_FROM;
  307. #elif defined(DXX_BUILD_DESCENT_II)
  308.         default_behavior = Robot_info[object_id].behavior;
  309. #endif
  310.  
  311.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  312.         auto obj = robot_create(object_id, segp, object_pos,
  313.                                 &vmd_identity_matrix, Polygon_models[Robot_info[object_id].model_num].rad,
  314.                                 default_behavior);
  315.  
  316.         if (obj == object_none)
  317.         {
  318.                 Int3();
  319.                 return object_none;
  320.         }
  321.  
  322.         ++LevelUniqueObjectState.accumulated_robots;
  323.         ++GameUniqueState.accumulated_robots;
  324.         //Set polygon-object-specific data
  325.  
  326.         obj->rtype.pobj_info.model_num = Robot_info[get_robot_id(obj)].model_num;
  327.         obj->rtype.pobj_info.subobj_flags = 0;
  328.  
  329.         //set Physics info
  330.  
  331.         obj->mtype.phys_info.mass = Robot_info[get_robot_id(obj)].mass;
  332.         obj->mtype.phys_info.drag = Robot_info[get_robot_id(obj)].drag;
  333.  
  334.         obj->mtype.phys_info.flags |= (PF_LEVELLING);
  335.  
  336.         obj->shields = Robot_info[get_robot_id(obj)].strength;
  337.  
  338.         create_n_segment_path(obj, 6, segment_none);            //      Create a 6 segment path from creation point.
  339.  
  340. #if defined(DXX_BUILD_DESCENT_I)
  341.         if (default_behavior == ai_behavior::AIB_RUN_FROM)
  342.                 obj->ctype.ai_info.ail.mode = ai_mode::AIM_RUN_FROM_OBJECT;
  343. #elif defined(DXX_BUILD_DESCENT_II)
  344.         obj->ctype.ai_info.ail.mode = ai_behavior_to_mode(default_behavior);
  345. #endif
  346.  
  347.         return obj;
  348. }
  349.  
  350. }
  351.  
  352. //      ----------------------------------------------------------------------------------------------------------
  353. static void robotmaker_proc(const d_vclip_array &Vclip, fvmsegptridx &vmsegptridx, FuelCenter *const robotcen, const unsigned numrobotcen)
  354. {
  355.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  356.         auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
  357.         auto &Objects = LevelUniqueObjectState.Objects;
  358.         auto &Vertices = LevelSharedVertexState.get_vertices();
  359.         auto &vcobjptr = Objects.vcptr;
  360.         auto &vmobjptridx = Objects.vmptridx;
  361.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  362.         int             matcen_num;
  363.         fix             top_time;
  364.  
  365.         if (robotcen->Enabled == 0)
  366.                 return;
  367.  
  368.         if (robotcen->Disable_time > 0) {
  369.                 robotcen->Disable_time -= FrameTime;
  370.                 if (robotcen->Disable_time <= 0) {
  371.                         robotcen->Enabled = 0;
  372.                 }
  373.         }
  374.  
  375.         //      No robot making in multiplayer mode.
  376.         if ((Game_mode & GM_MULTI) && (!(Game_mode & GM_MULTI_ROBOTS) || !multi_i_am_master()))
  377.                 return;
  378.  
  379.         // Wait until transmorgafier has capacity to make a robot...
  380.         if ( robotcen->Capacity <= 0 ) {
  381.                 return;
  382.         }
  383.  
  384.         const auto &&segp = vmsegptr(robotcen->segnum);
  385.         matcen_num = segp->matcen_num;
  386.  
  387.         if ( matcen_num == -1 ) {
  388.                 return;
  389.         }
  390.  
  391.         matcen_info *mi = &RobotCenters[matcen_num];
  392.         for (unsigned i = 0;; ++i)
  393.         {
  394.                 if (i >= mi->robot_flags.size())
  395.                         return;
  396.                 if (mi->robot_flags[i])
  397.                         break;
  398.         }
  399.  
  400.         // Wait until we have a free slot for this puppy...
  401.    //     <<<<<<<<<<<<<<<< Num robots in mine >>>>>>>>>>>>>>>>>>>>>>>>>>    <<<<<<<<<<<< Max robots in mine >>>>>>>>>>>>>>>
  402.         {
  403.                 auto &plr = get_local_player();
  404.                 if (LevelUniqueObjectState.accumulated_robots - plr.num_kills_level >= Gamesave_num_org_robots + Num_extry_robots)
  405.                 {
  406.                 return;
  407.                 }
  408.         }
  409.  
  410.         robotcen->Timer += FrameTime;
  411.  
  412.         auto &vcvertptr = Vertices.vcptr;
  413.         switch( robotcen->Flag )        {
  414.         case 0:         // Wait until next robot can generate
  415.                 if (Game_mode & GM_MULTI)
  416.                 {
  417.                         top_time = ROBOT_GEN_TIME;     
  418.                 }
  419.                 else
  420.                 {
  421.                         const auto center = compute_segment_center(vcvertptr, segp);
  422.                         const auto dist_to_player = vm_vec_dist_quick( ConsoleObject->pos, center );
  423.                         top_time = dist_to_player/64 + d_rand() * 2 + F1_0*2;
  424.                         if ( top_time > ROBOT_GEN_TIME )
  425.                                 top_time = ROBOT_GEN_TIME + d_rand();
  426.                         if ( top_time < F1_0*2 )
  427.                                 top_time = F1_0*3/2 + d_rand()*2;
  428.                 }
  429.  
  430.                 if (robotcen->Timer > top_time )        {
  431.                         int     count=0;
  432.                         int     my_station_num = numrobotcen;
  433.  
  434.                         //      Make sure this robotmaker hasn't put out its max without having any of them killed.
  435.                         range_for (const auto &&objp, vcobjptr)
  436.                         {
  437.                                 auto &obj = *objp;
  438.                                 if (obj.type == OBJ_ROBOT)
  439.                                         if ((obj.matcen_creator ^ 0x80) == my_station_num)
  440.                                                 count++;
  441.                         }
  442.                         if (count > GameUniqueState.Difficulty_level + 3) {
  443.                                 robotcen->Timer /= 2;
  444.                                 return;
  445.                         }
  446.  
  447.                         //      Whack on any robot or player in the matcen segment.
  448.                         count=0;
  449.                         auto segnum = robotcen->segnum;
  450.                         const auto &&csegp = vmsegptr(segnum);
  451.                         range_for (const auto objp, objects_in(csegp, vmobjptridx, vmsegptr))
  452.                         {
  453.                                 count++;
  454.                                 if ( count > MAX_OBJECTS )      {
  455.                                         Int3();
  456.                                         return;
  457.                                 }
  458.                                 if (objp->type==OBJ_ROBOT) {
  459.                                         collide_robot_and_materialization_center(objp);
  460.                                         robotcen->Timer = top_time/2;
  461.                                         return;
  462.                                 } else if (objp->type==OBJ_PLAYER ) {
  463.                                         collide_player_and_materialization_center(objp);
  464.                                         robotcen->Timer = top_time/2;
  465.                                         return;
  466.                                 }
  467.                         }
  468.  
  469.                         const auto &&cur_object_loc = compute_segment_center(vcvertptr, csegp);
  470.                         const auto &&robotcen_segp = vmsegptridx(robotcen->segnum);
  471.                         // HACK!!! The 10 under here should be something equal to the 1/2 the size of the segment.
  472.                         auto obj = object_create_explosion(robotcen_segp, cur_object_loc, i2f(10), VCLIP_MORPHING_ROBOT);
  473.  
  474.                         if (obj != object_none)
  475.                                 extract_orient_from_segment(vcvertptr, obj->orient, robotcen_segp);
  476.  
  477.                         if ( Vclip[VCLIP_MORPHING_ROBOT].sound_num > -1 )               {
  478.                                 digi_link_sound_to_pos(Vclip[VCLIP_MORPHING_ROBOT].sound_num, robotcen_segp, 0, cur_object_loc, 0, F1_0);
  479.                         }
  480.                         robotcen->Flag  = 1;
  481.                         robotcen->Timer = 0;
  482.  
  483.                 }
  484.                 break;
  485.         case 1:                 // Wait until 1/2 second after VCLIP started.
  486.                 if (robotcen->Timer > (Vclip[VCLIP_MORPHING_ROBOT].play_time/2) )       {
  487.  
  488.                         robotcen->Capacity -= EnergyToCreateOneRobot;
  489.                         robotcen->Flag = 0;
  490.  
  491.                         robotcen->Timer = 0;
  492.                         const auto &&cur_object_loc = compute_segment_center(vcvertptr, vcsegptr(robotcen->segnum));
  493.  
  494.                         // If this is the first materialization, set to valid robot.
  495.                         {
  496.                                 int     type;
  497.                                 ubyte   legal_types[sizeof(mi->robot_flags) * 8];   // the width of robot_flags[].
  498.                                 int     num_types;
  499.  
  500.                                 num_types = 0;
  501.                                 for (unsigned i = 0;; ++i)
  502.                                 {
  503.                                         if (i >= mi->robot_flags.size())
  504.                                                 break;
  505.                                         uint32_t flags = mi->robot_flags[i];
  506.                                         for (unsigned j = 0; flags && j < 8 * sizeof(flags); ++j)
  507.                                         {
  508.                                                 if (flags & 1)
  509.                                                         legal_types[num_types++] = (i * 32) + j;
  510.                                                 flags >>= 1;
  511.                                         }
  512.                                 }
  513.  
  514.                                 if (num_types == 1)
  515.                                         type = legal_types[0];
  516.                                 else
  517.                                         type = legal_types[(d_rand() * num_types) / 32768];
  518.  
  519.                                 const auto &&obj = create_morph_robot(vmsegptridx(robotcen->segnum), cur_object_loc, type );
  520.                                 if (obj != object_none) {
  521.                                         if (Game_mode & GM_MULTI)
  522.                                                 multi_send_create_robot(numrobotcen, obj, type);
  523.                                         obj->matcen_creator = (numrobotcen) | 0x80;
  524.  
  525.                                         // Make object faces player...
  526.                                         const auto direction = vm_vec_sub(ConsoleObject->pos,obj->pos );
  527.                                         vm_vector_2_matrix( obj->orient, direction, &obj->orient.uvec, nullptr);
  528.        
  529.                                         morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, obj);
  530.                                         //robotcen->last_created_obj = obj;
  531.                                         //robotcen->last_created_sig = robotcen->last_created_obj->signature;
  532.                                 }
  533.                         }
  534.  
  535.                 }
  536.                 break;
  537.         default:
  538.                 robotcen->Flag = 0;
  539.                 robotcen->Timer = 0;
  540.         }
  541. }
  542.  
  543. //-------------------------------------------------------------
  544. // Called once per frame, replenishes fuel supply.
  545. void fuelcen_update_all()
  546. {
  547.         auto &Station = LevelUniqueFuelcenterState.Station;
  548.         range_for (auto &&e, enumerate(partial_range(Station, LevelUniqueFuelcenterState.Num_fuelcenters)))
  549.         {
  550.                 auto &i = e.value;
  551.                 if (i.Type == SEGMENT_IS_ROBOTMAKER)
  552.                 {
  553.                         if (! (Game_suspended & SUSP_ROBOTS))
  554.                                 robotmaker_proc(Vclip, vmsegptridx, &i, e.idx);
  555.                 }
  556.         }
  557. }
  558.  
  559. namespace dsx {
  560.  
  561. #if defined(DXX_BUILD_DESCENT_I)
  562. constexpr std::integral_constant<unsigned, F1_0 / 3> FUELCEN_SOUND_DELAY{};
  563. #elif defined(DXX_BUILD_DESCENT_II)
  564. //play every half second
  565. constexpr std::integral_constant<unsigned, F1_0 / 4> FUELCEN_SOUND_DELAY{};
  566. #endif
  567.  
  568. //-------------------------------------------------------------
  569. fix fuelcen_give_fuel(const shared_segment &segp, fix MaxAmountCanTake)
  570. {
  571.         static fix64 last_play_time = 0;
  572.  
  573.         if (segp.special == SEGMENT_IS_FUELCEN)
  574.         {
  575.                 fix amount;
  576.  
  577. #if defined(DXX_BUILD_DESCENT_II)
  578.                 detect_escort_goal_fuelcen_accomplished();
  579. #endif
  580.  
  581.                 if (MaxAmountCanTake <= 0 )     {
  582.                         return 0;
  583.                 }
  584.  
  585.                 amount = fixmul(FrameTime,Fuelcen_give_amount);
  586.  
  587.                 if (amount > MaxAmountCanTake )
  588.                         amount = MaxAmountCanTake;
  589.  
  590.                 if (last_play_time + FUELCEN_SOUND_DELAY < GameTime64 || last_play_time > GameTime64)
  591.                 {
  592.                         last_play_time = GameTime64;
  593.                         multi_digi_play_sample(SOUND_REFUEL_STATION_GIVING_FUEL, F1_0/2);
  594.                 }
  595.                 return amount;
  596.         } else {
  597.                 return 0;
  598.         }
  599. }
  600.  
  601. #if defined(DXX_BUILD_DESCENT_II)
  602. //-------------------------------------------------------------
  603. // DM/050904
  604. // Repair centers
  605. // use same values as fuel centers
  606. fix repaircen_give_shields(const shared_segment &segp, const fix MaxAmountCanTake)
  607. {
  608.         static fix last_play_time=0;
  609.  
  610.         if (segp.special == SEGMENT_IS_REPAIRCEN)
  611.         {
  612.                 fix amount;
  613.                 if (MaxAmountCanTake <= 0 ) {
  614.                         return 0;
  615.                 }
  616.                 amount = fixmul(FrameTime,Fuelcen_give_amount);
  617.                 if (amount > MaxAmountCanTake )
  618.                         amount = MaxAmountCanTake;
  619.                 if (last_play_time > GameTime64)
  620.                         last_play_time = 0;
  621.                 if (GameTime64 > last_play_time+FUELCEN_SOUND_DELAY) {
  622.                         multi_digi_play_sample(SOUND_REFUEL_STATION_GIVING_FUEL, F1_0/2);
  623.                         last_play_time = GameTime64;
  624.                 }
  625.                 return amount;
  626.         } else {
  627.                 return 0;
  628.         }
  629. }
  630. #endif
  631.  
  632. //      --------------------------------------------------------------------------------------------
  633. void disable_matcens(void)
  634. {
  635.         auto &Station = LevelUniqueFuelcenterState.Station;
  636.         range_for (auto &s, partial_range(Station, LevelUniqueFuelcenterState.Num_fuelcenters))
  637.                 if (s.Type == SEGMENT_IS_ROBOTMAKER)
  638.                 {
  639.                         s.Enabled = 0;
  640.                         s.Disable_time = 0;
  641.                 }
  642. }
  643.  
  644. //      --------------------------------------------------------------------------------------------
  645. //      Initialize all materialization centers.
  646. //      Give them all the right number of lives.
  647. void init_all_matcens(void)
  648. {
  649.         auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
  650.         auto &Station = LevelUniqueFuelcenterState.Station;
  651.         const auto Num_fuelcenters = LevelUniqueFuelcenterState.Num_fuelcenters;
  652.         const auto &&robot_range = partial_const_range(RobotCenters, LevelSharedRobotcenterState.Num_robot_centers);
  653.         for (uint_fast32_t i = 0; i < Num_fuelcenters; i++)
  654.                 if (Station[i].Type == SEGMENT_IS_ROBOTMAKER) {
  655.                         Station[i].Lives = 3;
  656.                         Station[i].Enabled = 0;
  657.                         Station[i].Disable_time = 0;
  658.                         //      Make sure this fuelcen is pointed at by a matcen.
  659.                         if (std::find_if(robot_range.begin(), robot_range.end(), [i](const matcen_info &mi) {
  660.                                 return mi.fuelcen_num == i;
  661.                         }) == robot_range.end())
  662.                         {
  663.                                 Station[i].Lives = 0;
  664.                                 LevelError("Station %" PRIuFAST32 " has type robotmaker, but no robotmaker uses it; ignoring.", i);
  665.                         }
  666.                 }
  667.  
  668. #ifndef NDEBUG
  669.         //      Make sure all matcens point at a fuelcen
  670.         range_for (auto &i, robot_range)
  671.         {
  672.                 auto    fuelcen_num = i.fuelcen_num;
  673.                 Assert(fuelcen_num < Num_fuelcenters);
  674.                 Assert(Station[fuelcen_num].Type == SEGMENT_IS_ROBOTMAKER);
  675.         }
  676. #endif
  677.  
  678. }
  679.  
  680. }
  681.  
  682. struct d1mi_v25
  683. {
  684.         matcen_info *m;
  685.         d1mi_v25(matcen_info &mi) : m(&mi) {}
  686. };
  687.  
  688. struct d1cmi_v25
  689. {
  690.         const matcen_info *m;
  691.         d1cmi_v25(const matcen_info &mi) : m(&mi) {}
  692. };
  693.  
  694. #define D1_MATCEN_V25_MEMBERLIST        (p.m->robot_flags[0], serial::pad<sizeof(fix) * 2>(), p.m->segnum, p.m->fuelcen_num)
  695. DEFINE_SERIAL_UDT_TO_MESSAGE(d1mi_v25, p, D1_MATCEN_V25_MEMBERLIST);
  696. DEFINE_SERIAL_UDT_TO_MESSAGE(d1cmi_v25, p, D1_MATCEN_V25_MEMBERLIST);
  697. ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1mi_v25, 16);
  698.  
  699. namespace dsx {
  700. #if defined(DXX_BUILD_DESCENT_I)
  701. struct d1mi_v26
  702. {
  703.         matcen_info *m;
  704.         d1mi_v26(matcen_info &mi) : m(&mi) {}
  705. };
  706.  
  707. struct d1cmi_v26
  708. {
  709.         const matcen_info *m;
  710.         d1cmi_v26(const matcen_info &mi) : m(&mi) {}
  711. };
  712.  
  713. #define D1_MATCEN_V26_MEMBERLIST        (p.m->robot_flags[0], serial::pad<sizeof(uint32_t)>(), serial::pad<sizeof(fix) * 2>(), p.m->segnum, p.m->fuelcen_num)
  714. DEFINE_SERIAL_UDT_TO_MESSAGE(d1mi_v26, p, D1_MATCEN_V26_MEMBERLIST);
  715. DEFINE_SERIAL_UDT_TO_MESSAGE(d1cmi_v26, p, D1_MATCEN_V26_MEMBERLIST);
  716. ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1mi_v26, 20);
  717.  
  718. void matcen_info_read(PHYSFS_File *fp, matcen_info &mi, int version)
  719. {
  720.         if (version > 25)
  721.                 PHYSFSX_serialize_read<const d1mi_v26>(fp, mi);
  722.         else
  723.                 PHYSFSX_serialize_read<const d1mi_v25>(fp, mi);
  724. }
  725. #elif defined(DXX_BUILD_DESCENT_II)
  726. void fuelcen_check_for_goal(object &plrobj, const shared_segment &segp)
  727. {
  728.         Assert (game_mode_capture_flag());
  729.  
  730.         unsigned check_team;
  731.         powerup_type_t powerup_to_drop;
  732.         switch(segp.special)
  733.         {
  734.                 case SEGMENT_IS_GOAL_BLUE:
  735.                         check_team = TEAM_BLUE;
  736.                         powerup_to_drop = POW_FLAG_RED;
  737.                         break;
  738.                 case SEGMENT_IS_GOAL_RED:
  739.                         check_team = TEAM_RED;
  740.                         powerup_to_drop = POW_FLAG_BLUE;
  741.                         break;
  742.                 default:
  743.                         return;
  744.         }
  745.         if (get_team(Player_num) != check_team)
  746.                 return;
  747.         auto &player_info = plrobj.ctype.player_info;
  748.         if (player_info.powerup_flags & PLAYER_FLAGS_FLAG)
  749.         {
  750.                 player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG;
  751.                                 multi_send_capture_bonus (Player_num);
  752.                 maybe_drop_net_powerup(powerup_to_drop, 1, 0);
  753.         }
  754. }
  755.  
  756. void fuelcen_check_for_hoard_goal(object &plrobj, const shared_segment &segp)
  757. {
  758.         Assert (game_mode_hoard());
  759.  
  760.    if (Player_dead_state != player_dead_state::no)
  761.                 return;
  762.  
  763.         const auto special = segp.special;
  764.         if (special==SEGMENT_IS_GOAL_BLUE || special==SEGMENT_IS_GOAL_RED)
  765.         {
  766.                 auto &player_info = plrobj.ctype.player_info;
  767.                 if (auto &hoard_orbs = player_info.hoard.orbs)
  768.                 {
  769.                                 player_info.powerup_flags &= ~PLAYER_FLAGS_FLAG;
  770.                         multi_send_orb_bonus(Player_num, std::exchange(hoard_orbs, 0));
  771.       }
  772.         }
  773.  
  774. }
  775.  
  776.  
  777. /*
  778.  * reads an d1_matcen_info structure from a PHYSFS_File
  779.  */
  780. void d1_matcen_info_read(PHYSFS_File *fp, matcen_info &mi)
  781. {
  782.         PHYSFSX_serialize_read<const d1mi_v25>(fp, mi);
  783.         mi.robot_flags[1] = 0;
  784. }
  785.  
  786. DEFINE_SERIAL_UDT_TO_MESSAGE(matcen_info, m, (m.robot_flags, serial::pad<sizeof(fix) * 2>(), m.segnum, m.fuelcen_num));
  787. ASSERT_SERIAL_UDT_MESSAGE_SIZE(matcen_info, 20);
  788.  
  789. void matcen_info_read(PHYSFS_File *fp, matcen_info &mi)
  790. {
  791.         PHYSFSX_serialize_read(fp, mi);
  792. }
  793. #endif
  794.  
  795. void matcen_info_write(PHYSFS_File *fp, const matcen_info &mi, short version)
  796. {
  797.         if (version >= 27)
  798. #if defined(DXX_BUILD_DESCENT_I)
  799.                 PHYSFSX_serialize_write<d1cmi_v26>(fp, mi);
  800. #elif defined(DXX_BUILD_DESCENT_II)
  801.                 PHYSFSX_serialize_write(fp, mi);
  802. #endif
  803.         else
  804.                 PHYSFSX_serialize_write<d1cmi_v25>(fp, mi);
  805. }
  806. }
  807.  
  808. DEFINE_SERIAL_UDT_TO_MESSAGE(FuelCenter, fc, (serial::sign_extend<int>(fc.Type), serial::sign_extend<int>(fc.segnum), fc.Flag, fc.Enabled, fc.Lives, serial::pad<1>(), fc.Capacity, serial::pad<sizeof(fix)>(), fc.Timer, fc.Disable_time, serial::pad<3 * sizeof(fix)>()));
  809. ASSERT_SERIAL_UDT_MESSAGE_SIZE(FuelCenter, 40);
  810.  
  811. void fuelcen_read(PHYSFS_File *fp, FuelCenter &fc)
  812. {
  813.         PHYSFSX_serialize_read(fp, fc);
  814. }
  815.  
  816. void fuelcen_write(PHYSFS_File *fp, const FuelCenter &fc)
  817. {
  818.         PHYSFSX_serialize_write(fp, fc);
  819. }
  820.