Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

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