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