raze/source/games/blood/src/triggers.cpp
2022-10-25 07:06:49 +02:00

2607 lines
66 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT
This file is part of NBlood.
NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
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.
*/
//-------------------------------------------------------------------------
#include "ns.h" // Must come before everything else!
#include <random>
#include "build.h"
#include "blood.h"
#include "misc.h"
#include "d_net.h"
BEGIN_BLD_NS
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
unsigned int GetWaveValue(unsigned int nPhase, int nType)
{
switch (nType)
{
case 0:
return 0x8000 - (Cos(FixedToInt(nPhase << 10)) >> 15);
case 1:
return nPhase;
case 2:
return 0x10000 - (Cos(FixedToInt(nPhase << 9)) >> 14);
case 3:
return Sin(FixedToInt(nPhase << 9)) >> 14;
}
return nPhase;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool SetSpriteState(DBloodActor* actor, int nState, DBloodActor* initiator)
{
if ((actor->xspr.busy & 0xffff) == 0 && actor->xspr.state == nState)
return 0;
actor->xspr.busy = IntToFixed(nState);
actor->xspr.state = nState;
evKillActor(actor, initiator);
if ((actor->spr.flags & kHitagRespawn) != 0 && actor->spr.inittype >= kDudeBase && actor->spr.inittype < kDudeMax)
{
actor->xspr.respawnPending = 3;
evPostActor(actor, gGameOptions.nMonsterRespawnTime, kCallbackRespawn);
return 1;
}
if (actor->xspr.restState != nState && actor->xspr.waitTime > 0)
evPostActor(actor, (actor->xspr.waitTime * 120) / 10, actor->xspr.restState ? kCmdOn : kCmdOff, initiator);
if (actor->xspr.txID)
{
if (actor->xspr.command != kCmdLink && actor->xspr.triggerOn && actor->xspr.state)
evSendActor(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
if (actor->xspr.command != kCmdLink && actor->xspr.triggerOff && !actor->xspr.state)
evSendActor(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
}
return 1;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool SetWallState(walltype* pWall, int nState, DBloodActor* initiator)
{
auto pXWall = &pWall->xw();
if ((pXWall->busy & 0xffff) == 0 && pXWall->state == nState)
return 0;
pXWall->busy = IntToFixed(nState);
pXWall->state = nState;
evKillWall(pWall, initiator);
if (pXWall->restState != nState && pXWall->waitTime > 0)
evPostWall(pWall, (pXWall->waitTime * 120) / 10, pXWall->restState ? kCmdOn : kCmdOff, initiator);
if (pXWall->txID)
{
if (pXWall->command != kCmdLink && pXWall->triggerOn && pXWall->state)
evSendWall(pWall, pXWall->txID, (COMMAND_ID)pXWall->command, initiator);
if (pXWall->command != kCmdLink && pXWall->triggerOff && !pXWall->state)
evSendWall(pWall, pXWall->txID, (COMMAND_ID)pXWall->command, initiator);
}
return 1;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool SetSectorState(sectortype* pSector, int nState, DBloodActor* initiator)
{
assert(pSector->hasX());
auto pXSector = &pSector->xs();
if ((pXSector->busy & 0xffff) == 0 && pXSector->state == nState)
return 0;
pXSector->busy = IntToFixed(nState);
pXSector->state = nState;
evKillSector(pSector, initiator);
if (nState == 1)
{
if (pXSector->command != kCmdLink && pXSector->triggerOn && pXSector->txID)
evSendSector(pSector, pXSector->txID, (COMMAND_ID)pXSector->command, initiator);
if (pXSector->stopOn)
{
pXSector->stopOn = 0;
pXSector->stopOff = 0;
}
else if (pXSector->reTriggerA)
evPostSector(pSector, (pXSector->waitTimeA * 120) / 10, kCmdOff, initiator);
}
else
{
if (pXSector->command != kCmdLink && pXSector->triggerOff && pXSector->txID)
evSendSector(pSector, pXSector->txID, (COMMAND_ID)pXSector->command, initiator);
if (pXSector->stopOff)
{
pXSector->stopOn = 0;
pXSector->stopOff = 0;
}
else if (pXSector->reTriggerB)
evPostSector(pSector, (pXSector->waitTimeB * 120) / 10, kCmdOn, initiator);
}
return 1;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
TArray<BUSY> gBusy;
void AddBusy(sectortype* pSector, BUSYID a2, int nDelta)
{
assert(nDelta != 0);
for (auto& b : gBusy)
{
if (b.sect == pSector && b.type == a2)
{
b.delta = nDelta;
return;
}
}
if (VanillaMode() && gBusy.Size() == 128) return;
BUSY b = { pSector, nDelta, nDelta > 0 ? 0 : 65536, a2 };
gBusy.Push(b);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ReverseBusy(sectortype* pSector, BUSYID a2)
{
for (auto& b : gBusy)
{
if (b.sect == pSector && b.type == a2)
{
b.delta = -b.delta;
break;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
unsigned int GetSourceBusy(EVENT& a1)
{
if (a1.isSector())
{
auto sect = a1.getSector();
return sect->hasX() ? sect->xs().busy : 0;
}
else if (a1.isWall())
{
auto wal = a1.getWall();
return wal->hasX() ? wal->xw().busy : 0;
}
else if (a1.isActor())
{
auto pActor = a1.getActor();
return pActor && pActor->hasX() ? pActor->xspr.busy : false;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void LifeLeechOperate(DBloodActor* actor, EVENT event)
{
switch (event.cmd)
{
case kCmdSpritePush:
{
int nPlayer = actor->xspr.data4;
if (nPlayer >= 0 && nPlayer < kMaxPlayers && playeringame[nPlayer])
{
PLAYER* pPlayer = &gPlayer[nPlayer];
if (pPlayer->actor->xspr.health > 0)
{
evKillActor(actor);
pPlayer->ammoCount[8] = ClipHigh(pPlayer->ammoCount[8] + actor->xspr.data3, gAmmoInfo[8].max);
pPlayer->hasWeapon[kWeapLifeLeech] = 1;
if (pPlayer->curWeapon != kWeapLifeLeech)
{
if (!VanillaMode() && checkLitSprayOrTNT(pPlayer)) // if tnt/spray is actively used, do not switch weapon
break;
pPlayer->weaponState = 0;
pPlayer->nextWeapon = kWeapLifeLeech;
}
}
}
break;
}
case kCmdSpriteProximity:
{
auto target = actor->GetTarget();
if (target)
{
if (!actor->xspr.stateTimer)
{
if (target->spr.statnum == kStatDude && !(target->spr.flags & 32) && target->hasX())
{
int top, bottom;
GetActorExtents(actor, &top, &bottom);
int nType = target->spr.type - kDudeBase;
DUDEINFO* pDudeInfo = getDudeInfo(nType + kDudeBase);
int z1 = (top - actor->int_pos().Z) - 256;
int x = target->int_pos().X;
int y = target->int_pos().Y;
int z = target->int_pos().Z;
int nDist = approxDist(x - actor->int_pos().X, y - actor->int_pos().Y);
if (nDist != 0 && cansee(actor->int_pos().X, actor->int_pos().Y, top, actor->sector(), x, y, z, target->sector()))
{
int t = DivScale(nDist, 0x1aaaaa, 12);
x += (target->int_vel().X * t) >> 12;
y += (target->int_vel().Y * t) >> 12;
auto angBak = actor->spr.angle;
actor->spr.angle = VecToAngle(x - actor->int_pos().X, y - actor->int_pos().Y);
int dx = bcos(actor->int_ang());
int dy = bsin(actor->int_ang());
int tz = target->int_pos().Z - (target->spr.yrepeat * pDudeInfo->aimHeight) * 4;
int dz = DivScale(tz - top - 256, nDist, 10);
int nMissileType = kMissileLifeLeechAltNormal + (actor->xspr.data3 ? 1 : 0);
int t2;
if (!actor->xspr.data3)
t2 = 120 / 10;
else
t2 = (3 * 120) / 10;
auto missile = actFireMissile(actor, 0, z1, dx, dy, dz, nMissileType);
if (missile)
{
missile->SetOwner(actor);
actor->xspr.stateTimer = 1;
evPostActor(actor, t2, kCallbackLeechStateTimer);
actor->xspr.data3 = ClipLow(actor->xspr.data3 - 1, 0);
if (!VanillaMode()) // disable collisions so lifeleech doesn't do that weird bobbing
missile->spr.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
}
actor->spr.angle = angBak;
}
}
}
}
return;
}
}
actPostSprite(actor, kStatFree);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ActivateGenerator(DBloodActor*);
void OperateSprite(DBloodActor* actor, EVENT event)
{
DBloodActor* initiator = event.initiator;
#ifdef NOONE_EXTENSIONS
if (gModernMap && modernTypeOperateSprite(actor, event))
return;
#endif
switch (event.cmd) {
case kCmdLock:
actor->xspr.locked = 1;
return;
case kCmdUnlock:
actor->xspr.locked = 0;
return;
case kCmdToggleLock:
actor->xspr.locked = actor->xspr.locked ^ 1;
return;
}
if (actor->spr.statnum == kStatDude && actor->spr.type >= kDudeBase && actor->spr.type < kDudeMax) {
switch (event.cmd) {
case kCmdOff:
SetSpriteState(actor, 0, initiator);
break;
case kCmdSpriteProximity:
if (actor->xspr.state) break;
[[fallthrough]];
case kCmdOn:
case kCmdSpritePush:
case kCmdSpriteTouch:
if (!actor->xspr.state) SetSpriteState(actor, 1, initiator);
aiActivateDude(actor);
break;
}
return;
}
switch (actor->spr.type) {
case kTrapMachinegun:
if (actor->xspr.health <= 0) break;
switch (event.cmd) {
case kCmdOff:
if (!SetSpriteState(actor, 0, initiator)) break;
seqSpawn(40, actor, -1);
break;
case kCmdOn:
if (!SetSpriteState(actor, 1, initiator)) break;
seqSpawn(38, actor, nMGunOpenClient);
if (actor->xspr.data1 > 0)
actor->xspr.data2 = actor->xspr.data1;
break;
}
break;
case kThingFallingRock:
if (SetSpriteState(actor, 1, initiator))
actor->spr.flags |= 7;
break;
case kThingWallCrack:
if (SetSpriteState(actor, 0, initiator))
actPostSprite(actor, kStatFree);
break;
case kThingCrateFace:
if (SetSpriteState(actor, 0, initiator))
actPostSprite(actor, kStatFree);
break;
case kTrapZapSwitchable:
switch (event.cmd) {
case kCmdOff:
actor->xspr.state = 0;
actor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
break;
case kCmdOn:
actor->xspr.state = 1;
actor->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
actor->spr.cstat |= CSTAT_SPRITE_BLOCK;
break;
case kCmdToggle:
actor->xspr.state ^= 1;
actor->spr.cstat ^= CSTAT_SPRITE_INVISIBLE;
actor->spr.cstat ^= CSTAT_SPRITE_BLOCK;
break;
}
break;
case kTrapFlame:
switch (event.cmd) {
case kCmdOff:
if (!SetSpriteState(actor, 0, initiator)) break;
seqSpawn(40, actor, -1);
sfxKill3DSound(actor, 0, -1);
break;
case kCmdOn:
if (!SetSpriteState(actor, 1, initiator)) break;
seqSpawn(38, actor, -1);
sfxPlay3DSound(actor, 441, 0, 0);
break;
}
break;
case kSwitchPadlock:
switch (event.cmd) {
case kCmdOff:
SetSpriteState(actor, 0, initiator);
break;
case kCmdOn:
if (!SetSpriteState(actor, 1, initiator)) break;
seqSpawn(37, actor, -1);
break;
default:
SetSpriteState(actor, actor->xspr.state ^ 1, initiator);
if (actor->xspr.state) seqSpawn(37, actor, -1);
break;
}
break;
case kSwitchToggle:
switch (event.cmd) {
case kCmdOff:
if (!SetSpriteState(actor, 0, initiator)) break;
sfxPlay3DSound(actor, actor->xspr.data2, 0, 0);
break;
case kCmdOn:
if (!SetSpriteState(actor, 1, initiator)) break;
sfxPlay3DSound(actor, actor->xspr.data1, 0, 0);
break;
default:
if (!SetSpriteState(actor, actor->xspr.state ^ 1, initiator)) break;
if (actor->xspr.state) sfxPlay3DSound(actor, actor->xspr.data1, 0, 0);
else sfxPlay3DSound(actor, actor->xspr.data2, 0, 0);
break;
}
break;
case kSwitchOneWay:
switch (event.cmd) {
case kCmdOff:
if (!SetSpriteState(actor, 0, initiator)) break;
sfxPlay3DSound(actor, actor->xspr.data2, 0, 0);
break;
case kCmdOn:
if (!SetSpriteState(actor, 1, initiator)) break;
sfxPlay3DSound(actor, actor->xspr.data1, 0, 0);
break;
default:
if (!SetSpriteState(actor, actor->xspr.restState ^ 1, initiator)) break;
if (actor->xspr.state) sfxPlay3DSound(actor, actor->xspr.data1, 0, 0);
else sfxPlay3DSound(actor, actor->xspr.data2, 0, 0);
break;
}
break;
case kSwitchCombo:
switch (event.cmd) {
case kCmdOff:
actor->xspr.data1--;
if (actor->xspr.data1 < 0)
actor->xspr.data1 += actor->xspr.data3;
break;
default:
actor->xspr.data1++;
if (actor->xspr.data1 >= actor->xspr.data3)
actor->xspr.data1 -= actor->xspr.data3;
break;
}
sfxPlay3DSound(actor, actor->xspr.data4, -1, 0);
if (actor->xspr.command == kCmdLink && actor->xspr.txID > 0)
evSendActor(actor, actor->xspr.txID, kCmdLink, initiator);
if (actor->xspr.data1 == actor->xspr.data2)
SetSpriteState(actor, 1, initiator);
else
SetSpriteState(actor, 0, initiator);
break;
case kMarkerDudeSpawn:
if (gGameOptions.nMonsterSettings && actor->xspr.data1 >= kDudeBase && actor->xspr.data1 < kDudeMax)
{
auto spawned = actSpawnDude(actor, actor->xspr.data1, -1);
if (spawned) {
gKillMgr.AddKill(spawned);
switch (actor->xspr.data1) {
case kDudeBurningInnocent:
case kDudeBurningCultist:
case kDudeBurningZombieAxe:
case kDudeBurningZombieButcher:
case kDudeBurningTinyCaleb:
case kDudeBurningBeast: {
spawned->xspr.health = getDudeInfo(actor->xspr.data1)->startHealth << 4;
spawned->xspr.burnTime = 10;
spawned->SetTarget(nullptr);
aiActivateDude(spawned);
break;
default:
break;
}
}
}
}
break;
case kMarkerEarthQuake:
actor->xspr.triggerOn = 0;
actor->xspr.isTriggered = 1;
SetSpriteState(actor, 1, initiator);
for (int p = connecthead; p >= 0; p = connectpoint2[p]) {
auto vec = actor->int_pos() - gPlayer[p].actor->int_pos();
int dx = (vec.X) >> 4;
int dy = (vec.Y) >> 4;
int dz = (vec.Z) >> 8;
int nDist = dx * dx + dy * dy + dz * dz + 0x40000;
gPlayer[p].quakeEffect = DivScale(actor->xspr.data1, nDist, 16);
}
break;
case kThingTNTBarrel:
if (actor->spr.flags & kHitagRespawn) return;
[[fallthrough]];
case kThingArmedTNTStick:
case kThingArmedTNTBundle:
case kThingArmedSpray:
actExplodeSprite(actor);
break;
case kTrapExploder:
switch (event.cmd) {
case kCmdOn:
SetSpriteState(actor, 1, initiator);
break;
default:
actor->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
actExplodeSprite(actor);
break;
}
break;
case kThingArmedRemoteBomb:
if (actor->spr.statnum != kStatRespawn) {
if (event.cmd != kCmdOn) actExplodeSprite(actor);
else {
sfxPlay3DSound(actor, 454, 0, 0);
evPostActor(actor, 18, kCmdOff, initiator);
}
}
break;
case kThingArmedProxBomb:
if (actor->spr.statnum != kStatRespawn) {
switch (event.cmd) {
case kCmdSpriteProximity:
if (actor->xspr.state) break;
sfxPlay3DSound(actor, 452, 0, 0);
evPostActor(actor, 30, kCmdOff, initiator);
actor->xspr.state = 1;
[[fallthrough]];
case kCmdOn:
sfxPlay3DSound(actor, 451, 0, 0);
actor->xspr.Proximity = 1;
break;
default:
actExplodeSprite(actor);
break;
}
}
break;
case kThingDroppedLifeLeech:
LifeLeechOperate(actor, event);
break;
case kGenTrigger:
case kGenDripWater:
case kGenDripBlood:
case kGenMissileFireball:
case kGenMissileEctoSkull:
case kGenDart:
case kGenBubble:
case kGenBubbleMulti:
case kGenSound:
switch (event.cmd) {
case kCmdOff:
SetSpriteState(actor, 0, initiator);
break;
case kCmdRepeat:
if (actor->spr.type != kGenTrigger) ActivateGenerator(actor);
if (actor->xspr.txID) evSendActor(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
if (actor->xspr.busyTime > 0) {
int nRand = Random2(actor->xspr.data1);
evPostActor(actor, 120 * (nRand + actor->xspr.busyTime) / 10, kCmdRepeat, initiator);
}
break;
default:
if (!actor->xspr.state) {
SetSpriteState(actor, 1, initiator);
evPostActor(actor, 0, kCmdRepeat, initiator);
}
break;
}
break;
case kSoundPlayer:
if (gGameOptions.nGameType == 0)
{
PLAYER* pPlayer = &gPlayer[myconnectindex];
if (pPlayer->actor->xspr.health <= 0)
break;
pPlayer->restTime = 0;
}
sndStartSample(actor->xspr.data1, -1, 1, 0, CHANF_FORCE);
break;
case kThingObjectGib:
case kThingObjectExplode:
case kThingBloodBits:
case kThingBloodChunks:
case kThingZombieHead:
switch (event.cmd) {
case kCmdOff:
if (!SetSpriteState(actor, 0, initiator)) break;
actActivateGibObject(actor);
break;
case kCmdOn:
if (!SetSpriteState(actor, 1, initiator)) break;
actActivateGibObject(actor);
break;
default:
if (!SetSpriteState(actor, actor->xspr.state ^ 1, initiator)) break;
actActivateGibObject(actor);
break;
}
break;
default:
switch (event.cmd) {
case kCmdOff:
SetSpriteState(actor, 0, initiator);
break;
case kCmdOn:
SetSpriteState(actor, 1, initiator);
break;
default:
SetSpriteState(actor, actor->xspr.state ^ 1, initiator);
break;
}
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetupGibWallState(walltype* pWall, XWALL* pXWall)
{
walltype* pWall2 = NULL;
if (pWall->twoSided())
pWall2 = pWall->nextWall();
if (pXWall->state)
{
pWall->cstat &= ~(CSTAT_WALL_BLOCK | CSTAT_WALL_BLOCK_HITSCAN);
if (pWall2)
{
pWall2->cstat &= ~(CSTAT_WALL_BLOCK | CSTAT_WALL_BLOCK_HITSCAN);
pWall->cstat &= ~CSTAT_WALL_MASKED;
pWall2->cstat &= ~CSTAT_WALL_MASKED;
}
return;
}
bool bVector = pXWall->triggerVector != 0;
pWall->cstat |= CSTAT_WALL_BLOCK;
if (bVector)
pWall->cstat |= CSTAT_WALL_BLOCK_HITSCAN;
if (pWall2)
{
pWall2->cstat &= ~CSTAT_WALL_BLOCK;
if (bVector)
pWall2->cstat |= CSTAT_WALL_BLOCK_HITSCAN;
pWall->cstat |= CSTAT_WALL_MASKED;
pWall2->cstat |= CSTAT_WALL_MASKED;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void OperateWall(walltype* pWall, EVENT event)
{
DBloodActor* initiator = event.initiator;
auto pXWall = &pWall->xw();
switch (event.cmd) {
case kCmdLock:
pXWall->locked = 1;
return;
case kCmdUnlock:
pXWall->locked = 0;
return;
case kCmdToggleLock:
pXWall->locked ^= 1;
return;
}
#ifdef NOONE_EXTENSIONS
if (gModernMap && modernTypeOperateWall(pWall, event))
return;
#endif
switch (pWall->type) {
case kWallGib:
bool bStatus;
switch (event.cmd) {
case kCmdOn:
case kCmdWallImpact:
bStatus = SetWallState(pWall, 1, initiator);
break;
case kCmdOff:
bStatus = SetWallState(pWall, 0, initiator);
break;
default:
bStatus = SetWallState(pWall, pXWall->state ^ 1, initiator);
break;
}
if (bStatus) {
SetupGibWallState(pWall, pXWall);
if (pXWall->state) {
auto vel = DVector3(100, 100, 250) * (1. / FRACUNIT);
int nType = ClipRange(pXWall->data, 0, 31);
if (nType > 0)
GibWall(pWall, (GIBTYPE)nType, &vel);
}
}
return;
default:
switch (event.cmd) {
case kCmdOff:
SetWallState(pWall, 0, initiator);
break;
case kCmdOn:
SetWallState(pWall, 1, initiator);
break;
default:
SetWallState(pWall, pXWall->state ^ 1, initiator);
break;
}
return;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SectorStartSound(sectortype* pSector, int nState)
{
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatDecoration && actor->spr.type == kSoundSector && actor->hasX())
{
if (nState)
{
if (actor->xspr.data3)
sfxPlay3DSound(actor, actor->xspr.data3, 0, 0);
}
else
{
if (actor->xspr.data1)
sfxPlay3DSound(actor, actor->xspr.data1, 0, 0);
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SectorEndSound(sectortype* pSector, int nState)
{
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatDecoration && actor->spr.type == kSoundSector && actor->hasX())
{
if (nState)
{
if (actor->xspr.data2)
sfxPlay3DSound(actor, actor->xspr.data2, 0, 0);
}
else
{
if (actor->xspr.data4)
sfxPlay3DSound(actor, actor->xspr.data4, 0, 0);
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void PathSound(sectortype* pSector, int nSound)
{
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatDecoration && actor->spr.type == kSoundSector)
sfxPlay3DSound(actor, nSound, 0, 0);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void TranslateSector(sectortype* pSector, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, bool bAllWalls)
{
double a2f = FixedToFloat(a2);
double a3f = FixedToFloat(a3);
XSECTOR* pXSector = &pSector->xs();
int v20 = interpolatedvalue(a6, a9, a2f);
int vc = interpolatedvalue(a6, a9, a3f);
int v24 = interpolatedvalue(a7, a10, a2f);
int v8 = interpolatedvalue(a7, a10, a3f);
int v44 = interpolatedvalue(a8, a11, a2f);
int ang = interpolatedvalue(a8, a11, a3f);
int v14 = ang - v44;
DVector2 pivot = { a4 * inttoworld, a5 * inttoworld };
DVector2 offset = { (vc - a4) * inttoworld, (v8 - a5) * inttoworld };
DVector2 aoffset = { (vc) * inttoworld, (v8) * inttoworld };
DVector2 position = { (vc - v20) * inttoworld, (v8 - v24) * inttoworld };
auto angle = DAngle::fromBuild(ang);
auto angleofs = DAngle::fromBuild(v14);
auto rotatewall = [=](walltype* wal, DAngle angle, const DVector2& offset)
{
auto vec = wal->baseWall;
if (angle.Degrees() != 0)
vec = rotatepoint(pivot, vec, angle);
vec += offset;
vertexscan(wal, [&](walltype* wal)
{
viewInterpolateWall(wal);
wal->move(vec);
});
};
if (bAllWalls)
{
for (auto& wal : wallsofsector(pSector))
{
rotatewall(&wal, angle, offset);
}
}
else
{
for (auto& wal : wallsofsector(pSector))
{
auto p2Wall = wal.point2Wall();
if (wal.cstat & CSTAT_WALL_MOVE_FORWARD)
{
rotatewall(&wal, angle, offset);
if ((p2Wall->cstat & CSTAT_WALL_MOVE_MASK) == 0)
{
rotatewall(p2Wall, angle, offset);
}
continue;
}
if (wal.cstat & CSTAT_WALL_MOVE_BACKWARD)
{
rotatewall(&wal, -angle, -offset);
if ((p2Wall->cstat & CSTAT_WALL_MOVE_MASK) == 0)
{
rotatewall(p2Wall, -angle, -offset);
}
continue;
}
}
}
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
// allow to move markers by sector movements in game if flags 1 is added in editor.
switch (actor->spr.statnum) {
case kStatMarker:
case kStatPathMarker:
#ifdef NOONE_EXTENSIONS
if (!gModernMap || !(actor->spr.flags & 0x1)) continue;
#else
continue;
#endif
break;
}
int x = int(actor->basePoint.X * worldtoint);
int y = int(actor->basePoint.Y * worldtoint);
if (actor->spr.cstat & CSTAT_SPRITE_MOVE_FORWARD)
{
auto spot = rotatepoint(pivot, actor->basePoint, angle);
viewBackupSpriteLoc(actor);
actor->spr.pos.XY() = spot + aoffset - pivot;
actor->spr.angle += angleofs;
}
else if (actor->spr.cstat & CSTAT_SPRITE_MOVE_REVERSE)
{
// fix Y arg in RotatePoint for reverse (green) moving sprites. (Original Blood bug?)
DVector2 pivotDy(pivot.X, gModernMap ? pivot.Y : pivot.X);
auto spot = rotatepoint(pivotDy, actor->basePoint, angle);
viewBackupSpriteLoc(actor);
actor->spr.pos.XY() = spot - aoffset + pivot;
actor->spr.angle += angleofs;
}
else if (pXSector->Drag)
{
double top, bottom;
GetActorExtents(actor, &top, &bottom);
double floorZ = getflorzofslopeptr(pSector, actor->spr.pos);
if (!(actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) && floorZ <= bottom)
{
viewBackupSpriteLoc(actor);
if (angleofs != nullAngle)
{
DVector2 mypivot(v20 * inttoworld, v24 * inttoworld);
actor->spr.pos.XY() = rotatepoint(mypivot, actor->spr.pos.XY(), angleofs);
}
actor->spr.angle += angleofs;
actor->spr.pos += position;
}
}
}
#ifdef NOONE_EXTENSIONS
// translate sprites near outside walls
////////////////////////////////////////////////////////////
if (gModernMap)
{
auto ptr = gSprNSect.GetSprPtr(sectnum(pSector));
if (ptr)
{
for (auto& ac : *ptr)
{
if (ac == nullptr)
continue;
if (ac->spr.cstat & CSTAT_SPRITE_MOVE_FORWARD)
{
auto spot = rotatepoint(pivot, ac->basePoint, angle);
viewBackupSpriteLoc(ac);
ac->spr.pos.XY() = spot + aoffset - pivot;
ac->spr.angle += angleofs;
}
else if (ac->spr.cstat & CSTAT_SPRITE_MOVE_REVERSE)
{
auto spot = rotatepoint(pivot, ac->basePoint, angle);
viewBackupSpriteLoc(ac);
ac->spr.pos.XY() = spot - aoffset + pivot;
ac->spr.angle += angleofs;
}
}
}
}
/////////////////////
#endif
}
void TranslateSector(sectortype* pSector, int wave1, int wave2, const DVector2& pivot, const DVector2& pt1, DAngle ang1,const DVector2& pt2, DAngle ang2, bool allWalls)
{
TranslateSector(pSector, wave1, wave2, int(pivot.X * worldtoint), int(pivot.Y * worldtoint), int(pt1.X * worldtoint), int(pt1.Y * worldtoint), ang1.Buildang(), int(pt2.X * worldtoint), int(pt2.Y * worldtoint), ang2.Buildang(), allWalls);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ZTranslateSector(sectortype* pSector, XSECTOR* pXSector, int a3, int a4)
{
viewInterpolateSector(pSector);
int dfz = pXSector->onFloorZ - pXSector->offFloorZ;
int dcz = pXSector->onCeilZ - pXSector->offCeilZ;
#ifdef NOONE_EXTENSIONS
// get pointer to sprites near outside walls before translation
///////////////////////////////////////////////////////////////
auto ptr1 = (gModernMap && (dfz || dcz))? gSprNSect.GetSprPtr(sectnum(pSector)) : nullptr;
#endif
if (dfz != 0)
{
double old_Z = pSector->floorz;
pSector->set_int_floorz((pXSector->offFloorZ + MulScale(dfz, GetWaveValue(a3, a4), 16)));
pSector->baseFloor = pSector->floorz;
pSector->velFloor += (pSector->floorz - old_Z);
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatMarker || actor->spr.statnum == kStatPathMarker)
continue;
double top, bottom;
GetActorExtents(actor, &top, &bottom);
if (actor->spr.cstat & CSTAT_SPRITE_MOVE_FORWARD)
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += pSector->floorz - old_Z;
}
else if (actor->spr.flags & 2)
actor->spr.flags |= 4;
else if (old_Z <= bottom && !(actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK))
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += pSector->floorz - old_Z;
}
}
#ifdef NOONE_EXTENSIONS
// translate sprites near outside walls (floor)
////////////////////////////////////////////////////////////
if (ptr1)
{
for(auto& ac : *ptr1)
{
if (ac && (ac->spr.cstat & CSTAT_SPRITE_MOVE_FORWARD))
{
viewBackupSpriteLoc(ac);
ac->spr.pos.Z += pSector->floorz - old_Z;
}
}
}
/////////////////////
#endif
}
if (dcz != 0)
{
double old_Z = pSector->ceilingz;
pSector->set_int_ceilingz((pXSector->offCeilZ + MulScale(dcz, GetWaveValue(a3, a4), 16)));
pSector->baseCeil = pSector->ceilingz;
pSector->velCeil += pSector->ceilingz - old_Z;
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatMarker || actor->spr.statnum == kStatPathMarker)
continue;
if (actor->spr.cstat & CSTAT_SPRITE_MOVE_REVERSE)
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += pSector->ceilingz - old_Z;
}
}
#ifdef NOONE_EXTENSIONS
// translate sprites near outside walls (ceil)
////////////////////////////////////////////////////////////
if (ptr1)
{
for (auto& ac : *ptr1)
{
if (ac && (ac->spr.cstat & CSTAT_SPRITE_MOVE_REVERSE))
{
viewBackupSpriteLoc(ac);
ac->spr.pos.Z += pSector->ceilingz - old_Z;
}
}
}
/////////////////////
#endif
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DBloodActor* GetHighestSprite(sectortype* pSector, int nStatus, int* z)
{
*z = pSector->int_floorz();
DBloodActor* found = nullptr;
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == nStatus || nStatus == kStatFree)
{
int top, bottom;
GetActorExtents(actor, &top, &bottom);
if (actor->int_pos().Z - top > *z)
{
*z = actor->int_pos().Z - top;
found = actor;
}
}
}
return found;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DBloodActor* GetCrushedSpriteExtents(sectortype* pSector, int* pzTop, int* pzBot)
{
assert(pzTop != NULL && pzBot != NULL);
assert(pSector);
DBloodActor* found = nullptr;
int foundz = pSector->int_ceilingz();
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatDude || actor->spr.statnum == kStatThing)
{
int top, bottom;
GetActorExtents(actor, &top, &bottom);
if (foundz > top)
{
foundz = top;
*pzTop = top;
*pzBot = bottom;
found = actor;
}
}
}
return found;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int VCrushBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
int nWave;
if (pXSector->busy < a2)
nWave = pXSector->busyWaveA;
else
nWave = pXSector->busyWaveB;
int dz1 = pXSector->onCeilZ - pXSector->offCeilZ;
int vc = pXSector->offCeilZ;
if (dz1 != 0)
vc += MulScale(dz1, GetWaveValue(a2, nWave), 16);
int dz2 = pXSector->onFloorZ - pXSector->offFloorZ;
int v10 = pXSector->offFloorZ;
if (dz2 != 0)
v10 += MulScale(dz2, GetWaveValue(a2, nWave), 16);
int v18;
if (GetHighestSprite(pSector, 6, &v18) && vc >= v18)
return 1;
viewInterpolateSector(pSector);
if (dz1 != 0)
pSector->set_int_ceilingz(vc);
if (dz2 != 0)
pSector->set_int_floorz(v10);
pXSector->busy = a2;
if (pXSector->command == kCmdLink && pXSector->txID)
evSendSector(pSector, pXSector->txID, kCmdLink, initiator);
if ((a2 & 0xffff) == 0)
{
SetSectorState(pSector, FixedToInt(a2), initiator);
SectorEndSound(pSector, FixedToInt(a2));
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int VSpriteBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
int nWave;
if (pXSector->busy < a2)
nWave = pXSector->busyWaveA;
else
nWave = pXSector->busyWaveB;
int dz1 = pXSector->onFloorZ - pXSector->offFloorZ;
if (dz1 != 0)
{
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.cstat & CSTAT_SPRITE_MOVE_FORWARD)
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += actor->basePoint.Z + MulScale(dz1, GetWaveValue(a2, nWave), 16) * inttoworld;
}
}
}
int dz2 = pXSector->onCeilZ - pXSector->offCeilZ;
if (dz2 != 0)
{
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.cstat & CSTAT_SPRITE_MOVE_REVERSE)
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += actor->basePoint.Z + MulScale(dz2, GetWaveValue(a2, nWave), 16) * inttoworld;
}
}
}
pXSector->busy = a2;
if (pXSector->command == kCmdLink && pXSector->txID)
evSendSector(pSector, pXSector->txID, kCmdLink, initiator);
if ((a2 & 0xffff) == 0)
{
SetSectorState(pSector, FixedToInt(a2), initiator);
SectorEndSound(pSector, FixedToInt(a2));
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int VDoorBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
int vbp;
if (pXSector->state)
vbp = 65536 / ClipLow((120 * pXSector->busyTimeA) / 10, 1);
else
vbp = -65536 / ClipLow((120 * pXSector->busyTimeB) / 10, 1);
int top, bottom;
auto actor = GetCrushedSpriteExtents(pSector, &top, &bottom);
if (actor && a2 > pXSector->busy)
{
assert(actor->hasX());
if (pXSector->onCeilZ > pXSector->offCeilZ || pXSector->onFloorZ < pXSector->offFloorZ)
{
if (pXSector->interruptable)
{
if (pXSector->Crush)
{
if (actor->xspr.health <= 0)
return 2;
int nDamage;
if (pXSector->data == 0)
nDamage = 500;
else
nDamage = pXSector->data;
actDamageSprite(actor, actor, kDamageFall, nDamage << 4);
}
a2 = ClipRange(a2 - (vbp / 2) * 4, 0, 65536);
}
else if (pXSector->Crush && actor->xspr.health > 0)
{
int nDamage;
if (pXSector->data == 0)
nDamage = 500;
else
nDamage = pXSector->data;
actDamageSprite(actor, actor, kDamageFall, nDamage << 4);
a2 = ClipRange(a2 - (vbp / 2) * 4, 0, 65536);
}
}
}
else if (actor && a2 < pXSector->busy)
{
assert(actor->hasX());
if (pXSector->offCeilZ > pXSector->onCeilZ || pXSector->offFloorZ < pXSector->onFloorZ)
{
if (pXSector->interruptable)
{
if (pXSector->Crush)
{
if (actor->xspr.health <= 0)
return 2;
int nDamage;
if (pXSector->data == 0)
nDamage = 500;
else
nDamage = pXSector->data;
actDamageSprite(actor, actor, kDamageFall, nDamage << 4);
}
a2 = ClipRange(a2 + (vbp / 2) * 4, 0, 65536);
}
else if (pXSector->Crush && actor->xspr.health > 0)
{
int nDamage;
if (pXSector->data == 0)
nDamage = 500;
else
nDamage = pXSector->data;
actDamageSprite(actor, actor, kDamageFall, nDamage << 4);
a2 = ClipRange(a2 + (vbp / 2) * 4, 0, 65536);
}
}
}
int nWave;
if (pXSector->busy < a2)
nWave = pXSector->busyWaveA;
else
nWave = pXSector->busyWaveB;
ZTranslateSector(pSector, pXSector, a2, nWave);
pXSector->busy = a2;
if (pXSector->command == kCmdLink && pXSector->txID)
evSendSector(pSector, pXSector->txID, kCmdLink, initiator);
if ((a2 & 0xffff) == 0)
{
SetSectorState(pSector, FixedToInt(a2), initiator);
SectorEndSound(pSector, FixedToInt(a2));
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int HDoorBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
int nWave;
if (pXSector->busy < a2)
nWave = pXSector->busyWaveA;
else
nWave = pXSector->busyWaveB;
if (!pXSector->marker0 || !pXSector->marker1) return 0;
auto marker0 = pXSector->marker0;
auto marker1 = pXSector->marker1;
TranslateSector(pSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), marker0->spr.pos, marker0->spr.pos, marker0->spr.angle, marker1->spr.pos, marker1->spr.angle, pSector->type == kSectorSlide);
ZTranslateSector(pSector, pXSector, a2, nWave);
pXSector->busy = a2;
if (pXSector->command == kCmdLink && pXSector->txID)
evSendSector(pSector, pXSector->txID, kCmdLink, initiator);
if ((a2 & 0xffff) == 0)
{
SetSectorState(pSector, FixedToInt(a2), initiator);
SectorEndSound(pSector, FixedToInt(a2));
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int RDoorBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
int nWave;
if (pXSector->busy < a2)
nWave = pXSector->busyWaveA;
else
nWave = pXSector->busyWaveB;
if (!pXSector->marker0) return 0;
auto marker0 = pXSector->marker0;
TranslateSector(pSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), marker0->spr.pos, marker0->spr.pos, nullAngle, marker0->spr.pos, marker0->spr.angle, pSector->type == kSectorRotate);
ZTranslateSector(pSector, pXSector, a2, nWave);
pXSector->busy = a2;
if (pXSector->command == kCmdLink && pXSector->txID)
evSendSector(pSector, pXSector->txID, kCmdLink, initiator);
if ((a2 & 0xffff) == 0)
{
SetSectorState(pSector, FixedToInt(a2), initiator);
SectorEndSound(pSector, FixedToInt(a2));
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int StepRotateBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
if (!pXSector->marker0) return 0;
auto marker0 = pXSector->marker0;
auto ang1 = mapangle(pXSector->data);
DAngle ang2;
if (pXSector->busy < a2)
{
ang2 = ang1 + marker0->spr.angle;
int nWave = pXSector->busyWaveA;
TranslateSector(pSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), marker0->spr.pos, marker0->spr.pos, ang1, marker0->spr.pos, ang2, true);
}
else
{
ang2 = ang1 - marker0->spr.angle;
int nWave = pXSector->busyWaveB;
TranslateSector(pSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), marker0->spr.pos, marker0->spr.pos, ang2, marker0->spr.pos, ang1, true);
}
pXSector->busy = a2;
if (pXSector->command == kCmdLink && pXSector->txID)
evSendSector(pSector, pXSector->txID, kCmdLink, initiator);
if ((a2 & 0xffff) == 0)
{
SetSectorState(pSector, FixedToInt(a2), initiator);
SectorEndSound(pSector, FixedToInt(a2));
pXSector->data = ang2.Normalized360().Buildang();
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int GenSectorBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
pXSector->busy = a2;
if (pXSector->command == kCmdLink && pXSector->txID)
evSendSector(pSector, pXSector->txID, kCmdLink, initiator);
if ((a2 & 0xffff) == 0)
{
SetSectorState(pSector, FixedToInt(a2), initiator);
SectorEndSound(pSector, FixedToInt(a2));
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int PathBusy(sectortype* pSector, unsigned int a2, DBloodActor* initiator)
{
assert(pSector && pSector->hasX());
XSECTOR* pXSector = &pSector->xs();
auto basepath = pXSector->basePath;
auto marker0 = pXSector->marker0;
auto marker1 = pXSector->marker1;
if (!basepath || !marker0 || !marker1) return 0;
int nWave = marker0->xspr.wave;
TranslateSector(pSector, GetWaveValue(pXSector->busy, nWave), GetWaveValue(a2, nWave), basepath->spr.pos, marker0->spr.pos, marker0->spr.angle, marker1->spr.pos, marker1->spr.angle, true);
ZTranslateSector(pSector, pXSector, a2, nWave);
pXSector->busy = a2;
if ((a2 & 0xffff) == 0)
{
evPostSector(pSector, (120 * marker1->xspr.waitTime) / 10, kCmdOn, initiator);
pXSector->state = 0;
pXSector->busy = 0;
if (marker0->xspr.data4)
PathSound(pSector, marker0->xspr.data4);
pXSector->marker0 = marker1;
pXSector->data = marker1->xspr.data1;
return 3;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void OperateDoor(sectortype* pSector, EVENT event, BUSYID busyWave)
{
auto pXSector = &pSector->xs();
switch (event.cmd) {
case kCmdOff:
if (!pXSector->busy) break;
AddBusy(pSector, busyWave, -65536 / ClipLow((pXSector->busyTimeB * 120) / 10, 1));
SectorStartSound(pSector, 1);
break;
case kCmdOn:
if (pXSector->busy == 0x10000) break;
AddBusy(pSector, busyWave, 65536 / ClipLow((pXSector->busyTimeA * 120) / 10, 1));
SectorStartSound(pSector, 0);
break;
default:
if (pXSector->busy & 0xffff) {
if (pXSector->interruptable) {
ReverseBusy(pSector, busyWave);
pXSector->state = !pXSector->state;
}
}
else {
int nDelta;
if (!pXSector->state) nDelta = 65536 / ClipLow((pXSector->busyTimeA * 120) / 10, 1);
else nDelta = -65536 / ClipLow((pXSector->busyTimeB * 120) / 10, 1);
AddBusy(pSector, busyWave, nDelta);
SectorStartSound(pSector, pXSector->state);
}
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool SectorContainsDudes(sectortype* pSector)
{
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatDude)
return 1;
}
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void TeleFrag(DBloodActor* killer, sectortype* pSector)
{
BloodSectIterator it(pSector);
while (auto victim = it.Next())
{
if (victim->spr.statnum == kStatDude)
actDamageSprite(killer, victim, kDamageExplode, 4000);
else if (victim->spr.statnum == kStatThing)
actDamageSprite(killer, victim, kDamageExplode, 4000);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void OperateTeleport(sectortype* pSector)
{
assert(pSector);
auto pXSector = &pSector->xs();
auto destactor = pXSector->marker0;
assert(destactor != nullptr);
assert(destactor->spr.statnum == kStatMarker);
assert(destactor->spr.type == kMarkerWarpDest);
assert(destactor->insector());
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.statnum == kStatDude)
{
PLAYER* pPlayer;
bool bPlayer = actor->IsPlayerActor();
if (bPlayer)
pPlayer = &gPlayer[actor->spr.type - kDudePlayer1];
else
pPlayer = NULL;
if (bPlayer || !SectorContainsDudes(destactor->sector()))
{
if (!(gGameOptions.uNetGameFlags & 2))
{
TeleFrag(pXSector->actordata, destactor->sector());
}
actor->spr.pos.XY() = destactor->spr.pos.XY();
actor->spr.pos.Z += destactor->sector()->floorz - pSector->floorz;
actor->spr.angle = destactor->spr.angle;
ChangeActorSect(actor, destactor->sector());
sfxPlay3DSound(destactor, 201, -1, 0);
actor->ZeroVelocity();
actor->interpolated = false;
viewBackupSpriteLoc(actor);
if (pPlayer)
{
playerResetInertia(pPlayer);
pPlayer->zViewVel = pPlayer->zWeaponVel = 0;
pPlayer->angle.settarget(actor->spr.angle, true);
}
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void OperatePath(sectortype* pSector, EVENT event)
{
DBloodActor* actor;
assert(pSector);
auto pXSector = &pSector->xs();
if (!pXSector->marker0) return;
auto marker0 = pXSector->marker0;
int nId = marker0->xspr.data2;
BloodStatIterator it(kStatPathMarker);
while ((actor = it.Next()))
{
if (actor->spr.type == kMarkerPath)
{
if (actor->xspr.data1 == nId)
break;
}
}
// trigger marker after it gets reached
#ifdef NOONE_EXTENSIONS
if (gModernMap && marker0->xspr.state != 1)
trTriggerSprite(pXSector->marker0, kCmdOn, event.initiator);
#endif
if (actor == nullptr) {
viewSetSystemMessage("Unable to find path marker with id #%d for path sector", nId);
pXSector->state = 0;
pXSector->busy = 0;
return;
}
pXSector->marker1 = actor;
pXSector->offFloorZ = marker0->int_pos().Z;
pXSector->onFloorZ = actor->int_pos().Z;
switch (event.cmd) {
case kCmdOn:
pXSector->state = 0;
pXSector->busy = 0;
AddBusy(pSector, BUSYID_7, 65536 / ClipLow((120 * marker0->xspr.busyTime) / 10, 1));
if (marker0->xspr.data3) PathSound(pSector, marker0->xspr.data3);
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void OperateSector(sectortype* pSector, EVENT event)
{
if (!pSector->hasX()) return;
auto pXSector = &pSector->xs();
#ifdef NOONE_EXTENSIONS
if (gModernMap && modernTypeOperateSector(pSector, event))
return;
#endif
switch (event.cmd) {
case kCmdLock:
pXSector->locked = 1;
break;
case kCmdUnlock:
pXSector->locked = 0;
break;
case kCmdToggleLock:
pXSector->locked ^= 1;
break;
case kCmdStopOff:
pXSector->stopOn = 0;
pXSector->stopOff = 1;
break;
case kCmdStopOn:
pXSector->stopOn = 1;
pXSector->stopOff = 0;
break;
case kCmdStopNext:
pXSector->stopOn = 1;
pXSector->stopOff = 1;
break;
default:
#ifdef NOONE_EXTENSIONS
if (gModernMap && pXSector->unused1) break;
#endif
switch (pSector->type) {
case kSectorZMotionSprite:
OperateDoor(pSector, event, BUSYID_1);
break;
case kSectorZMotion:
OperateDoor(pSector, event, BUSYID_2);
break;
case kSectorSlideMarked:
case kSectorSlide:
OperateDoor(pSector, event, BUSYID_3);
break;
case kSectorRotateMarked:
case kSectorRotate:
OperateDoor(pSector, event, BUSYID_4);
break;
case kSectorRotateStep:
switch (event.cmd) {
case kCmdOn:
pXSector->state = 0;
pXSector->busy = 0;
AddBusy(pSector, BUSYID_5, 65536 / ClipLow((120 * pXSector->busyTimeA) / 10, 1));
SectorStartSound(pSector, 0);
break;
case kCmdOff:
pXSector->state = 1;
pXSector->busy = 65536;
AddBusy(pSector, BUSYID_5, -65536 / ClipLow((120 * pXSector->busyTimeB) / 10, 1));
SectorStartSound(pSector, 1);
break;
}
break;
case kSectorTeleport:
OperateTeleport(pSector);
break;
case kSectorPath:
OperatePath(pSector, event);
break;
default:
if (!pXSector->busyTimeA && !pXSector->busyTimeB)
{
DBloodActor* initiator = event.initiator;
switch (event.cmd) {
case kCmdOff:
SetSectorState(pSector, 0, initiator);
break;
case kCmdOn:
SetSectorState(pSector, 1, initiator);
break;
default:
SetSectorState(pSector, pXSector->state ^ 1, initiator);
break;
}
}
else {
OperateDoor(pSector, event, BUSYID_6);
}
break;
}
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitPath(sectortype* pSector, XSECTOR* pXSector)
{
DBloodActor* actor = nullptr;
assert(pSector);
int nId = pXSector->data;
BloodStatIterator it(kStatPathMarker);
while ((actor = it.Next()))
{
if (actor->spr.type == kMarkerPath && actor->hasX())
{
if (actor->xspr.data1 == nId)
break;
}
}
if (actor == nullptr) {
viewSetSystemMessage("Unable to find path marker with id #%d for path sector", nId);
return;
}
pXSector->basePath = pXSector->marker0 = actor;
if (pXSector->state)
evPostSector(pSector, 0, kCmdOn, nullptr);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void LinkSector(sectortype* pSector, EVENT event)
{
DBloodActor* initiator = event.initiator;
auto pXSector = &pSector->xs();
int nBusy = GetSourceBusy(event);
switch (pSector->type) {
case kSectorZMotionSprite:
VSpriteBusy(pSector, nBusy, initiator);
break;
case kSectorZMotion:
VDoorBusy(pSector, nBusy, initiator);
break;
case kSectorSlideMarked:
case kSectorSlide:
HDoorBusy(pSector, nBusy, initiator);
break;
case kSectorRotateMarked:
case kSectorRotate:
RDoorBusy(pSector, nBusy, initiator);
break;
default:
pXSector->busy = nBusy;
if ((pXSector->busy & 0xffff) == 0)
SetSectorState(pSector, FixedToInt(nBusy), initiator);
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void LinkSprite(DBloodActor* actor, EVENT event)
{
DBloodActor* initiator = event.initiator;
int nBusy = GetSourceBusy(event);
switch (actor->spr.type) {
case kSwitchCombo:
{
if (event.isActor())
{
auto actor2 = event.getActor();
actor->xspr.data1 = actor2 && actor2->hasX() ? actor2->xspr.data1 : 0;
if (actor->xspr.data1 == actor->xspr.data2)
SetSpriteState(actor, 1, initiator);
else
SetSpriteState(actor, 0, initiator);
}
}
break;
default:
{
actor->xspr.busy = nBusy;
if ((actor->xspr.busy & 0xffff) == 0)
SetSpriteState(actor, FixedToInt(nBusy), initiator);
}
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void LinkWall(walltype* pWall, EVENT& event)
{
int nBusy = GetSourceBusy(event);
pWall->xw().busy = nBusy;
if ((pWall->xw().busy & 0xffff) == 0)
SetWallState(pWall, FixedToInt(nBusy), event.initiator);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trTriggerSector(sectortype* pSector, int command, DBloodActor* initiator)
{
auto pXSector = &pSector->xs();
if (!pXSector->locked && !pXSector->isTriggered) {
if (pXSector->triggerOnce)
pXSector->isTriggered = 1;
if (pXSector->decoupled && pXSector->txID > 0)
evSendSector(pSector, pXSector->txID, (COMMAND_ID)pXSector->command, initiator);
else {
EVENT event;
event.cmd = command;
event.initiator = gModernMap? initiator : nullptr;
OperateSector(pSector, event);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trTriggerWall(walltype* pWall, int command, DBloodActor* initiator)
{
if (!pWall->hasX()) return;
auto pXWall = &pWall->xw();
if (!pXWall->locked && !pXWall->isTriggered) {
if (pXWall->triggerOnce)
pXWall->isTriggered = 1;
if (pXWall->decoupled && pXWall->txID > 0)
evSendWall(pWall, pXWall->txID, (COMMAND_ID)pXWall->command, initiator);
else {
EVENT event;
event.cmd = command;
event.initiator = gModernMap ? initiator : nullptr;
OperateWall(pWall, event);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trTriggerSprite(DBloodActor* actor, int command, DBloodActor* initiator)
{
if (!actor->xspr.locked && !actor->xspr.isTriggered) {
if (actor->xspr.triggerOnce)
actor->xspr.isTriggered = 1;
if (actor->xspr.Decoupled && actor->xspr.txID > 0)
evSendActor(actor, actor->xspr.txID, (COMMAND_ID)actor->xspr.command, initiator);
else {
EVENT event;
event.cmd = command;
event.initiator = gModernMap ? initiator : nullptr;
OperateSprite(actor, event);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trMessageSector(sectortype* pSector, EVENT event)
{
if (!pSector->hasX()) return;
XSECTOR* pXSector = &pSector->xs();
if (!pXSector->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock)
{
switch (event.cmd)
{
case kCmdLink:
LinkSector(pSector, event);
break;
#ifdef NOONE_EXTENSIONS
case kCmdModernUse:
modernTypeTrigger(OBJ_SECTOR, pSector, nullptr, nullptr, event);
break;
#endif
default:
OperateSector(pSector, event);
break;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trMessageWall(walltype* pWall, EVENT& event)
{
assert(pWall->hasX());
XWALL* pXWall = &pWall->xw();
if (!pXWall->locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock)
{
switch (event.cmd) {
case kCmdLink:
LinkWall(pWall, event);
break;
#ifdef NOONE_EXTENSIONS
case kCmdModernUse:
modernTypeTrigger(OBJ_WALL, nullptr, pWall, nullptr, event);
break;
#endif
default:
OperateWall(pWall, event);
break;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trMessageSprite(DBloodActor* actor, EVENT event)
{
if (actor->spr.statnum != kStatFree) {
if (!actor->xspr.locked || event.cmd == kCmdUnlock || event.cmd == kCmdToggleLock)
{
switch (event.cmd)
{
case kCmdLink:
LinkSprite(actor, event);
break;
#ifdef NOONE_EXTENSIONS
case kCmdModernUse:
modernTypeTrigger(OBJ_SPRITE, 0, 0, actor, event);
break;
#endif
default:
OperateSprite(actor, event);
break;
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ProcessMotion(void)
{
for (auto& sect : sector)
{
sectortype* pSector = &sect;
if (!pSector->hasX()) continue;
XSECTOR* pXSector = &pSector->xs();
if (pXSector->bobSpeed != 0)
{
if (pXSector->bobAlways)
pXSector->bobTheta += pXSector->bobSpeed;
else if (pXSector->busy == 0)
continue;
else
pXSector->bobTheta += MulScale(pXSector->bobSpeed, pXSector->busy, 16);
int zoff_i = MulScale(Sin(pXSector->bobTheta), pXSector->bobZRange << 8, 30);
double zoff = zoff_i * zinttoworld;
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
if (actor->spr.cstat & CSTAT_SPRITE_MOVE_MASK)
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += zoff;
}
}
if (pXSector->bobFloor)
{
double floorZ = pSector->floorz;
viewInterpolateSector(pSector);
pSector->setfloorz(pSector->baseFloor + zoff);
BloodSectIterator itr(pSector);
while (auto actor = itr.Next())
{
if (actor->spr.flags & 2)
actor->spr.flags |= 4;
else
{
double top, bottom;
GetActorExtents(actor, &top, &bottom);
if (bottom >= floorZ && (actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == 0)
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += zoff;
}
}
}
}
if (pXSector->bobCeiling)
{
double ceilZ = pSector->ceilingz;
viewInterpolateSector(pSector);
pSector->setceilingz(pSector->baseCeil + zoff);
BloodSectIterator itr(pSector);
while (auto actor = itr.Next())
{
double top, bottom;
GetActorExtents(actor, &top, &bottom);
if (top <= ceilZ && (actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == 0)
{
viewBackupSpriteLoc(actor);
actor->spr.pos.Z += zoff;
}
}
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void AlignSlopes(void)
{
for (auto& sect : sector)
{
if (sect.slopewallofs)
{
walltype* pWall = sect.firstWall() + sect.slopewallofs;
if (pWall->twoSided())
{
auto pNextSector = pWall->nextSector();
auto pos = pWall->center();
viewInterpolateSector(&sect);
alignflorslope(&sect, DVector3(pos, getflorzofslopeptr(pNextSector, pos)));
alignceilslope(&sect, DVector3(pos, getceilzofslopeptr(pNextSector, pos)));
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int(*gBusyProc[])(sectortype*, unsigned int, DBloodActor*) =
{
VCrushBusy,
VSpriteBusy,
VDoorBusy,
HDoorBusy,
RDoorBusy,
StepRotateBusy,
GenSectorBusy,
PathBusy
};
void trProcessBusy(void)
{
for (auto& sect : sector)
{
sect.velCeil = sect.velFloor = 0;
}
for (int i = gBusy.Size()-1; i >= 0; i--)
{
int nStatus;
int oldBusy = gBusy[i].busy;
gBusy[i].busy = ClipRange(oldBusy + gBusy[i].delta * 4, 0, 65536);
#ifdef NOONE_EXTENSIONS
if (!gModernMap || !gBusy[i].sect->xs().unused1) nStatus = gBusyProc[gBusy[i].type](gBusy[i].sect, gBusy[i].busy, nullptr);
else nStatus = 3; // allow to pause/continue motion for sectors any time by sending special command
#else
nStatus = gBusyProc[gBusy[i].type](gBusy[i].at0, gBusy[i].at8);
#endif
switch (nStatus) {
case 1:
gBusy[i].busy = oldBusy;
break;
case 2:
gBusy[i].busy = oldBusy;
gBusy[i].delta = -gBusy[i].delta;
break;
case 3:
gBusy[i] = gBusy.Last();
gBusy.Pop();
break;
}
}
ProcessMotion();
AlignSlopes();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void UpdateBasePoints(sectortype* pSector)
{
#ifdef NOONE_EXTENSIONS
if (gModernMap)
{
// must set basepoint for outside sprites as well
auto ptr1 = gSprNSect.GetSprPtr(sectnum(pSector));
if (ptr1)
{
for (auto& ac : *ptr1)
ac->basePoint = ac->spr.pos;
}
}
#endif
for (auto& wal : wallsofsector(pSector))
{
wal.baseWall = wal.pos;
}
BloodSectIterator it(pSector);
while (auto actor = it.Next())
{
actor->basePoint = actor->spr.pos;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitGenerator(DBloodActor*);
void trInit(TArray<DBloodActor*>& actors)
{
#ifdef NOONE_EXTENSIONS
if (gModernMap)
gSprNSect.Init(); // collect sprites near outside walls
#endif
gBusy.Clear();
for (auto actor : actors)
{
if (!actor->exists()) continue;
actor->spr.inittype = actor->spr.type;
actor->basePoint = actor->spr.pos;
}
for (auto& wal : wall)
{
wal.baseWall = wal.pos;
if (wal.hasX())
{
XWALL* pXWall = &wal.xw();
if (pXWall->state)
pXWall->busy = 65536;
}
}
for (auto& sect : sector)
{
sectortype* pSector = &sect;
pSector->baseFloor = pSector->floorz;
pSector->baseCeil = pSector->ceilingz;
if (pSector->hasX())
{
XSECTOR* pXSector = &pSector->xs();
if (pXSector->state)
pXSector->busy = 65536;
switch (pSector->type)
{
case kSectorCounter:
#ifdef NOONE_EXTENSIONS
if (gModernMap)
pXSector->triggerOff = false;
else
#endif
pXSector->triggerOnce = 1;
evPostSector(pSector, 0, kCallbackCounterCheck);
break;
case kSectorZMotion:
case kSectorZMotionSprite:
ZTranslateSector(pSector, pXSector, pXSector->busy, 1);
break;
case kSectorSlideMarked:
case kSectorSlide:
{
auto marker0 = pXSector->marker0;
auto marker1 = pXSector->marker1;
TranslateSector(pSector, 0, -65536, marker0->spr.pos, marker0->spr.pos, marker0->spr.angle, marker1->spr.pos, marker1->spr.angle, pSector->type == kSectorSlide);
UpdateBasePoints(pSector);
TranslateSector(pSector, 0, pXSector->busy, marker0->spr.pos, marker0->spr.pos, marker0->spr.angle, marker1->spr.pos, marker1->spr.angle, pSector->type == kSectorSlide);
ZTranslateSector(pSector, pXSector, pXSector->busy, 1);
break;
}
case kSectorRotateMarked:
case kSectorRotate:
{
auto marker0 = pXSector->marker0;
TranslateSector(pSector, 0, -65536, marker0->spr.pos, marker0->spr.pos, nullAngle, marker0->spr.pos, marker0->spr.angle, pSector->type == kSectorRotate);
UpdateBasePoints(pSector);
TranslateSector(pSector, 0, pXSector->busy, marker0->spr.pos, marker0->spr.pos, nullAngle, marker0->spr.pos, marker0->spr.angle, pSector->type == kSectorRotate);
ZTranslateSector(pSector, pXSector, pXSector->busy, 1);
break;
}
case kSectorPath:
InitPath(pSector, pXSector);
break;
default:
break;
}
}
}
for (auto actor : actors)
{
if (actor->spr.statnum < kStatFree && actor->hasX())
{
if (actor->xspr.state)
actor->xspr.busy = 65536;
switch (actor->spr.type) {
case kSwitchPadlock:
actor->xspr.triggerOnce = 1;
break;
#ifdef NOONE_EXTENSIONS
case kModernRandom:
case kModernRandom2:
if (!gModernMap || actor->xspr.state == actor->xspr.restState) break;
evPostActor(actor, (120 * actor->xspr.busyTime) / 10, kCmdRepeat, actor);
if (actor->xspr.waitTime > 0)
evPostActor(actor, (actor->xspr.waitTime * 120) / 10, actor->xspr.restState ? kCmdOn : kCmdOff, actor);
break;
case kModernSeqSpawner:
case kModernObjDataAccumulator:
case kModernDudeTargetChanger:
case kModernEffectSpawner:
case kModernWindGenerator:
if (actor->xspr.state == actor->xspr.restState) break;
evPostActor(actor, 0, kCmdRepeat, actor);
if (actor->xspr.waitTime > 0)
evPostActor(actor, (actor->xspr.waitTime * 120) / 10, actor->xspr.restState ? kCmdOn : kCmdOff, actor);
break;
#endif
case kGenTrigger:
case kGenDripWater:
case kGenDripBlood:
case kGenMissileFireball:
case kGenDart:
case kGenBubble:
case kGenBubbleMulti:
case kGenMissileEctoSkull:
case kGenSound:
InitGenerator(actor);
break;
case kThingArmedProxBomb:
actor->xspr.Proximity = 1;
break;
case kThingFallingRock:
if (actor->xspr.state) actor->spr.flags |= 7;
else actor->spr.flags &= ~7;
break;
}
if (actor->xspr.Vector) actor->spr.cstat |= CSTAT_SPRITE_BLOCK_HITSCAN;
if (actor->xspr.Push) actor->spr.cstat |= CSTAT_SPRITE_BLOOD_BIT1;
}
}
evSendGame(kChannelLevelStart, kCmdOn);
#ifdef NOONE_EXTENSIONS
if (gModernMap)
{
evSendGame(kChannelLevelStartRAZE, kCmdOn);
}
#endif
switch (gGameOptions.nGameType) {
case 1:
evSendGame(kChannelLevelStartCoop, kCmdOn);
break;
case 2:
evSendGame(kChannelLevelStartMatch, kCmdOn);
break;
case 3:
evSendGame(kChannelLevelStartMatch, kCmdOn);
evSendGame(kChannelLevelStartTeamsOnly, kCmdOn);
break;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void trTextOver(int nId)
{
const char* pzMessage = currentLevel->GetMessage(nId);
if (pzMessage)
viewSetMessage(pzMessage, VanillaMode() ? nullptr : TEXTCOLOR_GOLD, MESSAGE_PRIORITY_INI);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void InitGenerator(DBloodActor* actor)
{
assert(actor->hasX());
switch (actor->spr.type) {
case kGenTrigger:
actor->spr.cstat &= ~CSTAT_SPRITE_BLOCK;
actor->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
break;
}
if (actor->xspr.state != actor->xspr.restState && actor->xspr.busyTime > 0)
evPostActor(actor, (120 * (actor->xspr.busyTime + Random2(actor->xspr.data1))) / 10, kCmdRepeat, actor);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void ActivateGenerator(DBloodActor* actor)
{
assert(actor->hasX());
switch (actor->spr.type) {
case kGenDripWater:
case kGenDripBlood: {
double top, bottom;
GetActorExtents(actor, &top, &bottom);
actSpawnThing(actor->sector(), DVector3(actor->spr.pos.XY(), bottom), (actor->spr.type == kGenDripWater) ? kThingDripWater : kThingDripBlood);
break;
}
case kGenSound:
sfxPlay3DSound(actor, actor->xspr.data2, -1, 0);
break;
case kGenMissileFireball:
switch (actor->xspr.data2) {
case 0:
FireballTrapSeqCallback(3, actor);
break;
case 1:
seqSpawn(35, actor, nFireballTrapClient);
break;
case 2:
seqSpawn(36, actor, nFireballTrapClient);
break;
}
break;
case kGenMissileEctoSkull:
break;
case kGenBubble:
case kGenBubbleMulti: {
double top, bottom;
GetActorExtents(actor, &top, &bottom);
gFX.fxSpawnActor((actor->spr.type == kGenBubble) ? FX_23 : FX_26, actor->sector(), DVector3(actor->spr.pos.XY(), top), 0);
break;
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void FireballTrapSeqCallback(int, DBloodActor* actor)
{
if (actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR)
actFireMissile(actor, 0, 0, 0, 0, (actor->spr.cstat & CSTAT_SPRITE_YFLIP) ? 0x4000 : -0x4000, kMissileFireball);
else
actFireMissile(actor, 0, 0, bcos(actor->int_ang()), bsin(actor->int_ang()), 0, kMissileFireball);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void MGunFireSeqCallback(int, DBloodActor* actor)
{
if (actor->xspr.data2 > 0 || actor->xspr.data1 == 0)
{
if (actor->xspr.data2 > 0)
{
actor->xspr.data2--;
if (actor->xspr.data2 == 0)
evPostActor(actor, 1, kCmdOff, actor);
}
int dx = bcos(actor->int_ang()) + Random2(1000);
int dy = bsin(actor->int_ang()) + Random2(1000);
int dz = Random2(1000);
actFireVector(actor, 0, 0, dx, dy, dz, kVectorBullet);
sfxPlay3DSound(actor, 359, -1, 0);
}
}
void MGunOpenSeqCallback(int, DBloodActor* actor)
{
seqSpawn(39, actor, nMGunFireClient);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
FSerializer& Serialize(FSerializer& arc, const char* keyname, BUSY& w, BUSY* def)
{
if (arc.BeginObject(keyname))
{
arc("index", w.sect)
("type", w.type)
("delta", w.delta)
("busy", w.busy)
.EndObject();
}
return arc;
}
void SerializeTriggers(FSerializer& arc)
{
if (arc.BeginObject("triggers"))
{
arc("busy", gBusy)
.EndObject();
}
}
END_BLD_NS