dxx-rebirth/similar/main/switch.cpp
Kp 6cf6c62550 Convert some uses of nm_messagebox_str to run_blocking_newmenu
The latter more clearly shows that the code flow will not proceed past
this point while the menu is open.  This conversion sets the stage for
later changes to make these menus asynchronous.
2021-06-28 03:37:49 +00:00

954 lines
30 KiB
C++

/*
* Portions of this file are copyright Rebirth contributors and licensed as
* described in COPYING.txt.
* Portions of this file are copyright Parallax Software and licensed
* according to the Parallax license below.
* See COPYING.txt for license details.
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
*/
/*
*
* New Triggers and Switches.
*
*/
#include <stdexcept>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "newmenu.h"
#include "game.h"
#include "switch.h"
#include "inferno.h"
#include "segment.h"
#include "dxxerror.h"
#include "gameseg.h"
#include "wall.h"
#include "object.h"
#include "fuelcen.h"
#include "newdemo.h"
#include "player.h"
#include "endlevel.h"
#include "gameseq.h"
#include "net_udp.h"
#include "palette.h"
#include "hudmsg.h"
#include "robot.h"
#include "bm.h"
#include "physfs-serial.h"
#include "d_levelstate.h"
#include "compiler-range_for.h"
#include "partial_range.h"
#if DXX_USE_EDITOR
//-----------------------------------------------------------------
// Initializes all the switches.
void trigger_init()
{
auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
Triggers.set_count(0);
}
#endif
template <typename SF, typename O, typename... Oa>
static inline void trigger_wall_op(const trigger &t, SF &segment_factory, const O &op, Oa &&... oargs)
{
for (unsigned i = 0, num_links = t.num_links; i != num_links; ++i)
op(std::forward<Oa>(oargs)..., segment_factory(t.seg[i]), t.side[i]);
}
//-----------------------------------------------------------------
// Executes a link, attached to a trigger.
// Toggles all walls linked to the switch.
// Opens doors, Blasts blast walls, turns off illusions.
static void do_link(const trigger &t)
{
auto &Walls = LevelUniqueWallSubsystemState.Walls;
auto &vmwallptr = Walls.vmptr;
trigger_wall_op(t, vmsegptridx, wall_toggle, vmwallptr);
}
#if defined(DXX_BUILD_DESCENT_II)
namespace dsx {
//close a door
static void do_close_door(const trigger &t)
{
auto &Walls = LevelUniqueWallSubsystemState.Walls;
trigger_wall_op(t, vmsegptridx, wall_close_door, Walls);
}
//turns lighting on. returns true if lights were actually turned on. (they
//would not be if they had previously been shot out).
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)
{
int ret=0;
const auto op = [&LevelSharedDestructibleLightState, &Flickering_light_state, &TmapInfo, &ret](const vmsegptridx_t segnum, const unsigned sidenum) {
//check if tmap2 casts light before turning the light on. This
//is to keep us from turning on blown-out lights
const auto tm2 = get_texture_index(segnum->unique_segment::sides[sidenum].tmap_num2);
if (TmapInfo[tm2].lighting) {
ret |= add_light(LevelSharedDestructibleLightState, segnum, sidenum); //any light sets flag
enable_flicker(Flickering_light_state, segnum, sidenum);
}
};
trigger_wall_op(t, vmsegptridx, op);
return ret;
}
//turns lighting off. returns true if lights were actually turned off. (they
//would not be if they had previously been shot out).
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)
{
int ret=0;
const auto op = [&LevelSharedDestructibleLightState, &Flickering_light_state, &TmapInfo, &ret](const vmsegptridx_t segnum, const unsigned sidenum) {
//check if tmap2 casts light before turning the light off. This
//is to keep us from turning off blown-out lights
const auto tm2 = get_texture_index(segnum->unique_segment::sides[sidenum].tmap_num2);
if (TmapInfo[tm2].lighting) {
ret |= subtract_light(LevelSharedDestructibleLightState, segnum, sidenum); //any light sets flag
disable_flicker(Flickering_light_state, segnum, sidenum);
}
};
trigger_wall_op(t, vmsegptridx, op);
return ret;
}
// Unlocks all doors linked to the switch.
static void do_unlock_doors(fvcsegptr &vcsegptr, fvmwallptr &vmwallptr, const trigger &t)
{
const auto op = [&vmwallptr](const shared_segment &segp, const unsigned sidenum) {
const auto wall_num = segp.sides[sidenum].wall_num;
if (wall_num == wall_none)
return;
auto &w = *vmwallptr(wall_num);
w.flags &= ~WALL_DOOR_LOCKED;
w.keys = wall_key::none;
};
trigger_wall_op(t, vcsegptr, op);
}
// Locks all doors linked to the switch.
static void do_lock_doors(fvcsegptr &vcsegptr, fvmwallptr &vmwallptr, const trigger &t)
{
const auto op = [&vmwallptr](const shared_segment &segp, const unsigned sidenum) {
const auto wall_num = segp.sides[sidenum].wall_num;
if (wall_num == wall_none)
return;
auto &w = *vmwallptr(wall_num);
w.flags |= WALL_DOOR_LOCKED;
};
trigger_wall_op(t, vcsegptr, op);
}
// Changes walls pointed to by a trigger. returns true if any walls changed
static int do_change_walls(const trigger &t, const uint8_t new_wall_type)
{
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &Objects = LevelUniqueObjectState.Objects;
auto &Vertices = LevelSharedVertexState.get_vertices();
auto &vmobjptr = Objects.vmptr;
int ret=0;
auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
auto &Walls = LevelUniqueWallSubsystemState.Walls;
auto &vmwallptr = Walls.vmptr;
for (unsigned i = 0; i < t.num_links; ++i)
{
uint8_t cside;
const auto &&segp = vmsegptridx(t.seg[i]);
const auto side = t.side[i];
imsegptridx_t csegp = segment_none;
if (!IS_CHILD(segp->children[side]))
{
cside = side_none;
}
else
{
csegp = imsegptridx(segp->children[side]);
cside = find_connect_side(segp, csegp);
Assert(cside != side_none);
}
wall *w0p;
const auto w0num = segp->shared_segment::sides[side].wall_num;
if (const auto &&uw0p = vmwallptr.check_untrusted(w0num))
w0p = *uw0p;
else
{
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);
continue;
}
auto &wall0 = *w0p;
imwallptr_t wall1 = nullptr;
if ((cside == side_none || csegp->shared_segment::sides[cside].wall_num == wall_none ||
(wall1 = vmwallptr(csegp->shared_segment::sides[cside].wall_num))->type == new_wall_type) &&
wall0.type == new_wall_type)
continue; //already in correct state, so skip
ret |= 1;
auto &vcvertptr = Vertices.vcptr;
switch (t.type)
{
case trigger_action::open_wall:
if ((TmapInfo[get_texture_index(segp->unique_segment::sides[side].tmap_num)].flags & TMI_FORCE_FIELD)) {
ret |= 2;
const auto &&pos = compute_center_point_on_side(vcvertptr, segp, side);
digi_link_sound_to_pos( SOUND_FORCEFIELD_OFF, segp, side, pos, 0, F1_0 );
digi_kill_sound_linked_to_segment(segp,side,SOUND_FORCEFIELD_HUM);
wall0.type = new_wall_type;
if (wall1)
{
wall1->type = new_wall_type;
digi_kill_sound_linked_to_segment(csegp, cside, SOUND_FORCEFIELD_HUM);
}
}
else
start_wall_cloak(segp,side);
break;
case trigger_action::close_wall:
if ((TmapInfo[get_texture_index(segp->unique_segment::sides[side].tmap_num)].flags & TMI_FORCE_FIELD)) {
ret |= 2;
{
const auto &&pos = compute_center_point_on_side(vcvertptr, segp, side);
digi_link_sound_to_pos(SOUND_FORCEFIELD_HUM,segp,side,pos,1, F1_0/2);
}
case trigger_action::illusory_wall:
wall0.type = new_wall_type;
if (wall1)
wall1->type = new_wall_type;
}
else
start_wall_decloak(segp,side);
break;
default:
return 0;
}
LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, segp->shared_segment::sides[side].wall_num);
if (wall1)
LevelUniqueStuckObjectState.kill_stuck_objects(vmobjptr, csegp->shared_segment::sides[cside].wall_num);
}
flush_fcd_cache();
return ret;
}
#define print_trigger_message(pnum,trig,shot,message) \
((void)((print_trigger_message(pnum,trig,shot)) && \
HUD_init_message(HM_DEFAULT, message, &"s"[trig.num_links <= 1])))
static int (print_trigger_message)(int pnum, const trigger &t, int shot)
{
if (shot && pnum == Player_num && !(t.flags & trigger_behavior_flags::no_message))
return 1;
return 0;
}
}
#endif
static void do_matcen(const trigger &t)
{
range_for (auto &i, partial_const_range(t.seg, t.num_links))
trigger_matcen(vmsegptridx(i));
}
static void do_il_on(fvcsegptridx &vcsegptridx, fvmwallptr &vmwallptr, const trigger &t)
{
trigger_wall_op(t, vcsegptridx, wall_illusion_on, vmwallptr);
}
namespace dsx {
#if defined(DXX_BUILD_DESCENT_I)
static void do_il_off(fvcsegptridx &vcsegptridx, fvmwallptr &vmwallptr, const trigger &t)
{
trigger_wall_op(t, vcsegptridx, wall_illusion_off, vmwallptr);
}
#elif defined(DXX_BUILD_DESCENT_II)
static void do_il_off(fvcsegptridx &vcsegptridx, fvcvertptr &vcvertptr, fvmwallptr &vmwallptr, const trigger &t)
{
const auto &&op = [&vcvertptr, &vmwallptr](const vcsegptridx_t seg, const unsigned side) {
wall_illusion_off(vmwallptr, seg, side);
const auto &&cp = compute_center_point_on_side(vcvertptr, seg, side);
digi_link_sound_to_pos(SOUND_WALL_REMOVED, seg, side, cp, 0, F1_0);
};
trigger_wall_op(t, vcsegptridx, op);
}
#endif
// Slight variation on window_event_result meaning
// 'ignored' means we still want check_trigger to call multi_send_trigger
// 'handled' or 'close' means we don't
// 'close' will still close the game window
window_event_result check_trigger_sub(object &plrobj, const trgnum_t trigger_num, const playernum_t pnum, const unsigned shot)
{
#if defined(DXX_BUILD_DESCENT_II)
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &Vertices = LevelSharedVertexState.get_vertices();
#endif
auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
auto result = window_event_result::ignored;
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.
return window_event_result::handled;
auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
auto &vmtrgptr = Triggers.vmptr;
auto &trigger = *vmtrgptr(trigger_num);
auto &Walls = LevelUniqueWallSubsystemState.Walls;
auto &vmwallptr = Walls.vmptr;
#if defined(DXX_BUILD_DESCENT_I)
(void)shot;
if (pnum == Player_num) {
auto &player_info = plrobj.ctype.player_info;
if (trigger.flags & TRIGGER_SHIELD_DAMAGE) {
plrobj.shields -= trigger.value;
}
if (trigger.flags & TRIGGER_EXIT)
{
result = start_endlevel_sequence();
if (result == window_event_result::handled)
result = window_event_result::ignored; // call multi_send_trigger, or end game anyway
}
if (trigger.flags & TRIGGER_SECRET_EXIT) {
if (trigger.flags & TRIGGER_EXIT)
LevelError("Trigger %u is both a regular and secret exit! This is not a recommended combination.", trigger_num);
if (Newdemo_state == ND_STATE_RECORDING) // stop demo recording
Newdemo_state = ND_STATE_PAUSED;
if (Game_mode & GM_MULTI)
multi_send_endlevel_start(multi_endlevel_type::secret);
if (Game_mode & GM_NETWORK)
multi::dispatch->do_protocol_frame(1, 1);
result = std::max(PlayerFinishedLevel(1), result); //1 means go to secret level
LevelUniqueControlCenterState.Control_center_destroyed = 0;
return std::max(result, window_event_result::handled);
}
if (trigger.flags & TRIGGER_ENERGY_DRAIN) {
player_info.energy -= trigger.value;
}
}
if (trigger.flags & TRIGGER_CONTROL_DOORS) {
do_link(trigger);
}
if (trigger.flags & TRIGGER_MATCEN) {
if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
do_matcen(trigger);
}
if (trigger.flags & TRIGGER_ILLUSION_ON) {
do_il_on(vcsegptridx, vmwallptr, trigger);
}
if (trigger.flags & TRIGGER_ILLUSION_OFF) {
do_il_off(vcsegptridx, vmwallptr, trigger);
}
#elif defined(DXX_BUILD_DESCENT_II)
if (trigger.flags & trigger_behavior_flags::disabled)
return window_event_result::handled; // don't send trigger hit to other players
if (trigger.flags & trigger_behavior_flags::one_shot) //if this is a one-shot...
trigger.flags |= trigger_behavior_flags::disabled; //..then don't let it happen again
auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
auto &vcvertptr = Vertices.vcptr;
switch (trigger.type)
{
case trigger_action::normal_exit:
if (pnum!=Player_num)
break;
if (!EMULATING_D1)
digi_stop_digi_sounds(); //Sound shouldn't cut out when exiting a D1 lvl
if (Current_level_num > 0) {
result = start_endlevel_sequence();
} else if (Current_level_num < 0) {
if (plrobj.shields < 0 ||
Player_dead_state != player_dead_state::no)
break;
// NMN 04/09/07 Do endlevel movie if we are
// playing a D1 secret level
if (EMULATING_D1)
{
result = start_endlevel_sequence();
} else {
result = ExitSecretLevel();
}
return std::max(result, window_event_result::handled);
}
return std::max(result, window_event_result::handled);
break;
case trigger_action::secret_exit: {
int truth;
if (pnum!=Player_num)
break;
if (plrobj.shields < 0 ||
Player_dead_state != player_dead_state::no)
break;
if (is_SHAREWARE || is_MAC_SHARE) {
HUD_init_message_literal(HM_DEFAULT, "Secret Level Teleporter disabled in Descent 2 Demo");
digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
break;
}
if (Game_mode & GM_MULTI) {
HUD_init_message_literal(HM_DEFAULT, "Secret Level Teleporter disabled in multiplayer!");
digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
break;
}
truth = p_secret_level_destroyed();
if (Newdemo_state == ND_STATE_RECORDING) // record whether we're really going to the secret level
newdemo_record_secret_exit_blown(truth);
if ((Newdemo_state != ND_STATE_PLAYBACK) && truth) {
HUD_init_message_literal(HM_DEFAULT, "Secret Level destroyed. Exit disabled.");
digi_play_sample( SOUND_BAD_SELECTION, F1_0 );
break;
}
if (Newdemo_state == ND_STATE_RECORDING) // stop demo recording
Newdemo_state = ND_STATE_PAUSED;
digi_stop_digi_sounds();
EnterSecretLevel();
LevelUniqueControlCenterState.Control_center_destroyed = 0;
return window_event_result::handled;
}
case trigger_action::open_door:
do_link(trigger);
print_trigger_message(pnum, trigger, shot, "Door%s opened!");
break;
case trigger_action::close_door:
do_close_door(trigger);
print_trigger_message(pnum, trigger, shot, "Door%s closed!");
break;
case trigger_action::unlock_door:
do_unlock_doors(vcsegptr, vmwallptr, trigger);
print_trigger_message(pnum, trigger, shot, "Door%s unlocked!");
break;
case trigger_action::lock_door:
do_lock_doors(vcsegptr, vmwallptr, trigger);
print_trigger_message(pnum, trigger, shot, "Door%s locked!");
break;
case trigger_action::open_wall:
if (const auto w = do_change_walls(trigger, WALL_OPEN))
print_trigger_message(pnum, trigger, shot, (w & 2) ? "Force field%s deactivated!" : "Wall%s opened!");
break;
case trigger_action::close_wall:
if (const auto w = do_change_walls(trigger, WALL_CLOSED))
print_trigger_message(pnum, trigger, shot, (w & 2) ? "Force field%s activated!" : "Wall%s closed!");
break;
case trigger_action::illusory_wall:
//don't know what to say, so say nothing
do_change_walls(trigger, WALL_ILLUSION);
break;
case trigger_action::matcen:
if (!(Game_mode & GM_MULTI) || (Game_mode & GM_MULTI_ROBOTS))
do_matcen(trigger);
break;
case trigger_action::illusion_on:
do_il_on(vcsegptridx, vmwallptr, trigger);
print_trigger_message(pnum, trigger, shot, "Illusion%s on!");
break;
case trigger_action::illusion_off:
do_il_off(vcsegptridx, vcvertptr, vmwallptr, trigger);
print_trigger_message(pnum, trigger, shot, "Illusion%s off!");
break;
case trigger_action::light_off:
if (do_light_off(LevelSharedDestructibleLightState, TmapInfo, Flickering_light_state, trigger))
print_trigger_message(pnum, trigger, shot, "Light%s off!");
break;
case trigger_action::light_on:
if (do_light_on(LevelSharedDestructibleLightState, TmapInfo, Flickering_light_state, trigger))
print_trigger_message(pnum, trigger, shot, "Light%s on!");
break;
default:
Int3();
break;
}
#endif
return result;
}
//-----------------------------------------------------------------
// Checks for a trigger whenever an object hits a trigger side.
window_event_result check_trigger(const vcsegptridx_t seg, const unsigned side, object &plrobj, const vcobjptridx_t objnum, int shot)
{
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.
return window_event_result::ignored;
#if defined(DXX_BUILD_DESCENT_I)
if (objnum == &plrobj)
#elif defined(DXX_BUILD_DESCENT_II)
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
if (objnum == &plrobj || (objnum->type == OBJ_ROBOT && Robot_info[get_robot_id(objnum)].companion))
#endif
{
#if defined(DXX_BUILD_DESCENT_I)
if ( Newdemo_state == ND_STATE_PLAYBACK )
return window_event_result::ignored;
#elif defined(DXX_BUILD_DESCENT_II)
if ( Newdemo_state == ND_STATE_RECORDING )
newdemo_record_trigger( seg, side, objnum,shot);
#endif
const auto wall_num = seg->shared_segment::sides[side].wall_num;
if ( wall_num == wall_none ) return window_event_result::ignored;
auto &Walls = LevelUniqueWallSubsystemState.Walls;
auto &vcwallptr = Walls.vcptr;
const auto trigger_num = vcwallptr(wall_num)->trigger;
if (trigger_num == trigger_none)
return window_event_result::ignored;
{
auto result = check_trigger_sub(plrobj, trigger_num, Player_num,shot);
if (result != window_event_result::ignored)
return result;
}
if (Game_mode & GM_MULTI)
multi_send_trigger(trigger_num);
}
return window_event_result::handled;
}
/*
* reads a v29_trigger structure from a PHYSFS_File
*/
#if defined(DXX_BUILD_DESCENT_I)
void v26_trigger_read(PHYSFS_File *fp, trigger &t)
{
switch (const auto type = static_cast<trigger_action>(PHYSFSX_readByte(fp)))
{
case trigger_action::open_door: // door
t.flags = TRIGGER_CONTROL_DOORS;
break;
case trigger_action::matcen: // matcen
t.flags = TRIGGER_MATCEN;
break;
case trigger_action::normal_exit: // exit
t.flags = TRIGGER_EXIT;
break;
case trigger_action::secret_exit: // secret exit
t.flags = TRIGGER_SECRET_EXIT;
break;
case trigger_action::illusion_off: // illusion off
t.flags = TRIGGER_ILLUSION_OFF;
break;
case trigger_action::illusion_on: // illusion on
t.flags = TRIGGER_ILLUSION_ON;
break;
default:
con_printf(CON_URGENT, "error: unsupported trigger type %d", static_cast<int>(type));
throw std::runtime_error("unsupported trigger type");
}
if (PHYSFSX_readByte(fp) & 2) // one shot
t.flags |= TRIGGER_ONE_SHOT;
t.num_links = PHYSFSX_readShort(fp);
t.value = PHYSFSX_readInt(fp);
PHYSFSX_readInt(fp);
for (unsigned i=0; i < MAX_WALLS_PER_LINK; i++ )
t.seg[i] = PHYSFSX_readShort(fp);
for (unsigned i=0; i < MAX_WALLS_PER_LINK; i++ )
t.side[i] = PHYSFSX_readShort(fp);
}
void v25_trigger_read(PHYSFS_File *fp, trigger *t)
#elif defined(DXX_BUILD_DESCENT_II)
extern void v29_trigger_read(v29_trigger *t, PHYSFS_File *fp)
#endif
{
#if defined(DXX_BUILD_DESCENT_I)
PHYSFSX_readByte(fp);
#elif defined(DXX_BUILD_DESCENT_II)
t->type = PHYSFSX_readByte(fp);
#endif
t->flags = PHYSFSX_readShort(fp);
t->value = PHYSFSX_readFix(fp);
PHYSFSX_readFix(fp);
PHYSFSX_readByte(fp);
t->num_links = PHYSFSX_readShort(fp);
for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
t->seg[i] = PHYSFSX_readShort(fp);
for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
t->side[i] = PHYSFSX_readShort(fp);
}
#if defined(DXX_BUILD_DESCENT_II)
/*
* reads a v30_trigger structure from a PHYSFS_File
*/
extern void v30_trigger_read(v30_trigger *t, PHYSFS_File *fp)
{
t->flags = PHYSFSX_readShort(fp);
t->num_links = PHYSFSX_readByte(fp);
t->pad = PHYSFSX_readByte(fp);
t->value = PHYSFSX_readFix(fp);
t->time = PHYSFSX_readFix(fp);
for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
t->seg[i] = PHYSFSX_readShort(fp);
for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
t->side[i] = PHYSFSX_readShort(fp);
}
/*
* reads a trigger structure from a PHYSFS_File
*/
extern void trigger_read(trigger *t, PHYSFS_File *fp)
{
t->type = trigger_action{static_cast<uint8_t>(PHYSFSX_readByte(fp))};
t->flags = trigger_behavior_flags{static_cast<uint8_t>(PHYSFSX_readByte(fp))};
t->num_links = PHYSFSX_readByte(fp);
PHYSFSX_readByte(fp);
t->value = PHYSFSX_readFix(fp);
PHYSFSX_readFix(fp);
for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
t->seg[i] = PHYSFSX_readShort(fp);
for (unsigned i=0; i<MAX_WALLS_PER_LINK; i++ )
t->side[i] = PHYSFSX_readShort(fp);
}
static trigger_action trigger_type_from_flags(short flags)
{
if (flags & TRIGGER_CONTROL_DOORS)
return trigger_action::open_door;
else if (flags & (TRIGGER_SHIELD_DAMAGE | TRIGGER_ENERGY_DRAIN))
{
}
else if (flags & TRIGGER_EXIT)
return trigger_action::normal_exit;
else if (flags & TRIGGER_MATCEN)
return trigger_action::matcen;
else if (flags & TRIGGER_ILLUSION_OFF)
return trigger_action::illusion_off;
else if (flags & TRIGGER_SECRET_EXIT)
return trigger_action::secret_exit;
else if (flags & TRIGGER_ILLUSION_ON)
return trigger_action::illusion_on;
else if (flags & TRIGGER_UNLOCK_DOORS)
return trigger_action::unlock_door;
else if (flags & TRIGGER_OPEN_WALL)
return trigger_action::open_wall;
else if (flags & TRIGGER_CLOSE_WALL)
return trigger_action::close_wall;
else if (flags & TRIGGER_ILLUSORY_WALL)
return trigger_action::illusory_wall;
throw std::runtime_error("unsupported trigger type");
}
static void v30_trigger_to_v31_trigger(trigger &t, const v30_trigger &trig)
{
t.type = trigger_type_from_flags(trig.flags);
t.flags = (trig.flags & TRIGGER_ONE_SHOT) ? trigger_behavior_flags::one_shot : trigger_behavior_flags{0};
t.num_links = trig.num_links;
t.num_links = trig.num_links;
t.value = trig.value;
t.seg = trig.seg;
t.side = trig.side;
}
static void v29_trigger_read_as_v30(PHYSFS_File *fp, v30_trigger &trig)
{
v29_trigger trig29;
v29_trigger_read(&trig29, fp);
trig.flags = trig29.flags;
// skip trig29.link_num. v30_trigger does not need it
trig.num_links = trig29.num_links;
trig.value = trig29.value;
trig.time = trig29.time;
trig.seg = trig29.seg;
trig.side = trig29.side;
}
void v29_trigger_read_as_v31(PHYSFS_File *fp, trigger &t)
{
v30_trigger trig;
v29_trigger_read_as_v30(fp, trig);
v30_trigger_to_v31_trigger(t, trig);
}
void v30_trigger_read_as_v31(PHYSFS_File *fp, trigger &t)
{
v30_trigger trig;
v30_trigger_read(&trig, fp);
v30_trigger_to_v31_trigger(t, trig);
}
#endif
#if defined(DXX_BUILD_DESCENT_I)
DEFINE_SERIAL_UDT_TO_MESSAGE(trigger, t, (serial::pad<1>(), t.flags, t.value, serial::pad<5>(), t.num_links, t.seg, t.side));
ASSERT_SERIAL_UDT_MESSAGE_SIZE(trigger, 54);
#elif defined(DXX_BUILD_DESCENT_II)
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));
ASSERT_SERIAL_UDT_MESSAGE_SIZE(trigger, 52);
#endif
/*
* reads n trigger structs from a PHYSFS_File and swaps if specified
*/
void trigger_read(PHYSFS_File *fp, trigger &t)
{
PHYSFSX_serialize_read(fp, t);
}
void trigger_write(PHYSFS_File *fp, const trigger &t)
{
PHYSFSX_serialize_write(fp, t);
}
void v29_trigger_write(PHYSFS_File *fp, const trigger &rt)
{
const trigger *t = &rt;
PHYSFSX_writeU8(fp, 0); // unused 'type'
#if defined(DXX_BUILD_DESCENT_I)
PHYSFS_writeSLE16(fp, t->flags);
#elif defined(DXX_BUILD_DESCENT_II)
const auto one_shot_flag = (t->flags & trigger_behavior_flags::one_shot) ? TRIGGER_ONE_SHOT : TRIGGER_FLAG{0};
switch (t->type)
{
case trigger_action::open_door:
PHYSFS_writeSLE16(fp, TRIGGER_CONTROL_DOORS | one_shot_flag);
break;
case trigger_action::normal_exit:
PHYSFS_writeSLE16(fp, TRIGGER_EXIT | one_shot_flag);
break;
case trigger_action::matcen:
PHYSFS_writeSLE16(fp, TRIGGER_MATCEN | one_shot_flag);
break;
case trigger_action::illusion_off:
PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_OFF | one_shot_flag);
break;
case trigger_action::secret_exit:
PHYSFS_writeSLE16(fp, TRIGGER_SECRET_EXIT | one_shot_flag);
break;
case trigger_action::illusion_on:
PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_ON | one_shot_flag);
break;
case trigger_action::unlock_door:
PHYSFS_writeSLE16(fp, TRIGGER_UNLOCK_DOORS | one_shot_flag);
break;
case trigger_action::open_wall:
PHYSFS_writeSLE16(fp, TRIGGER_OPEN_WALL | one_shot_flag);
break;
case trigger_action::close_wall:
PHYSFS_writeSLE16(fp, TRIGGER_CLOSE_WALL | one_shot_flag);
break;
case trigger_action::illusory_wall:
PHYSFS_writeSLE16(fp, TRIGGER_ILLUSORY_WALL | one_shot_flag);
break;
default:
Int3();
PHYSFS_writeSLE16(fp, 0);
break;
}
#endif
PHYSFSX_writeFix(fp, t->value);
PHYSFSX_writeFix(fp, 0);
PHYSFSX_writeU8(fp, -1); //t->link_num
PHYSFS_writeSLE16(fp, t->num_links);
for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
PHYSFS_writeSLE16(fp, t->seg[i]);
for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
PHYSFS_writeSLE16(fp, t->side[i]);
}
}
namespace dsx {
void v30_trigger_write(PHYSFS_File *fp, const trigger &rt)
{
const trigger *t = &rt;
#if defined(DXX_BUILD_DESCENT_I)
uint8_t action;
if (t->flags & TRIGGER_CONTROL_DOORS)
action = static_cast<uint8_t>(trigger_action::open_door); // door
else if (t->flags & TRIGGER_MATCEN)
action = static_cast<uint8_t>(trigger_action::matcen); // matcen
else if (t->flags & TRIGGER_EXIT)
action = static_cast<uint8_t>(trigger_action::normal_exit); // exit
else if (t->flags & TRIGGER_SECRET_EXIT)
action = static_cast<uint8_t>(trigger_action::secret_exit); // secret exit
else if (t->flags & TRIGGER_ILLUSION_OFF)
action = static_cast<uint8_t>(trigger_action::illusion_off); // illusion off
else if (t->flags & TRIGGER_ILLUSION_ON)
action = static_cast<uint8_t>(trigger_action::illusion_on); // illusion on
else
action = 0;
PHYSFSX_writeU8(fp, action);
#elif defined(DXX_BUILD_DESCENT_II)
PHYSFSX_writeU8(fp, static_cast<uint8_t>(t->type));
#endif
#if defined(DXX_BUILD_DESCENT_I)
PHYSFS_writeSLE16(fp, t->flags);
#elif defined(DXX_BUILD_DESCENT_II)
const auto one_shot_flag = (t->flags & trigger_behavior_flags::one_shot) ? TRIGGER_ONE_SHOT : TRIGGER_FLAG{0};
switch (t->type)
{
case trigger_action::open_door:
PHYSFS_writeSLE16(fp, TRIGGER_CONTROL_DOORS | one_shot_flag);
break;
case trigger_action::normal_exit:
PHYSFS_writeSLE16(fp, TRIGGER_EXIT | one_shot_flag);
break;
case trigger_action::matcen:
PHYSFS_writeSLE16(fp, TRIGGER_MATCEN | one_shot_flag);
break;
case trigger_action::illusion_off:
PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_OFF | one_shot_flag);
break;
case trigger_action::secret_exit:
PHYSFS_writeSLE16(fp, TRIGGER_SECRET_EXIT | one_shot_flag);
break;
case trigger_action::illusion_on:
PHYSFS_writeSLE16(fp, TRIGGER_ILLUSION_ON | one_shot_flag);
break;
case trigger_action::unlock_door:
PHYSFS_writeSLE16(fp, TRIGGER_UNLOCK_DOORS | one_shot_flag);
break;
case trigger_action::open_wall:
PHYSFS_writeSLE16(fp, TRIGGER_OPEN_WALL | one_shot_flag);
break;
case trigger_action::close_wall:
PHYSFS_writeSLE16(fp, TRIGGER_CLOSE_WALL | one_shot_flag);
break;
case trigger_action::illusory_wall:
PHYSFS_writeSLE16(fp, TRIGGER_ILLUSORY_WALL | one_shot_flag);
break;
default:
Int3();
PHYSFS_writeSLE16(fp, 0);
break;
}
#endif
PHYSFSX_writeU8(fp, t->num_links);
PHYSFSX_writeU8(fp, 0); // t->pad
PHYSFSX_writeFix(fp, t->value);
PHYSFSX_writeFix(fp, 0);
for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
PHYSFS_writeSLE16(fp, t->seg[i]);
for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
PHYSFS_writeSLE16(fp, t->side[i]);
}
}
namespace dsx {
void v31_trigger_write(PHYSFS_File *fp, const trigger &rt)
{
const trigger *t = &rt;
#if defined(DXX_BUILD_DESCENT_I)
uint8_t action;
if (t->flags & TRIGGER_CONTROL_DOORS)
action = static_cast<uint8_t>(trigger_action::open_door); // door
else if (t->flags & TRIGGER_MATCEN)
action = static_cast<uint8_t>(trigger_action::matcen); // matcen
else if (t->flags & TRIGGER_EXIT)
action = static_cast<uint8_t>(trigger_action::normal_exit); // exit
else if (t->flags & TRIGGER_SECRET_EXIT)
action = static_cast<uint8_t>(trigger_action::secret_exit); // secret exit
else if (t->flags & TRIGGER_ILLUSION_OFF)
action = static_cast<uint8_t>(trigger_action::illusion_off); // illusion off
else if (t->flags & TRIGGER_ILLUSION_ON)
action = static_cast<uint8_t>(trigger_action::illusion_on); // illusion on
else
action = 0;
PHYSFSX_writeU8(fp, action);
#elif defined(DXX_BUILD_DESCENT_II)
PHYSFSX_writeU8(fp, static_cast<uint8_t>(t->type));
#endif
#if defined(DXX_BUILD_DESCENT_I)
PHYSFSX_writeU8(fp, (t->flags & TRIGGER_ONE_SHOT) ? 2 : 0); // flags
#elif defined(DXX_BUILD_DESCENT_II)
PHYSFSX_writeU8(fp, static_cast<uint8_t>(t->flags));
#endif
PHYSFSX_writeU8(fp, t->num_links);
PHYSFSX_writeU8(fp, 0); // t->pad
PHYSFSX_writeFix(fp, t->value);
PHYSFSX_writeFix(fp, 0);
for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
PHYSFS_writeSLE16(fp, t->seg[i]);
for (unsigned i = 0; i < MAX_WALLS_PER_LINK; i++)
PHYSFS_writeSLE16(fp, t->side[i]);
}
}