1 /*
2  * Portions of this file are copyright Rebirth contributors and licensed as
3  * described in COPYING.txt.
4  * Portions of this file are copyright Parallax Software and licensed
5  * according to the Parallax license below.
6  * See COPYING.txt for license details.
7 
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18 */
19 
20 /*
21  *
22  * New Triggers and Switches.
23  *
24  */
25 
26 #include <stdexcept>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <math.h>
30 #include <string.h>
31 
32 #include "newmenu.h"
33 #include "game.h"
34 #include "switch.h"
35 #include "inferno.h"
36 #include "segment.h"
37 #include "dxxerror.h"
38 #include "gameseg.h"
39 #include "wall.h"
40 #include "object.h"
41 #include "fuelcen.h"
42 #include "newdemo.h"
43 #include "player.h"
44 #include "endlevel.h"
45 #include "gameseq.h"
46 #include "net_udp.h"
47 #include "palette.h"
48 #include "hudmsg.h"
49 #include "robot.h"
50 #include "bm.h"
51 
52 #include "physfs-serial.h"
53 #include "d_levelstate.h"
54 #include "d_underlying_value.h"
55 #include "compiler-range_for.h"
56 #include "partial_range.h"
57 
58 #if DXX_USE_EDITOR
59 //-----------------------------------------------------------------
60 // Initializes all the switches.
trigger_init()61 void trigger_init()
62 {
63 	auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
64 	Triggers.set_count(0);
65 }
66 #endif
67 
68 template <typename SF, typename O, typename... Oa>
trigger_wall_op(const trigger & t,SF & segment_factory,const O & op,Oa &&...oargs)69 static inline void trigger_wall_op(const trigger &t, SF &segment_factory, const O &op, Oa &&... oargs)
70 {
71 	for (unsigned i = 0, num_links = t.num_links; i != num_links; ++i)
72 		op(std::forward<Oa>(oargs)..., segment_factory(t.seg[i]), t.side[i]);
73 }
74 
75 //-----------------------------------------------------------------
76 // Executes a link, attached to a trigger.
77 // Toggles all walls linked to the switch.
78 // Opens doors, Blasts blast walls, turns off illusions.
do_link(const trigger & t)79 static void do_link(const trigger &t)
80 {
81 	auto &Walls = LevelUniqueWallSubsystemState.Walls;
82 	auto &vmwallptr = Walls.vmptr;
83 	trigger_wall_op(t, vmsegptridx, wall_toggle, vmwallptr);
84 }
85 
86 #if defined(DXX_BUILD_DESCENT_II)
87 namespace dsx {
88 //close a door
do_close_door(const trigger & t)89 static void do_close_door(const trigger &t)
90 {
91 	auto &Walls = LevelUniqueWallSubsystemState.Walls;
92 	trigger_wall_op(t, vmsegptridx, wall_close_door, Walls);
93 }
94 
95 //turns lighting on.  returns true if lights were actually turned on. (they
96 //would not be if they had previously been shot out).
do_light_on(const d_level_shared_destructible_light_state & LevelSharedDestructibleLightState,const d_level_unique_tmap_info_state::TmapInfo_array & TmapInfo,d_flickering_light_state & Flickering_light_state,const trigger & t)97 static int do_light_on(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const d_level_unique_tmap_info_state::TmapInfo_array &TmapInfo, d_flickering_light_state &Flickering_light_state, const trigger &t)
98 {
99 	int ret=0;
100 	const auto op = [&LevelSharedDestructibleLightState, &Flickering_light_state, &TmapInfo, &ret](const vmsegptridx_t segnum, const unsigned sidenum) {
101 			//check if tmap2 casts light before turning the light on.  This
102 			//is to keep us from turning on blown-out lights
103 		const auto tm2 = get_texture_index(segnum->unique_segment::sides[sidenum].tmap_num2);
104 		if (TmapInfo[tm2].lighting) {
105 				ret |= add_light(LevelSharedDestructibleLightState, segnum, sidenum);		//any light sets flag
106 				enable_flicker(Flickering_light_state, segnum, sidenum);
107 			}
108 	};
109 	trigger_wall_op(t, vmsegptridx, op);
110 	return ret;
111 }
112 
113 //turns lighting off.  returns true if lights were actually turned off. (they
114 //would not be if they had previously been shot out).
do_light_off(const d_level_shared_destructible_light_state & LevelSharedDestructibleLightState,const d_level_unique_tmap_info_state::TmapInfo_array & TmapInfo,d_flickering_light_state & Flickering_light_state,const trigger & t)115 static int do_light_off(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, const d_level_unique_tmap_info_state::TmapInfo_array &TmapInfo, d_flickering_light_state &Flickering_light_state, const trigger &t)
116 {
117 	int ret=0;
118 	const auto op = [&LevelSharedDestructibleLightState, &Flickering_light_state, &TmapInfo, &ret](const vmsegptridx_t segnum, const unsigned sidenum) {
119 			//check if tmap2 casts light before turning the light off.  This
120 			//is to keep us from turning off blown-out lights
121 		const auto tm2 = get_texture_index(segnum->unique_segment::sides[sidenum].tmap_num2);
122 		if (TmapInfo[tm2].lighting) {
123 				ret |= subtract_light(LevelSharedDestructibleLightState, segnum, sidenum);	//any light sets flag
124 				disable_flicker(Flickering_light_state, segnum, sidenum);
125 			}
126 	};
127 	trigger_wall_op(t, vmsegptridx, op);
128 	return ret;
129 }
130 
131 // Unlocks all doors linked to the switch.
do_unlock_doors(fvcsegptr & vcsegptr,fvmwallptr & vmwallptr,const trigger & t)132 static void do_unlock_doors(fvcsegptr &vcsegptr, fvmwallptr &vmwallptr, const trigger &t)
133 {
134 	const auto op = [&vmwallptr](const shared_segment &segp, const unsigned sidenum) {
135 		const auto wall_num = segp.sides[sidenum].wall_num;
136 		if (wall_num == wall_none)
137 			return;
138 		auto &w = *vmwallptr(wall_num);
139 		w.flags &= ~wall_flag::door_locked;
140 		w.keys = wall_key::none;
141 	};
142 	trigger_wall_op(t, vcsegptr, op);
143 }
144 
145 // Locks all doors linked to the switch.
do_lock_doors(fvcsegptr & vcsegptr,fvmwallptr & vmwallptr,const trigger & t)146 static void do_lock_doors(fvcsegptr &vcsegptr, fvmwallptr &vmwallptr, const trigger &t)
147 {
148 	const auto op = [&vmwallptr](const shared_segment &segp, const unsigned sidenum) {
149 		const auto wall_num = segp.sides[sidenum].wall_num;
150 		if (wall_num == wall_none)
151 			return;
152 		auto &w = *vmwallptr(wall_num);
153 		w.flags |= wall_flag::door_locked;
154 	};
155 	trigger_wall_op(t, vcsegptr, op);
156 }
157 
158 // Changes walls pointed to by a trigger. returns true if any walls changed
do_change_walls(const trigger & t,const uint8_t new_wall_type)159 static int do_change_walls(const trigger &t, const uint8_t new_wall_type)
160 {
161 	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
162 	auto &Objects = LevelUniqueObjectState.Objects;
163 	auto &Vertices = LevelSharedVertexState.get_vertices();
164 	auto &vmobjptr = Objects.vmptr;
165 	int ret=0;
166 
167 	auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
168 	auto &Walls = LevelUniqueWallSubsystemState.Walls;
169 	auto &vmwallptr = Walls.vmptr;
170 	for (unsigned i = 0; i < t.num_links; ++i)
171 	{
172 		uint8_t cside;
173 		const auto &&segp = vmsegptridx(t.seg[i]);
174 		const auto side = t.side[i];
175 			imsegptridx_t csegp = segment_none;
176 
177 			if (!IS_CHILD(segp->children[side]))
178 			{
179 				cside = side_none;
180 			}
181 			else
182 			{
183 				csegp = imsegptridx(segp->children[side]);
184 				cside = find_connect_side(segp, csegp);
185 				Assert(cside != side_none);
186 			}
187 
188 			wall *w0p;
189 			const auto w0num = segp->shared_segment::sides[side].wall_num;
190 			if (const auto &&uw0p = vmwallptr.check_untrusted(w0num))
191 				w0p = *uw0p;
192 			else
193 			{
194 				LevelError("trigger %p link %u tried to open segment %hu, side %u which is an invalid wall; ignoring.", std::addressof(t), i, static_cast<segnum_t>(segp), side);
195 				continue;
196 			}
197 			auto &wall0 = *w0p;
198 			imwallptr_t wall1 = nullptr;
199 			if ((cside == side_none || csegp->shared_segment::sides[cside].wall_num == wall_none ||
200 				(wall1 = vmwallptr(csegp->shared_segment::sides[cside].wall_num))->type == new_wall_type) &&
201 				wall0.type == new_wall_type)
202 				continue;		//already in correct state, so skip
203 
204 			ret |= 1;
205 
206 			auto &vcvertptr = Vertices.vcptr;
207 			switch (t.type)
208 			{
209 				case trigger_action::open_wall:
210 					if ((TmapInfo[get_texture_index(segp->unique_segment::sides[side].tmap_num)].flags & tmapinfo_flag::force_field))
211 					{
212 						ret |= 2;
213 						const auto &&pos = compute_center_point_on_side(vcvertptr, segp, side);
214 						digi_link_sound_to_pos( SOUND_FORCEFIELD_OFF, segp, side, pos, 0, F1_0 );
215 						digi_kill_sound_linked_to_segment(segp,side,SOUND_FORCEFIELD_HUM);
216 						wall0.type = new_wall_type;
217 						if (wall1)
218 						{
219 							wall1->type = new_wall_type;
220 							digi_kill_sound_linked_to_segment(csegp, cside, SOUND_FORCEFIELD_HUM);
221 						}
222 					}
223 					else
224 						start_wall_cloak(segp,side);
225 					break;
226 
227 				case trigger_action::close_wall:
228 					if ((TmapInfo[get_texture_index(segp->unique_segment::sides[side].tmap_num)].flags & tmapinfo_flag::force_field))
229 					{
230 						ret |= 2;
231 						{
232 						const auto &&pos = compute_center_point_on_side(vcvertptr, segp, side);
233 						digi_link_sound_to_pos(SOUND_FORCEFIELD_HUM,segp,side,pos,1, F1_0/2);
234 						}
235 					case trigger_action::illusory_wall:
236 						wall0.type = new_wall_type;
237 						if (wall1)
238 							wall1->type = new_wall_type;
239 					}
240 					else
241 						start_wall_decloak(segp,side);
242 					break;
243 				default:
244 					return 0;
245 			}
246 
247 			LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, segp->shared_segment::sides[side].wall_num);
248 			if (wall1)
249 				LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, csegp->shared_segment::sides[cside].wall_num);
250   	}
251 	flush_fcd_cache();
252 
253 	return ret;
254 }
255 
256 #define print_trigger_message(pnum,trig,shot,message)	\
257 	((void)((print_trigger_message(pnum,trig,shot)) &&		\
258 		HUD_init_message(HM_DEFAULT, message, &"s"[trig.num_links <= 1])))
259 
260 static int (print_trigger_message)(int pnum, const trigger &t, int shot)
261  {
262 	if (shot && pnum == Player_num && !(t.flags & trigger_behavior_flags::no_message))
263 		return 1;
264 	return 0;
265  }
266 }
267 #endif
268 
do_matcen(const trigger & t)269 static void do_matcen(const trigger &t)
270 {
271 	range_for (auto &i, partial_const_range(t.seg, t.num_links))
272 		trigger_matcen(vmsegptridx(i));
273 }
274 
do_il_on(fvcsegptridx & vcsegptridx,fvmwallptr & vmwallptr,const trigger & t)275 static void do_il_on(fvcsegptridx &vcsegptridx, fvmwallptr &vmwallptr, const trigger &t)
276 {
277 	trigger_wall_op(t, vcsegptridx, wall_illusion_on, vmwallptr);
278 }
279 
280 namespace dsx {
281 
282 #if defined(DXX_BUILD_DESCENT_I)
do_il_off(fvcsegptridx & vcsegptridx,fvmwallptr & vmwallptr,const trigger & t)283 static void do_il_off(fvcsegptridx &vcsegptridx, fvmwallptr &vmwallptr, const trigger &t)
284 {
285 	trigger_wall_op(t, vcsegptridx, wall_illusion_off, vmwallptr);
286 }
287 #elif defined(DXX_BUILD_DESCENT_II)
288 static void do_il_off(fvcsegptridx &vcsegptridx, fvcvertptr &vcvertptr, fvmwallptr &vmwallptr, const trigger &t)
289 {
290 	const auto &&op = [&vcvertptr, &vmwallptr](const vcsegptridx_t seg, const unsigned side) {
291 		wall_illusion_off(vmwallptr, seg, side);
292 		const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side);
293 		digi_link_sound_to_pos(SOUND_WALL_REMOVED, seg, side, cp, 0, F1_0);
294 	};
295 	trigger_wall_op(t, vcsegptridx, op);
296 }
297 #endif
298 
299 // Slight variation on window_event_result meaning
300 // 'ignored' means we still want check_trigger to call multi_send_trigger
301 // 'handled' or 'close' means we don't
302 // 'close' will still close the game window
check_trigger_sub(object & plrobj,const trgnum_t trigger_num,const playernum_t pnum,const unsigned shot)303 window_event_result check_trigger_sub(object &plrobj, const trgnum_t trigger_num, const playernum_t pnum, const unsigned shot)
304 {
305 #if defined(DXX_BUILD_DESCENT_II)
306 	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
307 	auto &Vertices = LevelSharedVertexState.get_vertices();
308 #endif
309 	auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
310 	auto result = window_event_result::ignored;
311 
312 	if ((Game_mode & GM_MULTI) && vcplayerptr(pnum)->connected != CONNECT_PLAYING) // as a host we may want to handle triggers for our clients. to do that properly we must check wether we (host) or client is actually playing.
313 		return window_event_result::handled;
314 	auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
315 	auto &vmtrgptr = Triggers.vmptr;
316 	auto &trigger = *vmtrgptr(trigger_num);
317 	auto &Walls = LevelUniqueWallSubsystemState.Walls;
318 	auto &vmwallptr = Walls.vmptr;
319 
320 #if defined(DXX_BUILD_DESCENT_I)
321 	(void)shot;
322 	if (pnum == Player_num) {
323 		auto &player_info = plrobj.ctype.player_info;
324 		if (trigger.flags & TRIGGER_SHIELD_DAMAGE) {
325 			plrobj.shields -= trigger.value;
326 		}
327 
328 		if (trigger.flags & TRIGGER_EXIT)
329 		{
330 			result = start_endlevel_sequence();
331 			if (result == window_event_result::handled)
332 				result = window_event_result::ignored;	// call multi_send_trigger, or end game anyway
333 		}
334 
335 		if (trigger.flags & TRIGGER_SECRET_EXIT) {
336 			if (trigger.flags & TRIGGER_EXIT)
337 				LevelError("Trigger %u is both a regular and secret exit! This is not a recommended combination.", underlying_value(trigger_num));
338 			if (Newdemo_state == ND_STATE_RECORDING)		// stop demo recording
339 				Newdemo_state = ND_STATE_PAUSED;
340 
341 			if (Game_mode & GM_MULTI)
342 				multi_send_endlevel_start(multi_endlevel_type::secret);
343 			if (Game_mode & GM_NETWORK)
344 				multi::dispatch->do_protocol_frame(1, 1);
345 			result = std::max(PlayerFinishedLevel(1), result);		//1 means go to secret level
346 			LevelUniqueControlCenterState.Control_center_destroyed = 0;
347 			return std::max(result, window_event_result::handled);
348 		}
349 
350 		if (trigger.flags & TRIGGER_ENERGY_DRAIN) {
351 			player_info.energy -= trigger.value;
352 		}
353 	}
354 
355 	if (trigger.flags & TRIGGER_CONTROL_DOORS) {
356 		do_link(trigger);
357 	}
358 
359 	if (trigger.flags & TRIGGER_MATCEN) {
360 		if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
361 			do_matcen(trigger);
362 	}
363 
364 	if (trigger.flags & TRIGGER_ILLUSION_ON) {
365 		do_il_on(vcsegptridx, vmwallptr, trigger);
366 	}
367 
368 	if (trigger.flags & TRIGGER_ILLUSION_OFF) {
369 		do_il_off(vcsegptridx, vmwallptr, trigger);
370 	}
371 #elif defined(DXX_BUILD_DESCENT_II)
372 	if (trigger.flags & trigger_behavior_flags::disabled)
373 		return window_event_result::handled;		// don't send trigger hit to other players
374 
375 	if (trigger.flags & trigger_behavior_flags::one_shot)		//if this is a one-shot...
376 		trigger.flags |= trigger_behavior_flags::disabled;		//..then don't let it happen again
377 
378 	auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
379 	auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
380 	auto &vcvertptr = Vertices.vcptr;
381 	switch (trigger.type)
382 	{
383 		case trigger_action::normal_exit:
384 			if (pnum!=Player_num)
385 				break;
386 
387 			if (!EMULATING_D1)
388 				digi_stop_digi_sounds();  //Sound shouldn't cut out when exiting a D1 lvl
389 
390 			if (Current_level_num > 0) {
391 				result = start_endlevel_sequence();
392 			} else if (Current_level_num < 0) {
393 				if (plrobj.shields < 0 ||
394 					Player_dead_state != player_dead_state::no)
395 					break;
396 				// NMN 04/09/07 Do endlevel movie if we are
397 				//             playing a D1 secret level
398 				if (EMULATING_D1)
399 				{
400 					result = start_endlevel_sequence();
401 				} else {
402 					result = ExitSecretLevel();
403 				}
404 				return std::max(result, window_event_result::handled);
405 			}
406 			return std::max(result, window_event_result::handled);
407 			break;
408 
409 		case trigger_action::secret_exit: {
410 			int	truth;
411 
412 			if (pnum!=Player_num)
413 				break;
414 
415 			if (plrobj.shields < 0 ||
416 				Player_dead_state != player_dead_state::no)
417 				break;
418 
419 			if (is_SHAREWARE || is_MAC_SHARE) {
420 				HUD_init_message_literal(HM_DEFAULT, "Secret Level Teleporter disabled in Descent 2 Demo");
421 				digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
422 				break;
423 			}
424 
425 			if (Game_mode & GM_MULTI) {
426 				HUD_init_message_literal(HM_DEFAULT, "Secret Level Teleporter disabled in multiplayer!");
427 				digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
428 				break;
429 			}
430 
431 			truth = p_secret_level_destroyed();
432 
433 			if (Newdemo_state == ND_STATE_RECORDING)			// record whether we're really going to the secret level
434 				newdemo_record_secret_exit_blown(truth);
435 
436 			if ((Newdemo_state != ND_STATE_PLAYBACK) && truth) {
437 				HUD_init_message_literal(HM_DEFAULT, "Secret Level destroyed.  Exit disabled.");
438 				digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
439 				break;
440 			}
441 
442 			if (Newdemo_state == ND_STATE_RECORDING)		// stop demo recording
443 				Newdemo_state = ND_STATE_PAUSED;
444 
445 			digi_stop_digi_sounds();
446 
447 			EnterSecretLevel();
448 			LevelUniqueControlCenterState.Control_center_destroyed = 0;
449 			return window_event_result::handled;
450 		}
451 
452 		case trigger_action::open_door:
453 			do_link(trigger);
454 			print_trigger_message(pnum, trigger, shot, "Door%s opened!");
455 
456 			break;
457 
458 		case trigger_action::close_door:
459 			do_close_door(trigger);
460 			print_trigger_message(pnum, trigger, shot, "Door%s closed!");
461 			break;
462 
463 		case trigger_action::unlock_door:
464 			do_unlock_doors(vcsegptr, vmwallptr, trigger);
465 			print_trigger_message(pnum, trigger, shot, "Door%s unlocked!");
466 
467 			break;
468 
469 		case trigger_action::lock_door:
470 			do_lock_doors(vcsegptr, vmwallptr, trigger);
471 			print_trigger_message(pnum, trigger, shot, "Door%s locked!");
472 			break;
473 
474 		case trigger_action::open_wall:
475 			if (const auto w = do_change_walls(trigger, WALL_OPEN))
476 				print_trigger_message(pnum, trigger, shot, (w & 2) ? "Force field%s deactivated!" : "Wall%s opened!");
477 			break;
478 
479 		case trigger_action::close_wall:
480 			if (const auto w = do_change_walls(trigger, WALL_CLOSED))
481 				print_trigger_message(pnum, trigger, shot, (w & 2) ? "Force field%s activated!" : "Wall%s closed!");
482 			break;
483 
484 		case trigger_action::illusory_wall:
485 			//don't know what to say, so say nothing
486 			do_change_walls(trigger, WALL_ILLUSION);
487 			break;
488 
489 		case trigger_action::matcen:
490 			if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
491 				do_matcen(trigger);
492 			break;
493 
494 		case trigger_action::illusion_on:
495 			do_il_on(vcsegptridx, vmwallptr, trigger);
496 			print_trigger_message(pnum, trigger, shot, "Illusion%s on!");
497 			break;
498 
499 		case trigger_action::illusion_off:
500 			do_il_off(vcsegptridx, vcvertptr, vmwallptr, trigger);
501 			print_trigger_message(pnum, trigger, shot, "Illusion%s off!");
502 			break;
503 
504 		case trigger_action::light_off:
505 			if (do_light_off(LevelSharedDestructibleLightState, TmapInfo, Flickering_light_state, trigger))
506 				print_trigger_message(pnum, trigger, shot, "Light%s off!");
507 			break;
508 
509 		case trigger_action::light_on:
510 			if (do_light_on(LevelSharedDestructibleLightState, TmapInfo, Flickering_light_state, trigger))
511 				print_trigger_message(pnum, trigger, shot, "Light%s on!");
512 
513 			break;
514 
515 		default:
516 			Int3();
517 			break;
518 	}
519 #endif
520 
521 	return result;
522 }
523 
524 //-----------------------------------------------------------------
525 // Checks for a trigger whenever an object hits a trigger side.
check_trigger(const vcsegptridx_t seg,const unsigned side,object & plrobj,const vcobjptridx_t objnum,int shot)526 window_event_result check_trigger(const vcsegptridx_t seg, const unsigned side, object &plrobj, const vcobjptridx_t objnum, int shot)
527 {
528 	if ((Game_mode & GM_MULTI) && (get_local_player().connected != CONNECT_PLAYING)) // as a host we may want to handle triggers for our clients. so this function may be called when we are not playing.
529 		return window_event_result::ignored;
530 
531 #if defined(DXX_BUILD_DESCENT_I)
532 	if (objnum == &plrobj)
533 #elif defined(DXX_BUILD_DESCENT_II)
534 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
535 	if (objnum == &plrobj || (objnum->type == OBJ_ROBOT && Robot_info[get_robot_id(objnum)].companion))
536 #endif
537 	{
538 
539 #if defined(DXX_BUILD_DESCENT_I)
540 		if ( Newdemo_state == ND_STATE_PLAYBACK )
541 			return window_event_result::ignored;
542 #elif defined(DXX_BUILD_DESCENT_II)
543 		if ( Newdemo_state == ND_STATE_RECORDING )
544 			newdemo_record_trigger( seg, side, objnum,shot);
545 #endif
546 
547 		const auto wall_num = seg->shared_segment::sides[side].wall_num;
548 		if ( wall_num == wall_none ) return window_event_result::ignored;
549 
550 		auto &Walls = LevelUniqueWallSubsystemState.Walls;
551 		auto &vcwallptr = Walls.vcptr;
552 		const auto trigger_num = vcwallptr(wall_num)->trigger;
553 		if (trigger_num == trigger_none)
554 			return window_event_result::ignored;
555 
556 		{
557 			auto result = check_trigger_sub(plrobj, trigger_num, Player_num,shot);
558 			if (result != window_event_result::ignored)
559 				return result;
560 		}
561 
562 		if (Game_mode & GM_MULTI)
563 			multi_send_trigger(trigger_num);
564 	}
565 
566 	return window_event_result::handled;
567 }
568 
569 /*
570  * reads a v29_trigger structure from a PHYSFS_File
571  */
572 #if defined(DXX_BUILD_DESCENT_I)
v26_trigger_read(PHYSFS_File * fp,trigger & t)573 void v26_trigger_read(PHYSFS_File *fp, trigger &t)
574 {
575 	switch (const auto type = static_cast<trigger_action>(PHYSFSX_readByte(fp)))
576 	{
577 		case trigger_action::open_door: // door
578 			t.flags = TRIGGER_CONTROL_DOORS;
579 			break;
580 		case trigger_action::matcen: // matcen
581 			t.flags = TRIGGER_MATCEN;
582 			break;
583 		case trigger_action::normal_exit: // exit
584 			t.flags = TRIGGER_EXIT;
585 			break;
586 		case trigger_action::secret_exit: // secret exit
587 			t.flags = TRIGGER_SECRET_EXIT;
588 			break;
589 		case trigger_action::illusion_off: // illusion off
590 			t.flags = TRIGGER_ILLUSION_OFF;
591 			break;
592 		case trigger_action::illusion_on: // illusion on
593 			t.flags = TRIGGER_ILLUSION_ON;
594 			break;
595 		default:
596 			con_printf(CON_URGENT, "error: unsupported trigger type %d", static_cast<int>(type));
597 			throw std::runtime_error("unsupported trigger type");
598 	}
599 	if (PHYSFSX_readByte(fp) & 2)	// one shot
600 		t.flags |= TRIGGER_ONE_SHOT;
601 	t.num_links = PHYSFSX_readShort(fp);
602 	t.value = PHYSFSX_readInt(fp);
603 	PHYSFSX_readInt(fp);
604 	for (unsigned i=0; i < MAX_WALLS_PER_LINK; i++ )
605 		t.seg[i] = PHYSFSX_readShort(fp);
606 	for (unsigned i=0; i < MAX_WALLS_PER_LINK; i++ )
607 		t.side[i] = PHYSFSX_readShort(fp);
608 }
609 
v25_trigger_read(PHYSFS_File * fp,trigger * t)610 void v25_trigger_read(PHYSFS_File *fp, trigger *t)
611 #elif defined(DXX_BUILD_DESCENT_II)
612 extern void v29_trigger_read(v29_trigger *t, PHYSFS_File *fp)
613 #endif
614 {
615 #if defined(DXX_BUILD_DESCENT_I)
616 	PHYSFSX_readByte(fp);
617 #elif defined(DXX_BUILD_DESCENT_II)
618 	t->type = PHYSFSX_readByte(fp);
619 #endif
620 	t->flags = PHYSFSX_readShort(fp);
621 	t->value = PHYSFSX_readFix(fp);
622 	PHYSFSX_readFix(fp);
623 	PHYSFSX_readByte(fp);
624 	t->num_links = PHYSFSX_readShort(fp);
625 	for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
626 		t->seg[i] = PHYSFSX_readShort(fp);
627 	for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
628 		t->side[i] = PHYSFSX_readShort(fp);
629 }
630 
631 #if defined(DXX_BUILD_DESCENT_II)
632 /*
633  * reads a v30_trigger structure from a PHYSFS_File
634  */
v30_trigger_read(v30_trigger * t,PHYSFS_File * fp)635 extern void v30_trigger_read(v30_trigger *t, PHYSFS_File *fp)
636 {
637 	t->flags = PHYSFSX_readShort(fp);
638 	t->num_links = PHYSFSX_readByte(fp);
639 	t->pad = PHYSFSX_readByte(fp);
640 	t->value = PHYSFSX_readFix(fp);
641 	t->time = PHYSFSX_readFix(fp);
642 	for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
643 		t->seg[i] = PHYSFSX_readShort(fp);
644 	for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
645 		t->side[i] = PHYSFSX_readShort(fp);
646 }
647 
648 /*
649  * reads a trigger structure from a PHYSFS_File
650  */
trigger_read(trigger * t,PHYSFS_File * fp)651 extern void trigger_read(trigger *t, PHYSFS_File *fp)
652 {
653 	t->type = trigger_action{static_cast<uint8_t>(PHYSFSX_readByte(fp))};
654 	t->flags = trigger_behavior_flags{static_cast<uint8_t>(PHYSFSX_readByte(fp))};
655 	t->num_links = PHYSFSX_readByte(fp);
656 	PHYSFSX_readByte(fp);
657 	t->value = PHYSFSX_readFix(fp);
658 	PHYSFSX_readFix(fp);
659 	for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
660 		t->seg[i] = PHYSFSX_readShort(fp);
661 	for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
662 		t->side[i] = PHYSFSX_readShort(fp);
663 }
664 
trigger_type_from_flags(short flags)665 static trigger_action trigger_type_from_flags(short flags)
666 {
667 	if (flags & TRIGGER_CONTROL_DOORS)
668 		return trigger_action::open_door;
669 	else if (flags & (TRIGGER_SHIELD_DAMAGE | TRIGGER_ENERGY_DRAIN))
670 	{
671 	}
672 	else if (flags & TRIGGER_EXIT)
673 		return trigger_action::normal_exit;
674 	else if (flags & TRIGGER_MATCEN)
675 		return trigger_action::matcen;
676 	else if (flags & TRIGGER_ILLUSION_OFF)
677 		return trigger_action::illusion_off;
678 	else if (flags & TRIGGER_SECRET_EXIT)
679 		return trigger_action::secret_exit;
680 	else if (flags & TRIGGER_ILLUSION_ON)
681 		return trigger_action::illusion_on;
682 	else if (flags & TRIGGER_UNLOCK_DOORS)
683 		return trigger_action::unlock_door;
684 	else if (flags & TRIGGER_OPEN_WALL)
685 		return trigger_action::open_wall;
686 	else if (flags & TRIGGER_CLOSE_WALL)
687 		return trigger_action::close_wall;
688 	else if (flags & TRIGGER_ILLUSORY_WALL)
689 		return trigger_action::illusory_wall;
690 	throw std::runtime_error("unsupported trigger type");
691 }
692 
v30_trigger_to_v31_trigger(trigger & t,const v30_trigger & trig)693 static void v30_trigger_to_v31_trigger(trigger &t, const v30_trigger &trig)
694 {
695 	t.type        = trigger_type_from_flags(trig.flags);
696 	t.flags       = (trig.flags & TRIGGER_ONE_SHOT) ? trigger_behavior_flags::one_shot : trigger_behavior_flags{0};
697 	t.num_links   = trig.num_links;
698 	t.num_links   = trig.num_links;
699 	t.value       = trig.value;
700 	t.seg = trig.seg;
701 	t.side = trig.side;
702 }
703 
v29_trigger_read_as_v30(PHYSFS_File * fp,v30_trigger & trig)704 static void v29_trigger_read_as_v30(PHYSFS_File *fp, v30_trigger &trig)
705 {
706 	v29_trigger trig29;
707 	v29_trigger_read(&trig29, fp);
708 	trig.flags	= trig29.flags;
709 	// skip trig29.link_num. v30_trigger does not need it
710 	trig.num_links	= trig29.num_links;
711 	trig.value	= trig29.value;
712 	trig.time	= trig29.time;
713 	trig.seg = trig29.seg;
714 	trig.side = trig29.side;
715 }
716 
v29_trigger_read_as_v31(PHYSFS_File * fp,trigger & t)717 void v29_trigger_read_as_v31(PHYSFS_File *fp, trigger &t)
718 {
719 	v30_trigger trig;
720 	v29_trigger_read_as_v30(fp, trig);
721 	v30_trigger_to_v31_trigger(t, trig);
722 }
723 
v30_trigger_read_as_v31(PHYSFS_File * fp,trigger & t)724 void v30_trigger_read_as_v31(PHYSFS_File *fp, trigger &t)
725 {
726 	v30_trigger trig;
727 	v30_trigger_read(&trig, fp);
728 	v30_trigger_to_v31_trigger(t, trig);
729 }
730 #endif
731 
732 #if defined(DXX_BUILD_DESCENT_I)
733 DEFINE_SERIAL_UDT_TO_MESSAGE(trigger, t, (serial::pad<1>(), t.flags, t.value, serial::pad<5>(), t.num_links, t.seg, t.side));
734 ASSERT_SERIAL_UDT_MESSAGE_SIZE(trigger, 54);
735 #elif defined(DXX_BUILD_DESCENT_II)
736 DEFINE_SERIAL_UDT_TO_MESSAGE(trigger, t, (t.type, t.flags, t.num_links, serial::pad<1>(), t.value, serial::pad<4>(), t.seg, t.side));
737 ASSERT_SERIAL_UDT_MESSAGE_SIZE(trigger, 52);
738 #endif
739 
740 /*
741  * reads n trigger structs from a PHYSFS_File and swaps if specified
742  */
trigger_read(PHYSFS_File * fp,trigger & t)743 void trigger_read(PHYSFS_File *fp, trigger &t)
744 {
745 	PHYSFSX_serialize_read(fp, t);
746 }
747 
trigger_write(PHYSFS_File * fp,const trigger & t)748 void trigger_write(PHYSFS_File *fp, const trigger &t)
749 {
750 	PHYSFSX_serialize_write(fp, t);
751 }
752 
v29_trigger_write(PHYSFS_File * fp,const trigger & rt)753 void v29_trigger_write(PHYSFS_File *fp, const trigger &rt)
754 {
755 	const trigger *t = &rt;
756 	PHYSFSX_writeU8(fp, 0);		// unused 'type'
757 #if defined(DXX_BUILD_DESCENT_I)
758 	PHYSFS_writeSLE16(fp, t->flags);
759 #elif defined(DXX_BUILD_DESCENT_II)
760 	const auto one_shot_flag = (t->flags & trigger_behavior_flags::one_shot) ? TRIGGER_ONE_SHOT : TRIGGER_FLAG{0};
761 	switch (t->type)
762 	{
763 		case trigger_action::open_door:
764 			PHYSFS_writeSLE16(fp, TRIGGER_CONTROL_DOORS | one_shot_flag);
765 			break;
766 
767 		case trigger_action::normal_exit:
768 			PHYSFS_writeSLE16(fp, TRIGGER_EXIT | one_shot_flag);
769 			break;
770 
771 		case trigger_action::matcen:
772 			PHYSFS_writeSLE16(fp, TRIGGER_MATCEN | one_shot_flag);
773 			break;
774 
775 		case trigger_action::illusion_off:
776 			PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_OFF | one_shot_flag);
777 			break;
778 
779 		case trigger_action::secret_exit:
780 			PHYSFS_writeSLE16(fp, TRIGGER_SECRET_EXIT | one_shot_flag);
781 			break;
782 
783 		case trigger_action::illusion_on:
784 			PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_ON | one_shot_flag);
785 			break;
786 
787 		case trigger_action::unlock_door:
788 			PHYSFS_writeSLE16(fp, TRIGGER_UNLOCK_DOORS | one_shot_flag);
789 			break;
790 
791 		case trigger_action::open_wall:
792 			PHYSFS_writeSLE16(fp, TRIGGER_OPEN_WALL | one_shot_flag);
793 			break;
794 
795 		case trigger_action::close_wall:
796 			PHYSFS_writeSLE16(fp, TRIGGER_CLOSE_WALL | one_shot_flag);
797 			break;
798 
799 		case trigger_action::illusory_wall:
800 			PHYSFS_writeSLE16(fp, TRIGGER_ILLUSORY_WALL | one_shot_flag);
801 			break;
802 
803 		default:
804 			Int3();
805 			PHYSFS_writeSLE16(fp, 0);
806 			break;
807 	}
808 #endif
809 
810 	PHYSFSX_writeFix(fp, t->value);
811 	PHYSFSX_writeFix(fp, 0);
812 
813 	PHYSFSX_writeU8(fp, -1);	//t->link_num
814 	PHYSFS_writeSLE16(fp, t->num_links);
815 
816 	for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
817 		PHYSFS_writeSLE16(fp, t->seg[i]);
818 	for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
819 		PHYSFS_writeSLE16(fp, t->side[i]);
820 }
821 }
822 
823 namespace dsx {
v30_trigger_write(PHYSFS_File * fp,const trigger & rt)824 void v30_trigger_write(PHYSFS_File *fp, const trigger &rt)
825 {
826 	const trigger *t = &rt;
827 #if defined(DXX_BUILD_DESCENT_I)
828 	uint8_t action;
829 	if (t->flags & TRIGGER_CONTROL_DOORS)
830 		action = static_cast<uint8_t>(trigger_action::open_door); // door
831 	else if (t->flags & TRIGGER_MATCEN)
832 		action = static_cast<uint8_t>(trigger_action::matcen); // matcen
833 	else if (t->flags & TRIGGER_EXIT)
834 		action = static_cast<uint8_t>(trigger_action::normal_exit); // exit
835 	else if (t->flags & TRIGGER_SECRET_EXIT)
836 		action = static_cast<uint8_t>(trigger_action::secret_exit); // secret exit
837 	else if (t->flags & TRIGGER_ILLUSION_OFF)
838 		action = static_cast<uint8_t>(trigger_action::illusion_off); // illusion off
839 	else if (t->flags & TRIGGER_ILLUSION_ON)
840 		action = static_cast<uint8_t>(trigger_action::illusion_on); // illusion on
841 	else
842 		action = 0;
843 	PHYSFSX_writeU8(fp, action);
844 #elif defined(DXX_BUILD_DESCENT_II)
845 	PHYSFSX_writeU8(fp, static_cast<uint8_t>(t->type));
846 #endif
847 
848 #if defined(DXX_BUILD_DESCENT_I)
849 	PHYSFS_writeSLE16(fp, t->flags);
850 #elif defined(DXX_BUILD_DESCENT_II)
851 	const auto one_shot_flag = (t->flags & trigger_behavior_flags::one_shot) ? TRIGGER_ONE_SHOT : TRIGGER_FLAG{0};
852 	switch (t->type)
853 	{
854 		case trigger_action::open_door:
855 			PHYSFS_writeSLE16(fp, TRIGGER_CONTROL_DOORS | one_shot_flag);
856 			break;
857 
858 		case trigger_action::normal_exit:
859 			PHYSFS_writeSLE16(fp, TRIGGER_EXIT | one_shot_flag);
860 			break;
861 
862 		case trigger_action::matcen:
863 			PHYSFS_writeSLE16(fp, TRIGGER_MATCEN | one_shot_flag);
864 			break;
865 
866 		case trigger_action::illusion_off:
867 			PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_OFF | one_shot_flag);
868 			break;
869 
870 		case trigger_action::secret_exit:
871 			PHYSFS_writeSLE16(fp, TRIGGER_SECRET_EXIT | one_shot_flag);
872 			break;
873 
874 		case trigger_action::illusion_on:
875 			PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_ON | one_shot_flag);
876 			break;
877 
878 		case trigger_action::unlock_door:
879 			PHYSFS_writeSLE16(fp, TRIGGER_UNLOCK_DOORS | one_shot_flag);
880 			break;
881 
882 		case trigger_action::open_wall:
883 			PHYSFS_writeSLE16(fp, TRIGGER_OPEN_WALL | one_shot_flag);
884 			break;
885 
886 		case trigger_action::close_wall:
887 			PHYSFS_writeSLE16(fp, TRIGGER_CLOSE_WALL | one_shot_flag);
888 			break;
889 
890 		case trigger_action::illusory_wall:
891 			PHYSFS_writeSLE16(fp, TRIGGER_ILLUSORY_WALL | one_shot_flag);
892 			break;
893 
894 		default:
895 			Int3();
896 			PHYSFS_writeSLE16(fp, 0);
897 			break;
898 	}
899 #endif
900 
901 	PHYSFSX_writeU8(fp, t->num_links);
902 	PHYSFSX_writeU8(fp, 0);	// t->pad
903 
904 	PHYSFSX_writeFix(fp, t->value);
905 	PHYSFSX_writeFix(fp, 0);
906 
907 	for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
908 		PHYSFS_writeSLE16(fp, t->seg[i]);
909 	for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
910 		PHYSFS_writeSLE16(fp, t->side[i]);
911 }
912 }
913 
914 namespace dsx {
v31_trigger_write(PHYSFS_File * fp,const trigger & rt)915 void v31_trigger_write(PHYSFS_File *fp, const trigger &rt)
916 {
917 	const trigger *t = &rt;
918 #if defined(DXX_BUILD_DESCENT_I)
919 	uint8_t action;
920 	if (t->flags & TRIGGER_CONTROL_DOORS)
921 		action = static_cast<uint8_t>(trigger_action::open_door); // door
922 	else if (t->flags & TRIGGER_MATCEN)
923 		action = static_cast<uint8_t>(trigger_action::matcen); // matcen
924 	else if (t->flags & TRIGGER_EXIT)
925 		action = static_cast<uint8_t>(trigger_action::normal_exit); // exit
926 	else if (t->flags & TRIGGER_SECRET_EXIT)
927 		action = static_cast<uint8_t>(trigger_action::secret_exit); // secret exit
928 	else if (t->flags & TRIGGER_ILLUSION_OFF)
929 		action = static_cast<uint8_t>(trigger_action::illusion_off); // illusion off
930 	else if (t->flags & TRIGGER_ILLUSION_ON)
931 		action = static_cast<uint8_t>(trigger_action::illusion_on); // illusion on
932 	else
933 		action = 0;
934 	PHYSFSX_writeU8(fp, action);
935 #elif defined(DXX_BUILD_DESCENT_II)
936 	PHYSFSX_writeU8(fp, static_cast<uint8_t>(t->type));
937 #endif
938 
939 #if defined(DXX_BUILD_DESCENT_I)
940 	PHYSFSX_writeU8(fp, (t->flags & TRIGGER_ONE_SHOT) ? 2 : 0);		// flags
941 #elif defined(DXX_BUILD_DESCENT_II)
942 	PHYSFSX_writeU8(fp, static_cast<uint8_t>(t->flags));
943 #endif
944 
945 	PHYSFSX_writeU8(fp, t->num_links);
946 	PHYSFSX_writeU8(fp, 0);	// t->pad
947 
948 	PHYSFSX_writeFix(fp, t->value);
949 	PHYSFSX_writeFix(fp, 0);
950 
951 	for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
952 		PHYSFS_writeSLE16(fp, t->seg[i]);
953 	for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
954 		PHYSFS_writeSLE16(fp, t->side[i]);
955 }
956 }
957