raze/source/games/blood/src/animatesprite.cpp

1025 lines
30 KiB
C++
Raw Normal View History

2020-08-02 17:59:14 +00:00
//-------------------------------------------------------------------------
/*
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 <stdlib.h>
#include <string.h>
#include "build.h"
#include "v_font.h"
#include "blood.h"
2020-08-02 17:59:14 +00:00
#include "zstring.h"
#include "razemenu.h"
2020-08-02 17:59:14 +00:00
#include "gstrings.h"
#include "v_2ddrawer.h"
#include "v_video.h"
#include "v_font.h"
#include "hw_voxels.h"
2021-04-13 18:33:21 +00:00
#include "gamefuncs.h"
2020-08-02 17:59:14 +00:00
BEGIN_BLD_NS
static DAngle gCameraAng;
DAngle random_angles[16][3];
2020-08-02 17:59:14 +00:00
2021-12-29 19:03:42 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
tspritetype* viewInsertTSprite(tspriteArray& tsprites, sectortype* pSector, int nStatnum, tspritetype const* const parentTSprite)
2020-08-02 17:59:14 +00:00
{
tspritetype* pTSprite = tsprites.newTSprite();
2021-12-29 19:03:42 +00:00
memset(pTSprite, 0, sizeof(tspritetype));
pTSprite->cstat = CSTAT_SPRITE_YCENTER;
pTSprite->xrepeat = 64;
pTSprite->yrepeat = 64;
pTSprite->ownerActor = nullptr;
pTSprite->type = -int(tsprites.Size() - 1);
2021-12-29 19:03:42 +00:00
pTSprite->statnum = nStatnum;
pTSprite->sectp = pSector;
2022-08-10 22:01:00 +00:00
DVector3 pos = { 0,0,0 };
2021-12-29 19:03:42 +00:00
if (parentTSprite)
{
2022-08-10 22:01:00 +00:00
pos = parentTSprite->pos;
2021-12-29 19:03:42 +00:00
pTSprite->ownerActor = parentTSprite->ownerActor;
pTSprite->angle = parentTSprite->angle;
2021-12-29 19:03:42 +00:00
}
2022-08-29 20:59:44 +00:00
pos.XY() += gCameraAng.ToVector() * 2;
2022-08-10 22:01:00 +00:00
pTSprite->pos = pos;
2021-12-29 19:03:42 +00:00
return pTSprite;
2020-08-02 17:59:14 +00:00
}
static const int effectDetail[kViewEffectMax] = {
2021-12-29 19:03:42 +00:00
4, 4, 4, 4, 0, 0, 0, 0, 0, 1, 4, 4, 0, 0, 0, 1, 0, 0, 0
2020-08-02 17:59:14 +00:00
};
struct WEAPONICON {
2021-12-29 19:03:42 +00:00
int16_t nTile;
uint8_t zOffset;
2020-08-02 17:59:14 +00:00
};
static const WEAPONICON gWeaponIcon[] = {
2021-12-29 19:03:42 +00:00
{ -1, 0 },
{ -1, 0 }, // 1: pitchfork
{ 524, 6 }, // 2: flare gun
{ 559, 6 }, // 3: shotgun
{ 558, 8 }, // 4: tommy gun
{ 526, 6 }, // 5: napalm launcher
{ 589, 11 }, // 6: dynamite
{ 618, 11 }, // 7: spray can
{ 539, 6 }, // 8: tesla gun
{ 800, 0 }, // 9: life leech
{ 525, 11 }, // 10: voodoo doll
{ 811, 11 }, // 11: proxy bomb
{ 810, 11 }, // 12: remote bomb
{ -1, 0 },
2020-08-02 17:59:14 +00:00
};
2021-12-29 19:03:42 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static tspritetype* viewAddEffect(tspriteArray& tsprites, int nTSprite, VIEW_EFFECT nViewEffect)
2020-08-02 17:59:14 +00:00
{
2021-12-29 19:03:42 +00:00
assert(nViewEffect >= 0 && nViewEffect < kViewEffectMax);
auto pTSprite = tsprites.get(nTSprite);
2021-12-29 19:03:42 +00:00
auto owneractor = static_cast<DBloodActor*>(pTSprite->ownerActor);
if (gDetail < effectDetail[nViewEffect]) return NULL;
2021-12-29 19:03:42 +00:00
switch (nViewEffect)
{
case kViewEffectSpotProgress: {
int perc = (100 * owneractor->xspr.data3) / kMaxPatrolSpotValue;
int width = (94 * owneractor->xspr.data3) / kMaxPatrolSpotValue;
2022-09-13 22:42:59 +00:00
double top, bottom;
2021-12-29 19:03:42 +00:00
GetSpriteExtents(pTSprite, &top, &bottom);
auto pNSprite2 = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite2)
break;
pNSprite2->picnum = 2203;
pNSprite2->xrepeat = width;
pNSprite2->yrepeat = 20;
pNSprite2->pal = 10;
if (perc >= 75) pNSprite2->pal = 0;
else if (perc >= 50) pNSprite2->pal = 6;
2022-09-13 22:42:59 +00:00
pNSprite2->pos.Z = (top - 8);
2021-12-29 19:03:42 +00:00
pNSprite2->shade = -128;
break;
}
case kViewEffectAtom:
for (int i = 0; i < 16; i++)
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
auto ang = mapangle((PlayClock * 2048) / 120).Normalized360();
auto nRand1 = random_angles[i][0];
auto nRand2 = random_angles[i][1];
auto nRand3 = random_angles[i][2];
2021-12-29 19:03:42 +00:00
ang += nRand3;
auto vect = DVector3(32 * ang.ToVector(), 0);
DVector2 pt(vect.Y, vect.Z);
pt = rotatepoint({0,0}, pt, nRand1);
vect.Y = pt.X;
pt.X = vect.X;
pt = rotatepoint({0,0}, pt, nRand2);
vect.X = pt.X;
vect.Z = pt.Y;
pNSprite->pos = pTSprite->pos + vect;
2021-12-29 19:03:42 +00:00
pNSprite->picnum = 1720;
pNSprite->shade = -128;
}
break;
case kViewEffectFlag:
case kViewEffectBigFlag:
{
2022-09-13 22:42:59 +00:00
double top, bottom;
2021-12-29 19:03:42 +00:00
GetSpriteExtents(pTSprite, &top, &bottom);
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
pNSprite->shade = -128;
pNSprite->pal = 0;
2022-09-13 22:42:59 +00:00
pNSprite->pos.Z = top;
2021-12-29 19:03:42 +00:00
if (nViewEffect == kViewEffectFlag)
pNSprite->xrepeat = pNSprite->yrepeat = 24;
else
pNSprite->xrepeat = pNSprite->yrepeat = 64;
pNSprite->picnum = 3558;
return pNSprite;
}
case kViewEffectTesla:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
2022-02-07 07:47:18 +00:00
pNSprite->pos.Z = pTSprite->pos.Z;
2021-12-29 19:03:42 +00:00
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
pNSprite->shade = -128;
pNSprite->xrepeat = pTSprite->xrepeat;
pNSprite->yrepeat = pTSprite->yrepeat;
pNSprite->picnum = 2135;
break;
}
case kViewEffectShoot:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
pNSprite->shade = -128;
pNSprite->pal = 0;
pNSprite->xrepeat = pNSprite->yrepeat = 64;
pNSprite->picnum = 2605;
return pNSprite;
}
case kViewEffectReflectiveBall:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
pNSprite->shade = 26;
pNSprite->pal = 0;
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
pNSprite->xrepeat = pNSprite->yrepeat = 64;
pNSprite->picnum = 2089;
break;
}
case kViewEffectPhase:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
2022-09-13 22:42:59 +00:00
double top, bottom;
2021-12-29 19:03:42 +00:00
GetSpriteExtents(pTSprite, &top, &bottom);
2022-09-13 22:42:59 +00:00
pNSprite->pos.Z = top;
2021-12-29 19:03:42 +00:00
pNSprite->shade = 26;
pNSprite->pal = 0;
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
pNSprite->xrepeat = pNSprite->yrepeat = 24;
pNSprite->picnum = 626;
break;
}
case kViewEffectTrail:
{
auto nAng = pTSprite->angle;
2021-12-29 19:03:42 +00:00
if (pTSprite->cstat & CSTAT_SPRITE_ALIGNMENT_WALL)
{
nAng += DAngle90;
2021-12-29 19:03:42 +00:00
}
else
{
nAng += DAngle180;
2021-12-29 19:03:42 +00:00
}
for (int i = 0; i < 5; i++)
2021-12-29 19:03:42 +00:00
{
auto pSector = pTSprite->sectp;
auto pNSprite = viewInsertTSprite(tsprites, pSector, 32767, NULL);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
double nLen = 8.0 * (i + 1);
auto vect = nAng.ToVector() * nLen;
pNSprite->pos = pTSprite->pos + vect;
2021-12-29 19:03:42 +00:00
assert(pSector);
auto pSector2 = pSector;
updatesectorz(pNSprite->pos, &pSector2);
if (pSector2) pSector = pSector2;
pNSprite->sectp = pSector;
2021-12-29 19:03:42 +00:00
pNSprite->ownerActor = pTSprite->ownerActor;
pNSprite->picnum = pTSprite->picnum;
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
if (i < 2)
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT | CSTAT_SPRITE_TRANS_FLIP;
pNSprite->shade = ClipLow(pTSprite->shade - 16, -128);
pNSprite->xrepeat = pTSprite->xrepeat;
pNSprite->yrepeat = pTSprite->yrepeat;
pNSprite->picnum = pTSprite->picnum;
}
break;
}
case kViewEffectFlame:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
pNSprite->shade = -128;
2022-02-07 07:47:18 +00:00
pNSprite->pos.Z = pTSprite->pos.Z;
2021-12-29 19:03:42 +00:00
pNSprite->picnum = 908;
pNSprite->statnum = kStatDecoration;
pNSprite->xrepeat = pNSprite->yrepeat = (tileWidth(pTSprite->picnum) * pTSprite->xrepeat) / 64;
break;
}
case kViewEffectSmokeHigh:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
2022-09-13 22:42:59 +00:00
double top, bottom;
2021-12-29 19:03:42 +00:00
GetSpriteExtents(pTSprite, &top, &bottom);
2022-09-13 22:42:59 +00:00
pNSprite->pos.Z = top;
2021-12-29 19:03:42 +00:00
if (IsDudeSprite(pTSprite))
pNSprite->picnum = 672;
else
pNSprite->picnum = 754;
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
pNSprite->shade = 8;
pNSprite->xrepeat = pTSprite->xrepeat;
pNSprite->yrepeat = pTSprite->yrepeat;
break;
}
case kViewEffectSmokeLow:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
2022-09-13 22:42:59 +00:00
double top, bottom;
2021-12-29 19:03:42 +00:00
GetSpriteExtents(pTSprite, &top, &bottom);
2022-09-13 22:42:59 +00:00
pNSprite->pos.Z = bottom;
2021-12-29 19:03:42 +00:00
if (pTSprite->type >= kDudeBase && pTSprite->type < kDudeMax)
pNSprite->picnum = 672;
else
pNSprite->picnum = 754;
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
pNSprite->shade = 8;
pNSprite->xrepeat = pTSprite->xrepeat;
pNSprite->yrepeat = pTSprite->yrepeat;
break;
}
case kViewEffectTorchHigh:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
2022-09-13 22:42:59 +00:00
double top, bottom;
2021-12-29 19:03:42 +00:00
GetSpriteExtents(pTSprite, &top, &bottom);
2022-09-13 22:42:59 +00:00
pNSprite->pos.Z = top;
2021-12-29 19:03:42 +00:00
pNSprite->picnum = 2101;
pNSprite->shade = -128;
pNSprite->xrepeat = pNSprite->yrepeat = (tileWidth(pTSprite->picnum) * pTSprite->xrepeat) / 32;
break;
}
case kViewEffectTorchLow:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
2022-09-13 22:42:59 +00:00
double top, bottom;
2021-12-29 19:03:42 +00:00
GetSpriteExtents(pTSprite, &top, &bottom);
2022-09-13 22:42:59 +00:00
pNSprite->pos.Z = bottom;
2021-12-29 19:03:42 +00:00
pNSprite->picnum = 2101;
pNSprite->shade = -128;
pNSprite->xrepeat = pNSprite->yrepeat = (tileWidth(pTSprite->picnum) * pTSprite->xrepeat) / 32;
break;
}
case kViewEffectShadow:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
if (!pNSprite)
break;
2022-09-13 22:42:59 +00:00
pNSprite->pos.Z = getflorzofslopeptrf(pTSprite->sectp, pNSprite->pos);
if ((pNSprite->sectp->floorpicnum >= 4080) && (pNSprite->sectp->floorpicnum <= 4095) && !VanillaMode()) // if floor has ror, find actual floor
2021-12-29 19:03:42 +00:00
{
DVector3 cPos = pNSprite->pos;
double cZrel = cPos.Z;
auto cSect = pNSprite->sectp;
for (int i = 0; i < 16; i++) // scan through max stacked sectors
{
if (!CheckLink(cPos, &cSect)) // if no more floors underneath, abort
break;
const double newFloorZ = getflorzofslopeptrf(cSect, cPos.X, cPos.Z);
cZrel += newFloorZ - cPos.Z; // get height difference for next sector's ceiling/floor, and add to relative height for shadow
if ((cSect->floorpicnum < 4080) || (cSect->floorpicnum > 4095)) // if current sector is not open air, use as floor for shadow casting, otherwise continue to next sector
break;
cPos.Z = newFloorZ;
}
pNSprite->sectp = cSect;
pNSprite->pos.Z = cZrel;
2021-12-29 19:03:42 +00:00
}
pNSprite->shade = 127;
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
pNSprite->xrepeat = pTSprite->xrepeat;
pNSprite->yrepeat = pTSprite->yrepeat>>2;
pNSprite->picnum = pTSprite->picnum;
if (!VanillaMode() && (pTSprite->type == kThingDroppedLifeLeech)) // fix shadow for thrown lifeleech
pNSprite->picnum = 800;
pNSprite->pal = 5;
int height = tileHeight(pNSprite->picnum);
int center = height / 2 + tileTopOffset(pNSprite->picnum);
pNSprite->pos.Z -= (pNSprite->yrepeat * REPEAT_SCALE) * (height - center);
2021-12-29 19:03:42 +00:00
break;
}
case kViewEffectFlareHalo:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
pNSprite->shade = -128;
pNSprite->pal = 2;
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
2022-02-07 07:47:18 +00:00
pNSprite->pos.Z = pTSprite->pos.Z;
2021-12-29 19:03:42 +00:00
pNSprite->xrepeat = pTSprite->xrepeat;
pNSprite->yrepeat = pTSprite->yrepeat;
pNSprite->picnum = 2427;
break;
}
case kViewEffectCeilGlow:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
sectortype* pSector = pTSprite->sectp;
pNSprite->pos = { pTSprite->pos.X, pTSprite->pos.Y, pSector->ceilingz };
2021-12-29 19:03:42 +00:00
pNSprite->picnum = 624;
pNSprite->shade = ((pTSprite->int_pos().Z - pSector->int_ceilingz()) >> 8) - 64;
2021-12-29 19:03:42 +00:00
pNSprite->pal = 2;
pNSprite->xrepeat = pNSprite->yrepeat = 64;
pNSprite->cstat |= CSTAT_SPRITE_ONE_SIDE | CSTAT_SPRITE_ALIGNMENT_FLOOR | CSTAT_SPRITE_YFLIP | CSTAT_SPRITE_TRANSLUCENT;
pNSprite->angle = pTSprite->angle;
2021-12-29 19:03:42 +00:00
pNSprite->ownerActor = pTSprite->ownerActor;
break;
}
case kViewEffectFloorGlow:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
sectortype* pSector = pTSprite->sectp;
pNSprite->pos = { pTSprite->pos.X, pTSprite->pos.Y, pSector->floorz };
2021-12-29 19:03:42 +00:00
pNSprite->picnum = 624;
2022-08-20 17:40:23 +00:00
uint8_t nShade = (uint8_t)clamp(pSector->floorz - pTSprite->pos.Z, 0., 255.);
2021-12-29 19:03:42 +00:00
pNSprite->shade = nShade - 32;
pNSprite->pal = 2;
pNSprite->xrepeat = pNSprite->yrepeat = nShade;
pNSprite->cstat |= CSTAT_SPRITE_ONE_SIDE | CSTAT_SPRITE_ALIGNMENT_FLOOR | CSTAT_SPRITE_TRANSLUCENT;
pNSprite->angle = pTSprite->angle;
2021-12-29 19:03:42 +00:00
pNSprite->ownerActor = pTSprite->ownerActor;
break;
}
case kViewEffectSpear:
{
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
2022-02-07 07:47:18 +00:00
pNSprite->pos.Z = pTSprite->pos.Z;
2021-12-29 19:03:42 +00:00
if (gDetail > 1)
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT | CSTAT_SPRITE_TRANS_FLIP;
pNSprite->shade = ClipLow(pTSprite->shade - 32, -128);
pNSprite->xrepeat = pTSprite->xrepeat;
pNSprite->yrepeat = 64;
pNSprite->picnum = 775;
break;
}
case kViewEffectShowWeapon:
{
assert(pTSprite->type >= kDudePlayer1 && pTSprite->type <= kDudePlayer8);
PLAYER* pPlayer = &gPlayer[pTSprite->type - kDudePlayer1];
WEAPONICON weaponIcon = gWeaponIcon[pPlayer->curWeapon];
auto& nTile = weaponIcon.nTile;
if (nTile < 0) break;
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
2021-12-29 19:03:42 +00:00
if (!pNSprite)
break;
pNSprite->pos = pTSprite->pos.plusZ(-32 - weaponIcon.zOffset);
2021-12-29 19:03:42 +00:00
pNSprite->picnum = nTile;
pNSprite->shade = pTSprite->shade;
pNSprite->xrepeat = 32;
pNSprite->yrepeat = 32;
auto& nVoxel = voxelIndex[nTile];
if (cl_showweapon == 2 && r_voxels && nVoxel != -1)
{
auto gView = &gPlayer[gViewIndex];
pNSprite->set_int_ang((gView->actor->int_ang() + 512) & 2047); // always face viewer
2021-12-29 19:03:42 +00:00
pNSprite->cstat |= CSTAT_SPRITE_ALIGNMENT_SLAB;
pNSprite->cstat &= ~CSTAT_SPRITE_YFLIP;
pNSprite->picnum = nVoxel;
if (pPlayer->curWeapon == kWeapLifeLeech) // position lifeleech behind player
{
pNSprite->pos.XY() += gView->actor->spr.angle.ToVector() * 8;
2021-12-29 19:03:42 +00:00
}
if ((pPlayer->curWeapon == kWeapLifeLeech) || (pPlayer->curWeapon == kWeapVoodooDoll)) // make lifeleech/voodoo doll always face viewer like sprite
2022-09-16 16:48:50 +00:00
pNSprite->angle += DAngle90;
2021-12-29 19:03:42 +00:00
}
break;
}
}
return NULL;
2020-08-02 17:59:14 +00:00
}
2021-12-29 19:03:42 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void viewApplyDefaultPal(tspritetype* pTSprite, sectortype const* pSector)
2020-08-02 17:59:14 +00:00
{
2021-12-29 19:03:42 +00:00
XSECTOR const* pXSector = pSector->hasX() ? &pSector->xs() : nullptr;
if (pXSector && pXSector->color && (VanillaMode() || pSector->floorpal != 0))
{
copyfloorpal(pTSprite, pSector);
}
2020-08-02 17:59:14 +00:00
}
2021-12-29 19:03:42 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
2022-09-02 21:54:12 +00:00
static int GetOctant(int x, int y)
{
static const uint8_t OctantTable[8] = { 5, 6, 2, 1, 4, 7, 3, 0 };
int vc = abs(x) - abs(y);
return OctantTable[7 - (x < 0) - (y < 0) * 2 - (vc < 0) * 4];
}
void viewProcessSprites(tspriteArray& tsprites, int32_t cX, int32_t cY, int32_t cZ, DAngle cA, double interpfrac)
2020-08-02 17:59:14 +00:00
{
PLAYER* pPlayer = &gPlayer[gViewIndex];
int nViewSprites = tsprites.Size();
// shift before interpolating to increase precision.
DAngle myclock = DAngle::fromDeg(((PlayClock << 3) + (4 << 3) * interpfrac) * BAngToDegree);
2021-12-29 19:03:42 +00:00
gCameraAng = cA;
for (int nTSprite = int(tsprites.Size()) - 1; nTSprite >= 0; nTSprite--)
2021-12-29 19:03:42 +00:00
{
tspritetype* pTSprite = tsprites.get(nTSprite);
2021-12-29 19:03:42 +00:00
auto owneractor = static_cast<DBloodActor*>(pTSprite->ownerActor);
if (owneractor->spr.detail > gDetail)
{
pTSprite->xrepeat = 0;
continue;
}
int nTile = pTSprite->picnum;
if (nTile < 0 || nTile >= kMaxTiles)
{
pTSprite->xrepeat = 0;
continue;
}
// skip picnum 0 on face sprites. picnum 0 is a simple wall texture in Blood,
// but there are maps that use 0 on some operator sprites that may show up in potals as a result.
// Since the wall texture is perfectly fine for wall and floor sprites, these will be allowed to pass.
if (nTile == 0 && (pTSprite->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) == CSTAT_SPRITE_ALIGNMENT_FACING)
{
pTSprite->xrepeat = 0;
continue;
}
if (cl_interpolate && owneractor->interpolated && !(pTSprite->flags & 512))
{
pTSprite->pos = owneractor->interpolatedpos(interpfrac);
pTSprite->angle = owneractor->interpolatedangle(interpfrac);
2021-12-29 19:03:42 +00:00
}
int nAnim = 0;
switch (picanm[nTile].extra & 7) {
case 0:
if (!owneractor->hasX()) break;
switch (pTSprite->type) {
2022-05-06 16:27:44 +00:00
#ifdef NOONE_EXTENSIONS
case kModernCondition:
case kModernConditionFalse:
if (!gModernMap) break;
[[fallthrough]];
#endif
2021-12-29 19:03:42 +00:00
case kSwitchToggle:
case kSwitchOneWay:
if (owneractor->xspr.state) nAnim = 1;
break;
case kSwitchCombo:
nAnim = owneractor->xspr.data1;
break;
}
break;
case 1:
{
if (tilehasmodelorvoxel(pTSprite->picnum, pTSprite->pal) && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
2021-12-29 19:03:42 +00:00
{
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
break;
}
int dX = cX - pTSprite->int_pos().X;
int dY = cY - pTSprite->int_pos().Y;
2022-08-16 21:15:49 +00:00
RotateVector(&dX, &dY, 128 - pTSprite->int_ang());
2021-12-29 19:03:42 +00:00
nAnim = GetOctant(dX, dY);
if (nAnim <= 4)
{
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
}
else
{
nAnim = 8 - nAnim;
pTSprite->cstat |= CSTAT_SPRITE_XFLIP;
}
break;
}
case 2:
{
if (tilehasmodelorvoxel(pTSprite->picnum, pTSprite->pal) && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
2021-12-29 19:03:42 +00:00
{
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
break;
}
int dX = cX - pTSprite->int_pos().X;
int dY = cY - pTSprite->int_pos().Y;
2022-08-16 21:15:49 +00:00
RotateVector(&dX, &dY, 128 - pTSprite->int_ang());
2021-12-29 19:03:42 +00:00
nAnim = GetOctant(dX, dY);
break;
}
case 3:
{
if (owneractor->hasX())
{
if (owneractor->hit.florhit.type == kHitNone)
nAnim = 1;
}
else
{
int top, bottom;
GetSpriteExtents(pTSprite, &top, &bottom);
if (getflorzofslopeptr(pTSprite->sectp, pTSprite->pos) > bottom)
2021-12-29 19:03:42 +00:00
nAnim = 1;
}
break;
}
case 6:
case 7:
{
if (hw_models && md_tilehasmodel(pTSprite->picnum, pTSprite->pal) >= 0 && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
2021-12-29 19:03:42 +00:00
break;
// Can be overridden by def script
if (r_voxels && tiletovox[pTSprite->picnum] == -1 && voxelIndex[pTSprite->picnum] != -1 && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
2021-12-29 19:03:42 +00:00
{
if ((pTSprite->flags & kHitagRespawn) == 0)
{
pTSprite->cstat |= CSTAT_SPRITE_ALIGNMENT_SLAB;
pTSprite->cstat &= ~(CSTAT_SPRITE_XFLIP | CSTAT_SPRITE_YFLIP);
pTSprite->yoffset += tileTopOffset(pTSprite->picnum);
pTSprite->picnum = voxelIndex[pTSprite->picnum];
if ((picanm[nTile].extra & 7) == 7)
{
pTSprite->angle = myclock.Normalized360();
2021-12-29 19:03:42 +00:00
}
}
}
break;
}
}
while (nAnim > 0)
{
pTSprite->picnum += picanm[pTSprite->picnum].num + 1;
nAnim--;
}
if ((pTSprite->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) != CSTAT_SPRITE_ALIGNMENT_SLAB && r_voxels && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
2021-12-29 19:03:42 +00:00
{
int const nRootTile = pTSprite->picnum;
int nAnimTile = pTSprite->picnum + tileAnimateOfs(pTSprite->picnum, (pTSprite->ownerActor->GetIndex() & 16383));
2020-08-02 17:59:14 +00:00
#if 0
2021-12-29 19:03:42 +00:00
if (tiletovox[nAnimTile] != -1)
{
pTSprite->yoffset += tileTopOffset(nAnimTile);
pTSprite->xoffset += tileLeftOffset(nAnimTile);
}
2020-08-02 17:59:14 +00:00
#endif
2021-12-29 19:03:42 +00:00
int const nVoxel = tiletovox[pTSprite->picnum];
if (nVoxel != -1 && (picanm[nRootTile].extra & 7) == 7)
pTSprite->clipdist |= TSPR_MDLROTATE; // per-sprite rotation setting.
2021-12-29 19:03:42 +00:00
}
if ((pTSprite->cstat & CSTAT_SPRITE_ALIGNMENT_MASK) != CSTAT_SPRITE_ALIGNMENT_SLAB && hw_models && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
2021-12-29 19:03:42 +00:00
{
int const nRootTile = pTSprite->picnum;
int nAnimTile = pTSprite->picnum + tileAnimateOfs(pTSprite->picnum, (pTSprite->ownerActor->GetIndex() & 16383));
2021-12-29 19:03:42 +00:00
if (tile2model[Ptile2tile(nAnimTile, pTSprite->pal)].modelid >= 0 &&
tile2model[Ptile2tile(nAnimTile, pTSprite->pal)].framenum >= 0)
{
pTSprite->yoffset += tileTopOffset(nAnimTile);
pTSprite->xoffset += tileLeftOffset(nAnimTile);
if ((picanm[nRootTile].extra & 7) == 7)
pTSprite->clipdist |= TSPR_MDLROTATE; // per-sprite rotation setting.
2021-12-29 19:03:42 +00:00
}
}
sectortype* pSector = pTSprite->sectp;
2021-12-29 19:03:42 +00:00
XSECTOR const* pXSector = pSector->hasX() ? &pSector->xs() : nullptr;
int nShade = pTSprite->shade;
if ((pSector->ceilingstat & CSTAT_SECTOR_SKY) && (pSector->floorstat & CSTAT_SECTOR_NO_CEILINGSHADE) == 0)
{
nShade += tileShade[pSector->ceilingpicnum] + pSector->ceilingshade;
}
else
{
nShade += tileShade[pSector->floorpicnum] + pSector->floorshade;
}
nShade += tileShade[pTSprite->picnum];
pTSprite->shade = ClipRange(nShade, -128, 127);
if ((pTSprite->flags & kHitagRespawn) && pTSprite->ownerActor->spr.intowner == 3 && owneractor->hasX()) // Where does this 3 come from? Nothing sets it.
2021-12-29 19:03:42 +00:00
{
pTSprite->xrepeat = 48;
pTSprite->yrepeat = 48;
pTSprite->shade = -128;
pTSprite->picnum = 2272 + 2 * owneractor->xspr.respawnPending;
pTSprite->cstat &= ~(CSTAT_SPRITE_TRANSLUCENT | CSTAT_SPRITE_TRANS_FLIP);
if (((IsItemSprite(pTSprite) || IsAmmoSprite(pTSprite)) && gGameOptions.nItemSettings == 2)
|| (IsWeaponSprite(pTSprite) && gGameOptions.nWeaponSettings == 3))
{
pTSprite->xrepeat = pTSprite->yrepeat = 48;
}
else
{
pTSprite->xrepeat = pTSprite->yrepeat = 0;
}
}
if (owneractor->hasX() && owneractor->xspr.burnTime > 0)
{
pTSprite->shade = ClipRange(pTSprite->shade - 16 - QRandom(8), -128, 127);
}
if (pTSprite->flags & 256)
{
viewAddEffect(tsprites, nTSprite, kViewEffectSmokeHigh);
2021-12-29 19:03:42 +00:00
}
if (pTSprite->flags & 1024)
{
pTSprite->cstat |= CSTAT_SPRITE_XFLIP;
}
if (pTSprite->flags & 2048)
{
pTSprite->cstat |= CSTAT_SPRITE_YFLIP;
}
switch (pTSprite->statnum) {
case kStatDecoration: {
switch (pTSprite->type) {
case kDecorationCandle:
if (!owneractor->hasX() || owneractor->xspr.state == 1) {
pTSprite->shade = -128;
viewAddEffect(tsprites, nTSprite, kViewEffectPhase);
2021-12-29 19:03:42 +00:00
}
else {
pTSprite->shade = -8;
}
break;
case kDecorationTorch:
if (!owneractor->hasX() || owneractor->xspr.state == 1) {
pTSprite->picnum++;
viewAddEffect(tsprites, nTSprite, kViewEffectTorchHigh);
2021-12-29 19:03:42 +00:00
}
else {
viewAddEffect(tsprites, nTSprite, kViewEffectSmokeHigh);
2021-12-29 19:03:42 +00:00
}
break;
default:
viewApplyDefaultPal(pTSprite, pSector);
break;
}
}
break;
case kStatItem: {
switch (pTSprite->type) {
case kItemFlagABase:
if (owneractor->hasX() && owneractor->xspr.state > 0 && gGameOptions.nGameType == 3) {
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectBigFlag);
2021-12-29 19:03:42 +00:00
if (pNTSprite) pNTSprite->pal = 10;
}
break;
case kItemFlagBBase:
if (owneractor->hasX() && owneractor->xspr.state > 0 && gGameOptions.nGameType == 3) {
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectBigFlag);
2021-12-29 19:03:42 +00:00
if (pNTSprite) pNTSprite->pal = 7;
}
break;
case kItemFlagA:
pTSprite->pal = 10;
pTSprite->cstat |= CSTAT_SPRITE_BLOOD_BIT2;
break;
case kItemFlagB:
pTSprite->pal = 7;
pTSprite->cstat |= CSTAT_SPRITE_BLOOD_BIT2;
break;
default:
if (pTSprite->type >= kItemKeySkull && pTSprite->type < kItemKeyMax)
pTSprite->shade = -128;
viewApplyDefaultPal(pTSprite, pSector);
break;
}
}
break;
case kStatProjectile: {
switch (pTSprite->type) {
case kMissileTeslaAlt:
pTSprite->yrepeat = 128;
pTSprite->cstat |= CSTAT_SPRITE_ALIGNMENT_FLOOR;
break;
case kMissileTeslaRegular:
viewAddEffect(tsprites, nTSprite, kViewEffectTesla);
2021-12-29 19:03:42 +00:00
break;
case kMissileButcherKnife:
viewAddEffect(tsprites, nTSprite, kViewEffectTrail);
2021-12-29 19:03:42 +00:00
break;
case kMissileFlareRegular:
case kMissileFlareAlt:
if (pTSprite->statnum == kStatFlare) {
if (owneractor->GetTarget() == pPlayer->actor)
2021-12-29 19:03:42 +00:00
{
pTSprite->xrepeat = 0;
break;
}
}
viewAddEffect(tsprites, nTSprite, kViewEffectFlareHalo);
2021-12-29 19:03:42 +00:00
if (pTSprite->type != kMissileFlareRegular) break;
sectortype* pSector1 = pTSprite->sectp;
2021-12-29 19:03:42 +00:00
2022-08-20 17:40:23 +00:00
double zDiff = pTSprite->pos.Z - pSector1->ceilingz;
if ((pSector1->ceilingstat & CSTAT_SECTOR_SKY) == 0 && zDiff < 64)
{
viewAddEffect(tsprites, nTSprite, kViewEffectCeilGlow);
2021-12-29 19:03:42 +00:00
}
2022-08-20 17:40:23 +00:00
zDiff = (pSector1->floorz - pTSprite->pos.Z);
if ((pSector1->floorstat & CSTAT_SECTOR_SKY) == 0 && zDiff < 64)
{
viewAddEffect(tsprites, nTSprite, kViewEffectFloorGlow);
2021-12-29 19:03:42 +00:00
}
break;
}
break;
}
case kStatDude:
{
if (pTSprite->type == kDudeHand && owneractor->hasX() && owneractor->xspr.aiState == &hand13A3B4)
{
auto target = owneractor->GetTarget();
if (target && target->IsPlayerActor())
{
pTSprite->xrepeat = 0;
break;
}
}
if (pXSector && pXSector->color) copyfloorpal(pTSprite, pSector);
if (powerupCheck(pPlayer, kPwUpBeastVision) > 0) pTSprite->shade = -128;
2021-12-29 19:03:42 +00:00
if (IsPlayerSprite(pTSprite)) {
PLAYER* thisPlayer = &gPlayer[pTSprite->type - kDudePlayer1];
if (powerupCheck(thisPlayer, kPwUpShadowCloak) && !powerupCheck(pPlayer, kPwUpBeastVision)) {
2021-12-29 19:03:42 +00:00
pTSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
pTSprite->pal = 5;
}
else if (powerupCheck(thisPlayer, kPwUpDeathMask)) {
2021-12-29 19:03:42 +00:00
pTSprite->shade = -128;
pTSprite->pal = 5;
}
else if (powerupCheck(thisPlayer, kPwUpDoppleganger)) {
pTSprite->pal = 11 + (pPlayer->teamId & 3);
2021-12-29 19:03:42 +00:00
}
if (powerupCheck(thisPlayer, kPwUpReflectShots)) {
viewAddEffect(tsprites, nTSprite, kViewEffectReflectiveBall);
2021-12-29 19:03:42 +00:00
}
if (cl_showweapon && gGameOptions.nGameType > 0 && pPlayer) {
viewAddEffect(tsprites, nTSprite, kViewEffectShowWeapon);
2021-12-29 19:03:42 +00:00
}
if (thisPlayer->flashEffect && (pPlayer != thisPlayer || gViewPos != VIEWPOS_0)) {
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectShoot);
2021-12-29 19:03:42 +00:00
if (pNTSprite) {
POSTURE* pPosture = &thisPlayer->pPosture[thisPlayer->lifeMode][thisPlayer->posture];
pNTSprite->pos.XY() += pTSprite->angle.ToVector() * pPosture->xOffset;
pNTSprite->pos.Z = thisPlayer->actor->spr.pos.Z - pPosture->zOffset;
2021-12-29 19:03:42 +00:00
}
}
if (thisPlayer->hasFlag > 0 && gGameOptions.nGameType == 3) {
if (thisPlayer->hasFlag & 1) {
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectFlag);
2021-12-29 19:03:42 +00:00
if (pNTSprite)
{
pNTSprite->pal = 10;
pNTSprite->cstat |= CSTAT_SPRITE_XFLIP;
}
}
if (thisPlayer->hasFlag & 2) {
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectFlag);
2021-12-29 19:03:42 +00:00
if (pNTSprite)
{
pNTSprite->pal = 7;
pNTSprite->cstat |= CSTAT_SPRITE_XFLIP;
}
}
}
}
if (pTSprite->ownerActor != pPlayer->actor || gViewPos != VIEWPOS_0) {
if (getflorzofslopeptr(pTSprite->sectp, pTSprite->pos) >= cZ)
2021-12-29 19:03:42 +00:00
{
viewAddEffect(tsprites, nTSprite, kViewEffectShadow);
2021-12-29 19:03:42 +00:00
}
}
if (gModernMap && owneractor->hasX()) { // add target spot indicator for patrol dudes
if (owneractor->xspr.dudeFlag4 && aiInPatrolState(owneractor->xspr.aiState) && owneractor->xspr.data3 > 0 && owneractor->xspr.data3 <= kMaxPatrolSpotValue)
viewAddEffect(tsprites, nTSprite, kViewEffectSpotProgress);
2021-12-29 19:03:42 +00:00
}
break;
}
case kStatTraps: {
if (pTSprite->type == kTrapSawCircular) {
if (owneractor->xspr.state) {
if (owneractor->xspr.data1) {
pTSprite->picnum = 772;
if (owneractor->xspr.data2)
viewAddEffect(tsprites, nTSprite, kViewEffectSpear);
2021-12-29 19:03:42 +00:00
}
}
else if (owneractor->xspr.data1) pTSprite->picnum = 773;
else pTSprite->picnum = 656;
}
break;
}
case kStatThing: {
viewApplyDefaultPal(pTSprite, pSector);
if (pTSprite->type < kThingBase || pTSprite->type >= kThingMax || owneractor->hit.florhit.type == kHitNone)
{
if ((pTSprite->flags & kPhysMove) && getflorzofslopeptr(pTSprite->sectp, pTSprite->pos) >= cZ)
viewAddEffect(tsprites, nTSprite, kViewEffectShadow);
2021-12-29 19:03:42 +00:00
}
}
break;
}
}
for (int nTSprite = int(tsprites.Size() - 1); nTSprite >= nViewSprites; nTSprite--)
2021-12-29 19:03:42 +00:00
{
tspritetype* pTSprite = tsprites.get(nTSprite);
2021-12-29 19:03:42 +00:00
int nAnim = 0;
switch (picanm[pTSprite->picnum].extra & 7)
{
case 1:
{
int dX = cX - pTSprite->int_pos().X;
int dY = cY - pTSprite->int_pos().Y;
2022-08-16 21:15:49 +00:00
RotateVector(&dX, &dY, 128 - pTSprite->int_ang());
2021-12-29 19:03:42 +00:00
nAnim = GetOctant(dX, dY);
if (nAnim <= 4)
{
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
}
else
{
nAnim = 8 - nAnim;
pTSprite->cstat |= CSTAT_SPRITE_XFLIP;
}
break;
}
case 2:
{
int dX = cX - pTSprite->int_pos().X;
int dY = cY - pTSprite->int_pos().Y;
2022-08-16 21:15:49 +00:00
RotateVector(&dX, &dY, 128 - pTSprite->int_ang());
2021-12-29 19:03:42 +00:00
nAnim = GetOctant(dX, dY);
break;
}
}
while (nAnim > 0)
{
pTSprite->picnum += picanm[pTSprite->picnum].num + 1;
nAnim--;
}
}
2020-08-02 17:59:14 +00:00
}
2021-12-29 19:03:42 +00:00
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameInterface::processSprites(tspriteArray& tsprites, const DVector3& view, DAngle viewang, double interpfrac)
2021-03-20 22:01:16 +00:00
{
viewProcessSprites(tsprites, view.X * worldtoint, view.Y * worldtoint, view.Z * zworldtoint, viewang, interpfrac);
2021-03-20 22:01:16 +00:00
}
2021-03-29 19:48:23 +00:00
int display_mirror;
void GameInterface::EnterPortal(DCoreActor* viewer, int type)
2021-03-29 19:48:23 +00:00
{
2021-12-29 19:03:42 +00:00
if (type == PORTAL_WALL_MIRROR)
{
display_mirror++;
if (viewer) viewer->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
}
2021-03-29 19:48:23 +00:00
}
void GameInterface::LeavePortal(DCoreActor* viewer, int type)
2021-03-29 19:48:23 +00:00
{
2021-12-29 19:03:42 +00:00
if (type == PORTAL_WALL_MIRROR)
{
display_mirror--;
if (viewer && display_mirror == 0 && !(viewer->spr.cstat & CSTAT_SPRITE_TRANSLUCENT)) viewer->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
}
2021-03-29 19:48:23 +00:00
}
2020-08-02 17:59:14 +00:00
END_BLD_NS