This assignment looks bogus. If the signature saved when the object became stuck is different from the signature of the object currently using that slot, then remove_obsolete_stuck_objects kills the object using that slot. Signatures are defined to change when the object slot is freed and reused. Therefore, this statement kills the new object that took the slot of the obsolete object. This new object may be important, such as a spawned robot or a weapon.
1625 lines
45 KiB
C++
1625 lines
45 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.
|
|
*/
|
|
|
|
#include "wall.h"
|
|
#include "player.h"
|
|
#include "text.h"
|
|
#include "fireball.h"
|
|
#include "textures.h"
|
|
#include "newdemo.h"
|
|
#include "multi.h"
|
|
#include "gameseq.h"
|
|
#include "physfs-serial.h"
|
|
#include "gameseg.h"
|
|
#include "hudmsg.h"
|
|
#include "laser.h" // For seeing if a flare is stuck in a wall.
|
|
#include "effects.h"
|
|
|
|
#include "compiler-range_for.h"
|
|
#include "partial_range.h"
|
|
#include "segiter.h"
|
|
|
|
// Special door on boss level which is locked if not in multiplayer...sorry for this awful solution --MK.
|
|
#define BOSS_LOCKED_DOOR_LEVEL 7
|
|
#define BOSS_LOCKED_DOOR_SEG 595
|
|
#define BOSS_LOCKED_DOOR_SIDE 5
|
|
|
|
namespace dcx {
|
|
unsigned Num_wall_anims;
|
|
unsigned Num_open_doors; // Number of open doors
|
|
}
|
|
|
|
namespace dsx {
|
|
array<wclip, MAX_WALL_ANIMS> WallAnims; // Wall animations
|
|
array<active_door, MAX_DOORS> ActiveDoors;
|
|
}
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
#include "collide.h"
|
|
namespace dsx {
|
|
#define CLOAKING_WALL_TIME f1_0
|
|
|
|
array<cloaking_wall, MAX_CLOAKING_WALLS> CloakingWalls;
|
|
unsigned Num_cloaking_walls;
|
|
}
|
|
#endif
|
|
|
|
static std::pair<uint_fast32_t, uint_fast32_t> get_transparency_check_values(const side &side)
|
|
{
|
|
if (uint_fast32_t masked_tmap_num2 = side.tmap_num2 & 0x3FFF)
|
|
return {masked_tmap_num2, BM_FLAG_SUPER_TRANSPARENT};
|
|
return {side.tmap_num, BM_FLAG_TRANSPARENT};
|
|
}
|
|
|
|
// This function determines whether the current segment/side is transparent
|
|
// 1 = YES
|
|
// 0 = NO
|
|
static uint_fast32_t check_transparency(const side &side)
|
|
{
|
|
auto v = get_transparency_check_values(side);
|
|
return GameBitmaps[Textures[v.first].index].bm_flags & v.second;
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// This function checks whether we can fly through the given side.
|
|
// In other words, whether or not we have a 'doorway'
|
|
// Flags:
|
|
// WID_FLY_FLAG 1
|
|
// WID_RENDER_FLAG 2
|
|
// WID_RENDPAST_FLAG 4
|
|
// Return values:
|
|
// WID_WALL 2 // 0/1/0 wall
|
|
// WID_TRANSPARENT_WALL 6 // 0/1/1 transparent wall
|
|
// WID_ILLUSORY_WALL 3 // 1/1/0 illusory wall
|
|
// WID_TRANSILLUSORY_WALL 7 // 1/1/1 transparent illusory wall
|
|
// WID_NO_WALL 5 // 1/0/1 no wall, can fly through
|
|
WALL_IS_DOORWAY_result_t wall_is_doorway(const side &side)
|
|
{
|
|
auto &w = *vcwallptr(side.wall_num);
|
|
const auto type = w.type;
|
|
if (type == WALL_OPEN)
|
|
return WID_NO_WALL;
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (unlikely(type == WALL_CLOAKED))
|
|
return WID_CLOAKED_WALL;
|
|
#endif
|
|
|
|
const auto flags = w.flags;
|
|
if (type == WALL_ILLUSION) {
|
|
if (flags & WALL_ILLUSION_OFF)
|
|
return WID_NO_WALL;
|
|
else {
|
|
if (check_transparency(side))
|
|
return WID_TRANSILLUSORY_WALL;
|
|
else
|
|
return WID_ILLUSORY_WALL;
|
|
}
|
|
}
|
|
|
|
if (type == WALL_BLASTABLE) {
|
|
if (flags & WALL_BLASTED)
|
|
return WID_TRANSILLUSORY_WALL;
|
|
if (check_transparency(side))
|
|
return WID_TRANSPARENT_WALL;
|
|
else
|
|
return WID_WALL;
|
|
}
|
|
if (unlikely(flags & WALL_DOOR_OPENED))
|
|
return WID_TRANSILLUSORY_WALL;
|
|
if (likely(type == WALL_DOOR) && unlikely(w.state == WALL_DOOR_OPENING))
|
|
return WID_TRANSPARENT_WALL;
|
|
// If none of the above flags are set, there is no doorway.
|
|
if (check_transparency(side))
|
|
return WID_TRANSPARENT_WALL;
|
|
else
|
|
return WID_WALL; // There are children behind the door.
|
|
}
|
|
|
|
#ifdef EDITOR
|
|
//-----------------------------------------------------------------
|
|
// Initializes all the walls (in other words, no special walls)
|
|
void wall_init()
|
|
{
|
|
Walls.set_count(0);
|
|
range_for (auto &w, Walls)
|
|
{
|
|
w.segnum = segment_none;
|
|
w.sidenum = -1;
|
|
w.type = WALL_NORMAL;
|
|
w.flags = 0;
|
|
w.hps = 0;
|
|
w.trigger = -1;
|
|
w.clip_num = -1;
|
|
w.linked_wall = -1;
|
|
}
|
|
Num_open_doors = 0;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
Num_cloaking_walls = 0;
|
|
#endif
|
|
|
|
}
|
|
#endif
|
|
|
|
//set the tmap_num or tmap_num2 field for a wall/door
|
|
void wall_set_tmap_num(const vsegptridx_t seg,int side,const vsegptridx_t csegp,int cside,int anim_num,int frame_num)
|
|
{
|
|
wclip *anim = &WallAnims[anim_num];
|
|
int tmap = anim->frames[frame_num];
|
|
|
|
if ( Newdemo_state==ND_STATE_PLAYBACK ) return;
|
|
|
|
if (anim->flags & WCF_TMAP1) {
|
|
if (tmap != seg->sides[side].tmap_num || tmap != csegp->sides[cside].tmap_num)
|
|
{
|
|
seg->sides[side].tmap_num = csegp->sides[cside].tmap_num = tmap;
|
|
if ( Newdemo_state == ND_STATE_RECORDING )
|
|
newdemo_record_wall_set_tmap_num1(seg,side,csegp,cside,tmap);
|
|
}
|
|
} else {
|
|
Assert(tmap!=0 && seg->sides[side].tmap_num2!=0);
|
|
if (tmap != seg->sides[side].tmap_num2 || tmap != csegp->sides[cside].tmap_num2)
|
|
{
|
|
seg->sides[side].tmap_num2 = csegp->sides[cside].tmap_num2 = tmap;
|
|
if ( Newdemo_state == ND_STATE_RECORDING )
|
|
newdemo_record_wall_set_tmap_num2(seg,side,csegp,cside,tmap);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------------------
|
|
//when the wall has used all its hitpoints, this will destroy it
|
|
static void blast_blastable_wall(const vsegptridx_t seg, int side)
|
|
{
|
|
auto &w0 = *vwallptr(seg->sides[side].wall_num);
|
|
w0.hps = -1; //say it's blasted
|
|
|
|
const auto &&csegp = seg.absolute_sibling(seg->children[side]);
|
|
auto Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
const auto &&w1 = wallptr(cwall_num);
|
|
kill_stuck_objects(seg->sides[side].wall_num);
|
|
if (w1)
|
|
kill_stuck_objects(cwall_num);
|
|
|
|
const auto a = w0.clip_num;
|
|
//if this is an exploding wall, explode it
|
|
if (WallAnims[a].flags & WCF_EXPLODES)
|
|
explode_wall(seg,side);
|
|
else {
|
|
//if not exploding, set final frame, and make door passable
|
|
const auto n = WallAnims[a].num_frames;
|
|
w0.flags |= WALL_BLASTED;
|
|
if (w1)
|
|
w1->flags |= WALL_BLASTED;
|
|
wall_set_tmap_num(seg,side,csegp,Connectside,a,n-1);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------
|
|
// Destroys a blastable wall.
|
|
void wall_destroy(const vsegptridx_t seg, int side)
|
|
{
|
|
auto &w = *vwallptr(seg->sides[side].wall_num);
|
|
if (w.type == WALL_BLASTABLE)
|
|
blast_blastable_wall( seg, side );
|
|
else
|
|
Error("Hey bub, you are trying to destroy an indestructable wall.");
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// Deteriorate appearance of wall. (Changes bitmap (paste-ons))
|
|
void wall_damage(const vsegptridx_t seg, int side, fix damage)
|
|
{
|
|
int i;
|
|
|
|
if (seg->sides[side].wall_num == wall_none) {
|
|
return;
|
|
}
|
|
|
|
auto &w0 = *vwallptr(seg->sides[side].wall_num);
|
|
if (w0.type != WALL_BLASTABLE)
|
|
return;
|
|
|
|
if (!(w0.flags & WALL_BLASTED) && w0.hps >= 0)
|
|
{
|
|
const auto &&csegp = seg.absolute_sibling(seg->children[side]);
|
|
auto Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
if (const auto &&w1 = wallptr(cwall_num))
|
|
w1->hps -= damage;
|
|
w0.hps -= damage;
|
|
|
|
const auto a = w0.clip_num;
|
|
const auto n = WallAnims[a].num_frames;
|
|
|
|
if (w0.hps < WALL_HPS*1/n) {
|
|
blast_blastable_wall( seg, side );
|
|
if (Game_mode & GM_MULTI)
|
|
multi_send_door_open(seg, side, w0.flags);
|
|
}
|
|
else
|
|
for (i=0;i<n;i++)
|
|
if (w0.hps < WALL_HPS*(n-i)/n)
|
|
{
|
|
wall_set_tmap_num(seg,side,csegp,Connectside,a,i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------
|
|
// Opens a door
|
|
void wall_open_door(const vsegptridx_t seg, int side)
|
|
{
|
|
active_door *d;
|
|
|
|
const auto wall_num = seg->sides[side].wall_num;
|
|
wall *const w = vwallptr(wall_num);
|
|
//kill_stuck_objects(seg->sides[side].wall_num);
|
|
|
|
if ((w->state == WALL_DOOR_OPENING) || //already opening
|
|
(w->state == WALL_DOOR_WAITING)) //open, waiting to close
|
|
return;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (w->state == WALL_DOOR_OPEN) //open, & staying open
|
|
return;
|
|
#endif
|
|
|
|
if (w->state == WALL_DOOR_CLOSING) { //closing, so reuse door
|
|
|
|
int i;
|
|
|
|
d = NULL;
|
|
|
|
for (i=0;i<Num_open_doors;i++) { //find door
|
|
|
|
d = &ActiveDoors[i];
|
|
|
|
if (d->front_wallnum[0]==wall_num || d->back_wallnum[0]==wall_num ||
|
|
(d->n_parts==2 && (d->front_wallnum[1]==wall_num || d->back_wallnum[1]==wall_num)))
|
|
break;
|
|
}
|
|
|
|
if (i>=Num_open_doors) // likely in demo playback or multiplayer
|
|
{
|
|
d = &ActiveDoors[Num_open_doors];
|
|
d->time = 0;
|
|
Num_open_doors++;
|
|
Assert( Num_open_doors < MAX_DOORS );
|
|
}
|
|
else
|
|
{
|
|
Assert( d!=NULL ); // Get John!
|
|
|
|
d->time = WallAnims[w->clip_num].play_time - d->time;
|
|
|
|
if (d->time < 0)
|
|
d->time = 0;
|
|
}
|
|
|
|
}
|
|
else { //create new door
|
|
Assert(w->state == WALL_DOOR_CLOSED);
|
|
d = &ActiveDoors[Num_open_doors];
|
|
d->time = 0;
|
|
Num_open_doors++;
|
|
Assert( Num_open_doors < MAX_DOORS );
|
|
}
|
|
|
|
|
|
w->state = WALL_DOOR_OPENING;
|
|
|
|
// So that door can't be shot while opening
|
|
const auto &&csegp = vcsegptr(seg->children[side]);
|
|
auto Connectside = find_connect_side(seg, csegp);
|
|
if (Connectside != side_none)
|
|
{
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
if (const auto &&w1 = wallptr(cwall_num))
|
|
{
|
|
w1->state = WALL_DOOR_OPENING;
|
|
d->back_wallnum[0] = cwall_num;
|
|
}
|
|
d->front_wallnum[0] = seg->sides[side].wall_num;
|
|
}
|
|
else
|
|
con_printf(CON_URGENT, "Illegal Connectside %i in wall_open_door. Trying to hop over. Please check your level!", side);
|
|
|
|
if (Newdemo_state == ND_STATE_RECORDING) {
|
|
newdemo_record_door_opening(seg, side);
|
|
}
|
|
|
|
if (w->linked_wall != wall_none)
|
|
{
|
|
wall *const w2 = vwallptr(w->linked_wall);
|
|
|
|
Assert(w2->linked_wall == seg->sides[side].wall_num);
|
|
//Assert(!(w2->flags & WALL_DOOR_OPENING || w2->flags & WALL_DOOR_OPENED));
|
|
|
|
w2->state = WALL_DOOR_OPENING;
|
|
|
|
const auto &&seg2 = vcsegptridx(w2->segnum);
|
|
Connectside = find_connect_side(seg2, vcsegptr(seg2->children[w2->sidenum]));
|
|
Assert(Connectside != side_none);
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
if (const auto &&w3 = wallptr(cwall_num))
|
|
w3->state = WALL_DOOR_OPENING;
|
|
|
|
d->n_parts = 2;
|
|
d->front_wallnum[1] = w->linked_wall;
|
|
d->back_wallnum[1] = cwall_num;
|
|
}
|
|
else
|
|
d->n_parts = 1;
|
|
|
|
|
|
if ( Newdemo_state != ND_STATE_PLAYBACK )
|
|
{
|
|
// NOTE THE LINK TO ABOVE!!!!
|
|
const auto cp = compute_center_point_on_side(seg, side );
|
|
if (WallAnims[w->clip_num].open_sound > -1 )
|
|
digi_link_sound_to_pos( WallAnims[w->clip_num].open_sound, seg, side, cp, 0, F1_0 );
|
|
|
|
}
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
// This function closes the specified door and restores the closed
|
|
// door texture. This is called when the animation is done
|
|
void wall_close_door(int door_num)
|
|
{
|
|
active_door *d;
|
|
int i;
|
|
|
|
d = &ActiveDoors[door_num];
|
|
|
|
range_for (const auto p, partial_const_range(d->front_wallnum, d->n_parts))
|
|
{
|
|
wall *const w = vwallptr(p);
|
|
int side;
|
|
|
|
const auto &&seg = vsegptridx(w->segnum);
|
|
side = w->sidenum;
|
|
|
|
Assert(seg->sides[side].wall_num != wall_none); //Closing door on illegal wall
|
|
|
|
const auto &&csegp = seg.absolute_sibling(seg->children[side]);
|
|
auto Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
|
|
w->state = WALL_DOOR_CLOSED;
|
|
vwallptr(csegp->sides[Connectside].wall_num)->state = WALL_DOOR_CLOSED;
|
|
|
|
wall_set_tmap_num(seg,side,csegp,Connectside,w->clip_num,0);
|
|
|
|
}
|
|
|
|
for (i=door_num;i<Num_open_doors;i++)
|
|
ActiveDoors[i] = ActiveDoors[i+1];
|
|
|
|
Num_open_doors--;
|
|
}
|
|
#endif
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
//-----------------------------------------------------------------
|
|
// start the transition from closed -> open wall
|
|
void start_wall_cloak(const vsegptridx_t seg, int side)
|
|
{
|
|
cloaking_wall *d;
|
|
|
|
if ( Newdemo_state==ND_STATE_PLAYBACK ) return;
|
|
|
|
const auto &&w = vwallptridx(seg->sides[side].wall_num);
|
|
|
|
if (w->type == WALL_OPEN || w->state == WALL_DOOR_CLOAKING) //already open or cloaking
|
|
return;
|
|
|
|
const auto &&csegp = vcsegptr(seg->children[side]);
|
|
auto Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
|
|
if (w->state == WALL_DOOR_DECLOAKING) { //decloaking, so reuse door
|
|
d = NULL;
|
|
|
|
for (unsigned i = 0; i < Num_cloaking_walls; ++i)
|
|
{ //find door
|
|
|
|
d = &CloakingWalls[i];
|
|
if (d->front_wallnum == w || d->back_wallnum == w)
|
|
{
|
|
d->time = CLOAKING_WALL_TIME - d->time;
|
|
break;
|
|
}
|
|
}
|
|
Assert( d!=NULL ); // Get John!
|
|
}
|
|
else if (w->state == WALL_DOOR_CLOSED) { //create new door
|
|
d = &CloakingWalls[Num_cloaking_walls];
|
|
d->time = 0;
|
|
if (Num_cloaking_walls >= MAX_CLOAKING_WALLS) { //no more!
|
|
Int3(); //ran out of cloaking wall slots
|
|
w->type = WALL_OPEN;
|
|
if (const auto &&w1 = wallptr(cwall_num))
|
|
w1->type = WALL_OPEN;
|
|
return;
|
|
}
|
|
Num_cloaking_walls++;
|
|
}
|
|
else {
|
|
Int3(); //unexpected wall state
|
|
return;
|
|
}
|
|
|
|
w->state = WALL_DOOR_CLOAKING;
|
|
if (const auto &&w1 = wallptr(cwall_num))
|
|
w1->state = WALL_DOOR_CLOAKING;
|
|
|
|
d->front_wallnum = seg->sides[side].wall_num;
|
|
d->back_wallnum = cwall_num;
|
|
Assert(w->linked_wall == wall_none);
|
|
|
|
if ( Newdemo_state != ND_STATE_PLAYBACK ) {
|
|
const auto cp = compute_center_point_on_side(seg, side );
|
|
digi_link_sound_to_pos( SOUND_WALL_CLOAK_ON, seg, side, cp, 0, F1_0 );
|
|
}
|
|
|
|
auto &df = d->front_ls;
|
|
auto &db = d->back_ls;
|
|
auto &s0_uvls = seg->sides[side].uvls;
|
|
auto &s1_uvls = csegp->sides[Connectside].uvls;
|
|
for (unsigned i = 0; i < 4; ++i)
|
|
{
|
|
df[i] = s0_uvls[i].l;
|
|
db[i] = s1_uvls[i].l;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// start the transition from open -> closed wall
|
|
void start_wall_decloak(const vsegptridx_t seg, int side)
|
|
{
|
|
cloaking_wall *d;
|
|
|
|
if ( Newdemo_state==ND_STATE_PLAYBACK ) return;
|
|
|
|
Assert(seg->sides[side].wall_num != wall_none); //Opening door on illegal wall
|
|
|
|
const auto &&w = vwallptridx(seg->sides[side].wall_num);
|
|
|
|
if (w->type == WALL_CLOSED || w->state == WALL_DOOR_DECLOAKING) //already closed or decloaking
|
|
return;
|
|
|
|
if (w->state == WALL_DOOR_CLOAKING) { //cloaking, so reuse door
|
|
d = NULL;
|
|
|
|
for (unsigned i = 0; i < Num_cloaking_walls; ++i)
|
|
{ //find door
|
|
|
|
d = &CloakingWalls[i];
|
|
|
|
if (d->front_wallnum == w || d->back_wallnum == w)
|
|
{
|
|
d->time = CLOAKING_WALL_TIME - d->time;
|
|
break;
|
|
}
|
|
}
|
|
Assert( d!=NULL ); // Get John!
|
|
}
|
|
else if (w->state == WALL_DOOR_CLOSED) { //create new door
|
|
d = &CloakingWalls[Num_cloaking_walls];
|
|
d->time = 0;
|
|
if (Num_cloaking_walls >= MAX_CLOAKING_WALLS) { //no more!
|
|
Int3(); //ran out of cloaking wall slots
|
|
/* what is this _doing_ here?
|
|
w->type = WALL_CLOSED;
|
|
Walls[csegp->sides[Connectside].wall_num].type = WALL_CLOSED;
|
|
*/
|
|
return;
|
|
}
|
|
Num_cloaking_walls++;
|
|
}
|
|
else {
|
|
Int3(); //unexpected wall state
|
|
return;
|
|
}
|
|
|
|
w->state = WALL_DOOR_DECLOAKING;
|
|
|
|
// So that door can't be shot while opening
|
|
const auto &&csegp = vcsegptr(seg->children[side]);
|
|
auto Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
if (const auto &&w1 = wallptr(cwall_num))
|
|
w1->state = WALL_DOOR_DECLOAKING;
|
|
|
|
d->front_wallnum = seg->sides[side].wall_num;
|
|
d->back_wallnum = csegp->sides[Connectside].wall_num;
|
|
Assert(w->linked_wall == wall_none);
|
|
|
|
if ( Newdemo_state != ND_STATE_PLAYBACK ) {
|
|
const auto cp = compute_center_point_on_side(seg, side );
|
|
digi_link_sound_to_pos( SOUND_WALL_CLOAK_OFF, seg, side, cp, 0, F1_0 );
|
|
}
|
|
|
|
auto &df = d->front_ls;
|
|
auto &db = d->back_ls;
|
|
auto &s0_uvls = seg->sides[side].uvls;
|
|
auto &s1_uvls = csegp->sides[Connectside].uvls;
|
|
for (unsigned i = 0; i < 4; ++i)
|
|
{
|
|
df[i] = s0_uvls[i].l;
|
|
db[i] = s1_uvls[i].l;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------
|
|
// This function closes the specified door and restores the closed
|
|
// door texture. This is called when the animation is done
|
|
void wall_close_door_num(int door_num)
|
|
{
|
|
active_door *d;
|
|
int i;
|
|
|
|
d = &ActiveDoors[door_num];
|
|
|
|
range_for (const auto p, partial_const_range(d->front_wallnum, d->n_parts))
|
|
{
|
|
int side;
|
|
|
|
wall *const w = vwallptr(p);
|
|
|
|
const auto &&seg = vsegptridx(w->segnum);
|
|
side = w->sidenum;
|
|
w->state = WALL_DOOR_CLOSED;
|
|
|
|
Assert(seg->sides[side].wall_num != wall_none); //Closing door on illegal wall
|
|
|
|
const auto &&csegp = seg.absolute_sibling(seg->children[side]);
|
|
auto Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
if (const auto &&w1 = wallptr(cwall_num))
|
|
w1->state = WALL_DOOR_CLOSED;
|
|
|
|
wall_set_tmap_num(seg,side,csegp,Connectside,w->clip_num,0);
|
|
}
|
|
|
|
for (i=door_num;i<Num_open_doors;i++)
|
|
ActiveDoors[i] = ActiveDoors[i+1];
|
|
|
|
Num_open_doors--;
|
|
|
|
}
|
|
|
|
static uint8_t check_poke(const vcobjptr_t obj, const vcsegptr_t segnum,int side)
|
|
{
|
|
//note: don't let objects with zero size block door
|
|
if (!obj->size)
|
|
return 0;
|
|
return get_seg_masks(obj->pos, segnum, obj->size).sidemask & (1 << side); //pokes through side!
|
|
}
|
|
|
|
static int is_door_side_free(const vcsegptr_t seg, int side)
|
|
{
|
|
range_for (const auto &&obj, objects_in(seg))
|
|
{
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (obj->type == OBJ_WEAPON)
|
|
continue;
|
|
if (obj->type == OBJ_FIREBALL)
|
|
continue;
|
|
#endif
|
|
if (check_poke(obj,seg,side))
|
|
return 0; //not free
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//returns true of door in unobjstructed (& thus can close)
|
|
static int is_door_free(const vcsegptridx_t seg,int side)
|
|
{
|
|
if (!is_door_side_free(seg, side))
|
|
return 0;
|
|
const auto &&csegp = vcsegptr(seg->children[side]);
|
|
const auto &&Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
//go through each object in each of two segments, and see if
|
|
//it pokes into the connecting seg
|
|
return is_door_side_free(csegp, Connectside);
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
//-----------------------------------------------------------------
|
|
// Closes a door
|
|
void wall_close_door(const vsegptridx_t seg, int side)
|
|
{
|
|
active_door *d;
|
|
|
|
const auto wall_num = seg->sides[side].wall_num;
|
|
wall *const w = vwallptr(wall_num);
|
|
if ((w->state == WALL_DOOR_CLOSING) || //already closing
|
|
(w->state == WALL_DOOR_WAITING) || //open, waiting to close
|
|
(w->state == WALL_DOOR_CLOSED)) //closed
|
|
return;
|
|
|
|
if (!is_door_free(seg,side))
|
|
return;
|
|
|
|
if (w->state == WALL_DOOR_OPENING) { //reuse door
|
|
|
|
int i;
|
|
|
|
d = NULL;
|
|
|
|
for (i=0;i<Num_open_doors;i++) { //find door
|
|
|
|
d = &ActiveDoors[i];
|
|
|
|
if (d->front_wallnum[0]==wall_num || d->back_wallnum[0]==wall_num ||
|
|
(d->n_parts==2 && (d->front_wallnum[1]==wall_num || d->back_wallnum[1]==wall_num)))
|
|
break;
|
|
}
|
|
|
|
Assert(i<Num_open_doors); //didn't find door!
|
|
Assert( d!=NULL ); // Get John!
|
|
|
|
d->time = WallAnims[w->clip_num].play_time - d->time;
|
|
|
|
if (d->time < 0)
|
|
d->time = 0;
|
|
|
|
}
|
|
else { //create new door
|
|
Assert(w->state == WALL_DOOR_OPEN);
|
|
d = &ActiveDoors[Num_open_doors];
|
|
d->time = 0;
|
|
Num_open_doors++;
|
|
Assert( Num_open_doors < MAX_DOORS );
|
|
}
|
|
|
|
w->state = WALL_DOOR_CLOSING;
|
|
|
|
// So that door can't be shot while opening
|
|
const auto &&csegp = vcsegptr(seg->children[side]);
|
|
const auto &&Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
if (const auto &&w1 = wallptr(cwall_num))
|
|
w1->state = WALL_DOOR_CLOSING;
|
|
|
|
d->front_wallnum[0] = seg->sides[side].wall_num;
|
|
d->back_wallnum[0] = cwall_num;
|
|
if (Newdemo_state == ND_STATE_RECORDING) {
|
|
newdemo_record_door_opening(seg, side);
|
|
}
|
|
|
|
if (w->linked_wall != wall_none)
|
|
{
|
|
Int3(); //don't think we ever used linked walls
|
|
}
|
|
else
|
|
d->n_parts = 1;
|
|
|
|
|
|
if ( Newdemo_state != ND_STATE_PLAYBACK )
|
|
{
|
|
// NOTE THE LINK TO ABOVE!!!!
|
|
const auto cp = compute_center_point_on_side(seg, side );
|
|
if (WallAnims[w->clip_num].open_sound > -1 )
|
|
digi_link_sound_to_pos( WallAnims[w->clip_num].open_sound, seg, side, cp, 0, F1_0 );
|
|
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//-----------------------------------------------------------------
|
|
// Animates opening of a door.
|
|
// Called in the game loop.
|
|
void do_door_open(int door_num)
|
|
{
|
|
int p;
|
|
active_door *d;
|
|
|
|
// Assert(door_num != -1); //Trying to do_door_open on illegal door
|
|
if (door_num == -1)
|
|
return;
|
|
|
|
d = &ActiveDoors[door_num];
|
|
|
|
for (p=0;p<d->n_parts;p++) {
|
|
int side;
|
|
fix time_elapsed, time_total, one_frame;
|
|
int i, n;
|
|
|
|
wall *const w = vwallptr(d->front_wallnum[p]);
|
|
kill_stuck_objects(d->front_wallnum[p]);
|
|
kill_stuck_objects(d->back_wallnum[p]);
|
|
|
|
const auto &&seg = vsegptridx(w->segnum);
|
|
side = w->sidenum;
|
|
|
|
// Assert(seg->sides[side].wall_num != -1); //Trying to do_door_open on illegal wall
|
|
if (seg->sides[side].wall_num == wall_none)
|
|
{
|
|
con_printf(CON_URGENT, "Trying to do_door_open on illegal wall %i. Please check your level!",side);
|
|
continue;
|
|
}
|
|
|
|
const auto &&csegp = seg.absolute_sibling(seg->children[side]);
|
|
const auto &&Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
|
|
d->time += FrameTime;
|
|
|
|
time_elapsed = d->time;
|
|
n = WallAnims[w->clip_num].num_frames;
|
|
time_total = WallAnims[w->clip_num].play_time;
|
|
|
|
one_frame = time_total/n;
|
|
|
|
i = time_elapsed/one_frame;
|
|
|
|
if (i < n)
|
|
wall_set_tmap_num(seg,side,csegp,Connectside,w->clip_num,i);
|
|
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
const auto &&w1 = vwallptr(cwall_num);
|
|
if (i> n/2) {
|
|
w->flags |= WALL_DOOR_OPENED;
|
|
w1->flags |= WALL_DOOR_OPENED;
|
|
}
|
|
|
|
if (i >= n-1) {
|
|
wall_set_tmap_num(seg,side,csegp,Connectside,w->clip_num,n-1);
|
|
|
|
// If our door is not automatic just remove it from the list.
|
|
if (!(w->flags & WALL_DOOR_AUTO)) {
|
|
for (i=door_num;i<Num_open_doors;i++)
|
|
ActiveDoors[i] = ActiveDoors[i+1];
|
|
Num_open_doors--;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
w->state = WALL_DOOR_OPEN;
|
|
w1->state = WALL_DOOR_OPEN;
|
|
#endif
|
|
}
|
|
else {
|
|
|
|
w->state = WALL_DOOR_WAITING;
|
|
w1->state = WALL_DOOR_WAITING;
|
|
|
|
ActiveDoors[Num_open_doors].time = 0; //counts up
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// Animates and processes the closing of a door.
|
|
// Called from the game loop.
|
|
void do_door_close(int door_num)
|
|
{
|
|
int p;
|
|
active_door *d;
|
|
|
|
Assert(door_num != -1); //Trying to do_door_open on illegal door
|
|
|
|
d = &ActiveDoors[door_num];
|
|
|
|
auto &w0 = *vwallptr(d->front_wallnum[0]);
|
|
|
|
const auto &&wsegp = vsegptridx(w0.segnum);
|
|
|
|
//check for objects in doorway before closing
|
|
if (w0.flags & WALL_DOOR_AUTO)
|
|
if (!is_door_free(wsegp, w0.sidenum)) {
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
digi_kill_sound_linked_to_segment(w0.segnum, w0.sidenum, -1);
|
|
wall_open_door(wsegp, w0.sidenum); //re-open door
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
for (p=0;p<d->n_parts;p++) {
|
|
int side;
|
|
fix time_elapsed, time_total, one_frame;
|
|
int i, n;
|
|
|
|
auto &wp = *vwallptr(d->front_wallnum[p]);
|
|
|
|
const auto &seg = wsegp;
|
|
side = wp.sidenum;
|
|
|
|
if (seg->sides[side].wall_num == wall_none) {
|
|
return;
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
//if here, must be auto door
|
|
//don't assert here, because now we have triggers to close non-auto doors
|
|
Assert(wp.flags & WALL_DOOR_AUTO);
|
|
#endif
|
|
|
|
// Otherwise, close it.
|
|
const auto &&csegp = seg.absolute_sibling(seg->children[side]);
|
|
const auto &&Connectside = find_connect_side(seg, csegp);
|
|
Assert(Connectside != side_none);
|
|
|
|
|
|
if ( Newdemo_state != ND_STATE_PLAYBACK )
|
|
// NOTE THE LINK TO ABOVE!!
|
|
if (p==0) //only play one sound for linked doors
|
|
if ( d->time==0 ) { //first time
|
|
const auto cp = compute_center_point_on_side(seg, side );
|
|
if (WallAnims[wp.clip_num].close_sound > -1 )
|
|
digi_link_sound_to_pos(WallAnims[wp.clip_num].close_sound, seg, side, cp, 0, F1_0);
|
|
}
|
|
|
|
d->time += FrameTime;
|
|
|
|
time_elapsed = d->time;
|
|
n = WallAnims[wp.clip_num].num_frames;
|
|
time_total = WallAnims[wp.clip_num].play_time;
|
|
|
|
one_frame = time_total/n;
|
|
|
|
i = n-time_elapsed/one_frame-1;
|
|
|
|
const auto cwall_num = csegp->sides[Connectside].wall_num;
|
|
auto &w1 = *vwallptr(cwall_num);
|
|
if (i < n/2) {
|
|
wp.flags &= ~WALL_DOOR_OPENED;
|
|
w1.flags &= ~WALL_DOOR_OPENED;
|
|
}
|
|
|
|
// Animate door.
|
|
if (i > 0) {
|
|
wall_set_tmap_num(seg, side, csegp, Connectside, wp.clip_num, i);
|
|
|
|
wp.state = WALL_DOOR_CLOSING;
|
|
w1.state = WALL_DOOR_CLOSING;
|
|
|
|
ActiveDoors[Num_open_doors].time = 0; //counts up
|
|
|
|
} else
|
|
wall_close_door_num(door_num);
|
|
}
|
|
}
|
|
|
|
template <typename F>
|
|
static void wall_illusion_op(const vsegptridx_t seg, unsigned side, F op)
|
|
{
|
|
const auto wall0 = seg->sides[side].wall_num;
|
|
if (wall0 == wall_none)
|
|
return;
|
|
const auto &&csegp = vcsegptr(seg->children[side]);
|
|
const auto &&cside = find_connect_side(seg, csegp);
|
|
if (cside == side_none)
|
|
{
|
|
assert(cside != side_none);
|
|
return;
|
|
}
|
|
const auto wall1 = csegp->sides[cside].wall_num;
|
|
op(wall0);
|
|
op(wall1);
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// Turns off an illusionary wall (This will be used primarily for
|
|
// wall switches or triggers that can turn on/off illusionary walls.)
|
|
void wall_illusion_off(const vsegptridx_t seg, int side)
|
|
{
|
|
const auto op = [](const wallnum_t wall_num) {
|
|
vwallptr(wall_num)->flags |= WALL_ILLUSION_OFF;
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
kill_stuck_objects(wall_num);
|
|
#endif
|
|
};
|
|
wall_illusion_op(seg, side, op);
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// Turns on an illusionary wall (This will be used primarily for
|
|
// wall switches or triggers that can turn on/off illusionary walls.)
|
|
void wall_illusion_on(const vsegptridx_t seg, int side)
|
|
{
|
|
const auto op = [](const wallnum_t wall_num) {
|
|
vwallptr(wall_num)->flags &= ~WALL_ILLUSION_OFF;
|
|
};
|
|
wall_illusion_op(seg, side, op);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Allowed to open the normally locked special boss door if in multiplayer mode.
|
|
static int special_boss_opening_allowed(segnum_t segnum, int sidenum)
|
|
{
|
|
if (Game_mode & GM_MULTI)
|
|
return (Current_level_num == BOSS_LOCKED_DOOR_LEVEL) && (segnum == BOSS_LOCKED_DOOR_SEG) && (sidenum == BOSS_LOCKED_DOOR_SIDE);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// Determines what happens when a wall is shot
|
|
//returns info about wall. see wall.h for codes
|
|
//obj is the object that hit...either a weapon or the player himself
|
|
//playernum is the number the player who hit the wall or fired the weapon,
|
|
//or -1 if a robot fired the weapon
|
|
wall_hit_process_t wall_hit_process(const vsegptridx_t seg, int side, fix damage, int playernum, const vobjptr_t obj)
|
|
{
|
|
fix show_message;
|
|
|
|
// If it is not a "wall" then just return.
|
|
if ( seg->sides[side].wall_num == wall_none )
|
|
return wall_hit_process_t::WHP_NOT_SPECIAL;
|
|
|
|
wall *const w = vwallptr(seg->sides[side].wall_num);
|
|
|
|
if ( Newdemo_state == ND_STATE_RECORDING )
|
|
newdemo_record_wall_hit_process( seg, side, damage, playernum );
|
|
|
|
if (w->type == WALL_BLASTABLE) {
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (obj->ctype.laser_info.parent_type == OBJ_PLAYER)
|
|
#endif
|
|
wall_damage(seg, side, damage);
|
|
return wall_hit_process_t::WHP_BLASTABLE;
|
|
}
|
|
|
|
if (playernum != Player_num) //return if was robot fire
|
|
return wall_hit_process_t::WHP_NOT_SPECIAL;
|
|
|
|
Assert( playernum > -1 );
|
|
|
|
// Determine whether player is moving forward. If not, don't say negative
|
|
// messages because he probably didn't intentionally hit the door.
|
|
if (obj->type == OBJ_PLAYER)
|
|
show_message = (vm_vec_dot(obj->orient.fvec, obj->mtype.phys_info.velocity) > 0);
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
else if (obj->type == OBJ_ROBOT)
|
|
show_message = 0;
|
|
else if ((obj->type == OBJ_WEAPON) && (obj->ctype.laser_info.parent_type == OBJ_ROBOT))
|
|
show_message = 0;
|
|
#endif
|
|
else
|
|
show_message = 1;
|
|
|
|
/* Set key_color only after the type matches, since TXT_* are macros
|
|
* that trigger a load from memory. Use operator,() to suppress the
|
|
* truth test on the second branch since the compiler cannot prove
|
|
* that the loaded value will always be non-null.
|
|
*/
|
|
const char *key_color;
|
|
if (
|
|
(w->keys == KEY_BLUE && (key_color = TXT_BLUE, true)) ||
|
|
(w->keys == KEY_GOLD && (key_color = TXT_YELLOW, true)) ||
|
|
(w->keys == KEY_RED && (key_color = TXT_RED, true))
|
|
)
|
|
{
|
|
const auto &objp = get_local_plrobj();
|
|
const auto &player_info = objp.ctype.player_info;
|
|
if (!(player_info.powerup_flags & static_cast<PLAYER_FLAG>(w->keys)))
|
|
{
|
|
static_assert(KEY_BLUE == static_cast<unsigned>(PLAYER_FLAGS_BLUE_KEY), "BLUE key flag mismatch");
|
|
static_assert(KEY_GOLD == static_cast<unsigned>(PLAYER_FLAGS_GOLD_KEY), "GOLD key flag mismatch");
|
|
static_assert(KEY_RED == static_cast<unsigned>(PLAYER_FLAGS_RED_KEY), "RED key flag mismatch");
|
|
if (show_message)
|
|
HUD_init_message(HM_DEFAULT, "%s %s",key_color,TXT_ACCESS_DENIED);
|
|
return wall_hit_process_t::WHP_NO_KEY;
|
|
}
|
|
}
|
|
|
|
if (w->type == WALL_DOOR)
|
|
{
|
|
if ((w->flags & WALL_DOOR_LOCKED ) && !(special_boss_opening_allowed(seg, side)) ) {
|
|
if (show_message)
|
|
HUD_init_message_literal(HM_DEFAULT, TXT_CANT_OPEN_DOOR);
|
|
return wall_hit_process_t::WHP_NO_KEY;
|
|
}
|
|
else {
|
|
if (w->state != WALL_DOOR_OPENING)
|
|
{
|
|
wall_open_door(seg, side);
|
|
if (Game_mode & GM_MULTI)
|
|
{
|
|
int flags;
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
flags = 0;
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
flags = w->flags;
|
|
#endif
|
|
multi_send_door_open(seg, side,flags);
|
|
}
|
|
}
|
|
return wall_hit_process_t::WHP_DOOR;
|
|
|
|
}
|
|
}
|
|
return wall_hit_process_t::WHP_NOT_SPECIAL; //default is treat like normal wall
|
|
}
|
|
|
|
//-----------------------------------------------------------------
|
|
// Opens doors/destroys wall/shuts off triggers.
|
|
void wall_toggle(const vsegptridx_t segp, unsigned side)
|
|
{
|
|
if (side >= MAX_SIDES_PER_SEGMENT)
|
|
{
|
|
#ifndef NDEBUG
|
|
Warning("Can't toggle side %u of segment %d (%u)!\n", side, static_cast<segnum_t>(segp), Highest_segment_index);
|
|
#endif
|
|
return;
|
|
}
|
|
auto wall_num = segp->sides[side].wall_num;
|
|
if (wall_num == wall_none) {
|
|
return;
|
|
}
|
|
|
|
if ( Newdemo_state == ND_STATE_RECORDING )
|
|
newdemo_record_wall_toggle(segp, side);
|
|
|
|
wall *const w = vwallptr(wall_num);
|
|
if (w->type == WALL_BLASTABLE)
|
|
wall_destroy(segp, side);
|
|
|
|
if (w->type == WALL_DOOR && w->state == WALL_DOOR_CLOSED)
|
|
wall_open_door(segp, side);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------
|
|
// Tidy up Walls array for load/save purposes.
|
|
void reset_walls()
|
|
{
|
|
range_for (auto &w, partial_range(Walls, Num_walls, MAX_WALLS))
|
|
{
|
|
w.type = WALL_NORMAL;
|
|
w.flags = 0;
|
|
w.hps = 0;
|
|
w.trigger = -1;
|
|
w.clip_num = -1;
|
|
}
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
static void do_cloaking_wall_frame(int cloaking_wall_num)
|
|
{
|
|
cloaking_wall *d;
|
|
sbyte old_cloak; // for demo recording
|
|
|
|
if ( Newdemo_state==ND_STATE_PLAYBACK ) return;
|
|
|
|
d = &CloakingWalls[cloaking_wall_num];
|
|
wall *const wfront = vwallptr(d->front_wallnum);
|
|
wall *const wback = wallptr(d->back_wallnum);
|
|
|
|
old_cloak = wfront->cloak_value;
|
|
|
|
d->time += FrameTime;
|
|
|
|
if (d->time > CLOAKING_WALL_TIME) {
|
|
int i;
|
|
|
|
wfront->type = WALL_OPEN;
|
|
wfront->state = WALL_DOOR_CLOSED; //why closed? why not?
|
|
if (wback) {
|
|
wback->type = WALL_OPEN;
|
|
wback->state = WALL_DOOR_CLOSED; //why closed? why not?
|
|
}
|
|
|
|
for (i=cloaking_wall_num;i<Num_cloaking_walls;i++)
|
|
CloakingWalls[i] = CloakingWalls[i+1];
|
|
Num_cloaking_walls--;
|
|
|
|
}
|
|
else if (d->time > CLOAKING_WALL_TIME/2) {
|
|
int old_type=wfront->type;
|
|
|
|
wfront->cloak_value = ((d->time - CLOAKING_WALL_TIME/2) * (GR_FADE_LEVELS-2)) / (CLOAKING_WALL_TIME/2);
|
|
if (wback)
|
|
wback->cloak_value = wfront->cloak_value;
|
|
|
|
if (old_type != WALL_CLOAKED) { //just switched
|
|
int i;
|
|
|
|
wfront->type = WALL_CLOAKED;
|
|
if (wback)
|
|
wback->type = WALL_CLOAKED;
|
|
|
|
for (i=0;i<4;i++) {
|
|
Segments[wfront->segnum].sides[wfront->sidenum].uvls[i].l = d->front_ls[i];
|
|
if (wback)
|
|
Segments[wback->segnum].sides[wback->sidenum].uvls[i].l = d->back_ls[i];
|
|
}
|
|
}
|
|
}
|
|
else { //fading out
|
|
fix light_scale;
|
|
int i;
|
|
|
|
light_scale = fixdiv(CLOAKING_WALL_TIME/2-d->time,CLOAKING_WALL_TIME/2);
|
|
|
|
for (i=0;i<4;i++) {
|
|
Segments[wfront->segnum].sides[wfront->sidenum].uvls[i].l = fixmul(d->front_ls[i],light_scale);
|
|
if (wback)
|
|
Segments[wback->segnum].sides[wback->sidenum].uvls[i].l = fixmul(d->back_ls[i],light_scale);
|
|
}
|
|
}
|
|
|
|
// check if the actual cloak_value changed in this frame to prevent redundant recordings and wasted bytes
|
|
if ( Newdemo_state == ND_STATE_RECORDING && (wfront->cloak_value != old_cloak || d->time == FrameTime) )
|
|
newdemo_record_cloaking_wall(d->front_wallnum, d->back_wallnum, wfront->type, wfront->state, wfront->cloak_value, Segments[wfront->segnum].sides[wfront->sidenum].uvls[0].l, Segments[wfront->segnum].sides[wfront->sidenum].uvls[1].l, Segments[wfront->segnum].sides[wfront->sidenum].uvls[2].l, Segments[wfront->segnum].sides[wfront->sidenum].uvls[3].l);
|
|
|
|
}
|
|
|
|
static void do_decloaking_wall_frame(int cloaking_wall_num)
|
|
{
|
|
cloaking_wall *d;
|
|
sbyte old_cloak; // for demo recording
|
|
|
|
if ( Newdemo_state==ND_STATE_PLAYBACK ) return;
|
|
|
|
d = &CloakingWalls[cloaking_wall_num];
|
|
wall *const wfront = vwallptr(d->front_wallnum);
|
|
wall *const wback = wallptr(d->back_wallnum);
|
|
|
|
old_cloak = wfront->cloak_value;
|
|
|
|
d->time += FrameTime;
|
|
|
|
if (d->time > CLOAKING_WALL_TIME) {
|
|
int i;
|
|
|
|
wfront->state = WALL_DOOR_CLOSED;
|
|
if (wback)
|
|
wback->state = WALL_DOOR_CLOSED;
|
|
|
|
for (i=0;i<4;i++) {
|
|
Segments[wfront->segnum].sides[wfront->sidenum].uvls[i].l = d->front_ls[i];
|
|
if (wback)
|
|
Segments[wback->segnum].sides[wback->sidenum].uvls[i].l = d->back_ls[i];
|
|
}
|
|
|
|
for (i=cloaking_wall_num;i<Num_cloaking_walls;i++)
|
|
CloakingWalls[i] = CloakingWalls[i+1];
|
|
Num_cloaking_walls--;
|
|
|
|
}
|
|
else if (d->time > CLOAKING_WALL_TIME/2) { //fading in
|
|
fix light_scale;
|
|
int i;
|
|
|
|
wfront->type = wback->type = WALL_CLOSED;
|
|
|
|
light_scale = fixdiv(d->time-CLOAKING_WALL_TIME/2,CLOAKING_WALL_TIME/2);
|
|
|
|
for (i=0;i<4;i++) {
|
|
Segments[wfront->segnum].sides[wfront->sidenum].uvls[i].l = fixmul(d->front_ls[i],light_scale);
|
|
if (wback)
|
|
Segments[wback->segnum].sides[wback->sidenum].uvls[i].l = fixmul(d->back_ls[i],light_scale);
|
|
}
|
|
}
|
|
else { //cloaking in
|
|
wfront->cloak_value = ((CLOAKING_WALL_TIME/2 - d->time) * (GR_FADE_LEVELS-2)) / (CLOAKING_WALL_TIME/2);
|
|
wfront->type = WALL_CLOAKED;
|
|
if (wback) {
|
|
wback->cloak_value = wfront->cloak_value;
|
|
wback->type = WALL_CLOAKED;
|
|
}
|
|
}
|
|
|
|
// check if the actual cloak_value changed in this frame to prevent redundant recordings and wasted bytes
|
|
if ( Newdemo_state == ND_STATE_RECORDING && (wfront->cloak_value != old_cloak || d->time == FrameTime) )
|
|
newdemo_record_cloaking_wall(d->front_wallnum, d->back_wallnum, wfront->type, wfront->state, wfront->cloak_value, Segments[wfront->segnum].sides[wfront->sidenum].uvls[0].l, Segments[wfront->segnum].sides[wfront->sidenum].uvls[1].l, Segments[wfront->segnum].sides[wfront->sidenum].uvls[2].l, Segments[wfront->segnum].sides[wfront->sidenum].uvls[3].l);
|
|
|
|
}
|
|
#endif
|
|
|
|
void wall_frame_process()
|
|
{
|
|
int i;
|
|
|
|
for (i=0;i<Num_open_doors;i++) {
|
|
active_door *d;
|
|
d = &ActiveDoors[i];
|
|
wall *const w = vwallptr(d->front_wallnum[0]);
|
|
|
|
if (w->state == WALL_DOOR_OPENING)
|
|
do_door_open(i);
|
|
else if (w->state == WALL_DOOR_CLOSING)
|
|
do_door_close(i);
|
|
else if (w->state == WALL_DOOR_WAITING) {
|
|
d->time += FrameTime;
|
|
|
|
// set flags to fix occasional netgame problem where door is waiting to close but open flag isn't set
|
|
w->flags |= WALL_DOOR_OPENED;
|
|
if (wall *const w1 = wallptr(d->back_wallnum[0]))
|
|
w1->flags |= WALL_DOOR_OPENED;
|
|
|
|
if (d->time > DOOR_WAIT_TIME)
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (is_door_free(vcsegptridx(w->segnum), w->sidenum))
|
|
#endif
|
|
{
|
|
w->state = WALL_DOOR_CLOSING;
|
|
d->time = 0;
|
|
}
|
|
}
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
else if (w->state == WALL_DOOR_CLOSED || w->state == WALL_DOOR_OPEN) {
|
|
//this shouldn't happen. if the wall is in one of these states,
|
|
//there shouldn't be an activedoor entry for it. So we'll kill
|
|
//the activedoor entry. Tres simple.
|
|
int t;
|
|
Int3(); //a bad thing has happened, but I'll try to fix it up
|
|
for (t=i;t<Num_open_doors;t++)
|
|
ActiveDoors[t] = ActiveDoors[t+1];
|
|
Num_open_doors--;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
for (i=0;i<Num_cloaking_walls;i++) {
|
|
cloaking_wall *d;
|
|
|
|
d = &CloakingWalls[i];
|
|
wall *const w = vwallptr(d->front_wallnum);
|
|
|
|
if (w->state == WALL_DOOR_CLOAKING)
|
|
do_cloaking_wall_frame(i);
|
|
else if (w->state == WALL_DOOR_DECLOAKING)
|
|
do_decloaking_wall_frame(i);
|
|
#ifdef _DEBUG
|
|
else
|
|
Int3(); //unexpected wall state
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static unsigned Num_stuck_objects;
|
|
static array<stuckobj, 32> Stuck_objects;
|
|
|
|
// An object got stuck in a door (like a flare).
|
|
// Add global entry.
|
|
void add_stuck_object(const vobjptridx_t objp, const vsegptr_t segp, int sidenum)
|
|
{
|
|
const auto wallnum = segp->sides[sidenum].wall_num;
|
|
if (wallnum != wall_none)
|
|
{
|
|
if (vwallptr(wallnum)->flags & WALL_BLASTED)
|
|
objp->flags |= OF_SHOULD_BE_DEAD;
|
|
range_for (auto &i, Stuck_objects)
|
|
{
|
|
if (i.wallnum == wall_none)
|
|
{
|
|
i.wallnum = wallnum;
|
|
i.objnum = objp;
|
|
i.signature = objp->signature;
|
|
Num_stuck_objects++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Look at the list of stuck objects, clean up in case an object has gone away, but not been removed here.
|
|
// Removes up to one/frame.
|
|
void remove_obsolete_stuck_objects(void)
|
|
{
|
|
int objnum;
|
|
|
|
objnum = d_tick_count % Stuck_objects.size();
|
|
|
|
if (Stuck_objects[objnum].wallnum != wall_none)
|
|
if ((Stuck_objects[objnum].wallnum == 0) || (vcobjptr(Stuck_objects[objnum].objnum)->signature != Stuck_objects[objnum].signature))
|
|
{
|
|
Num_stuck_objects--;
|
|
Stuck_objects[objnum].wallnum = wall_none;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
// --------------------------------------------------------------------------------------------------
|
|
// Look at the list of stuck objects, clean up in case an object has gone away, but not been removed here.
|
|
// Removes up to one/frame.
|
|
void remove_obsolete_stuck_objects(void)
|
|
{
|
|
int objnum;
|
|
|
|
// Safety and efficiency code. If no stuck objects, should never get inside the IF, but this is faster.
|
|
if (!Num_stuck_objects)
|
|
return;
|
|
|
|
objnum = d_tick_count % Stuck_objects.size();
|
|
|
|
if (Stuck_objects[objnum].wallnum != wall_none)
|
|
if (vcwallptr(Stuck_objects[objnum].wallnum)->state != WALL_DOOR_CLOSED ||
|
|
vcobjptr(Stuck_objects[objnum].objnum)->signature != Stuck_objects[objnum].signature)
|
|
{
|
|
Num_stuck_objects--;
|
|
Stuck_objects[objnum].wallnum = wall_none;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------------------------------
|
|
// Door with wall index wallnum is opening, kill all objects stuck in it.
|
|
void kill_stuck_objects(int wallnum)
|
|
{
|
|
if (wallnum < 0 || Num_stuck_objects == 0) {
|
|
return;
|
|
}
|
|
|
|
unsigned n = 0;
|
|
|
|
range_for (auto &i, Stuck_objects)
|
|
{
|
|
if (i.wallnum == wallnum)
|
|
{
|
|
i.wallnum = wall_none;
|
|
const auto &&objp = vobjptr(i.objnum);
|
|
if (objp->type == OBJ_WEAPON) {
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
#define DXX_WEAPON_LIFELEFT F1_0/4
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
#define DXX_WEAPON_LIFELEFT F1_0/8
|
|
#endif
|
|
objp->lifeleft = DXX_WEAPON_LIFELEFT;
|
|
}
|
|
}
|
|
else if (i.wallnum != wall_none)
|
|
{
|
|
++n;
|
|
}
|
|
}
|
|
Num_stuck_objects = n;
|
|
// Ok, this is awful, but we need to do things whenever a door opens/closes/disappears, etc.
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
flush_fcd_cache();
|
|
#endif
|
|
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
// -----------------------------------------------------------------------------------
|
|
// Initialize stuck objects array. Called at start of level
|
|
void init_stuck_objects(void)
|
|
{
|
|
range_for (auto &i, Stuck_objects)
|
|
i.wallnum = wall_none;
|
|
Num_stuck_objects = 0;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
// Clear out all stuck objects. Called for a new ship
|
|
void clear_stuck_objects(void)
|
|
{
|
|
range_for (auto &i, Stuck_objects)
|
|
{
|
|
if (i.wallnum != wall_none)
|
|
{
|
|
const auto &&objp = vobjptr(i.objnum);
|
|
if (objp->type == OBJ_WEAPON && get_weapon_id(objp) == weapon_id_type::FLARE_ID)
|
|
objp->lifeleft = F1_0/8;
|
|
|
|
i.wallnum = wall_none;
|
|
Num_stuck_objects--;
|
|
}
|
|
}
|
|
|
|
Assert(Num_stuck_objects == 0);
|
|
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
#define MAX_BLAST_GLASS_DEPTH 5
|
|
|
|
static void bng_process_segment(const object &objp, fix damage, const vsegptridx_t segp, int depth, visited_segment_bitarray_t &visited)
|
|
{
|
|
visited[segp] = true;
|
|
int i, sidenum;
|
|
|
|
if (depth > MAX_BLAST_GLASS_DEPTH)
|
|
return;
|
|
|
|
depth++;
|
|
|
|
for (sidenum=0; sidenum<MAX_SIDES_PER_SEGMENT; sidenum++) {
|
|
int tm;
|
|
fix dist;
|
|
|
|
// Process only walls which have glass.
|
|
if ((tm=segp->sides[sidenum].tmap_num2) != 0) {
|
|
int ec, db;
|
|
|
|
tm &= 0x3fff; //tm without flags
|
|
|
|
if ((((ec=TmapInfo[tm].eclip_num)!=-1) && ((db=Effects[ec].dest_bm_num)!=-1 && !(Effects[ec].flags&EF_ONE_SHOT))) || (ec==-1 && (TmapInfo[tm].destroyed!=-1))) {
|
|
const auto pnt = compute_center_point_on_side(segp, sidenum);
|
|
dist = vm_vec_dist_quick(pnt, objp.pos);
|
|
if (dist < damage/2) {
|
|
dist = find_connected_distance(pnt, segp, objp.pos, segp.absolute_sibling(objp.segnum), MAX_BLAST_GLASS_DEPTH, WID_RENDPAST_FLAG);
|
|
if ((dist > 0) && (dist < damage/2))
|
|
check_effect_blowup(segp, sidenum, pnt, vcobjptr(objp.ctype.laser_info.parent_num)->ctype.laser_info, 1, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i=0; i<MAX_SIDES_PER_SEGMENT; i++) {
|
|
auto segnum = segp->children[i];
|
|
|
|
if (segnum != segment_none) {
|
|
if (!visited[segnum]) {
|
|
if (WALL_IS_DOORWAY(segp, i) & WID_FLY_FLAG) {
|
|
bng_process_segment(objp, damage, segp.absolute_sibling(segnum), depth, visited);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------------
|
|
// objp is going to detonate
|
|
// blast nearby monitors, lights, maybe other things
|
|
void blast_nearby_glass(const object &objp, fix damage)
|
|
{
|
|
visited_segment_bitarray_t visited;
|
|
bng_process_segment(objp, damage, vsegptridx(objp.segnum), 0, visited);
|
|
}
|
|
|
|
struct d1wclip
|
|
{
|
|
wclip *wc;
|
|
d1wclip(wclip &w) : wc(&w) {}
|
|
};
|
|
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(d1wclip, dwc, (dwc.wc->play_time, dwc.wc->num_frames, dwc.wc->d1_frames, dwc.wc->open_sound, dwc.wc->close_sound, dwc.wc->flags, dwc.wc->filename, serial::pad<1>()));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(d1wclip, 26 + (sizeof(int16_t) * MAX_CLIP_FRAMES_D1));
|
|
#endif
|
|
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(wclip, wc, (wc.play_time, wc.num_frames, wc.frames, wc.open_sound, wc.close_sound, wc.flags, wc.filename, serial::pad<1>()));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(wclip, 26 + (sizeof(int16_t) * MAX_CLIP_FRAMES));
|
|
|
|
/*
|
|
* reads a wclip structure from a PHYSFS_File
|
|
*/
|
|
void wclip_read(PHYSFS_File *fp, wclip &wc)
|
|
{
|
|
PHYSFSX_serialize_read(fp, wc);
|
|
}
|
|
|
|
#if 0
|
|
void wclip_write(PHYSFS_File *fp, const wclip &wc)
|
|
{
|
|
PHYSFSX_serialize_write(fp, wc);
|
|
}
|
|
#endif
|
|
|
|
struct wrap_v16_wall
|
|
{
|
|
const wall *w;
|
|
wrap_v16_wall(const wall &t) : w(&t) {}
|
|
};
|
|
|
|
#define _SERIAL_UDT_WALL_V16_MEMBERS(P) (P type, P flags, P hps, P trigger, P clip_num, P keys)
|
|
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(v16_wall, w, _SERIAL_UDT_WALL_V16_MEMBERS(w.));
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(wrap_v16_wall, w, _SERIAL_UDT_WALL_V16_MEMBERS(w.w->));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(wrap_v16_wall, 9);
|
|
|
|
/*
|
|
* reads a v16_wall structure from a PHYSFS_File
|
|
*/
|
|
void v16_wall_read(PHYSFS_File *fp, v16_wall &w)
|
|
{
|
|
PHYSFSX_serialize_read(fp, w);
|
|
}
|
|
|
|
struct wrap_v19_wall
|
|
{
|
|
const wall *w;
|
|
wrap_v19_wall(const wall &t) : w(&t) {}
|
|
};
|
|
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(v19_wall, w, (w.segnum, serial::pad<2>(), w.sidenum, w.type, w.flags, w.hps, w.trigger, w.clip_num, w.keys, w.linked_wall));
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(wrap_v19_wall, w, (w.w->segnum, serial::pad<2>(), w.w->sidenum, serial::pad<3>(), w.w->type, w.w->flags, w.w->hps, w.w->trigger, w.w->clip_num, w.w->keys, w.w->linked_wall, serial::pad<2>()));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(v19_wall, 21);
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(wrap_v19_wall, 21);
|
|
|
|
/*
|
|
* reads a v19_wall structure from a PHYSFS_File
|
|
*/
|
|
void v19_wall_read(PHYSFS_File *fp, v19_wall &w)
|
|
{
|
|
PHYSFSX_serialize_read(fp, w);
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_I)
|
|
#define _SERIAL_UDT_WALL_D2X_MEMBERS serial::pad<2>()
|
|
#elif defined(DXX_BUILD_DESCENT_II)
|
|
#define _SERIAL_UDT_WALL_D2X_MEMBERS w.controlling_trigger, w.cloak_value
|
|
#endif
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(wall, w, (w.segnum, serial::pad<2>(), w.sidenum, serial::pad<3>(), w.hps, w.linked_wall, serial::pad<2>(), w.type, w.flags, w.state, w.trigger, w.clip_num, w.keys, _SERIAL_UDT_WALL_D2X_MEMBERS));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(wall, 24);
|
|
|
|
/*
|
|
* reads a wall structure from a PHYSFS_File
|
|
*/
|
|
void wall_read(PHYSFS_File *fp, wall &w)
|
|
{
|
|
PHYSFSX_serialize_read(fp, w);
|
|
}
|
|
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(active_door, d, (d.n_parts, d.front_wallnum, d.back_wallnum, d.time));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(active_door, 16);
|
|
|
|
/*
|
|
* reads an active_door structure from a PHYSFS_File
|
|
*/
|
|
void active_door_read(PHYSFS_File *fp, active_door &ad)
|
|
{
|
|
PHYSFSX_serialize_read(fp, ad);
|
|
}
|
|
|
|
void active_door_write(PHYSFS_File *fp, const active_door &ad)
|
|
{
|
|
PHYSFSX_serialize_write(fp, ad);
|
|
}
|
|
|
|
void wall_write(PHYSFS_File *fp, const wall &w, short version)
|
|
{
|
|
if (version <= 16)
|
|
PHYSFSX_serialize_write<wrap_v16_wall>(fp, w);
|
|
else if (version <= 19)
|
|
PHYSFSX_serialize_write<wrap_v19_wall>(fp, w);
|
|
else
|
|
PHYSFSX_serialize_write(fp, w);
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
DEFINE_SERIAL_UDT_TO_MESSAGE(cloaking_wall, cw, (cw.front_wallnum, cw.back_wallnum, cw.front_ls, cw.back_ls, cw.time));
|
|
ASSERT_SERIAL_UDT_MESSAGE_SIZE(cloaking_wall, 40);
|
|
|
|
void cloaking_wall_read(cloaking_wall &cw, PHYSFS_File *fp)
|
|
{
|
|
PHYSFSX_serialize_read(fp, cw);
|
|
}
|
|
|
|
void cloaking_wall_write(const cloaking_wall &cw, PHYSFS_File *fp)
|
|
{
|
|
PHYSFSX_serialize_write(fp, cw);
|
|
}
|
|
#endif
|