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.  * Bitmap and palette loading functions.
  23.  *
  24.  */
  25.  
  26. #include <algorithm>
  27. #include <stdio.h>
  28. #include <stdlib.h>
  29. #include <string.h>
  30.  
  31. #include "pstypes.h"
  32. #include "inferno.h"
  33. #include "gr.h"
  34. #include "bm.h"
  35. #include "u_mem.h"
  36. #include "dxxerror.h"
  37. #include "object.h"
  38. #include "vclip.h"
  39. #include "effects.h"
  40. #include "polyobj.h"
  41. #include "wall.h"
  42. #include "textures.h"
  43. #include "game.h"
  44. #include "multi.h"
  45. #include "iff.h"
  46. #include "powerup.h"
  47. #include "sounds.h"
  48. #include "piggy.h"
  49. #include "aistruct.h"
  50. #include "robot.h"
  51. #include "weapon.h"
  52. #include "gauges.h"
  53. #include "player.h"
  54. #include "endlevel.h"
  55. #include "cntrlcen.h"
  56. #include "makesig.h"
  57. #include "interp.h"
  58. #include "console.h"
  59. #include "rle.h"
  60. #include "physfsx.h"
  61. #include "internal.h"
  62. #include "strutil.h"
  63.  
  64. #if DXX_USE_EDITOR
  65. #include "editor/texpage.h"
  66. #endif
  67.  
  68. #include "compiler-range_for.h"
  69. #include "d_range.h"
  70. #include "partial_range.h"
  71. #include <memory>
  72.  
  73. std::array<ubyte, MAX_SOUNDS> Sounds, AltSounds;
  74.  
  75. unsigned NumTextures;
  76.  
  77. #if DXX_USE_EDITOR
  78. int Num_object_subtypes = 1;
  79. #endif
  80.  
  81. namespace dsx {
  82. #if defined(DXX_BUILD_DESCENT_I)
  83. int Num_total_object_types;
  84.  
  85. sbyte   ObjType[MAX_OBJTYPE];
  86. sbyte   ObjId[MAX_OBJTYPE];
  87. fix     ObjStrength[MAX_OBJTYPE];
  88. #elif defined(DXX_BUILD_DESCENT_II)
  89. //the polygon model number to use for the marker
  90. int     Marker_model_num = -1;
  91. unsigned N_ObjBitmaps;
  92. static void bm_free_extra_objbitmaps();
  93. #endif
  94.  
  95. Textures_array Textures;                // All textures.
  96. //for each model, a model number for dying & dead variants, or -1 if none
  97. std::array<int, MAX_POLYGON_MODELS> Dying_modelnums, Dead_modelnums;
  98. std::array<bitmap_index, N_COCKPIT_BITMAPS> cockpit_bitmap;
  99. }
  100.  
  101. //right now there's only one player ship, but we can have another by
  102. //adding an array and setting the pointer to the active ship.
  103. namespace dcx {
  104. player_ship only_player_ship;
  105.  
  106. //----------------- Miscellaneous bitmap pointers ---------------
  107. unsigned Num_cockpits;
  108. }
  109.  
  110. //---------------- Variables for wall textures ------------------
  111.  
  112. //---------------- Variables for object textures ----------------
  113.  
  114. int             First_multi_bitmap_num=-1;
  115.  
  116. std::array<bitmap_index, MAX_OBJ_BITMAPS> ObjBitmaps;
  117. std::array<ushort, MAX_OBJ_BITMAPS>          ObjBitmapPtrs;     // These point back into ObjBitmaps, since some are used twice.
  118.  
  119. namespace dsx {
  120. void gamedata_close()
  121. {
  122.         free_polygon_models();
  123. #if defined(DXX_BUILD_DESCENT_II)
  124.         bm_free_extra_objbitmaps();
  125. #endif
  126.         free_endlevel_data();
  127.         rle_cache_close();
  128.         piggy_close();
  129. }
  130. }
  131.  
  132. /*
  133.  * reads n tmap_info structs from a PHYSFS_File
  134.  */
  135. #if defined(DXX_BUILD_DESCENT_I)
  136. static void tmap_info_read(tmap_info &ti, PHYSFS_File *fp)
  137. {
  138.         PHYSFS_read(fp, ti.filename, 13, 1);
  139.         ti.flags = PHYSFSX_readByte(fp);
  140.         ti.lighting = PHYSFSX_readFix(fp);
  141.         ti.damage = PHYSFSX_readFix(fp);
  142.         ti.eclip_num = PHYSFSX_readInt(fp);
  143. }
  144.  
  145. //-----------------------------------------------------------------
  146. // Initializes game properties data (including texture caching system) and sound data.
  147. int gamedata_init()
  148. {
  149.         int retval;
  150.        
  151.         init_polygon_models();
  152.         retval = properties_init();                             // This calls properties_read_cmp if appropriate
  153.         if (retval)
  154.                 gamedata_read_tbl(Vclip, retval == PIGGY_PC_SHAREWARE);
  155.  
  156.         piggy_read_sounds(retval == PIGGY_PC_SHAREWARE);
  157.        
  158.         return 0;
  159. }
  160.  
  161. namespace dsx {
  162.  
  163. // Read compiled properties data from descent.pig
  164. // (currently only ever called if D1)
  165. void properties_read_cmp(d_vclip_array &Vclip, PHYSFS_File * fp)
  166. {
  167.         auto &Effects = LevelUniqueEffectsClipState.Effects;
  168.         auto &Robot_joints = LevelSharedRobotJointState.Robot_joints;
  169.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  170.         auto &WallAnims = GameSharedState.WallAnims;
  171.         //  bitmap_index is a short
  172.        
  173.         NumTextures = PHYSFSX_readInt(fp);
  174.         bitmap_index_read_n(fp, Textures);
  175.         range_for (tmap_info &ti, TmapInfo)
  176.                 tmap_info_read(ti, fp);
  177.  
  178.         PHYSFS_read(fp, Sounds, sizeof(Sounds[0]), Sounds.size());
  179.         PHYSFS_read(fp, AltSounds, sizeof(AltSounds[0]), AltSounds.size());
  180.        
  181.         Num_vclips = PHYSFSX_readInt(fp);
  182.         range_for (vclip &vc, Vclip)
  183.                 vclip_read(fp, vc);
  184.  
  185.         Num_effects = PHYSFSX_readInt(fp);
  186.         range_for (eclip &ec, Effects)
  187.                 eclip_read(fp, ec);
  188.  
  189.         Num_wall_anims = PHYSFSX_readInt(fp);
  190.         range_for (auto &w, WallAnims)
  191.                 wclip_read(fp, w);
  192.  
  193.         LevelSharedRobotInfoState.N_robot_types = PHYSFSX_readInt(fp);
  194.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  195.         range_for (auto &r, Robot_info)
  196.                 robot_info_read(fp, r);
  197.  
  198.         N_robot_joints = PHYSFSX_readInt(fp);
  199.         range_for (auto &r, Robot_joints)
  200.                 jointpos_read(fp, r);
  201.  
  202.         N_weapon_types = PHYSFSX_readInt(fp);
  203.         weapon_info_read_n(Weapon_info, MAX_WEAPON_TYPES, fp, 0);
  204.  
  205.         N_powerup_types = PHYSFSX_readInt(fp);
  206.         range_for (auto &p, Powerup_info)
  207.                 powerup_type_info_read(fp, p);
  208.  
  209.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  210.         N_polygon_models = PHYSFSX_readInt(fp);
  211.         {
  212.                 const auto &&r = partial_range(Polygon_models, N_polygon_models);
  213.         range_for (auto &p, r)
  214.                 polymodel_read(&p, fp);
  215.  
  216.         range_for (auto &p, r)
  217.                 polygon_model_data_read(&p, fp);
  218.         }
  219.  
  220.         bitmap_index_read_n(fp, partial_range(Gauges, MAX_GAUGE_BMS));
  221.        
  222.         range_for (auto &i, Dying_modelnums)
  223.                 i = PHYSFSX_readInt(fp);
  224.         range_for (auto &i, Dead_modelnums)
  225.                 i = PHYSFSX_readInt(fp);
  226.  
  227.         bitmap_index_read_n(fp, ObjBitmaps);
  228.         range_for (auto &i, ObjBitmapPtrs)
  229.                 i = PHYSFSX_readShort(fp);
  230.  
  231.         player_ship_read(&only_player_ship, fp);
  232.  
  233.         Num_cockpits = PHYSFSX_readInt(fp);
  234.         bitmap_index_read_n(fp, cockpit_bitmap);
  235.  
  236.         PHYSFS_read(fp, Sounds, sizeof(Sounds[0]), Sounds.size());
  237.         PHYSFS_read(fp, AltSounds, sizeof(AltSounds[0]), AltSounds.size());
  238.  
  239.         Num_total_object_types = PHYSFSX_readInt(fp);
  240.         PHYSFS_read( fp, ObjType, sizeof(ubyte), MAX_OBJTYPE );
  241.         PHYSFS_read( fp, ObjId, sizeof(ubyte), MAX_OBJTYPE );
  242.         range_for (auto &i, ObjStrength)
  243.                 i = PHYSFSX_readFix(fp);
  244.  
  245.         First_multi_bitmap_num = PHYSFSX_readInt(fp);
  246.         Reactors[0].n_guns = PHYSFSX_readInt(fp);
  247.  
  248.         range_for (auto &i, Reactors[0].gun_points)
  249.                 PHYSFSX_readVector(fp, i);
  250.         range_for (auto &i, Reactors[0].gun_dirs)
  251.                 PHYSFSX_readVector(fp, i);
  252.  
  253.         exit_modelnum = PHYSFSX_readInt(fp);   
  254.         destroyed_exit_modelnum = PHYSFSX_readInt(fp);
  255.  
  256. #if DXX_USE_EDITOR
  257.         //Build tmaplist
  258.         auto &&effect_range = partial_const_range(Effects, Num_effects);
  259.         LevelUniqueTmapInfoState.Num_tmaps = TextureEffects + std::count_if(effect_range.begin(), effect_range.end(), [](const eclip &e) { return e.changing_wall_texture >= 0; });
  260.         #endif
  261. }
  262.  
  263. }
  264. #elif defined(DXX_BUILD_DESCENT_II)
  265. static void tmap_info_read(tmap_info &ti, PHYSFS_File *fp)
  266. {
  267.         ti.flags = PHYSFSX_readByte(fp);
  268.         PHYSFSX_readByte(fp);
  269.         PHYSFSX_readByte(fp);
  270.         PHYSFSX_readByte(fp);
  271.         ti.lighting = PHYSFSX_readFix(fp);
  272.         ti.damage = PHYSFSX_readFix(fp);
  273.         ti.eclip_num = PHYSFSX_readShort(fp);
  274.         ti.destroyed = PHYSFSX_readShort(fp);
  275.         ti.slide_u = PHYSFSX_readShort(fp);
  276.         ti.slide_v = PHYSFSX_readShort(fp);
  277. }
  278.  
  279. //-----------------------------------------------------------------
  280. // Initializes game properties data (including texture caching system) and sound data.
  281. int gamedata_init()
  282. {
  283.         init_polygon_models();
  284.  
  285. #if DXX_USE_EDITOR
  286.         // The pc_shareware argument is currently unused for Descent 2,
  287.         // but *may* be useful for loading Descent 1 Shareware texture properties.
  288.         if (!gamedata_read_tbl(Vclip, 0))
  289. #endif
  290.                 if (!properties_init())                         // This calls properties_read_cmp
  291.                                 Error("Cannot open ham file\n");
  292.  
  293.         piggy_read_sounds();
  294.  
  295.         return 0;
  296. }
  297.  
  298. namespace dsx {
  299.  
  300. void bm_read_all(d_vclip_array &Vclip, PHYSFS_File * fp)
  301. {
  302.         auto &Effects = LevelUniqueEffectsClipState.Effects;
  303.         auto &Robot_joints = LevelSharedRobotJointState.Robot_joints;
  304.         auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
  305.         auto &WallAnims = GameSharedState.WallAnims;
  306.         unsigned t;
  307.  
  308.         NumTextures = PHYSFSX_readInt(fp);
  309.         bitmap_index_read_n(fp, partial_range(Textures, NumTextures));
  310.         range_for (tmap_info &ti, partial_range(TmapInfo, NumTextures))
  311.                 tmap_info_read(ti, fp);
  312.  
  313.         t = PHYSFSX_readInt(fp);
  314.         PHYSFS_read( fp, Sounds, sizeof(ubyte), t );
  315.         PHYSFS_read( fp, AltSounds, sizeof(ubyte), t );
  316.  
  317.         Num_vclips = PHYSFSX_readInt(fp);
  318.         range_for (vclip &vc, partial_range(Vclip, Num_vclips))
  319.                 vclip_read(fp, vc);
  320.  
  321.         Num_effects = PHYSFSX_readInt(fp);
  322.         range_for (eclip &ec, partial_range(Effects, Num_effects))
  323.                 eclip_read(fp, ec);
  324.  
  325.         Num_wall_anims = PHYSFSX_readInt(fp);
  326.         range_for (auto &w, partial_range(WallAnims, Num_wall_anims))
  327.                 wclip_read(fp, w);
  328.  
  329.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  330.         LevelSharedRobotInfoState.N_robot_types = PHYSFSX_readInt(fp);
  331.         range_for (auto &r, partial_range(Robot_info, LevelSharedRobotInfoState.N_robot_types))
  332.                 robot_info_read(fp, r);
  333.  
  334.         N_robot_joints = PHYSFSX_readInt(fp);
  335.         range_for (auto &r, partial_range(Robot_joints, N_robot_joints))
  336.                 jointpos_read(fp, r);
  337.  
  338.         N_weapon_types = PHYSFSX_readInt(fp);
  339.         weapon_info_read_n(Weapon_info, N_weapon_types, fp, Piggy_hamfile_version);
  340.  
  341.         N_powerup_types = PHYSFSX_readInt(fp);
  342.         range_for (auto &p, partial_range(Powerup_info, N_powerup_types))
  343.                 powerup_type_info_read(fp, p);
  344.  
  345.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  346.         N_polygon_models = PHYSFSX_readInt(fp);
  347.         {
  348.                 const auto &&r = partial_range(Polygon_models, N_polygon_models);
  349.         range_for (auto &p, r)
  350.                 polymodel_read(&p, fp);
  351.  
  352.         range_for (auto &p, r)
  353.                 polygon_model_data_read(&p, fp);
  354.         }
  355.  
  356.         range_for (auto &i, partial_range(Dying_modelnums, N_polygon_models))
  357.                 i = PHYSFSX_readInt(fp);
  358.         range_for (auto &i, partial_range(Dead_modelnums, N_polygon_models))
  359.                 i = PHYSFSX_readInt(fp);
  360.  
  361.         t = PHYSFSX_readInt(fp);
  362.         bitmap_index_read_n(fp, partial_range(Gauges, t));
  363.         bitmap_index_read_n(fp, partial_range(Gauges_hires, t));
  364.  
  365.         N_ObjBitmaps = PHYSFSX_readInt(fp);
  366.         bitmap_index_read_n(fp, partial_range(ObjBitmaps, N_ObjBitmaps));
  367.         range_for (auto &i, partial_range(ObjBitmapPtrs, N_ObjBitmaps))
  368.                 i = PHYSFSX_readShort(fp);
  369.  
  370.         player_ship_read(&only_player_ship, fp);
  371.  
  372.         Num_cockpits = PHYSFSX_readInt(fp);
  373.         bitmap_index_read_n(fp, partial_range(cockpit_bitmap, Num_cockpits));
  374.  
  375. //@@    PHYSFS_read( fp, &Num_total_object_types, sizeof(int), 1 );
  376. //@@    PHYSFS_read( fp, ObjType, sizeof(byte), Num_total_object_types );
  377. //@@    PHYSFS_read( fp, ObjId, sizeof(byte), Num_total_object_types );
  378. //@@    PHYSFS_read( fp, ObjStrength, sizeof(fix), Num_total_object_types );
  379.  
  380.         First_multi_bitmap_num = PHYSFSX_readInt(fp);
  381.  
  382.         Num_reactors = PHYSFSX_readInt(fp);
  383.         reactor_read_n(fp, partial_range(Reactors, Num_reactors));
  384.  
  385.         Marker_model_num = PHYSFSX_readInt(fp);
  386.  
  387.         //@@PHYSFS_read( fp, &N_controlcen_guns, sizeof(int), 1 );
  388.         //@@PHYSFS_read( fp, controlcen_gun_points, sizeof(vms_vector), N_controlcen_guns );
  389.         //@@PHYSFS_read( fp, controlcen_gun_dirs, sizeof(vms_vector), N_controlcen_guns );
  390.  
  391.         if (Piggy_hamfile_version < 3) { // D1
  392.                 exit_modelnum = PHYSFSX_readInt(fp);
  393.                 destroyed_exit_modelnum = PHYSFSX_readInt(fp);
  394.         }
  395.         else    // D2: to be loaded later
  396.                 exit_modelnum = destroyed_exit_modelnum = N_polygon_models;
  397. }
  398.  
  399. int extra_bitmap_num = 0;
  400. bool Exit_models_loaded; // this and below only really used for D2
  401. bool Exit_bitmaps_loaded;
  402. unsigned Exit_bitmap_index;
  403.  
  404. static void bm_free_extra_objbitmaps()
  405. {
  406.         int i;
  407.  
  408.         if (!extra_bitmap_num)
  409.                 extra_bitmap_num = Num_bitmap_files;
  410.  
  411.         for (i = Num_bitmap_files; i < extra_bitmap_num; i++)
  412.         {
  413.                 N_ObjBitmaps--;
  414.                 d_free(GameBitmaps[i].bm_mdata);
  415.         }
  416.         extra_bitmap_num = Num_bitmap_files;
  417.         Exit_bitmaps_loaded = false;
  418. }
  419.  
  420. static void bm_free_extra_models()
  421. {
  422.         Exit_models_loaded = false;
  423.         const auto base = std::min(N_D2_POLYGON_MODELS.value, exit_modelnum);
  424.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  425.         range_for (auto &p, partial_range(Polygon_models, base, std::exchange(N_polygon_models, base)))
  426.                 free_model(p);
  427. }
  428.  
  429. //type==1 means 1.1, type==2 means 1.2 (with weapons)
  430. void bm_read_extra_robots(const char *fname, Mission::descent_version_type type)
  431. {
  432.         auto &Robot_joints = LevelSharedRobotJointState.Robot_joints;
  433.         int t,version;
  434.  
  435.         auto fp = PHYSFSX_openReadBuffered(fname);
  436.         if (!fp)
  437.         {
  438.                 Error("Failed to open HAM file \"%s\"", fname);
  439.                 return;
  440.         }
  441.  
  442.         if (type == Mission::descent_version_type::descent2z)
  443.         {
  444.                 int sig;
  445.  
  446.                 sig = PHYSFSX_readInt(fp);
  447.                 if (sig != MAKE_SIG('X','H','A','M'))
  448.                         return;
  449.                 version = PHYSFSX_readInt(fp);
  450.         }
  451.         else
  452.                 version = 0;
  453.         (void)version; // NOTE: we do not need it, but keep it for possible further use
  454.  
  455.         bm_free_extra_models();
  456.         bm_free_extra_objbitmaps();
  457.  
  458.         //read extra weapons
  459.  
  460.         t = PHYSFSX_readInt(fp);
  461.         N_weapon_types = N_D2_WEAPON_TYPES+t;
  462.         weapon_info_read_n(Weapon_info, N_weapon_types, fp, 3, N_D2_WEAPON_TYPES);
  463.  
  464.         //now read robot info
  465.  
  466.         t = PHYSFSX_readInt(fp);
  467.         const auto N_robot_types = LevelSharedRobotInfoState.N_robot_types = N_D2_ROBOT_TYPES + t;
  468.         if (N_robot_types >= MAX_ROBOT_TYPES)
  469.                 Error("Too many robots (%d) in <%s>.  Max is %d.",t,fname,MAX_ROBOT_TYPES-N_D2_ROBOT_TYPES);
  470.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  471.         range_for (auto &r, partial_range(Robot_info, N_D2_ROBOT_TYPES.value, N_robot_types))
  472.                 robot_info_read(fp, r);
  473.  
  474.         t = PHYSFSX_readInt(fp);
  475.         N_robot_joints = N_D2_ROBOT_JOINTS+t;
  476.         if (N_robot_joints >= MAX_ROBOT_JOINTS)
  477.                 Error("Too many robot joints (%d) in <%s>.  Max is %d.",t,fname,MAX_ROBOT_JOINTS-N_D2_ROBOT_JOINTS);
  478.         range_for (auto &r, partial_range(Robot_joints, N_D2_ROBOT_JOINTS.value, N_robot_joints))
  479.                 jointpos_read(fp, r);
  480.  
  481.         unsigned u = PHYSFSX_readInt(fp);
  482.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  483.         N_polygon_models = N_D2_POLYGON_MODELS+u;
  484.         if (N_polygon_models >= MAX_POLYGON_MODELS)
  485.                 Error("Too many polygon models (%d) in <%s>.  Max is %d.",u,fname,MAX_POLYGON_MODELS-N_D2_POLYGON_MODELS);
  486.         {
  487.                 const auto &&r = partial_range(Polygon_models, N_D2_POLYGON_MODELS.value, N_polygon_models);
  488.                 range_for (auto &p, r)
  489.                 polymodel_read(&p, fp);
  490.  
  491.                 range_for (auto &p, r)
  492.                         polygon_model_data_read(&p, fp);
  493.         }
  494.  
  495.         range_for (auto &i, partial_range(Dying_modelnums, N_D2_POLYGON_MODELS.value, N_polygon_models))
  496.                 i = PHYSFSX_readInt(fp);
  497.         range_for (auto &i, partial_range(Dead_modelnums, N_D2_POLYGON_MODELS.value, N_polygon_models))
  498.                 i = PHYSFSX_readInt(fp);
  499.  
  500.         t = PHYSFSX_readInt(fp);
  501.         if (N_D2_OBJBITMAPS+t >= ObjBitmaps.size())
  502.                 Error("Too many object bitmaps (%d) in <%s>.  Max is %" DXX_PRI_size_type ".", t, fname, ObjBitmaps.size() - N_D2_OBJBITMAPS);
  503.         bitmap_index_read_n(fp, partial_range(ObjBitmaps, N_D2_OBJBITMAPS.value, N_D2_OBJBITMAPS + t));
  504.  
  505.         t = PHYSFSX_readInt(fp);
  506.         if (N_D2_OBJBITMAPPTRS+t >= ObjBitmapPtrs.size())
  507.                 Error("Too many object bitmap pointers (%d) in <%s>.  Max is %" DXX_PRI_size_type ".", t, fname, ObjBitmapPtrs.size() - N_D2_OBJBITMAPPTRS);
  508.         range_for (auto &i, partial_range(ObjBitmapPtrs, N_D2_OBJBITMAPPTRS.value, N_D2_OBJBITMAPPTRS + t))
  509.                 i = PHYSFSX_readShort(fp);
  510. }
  511.  
  512. int Robot_replacements_loaded = 0;
  513.  
  514. void load_robot_replacements(const d_fname &level_name)
  515. {
  516.         auto &Robot_joints = LevelSharedRobotJointState.Robot_joints;
  517.         int t,i,j;
  518.         char ifile_name[FILENAME_LEN];
  519.  
  520.         change_filename_extension(ifile_name, level_name, ".HXM" );
  521.  
  522.         auto fp = PHYSFSX_openReadBuffered(ifile_name);
  523.         if (!fp)                //no robot replacement file
  524.                 return;
  525.  
  526.         t = PHYSFSX_readInt(fp);                        //read id "HXM!"
  527.         if (t!= 0x21584d48)
  528.                 Error("ID of HXM! file incorrect");
  529.  
  530.         t = PHYSFSX_readInt(fp);                        //read version
  531.         if (t<1)
  532.                 Error("HXM! version too old (%d)",t);
  533.  
  534.         t = PHYSFSX_readInt(fp);                        //read number of robots
  535.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  536.         const auto N_robot_types = LevelSharedRobotInfoState.N_robot_types;
  537.         for (j=0;j<t;j++) {
  538.                 i = PHYSFSX_readInt(fp);                //read robot number
  539.                 if (i<0 || i>=N_robot_types)
  540.                         Error("Robots number (%d) out of range in (%s).  Range = [0..%d].",i,static_cast<const char *>(level_name),N_robot_types-1);
  541.                 robot_info_read(fp, Robot_info[i]);
  542.         }
  543.  
  544.         t = PHYSFSX_readInt(fp);                        //read number of joints
  545.         for (j=0;j<t;j++) {
  546.                 i = PHYSFSX_readInt(fp);                //read joint number
  547.                 if (i<0 || i>=N_robot_joints)
  548.                         Error("Robots joint (%d) out of range in (%s).  Range = [0..%d].",i,static_cast<const char *>(level_name),N_robot_joints-1);
  549.                 jointpos_read(fp, Robot_joints[i]);
  550.         }
  551.  
  552.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  553.         t = PHYSFSX_readInt(fp);                        //read number of polygon models
  554.         for (j=0;j<t;j++)
  555.         {
  556.                 i = PHYSFSX_readInt(fp);                //read model number
  557.                 if (i<0 || i>=N_polygon_models)
  558.                         Error("Polygon model (%d) out of range in (%s).  Range = [0..%d].",i,static_cast<const char *>(level_name),N_polygon_models-1);
  559.  
  560.                 free_model(Polygon_models[i]);
  561.                 polymodel_read(&Polygon_models[i], fp);
  562.                 polygon_model_data_read(&Polygon_models[i], fp);
  563.  
  564.                 Dying_modelnums[i] = PHYSFSX_readInt(fp);
  565.                 Dead_modelnums[i] = PHYSFSX_readInt(fp);
  566.         }
  567.  
  568.         t = PHYSFSX_readInt(fp);                        //read number of objbitmaps
  569.         for (j=0;j<t;j++) {
  570.                 i = PHYSFSX_readInt(fp);                //read objbitmap number
  571.                 if (i < 0 || i >= ObjBitmaps.size())
  572.                         Error("Object bitmap number (%d) out of range in (%s).  Range = [0..%" DXX_PRI_size_type "].", i, static_cast<const char *>(level_name), ObjBitmaps.size() - 1);
  573.                 bitmap_index_read(fp, ObjBitmaps[i]);
  574.         }
  575.  
  576.         t = PHYSFSX_readInt(fp);                        //read number of objbitmapptrs
  577.         for (j=0;j<t;j++) {
  578.                 i = PHYSFSX_readInt(fp);                //read objbitmapptr number
  579.                 if (i < 0 || i >= ObjBitmapPtrs.size())
  580.                         Error("Object bitmap pointer (%d) out of range in (%s).  Range = [0..%" DXX_PRI_size_type "].", i, static_cast<const char *>(level_name), ObjBitmapPtrs.size() - 1);
  581.                 ObjBitmapPtrs[i] = PHYSFSX_readShort(fp);
  582.         }
  583.         Robot_replacements_loaded = 1;
  584. }
  585.  
  586.  
  587. /*
  588.  * Routines for loading exit models
  589.  *
  590.  * Used by d1 levels (including some add-ons), and by d2 shareware.
  591.  * Could potentially be used by d2 add-on levels, but only if they
  592.  * don't use "extra" robots... or maybe they do
  593.  */
  594.  
  595. // formerly exitmodel_bm_load_sub
  596. static bitmap_index read_extra_bitmap_iff(const char * filename )
  597. {
  598.         bitmap_index bitmap_num;
  599.         grs_bitmap * n = &GameBitmaps[extra_bitmap_num];
  600.         palette_array_t newpal;
  601.         int iff_error;          //reference parm to avoid warning message
  602.  
  603.         bitmap_num.index = 0;
  604.  
  605.         //MALLOC( new, grs_bitmap, 1 );
  606.         iff_error = iff_read_bitmap(filename, *n, &newpal);
  607.         if (iff_error != IFF_NO_ERROR)          {
  608.                 con_printf(CON_DEBUG, "Error loading exit model bitmap <%s> - IFF error: %s", filename, iff_errormsg(iff_error));
  609.                 return bitmap_num;
  610.         }
  611.  
  612.         gr_remap_bitmap_good(*n, newpal, iff_has_transparency ? iff_transparent_color : -1, 254);
  613.  
  614. #if !DXX_USE_OGL
  615.         n->avg_color = 0;       //compute_average_pixel(new);
  616. #endif
  617.  
  618.         bitmap_num.index = extra_bitmap_num;
  619.  
  620.         GameBitmaps[extra_bitmap_num++] = *n;
  621.  
  622.         //d_free( n );
  623.         return bitmap_num;
  624. }
  625.  
  626. // formerly load_exit_model_bitmap
  627. static grs_bitmap *bm_load_extra_objbitmap(const char *name)
  628. {
  629.         assert(N_ObjBitmaps < ObjBitmaps.size());
  630.         {
  631.                 ObjBitmaps[N_ObjBitmaps] = read_extra_bitmap_iff(name);
  632.  
  633.                 if (ObjBitmaps[N_ObjBitmaps].index == 0)
  634.                 {
  635.                         RAIIdmem<char[]> name2(d_strdup(name));
  636.                         *strrchr(name2.get(), '.') = '\0';
  637.                         ObjBitmaps[N_ObjBitmaps] = read_extra_bitmap_d1_pig(name2.get());
  638.                 }
  639.                 if (ObjBitmaps[N_ObjBitmaps].index == 0)
  640.                         return NULL;
  641.  
  642.                 if (GameBitmaps[ObjBitmaps[N_ObjBitmaps].index].bm_w!=64 || GameBitmaps[ObjBitmaps[N_ObjBitmaps].index].bm_h!=64)
  643.                         Error("Bitmap <%s> is not 64x64",name);
  644.                 ObjBitmapPtrs[N_ObjBitmaps] = N_ObjBitmaps;
  645.                 N_ObjBitmaps++;
  646.                 assert(N_ObjBitmaps < ObjBitmaps.size());
  647.                 return &GameBitmaps[ObjBitmaps[N_ObjBitmaps-1].index];
  648.         }
  649. }
  650.  
  651. static void bm_unload_last_objbitmaps(unsigned count)
  652. {
  653.         assert(N_ObjBitmaps >= count);
  654.  
  655.         unsigned new_N_ObjBitmaps = N_ObjBitmaps - count;
  656.         range_for (auto &o, partial_range(ObjBitmaps, new_N_ObjBitmaps, N_ObjBitmaps))
  657.                 d_free(GameBitmaps[o.index].bm_mdata);
  658.         N_ObjBitmaps = new_N_ObjBitmaps;
  659. }
  660.  
  661. // only called for D2 registered, but there is a D1 check anyway for
  662. // possible later use
  663. int load_exit_models()
  664. {
  665.         int start_num;
  666.  
  667. /*
  668.         don't free extra models in native D2 mode -- ziplantil. it's our
  669.         responsibility to make sure the exit stuff is already loaded rather than
  670.         loading it all again.
  671.  
  672.         however, in D1 mode, we always need to reload everything due to how
  673.         the exit data is loaded (which is different from D2 native mode)
  674. */
  675.         if (EMULATING_D1) // D1?
  676.         {
  677.                 bm_free_extra_models();
  678.                 bm_free_extra_objbitmaps();
  679.         }
  680.  
  681.         // make sure there is enough space to load textures and models
  682.         if (!Exit_bitmaps_loaded && N_ObjBitmaps > ObjBitmaps.size() - 6)
  683.         {
  684.                 return 0;
  685.         }
  686.         if (!Exit_models_loaded && N_polygon_models > MAX_POLYGON_MODELS - 2)
  687.         {
  688.                 return 0;
  689.         }
  690.  
  691.         start_num = N_ObjBitmaps;
  692.         if (!Exit_bitmaps_loaded)
  693.         {
  694.                 if (!bm_load_extra_objbitmap("steel1.bbm") ||
  695.                         !bm_load_extra_objbitmap("rbot061.bbm") ||
  696.                         !bm_load_extra_objbitmap("rbot062.bbm") ||
  697.                         !bm_load_extra_objbitmap("steel1.bbm") ||
  698.                         !bm_load_extra_objbitmap("rbot061.bbm") ||
  699.                         !bm_load_extra_objbitmap("rbot063.bbm"))
  700.                 {
  701.                         // unload the textures that we already loaded
  702.                         bm_unload_last_objbitmaps(N_ObjBitmaps - start_num);
  703.                         con_puts(CON_NORMAL, "Can't load exit models!");
  704.                         return 0;
  705.                 }      
  706.                 Exit_bitmap_index = start_num;
  707.         }
  708.  
  709.         auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
  710.         if (Exit_models_loaded && exit_modelnum < N_polygon_models && destroyed_exit_modelnum < N_polygon_models)
  711.         {
  712.                 // already loaded, just adjust texture indexes
  713.                 Polygon_models[exit_modelnum].first_texture = Exit_bitmap_index;
  714.                 Polygon_models[destroyed_exit_modelnum].first_texture = Exit_bitmap_index+3;
  715.                 return 1;
  716.         }
  717.  
  718.         if (auto exit_hamfile = PHYSFSX_openReadBuffered("exit.ham"))
  719.         {
  720.                 exit_modelnum = N_polygon_models++;
  721.                 destroyed_exit_modelnum = N_polygon_models++;
  722.                 polymodel_read(&Polygon_models[exit_modelnum], exit_hamfile);
  723.                 polymodel_read(&Polygon_models[destroyed_exit_modelnum], exit_hamfile);
  724.                 Polygon_models[exit_modelnum].first_texture = start_num;
  725.                 Polygon_models[destroyed_exit_modelnum].first_texture = start_num+3;
  726.  
  727.                 polygon_model_data_read(&Polygon_models[exit_modelnum], exit_hamfile);
  728.  
  729.                 polygon_model_data_read(&Polygon_models[destroyed_exit_modelnum], exit_hamfile);
  730.         } else if (PHYSFSX_exists("exit01.pof",1) && PHYSFSX_exists("exit01d.pof",1)) {
  731.  
  732.                 exit_modelnum = load_polygon_model("exit01.pof", 3, start_num, NULL);
  733.                 destroyed_exit_modelnum = load_polygon_model("exit01d.pof", 3, start_num + 3, NULL);
  734.  
  735. #if DXX_USE_OGL
  736.                 ogl_cache_polymodel_textures(exit_modelnum);
  737.                 ogl_cache_polymodel_textures(destroyed_exit_modelnum);
  738. #endif
  739.         }
  740.         else if ((exit_hamfile = PHYSFSX_openReadBuffered(D1_PIGFILE)))
  741.         {
  742.                 int offset, offset2;
  743.                 int hamsize;
  744.                 hamsize = PHYSFS_fileLength(exit_hamfile);
  745.                 switch (hamsize) { //total hack for loading models
  746.                 case D1_PIGSIZE:
  747.                         offset = 91848;     /* and 92582  */
  748.                         offset2 = 383390;   /* and 394022 */
  749.                         break;
  750.                 default:
  751.                 case D1_SHARE_BIG_PIGSIZE:
  752.                 case D1_SHARE_10_PIGSIZE:
  753.                 case D1_SHARE_PIGSIZE:
  754.                 case D1_10_BIG_PIGSIZE:
  755.                 case D1_10_PIGSIZE:
  756.                         Int3();             /* exit models should be in .pofs */
  757.                         DXX_BOOST_FALLTHROUGH;
  758.                 case D1_OEM_PIGSIZE:
  759.                 case D1_MAC_PIGSIZE:
  760.                 case D1_MAC_SHARE_PIGSIZE:
  761.                         // unload the textures that we already loaded
  762.                         bm_unload_last_objbitmaps(N_ObjBitmaps - start_num);
  763.                         con_puts(CON_NORMAL, "Can't load exit models!");
  764.                         return 0;
  765.                 }
  766.                 PHYSFSX_fseek(exit_hamfile, offset, SEEK_SET);
  767.                 exit_modelnum = N_polygon_models++;
  768.                 destroyed_exit_modelnum = N_polygon_models++;
  769.                 polymodel_read(&Polygon_models[exit_modelnum], exit_hamfile);
  770.                 polymodel_read(&Polygon_models[destroyed_exit_modelnum], exit_hamfile);
  771.                 Polygon_models[exit_modelnum].first_texture = start_num;
  772.                 Polygon_models[destroyed_exit_modelnum].first_texture = start_num+3;
  773.  
  774.                 PHYSFSX_fseek(exit_hamfile, offset2, SEEK_SET);
  775.                 polygon_model_data_read(&Polygon_models[exit_modelnum], exit_hamfile);
  776.                 polygon_model_data_read(&Polygon_models[destroyed_exit_modelnum], exit_hamfile);
  777.         } else {
  778.                 // unload the textures that we already loaded
  779.                 bm_unload_last_objbitmaps(N_ObjBitmaps - start_num);
  780.                 con_puts(CON_NORMAL, "Can't load exit models!");
  781.                 return 0;
  782.         }
  783.  
  784.         // set to be loaded, but only on D2 - always reload the data on D1
  785.         Exit_models_loaded = Exit_bitmaps_loaded = !EMULATING_D1;
  786.         return 1;
  787. }
  788.  
  789. }
  790. #endif
  791.  
  792. void compute_average_rgb(grs_bitmap *bm, std::array<fix, 3> &rgb)
  793. {
  794.         rgb = {};
  795.         if (unlikely(!bm->get_bitmap_data()))
  796.                 return;
  797.         const uint_fast32_t bm_h = bm->bm_h;
  798.         const uint_fast32_t bm_w = bm->bm_w;
  799.         if (unlikely(!bm_h) || unlikely(!bm_w))
  800.                 return;
  801.  
  802.         const auto process_one = [&rgb](uint8_t color) {
  803.                 if (color == TRANSPARENCY_COLOR)
  804.                         return;
  805.                 auto &t_rgb = gr_palette[color];
  806.                 if (t_rgb.r == t_rgb.g && t_rgb.r == t_rgb.b)
  807.                         return;
  808.                 rgb[0] += t_rgb.r;
  809.                 rgb[1] += t_rgb.g;
  810.                 rgb[2] += t_rgb.b;
  811.         };
  812.         if (bm->get_flag_mask(BM_FLAG_RLE))
  813.         {
  814.                 bm_rle_expand expander(*bm);
  815.                 const auto &&buf = std::make_unique<uint8_t[]>(bm_w);
  816.                 range_for (const uint_fast32_t i, xrange(bm_h))
  817.                 {
  818.                         (void)i;
  819.                         const auto &&range = unchecked_partial_range(buf.get(), bm_w);
  820.                         if (expander.step(bm_rle_expand_range(range.begin(), range.end())) != bm_rle_expand::again)
  821.                                 break;
  822.                         range_for (const auto color, range)
  823.                                 process_one(color);
  824.                 }
  825.         }
  826.         else
  827.         {
  828.                 range_for (const auto color, unchecked_partial_range(bm->bm_data, bm_w * bm_h))
  829.                         process_one(color);
  830.         }
  831. }
  832.