mirror of
https://github.com/ZDoom/Raze.git
synced 2024-12-13 22:21:14 +00:00
e302c45a0b
This reverts commit 29da39dda0
.
Something in here was wrong, so back to the start with it.
The positive effect was too small anyway.
2738 lines
69 KiB
C++
2738 lines
69 KiB
C++
//-------------------------------------------------------------------------
|
|
/*
|
|
Copyright (C) 1997, 2005 - 3D Realms Entertainment
|
|
|
|
This file is part of Shadow Warrior version 1.2
|
|
|
|
Shadow Warrior is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
Original Source: 1997 - Frank Maddin and Jim Norwood
|
|
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
|
|
*/
|
|
//-------------------------------------------------------------------------
|
|
#include "ns.h"
|
|
#include "build.h"
|
|
|
|
#include "names2.h"
|
|
#include "panel.h"
|
|
#include "misc.h"
|
|
#include "tags.h"
|
|
#include "sector.h"
|
|
#include "player.h"
|
|
#include "weapon.h"
|
|
#include "jtags.h"
|
|
|
|
#include "network.h"
|
|
|
|
#include "break.h"
|
|
#include "misc.h"
|
|
#include "sprite.h"
|
|
#include "light.h"
|
|
#include "gstrings.h"
|
|
#include "secrets.h"
|
|
|
|
BEGIN_SW_NS
|
|
|
|
#define LAVASIZ 128
|
|
#define LAVALOGSIZ 7
|
|
#define LAVAMAXDROPS 32
|
|
#define DEFAULT_DOOR_SPEED 800
|
|
|
|
enum
|
|
{
|
|
SINE_FLOOR = (1 << 0),
|
|
SINE_CEILING = (1 << 1),
|
|
SINE_SLOPED = BIT(3),
|
|
};
|
|
|
|
ANIMATOR DoGrating;
|
|
void DoPlayerBeginForceJump(PLAYER*);
|
|
|
|
sectortype* FindNextSectorByTag(sectortype* sect, int tag);
|
|
short LevelSecrets;
|
|
bool TestVatorMatchActive(short match);
|
|
bool TestSpikeMatchActive(short match);
|
|
bool TestRotatorMatchActive(short match);
|
|
bool TestSlidorMatchActive(short match);
|
|
int PlayerCheckDeath(PLAYER*, DSWActor*);
|
|
void DoVatorOperate(PLAYER*, sectortype*);
|
|
void DoVatorMatch(PLAYER* pp, short match);
|
|
void DoRotatorOperate(PLAYER*, sectortype*);
|
|
void DoRotatorMatch(PLAYER* pp, short match, bool);
|
|
void DoSlidorOperate(PLAYER*, sectortype*);
|
|
void DoSlidorMatch(PLAYER* pp, short match, bool);
|
|
|
|
void KillMatchingCrackSprites(short match);
|
|
int DoTrapReset(short match);
|
|
int DoTrapMatch(short match);
|
|
|
|
PLAYER* GlobPlayerP;
|
|
|
|
ANIM Anim[MAXANIM];
|
|
short AnimCnt = 0;
|
|
|
|
SINE_WAVE_FLOOR SineWaveFloor[MAX_SINE_WAVE][21];
|
|
SINE_WALL SineWall[MAX_SINE_WALL][MAX_SINE_WALL_POINTS];
|
|
SPRING_BOARD SpringBoard[20];
|
|
|
|
void SetSectorWallBits(sectortype* sect, int bit_mask, bool set_sectwall, bool set_nextwall)
|
|
{
|
|
auto start_wall = sect->firstWall();
|
|
auto wall_num = start_wall;
|
|
|
|
do
|
|
{
|
|
if (set_sectwall)
|
|
wall_num->extra |= bit_mask;
|
|
|
|
if (set_nextwall)
|
|
{
|
|
if (wall_num->twoSided())
|
|
wall_num->nextWall()->extra |= bit_mask;
|
|
}
|
|
|
|
wall_num = wall_num->point2Wall();
|
|
}
|
|
while (wall_num != start_wall);
|
|
|
|
}
|
|
|
|
void WallSetupDontMove(void)
|
|
{
|
|
walltype* wallp;
|
|
|
|
SWStatIterator it(STAT_WALL_DONT_MOVE_UPPER);
|
|
while (auto iActor = it.Next())
|
|
{
|
|
SWStatIterator it1(STAT_WALL_DONT_MOVE_LOWER);
|
|
while (auto jActor = it1.Next())
|
|
{
|
|
if (iActor->spr.lotag == jActor->spr.lotag)
|
|
{
|
|
for(auto& wal : wall)
|
|
{
|
|
if (wal.pos.X < jActor->spr.pos.X && wal.pos.X > iActor->spr.pos.X && wal.pos.Y < jActor->spr.pos.Y && wal.pos.Y > iActor->spr.pos.Y)
|
|
{
|
|
wal.extra |= WALLFX_DONT_MOVE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void WallSetupLoop(walltype* wp, int16_t lotag, int16_t extra)
|
|
{
|
|
// set first wall
|
|
{
|
|
wp->extra |= extra;
|
|
|
|
if (wp->twoSided())
|
|
wp->nextWall()->extra |= extra;
|
|
}
|
|
|
|
// Travel all the way around loop setting wall bits
|
|
for (auto wall_num = wp->point2Wall();
|
|
wall_num->lotag != lotag;
|
|
wall_num = wall_num->point2Wall())
|
|
{
|
|
wall_num->extra |= extra;
|
|
if (wall_num->twoSided())
|
|
wall_num->nextWall()->extra |= extra;
|
|
}
|
|
}
|
|
|
|
void WallSetup(void)
|
|
{
|
|
short NextSineWall = 0;
|
|
|
|
WallSetupDontMove();
|
|
|
|
memset(SineWall, 0, sizeof(SineWall));
|
|
|
|
extern int x_min_bound, y_min_bound, x_max_bound, y_max_bound;
|
|
|
|
for (auto& wal : wall)
|
|
{
|
|
if (wal.picnum == FAF_PLACE_MIRROR_PIC)
|
|
wal.picnum = FAF_MIRROR_PIC;
|
|
|
|
if (wal.picnum == FAF_PLACE_MIRROR_PIC+1)
|
|
wal.picnum = FAF_MIRROR_PIC+1;
|
|
|
|
// this overwrites the lotag so it needs to be called LAST - its down there
|
|
// SetupWallForBreak(wp);
|
|
|
|
switch (wal.lotag)
|
|
{
|
|
case TAG_WALL_LOOP_DONT_SPIN:
|
|
{
|
|
WallSetupLoop(&wal, TAG_WALL_LOOP_DONT_SPIN, WALLFX_LOOP_DONT_SPIN);
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_LOOP_DONT_SCALE:
|
|
{
|
|
WallSetupLoop(&wal, TAG_WALL_LOOP_DONT_SCALE, WALLFX_DONT_SCALE);
|
|
wal.lotag = 0;
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_LOOP_OUTER_SECONDARY:
|
|
{
|
|
// make sure it's a red wall
|
|
if (wal.twoSided())
|
|
{
|
|
WallSetupLoop(&wal, TAG_WALL_LOOP_OUTER_SECONDARY, WALLFX_LOOP_OUTER | WALLFX_LOOP_OUTER_SECONDARY);
|
|
}
|
|
else
|
|
{
|
|
Printf(PRINT_HIGH, "one-sided wall %d in loop setup\n", wallnum(&wal));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_LOOP_OUTER:
|
|
{
|
|
// make sure it's a red wall
|
|
if (wal.twoSided())
|
|
{
|
|
WallSetupLoop(&wal, TAG_WALL_LOOP_OUTER, WALLFX_LOOP_OUTER);
|
|
}
|
|
else
|
|
{
|
|
Printf(PRINT_HIGH, "one-sided wall %d in loop setup\n", wallnum(&wal));
|
|
}
|
|
wal.lotag = 0;
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_DONT_MOVE:
|
|
{
|
|
// set first wall
|
|
wal.extra |= WALLFX_DONT_MOVE;
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_LOOP_SPIN_2X:
|
|
{
|
|
WallSetupLoop(&wal, TAG_WALL_LOOP_SPIN_2X, WALLFX_LOOP_SPIN_2X);
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_LOOP_SPIN_4X:
|
|
{
|
|
WallSetupLoop(&wal, TAG_WALL_LOOP_SPIN_4X, WALLFX_LOOP_SPIN_4X);
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_LOOP_REVERSE_SPIN:
|
|
{
|
|
WallSetupLoop(&wal, TAG_WALL_LOOP_REVERSE_SPIN, WALLFX_LOOP_REVERSE_SPIN);
|
|
break;
|
|
}
|
|
|
|
case TAG_WALL_SINE_Y_BEGIN:
|
|
case TAG_WALL_SINE_X_BEGIN:
|
|
{
|
|
walltype* wall_num;
|
|
int cnt, num_points, type, tag_end;
|
|
SINE_WALL* sw;
|
|
int range = 250, speed = 3, peak = 0;
|
|
|
|
tag_end = wal.lotag + 2;
|
|
|
|
type = wal.lotag - TAG_WALL_SINE_Y_BEGIN;
|
|
|
|
|
|
// count up num_points
|
|
for (wall_num = &wal, num_points = 0;
|
|
num_points < MAX_SINE_WALL_POINTS && wall_num->lotag != tag_end;
|
|
wall_num = wall_num->point2Wall(), num_points++)
|
|
{
|
|
if (num_points == 0)
|
|
{
|
|
if (wall_num->hitag)
|
|
range = wall_num->hitag;
|
|
}
|
|
else if (num_points == 1)
|
|
{
|
|
if (wall_num->hitag)
|
|
speed = wall_num->hitag;
|
|
}
|
|
else if (num_points == 2)
|
|
{
|
|
if (wall_num->hitag)
|
|
peak = wall_num->hitag;
|
|
}
|
|
}
|
|
|
|
if (peak)
|
|
num_points = peak;
|
|
|
|
for (wall_num = &wal, cnt = 0;
|
|
cnt < MAX_SINE_WALL_POINTS && wall_num->lotag != tag_end;
|
|
wall_num = wall_num->point2Wall(), cnt++)
|
|
{
|
|
// set the first on up
|
|
sw = &SineWall[NextSineWall][cnt];
|
|
|
|
sw->type = type;
|
|
sw->wallp = wall_num;
|
|
sw->speed_shift = speed;
|
|
sw->range = range;
|
|
|
|
// don't allow bullet holes/stars
|
|
wall_num->extra |= WALLFX_DONT_STICK;
|
|
|
|
if (!sw->type)
|
|
sw->orig_xy = wall_num->pos.Y - (sw->range >> 2);
|
|
else
|
|
sw->orig_xy = wall_num->pos.X - (sw->range >> 2);
|
|
|
|
sw->sintable_ndx = cnt * (2048 / num_points);
|
|
}
|
|
|
|
NextSineWall++;
|
|
|
|
ASSERT(NextSineWall < MAX_SINE_WALL);
|
|
|
|
}
|
|
}
|
|
|
|
// this overwrites the lotag so it needs to be called LAST
|
|
SetupWallForBreak(&wal);
|
|
}
|
|
}
|
|
|
|
|
|
void SectorLiquidSet(sectortype* sectp)
|
|
{
|
|
// ///////////////////////////////////
|
|
//
|
|
// CHECK for pics that mean something
|
|
//
|
|
// ///////////////////////////////////
|
|
|
|
if (sectp->floorpicnum >= 300 && sectp->floorpicnum <= 307)
|
|
{
|
|
sectp->u_defined = true;
|
|
sectp->extra |= (SECTFX_LIQUID_WATER);
|
|
}
|
|
else if (sectp->floorpicnum >= 320 && sectp->floorpicnum <= 343)
|
|
{
|
|
sectp->u_defined = true;
|
|
sectp->extra |= (SECTFX_LIQUID_WATER);
|
|
}
|
|
else if (sectp->floorpicnum >= 780 && sectp->floorpicnum <= 794)
|
|
{
|
|
sectp->u_defined = true;
|
|
sectp->extra |= (SECTFX_LIQUID_WATER);
|
|
}
|
|
else if (sectp->floorpicnum >= 890 && sectp->floorpicnum <= 897)
|
|
{
|
|
sectp->u_defined = true;
|
|
sectp->extra |= (SECTFX_LIQUID_WATER);
|
|
}
|
|
else if (sectp->floorpicnum >= 175 && sectp->floorpicnum <= 182)
|
|
{
|
|
sectp->u_defined = true;
|
|
sectp->extra |= (SECTFX_LIQUID_LAVA);
|
|
if (!sectp->damage)
|
|
sectp->damage = 40;
|
|
}
|
|
}
|
|
|
|
void SectorSetup(void)
|
|
{
|
|
int tag;
|
|
int NextSineWave = 0;
|
|
int ndx;
|
|
|
|
WallSetup();
|
|
|
|
for (ndx = 0; ndx < MAX_SECTOR_OBJECTS; ndx++)
|
|
{
|
|
memset(&SectorObject[ndx], -1, sizeof(SectorObject[0]));
|
|
// 0 pointers
|
|
memset(&SectorObject[ndx].sectp, 0, sizeof(SectorObject[0].sectp));
|
|
memset(&SectorObject[ndx].so_actors, 0, sizeof(SectorObject[0].so_actors));
|
|
SectorObject[ndx].match_event_actor = nullptr;
|
|
SectorObject[ndx].PreMoveAnimator = nullptr;
|
|
SectorObject[ndx].PostMoveAnimator = nullptr;
|
|
SectorObject[ndx].Animator = nullptr;
|
|
SectorObject[ndx].controller = nullptr;
|
|
SectorObject[ndx].sp_child = nullptr;
|
|
SectorObject[ndx].mid_sector = nullptr;
|
|
SectorObject[ndx].op_main_sector = nullptr;
|
|
SectorObject[ndx].morph_wall_point = nullptr;
|
|
SectorObject[ndx].pmid.X = INT32_MAX;
|
|
}
|
|
|
|
memset(SineWaveFloor, 0, sizeof(SineWaveFloor));
|
|
memset(SpringBoard, 0, sizeof(SpringBoard));
|
|
|
|
LevelSecrets = 0;
|
|
|
|
for(auto§: sector)
|
|
{
|
|
auto const sectp = §
|
|
tag = sectp->lotag;
|
|
|
|
// ///////////////////////////////////
|
|
//
|
|
// CHECK for pics that mean something
|
|
//
|
|
// ///////////////////////////////////
|
|
|
|
// ///////////////////////////////////
|
|
//
|
|
// CHECK for flags
|
|
//
|
|
// ///////////////////////////////////
|
|
|
|
if ((sectp->extra & SECTFX_SINK))
|
|
{
|
|
SectorLiquidSet(sectp);
|
|
}
|
|
|
|
if ((sectp->floorstat & CSTAT_SECTOR_SKY))
|
|
{
|
|
// don't do a z adjust for FAF area
|
|
if (sectp->floorpicnum != FAF_PLACE_MIRROR_PIC)
|
|
{
|
|
sectp->extra |= (SECTFX_Z_ADJUST);
|
|
}
|
|
}
|
|
|
|
if ((sectp->ceilingstat & CSTAT_SECTOR_SKY))
|
|
{
|
|
// don't do a z adjust for FAF area
|
|
if (sectp->ceilingpicnum != FAF_PLACE_MIRROR_PIC)
|
|
{
|
|
sectp->extra |= (SECTFX_Z_ADJUST);
|
|
}
|
|
}
|
|
|
|
// ///////////////////////////////////
|
|
//
|
|
// CHECK for sector/sprite objects
|
|
//
|
|
// ///////////////////////////////////
|
|
|
|
if (tag >= TAG_OBJECT_CENTER && tag < TAG_OBJECT_CENTER + 100)
|
|
{
|
|
SetupSectorObject(sectp, tag);
|
|
}
|
|
|
|
// ///////////////////////////////////
|
|
//
|
|
// CHECK lo and hi tags
|
|
//
|
|
// ///////////////////////////////////
|
|
|
|
switch (tag)
|
|
{
|
|
case TAG_SECRET_AREA_TRIGGER:
|
|
LevelSecrets++;
|
|
break;
|
|
|
|
case TAG_DOOR_SLIDING:
|
|
SetSectorWallBits(sectp, WALLFX_DONT_STICK, true, true);
|
|
break;
|
|
|
|
case TAG_SINE_WAVE_FLOOR:
|
|
case TAG_SINE_WAVE_CEILING:
|
|
case TAG_SINE_WAVE_BOTH:
|
|
{
|
|
SINE_WAVE_FLOOR *swf;
|
|
uint16_t swf_ndx = 0;
|
|
short cnt = 0, sector_cnt;
|
|
int range;
|
|
int range_diff = 0;
|
|
int wave_diff = 0;
|
|
short peak_dist = 0;
|
|
short speed_shift = 3;
|
|
short num;
|
|
|
|
num = (tag - TAG_SINE_WAVE_FLOOR) / 20;
|
|
|
|
// set the first on up
|
|
swf = &SineWaveFloor[NextSineWave][swf_ndx];
|
|
|
|
swf->flags = 0;
|
|
|
|
switch (num)
|
|
{
|
|
case 0:
|
|
swf->flags |= (SINE_FLOOR);
|
|
if ((sectp->floorstat & CSTAT_SECTOR_SLOPE))
|
|
{
|
|
swf->flags |= (SINE_SLOPED);
|
|
}
|
|
break;
|
|
case 1:
|
|
swf->flags |= (SINE_CEILING);
|
|
break;
|
|
case 2:
|
|
swf->flags |= (SINE_FLOOR | SINE_CEILING);
|
|
break;
|
|
}
|
|
|
|
|
|
swf->sectp = sectp;
|
|
ASSERT(swf->sectp->hitag != 0);
|
|
swf->range = range = Z(swf->sectp->hitag);
|
|
swf->floor_origz = swf->sectp->floorz - (range >> 2);
|
|
swf->ceiling_origz = swf->sectp->ceilingz - (range >> 2);
|
|
|
|
// look for the rest by distance
|
|
auto near_sectp = sectp, base_sectp = sectp;
|
|
for (swf_ndx = 1, sector_cnt = 1; true; swf_ndx++)
|
|
{
|
|
near_sectp = FindNextSectorByTag(base_sectp, tag + swf_ndx);
|
|
|
|
if (near_sectp != nullptr)
|
|
{
|
|
swf = &SineWaveFloor[NextSineWave][swf_ndx];
|
|
|
|
if (swf_ndx == 1 && near_sectp->hitag)
|
|
range_diff = near_sectp->hitag;
|
|
else if (swf_ndx == 2 && near_sectp->hitag)
|
|
speed_shift = near_sectp->hitag;
|
|
else if (swf_ndx == 3 && near_sectp->hitag)
|
|
peak_dist = near_sectp->hitag;
|
|
|
|
swf->sectp = near_sectp;
|
|
swf->floor_origz = swf->sectp->floorz - (range >> 2);
|
|
swf->ceiling_origz = swf->sectp->ceilingz - (range >> 2);
|
|
range -= range_diff;
|
|
swf->range = range;
|
|
|
|
base_sectp = swf->sectp;
|
|
sector_cnt++;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
|
|
ASSERT(swf_ndx <= SIZ(SineWaveFloor[NextSineWave]));
|
|
|
|
// more than 6 waves and something in high tag - set up wave
|
|
// dissapate
|
|
if (sector_cnt > 8 && base_sectp->hitag)
|
|
{
|
|
wave_diff = base_sectp->hitag;
|
|
}
|
|
|
|
// setup the sintable_ndx based on the actual number of
|
|
// sectors (swf_ndx)
|
|
for (swf = &SineWaveFloor[NextSineWave][0], cnt = 0; swf->sectp != 0 && swf < (SINE_WAVE_FLOOR*)&SineWaveFloor[SIZ(SineWaveFloor)]; swf++, cnt++)
|
|
{
|
|
if (peak_dist)
|
|
swf->sintable_ndx = cnt * (2048 / peak_dist);
|
|
else
|
|
swf->sintable_ndx = cnt * (2048 / swf_ndx);
|
|
|
|
swf->speed_shift = speed_shift;
|
|
}
|
|
|
|
// set up the a real wave that dissapates at the end
|
|
if (wave_diff)
|
|
{
|
|
for (cnt = sector_cnt - 1; cnt >= 0; cnt--)
|
|
{
|
|
// only do the last (actually the first) few for the
|
|
// dissapate
|
|
if (cnt > 8)
|
|
continue;
|
|
|
|
swf = &SineWaveFloor[NextSineWave][cnt];
|
|
|
|
swf->range -= wave_diff;
|
|
|
|
wave_diff += wave_diff;
|
|
|
|
if (swf->range < Z(4))
|
|
swf->range = Z(4);
|
|
|
|
// reset origz's based on new range
|
|
swf->floor_origz = swf->sectp->floorz - (swf->range >> 2);
|
|
swf->ceiling_origz = swf->sectp->ceilingz - (swf->range >> 2);
|
|
}
|
|
}
|
|
|
|
NextSineWave++;
|
|
|
|
ASSERT(NextSineWave < MAX_SINE_WAVE);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SectorMidPoint(sectortype* sectp, int *xmid, int *ymid, int *zmid)
|
|
{
|
|
int xsum = 0, ysum = 0;
|
|
|
|
for(auto& wal : wallsofsector(sectp))
|
|
{
|
|
xsum += wal.pos.X;
|
|
ysum += wal.pos.Y;
|
|
}
|
|
|
|
*xmid = xsum / (sectp->wallnum);
|
|
*ymid = ysum / (sectp->wallnum);
|
|
|
|
*zmid = (sectp->floorz + sectp->ceilingz) >> 1;
|
|
}
|
|
|
|
|
|
void DoSpringBoard(PLAYER* pp)
|
|
{
|
|
|
|
pp->jump_speed = -pp->cursector->hitag;
|
|
DoPlayerBeginForceJump(pp);
|
|
return;
|
|
}
|
|
|
|
|
|
void DoSpringBoardDown(void)
|
|
{
|
|
unsigned sb;
|
|
SPRING_BOARD *sbp;
|
|
|
|
for (sb = 0; sb < SIZ(SpringBoard); sb++)
|
|
{
|
|
sbp = &SpringBoard[sb];
|
|
|
|
// if empty set up an entry to close the sb later
|
|
if (sbp->sectp != nullptr)
|
|
{
|
|
if ((sbp->TimeOut -= synctics) <= 0)
|
|
{
|
|
int destz;
|
|
|
|
destz = safenextsectorneighborzptr(sbp->sectp, sbp->sectp->floorz, 1, 1)->floorz;
|
|
|
|
AnimSet(ANIM_Floorz, sbp->sectp, destz, 256);
|
|
|
|
sbp->sectp->lotag = TAG_SPRING_BOARD;
|
|
|
|
sbp->sectp = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
sectortype* FindNextSectorByTag(sectortype* sect, int tag)
|
|
{
|
|
for(auto& wal : wallsofsector(sect))
|
|
{
|
|
if (wal.twoSided())
|
|
{
|
|
if (wal.nextSector()->lotag == tag)
|
|
{
|
|
return wal.nextSector();
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
short DoSpawnActorTrigger(short match)
|
|
{
|
|
short spawn_count = 0;
|
|
|
|
SWStatIterator it(STAT_SPAWN_TRIGGER);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.hitag == match)
|
|
{
|
|
if (ActorSpawn(actor))
|
|
{
|
|
DoSpawnTeleporterEffectPlace(actor);
|
|
PlaySound(DIGI_PLAYER_TELEPORT, actor, v3df_none);
|
|
spawn_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return spawn_count;
|
|
}
|
|
|
|
int OperateSector(sectortype* sect, short player_is_operating)
|
|
{
|
|
PLAYER* pp = GlobPlayerP;
|
|
|
|
// Don't let actors operate locked or secret doors
|
|
if (!player_is_operating)
|
|
{
|
|
if (sect->hasU() && sect->stag == SECT_LOCK_DOOR)
|
|
return false;
|
|
|
|
SWSectIterator it(sect);
|
|
while (auto actor = it.Next())
|
|
{
|
|
auto fsect = actor->sector();
|
|
|
|
if (fsect->hasU() && fsect->stag == SECT_LOCK_DOOR)
|
|
return false;
|
|
|
|
if (actor->spr.statnum == STAT_VATOR && SP_TAG1(actor) == SECT_VATOR && TEST_BOOL7(actor))
|
|
return false;
|
|
if (actor->spr.statnum == STAT_ROTATOR && SP_TAG1(actor) == SECT_ROTATOR && TEST_BOOL7(actor))
|
|
return false;
|
|
if (actor->spr.statnum == STAT_SLIDOR && SP_TAG1(actor) == SECT_SLIDOR && TEST_BOOL7(actor))
|
|
return false;
|
|
|
|
}
|
|
}
|
|
|
|
switch (sect->lotag)
|
|
{
|
|
|
|
case TAG_VATOR:
|
|
DoVatorOperate(pp, sect);
|
|
return true;
|
|
|
|
case TAG_ROTATOR:
|
|
DoRotatorOperate(pp, sect);
|
|
return true;
|
|
|
|
case TAG_SLIDOR:
|
|
DoSlidorOperate(pp, sect);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
enum
|
|
{
|
|
SWITCH_LEVER = 581,
|
|
SWITCH_FUSE = 558,
|
|
SWITCH_FLIP = 561,
|
|
SWITCH_RED_CHAIN = 563,
|
|
SWITCH_GREEN_CHAIN = 565,
|
|
SWITCH_TOUCH = 567,
|
|
SWITCH_DRAGON = 569,
|
|
|
|
SWITCH_LIGHT = 551,
|
|
SWITCH_1 = 575,
|
|
SWITCH_3 = 579,
|
|
|
|
SWITCH_SHOOTABLE_1 = 577,
|
|
SWITCH_4 = 571,
|
|
SWITCH_5 = 573,
|
|
SWITCH_6 = 583,
|
|
EXIT_SWITCH = 2470,
|
|
|
|
SWITCH_SKULL = 553,
|
|
};
|
|
|
|
int AnimateSwitch(DSWActor* actor, int tgt_value)
|
|
{
|
|
// if the value is not ON or OFF
|
|
// then it is a straight toggle
|
|
|
|
switch (actor->spr.picnum)
|
|
{
|
|
// set to true/ON
|
|
case SWITCH_SKULL:
|
|
case SWITCH_LEVER:
|
|
case SWITCH_LIGHT:
|
|
case SWITCH_SHOOTABLE_1:
|
|
case SWITCH_1:
|
|
case SWITCH_3:
|
|
case SWITCH_FLIP:
|
|
case SWITCH_RED_CHAIN:
|
|
case SWITCH_GREEN_CHAIN:
|
|
case SWITCH_TOUCH:
|
|
case SWITCH_DRAGON:
|
|
case SWITCH_4:
|
|
case SWITCH_5:
|
|
case SWITCH_6:
|
|
case EXIT_SWITCH:
|
|
|
|
// dont toggle - return the current state
|
|
if (tgt_value == 999)
|
|
return false;
|
|
|
|
actor->spr.picnum += 1;
|
|
|
|
// if the tgt_value should be true
|
|
// flip it again - recursive but only once
|
|
if (tgt_value == false)
|
|
{
|
|
AnimateSwitch(actor, tgt_value);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
// set to true
|
|
case SWITCH_SKULL + 1:
|
|
case SWITCH_LEVER + 1:
|
|
case SWITCH_LIGHT + 1:
|
|
case SWITCH_1 + 1:
|
|
case SWITCH_3 + 1:
|
|
case SWITCH_FLIP + 1:
|
|
case SWITCH_RED_CHAIN + 1:
|
|
case SWITCH_GREEN_CHAIN + 1:
|
|
case SWITCH_TOUCH + 1:
|
|
case SWITCH_DRAGON + 1:
|
|
case SWITCH_SHOOTABLE_1 + 1:
|
|
case SWITCH_4+1:
|
|
case SWITCH_5+1:
|
|
case SWITCH_6+1:
|
|
case EXIT_SWITCH+1:
|
|
|
|
// dont toggle - return the current state
|
|
if (tgt_value == 999)
|
|
return true;
|
|
|
|
actor->spr.picnum -= 1;
|
|
|
|
if (tgt_value == int(true))
|
|
{
|
|
AnimateSwitch(actor, tgt_value);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void SectorExp(DSWActor* actor, sectortype* sectp, short orig_ang, int zh)
|
|
{
|
|
int x,y,z;
|
|
|
|
actor->spr.cstat &= ~(CSTAT_SPRITE_ALIGNMENT_WALL|CSTAT_SPRITE_ALIGNMENT_FLOOR);
|
|
SectorMidPoint(sectp, &x, &y, &z);
|
|
actor->spr.ang = orig_ang;
|
|
actor->spr.pos.X = x;
|
|
actor->spr.pos.Y = y;
|
|
actor->spr.pos.Z = z;
|
|
|
|
// randomize the explosions
|
|
actor->spr.ang += RANDOM_P2(256) - 128;
|
|
actor->spr.pos.X += RANDOM_P2(1024) - 512;
|
|
actor->spr.pos.Y += RANDOM_P2(1024) - 512;
|
|
actor->spr.pos.Z = zh;
|
|
|
|
// setup vars needed by SectorExp
|
|
ChangeActorSect(actor, sectp);
|
|
getzsofslopeptr(actor->sector(), actor->spr.pos.X, actor->spr.pos.Y, &actor->user.hiz, &actor->user.loz);
|
|
|
|
// spawn explosion
|
|
auto exp = SpawnSectorExp(actor);
|
|
if (!exp) return;
|
|
|
|
exp->spr.xrepeat += (RANDOM_P2(32<<8)>>8) - 16;
|
|
exp->spr.yrepeat += (RANDOM_P2(32<<8)>>8) - 16;
|
|
exp->user.change.X = MOVEx(92, exp->spr.ang);
|
|
exp->user.change.Y = MOVEy(92, exp->spr.ang);
|
|
}
|
|
|
|
|
|
void DoExplodeSector(short match)
|
|
{
|
|
short orig_ang;
|
|
int zh;
|
|
|
|
sectortype* sectp;
|
|
|
|
orig_ang = 0; //actor->spr.ang;
|
|
|
|
SWStatIterator it(STAT_EXPLODING_CEIL_FLOOR);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (match != actor->spr.lotag)
|
|
continue;
|
|
|
|
if (!actor->hasU())
|
|
SpawnUser(actor, 0, nullptr);
|
|
|
|
sectp = actor->sector();
|
|
|
|
sectp->addceilingz(-Z(SP_TAG4(actor)));
|
|
|
|
if (SP_TAG5(actor))
|
|
{
|
|
sectp->setfloorslope(SP_TAG5(actor));
|
|
}
|
|
|
|
if (SP_TAG6(actor))
|
|
{
|
|
sectp->setceilingslope(SP_TAG6(actor));
|
|
}
|
|
|
|
for (zh = sectp->ceilingz; zh < sectp->floorz; zh += Z(60))
|
|
{
|
|
SectorExp(actor, actor->sector(), orig_ang, zh + Z(RANDOM_P2(64)) - Z(32));
|
|
}
|
|
|
|
// don't need it any more
|
|
KillActor(actor);
|
|
}
|
|
}
|
|
|
|
|
|
int DoSpawnSpot(DSWActor* actor)
|
|
{
|
|
if ((actor->user.WaitTics -= synctics) < 0)
|
|
{
|
|
change_actor_stat(actor, STAT_SPAWN_SPOT);
|
|
SpawnShrap(actor, nullptr);
|
|
|
|
if (actor->user.LastDamage == 1)
|
|
{
|
|
KillActor(actor);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// spawns shrap when killing an object
|
|
void DoSpawnSpotsForKill(short match)
|
|
{
|
|
if (match < 0)
|
|
return;
|
|
|
|
SWStatIterator it(STAT_SPAWN_SPOT);
|
|
while (auto actor = it.Next())
|
|
{
|
|
// change the stat num and set the delay correctly to call SpawnShrap
|
|
if (actor->spr.hitag == SPAWN_SPOT && actor->spr.lotag == match)
|
|
{
|
|
change_actor_stat(actor, STAT_NO_STATE);
|
|
actor->user.ActorActionFunc = DoSpawnSpot;
|
|
actor->user.WaitTics = SP_TAG5(actor) * 15;
|
|
SetActorZ(actor, &actor->spr.pos);
|
|
// setting for Killed
|
|
actor->user.LastDamage = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// spawns shrap when damaging an object
|
|
void DoSpawnSpotsForDamage(short match)
|
|
{
|
|
if (match < 0)
|
|
return;
|
|
|
|
SWStatIterator it(STAT_SPAWN_SPOT);
|
|
while (auto actor = it.Next())
|
|
{
|
|
// change the stat num and set the delay correctly to call SpawnShrap
|
|
if (actor->spr.hitag == SPAWN_SPOT && actor->spr.lotag == match)
|
|
{
|
|
change_actor_stat(actor, STAT_NO_STATE);
|
|
actor->user.ActorActionFunc = DoSpawnSpot;
|
|
actor->user.WaitTics = SP_TAG7(actor) * 15;
|
|
// setting for Damaged
|
|
actor->user.LastDamage = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoSoundSpotMatch(short match, short sound_num, short sound_type)
|
|
{
|
|
int flags;
|
|
short snd2play;
|
|
|
|
//sound_type is not used
|
|
|
|
sound_num--;
|
|
|
|
ASSERT(sound_num >= 0);
|
|
|
|
SWStatIterator it(STAT_SOUND_SPOT);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (SP_TAG2(actor) == match && !TEST_BOOL6(actor))
|
|
{
|
|
short snd[3];
|
|
|
|
snd[0] = SP_TAG13(actor); // tag4 is copied to tag13
|
|
snd[1] = SP_TAG5(actor);
|
|
snd[2] = SP_TAG6(actor);
|
|
|
|
snd2play = 0;
|
|
flags = 0;
|
|
|
|
if (TEST_BOOL2(actor))
|
|
flags = v3df_follow|v3df_nolookup|v3df_init;
|
|
|
|
// play once and only once
|
|
if (TEST_BOOL1(actor))
|
|
SET_BOOL6(actor);
|
|
|
|
// don't pan
|
|
if (TEST_BOOL4(actor))
|
|
flags |= v3df_dontpan;
|
|
// add doppler
|
|
if (TEST_BOOL5(actor))
|
|
flags |= v3df_doppler;
|
|
// random
|
|
if (TEST_BOOL3(actor))
|
|
{
|
|
if (snd[0] && snd[1])
|
|
{
|
|
snd2play = snd[RandomRange(2)];
|
|
}
|
|
else if (snd[0] && snd[1] && snd[2])
|
|
{
|
|
snd2play = snd[RandomRange(3)];
|
|
}
|
|
}
|
|
else if (snd[sound_num])
|
|
{
|
|
snd2play = snd[sound_num];
|
|
}
|
|
|
|
if (snd2play <= 0)
|
|
continue;
|
|
|
|
if (TEST_BOOL7(actor))
|
|
{
|
|
PLAYER* pp = GlobPlayerP;
|
|
|
|
if (pp)
|
|
{
|
|
if (pp == Player+myconnectindex)
|
|
PlayerSound(snd2play, v3df_dontpan|v3df_follow,pp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PlaySound(snd2play, actor, flags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoSoundSpotStopSound(short match)
|
|
{
|
|
|
|
SWStatIterator it(STAT_SOUND_SPOT);
|
|
while (auto actor = it.Next())
|
|
{
|
|
// found match and is a follow type
|
|
if (SP_TAG2(actor) == match && TEST_BOOL2(actor))
|
|
{
|
|
DeleteNoSoundOwner(actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoStopSoundSpotMatch(short match)
|
|
{
|
|
SWStatIterator it(STAT_STOP_SOUND_SPOT);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (SP_TAG2(actor) == match)
|
|
{
|
|
DoSoundSpotStopSound(SP_TAG5(actor));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool TestKillSectorObject(SECTOR_OBJECT* sop)
|
|
{
|
|
if ((sop->flags & SOBJ_KILLABLE))
|
|
{
|
|
KillMatchingCrackSprites(sop->match_event);
|
|
// get new sectnums
|
|
CollapseSectorObject(sop, sop->pmid.X, sop->pmid.Y);
|
|
DoSpawnSpotsForKill(sop->match_event);
|
|
KillSectorObjectSprites(sop);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
short DoSectorObjectKillMatch(short match)
|
|
{
|
|
SECTOR_OBJECT* sop;
|
|
|
|
for (sop = SectorObject; sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++)
|
|
{
|
|
if (SO_EMPTY(sop))
|
|
continue;
|
|
|
|
if (sop->match_event == match)
|
|
return TestKillSectorObject(sop);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool SearchExplodeSectorMatch(short match)
|
|
{
|
|
// THIS IS ONLY CALLED FROM DoMatchEverything
|
|
SWStatIterator it(STAT_SPRITE_HIT_MATCH);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.hitag == match)
|
|
{
|
|
KillMatchingCrackSprites(match);
|
|
DoExplodeSector(match);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void KillMatchingCrackSprites(short match)
|
|
{
|
|
SWStatIterator it(STAT_SPRITE_HIT_MATCH);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.hitag == match)
|
|
{
|
|
if (SP_TAG8(actor) & BIT(2))
|
|
continue;
|
|
|
|
KillActor(actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WeaponExplodeSectorInRange(DSWActor* wActor)
|
|
{
|
|
int dist;
|
|
int radius;
|
|
|
|
SWStatIterator it(STAT_SPRITE_HIT_MATCH);
|
|
while (auto actor = it.Next())
|
|
{
|
|
// test to see if explosion is close to crack sprite
|
|
dist = FindDistance3D(wActor->spr.pos - actor->spr.pos);
|
|
|
|
if (actor->spr.clipdist == 0)
|
|
continue;
|
|
|
|
radius = (((int)actor->spr.clipdist) << 2) * 8;
|
|
|
|
if ((unsigned int)dist > (wActor->user.Radius/2) + radius)
|
|
continue;
|
|
|
|
if (!FAFcansee(wActor->spr.pos.X,wActor->spr.pos.Y,wActor->spr.pos.Z,wActor->sector(),actor->spr.pos.X,actor->spr.pos.Y,actor->spr.pos.Z,actor->sector()))
|
|
continue;
|
|
|
|
|
|
// pass in explosion type
|
|
MissileHitMatch(wActor, WPN_ROCKET, actor);
|
|
}
|
|
}
|
|
|
|
|
|
void ShootableSwitch(DSWActor* actor)
|
|
{
|
|
switch (actor->spr.picnum)
|
|
{
|
|
case SWITCH_SHOOTABLE_1:
|
|
//actor->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);
|
|
OperateSprite(actor, false);
|
|
actor->spr.picnum = SWITCH_SHOOTABLE_1 + 1;
|
|
break;
|
|
case SWITCH_FUSE:
|
|
case SWITCH_FUSE + 1:
|
|
actor->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);
|
|
OperateSprite(actor, false);
|
|
actor->spr.picnum = SWITCH_FUSE + 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DoDeleteSpriteMatch(short match)
|
|
{
|
|
static short StatList[] =
|
|
{
|
|
STAT_DEFAULT,
|
|
STAT_VATOR,
|
|
STAT_SPIKE,
|
|
STAT_TRAP,
|
|
STAT_ITEM,
|
|
STAT_LIGHTING,
|
|
STAT_STATIC_FIRE,
|
|
STAT_AMBIENT,
|
|
STAT_FAF
|
|
};
|
|
|
|
int del_x = 0,del_y = 0;
|
|
unsigned stat;
|
|
|
|
while (true)
|
|
{
|
|
DSWActor* found = nullptr;
|
|
|
|
// search for a DELETE_SPRITE with same match tag
|
|
SWStatIterator it(STAT_DELETE_SPRITE);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.lotag == match)
|
|
{
|
|
found = actor;
|
|
del_x = actor->spr.pos.X;
|
|
del_y = actor->spr.pos.Y;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found == nullptr)
|
|
return;
|
|
|
|
for (stat = 0; stat < SIZ(StatList); stat++)
|
|
{
|
|
it.Reset(StatList[stat]);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (del_x == actor->spr.pos.X && del_y == actor->spr.pos.Y)
|
|
{
|
|
// special case lighting delete of Fade On/off after fades
|
|
if (StatList[stat] == STAT_LIGHTING)
|
|
{
|
|
// set shade to darkest and then kill it
|
|
actor->spr.shade = int8_t(SP_TAG6(actor));
|
|
actor->spr.pal = 0;
|
|
SectorLightShade(actor, actor->spr.shade);
|
|
DiffuseLighting(actor);
|
|
}
|
|
|
|
SpriteQueueDelete(actor);
|
|
KillActor(actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
// kill the DELETE_SPRITE
|
|
KillActor(found);
|
|
}
|
|
}
|
|
|
|
void DoChangorMatch(short match)
|
|
{
|
|
SWStatIterator it(STAT_CHANGOR);
|
|
while (auto actor = it.Next())
|
|
{
|
|
auto sectp = actor->sector();
|
|
|
|
if (SP_TAG2(actor) != match)
|
|
continue;
|
|
|
|
if (TEST_BOOL1(actor))
|
|
{
|
|
sectp->ceilingpicnum = SP_TAG4(actor);
|
|
sectp->addceilingz(Z(SP_TAG5(actor)));
|
|
sectp->ceilingheinum += SP_TAG6(actor);
|
|
|
|
if (sectp->ceilingheinum)
|
|
sectp->ceilingstat |= CSTAT_SECTOR_SLOPE;
|
|
else
|
|
sectp->ceilingstat &= ~CSTAT_SECTOR_SLOPE;
|
|
|
|
sectp->ceilingshade += SP_TAG7(actor);
|
|
sectp->ceilingpal += SP_TAG8(actor);
|
|
}
|
|
else
|
|
{
|
|
sectp->floorpicnum = SP_TAG4(actor);
|
|
sectp->addfloorz(Z(SP_TAG5(actor)));
|
|
sectp->floorheinum += SP_TAG6(actor);
|
|
|
|
if (sectp->floorheinum)
|
|
sectp->floorstat |= CSTAT_SECTOR_SLOPE;
|
|
else
|
|
sectp->floorstat &= ~CSTAT_SECTOR_SLOPE;
|
|
|
|
sectp->floorshade += SP_TAG7(actor);
|
|
sectp->floorpal += SP_TAG8(actor);
|
|
}
|
|
|
|
sectp->visibility += SP_TAG9(actor);
|
|
|
|
// if not set then go ahead and kill it
|
|
if (TEST_BOOL2(actor) == 0)
|
|
{
|
|
KillActor(actor);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoMatchEverything(PLAYER* pp, short match, short state)
|
|
{
|
|
PLAYER* bak;
|
|
|
|
bak = GlobPlayerP;
|
|
GlobPlayerP = pp;
|
|
// CAREFUL! pp == nullptr is a valid case for this routine
|
|
DoStopSoundSpotMatch(match);
|
|
DoSoundSpotMatch(match, 1, SOUND_EVERYTHING_TYPE);
|
|
GlobPlayerP = bak;
|
|
|
|
DoLightingMatch(match, state);
|
|
|
|
DoQuakeMatch(match);
|
|
|
|
// make sure all vators are inactive before allowing
|
|
// to repress switch
|
|
if (!TestVatorMatchActive(match))
|
|
DoVatorMatch(pp, match);
|
|
|
|
if (!TestSpikeMatchActive(match))
|
|
DoSpikeMatch(match);
|
|
|
|
if (!TestRotatorMatchActive(match))
|
|
DoRotatorMatch(pp, match, false);
|
|
|
|
if (!TestSlidorMatchActive(match))
|
|
DoSlidorMatch(pp, match, false);
|
|
|
|
DoSectorObjectKillMatch(match);
|
|
DoSectorObjectSetScale(match);
|
|
|
|
DoSOevent(match, state);
|
|
DoSpawnActorTrigger(match);
|
|
|
|
// this may or may not find an exploding sector
|
|
SearchExplodeSectorMatch(match);
|
|
|
|
CopySectorMatch(match);
|
|
DoWallMoveMatch(match);
|
|
DoSpawnSpotsForKill(match);
|
|
|
|
DoTrapReset(match);
|
|
DoTrapMatch(match);
|
|
|
|
SpawnItemsMatch(match);
|
|
DoChangorMatch(match);
|
|
DoDeleteSpriteMatch(match);
|
|
}
|
|
|
|
bool ComboSwitchTest(short combo_type, short match)
|
|
{
|
|
int state;
|
|
|
|
SWStatIterator it(STAT_DEFAULT);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.lotag == combo_type && actor->spr.hitag == match)
|
|
{
|
|
// dont toggle - get the current state
|
|
state = AnimateSwitch(actor, 999);
|
|
|
|
// if any one is not set correctly then switch is not set
|
|
if (state != SP_TAG3(actor))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// NOTE: switches are always wall sprites
|
|
int OperateSprite(DSWActor* actor, short player_is_operating)
|
|
{
|
|
PLAYER* pp = nullptr;
|
|
short state;
|
|
short key_num=0;
|
|
extern STATE s_Pachinko1Operate[];
|
|
extern STATE s_Pachinko2Operate[];
|
|
extern STATE s_Pachinko3Operate[];
|
|
extern STATE s_Pachinko4Operate[];
|
|
|
|
if (Prediction)
|
|
return false;
|
|
|
|
if (actor->spr.picnum == ST1)
|
|
return false;
|
|
|
|
if (player_is_operating)
|
|
{
|
|
pp = GlobPlayerP;
|
|
|
|
if (!FAFcansee(pp->pos.X, pp->pos.Y, pp->pos.Z, pp->cursector, actor->spr.pos.X, actor->spr.pos.Y, actor->spr.pos.Z - (ActorSizeZ(actor) >> 1), actor->sector()))
|
|
return false;
|
|
}
|
|
|
|
switch (actor->spr.lotag)
|
|
{
|
|
case TOILETGIRL_R0:
|
|
case WASHGIRL_R0:
|
|
case CARGIRL_R0:
|
|
case MECHANICGIRL_R0:
|
|
case SAILORGIRL_R0:
|
|
case PRUNEGIRL_R0:
|
|
{
|
|
short choose_snd;
|
|
|
|
actor->user.FlagOwner = 1;
|
|
actor->user.WaitTics = SEC(4);
|
|
|
|
if (pp != Player+myconnectindex) return true;
|
|
|
|
choose_snd = StdRandomRange(1000);
|
|
if (actor->spr.lotag == CARGIRL_R0)
|
|
{
|
|
if (choose_snd > 700)
|
|
PlayerSound(DIGI_JG44052, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 500)
|
|
PlayerSound(DIGI_JG45014, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 250)
|
|
PlayerSound(DIGI_JG44068, v3df_dontpan|v3df_follow,pp);
|
|
else
|
|
PlayerSound(DIGI_JG45010, v3df_dontpan|v3df_follow,pp);
|
|
}
|
|
else if (actor->spr.lotag == MECHANICGIRL_R0)
|
|
{
|
|
if (choose_snd > 700)
|
|
PlayerSound(DIGI_JG44027, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 500)
|
|
PlayerSound(DIGI_JG44038, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 250)
|
|
PlayerSound(DIGI_JG44039, v3df_dontpan|v3df_follow,pp);
|
|
else
|
|
PlayerSound(DIGI_JG44048, v3df_dontpan|v3df_follow,pp);
|
|
}
|
|
else if (actor->spr.lotag == SAILORGIRL_R0)
|
|
{
|
|
if (choose_snd > 700)
|
|
PlayerSound(DIGI_JG45018, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 500)
|
|
PlayerSound(DIGI_JG45030, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 250)
|
|
PlayerSound(DIGI_JG45033, v3df_dontpan|v3df_follow,pp);
|
|
else
|
|
PlayerSound(DIGI_JG45043, v3df_dontpan|v3df_follow,pp);
|
|
}
|
|
else if (actor->spr.lotag == PRUNEGIRL_R0)
|
|
{
|
|
if (choose_snd > 700)
|
|
PlayerSound(DIGI_JG45053, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 500)
|
|
PlayerSound(DIGI_JG45067, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 250)
|
|
PlayerSound(DIGI_JG46005, v3df_dontpan|v3df_follow,pp);
|
|
else
|
|
PlayerSound(DIGI_JG46010, v3df_dontpan|v3df_follow,pp);
|
|
}
|
|
else if (actor->spr.lotag == TOILETGIRL_R0)
|
|
{
|
|
if (choose_snd > 700)
|
|
PlayerSound(DIGI_WHATYOUEATBABY, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 500)
|
|
PlayerSound(DIGI_WHATDIEDUPTHERE, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 250)
|
|
PlayerSound(DIGI_YOUGOPOOPOO, v3df_dontpan|v3df_follow,pp);
|
|
else
|
|
PlayerSound(DIGI_PULLMYFINGER, v3df_dontpan|v3df_follow,pp);
|
|
}
|
|
else
|
|
{
|
|
if (choose_snd > 700)
|
|
PlayerSound(DIGI_SOAPYOUGOOD, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 500)
|
|
PlayerSound(DIGI_WASHWANG, v3df_dontpan|v3df_follow,pp);
|
|
else if (choose_snd > 250)
|
|
PlayerSound(DIGI_DROPSOAP, v3df_dontpan|v3df_follow,pp);
|
|
else
|
|
PlayerSound(DIGI_REALTITS, v3df_dontpan|v3df_follow,pp);
|
|
}
|
|
}
|
|
return true;
|
|
|
|
case PACHINKO1:
|
|
|
|
// Don't mess with it if it's already going
|
|
if (actor->user.WaitTics > 0) return true;
|
|
|
|
PlaySound(DIGI_PFLIP, actor, v3df_none);
|
|
actor->user.WaitTics = SEC(3) + SEC(RandomRange(10));
|
|
ChangeState(actor,s_Pachinko1Operate);
|
|
|
|
return true;
|
|
|
|
case PACHINKO2:
|
|
|
|
// Don't mess with it if it's already going
|
|
if (actor->user.WaitTics > 0) return true;
|
|
|
|
PlaySound(DIGI_PFLIP, actor, v3df_none);
|
|
actor->user.WaitTics = SEC(3) + SEC(RandomRange(10));
|
|
ChangeState(actor,s_Pachinko2Operate);
|
|
|
|
return true;
|
|
|
|
case PACHINKO3:
|
|
|
|
// Don't mess with it if it's already going
|
|
if (actor->user.WaitTics > 0) return true;
|
|
|
|
PlaySound(DIGI_PFLIP, actor, v3df_none);
|
|
actor->user.WaitTics = SEC(3) + SEC(RandomRange(10));
|
|
ChangeState(actor,s_Pachinko3Operate);
|
|
|
|
return true;
|
|
|
|
case PACHINKO4:
|
|
|
|
// Don't mess with it if it's already going
|
|
if (actor->user.WaitTics > 0) return true;
|
|
|
|
PlaySound(DIGI_PFLIP, actor, v3df_none);
|
|
actor->user.WaitTics = SEC(3) + SEC(RandomRange(10));
|
|
ChangeState(actor,s_Pachinko4Operate);
|
|
|
|
return true;
|
|
|
|
case SWITCH_LOCKED:
|
|
key_num = actor->spr.hitag;
|
|
if (pp->HasKey[key_num - 1])
|
|
{
|
|
for(auto& sect: sector)
|
|
{
|
|
if (sect.hasU() && sect.stag == SECT_LOCK_DOOR && sect.number == key_num)
|
|
sect.number = 0; // unlock all doors of this type
|
|
}
|
|
UnlockKeyLock(key_num, actor);
|
|
}
|
|
|
|
return true;
|
|
|
|
case TAG_COMBO_SWITCH_EVERYTHING:
|
|
|
|
// change the switch state
|
|
AnimateSwitch(actor, -1);
|
|
PlaySound(DIGI_REGULARSWITCH, actor, v3df_none);
|
|
|
|
if (ComboSwitchTest(TAG_COMBO_SWITCH_EVERYTHING, actor->spr.hitag))
|
|
{
|
|
DoMatchEverything(pp, actor->spr.hitag, true);
|
|
}
|
|
|
|
return true;
|
|
|
|
case TAG_COMBO_SWITCH_EVERYTHING_ONCE:
|
|
|
|
// change the switch state
|
|
AnimateSwitch(actor, -1);
|
|
PlaySound(DIGI_REGULARSWITCH, actor, v3df_none);
|
|
|
|
if (ComboSwitchTest(TAG_COMBO_SWITCH_EVERYTHING, actor->spr.hitag))
|
|
{
|
|
DoMatchEverything(pp, actor->spr.hitag, true);
|
|
}
|
|
|
|
actor->spr.lotag = 0;
|
|
actor->spr.hitag = 0;
|
|
return true;
|
|
|
|
case TAG_SWITCH_EVERYTHING:
|
|
state = AnimateSwitch(actor, -1);
|
|
DoMatchEverything(pp, actor->spr.hitag, state);
|
|
return true;
|
|
|
|
case TAG_SWITCH_EVERYTHING_ONCE:
|
|
state = AnimateSwitch(actor, -1);
|
|
DoMatchEverything(pp, actor->spr.hitag, state);
|
|
actor->spr.lotag = 0;
|
|
actor->spr.hitag = 0;
|
|
return true;
|
|
|
|
case TAG_LIGHT_SWITCH:
|
|
|
|
state = AnimateSwitch(actor, -1);
|
|
DoLightingMatch(actor->spr.hitag, state);
|
|
return true;
|
|
|
|
case TAG_SPRITE_SWITCH_VATOR:
|
|
{
|
|
// make sure all vators are inactive before allowing
|
|
// to repress switch
|
|
if (!TestVatorMatchActive(actor->spr.hitag))
|
|
DoVatorMatch(pp, actor->spr.hitag);
|
|
|
|
if (!TestSpikeMatchActive(actor->spr.hitag))
|
|
DoSpikeMatch(actor->spr.hitag);
|
|
|
|
if (!TestRotatorMatchActive(actor->spr.hitag))
|
|
DoRotatorMatch(pp, actor->spr.hitag, false);
|
|
|
|
if (!TestSlidorMatchActive(actor->spr.hitag))
|
|
DoSlidorMatch(pp, actor->spr.hitag, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
case TAG_LEVEL_EXIT_SWITCH:
|
|
{
|
|
AnimateSwitch(actor, -1);
|
|
|
|
PlaySound(DIGI_BIGSWITCH, actor, v3df_none);
|
|
|
|
MapRecord *map;
|
|
if (actor->spr.hitag)
|
|
map = FindMapByLevelNum(actor->spr.hitag);
|
|
else
|
|
map = FindNextMap(currentLevel);
|
|
ChangeLevel(map, g_nextskill);
|
|
|
|
return true;
|
|
}
|
|
|
|
case TAG_SPRITE_GRATING:
|
|
{
|
|
change_actor_stat(actor, STAT_NO_STATE);
|
|
|
|
SpawnUser(actor, 0, nullptr);
|
|
|
|
actor->user.ActorActionFunc = DoGrating;
|
|
|
|
actor->spr.lotag = 0;
|
|
actor->spr.hitag /= 2;
|
|
|
|
return true;
|
|
}
|
|
|
|
case TAG_SO_SCALE_SWITCH:
|
|
AnimateSwitch(actor, -1);
|
|
DoSectorObjectSetScale(actor->spr.hitag);
|
|
return true;
|
|
|
|
case TAG_SO_SCALE_ONCE_SWITCH:
|
|
AnimateSwitch(actor, -1);
|
|
DoSectorObjectSetScale(actor->spr.hitag);
|
|
actor->spr.lotag = 0;
|
|
actor->spr.hitag = 0;
|
|
return true;
|
|
|
|
case TAG_SO_EVENT_SWITCH:
|
|
{
|
|
state = AnimateSwitch(actor, -1);
|
|
|
|
DoMatchEverything(nullptr, actor->spr.hitag, state);
|
|
|
|
actor->spr.hitag = 0;
|
|
actor->spr.lotag = 0;
|
|
|
|
PlaySound(DIGI_REGULARSWITCH, actor, v3df_none);
|
|
break;
|
|
}
|
|
|
|
case TAG_ROTATE_SO_SWITCH:
|
|
{
|
|
short so_num;
|
|
SECTOR_OBJECT* sop;
|
|
|
|
so_num = actor->spr.hitag;
|
|
|
|
ASSERT(so_num <= 20);
|
|
ASSERT(SectorObject[so_num].num_sectors != -1);
|
|
|
|
AnimateSwitch(actor, -1);
|
|
|
|
sop = &SectorObject[so_num];
|
|
|
|
sop->ang_tgt = NORM_ANGLE(sop->ang_tgt + 512);
|
|
|
|
PlaySound(DIGI_BIGSWITCH, actor, v3df_none);
|
|
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int DoTrapReset(short match)
|
|
{
|
|
SWStatIterator it(STAT_TRAP);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.lotag != match)
|
|
continue;
|
|
|
|
// if correct type and matches
|
|
if (actor->spr.hitag == FIREBALL_TRAP)
|
|
actor->user.WaitTics = 0;
|
|
|
|
// if correct type and matches
|
|
if (actor->spr.hitag == BOLT_TRAP)
|
|
actor->user.WaitTics = 0;
|
|
|
|
// if correct type and matches
|
|
if (actor->spr.hitag == SPEAR_TRAP)
|
|
actor->user.WaitTics = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int DoTrapMatch(short match)
|
|
{
|
|
// may need to be reset to fire immediately
|
|
|
|
SWStatIterator it(STAT_TRAP);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->spr.lotag != match)
|
|
continue;
|
|
|
|
// if correct type and matches
|
|
if (actor->spr.hitag == FIREBALL_TRAP)
|
|
{
|
|
actor->user.WaitTics -= synctics;
|
|
|
|
if (actor->user.WaitTics <= 0)
|
|
{
|
|
actor->user.WaitTics = 1 * 120;
|
|
InitFireballTrap(actor);
|
|
}
|
|
}
|
|
|
|
// if correct type and matches
|
|
if (actor->spr.hitag == BOLT_TRAP)
|
|
{
|
|
actor->user.WaitTics -= synctics;
|
|
|
|
if (actor->user.WaitTics <= 0)
|
|
{
|
|
actor->user.WaitTics = 1 * 120;
|
|
InitBoltTrap(actor);
|
|
}
|
|
}
|
|
|
|
// if correct type and matches
|
|
if (actor->spr.hitag == SPEAR_TRAP)
|
|
{
|
|
actor->user.WaitTics -= synctics;
|
|
|
|
if (actor->user.WaitTics <= 0)
|
|
{
|
|
actor->user.WaitTics = 1 * 120;
|
|
InitSpearTrap(actor);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void OperateTripTrigger(PLAYER* pp)
|
|
{
|
|
if (Prediction)
|
|
return;
|
|
|
|
if (!pp->insector())
|
|
return;
|
|
|
|
sectortype* sectp = pp->cursector;
|
|
|
|
// old method
|
|
switch (pp->cursector->lotag)
|
|
{
|
|
// same tag for sector as for switch
|
|
case TAG_LEVEL_EXIT_SWITCH:
|
|
{
|
|
MapRecord *map;
|
|
if (sectp->hitag)
|
|
map = FindMapByLevelNum(sectp->hitag);
|
|
else
|
|
map = FindNextMap(currentLevel);
|
|
ChangeLevel(map, g_nextskill);
|
|
break;
|
|
}
|
|
|
|
case TAG_SECRET_AREA_TRIGGER:
|
|
if (pp == Player+myconnectindex)
|
|
PlayerSound(DIGI_ANCIENTSECRET, v3df_dontpan|v3df_doppler|v3df_follow,pp);
|
|
|
|
SECRET_Trigger(sectnum(pp->cursector));
|
|
|
|
PutStringInfo(pp, GStrings("TXTS_SECRET"));
|
|
// always give to the first player
|
|
Player->SecretsFound++;
|
|
sectp->lotag = 0;
|
|
sectp->hitag = 0;
|
|
break;
|
|
|
|
case TAG_TRIGGER_EVERYTHING:
|
|
DoMatchEverything(pp, sectp->hitag, -1);
|
|
break;
|
|
|
|
case TAG_TRIGGER_EVERYTHING_ONCE:
|
|
DoMatchEverything(pp, sectp->hitag, -1);
|
|
sectp->lotag = 0;
|
|
sectp->hitag = 0;
|
|
break;
|
|
|
|
case TAG_SECTOR_TRIGGER_VATOR:
|
|
if (!TestVatorMatchActive(sectp->hitag))
|
|
DoVatorMatch(pp, sectp->hitag);
|
|
if (!TestSpikeMatchActive(sectp->hitag))
|
|
DoSpikeMatch(sectp->hitag);
|
|
if (!TestRotatorMatchActive(sectp->hitag))
|
|
DoRotatorMatch(pp, sectp->hitag, false);
|
|
if (!TestSlidorMatchActive(sectp->hitag))
|
|
DoSlidorMatch(pp, sectp->hitag, false);
|
|
break;
|
|
|
|
case TAG_LIGHT_TRIGGER:
|
|
DoLightingMatch(sectp->hitag, -1);
|
|
break;
|
|
|
|
case TAG_SO_SCALE_TRIGGER:
|
|
DoSectorObjectSetScale(sectp->hitag);
|
|
break;
|
|
|
|
case TAG_SO_SCALE_ONCE_TRIGGER:
|
|
DoSectorObjectSetScale(sectp->hitag);
|
|
sectp->lotag = 0;
|
|
sectp->hitag = 0;
|
|
break;
|
|
|
|
case TAG_TRIGGER_ACTORS:
|
|
{
|
|
int dist;
|
|
int i;
|
|
|
|
dist = sectp->hitag;
|
|
|
|
SWStatIterator it(STAT_ENEMY);
|
|
while (auto actor = it.Next())
|
|
{
|
|
if (actor->user.Flags & (SPR_WAIT_FOR_TRIGGER))
|
|
{
|
|
if (Distance(actor->spr.pos.X, actor->spr.pos.Y, pp->pos.X, pp->pos.Y) < dist)
|
|
{
|
|
actor->user.targetActor = pp->actor;
|
|
actor->user.Flags &= ~(SPR_WAIT_FOR_TRIGGER);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case TAG_TRIGGER_MISSILE_TRAP:
|
|
{
|
|
// reset traps so they fire immediately
|
|
DoTrapReset(pp->cursector->hitag);
|
|
break;
|
|
}
|
|
|
|
case TAG_TRIGGER_EXPLODING_SECTOR:
|
|
{
|
|
DoMatchEverything(nullptr, pp->cursector->hitag, -1);
|
|
break;
|
|
}
|
|
|
|
case TAG_SPAWN_ACTOR_TRIGGER:
|
|
{
|
|
DoMatchEverything(nullptr, pp->cursector->hitag, -1);
|
|
|
|
pp->cursector->hitag = 0;
|
|
pp->cursector->lotag = 0;
|
|
break;
|
|
}
|
|
|
|
case TAG_SO_EVENT_TRIGGER:
|
|
{
|
|
DoMatchEverything(nullptr, pp->cursector->hitag, -1);
|
|
|
|
pp->cursector->hitag = 0;
|
|
pp->cursector->lotag = 0;
|
|
|
|
PlaySound(DIGI_REGULARSWITCH, pp, v3df_none);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OperateContinuousTrigger(PLAYER* pp)
|
|
{
|
|
if (Prediction)
|
|
return;
|
|
|
|
if (!pp->insector())
|
|
return;
|
|
|
|
switch (pp->cursector->lotag)
|
|
{
|
|
case TAG_TRIGGER_MISSILE_TRAP:
|
|
{
|
|
DoTrapMatch(pp->cursector->hitag);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
short PlayerTakeSectorDamage(PLAYER* pp)
|
|
{
|
|
auto sectu = pp->cursector;
|
|
DSWActor* actor = pp->actor;
|
|
|
|
// the calling routine must make sure sectu exists
|
|
if ((actor->user.DamageTics -= synctics) < 0)
|
|
{
|
|
actor->user.DamageTics = DAMAGE_TIME;
|
|
|
|
PlayerUpdateHealth(pp, -sectu->damage);
|
|
PlayerCheckDeath(pp, nullptr);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Needed in order to see if Player should grunt if he can't find a wall to operate on
|
|
// If player is too far away, don't grunt
|
|
enum { PLAYER_SOUNDEVENT_TAG = 900 };
|
|
bool NearThings(PLAYER* pp)
|
|
{
|
|
HitInfo near;
|
|
|
|
// Check player's current sector for triggered sound
|
|
if (pp->cursector->hitag == PLAYER_SOUNDEVENT_TAG)
|
|
{
|
|
if (pp == Player+myconnectindex)
|
|
PlayerSound(pp->cursector->lotag, v3df_follow|v3df_dontpan,pp);
|
|
return false;
|
|
}
|
|
|
|
neartag(pp->pos, pp->cursector, pp->angle.ang.asbuild(), near, 1024, NTAG_SEARCH_LO_HI);
|
|
|
|
|
|
// hit a sprite? Check to see if it has sound info in it!
|
|
// This can work with any sprite!
|
|
if (near.actor() != nullptr)
|
|
{
|
|
auto actor = near.actor();
|
|
|
|
// Go through list of cases
|
|
if (actor->spr.hitag == PLAYER_SOUNDEVENT_TAG)
|
|
{
|
|
if (pp == Player+myconnectindex)
|
|
PlayerSound(actor->spr.lotag, v3df_follow|v3df_dontpan,pp);
|
|
}
|
|
return false; // Return false so he doesn't grunt
|
|
}
|
|
|
|
if (near.hitWall != nullptr)
|
|
{
|
|
// Check player's current sector for triggered sound
|
|
if (near.hitWall->hitag == PLAYER_SOUNDEVENT_TAG)
|
|
{
|
|
if (pp == Player+myconnectindex)
|
|
PlayerSound(near.hitWall->lotag, v3df_follow|v3df_dontpan,pp);
|
|
return false; // We are playing a sound so don't return true
|
|
}
|
|
return true;
|
|
}
|
|
// This only gets called if nothing else worked, check for nearness to a wall
|
|
{
|
|
HitInfo hit{};
|
|
short dang = pp->angle.ang.asbuild();
|
|
|
|
FAFhitscan(pp->pos.X, pp->pos.Y, pp->pos.Z - Z(30), pp->cursector, // Start position
|
|
bcos(dang), // X vector of 3D ang
|
|
bsin(dang), // Y vector of 3D ang
|
|
0, // Z vector of 3D ang
|
|
hit, CLIPMASK_MISSILE);
|
|
|
|
if (hit.hitSector == nullptr)
|
|
return false;
|
|
|
|
if (Distance(hit.hitpos.X, hit.hitpos.Y, pp->pos.X, pp->pos.Y) > 1500)
|
|
return false;
|
|
|
|
// hit a sprite?
|
|
if (hit.actor() != nullptr)
|
|
return false;
|
|
|
|
if (near.hitSector != nullptr)
|
|
return true;
|
|
|
|
if (hit.hitWall != nullptr)
|
|
{
|
|
// Near a plain old vanilla wall. Can't do anything but grunt.
|
|
if (!(hit.hitWall->extra & WALLFX_DONT_STICK) && pp == Player+myconnectindex)
|
|
{
|
|
if (StdRandomRange(1000) > 970)
|
|
PlayerSound(DIGI_HITTINGWALLS, v3df_follow|v3df_dontpan,pp);
|
|
else
|
|
PlayerSound(DIGI_SEARCHWALL, v3df_follow|v3df_dontpan,pp);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
short nti_cnt;
|
|
|
|
void NearTagList(NEAR_TAG_INFO* ntip, PLAYER* pp, int z, int dist, int type, int count)
|
|
{
|
|
short save_lotag, save_hitag;
|
|
HitInfo near;
|
|
|
|
|
|
neartag({ pp->pos.X, pp->pos.Y, z }, pp->cursector, pp->angle.ang.asbuild(), near, dist, type);
|
|
|
|
if (near.hitSector != nullptr)
|
|
{
|
|
auto ntsec = near.hitSector;
|
|
// save off values
|
|
save_lotag = ntsec->lotag;
|
|
save_hitag = ntsec->hitag;
|
|
|
|
ntip->dist = near.hitpos.X;
|
|
ntip->sectp = ntsec;
|
|
ntip->wallp = nullptr;
|
|
ntip->actor = nullptr;
|
|
nti_cnt++;
|
|
ntip++;
|
|
|
|
if (nti_cnt >= count)
|
|
return;
|
|
|
|
// remove them
|
|
ntsec->lotag = 0;
|
|
ntsec->hitag = 0;
|
|
|
|
NearTagList(ntip, pp, z, dist, type, count);
|
|
|
|
// reset off values
|
|
ntsec->lotag = save_lotag;
|
|
ntsec->hitag = save_hitag;
|
|
}
|
|
else if (near.hitWall != nullptr)
|
|
{
|
|
auto ntwall = near.hitWall;
|
|
// save off values
|
|
save_lotag = ntwall->lotag;
|
|
save_hitag = ntwall->hitag;
|
|
|
|
ntip->dist = near.hitpos.X;
|
|
ntip->sectp = nullptr;
|
|
ntip->wallp = ntwall;
|
|
ntip->actor = nullptr;
|
|
nti_cnt++;
|
|
ntip++;
|
|
|
|
if (nti_cnt >= count)
|
|
return;
|
|
|
|
// remove them
|
|
ntwall->lotag = 0;
|
|
ntwall->hitag = 0;
|
|
|
|
NearTagList(ntip, pp, z, dist, type, count);
|
|
|
|
// reset off values
|
|
ntwall->lotag = save_lotag;
|
|
ntwall->hitag = save_hitag;
|
|
}
|
|
else if (near.actor() != nullptr)
|
|
{
|
|
auto actor = near.actor();
|
|
// save off values
|
|
save_lotag = actor->spr.lotag;
|
|
save_hitag = actor->spr.hitag;
|
|
|
|
ntip->dist = near.hitpos.X;
|
|
ntip->sectp = nullptr;
|
|
ntip->wallp = nullptr;
|
|
ntip->actor = actor;
|
|
nti_cnt++;
|
|
ntip++;
|
|
|
|
if (nti_cnt >= count)
|
|
return;
|
|
|
|
// remove them
|
|
actor->spr.lotag = 0;
|
|
actor->spr.hitag = 0;
|
|
|
|
NearTagList(ntip, pp, z, dist, type, count);
|
|
|
|
// reset off values
|
|
actor->spr.lotag = save_lotag;
|
|
actor->spr.hitag = save_hitag;
|
|
}
|
|
else
|
|
{
|
|
ntip->dist = -1;
|
|
ntip->sectp = nullptr;
|
|
ntip->wallp = nullptr;
|
|
ntip->actor = nullptr;
|
|
nti_cnt++;
|
|
ntip++;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void BuildNearTagList(NEAR_TAG_INFO* ntip, int size, PLAYER* pp, int z, int dist, int type, int count)
|
|
{
|
|
memset(ntip, -1, size);
|
|
nti_cnt = 0;
|
|
NearTagList(ntip, pp, z, dist, type, count);
|
|
}
|
|
|
|
|
|
int DoPlayerGrabStar(PLAYER* pp)
|
|
{
|
|
int i;
|
|
|
|
// MUST check exact z's of each star or it will never work
|
|
for (i = 0; i < MAX_STAR_QUEUE; i++)
|
|
{
|
|
auto actor = StarQueue[i];
|
|
if (actor != nullptr)
|
|
{
|
|
if (FindDistance3D(actor->spr.pos.X - pp->pos.X, actor->spr.pos.Y - pp->pos.Y, actor->spr.pos.Z - pp->pos.Z + Z(12)) < 500)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i < MAX_STAR_QUEUE)
|
|
{
|
|
// Pull a star out of wall and up your ammo
|
|
PlayerUpdateAmmo(pp, WPN_STAR, 1);
|
|
PlaySound(DIGI_ITEM, StarQueue[i], v3df_none);
|
|
KillActor(StarQueue[i]);
|
|
StarQueue[i] = nullptr;
|
|
if (pp->WpnFlags & (BIT(WPN_STAR)))
|
|
return true;
|
|
pp->WpnFlags |= (BIT(WPN_STAR));
|
|
InitWeaponStar(pp);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
void PlayerOperateEnv(PLAYER* pp)
|
|
{
|
|
bool found;
|
|
|
|
if (Prediction || !pp->actor)
|
|
return;
|
|
|
|
//
|
|
// Switch & door activations
|
|
//
|
|
|
|
if (pp->input.actions & SB_OPEN)
|
|
{
|
|
if (pp->KeyPressBits & SB_OPEN)
|
|
{
|
|
// if space bar pressed
|
|
short nt_ndx;
|
|
NEAR_TAG_INFO nti[16];
|
|
|
|
if (DoPlayerGrabStar(pp))
|
|
{
|
|
pp->KeyPressBits &= ~SB_OPEN;
|
|
}
|
|
else
|
|
{
|
|
NearThings(pp); // Check for player sound specified in a level sprite
|
|
}
|
|
|
|
BuildNearTagList(nti, sizeof(nti), pp, pp->pos.Z, 2048L, NTAG_SEARCH_LO_HI, 8);
|
|
|
|
found = false;
|
|
|
|
// try and find a sprite
|
|
for (nt_ndx = 0; nti[nt_ndx].dist >= 0; nt_ndx++)
|
|
{
|
|
if (nti[nt_ndx].actor != nullptr && nti[nt_ndx].dist < 1024 + 768)
|
|
{
|
|
if (OperateSprite(nti[nt_ndx].actor, true))
|
|
{
|
|
pp->KeyPressBits &= ~SB_OPEN;
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if not found look at different z positions
|
|
if (!found)
|
|
{
|
|
int z[3];
|
|
DSWActor* plActor = pp->actor;
|
|
|
|
z[0] = plActor->spr.pos.Z - ActorSizeZ(plActor) - Z(10);
|
|
z[1] = plActor->spr.pos.Z;
|
|
z[2] = (z[0] + z[1]) >> 1;
|
|
|
|
for (unsigned i = 0; i < SIZ(z); i++)
|
|
{
|
|
BuildNearTagList(nti, sizeof(nti), pp, z[i], 1024 + 768L, NTAG_SEARCH_LO_HI, 8);
|
|
|
|
for (nt_ndx = 0; nti[nt_ndx].dist >= 0; nt_ndx++)
|
|
{
|
|
if (nti[nt_ndx].actor != nullptr && nti[nt_ndx].dist < 1024 + 768)
|
|
{
|
|
if (OperateSprite(nti[nt_ndx].actor, true))
|
|
{
|
|
pp->KeyPressBits &= ~SB_OPEN;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
int neartaghitdist;
|
|
sectortype* neartagsector;
|
|
|
|
neartaghitdist = nti[0].dist;
|
|
neartagsector = nti[0].sectp;
|
|
|
|
if (neartagsector != nullptr && neartaghitdist < 1024)
|
|
{
|
|
if (OperateSector(neartagsector, true))
|
|
{
|
|
// Release the key
|
|
pp->KeyPressBits &= ~SB_OPEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Trigger operations
|
|
//
|
|
|
|
switch (pp->cursector->lotag)
|
|
{
|
|
case TAG_VATOR:
|
|
DoVatorOperate(pp, pp->cursector);
|
|
DoSpikeOperate(pp->cursector);
|
|
DoRotatorOperate(pp, pp->cursector);
|
|
DoSlidorOperate(pp, pp->cursector);
|
|
break;
|
|
case TAG_SPRING_BOARD:
|
|
DoSpringBoard(pp);
|
|
pp->KeyPressBits &= ~SB_OPEN;
|
|
break;
|
|
case TAG_DOOR_ROTATE:
|
|
if (OperateSector(pp->cursector, true))
|
|
pp->KeyPressBits &= ~SB_OPEN;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reset the key when syncbit key is not in use
|
|
pp->KeyPressBits |= SB_OPEN;
|
|
}
|
|
|
|
// ////////////////////////////
|
|
//
|
|
// Sector Damage
|
|
//
|
|
// ////////////////////////////
|
|
|
|
sectortype* sectp = pp->cursector;
|
|
if (pp->insector() && sectp->hasU() && sectp->damage)
|
|
{
|
|
if ((sectp->flags & SECTFU_DAMAGE_ABOVE_SECTOR))
|
|
{
|
|
PlayerTakeSectorDamage(pp);
|
|
}
|
|
else if ((ActorZOfBottom(pp->actor) >= sectp->floorz) && !(pp->Flags & PF_DIVING))
|
|
{
|
|
PlayerTakeSectorDamage(pp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pp->actor->user.DamageTics = 0;
|
|
}
|
|
|
|
|
|
// ////////////////////////////
|
|
//
|
|
// Trigger stuff
|
|
//
|
|
// ////////////////////////////
|
|
|
|
OperateContinuousTrigger(pp);
|
|
|
|
// just changed sectors
|
|
if (pp->lastcursector != pp->cursector)
|
|
{
|
|
OperateTripTrigger(pp);
|
|
|
|
if (pp->insector() && (pp->cursector->extra & SECTFX_WARP_SECTOR))
|
|
{
|
|
if (!(pp->Flags2 & PF2_TELEPORTED))
|
|
{
|
|
DoPlayerWarpTeleporter(pp);
|
|
}
|
|
}
|
|
|
|
pp->Flags2 &= ~(PF2_TELEPORTED);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DoSineWaveFloor(void)
|
|
{
|
|
SINE_WAVE_FLOOR *swf;
|
|
int newz;
|
|
int wave;
|
|
int flags;
|
|
|
|
for (wave = 0; wave < MAX_SINE_WAVE; wave++)
|
|
{
|
|
for (swf = &SineWaveFloor[wave][0], flags = swf->flags; swf->sectp != nullptr && swf < &SineWaveFloor[wave][SIZ(SineWaveFloor[wave])]; swf++)
|
|
{
|
|
|
|
swf->sintable_ndx = NORM_ANGLE(swf->sintable_ndx + (synctics << swf->speed_shift));
|
|
|
|
if ((flags & SINE_FLOOR))
|
|
{
|
|
newz = swf->floor_origz + MulScale(swf->range, bsin(swf->sintable_ndx), 14);
|
|
swf->sectp->setfloorz(newz);
|
|
}
|
|
|
|
if ((flags & SINE_CEILING))
|
|
{
|
|
newz = swf->ceiling_origz + MulScale(swf->range, bsin(swf->sintable_ndx), 14);
|
|
swf->sectp->setceilingz(newz);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/* SLOPED SIN-WAVE FLOORS:
|
|
|
|
It's best to program sloped sin-wave floors in 2 steps:
|
|
1. First set the floorz of the floor as the sin code normally does it.
|
|
2. Adjust the slopes by calling alignflorslope once for each sector.
|
|
|
|
Note: For this to work, the first wall of each sin-wave sector must be
|
|
aligned on the same side of each sector for the entire strip.
|
|
*/
|
|
|
|
for (wave = 0; wave < MAX_SINE_WAVE; wave++)
|
|
{
|
|
for (swf = &SineWaveFloor[wave][0], flags = swf->flags; swf->sectp != nullptr && swf < &SineWaveFloor[wave][SIZ(SineWaveFloor[wave])]; swf++)
|
|
{
|
|
auto sect = swf->sectp;
|
|
if (!(sect->floorstat & CSTAT_SECTOR_SLOPE))
|
|
continue;
|
|
|
|
if ((flags & SINE_SLOPED))
|
|
{
|
|
walltype* wal;
|
|
if (sect->wallnum == 4)
|
|
{
|
|
//Set wal to the wall on the opposite side of the sector
|
|
wal = sect->firstWall() + 2;
|
|
|
|
//Pass (Sector, x, y, z)
|
|
alignflorslope(sect,wal->pos.X,wal->pos.Y, wal->nextSector()->floorz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void DoSineWaveWall(void)
|
|
{
|
|
SINE_WALL *sw;
|
|
int New;
|
|
short sw_num;
|
|
|
|
for (sw_num = 0; sw_num < MAX_SINE_WAVE; sw_num++)
|
|
{
|
|
for (sw = &SineWall[sw_num][0]; sw->wallp != nullptr && sw < &SineWall[sw_num][MAX_SINE_WALL_POINTS]; sw++)
|
|
{
|
|
auto wal = sw->wallp;
|
|
// move through the sintable
|
|
sw->sintable_ndx = NORM_ANGLE(sw->sintable_ndx + (synctics << sw->speed_shift));
|
|
|
|
if (!sw->type)
|
|
{
|
|
New = sw->orig_xy + MulScale(sw->range, bsin(sw->sintable_ndx), 14);
|
|
dragpoint(wal, wal->pos.X, New);
|
|
}
|
|
else
|
|
{
|
|
New = sw->orig_xy + MulScale(sw->range, bsin(sw->sintable_ndx), 14);
|
|
dragpoint(wal, New, wal->pos.Y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoAnim(int numtics)
|
|
{
|
|
int i, animval;
|
|
|
|
for (i = AnimCnt - 1; i >= 0; i--)
|
|
{
|
|
animval = Anim[i].Addr(true);
|
|
|
|
// if LESS THAN goal
|
|
if (animval < Anim[i].goal)
|
|
{
|
|
// move it
|
|
animval += (numtics * PIXZ(Anim[i].vel));
|
|
|
|
Anim[i].vel += Anim[i].vel_adj * numtics;
|
|
|
|
// if the other way make it equal
|
|
if (animval > Anim[i].goal)
|
|
animval = Anim[i].goal;
|
|
}
|
|
|
|
// if GREATER THAN goal
|
|
if (animval > Anim[i].goal)
|
|
{
|
|
animval -= (numtics * PIXZ(Anim[i].vel));
|
|
|
|
Anim[i].vel += Anim[i].vel_adj * numtics;
|
|
|
|
if (animval < Anim[i].goal)
|
|
animval = Anim[i].goal;
|
|
}
|
|
|
|
Anim[i].Addr(true) =animval;
|
|
|
|
// EQUAL this entry has finished
|
|
if (animval == Anim[i].goal)
|
|
{
|
|
ANIM_CALLBACKp acp = Anim[i].callback;
|
|
|
|
// do a callback when done if not nullptr
|
|
if (Anim[i].callback)
|
|
(*Anim[i].callback)(&Anim[i], Anim[i].callbackdata);
|
|
|
|
// only delete it if the callback has not changed
|
|
// Logic here is that if the callback changed then something
|
|
// else must be happening with it - dont delete it
|
|
if (Anim[i].callback == acp)
|
|
{
|
|
// decrement the count
|
|
AnimCnt--;
|
|
|
|
// move the last entry to the current one to free the last
|
|
// entry up
|
|
Anim[i] = Anim[AnimCnt];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AnimClear(void)
|
|
{
|
|
AnimCnt = 0;
|
|
}
|
|
|
|
short AnimGetGoal(int animtype, int animindex, DSWActor* animactor)
|
|
{
|
|
int i, j;
|
|
|
|
j = -1;
|
|
for (i = 0; i < AnimCnt; i++)
|
|
{
|
|
if (animtype == Anim[i].animtype && animindex == Anim[i].animindex && animactor == Anim[i].animactor )
|
|
{
|
|
j = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return j;
|
|
}
|
|
|
|
void AnimDelete(int animtype, int animindex, DSWActor* animactor)
|
|
{
|
|
int i, j;
|
|
|
|
j = -1;
|
|
for (i = 0; i < AnimCnt; i++)
|
|
{
|
|
if (animtype == Anim[i].animtype && animindex == Anim[i].animindex && animactor == Anim[i].animactor )
|
|
{
|
|
j = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (j == -1)
|
|
return;
|
|
|
|
// decrement the count
|
|
AnimCnt--;
|
|
|
|
// move the last entry to the current one to free the last entry up
|
|
Anim[j] = Anim[AnimCnt];
|
|
}
|
|
|
|
|
|
int AnimSet(int animtype, int animindex, DSWActor* animactor, fixed_t thegoal, int thevel)
|
|
{
|
|
int i, j;
|
|
|
|
ASSERT(AnimCnt < MAXANIM - 1);
|
|
|
|
j = AnimCnt;
|
|
|
|
// look for existing animation and reset it
|
|
for (i = 0; i < AnimCnt; i++)
|
|
{
|
|
if (animtype == Anim[i].animtype && animindex == Anim[i].animindex && animactor == Anim[i].animactor )
|
|
{
|
|
j = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Anim[j].animtype = animtype;
|
|
Anim[j].animindex = animindex;
|
|
Anim[j].animactor = animactor;
|
|
Anim[j].goal = thegoal;
|
|
Anim[j].vel = Z(thevel);
|
|
Anim[j].vel_adj = 0;
|
|
Anim[j].callback = nullptr;
|
|
Anim[j].callbackdata = nullptr;
|
|
|
|
if (j == AnimCnt)
|
|
AnimCnt++;
|
|
|
|
return j;
|
|
}
|
|
|
|
short AnimSetCallback(short anim_ndx, ANIM_CALLBACKp call, SECTOR_OBJECT* data)
|
|
{
|
|
ASSERT(anim_ndx < AnimCnt);
|
|
|
|
if (anim_ndx == -1)
|
|
return anim_ndx;
|
|
|
|
Anim[anim_ndx].callback = call;
|
|
Anim[anim_ndx].callbackdata = data;
|
|
|
|
return anim_ndx;
|
|
}
|
|
|
|
short AnimSetVelAdj(short anim_ndx, short vel_adj)
|
|
{
|
|
ASSERT(anim_ndx < AnimCnt);
|
|
|
|
if (anim_ndx == -1)
|
|
return anim_ndx;
|
|
|
|
Anim[anim_ndx].vel_adj = vel_adj;
|
|
|
|
return anim_ndx;
|
|
}
|
|
|
|
|
|
|
|
void DoPanning(void)
|
|
{
|
|
int nx, ny;
|
|
int i;
|
|
sectortype* sectp;
|
|
walltype* wallp;
|
|
|
|
SWStatIterator it(STAT_FLOOR_PAN);
|
|
while (auto actor = it.Next())
|
|
{
|
|
sectp = actor->sector();
|
|
|
|
nx = MulScale(actor->spr.xvel, bcos(actor->spr.ang), 20);
|
|
ny = MulScale(actor->spr.xvel, bsin(actor->spr.ang), 20);
|
|
|
|
sectp->addfloorxpan((float)nx);
|
|
sectp->addfloorypan((float)ny);
|
|
}
|
|
|
|
it.Reset(STAT_CEILING_PAN);
|
|
while (auto actor = it.Next())
|
|
{
|
|
sectp = actor->sector();
|
|
|
|
nx = MulScale(actor->spr.xvel, bcos(actor->spr.ang), 20);
|
|
ny = MulScale(actor->spr.xvel, bsin(actor->spr.ang), 20);
|
|
|
|
sectp->addceilingxpan((float)nx);
|
|
sectp->addceilingypan((float)ny);
|
|
}
|
|
|
|
it.Reset(STAT_WALL_PAN);
|
|
while (auto actor = it.Next())
|
|
{
|
|
wallp = actor->tempwall;
|
|
|
|
nx = MulScale(actor->spr.xvel, bcos(actor->spr.ang), 20);
|
|
ny = MulScale(actor->spr.xvel, bsin(actor->spr.ang), 20);
|
|
|
|
wallp->addxpan((float)nx);
|
|
wallp->addypan((float)ny);
|
|
}
|
|
}
|
|
|
|
|
|
void DoSector(void)
|
|
{
|
|
SECTOR_OBJECT* sop;
|
|
bool riding;
|
|
int sync_flag;
|
|
short pnum;
|
|
int min_dist,dist,a,b,c;
|
|
PLAYER* pp;
|
|
|
|
for (sop = SectorObject; sop < &SectorObject[MAX_SECTOR_OBJECTS]; sop++)
|
|
{
|
|
|
|
if (SO_EMPTY(sop))
|
|
continue;
|
|
|
|
|
|
riding = false;
|
|
min_dist = 999999;
|
|
|
|
TRAVERSE_CONNECT(pnum)
|
|
{
|
|
pp = &Player[pnum];
|
|
|
|
if (pp->sop_riding == sop)
|
|
{
|
|
riding = true;
|
|
pp->sop_riding = nullptr;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
DISTANCE(pp->pos.X, pp->pos.Y, sop->pmid.X, sop->pmid.Y, dist, a, b, c);
|
|
if (dist < min_dist)
|
|
min_dist = dist;
|
|
}
|
|
}
|
|
|
|
if (sop->Animator)
|
|
{
|
|
(*sop->Animator)(sop);
|
|
continue;
|
|
}
|
|
|
|
// force sync SOs to be updated regularly
|
|
if ((sync_flag = (sop->flags & (SOBJ_SYNC1|SOBJ_SYNC2))) != 0)
|
|
{
|
|
if (sync_flag == SOBJ_SYNC1)
|
|
MoveSectorObjects(sop, synctics);
|
|
else
|
|
{
|
|
if (MoveSkip2 == 0)
|
|
MoveSectorObjects(sop, synctics*2);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (riding)
|
|
{
|
|
// if riding move smoothly
|
|
// update every time
|
|
MoveSectorObjects(sop, synctics);
|
|
}
|
|
else
|
|
{
|
|
if (min_dist < 15000)
|
|
{
|
|
// if close update every other time
|
|
if (MoveSkip2 == 0)
|
|
MoveSectorObjects(sop, synctics * 2);
|
|
}
|
|
else
|
|
{
|
|
// if further update every 4th time
|
|
if (MoveSkip4 == 0)
|
|
MoveSectorObjects(sop, synctics * 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
DoPanning();
|
|
DoLighting();
|
|
DoSineWaveFloor();
|
|
DoSineWaveWall();
|
|
DoSpringBoardDown();
|
|
}
|
|
|
|
|
|
#include "saveable.h"
|
|
|
|
static saveable_code saveable_sector_code[] =
|
|
{
|
|
SAVE_CODE(DoSpawnSpot),
|
|
};
|
|
|
|
saveable_module saveable_sector =
|
|
{
|
|
// code
|
|
saveable_sector_code,
|
|
SIZ(saveable_sector_code),
|
|
|
|
// data
|
|
nullptr,
|
|
0
|
|
};
|
|
|
|
|
|
END_SW_NS
|