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