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 #include "wall.h"
21 #include "text.h"
22 #include "fireball.h"
23 #include "textures.h"
24 #include "newdemo.h"
25 #include "multi.h"
26 #include "gameseq.h"
27 #include "physfs-serial.h"
28 #include "gameseg.h"
29 #include "hudmsg.h"
30 #include "effects.h"
31
32 #include "d_enumerate.h"
33 #include "d_levelstate.h"
34 #include "d_range.h"
35 #include "compiler-range_for.h"
36 #include "segiter.h"
37 #include "d_zip.h"
38
39 // Special door on boss level which is locked if not in multiplayer...sorry for this awful solution --MK.
40 #define BOSS_LOCKED_DOOR_LEVEL 7
41 #define BOSS_LOCKED_DOOR_SEG 595
42 #define BOSS_LOCKED_DOOR_SIDE 5
43
44 namespace dcx {
45 unsigned Num_wall_anims;
46 }
47
48 namespace dsx {
49
50 namespace {
51
52 struct ad_removal_predicate
53 {
54 bool operator()(active_door &) const;
55 };
56
57 struct find_active_door_predicate
58 {
59 const wallnum_t wall_num;
find_active_door_predicatedsx::__anon3178b3790111::find_active_door_predicate60 explicit find_active_door_predicate(const wallnum_t i) :
61 wall_num(i)
62 {
63 }
operator ()dsx::__anon3178b3790111::find_active_door_predicate64 bool operator()(active_door &d) const {
65 if (d.front_wallnum[0] == wall_num)
66 return true;
67 if (d.back_wallnum[0] == wall_num)
68 return true;
69 if (d.n_parts != 2)
70 return false;
71 if (d.front_wallnum[1] == wall_num)
72 return true;
73 if (d.back_wallnum[1] == wall_num)
74 return true;
75 return false;
76 }
77 };
78
79 }
80
81 }
82 #if defined(DXX_BUILD_DESCENT_II)
83 #include "collide.h"
84 namespace dsx {
85 constexpr std::integral_constant<unsigned, F1_0> CLOAKING_WALL_TIME{};
86
87 namespace {
88
89 struct cwframe
90 {
91 wall &w;
92 std::array<uvl, 4> &uvls;
cwframedsx::__anon3178b3790211::cwframe93 cwframe(fvmsegptr &vmsegptr, wall &wr) :
94 w(wr),
95 uvls(vmsegptr(w.segnum)->unique_segment::sides[w.sidenum].uvls)
96 {
97 }
98 };
99
100 struct cwresult
101 {
102 bool remove;
103 bool record;
104 cwresult() = default;
cwresultdsx::__anon3178b3790211::cwresult105 explicit cwresult(bool r) :
106 remove(false), record(r)
107 {
108 }
109 };
110
111 struct cw_removal_predicate
112 {
113 fvmsegptr &vmsegptr;
114 wall_array &Walls;
115 unsigned num_cloaking_walls = 0;
116 bool operator()(cloaking_wall &d);
cw_removal_predicatedsx::__anon3178b3790211::cw_removal_predicate117 cw_removal_predicate(fvmsegptr &v, wall_array &w) :
118 vmsegptr(v), Walls(w)
119 {
120 }
121 };
122
123 struct find_cloaked_wall_predicate
124 {
125 const vmwallidx_t w;
find_cloaked_wall_predicatedsx::__anon3178b3790211::find_cloaked_wall_predicate126 find_cloaked_wall_predicate(const vmwallidx_t i) :
127 w(i)
128 {
129 }
operator ()dsx::__anon3178b3790211::find_cloaked_wall_predicate130 bool operator()(const cloaking_wall &cw) const
131 {
132 return cw.front_wallnum == w || cw.back_wallnum == w;
133 }
134 };
135
136 }
137
138 }
139 #endif
140
141 namespace {
142
get_transparency_check_values(const unique_side & side)143 static std::pair<uint_fast32_t, uint_fast32_t> get_transparency_check_values(const unique_side &side)
144 {
145 if (const auto masked_tmap_num2 = static_cast<uint_fast32_t>(get_texture_index(side.tmap_num2)))
146 return {masked_tmap_num2, BM_FLAG_SUPER_TRANSPARENT};
147 return {get_texture_index(side.tmap_num), BM_FLAG_TRANSPARENT};
148 }
149
150 // This function determines whether the current segment/side is transparent
151 // 1 = YES
152 // 0 = NO
check_transparency(const GameBitmaps_array & GameBitmaps,const Textures_array & Textures,const unique_side & side)153 static uint_fast32_t check_transparency(const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, const unique_side &side)
154 {
155 const auto &&v = get_transparency_check_values(side);
156 return GameBitmaps[Textures[v.first].index].get_flag_mask(v.second);
157 }
158
159 }
160
161 //-----------------------------------------------------------------
162 // This function checks whether we can fly through the given side.
163 // In other words, whether or not we have a 'doorway'
164 // Flags:
165 // WID_FLY_FLAG 1
166 // WID_RENDER_FLAG 2
167 // WID_RENDPAST_FLAG 4
168 // Return values:
169 // WID_WALL 2 // 0/1/0 wall
170 // WID_TRANSPARENT_WALL 6 // 0/1/1 transparent wall
171 // WID_ILLUSORY_WALL 3 // 1/1/0 illusory wall
172 // WID_TRANSILLUSORY_WALL 7 // 1/1/1 transparent illusory wall
173 // WID_NO_WALL 5 // 1/0/1 no wall, can fly through
174 namespace dsx {
175
176 namespace {
177
wall_is_doorway(const GameBitmaps_array & GameBitmaps,const Textures_array & Textures,fvcwallptr & vcwallptr,const shared_side & sside,const unique_side & uside)178 static WALL_IS_DOORWAY_result_t wall_is_doorway(const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, fvcwallptr &vcwallptr, const shared_side &sside, const unique_side &uside)
179 {
180 auto &w = *vcwallptr(sside.wall_num);
181 const auto type = w.type;
182 if (type == WALL_OPEN)
183 return WID_NO_WALL;
184
185 #if defined(DXX_BUILD_DESCENT_II)
186 if (unlikely(type == WALL_CLOAKED))
187 return WID_CLOAKED_WALL;
188 #endif
189
190 const auto flags = w.flags;
191 if (type == WALL_ILLUSION) {
192 if (flags & wall_flag::illusion_off)
193 return WID_NO_WALL;
194 else {
195 if (check_transparency(GameBitmaps, Textures, uside))
196 return WID_TRANSILLUSORY_WALL;
197 else
198 return WID_ILLUSORY_WALL;
199 }
200 }
201
202 if (type == WALL_BLASTABLE) {
203 if (flags & wall_flag::blasted)
204 return WID_TRANSILLUSORY_WALL;
205 }
206 else
207 {
208 if (unlikely(flags & wall_flag::door_opened))
209 return WID_TRANSILLUSORY_WALL;
210 if (likely(type == WALL_DOOR) && unlikely(w.state == wall_state::opening))
211 return WID_TRANSPARENT_WALL;
212 }
213 // If none of the above flags are set, there is no doorway.
214 if (check_transparency(GameBitmaps, Textures, uside))
215 return WID_TRANSPARENT_WALL;
216 else
217 return WID_WALL; // There are children behind the door.
218 }
219
220 }
221
WALL_IS_DOORWAY(const GameBitmaps_array & GameBitmaps,const Textures_array & Textures,fvcwallptr & vcwallptr,const cscusegment seg,const uint_fast32_t side)222 WALL_IS_DOORWAY_result_t WALL_IS_DOORWAY(const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, fvcwallptr &vcwallptr, const cscusegment seg, const uint_fast32_t side)
223 {
224 const auto child = seg.s.children[side];
225 if (unlikely(child == segment_none))
226 return WID_WALL;
227 if (unlikely(child == segment_exit))
228 return WID_EXTERNAL;
229 auto &sside = seg.s.sides[side];
230 if (likely(sside.wall_num == wall_none))
231 return WID_NO_WALL;
232 auto &uside = seg.u.sides[side];
233 return wall_is_doorway(GameBitmaps, Textures, vcwallptr, sside, uside);
234 }
235
236 #if DXX_USE_EDITOR
237 //-----------------------------------------------------------------
238 // Initializes all the walls (in other words, no special walls)
wall_init()239 void wall_init()
240 {
241 init_exploding_walls();
242 auto &Walls = LevelUniqueWallSubsystemState.Walls;
243 Walls.set_count(0);
244 range_for (auto &w, Walls)
245 {
246 w.segnum = segment_none;
247 w.sidenum = -1;
248 w.type = WALL_NORMAL;
249 w.flags = {};
250 w.hps = 0;
251 w.trigger = trigger_none;
252 w.clip_num = -1;
253 w.linked_wall = wall_none;
254 }
255 auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
256 ActiveDoors.set_count(0);
257 #if defined(DXX_BUILD_DESCENT_II)
258 auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls;
259 CloakingWalls.set_count(0);
260 #endif
261
262 }
263 #endif
264
265 //set the tmap_num or tmap_num2 field for a wall/door
wall_set_tmap_num(const wclip & anim,const vmsegptridx_t seg,const unsigned side,const vmsegptridx_t csegp,const unsigned cside,const unsigned frame_num)266 void wall_set_tmap_num(const wclip &anim, const vmsegptridx_t seg, const unsigned side, const vmsegptridx_t csegp, const unsigned cside, const unsigned frame_num)
267 {
268 const auto newdemo_state = Newdemo_state;
269 if (newdemo_state == ND_STATE_PLAYBACK)
270 return;
271
272 const auto tmap = anim.frames[frame_num];
273 auto &uside = seg->unique_segment::sides[side];
274 auto &cuside = csegp->unique_segment::sides[cside];
275 if (anim.flags & WCF_TMAP1) {
276 const texture1_value t1{tmap};
277 if (t1 != uside.tmap_num || t1 != cuside.tmap_num)
278 {
279 uside.tmap_num = cuside.tmap_num = t1;
280 if (newdemo_state == ND_STATE_RECORDING)
281 newdemo_record_wall_set_tmap_num1(seg,side,csegp,cside,t1);
282 }
283 } else {
284 const texture2_value t2{tmap};
285 if (t2 != uside.tmap_num2 || t2 != cuside.tmap_num2)
286 {
287 uside.tmap_num2 = cuside.tmap_num2 = t2;
288 if (newdemo_state == ND_STATE_RECORDING)
289 newdemo_record_wall_set_tmap_num2(seg,side,csegp,cside,t2);
290 }
291 }
292 }
293
294 }
295
296 namespace {
297
298 // -------------------------------------------------------------------------------
299 //when the wall has used all its hitpoints, this will destroy it
blast_blastable_wall(const vmsegptridx_t seg,const unsigned side,wall & w0)300 static void blast_blastable_wall(const vmsegptridx_t seg, const unsigned side, wall &w0)
301 {
302 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
303 auto &Objects = LevelUniqueObjectState.Objects;
304 auto &Vertices = LevelSharedVertexState.get_vertices();
305 auto &vmobjptr = Objects.vmptr;
306 auto &sside = seg->shared_segment::sides[side];
307 const auto wall_num = sside.wall_num;
308 w0.hps = -1; //say it's blasted
309
310 const auto &&csegp = seg.absolute_sibling(seg->shared_segment::children[side]);
311 auto Connectside = find_connect_side(seg, csegp);
312 Assert(Connectside != side_none);
313 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
314 auto &Walls = LevelUniqueWallSubsystemState.Walls;
315 auto &WallAnims = GameSharedState.WallAnims;
316 auto &imwallptr = Walls.imptr;
317 const auto &&w1 = imwallptr(cwall_num);
318 if (w1)
319 LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, cwall_num);
320 LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, wall_num);
321 flush_fcd_cache();
322
323 const auto a = w0.clip_num;
324 auto &wa = WallAnims[a];
325 //if this is an exploding wall, explode it
326 if (wa.flags & WCF_EXPLODES)
327 {
328 auto &vcvertptr = Vertices.vcptr;
329 explode_wall(vcvertptr, seg, side, w0);
330 }
331 else {
332 //if not exploding, set final frame, and make door passable
333 const auto n = wa.num_frames;
334 w0.flags |= wall_flag::blasted;
335 if (w1)
336 w1->flags |= wall_flag::blasted;
337 wall_set_tmap_num(wa, seg, side, csegp, Connectside, n - 1);
338 }
339
340 }
341
342 }
343
344 //-----------------------------------------------------------------
345 // Destroys a blastable wall.
wall_destroy(const vmsegptridx_t seg,const unsigned side)346 void wall_destroy(const vmsegptridx_t seg, const unsigned side)
347 {
348 auto &Walls = LevelUniqueWallSubsystemState.Walls;
349 auto &vmwallptr = Walls.vmptr;
350 auto &w = *vmwallptr(seg->shared_segment::sides[side].wall_num);
351 if (w.type == WALL_BLASTABLE)
352 blast_blastable_wall(seg, side, w);
353 else
354 Error("Hey bub, you are trying to destroy an indestructable wall.");
355 }
356
357 //-----------------------------------------------------------------
358 // Deteriorate appearance of wall. (Changes bitmap (paste-ons))
wall_damage(const vmsegptridx_t seg,const unsigned side,fix damage)359 void wall_damage(const vmsegptridx_t seg, const unsigned side, fix damage)
360 {
361 auto &WallAnims = GameSharedState.WallAnims;
362 int i;
363
364 auto &sside = seg->shared_segment::sides[side];
365 const auto wall_num = sside.wall_num;
366 if (wall_num == wall_none) {
367 return;
368 }
369
370 auto &Walls = LevelUniqueWallSubsystemState.Walls;
371 auto &vmwallptr = Walls.vmptr;
372 auto &w0 = *vmwallptr(wall_num);
373 if (w0.type != WALL_BLASTABLE)
374 return;
375
376 if (!(w0.flags & wall_flag::blasted) && w0.hps >= 0)
377 {
378 const auto &&csegp = seg.absolute_sibling(seg->shared_segment::children[side]);
379 auto Connectside = find_connect_side(seg, csegp);
380 Assert(Connectside != side_none);
381 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
382 auto &imwallptr = Walls.imptr;
383 if (const auto &&w1p = imwallptr(cwall_num))
384 {
385 auto &w1 = *w1p;
386 w1.hps -= damage;
387 }
388 w0.hps -= damage;
389
390 const auto a = w0.clip_num;
391 const auto n = WallAnims[a].num_frames;
392
393 if (w0.hps < WALL_HPS*1/n) {
394 blast_blastable_wall(seg, side, w0);
395 if (Game_mode & GM_MULTI)
396 multi_send_door_open(seg, side, w0.flags);
397 }
398 else
399 for (i=0;i<n;i++)
400 if (w0.hps < WALL_HPS*(n-i)/n)
401 {
402 wall_set_tmap_num(WallAnims[a], seg, side, csegp, Connectside, i);
403 }
404 }
405 }
406
407
408 //-----------------------------------------------------------------
409 // Opens a door
410 namespace dsx {
wall_open_door(const vmsegptridx_t seg,const unsigned side)411 void wall_open_door(const vmsegptridx_t seg, const unsigned side)
412 {
413 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
414 auto &Objects = LevelUniqueObjectState.Objects;
415 auto &Vertices = LevelSharedVertexState.get_vertices();
416 auto &WallAnims = GameSharedState.WallAnims;
417 auto &vmobjptr = Objects.vmptr;
418 active_door *d;
419
420 auto &sside = seg->shared_segment::sides[side];
421 const auto wall_num = sside.wall_num;
422 auto &Walls = LevelUniqueWallSubsystemState.Walls;
423 auto &vmwallptr = Walls.vmptr;
424 wall *const w = vmwallptr(wall_num);
425 LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, wall_num);
426
427 if (w->state == wall_state::opening || //already opening
428 w->state == wall_state::waiting) //open, waiting to close
429 return;
430 #if defined(DXX_BUILD_DESCENT_II)
431 if (w->state == wall_state::open) //open, & staying open
432 return;
433 #endif
434
435 auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
436 auto &vmactdoorptr = ActiveDoors.vmptr;
437 if (w->state == wall_state::closing) { //closing, so reuse door
438 const auto &&r = make_range(vmactdoorptr);
439 const auto &&i = std::find_if(r.begin(), r.end(), find_active_door_predicate(wall_num));
440 if (i == r.end()) // likely in demo playback or multiplayer
441 {
442 const auto c = ActiveDoors.get_count();
443 ActiveDoors.set_count(c + 1);
444 d = vmactdoorptr(static_cast<actdoornum_t>(c));
445 d->time = 0;
446 }
447 else
448 {
449 d = *i;
450 d->time = WallAnims[w->clip_num].play_time - d->time;
451 if (d->time < 0)
452 d->time = 0;
453 }
454 }
455 else { //create new door
456 Assert(w->state == wall_state::closed);
457 const auto i = ActiveDoors.get_count();
458 ActiveDoors.set_count(i + 1);
459 d = vmactdoorptr(static_cast<actdoornum_t>(i));
460 d->time = 0;
461 }
462
463
464 w->state = wall_state::opening;
465
466 // So that door can't be shot while opening
467 const auto &&csegp = vcsegptr(seg->shared_segment::children[side]);
468 auto Connectside = find_connect_side(seg, csegp);
469 if (Connectside != side_none)
470 {
471 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
472 auto &imwallptr = Walls.imptr;
473 if (const auto &&w1 = imwallptr(cwall_num))
474 {
475 w1->state = wall_state::opening;
476 d->back_wallnum[0] = cwall_num;
477 }
478 d->front_wallnum[0] = seg->shared_segment::sides[side].wall_num;
479 }
480 else
481 con_printf(CON_URGENT, "Illegal Connectside %i in wall_open_door. Trying to hop over. Please check your level!", side);
482
483 if (Newdemo_state == ND_STATE_RECORDING) {
484 newdemo_record_door_opening(seg, side);
485 }
486
487 if (w->linked_wall != wall_none)
488 {
489 wall *const w2 = vmwallptr(w->linked_wall);
490
491 Assert(w2->linked_wall == seg->shared_segment::sides[side].wall_num);
492 //Assert(!(w2->flags & WALL_DOOR_OPENING || w2->flags & WALL_DOOR_OPENED));
493
494 w2->state = wall_state::opening;
495
496 const auto &&seg2 = vcsegptridx(w2->segnum);
497 const auto &&csegp2 = vcsegptr(seg2->shared_segment::children[w2->sidenum]);
498 Connectside = find_connect_side(seg2, csegp2);
499 Assert(Connectside != side_none);
500 const auto cwall_num = csegp2->shared_segment::sides[Connectside].wall_num;
501 auto &imwallptr = Walls.imptr;
502 if (const auto &&w3 = imwallptr(cwall_num))
503 w3->state = wall_state::opening;
504
505 d->n_parts = 2;
506 d->front_wallnum[1] = w->linked_wall;
507 d->back_wallnum[1] = cwall_num;
508 }
509 else
510 d->n_parts = 1;
511
512
513 if ( Newdemo_state != ND_STATE_PLAYBACK )
514 {
515 // NOTE THE LINK TO ABOVE!!!!
516 auto &vcvertptr = Vertices.vcptr;
517 const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side);
518 const auto open_sound = WallAnims[w->clip_num].open_sound;
519 if (open_sound > -1)
520 digi_link_sound_to_pos(open_sound, seg, side, cp, 0, F1_0);
521
522 }
523 }
524
525 #if defined(DXX_BUILD_DESCENT_II)
526 //-----------------------------------------------------------------
527 // start the transition from closed -> open wall
start_wall_cloak(const vmsegptridx_t seg,const unsigned side)528 void start_wall_cloak(const vmsegptridx_t seg, const unsigned side)
529 {
530 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
531 auto &Vertices = LevelSharedVertexState.get_vertices();
532 cloaking_wall *d;
533
534 if ( Newdemo_state==ND_STATE_PLAYBACK ) return;
535
536 auto &Walls = LevelUniqueWallSubsystemState.Walls;
537 const auto &&w = Walls.vmptridx(seg->shared_segment::sides[side].wall_num);
538
539 if (w->type == WALL_OPEN || w->state == wall_state::cloaking) //already open or cloaking
540 return;
541
542 const auto &&csegp = vcsegptr(seg->children[side]);
543 auto Connectside = find_connect_side(seg, csegp);
544 Assert(Connectside != side_none);
545 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
546
547 auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls;
548 if (w->state == wall_state::decloaking)
549 { //decloaking, so reuse door
550 const auto &&r = make_range(CloakingWalls.vmptr);
551 const auto i = std::find_if(r.begin(), r.end(), find_cloaked_wall_predicate(w));
552 if (i == r.end())
553 {
554 d_debugbreak();
555 return;
556 }
557 d = *i;
558 d->time = CLOAKING_WALL_TIME - d->time;
559 }
560 else if (w->state == wall_state::closed) { //create new door
561 const clwallnum_t c = CloakingWalls.get_count();
562 if (c >= CloakingWalls.size())
563 {
564 Int3(); //ran out of cloaking wall slots
565 w->type = WALL_OPEN;
566 if (const auto &&w1 = Walls.imptr(cwall_num))
567 w1->type = WALL_OPEN;
568 return;
569 }
570 CloakingWalls.set_count(c + 1);
571 d = CloakingWalls.vmptr(c);
572 d->time = 0;
573 }
574 else {
575 Int3(); //unexpected wall state
576 return;
577 }
578
579 w->state = wall_state::cloaking;
580 if (const auto &&w1 = Walls.imptr(cwall_num))
581 w1->state = wall_state::cloaking;
582
583 d->front_wallnum = seg->shared_segment::sides[side].wall_num;
584 d->back_wallnum = cwall_num;
585 Assert(w->linked_wall == wall_none);
586
587 if ( Newdemo_state != ND_STATE_PLAYBACK ) {
588 auto &vcvertptr = Vertices.vcptr;
589 const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side);
590 digi_link_sound_to_pos( SOUND_WALL_CLOAK_ON, seg, side, cp, 0, F1_0 );
591 }
592
593 for (auto &&[front_ls, back_ls, s0_uvls, s1_uvls] : zip(
594 d->front_ls,
595 d->back_ls,
596 seg->unique_segment::sides[side].uvls,
597 csegp->unique_segment::sides[Connectside].uvls
598 ))
599 {
600 front_ls = s0_uvls.l;
601 back_ls = s1_uvls.l;
602 }
603 }
604
605 //-----------------------------------------------------------------
606 // start the transition from open -> closed wall
start_wall_decloak(const vmsegptridx_t seg,const unsigned side)607 void start_wall_decloak(const vmsegptridx_t seg, const unsigned side)
608 {
609 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
610 auto &Vertices = LevelSharedVertexState.get_vertices();
611 cloaking_wall *d;
612
613 if ( Newdemo_state==ND_STATE_PLAYBACK ) return;
614
615 auto &sside = seg->shared_segment::sides[side];
616 assert(sside.wall_num != wall_none); //Opening door on illegal wall
617
618 auto &Walls = LevelUniqueWallSubsystemState.Walls;
619 const auto &&w = Walls.vmptridx(sside.wall_num);
620
621 if (w->type == WALL_CLOSED || w->state == wall_state::decloaking) //already closed or decloaking
622 return;
623
624 auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls;
625 if (w->state == wall_state::cloaking) { //cloaking, so reuse door
626 const auto &&r = make_range(CloakingWalls.vmptr);
627 const auto i = std::find_if(r.begin(), r.end(), find_cloaked_wall_predicate(w));
628 if (i == r.end())
629 {
630 d_debugbreak();
631 return;
632 }
633 d = *i;
634 d->time = CLOAKING_WALL_TIME - d->time;
635 }
636 else if (w->state == wall_state::closed) { //create new door
637 const clwallnum_t c = CloakingWalls.get_count();
638 if (c >= CloakingWalls.size())
639 {
640 Int3(); //ran out of cloaking wall slots
641 /* what is this _doing_ here?
642 w->type = WALL_CLOSED;
643 Walls[csegp->sides[Connectside].wall_num].type = WALL_CLOSED;
644 */
645 return;
646 }
647 CloakingWalls.set_count(c + 1);
648 d = CloakingWalls.vmptr(c);
649 d->time = 0;
650 }
651 else {
652 Int3(); //unexpected wall state
653 return;
654 }
655
656 w->state = wall_state::decloaking;
657
658 // So that door can't be shot while opening
659 const auto &&csegp = vcsegptr(seg->children[side]);
660 auto Connectside = find_connect_side(seg, csegp);
661 Assert(Connectside != side_none);
662 auto &csside = csegp->shared_segment::sides[Connectside];
663 const auto cwall_num = csside.wall_num;
664 if (const auto &&w1 = Walls.imptr(cwall_num))
665 w1->state = wall_state::decloaking;
666
667 d->front_wallnum = seg->shared_segment::sides[side].wall_num;
668 d->back_wallnum = csside.wall_num;
669 Assert(w->linked_wall == wall_none);
670
671 if ( Newdemo_state != ND_STATE_PLAYBACK ) {
672 auto &vcvertptr = Vertices.vcptr;
673 const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side);
674 digi_link_sound_to_pos( SOUND_WALL_CLOAK_OFF, seg, side, cp, 0, F1_0 );
675 }
676
677 for (auto &&[front_ls, back_ls, s0_uvls, s1_uvls] : zip(
678 d->front_ls,
679 d->back_ls,
680 seg->unique_segment::sides[side].uvls,
681 csegp->unique_segment::sides[Connectside].uvls
682 ))
683 {
684 front_ls = s0_uvls.l;
685 back_ls = s1_uvls.l;
686 }
687 }
688 #endif
689
690 //-----------------------------------------------------------------
691 // This function closes the specified door and restores the closed
692 // door texture. This is called when the animation is done
wall_close_door_ref(fvmsegptridx & vmsegptridx,wall_array & Walls,const wall_animations_array & WallAnims,active_door & d)693 void wall_close_door_ref(fvmsegptridx &vmsegptridx, wall_array &Walls, const wall_animations_array &WallAnims, active_door &d)
694 {
695 range_for (const auto p, partial_const_range(d.front_wallnum, d.n_parts))
696 {
697 wall &w = *Walls.vmptr(p);
698
699 const auto &&seg = vmsegptridx(w.segnum);
700 const auto side = w.sidenum;
701 w.state = wall_state::closed;
702
703 assert(seg->shared_segment::sides[side].wall_num != wall_none); //Closing door on illegal wall
704
705 const auto &&csegp = seg.absolute_sibling(seg->shared_segment::children[side]);
706 auto Connectside = find_connect_side(seg, csegp);
707 Assert(Connectside != side_none);
708 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
709 if (const auto &&w1 = Walls.imptr(cwall_num))
710 w1->state = wall_state::closed;
711
712 wall_set_tmap_num(WallAnims[w.clip_num], seg, side, csegp, Connectside, 0);
713 }
714 }
715
716 }
717
718 namespace dcx {
719
720 namespace {
721
check_poke(fvcvertptr & vcvertptr,const object_base & obj,const shared_segment & seg,const unsigned side)722 static unsigned check_poke(fvcvertptr &vcvertptr, const object_base &obj, const shared_segment &seg, const unsigned side)
723 {
724 //note: don't let objects with zero size block door
725 if (!obj.size)
726 return 0;
727 return get_seg_masks(vcvertptr, obj.pos, seg, obj.size).sidemask & (1 << side); //pokes through side!
728 }
729
730 }
731
732 }
733
734 namespace dsx {
735
736 namespace {
737
is_door_side_obstructed(fvcobjptridx & vcobjptridx,fvcsegptr & vcsegptr,const cscusegment seg,const unsigned side)738 static unsigned is_door_side_obstructed(fvcobjptridx &vcobjptridx, fvcsegptr &vcsegptr, const cscusegment seg, const unsigned side)
739 {
740 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
741 auto &Vertices = LevelSharedVertexState.get_vertices();
742 auto &vcvertptr = Vertices.vcptr;
743 range_for (const object_base &obj, objects_in(seg, vcobjptridx, vcsegptr))
744 {
745 #if defined(DXX_BUILD_DESCENT_II)
746 if (obj.type == OBJ_WEAPON)
747 continue;
748 if (obj.type == OBJ_FIREBALL)
749 continue;
750 #endif
751 if (const auto obstructed = check_poke(vcvertptr, obj, seg, side))
752 return obstructed;
753 }
754 return 0;
755 }
756
757 //returns true if door is obstructed (& thus cannot close)
is_door_obstructed(fvcobjptridx & vcobjptridx,fvcsegptr & vcsegptr,const vcsegptridx_t seg,const unsigned side)758 static unsigned is_door_obstructed(fvcobjptridx &vcobjptridx, fvcsegptr &vcsegptr, const vcsegptridx_t seg, const unsigned side)
759 {
760 if (const auto obstructed = is_door_side_obstructed(vcobjptridx, vcsegptr, seg, side))
761 return obstructed;
762 const auto &&csegp = vcsegptr(seg->shared_segment::children[side]);
763 const auto &&Connectside = find_connect_side(seg, csegp);
764 Assert(Connectside != side_none);
765 //go through each object in each of two segments, and see if
766 //it pokes into the connecting seg
767 return is_door_side_obstructed(vcobjptridx, vcsegptr, csegp, Connectside);
768 }
769
770 }
771
772 #if defined(DXX_BUILD_DESCENT_II)
773 //-----------------------------------------------------------------
774 // Closes a door
wall_close_door(wall_array & Walls,const vmsegptridx_t seg,const unsigned side)775 void wall_close_door(wall_array &Walls, const vmsegptridx_t seg, const unsigned side)
776 {
777 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
778 auto &Objects = LevelUniqueObjectState.Objects;
779 auto &Vertices = LevelSharedVertexState.get_vertices();
780 auto &WallAnims = GameSharedState.WallAnims;
781 auto &vcobjptridx = Objects.vcptridx;
782 active_door *d;
783
784 const auto wall_num = seg->shared_segment::sides[side].wall_num;
785 wall *const w = Walls.vmptr(wall_num);
786 if ((w->state == wall_state::closing) || //already closing
787 (w->state == wall_state::waiting) || //open, waiting to close
788 (w->state == wall_state::closed)) //closed
789 return;
790
791 if (is_door_obstructed(vcobjptridx, vcsegptr, seg, side))
792 return;
793
794 auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
795 auto &vmactdoorptr = ActiveDoors.vmptr;
796 if (w->state == wall_state::opening)
797 { //reuse door
798 const auto &&r = make_range(vmactdoorptr);
799 const auto &&i = std::find_if(r.begin(), r.end(), find_active_door_predicate(wall_num));
800 if (i == r.end())
801 {
802 d_debugbreak();
803 return;
804 }
805 d = *i;
806 d->time = WallAnims[w->clip_num].play_time - d->time;
807
808 if (d->time < 0)
809 d->time = 0;
810
811 }
812 else { //create new door
813 assert(w->state == wall_state::open);
814 const auto i = ActiveDoors.get_count();
815 ActiveDoors.set_count(i + 1);
816 d = vmactdoorptr(static_cast<actdoornum_t>(i));
817 d->time = 0;
818 }
819
820 w->state = wall_state::closing;
821
822 // So that door can't be shot while opening
823 const auto &&csegp = vcsegptr(seg->children[side]);
824 const auto &&Connectside = find_connect_side(seg, csegp);
825 Assert(Connectside != side_none);
826 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
827 if (const auto &&w1 = Walls.imptr(cwall_num))
828 w1->state = wall_state::closing;
829
830 d->front_wallnum[0] = seg->shared_segment::sides[side].wall_num;
831 d->back_wallnum[0] = cwall_num;
832 if (Newdemo_state == ND_STATE_RECORDING) {
833 newdemo_record_door_opening(seg, side);
834 }
835
836 if (w->linked_wall != wall_none)
837 {
838 Int3(); //don't think we ever used linked walls
839 }
840 else
841 d->n_parts = 1;
842
843
844 if ( Newdemo_state != ND_STATE_PLAYBACK )
845 {
846 // NOTE THE LINK TO ABOVE!!!!
847 auto &vcvertptr = Vertices.vcptr;
848 const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side);
849 const auto open_sound = WallAnims[w->clip_num].open_sound;
850 if (open_sound > -1)
851 digi_link_sound_to_pos(open_sound, seg, side, cp, 0, F1_0);
852
853 }
854 }
855 #endif
856
857 namespace {
858
859 //-----------------------------------------------------------------
860 // Animates opening of a door.
861 // Called in the game loop.
do_door_open(active_door & d)862 static bool do_door_open(active_door &d)
863 {
864 auto &Objects = LevelUniqueObjectState.Objects;
865 auto &WallAnims = GameSharedState.WallAnims;
866 auto &vmobjptr = Objects.vmptr;
867 bool remove = false;
868 auto &Walls = LevelUniqueWallSubsystemState.Walls;
869 auto &vmwallptr = Walls.vmptr;
870 for (unsigned p = 0; p < d.n_parts; ++p)
871 {
872 int side;
873 fix time_elapsed, time_total, one_frame;
874 int i;
875
876 wall &w = *vmwallptr(d.front_wallnum[p]);
877 LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, d.front_wallnum[p]);
878 LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, d.back_wallnum[p]);
879
880 const auto &&seg = vmsegptridx(w.segnum);
881 side = w.sidenum;
882
883 if (seg->shared_segment::sides[side].wall_num == wall_none)
884 {
885 con_printf(CON_URGENT, "Trying to do_door_open on illegal wall %i. Please check your level!",side);
886 continue;
887 }
888
889 const auto &&csegp = seg.absolute_sibling(seg->shared_segment::children[side]);
890 const auto &&Connectside = find_connect_side(seg, csegp);
891 Assert(Connectside != side_none);
892
893 d.time += FrameTime;
894
895 time_elapsed = d.time;
896 auto &wa = WallAnims[w.clip_num];
897 const auto n = wa.num_frames;
898 time_total = wa.play_time;
899
900 one_frame = time_total/n;
901
902 i = time_elapsed/one_frame;
903
904 if (i < n)
905 wall_set_tmap_num(wa, seg, side, csegp, Connectside, i);
906
907 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
908 auto &w1 = *vmwallptr(cwall_num);
909 if (i> n/2) {
910 w.flags |= wall_flag::door_opened;
911 w1.flags |= wall_flag::door_opened;
912 }
913
914 if (i >= n-1) {
915 wall_set_tmap_num(wa, seg, side, csegp, Connectside, n - 1);
916
917 // If our door is not automatic just remove it from the list.
918 if (!(w.flags & wall_flag::door_auto))
919 {
920 remove = true;
921 #if defined(DXX_BUILD_DESCENT_II)
922 w.state = wall_state::open;
923 w1.state = wall_state::open;
924 #endif
925 }
926 else {
927 w.state = wall_state::waiting;
928 w1.state = wall_state::waiting;
929 }
930 }
931
932 }
933 flush_fcd_cache();
934 return remove;
935 }
936
937 //-----------------------------------------------------------------
938 // Animates and processes the closing of a door.
939 // Called from the game loop.
do_door_close(active_door & d)940 static bool do_door_close(active_door &d)
941 {
942 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
943 auto &Objects = LevelUniqueObjectState.Objects;
944 auto &Vertices = LevelSharedVertexState.get_vertices();
945 auto &vcobjptridx = Objects.vcptridx;
946 auto &Walls = LevelUniqueWallSubsystemState.Walls;
947 auto &WallAnims = GameSharedState.WallAnims;
948 auto &vmwallptr = Walls.vmptr;
949 auto &w0 = *vmwallptr(d.front_wallnum[0]);
950 const auto &&seg0 = vmsegptridx(w0.segnum);
951
952 //check for objects in doorway before closing
953 if (w0.flags & wall_flag::door_auto)
954 if (is_door_obstructed(vcobjptridx, vcsegptr, seg0, w0.sidenum))
955 {
956 #if defined(DXX_BUILD_DESCENT_II)
957 digi_kill_sound_linked_to_segment(w0.segnum, w0.sidenum, -1);
958 wall_open_door(seg0, w0.sidenum); //re-open door
959 #endif
960 return false;
961 }
962
963 bool played_sound = false;
964 bool remove = false;
965 range_for (const auto p, partial_const_range(d.front_wallnum, d.n_parts))
966 {
967 int side;
968 fix time_elapsed, time_total, one_frame;
969 int i;
970
971 auto &wp = *vmwallptr(p);
972
973 const auto &&seg = vmsegptridx(wp.segnum);
974 side = wp.sidenum;
975
976 if (seg->shared_segment::sides[side].wall_num == wall_none) {
977 return false;
978 }
979
980 #if defined(DXX_BUILD_DESCENT_I)
981 //if here, must be auto door
982 //don't assert here, because now we have triggers to close non-auto doors
983 assert(wp.flags & wall_flag::door_auto);
984 #endif
985
986 // Otherwise, close it.
987 const auto &&csegp = seg.absolute_sibling(seg->shared_segment::children[side]);
988 const auto &&Connectside = find_connect_side(seg, csegp);
989 Assert(Connectside != side_none);
990
991
992 auto &wa = WallAnims[wp.clip_num];
993 if ( Newdemo_state != ND_STATE_PLAYBACK )
994 {
995 // NOTE THE LINK TO ABOVE!!
996 if (!played_sound) //only play one sound for linked doors
997 {
998 played_sound = true;
999 if (d.time == 0)
1000 { //first time
1001 const auto close_sound = wa.close_sound;
1002 if (close_sound > -1)
1003 {
1004 auto &vcvertptr = Vertices.vcptr;
1005 digi_link_sound_to_pos(close_sound, seg, side, compute_center_point_on_side(vcvertptr, seg, side), 0, F1_0);
1006 }
1007 }
1008 }
1009 }
1010
1011 d.time += FrameTime;
1012
1013 time_elapsed = d.time;
1014 const auto n = wa.num_frames;
1015 time_total = wa.play_time;
1016
1017 one_frame = time_total/n;
1018
1019 i = n-time_elapsed/one_frame-1;
1020
1021 const auto cwall_num = csegp->shared_segment::sides[Connectside].wall_num;
1022 auto &w1 = *vmwallptr(cwall_num);
1023 if (i < n/2) {
1024 wp.flags &= ~wall_flag::door_opened;
1025 w1.flags &= ~wall_flag::door_opened;
1026 }
1027
1028 // Animate door.
1029 if (i > 0) {
1030 wall_set_tmap_num(wa, seg, side, csegp, Connectside, i);
1031
1032 wp.state = wall_state::closing;
1033 w1.state = wall_state::closing;
1034 } else
1035 remove = true;
1036 }
1037
1038 if (remove)
1039 wall_close_door_ref(Segments.vmptridx, Walls, WallAnims, d);
1040 return remove;
1041 }
1042
wall_illusion_op(fvmwallptr & vmwallptr,const vcsegptridx_t seg,const unsigned side)1043 static std::pair<wall *, wall *> wall_illusion_op(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side)
1044 {
1045 const auto wall0 = seg->shared_segment::sides[side].wall_num;
1046 if (wall0 == wall_none)
1047 return {nullptr, nullptr};
1048 const shared_segment &csegp = *seg.absolute_sibling(seg->shared_segment::children[side]);
1049 const auto &&cside = find_connect_side(seg, csegp);
1050 if (cside == side_none)
1051 {
1052 assert(cside != side_none);
1053 return {nullptr, nullptr};
1054 }
1055 const auto wall1 = csegp.sides[cside].wall_num;
1056 if (wall1 == wall_none)
1057 return {nullptr, nullptr};
1058 return {vmwallptr(wall0), vmwallptr(wall1)};
1059 }
1060
1061 template <typename F>
wall_illusion_op(fvmwallptr & vmwallptr,const vcsegptridx_t seg,const unsigned side,const F op)1062 static void wall_illusion_op(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side, const F op)
1063 {
1064 const auto &&r = wall_illusion_op(vmwallptr, seg, side);
1065 if (r.first)
1066 {
1067 op(*r.first);
1068 op(*r.second);
1069 }
1070 }
1071
1072 }
1073
1074 //-----------------------------------------------------------------
1075 // Turns off an illusionary wall (This will be used primarily for
1076 // wall switches or triggers that can turn on/off illusionary walls.)
wall_illusion_off(fvmwallptr & vmwallptr,const vcsegptridx_t seg,const unsigned side)1077 void wall_illusion_off(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side)
1078 {
1079 const auto &&op = [](wall &w) {
1080 w.flags |= wall_flag::illusion_off;
1081 };
1082 wall_illusion_op(vmwallptr, seg, side, op);
1083 }
1084
1085 //-----------------------------------------------------------------
1086 // Turns on an illusionary wall (This will be used primarily for
1087 // wall switches or triggers that can turn on/off illusionary walls.)
wall_illusion_on(fvmwallptr & vmwallptr,const vcsegptridx_t seg,const unsigned side)1088 void wall_illusion_on(fvmwallptr &vmwallptr, const vcsegptridx_t seg, const unsigned side)
1089 {
1090 const auto &&op = [](wall &w) {
1091 w.flags &= ~wall_flag::illusion_off;
1092 };
1093 wall_illusion_op(vmwallptr, seg, side, op);
1094 }
1095
1096 }
1097
1098 namespace {
1099
1100 // -----------------------------------------------------------------------------
1101 // Allowed to open the normally locked special boss door if in multiplayer mode.
special_boss_opening_allowed(segnum_t segnum,int sidenum)1102 static int special_boss_opening_allowed(segnum_t segnum, int sidenum)
1103 {
1104 if (Game_mode & GM_MULTI)
1105 return (Current_level_num == BOSS_LOCKED_DOOR_LEVEL) && (segnum == BOSS_LOCKED_DOOR_SEG) && (sidenum == BOSS_LOCKED_DOOR_SIDE);
1106 else
1107 return 0;
1108 }
1109
1110 }
1111
1112 //-----------------------------------------------------------------
1113 // Determines what happens when a wall is shot
1114 //returns info about wall. see wall.h for codes
1115 //obj is the object that hit...either a weapon or the player himself
1116 //playernum is the number the player who hit the wall or fired the weapon,
1117 //or -1 if a robot fired the weapon
1118 namespace dsx {
wall_hit_process(const player_flags powerup_flags,const vmsegptridx_t seg,const unsigned side,const fix damage,const unsigned playernum,const object & obj)1119 wall_hit_process_t wall_hit_process(const player_flags powerup_flags, const vmsegptridx_t seg, const unsigned side, const fix damage, const unsigned playernum, const object &obj)
1120 {
1121 fix show_message;
1122
1123 // If it is not a "wall" then just return.
1124 const auto wall_num = seg->shared_segment::sides[side].wall_num;
1125 if (wall_num == wall_none)
1126 return wall_hit_process_t::WHP_NOT_SPECIAL;
1127
1128 auto &Walls = LevelUniqueWallSubsystemState.Walls;
1129 auto &vmwallptr = Walls.vmptr;
1130 wall *const w = vmwallptr(wall_num);
1131
1132 if ( Newdemo_state == ND_STATE_RECORDING )
1133 newdemo_record_wall_hit_process( seg, side, damage, playernum );
1134
1135 if (w->type == WALL_BLASTABLE) {
1136 #if defined(DXX_BUILD_DESCENT_II)
1137 if (obj.ctype.laser_info.parent_type == OBJ_PLAYER)
1138 #endif
1139 wall_damage(seg, side, damage);
1140 return wall_hit_process_t::WHP_BLASTABLE;
1141 }
1142
1143 if (playernum != Player_num) //return if was robot fire
1144 return wall_hit_process_t::WHP_NOT_SPECIAL;
1145
1146 // Determine whether player is moving forward. If not, don't say negative
1147 // messages because he probably didn't intentionally hit the door.
1148 if (obj.type == OBJ_PLAYER)
1149 show_message = (vm_vec_dot(obj.orient.fvec, obj.mtype.phys_info.velocity) > 0);
1150 #if defined(DXX_BUILD_DESCENT_II)
1151 else if (obj.type == OBJ_ROBOT)
1152 show_message = 0;
1153 else if (obj.type == OBJ_WEAPON && obj.ctype.laser_info.parent_type == OBJ_ROBOT)
1154 show_message = 0;
1155 #endif
1156 else
1157 show_message = 1;
1158
1159 /* Set key_color only after the type matches, since TXT_* are macros
1160 * that trigger a load from memory. Use operator,() to suppress the
1161 * truth test on the second branch since the compiler cannot prove
1162 * that the loaded value will always be non-null.
1163 */
1164 const char *key_color;
1165 if (
1166 (w->keys == wall_key::blue && (key_color = TXT_BLUE, true)) ||
1167 (w->keys == wall_key::gold && (key_color = TXT_YELLOW, true)) ||
1168 (w->keys == wall_key::red && (key_color = TXT_RED, true))
1169 )
1170 {
1171 if (!(powerup_flags & static_cast<PLAYER_FLAG>(w->keys)))
1172 {
1173 static_assert(static_cast<unsigned>(wall_key::blue) == static_cast<unsigned>(PLAYER_FLAGS_BLUE_KEY), "BLUE key flag mismatch");
1174 static_assert(static_cast<unsigned>(wall_key::gold) == static_cast<unsigned>(PLAYER_FLAGS_GOLD_KEY), "GOLD key flag mismatch");
1175 static_assert(static_cast<unsigned>(wall_key::red) == static_cast<unsigned>(PLAYER_FLAGS_RED_KEY), "RED key flag mismatch");
1176 if (show_message)
1177 HUD_init_message(HM_DEFAULT, "%s %s",key_color,TXT_ACCESS_DENIED);
1178 return wall_hit_process_t::WHP_NO_KEY;
1179 }
1180 }
1181
1182 if (w->type == WALL_DOOR)
1183 {
1184 if ((w->flags & wall_flag::door_locked) && !special_boss_opening_allowed(seg, side))
1185 {
1186 if (show_message)
1187 HUD_init_message_literal(HM_DEFAULT, TXT_CANT_OPEN_DOOR);
1188 return wall_hit_process_t::WHP_NO_KEY;
1189 }
1190 else {
1191 if (w->state != wall_state::opening)
1192 {
1193 wall_open_door(seg, side);
1194 if (Game_mode & GM_MULTI)
1195 {
1196 #if defined(DXX_BUILD_DESCENT_I)
1197 const wall_flags flags{};
1198 #elif defined(DXX_BUILD_DESCENT_II)
1199 const auto flags = w->flags;
1200 #endif
1201 multi_send_door_open(seg, side,flags);
1202 }
1203 }
1204 return wall_hit_process_t::WHP_DOOR;
1205
1206 }
1207 }
1208 return wall_hit_process_t::WHP_NOT_SPECIAL; //default is treat like normal wall
1209 }
1210
1211 //-----------------------------------------------------------------
1212 // Opens doors/destroys wall/shuts off triggers.
wall_toggle(fvmwallptr & vmwallptr,const vmsegptridx_t segp,const unsigned side)1213 void wall_toggle(fvmwallptr &vmwallptr, const vmsegptridx_t segp, const unsigned side)
1214 {
1215 if (side >= MAX_SIDES_PER_SEGMENT)
1216 {
1217 #ifndef NDEBUG
1218 Warning("Can't toggle side %u of segment %d (%u)!\n", side, static_cast<segnum_t>(segp), Highest_segment_index);
1219 #endif
1220 return;
1221 }
1222 const auto wall_num = segp->shared_segment::sides[side].wall_num;
1223 if (wall_num == wall_none)
1224 {
1225 LevelError("Ignoring attempt to toggle wall in segment %hu, side %u: no wall exists there.", segp.get_unchecked_index(), side);
1226 return;
1227 }
1228
1229 if ( Newdemo_state == ND_STATE_RECORDING )
1230 newdemo_record_wall_toggle(segp, side);
1231
1232 wall *const w = vmwallptr(wall_num);
1233 if (w->type == WALL_BLASTABLE)
1234 wall_destroy(segp, side);
1235
1236 if (w->type == WALL_DOOR && w->state == wall_state::closed)
1237 wall_open_door(segp, side);
1238 }
1239
operator ()(active_door & d) const1240 bool ad_removal_predicate::operator()(active_door &d) const
1241 {
1242 #if defined(DXX_BUILD_DESCENT_II)
1243 auto &Objects = LevelUniqueObjectState.Objects;
1244 auto &vcobjptridx = Objects.vcptridx;
1245 #endif
1246 auto &Walls = LevelUniqueWallSubsystemState.Walls;
1247 wall &w = *Walls.vmptr(d.front_wallnum[0]);
1248 if (w.state == wall_state::opening)
1249 return do_door_open(d);
1250 else if (w.state == wall_state::closing)
1251 return do_door_close(d);
1252 else if (w.state == wall_state::waiting)
1253 {
1254 d.time += FrameTime;
1255 // set flags to fix occasional netgame problem where door is waiting to close but open flag isn't set
1256 w.flags |= wall_flag::door_opened;
1257 if (wall *const w1 = Walls.imptr(d.back_wallnum[0]))
1258 w1->flags |= wall_flag::door_opened;
1259 if (d.time > DOOR_WAIT_TIME)
1260 #if defined(DXX_BUILD_DESCENT_II)
1261 if (!is_door_obstructed(vcobjptridx, vcsegptr, vcsegptridx(w.segnum), w.sidenum))
1262 #endif
1263 {
1264 w.state = wall_state::closing;
1265 d.time = 0;
1266 }
1267 }
1268 return false;
1269 }
1270
1271 #if defined(DXX_BUILD_DESCENT_II)
1272 namespace {
1273
copy_cloaking_wall_light_to_wall(std::array<uvl,4> & back_uvls,std::array<uvl,4> & front_uvls,const cloaking_wall & d)1274 static void copy_cloaking_wall_light_to_wall(std::array<uvl, 4> &back_uvls, std::array<uvl, 4> &front_uvls, const cloaking_wall &d)
1275 {
1276 range_for (const uint_fast32_t i, xrange(4u))
1277 {
1278 back_uvls[i].l = d.back_ls[i];
1279 front_uvls[i].l = d.front_ls[i];
1280 }
1281 }
1282
scale_cloaking_wall_light_to_wall(std::array<uvl,4> & back_uvls,std::array<uvl,4> & front_uvls,const cloaking_wall & d,const fix light_scale)1283 static void scale_cloaking_wall_light_to_wall(std::array<uvl, 4> &back_uvls, std::array<uvl, 4> &front_uvls, const cloaking_wall &d, const fix light_scale)
1284 {
1285 range_for (const uint_fast32_t i, xrange(4u))
1286 {
1287 back_uvls[i].l = fixmul(d.back_ls[i], light_scale);
1288 front_uvls[i].l = fixmul(d.front_ls[i], light_scale);
1289 }
1290 }
1291
do_cloaking_wall_frame(const bool initial,cloaking_wall & d,const cwframe front,const cwframe back)1292 static cwresult do_cloaking_wall_frame(const bool initial, cloaking_wall &d, const cwframe front, const cwframe back)
1293 {
1294 cwresult r(initial);
1295 if (d.time > CLOAKING_WALL_TIME) {
1296 front.w.type = back.w.type = WALL_OPEN;
1297 front.w.state = back.w.state = wall_state::closed; //why closed? why not?
1298 r.remove = true;
1299 }
1300 else if (d.time > CLOAKING_WALL_TIME/2) {
1301 const int8_t cloak_value = ((d.time - CLOAKING_WALL_TIME / 2) * (GR_FADE_LEVELS - 2)) / (CLOAKING_WALL_TIME / 2);
1302 if (front.w.cloak_value != cloak_value)
1303 {
1304 r.record = true;
1305 front.w.cloak_value = back.w.cloak_value = cloak_value;
1306 }
1307
1308 if (front.w.type != WALL_CLOAKED)
1309 { //just switched
1310 front.w.type = back.w.type = WALL_CLOAKED;
1311 copy_cloaking_wall_light_to_wall(back.uvls, front.uvls, d);
1312 }
1313 }
1314 else { //fading out
1315 fix light_scale;
1316 light_scale = fixdiv(CLOAKING_WALL_TIME / 2 - d.time, CLOAKING_WALL_TIME / 2);
1317 scale_cloaking_wall_light_to_wall(back.uvls, front.uvls, d, light_scale);
1318 }
1319 return r;
1320 }
1321
do_decloaking_wall_frame(const bool initial,cloaking_wall & d,const cwframe front,const cwframe back)1322 static cwresult do_decloaking_wall_frame(const bool initial, cloaking_wall &d, const cwframe front, const cwframe back)
1323 {
1324 cwresult r(initial);
1325 if (d.time > CLOAKING_WALL_TIME) {
1326
1327 back.w.state = wall_state::closed;
1328 front.w.state = wall_state::closed;
1329 copy_cloaking_wall_light_to_wall(back.uvls, front.uvls, d);
1330 r.remove = true;
1331 }
1332 else if (d.time > CLOAKING_WALL_TIME/2) { //fading in
1333 fix light_scale;
1334 front.w.type = back.w.type = WALL_CLOSED;
1335
1336 light_scale = fixdiv(d.time - CLOAKING_WALL_TIME / 2, CLOAKING_WALL_TIME / 2);
1337 scale_cloaking_wall_light_to_wall(back.uvls, front.uvls, d, light_scale);
1338 }
1339 else { //cloaking in
1340 const int8_t cloak_value = ((CLOAKING_WALL_TIME / 2 - d.time) * (GR_FADE_LEVELS - 2)) / (CLOAKING_WALL_TIME / 2);
1341 if (front.w.cloak_value != cloak_value)
1342 {
1343 front.w.cloak_value = back.w.cloak_value = cloak_value;
1344 r.record = true;
1345 }
1346 front.w.type = WALL_CLOAKED;
1347 back.w.type = WALL_CLOAKED;
1348 }
1349 return r;
1350 }
1351
operator ()(cloaking_wall & d)1352 bool cw_removal_predicate::operator()(cloaking_wall &d)
1353 {
1354 const cwframe front(vmsegptr, *Walls.vmptr(d.front_wallnum));
1355 const auto &&wpback = Walls.imptr(d.back_wallnum);
1356 const cwframe back = (wpback ? cwframe(vmsegptr, *wpback) : front);
1357 const bool initial = (d.time == 0);
1358 d.time += FrameTime;
1359
1360 cwresult r;
1361 if (front.w.state == wall_state::cloaking)
1362 r = do_cloaking_wall_frame(initial, d, front, back);
1363 else if (front.w.state == wall_state::decloaking)
1364 r = do_decloaking_wall_frame(initial, d, front, back);
1365 else
1366 {
1367 d_debugbreak(); //unexpected wall state
1368 return false;
1369 }
1370 if (r.record)
1371 {
1372 // check if the actual cloak_value changed in this frame to prevent redundant recordings and wasted bytes
1373 if (Newdemo_state == ND_STATE_RECORDING && r.record)
1374 newdemo_record_cloaking_wall(d.front_wallnum, d.back_wallnum, front.w.type, front.w.state, front.w.cloak_value, front.uvls[0].l, front.uvls[1].l, front.uvls[2].l, front.uvls[3].l);
1375 }
1376 if (!r.remove)
1377 ++ num_cloaking_walls;
1378 return r.remove;
1379 }
1380
1381 }
1382 #endif
1383
1384 namespace {
1385
process_exploding_walls()1386 static void process_exploding_walls()
1387 {
1388 if (Newdemo_state == ND_STATE_PLAYBACK)
1389 return;
1390 if (unsigned num_exploding_walls = Num_exploding_walls)
1391 {
1392 auto &Walls = LevelUniqueWallSubsystemState.Walls;
1393 range_for (auto &&wp, Walls.vmptr)
1394 {
1395 auto &w1 = *wp;
1396 if (w1.flags & wall_flag::exploding)
1397 {
1398 assert(num_exploding_walls);
1399 const auto n = do_exploding_wall_frame(w1);
1400 num_exploding_walls -= n;
1401 if (!num_exploding_walls)
1402 break;
1403 }
1404 }
1405 }
1406 }
1407
1408 }
1409
wall_frame_process()1410 void wall_frame_process()
1411 {
1412 process_exploding_walls();
1413 {
1414 auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
1415 const auto &&r = partial_range(ActiveDoors, ActiveDoors.get_count());
1416 auto &&i = std::remove_if(r.begin(), r.end(), ad_removal_predicate());
1417 ActiveDoors.set_count(std::distance(r.begin(), i));
1418 }
1419 #if defined(DXX_BUILD_DESCENT_II)
1420 if (Newdemo_state != ND_STATE_PLAYBACK)
1421 {
1422 auto &CloakingWalls = LevelUniqueWallSubsystemState.CloakingWalls;
1423 const auto &&r = partial_range(CloakingWalls, CloakingWalls.get_count());
1424 auto &Walls = LevelUniqueWallSubsystemState.Walls;
1425 cw_removal_predicate rp{Segments.vmptr, Walls};
1426 std::remove_if(r.begin(), r.end(), std::ref(rp));
1427 CloakingWalls.set_count(rp.num_cloaking_walls);
1428 }
1429 #endif
1430 }
1431
1432 d_level_unique_stuck_object_state LevelUniqueStuckObjectState;
1433
1434 // An object got stuck in a door (like a flare).
1435 // Add global entry.
add_stuck_object(fvcwallptr & vcwallptr,const vmobjptridx_t objp,const shared_segment & segp,const unsigned sidenum)1436 void d_level_unique_stuck_object_state::add_stuck_object(fvcwallptr &vcwallptr, const vmobjptridx_t objp, const shared_segment &segp, const unsigned sidenum)
1437 {
1438 const auto wallnum = segp.sides[sidenum].wall_num;
1439 if (wallnum != wall_none)
1440 {
1441 if (vcwallptr(wallnum)->flags & wall_flag::blasted)
1442 {
1443 objp->flags |= OF_SHOULD_BE_DEAD;
1444 return;
1445 }
1446 if (Num_stuck_objects >= Stuck_objects.size())
1447 {
1448 assert(Num_stuck_objects <= Stuck_objects.size());
1449 con_printf(CON_NORMAL, "%s:%u: all stuck objects are busy; terminating %hu early", __FILE__, __LINE__, objp.get_unchecked_index());
1450 objp->flags |= OF_SHOULD_BE_DEAD;
1451 return;
1452 }
1453 auto &so = Stuck_objects[Num_stuck_objects++];
1454 so.wallnum = wallnum;
1455 so.objnum = objp;
1456 }
1457 }
1458
remove_stuck_object(const vcobjidx_t obj)1459 void d_level_unique_stuck_object_state::remove_stuck_object(const vcobjidx_t obj)
1460 {
1461 auto &&pr = partial_range(Stuck_objects, Num_stuck_objects);
1462 const auto predicate = [obj](const stuckobj &so) { return so.objnum == obj; };
1463 const auto i = std::find_if(pr.begin(), pr.end(), predicate);
1464 if (i == pr.end())
1465 /* Objects enter this function if they are able to become stuck,
1466 * without regard to whether they actually are stuck. If the
1467 * object terminated without being stuck in a wall, then it will
1468 * not be found in Stuck_objects.
1469 */
1470 return;
1471 /* If pr.begin() == pr.end(), then i == pr.end(), and this line
1472 * cannot be reached.
1473 *
1474 * If pr.begin() != pr.end(), then prev(pr.end()) must point to a
1475 * valid element.
1476 *
1477 * Move that valid element to the location vacated by the removed
1478 * object. This may be a self-move if the removed object is the
1479 * last object.
1480 */
1481 auto &last_element = *std::prev(pr.end());
1482 static_assert(std::is_trivially_move_assignable<stuckobj>::value, "stuckobj move may require a check to prevent self-move");
1483 *i = std::move(last_element);
1484 DXX_POISON_VAR(last_element.wallnum, 0xcc);
1485 DXX_POISON_VAR(last_element.objnum, 0xcc);
1486 -- Num_stuck_objects;
1487 }
1488
1489 // ----------------------------------------------------------------------------------------------------
1490 // Door with wall index wallnum is opening, kill all objects stuck in it.
kill_stuck_objects(fvmobjptr & vmobjptr,const vcwallidx_t wallnum)1491 void d_level_unique_stuck_object_state::kill_stuck_objects(fvmobjptr &vmobjptr, const vcwallidx_t wallnum)
1492 {
1493 if (!Num_stuck_objects)
1494 return;
1495 auto &&pr = partial_range(Stuck_objects, Num_stuck_objects);
1496 const auto predicate = [&vmobjptr, wallnum](const stuckobj &so)
1497 {
1498 if (so.wallnum != wallnum)
1499 return false;
1500 auto &obj = *vmobjptr(so.objnum);
1501 #if defined(DXX_BUILD_DESCENT_I)
1502 #define DXX_WEAPON_LIFELEFT F1_0/4
1503 #elif defined(DXX_BUILD_DESCENT_II)
1504 #define DXX_WEAPON_LIFELEFT F1_0/8
1505 #endif
1506 assert(obj.type == OBJ_WEAPON);
1507 assert(obj.movement_source == object::movement_type::physics);
1508 assert(obj.mtype.phys_info.flags & PF_STICK);
1509 obj.lifeleft = DXX_WEAPON_LIFELEFT;
1510 return true;
1511 };
1512 const auto i = std::remove_if(pr.begin(), pr.end(), predicate);
1513 static_assert(std::is_trivially_destructible<stuckobj>::value, "stuckobj destructor not called");
1514 Num_stuck_objects = std::distance(pr.begin(), i);
1515 std::array<stuckobj, 1> empty;
1516 DXX_POISON_VAR(empty, 0xcc);
1517 std::fill(i, pr.end(), empty[0]);
1518 }
1519
1520 }
1521
1522 namespace dcx {
1523 // -----------------------------------------------------------------------------------
1524 // Initialize stuck objects array. Called at start of level
init_stuck_objects()1525 void d_level_unique_stuck_object_state::init_stuck_objects()
1526 {
1527 DXX_POISON_VAR(Stuck_objects, 0xcc);
1528 Num_stuck_objects = 0;
1529 }
1530 }
1531
1532 #if defined(DXX_BUILD_DESCENT_II)
1533 // -----------------------------------------------------------------------------------
1534 #define MAX_BLAST_GLASS_DEPTH 5
1535
1536 namespace dsx {
1537 namespace {
1538
1539 class blast_nearby_glass_context
1540 {
1541 using TmapInfo_array = d_level_unique_tmap_info_state::TmapInfo_array;
1542 const object &obj;
1543 const fix damage;
1544 const d_eclip_array &Effects;
1545 const GameBitmaps_array &GameBitmaps;
1546 const Textures_array &Textures;
1547 const TmapInfo_array &TmapInfo;
1548 const d_vclip_array &Vclip;
1549 fvcvertptr &vcvertptr;
1550 fvcwallptr &vcwallptr;
1551 visited_segment_bitarray_t visited;
1552 unsigned can_blast(texture2_value tmap_num2) const;
1553 public:
blast_nearby_glass_context(const object & obj,const fix damage,const d_eclip_array & Effects,const GameBitmaps_array & GameBitmaps,const Textures_array & Textures,const TmapInfo_array & TmapInfo,const d_vclip_array & Vclip,fvcvertptr & vcvertptr,fvcwallptr & vcwallptr)1554 blast_nearby_glass_context(const object &obj, const fix damage, const d_eclip_array &Effects, const GameBitmaps_array &GameBitmaps, const Textures_array &Textures, const TmapInfo_array &TmapInfo, const d_vclip_array &Vclip, fvcvertptr &vcvertptr, fvcwallptr &vcwallptr) :
1555 obj(obj), damage(damage), Effects(Effects), GameBitmaps(GameBitmaps),
1556 Textures(Textures), TmapInfo(TmapInfo), Vclip(Vclip),
1557 vcvertptr(vcvertptr), vcwallptr(vcwallptr), visited{}
1558 {
1559 }
1560 blast_nearby_glass_context(const blast_nearby_glass_context &) = delete;
1561 blast_nearby_glass_context &operator=(const blast_nearby_glass_context &) = delete;
1562 void process_segment(vmsegptridx_t segp, unsigned steps_remaining);
1563 };
1564
can_blast(const texture2_value tmap_num2) const1565 unsigned blast_nearby_glass_context::can_blast(const texture2_value tmap_num2) const
1566 {
1567 const auto tm = get_texture_index(tmap_num2); //tm without flags
1568 auto &ti = TmapInfo[tm];
1569 const auto ec = ti.eclip_num;
1570 if (ec == eclip_none)
1571 {
1572 return ti.destroyed != -1;
1573 }
1574 else
1575 {
1576 auto &e = Effects[ec];
1577 return e.dest_bm_num != ~0u && !(e.flags & EF_ONE_SHOT);
1578 }
1579 }
1580
process_segment(const vmsegptridx_t segp,const unsigned steps_remaining)1581 void blast_nearby_glass_context::process_segment(const vmsegptridx_t segp, const unsigned steps_remaining)
1582 {
1583 visited[segp] = true;
1584
1585 const auto &objp = obj;
1586 for (const auto &&[sidenum, uside] : enumerate(segp->unique_segment::sides))
1587 {
1588 fix dist;
1589
1590 // Process only walls which have glass.
1591 if (const auto tmap_num2 = uside.tmap_num2; tmap_num2 != texture2_value::None)
1592 {
1593 if (can_blast(tmap_num2))
1594 {
1595 const auto &&pnt = compute_center_point_on_side(vcvertptr, segp, sidenum);
1596 dist = vm_vec_dist_quick(pnt, objp.pos);
1597 if (dist < damage/2) {
1598 dist = find_connected_distance(pnt, segp, objp.pos, segp.absolute_sibling(objp.segnum), MAX_BLAST_GLASS_DEPTH, WALL_IS_DOORWAY_FLAG::rendpast);
1599 if ((dist > 0) && (dist < damage/2))
1600 {
1601 assert(objp.type == OBJ_WEAPON);
1602 check_effect_blowup(LevelSharedSegmentState.DestructibleLights, Vclip, segp, sidenum, pnt, objp.ctype.laser_info, 1, 0);
1603 }
1604 }
1605 }
1606 }
1607 }
1608
1609 const unsigned next_steps_remaining = steps_remaining - 1;
1610 if (!next_steps_remaining)
1611 return;
1612 for (const auto &&[i, segnum] : enumerate(segp->children))
1613 {
1614 if (segnum != segment_none) {
1615 if (!visited[segnum]) {
1616 if (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, i) & WALL_IS_DOORWAY_FLAG::fly)
1617 {
1618 process_segment(segp.absolute_sibling(segnum), next_steps_remaining);
1619 }
1620 }
1621 }
1622 }
1623 }
1624
1625 struct d1wclip
1626 {
1627 wclip *const wc;
d1wclipdsx::__anon3178b3791011::d1wclip1628 d1wclip(wclip &w) : wc(&w) {}
1629 };
1630
1631 DEFINE_SERIAL_UDT_TO_MESSAGE(d1wclip, dwc, (dwc.wc->play_time, dwc.wc->num_frames, dwc.wc->d1_frames, dwc.wc->open_sound, dwc.wc->close_sound, dwc.wc->flags, dwc.wc->filename, serial::pad<1>()));
1632 ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1wclip, 26 + (sizeof(int16_t) * MAX_CLIP_FRAMES_D1));
1633
1634 }
1635
1636 // -----------------------------------------------------------------------------------
1637 // objp is going to detonate
1638 // blast nearby monitors, lights, maybe other things
blast_nearby_glass(const object & objp,const fix damage)1639 void blast_nearby_glass(const object &objp, const fix damage)
1640 {
1641 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1642 auto &Effects = LevelUniqueEffectsClipState.Effects;
1643 auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
1644 auto &Vertices = LevelSharedVertexState.get_vertices();
1645 auto &vcvertptr = Vertices.vcptr;
1646 auto &Walls = LevelUniqueWallSubsystemState.Walls;
1647 auto &vcwallptr = Walls.vcptr;
1648 blast_nearby_glass_context{objp, damage, Effects, GameBitmaps, Textures, TmapInfo, Vclip, vcvertptr, vcwallptr}.process_segment(vmsegptridx(objp.segnum), MAX_BLAST_GLASS_DEPTH);
1649 }
1650
1651 }
1652
1653 #endif
1654
1655 DEFINE_SERIAL_UDT_TO_MESSAGE(wclip, wc, (wc.play_time, wc.num_frames, wc.frames, wc.open_sound, wc.close_sound, wc.flags, wc.filename, serial::pad<1>()));
1656 ASSERT_SERIAL_UDT_MESSAGE_SIZE(wclip, 26 + (sizeof(int16_t) * MAX_CLIP_FRAMES));
1657
1658 namespace dsx {
1659 /*
1660 * reads a wclip structure from a PHYSFS_File
1661 */
wclip_read(PHYSFS_File * fp,wclip & wc)1662 void wclip_read(PHYSFS_File *fp, wclip &wc)
1663 {
1664 PHYSFSX_serialize_read(fp, wc);
1665 }
1666
1667 #if 0
1668 void wclip_write(PHYSFS_File *fp, const wclip &wc)
1669 {
1670 PHYSFSX_serialize_write(fp, wc);
1671 }
1672 #endif
1673 }
1674
1675 struct wrap_v16_wall
1676 {
1677 const wall *w;
wrap_v16_wallwrap_v16_wall1678 wrap_v16_wall(const wall &t) : w(&t) {}
1679 };
1680
1681 #define _SERIAL_UDT_WALL_V16_MEMBERS(P) (P type, P flags, P hps, P trigger, P clip_num, P keys)
1682
1683 DEFINE_SERIAL_UDT_TO_MESSAGE(v16_wall, w, _SERIAL_UDT_WALL_V16_MEMBERS(w.));
1684 DEFINE_SERIAL_UDT_TO_MESSAGE(wrap_v16_wall, w, _SERIAL_UDT_WALL_V16_MEMBERS(w.w->));
1685 ASSERT_SERIAL_UDT_MESSAGE_SIZE(wrap_v16_wall, 9);
1686
1687 /*
1688 * reads a v16_wall structure from a PHYSFS_File
1689 */
v16_wall_read(PHYSFS_File * fp,v16_wall & w)1690 void v16_wall_read(PHYSFS_File *fp, v16_wall &w)
1691 {
1692 PHYSFSX_serialize_read(fp, w);
1693 }
1694
1695 struct wrap_v19_wall
1696 {
1697 const wall *w;
wrap_v19_wallwrap_v19_wall1698 wrap_v19_wall(const wall &t) : w(&t) {}
1699 };
1700
1701 DEFINE_SERIAL_UDT_TO_MESSAGE(v19_wall, w, (w.segnum, serial::pad<2>(), w.sidenum, w.type, w.flags, w.hps, w.trigger, w.clip_num, w.keys, w.linked_wall, serial::pad<2>()));
1702 DEFINE_SERIAL_UDT_TO_MESSAGE(wrap_v19_wall, w, (w.w->segnum, serial::pad<2>(), w.w->sidenum, serial::pad<3>(), w.w->type, w.w->flags, w.w->hps, w.w->trigger, w.w->clip_num, w.w->keys, w.w->linked_wall, serial::pad<2>()));
1703 ASSERT_SERIAL_UDT_MESSAGE_SIZE(v19_wall, 21);
1704 ASSERT_SERIAL_UDT_MESSAGE_SIZE(wrap_v19_wall, 21);
1705
1706 /*
1707 * reads a v19_wall structure from a PHYSFS_File
1708 */
v19_wall_read(PHYSFS_File * fp,v19_wall & w)1709 void v19_wall_read(PHYSFS_File *fp, v19_wall &w)
1710 {
1711 PHYSFSX_serialize_read(fp, w);
1712 }
1713
1714 #if defined(DXX_BUILD_DESCENT_I)
1715 #define _SERIAL_UDT_WALL_D2X_MEMBERS serial::pad<2>()
1716 #elif defined(DXX_BUILD_DESCENT_II)
1717 #define _SERIAL_UDT_WALL_D2X_MEMBERS w.controlling_trigger, w.cloak_value
1718 #endif
1719 DEFINE_SERIAL_UDT_TO_MESSAGE(wall, w, (serial::sign_extend<int>(w.segnum), w.sidenum, serial::pad<3, 0>(), w.hps, w.linked_wall, serial::pad<2, 0>(), w.type, w.flags, w.state, w.trigger, w.clip_num, w.keys, _SERIAL_UDT_WALL_D2X_MEMBERS));
1720 ASSERT_SERIAL_UDT_MESSAGE_SIZE(wall, 24);
1721
1722 namespace dsx {
1723 /*
1724 * reads a wall structure from a PHYSFS_File
1725 */
wall_read(PHYSFS_File * fp,wall & w)1726 void wall_read(PHYSFS_File *fp, wall &w)
1727 {
1728 PHYSFSX_serialize_read(fp, w);
1729 w.flags &= ~wall_flag::exploding;
1730 }
1731
1732 }
1733
1734 DEFINE_SERIAL_UDT_TO_MESSAGE(active_door, d, (d.n_parts, d.front_wallnum, d.back_wallnum, d.time));
1735 ASSERT_SERIAL_UDT_MESSAGE_SIZE(active_door, 16);
1736
1737 /*
1738 * reads an active_door structure from a PHYSFS_File
1739 */
active_door_read(PHYSFS_File * fp,active_door & ad)1740 void active_door_read(PHYSFS_File *fp, active_door &ad)
1741 {
1742 PHYSFSX_serialize_read(fp, ad);
1743 }
1744
active_door_write(PHYSFS_File * fp,const active_door & ad)1745 void active_door_write(PHYSFS_File *fp, const active_door &ad)
1746 {
1747 PHYSFSX_serialize_write(fp, ad);
1748 }
1749
1750 namespace dsx {
1751
wall_write(PHYSFS_File * fp,const wall & w,short version)1752 void wall_write(PHYSFS_File *fp, const wall &w, short version)
1753 {
1754 if (version <= 16)
1755 PHYSFSX_serialize_write<wrap_v16_wall>(fp, w);
1756 else if (version <= 19)
1757 PHYSFSX_serialize_write<wrap_v19_wall>(fp, w);
1758 else
1759 PHYSFSX_serialize_write(fp, w);
1760 }
1761
1762 }
1763
1764 #if defined(DXX_BUILD_DESCENT_II)
1765 DEFINE_SERIAL_UDT_TO_MESSAGE(dsx::cloaking_wall, cw, (cw.front_wallnum, cw.back_wallnum, cw.front_ls, cw.back_ls, cw.time));
1766 ASSERT_SERIAL_UDT_MESSAGE_SIZE(dsx::cloaking_wall, 40);
1767
1768 namespace dsx {
cloaking_wall_read(cloaking_wall & cw,PHYSFS_File * fp)1769 void cloaking_wall_read(cloaking_wall &cw, PHYSFS_File *fp)
1770 {
1771 PHYSFSX_serialize_read(fp, cw);
1772 }
1773
cloaking_wall_write(const cloaking_wall & cw,PHYSFS_File * fp)1774 void cloaking_wall_write(const cloaking_wall &cw, PHYSFS_File *fp)
1775 {
1776 PHYSFSX_serialize_write(fp, cw);
1777 }
1778 }
1779 #endif
1780