mirror of
https://github.com/ZDoom/Raze.git
synced 2024-11-28 15:02:24 +00:00
1000 lines
29 KiB
C++
1000 lines
29 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "build.h"
|
|
#include "v_font.h"
|
|
|
|
#include "blood.h"
|
|
|
|
#include "zstring.h"
|
|
#include "razemenu.h"
|
|
#include "gstrings.h"
|
|
#include "v_2ddrawer.h"
|
|
#include "v_video.h"
|
|
#include "v_font.h"
|
|
#include "hw_voxels.h"
|
|
#include "gamefuncs.h"
|
|
#include "texturemanager.h"
|
|
#include "texinfo.h"
|
|
#include "models/modeldata.h"
|
|
|
|
BEGIN_BLD_NS
|
|
|
|
static DAngle gCameraAng;
|
|
DAngle random_angles[16][3];
|
|
|
|
// to allow quick replacement later
|
|
|
|
bool IsPlayerSprite(tspritetype const* const pSprite)
|
|
{
|
|
return pSprite->type >= kDudePlayer1 && pSprite->type <= kDudePlayer8;
|
|
}
|
|
|
|
bool IsDudeSprite(tspritetype const* const pSprite)
|
|
{
|
|
return pSprite->type >= kDudeBase && pSprite->type < kDudeMax;
|
|
}
|
|
|
|
bool IsItemSprite(tspritetype const* const pSprite)
|
|
{
|
|
return pSprite->type >= kItemBase && pSprite->type < kItemMax;
|
|
}
|
|
|
|
bool IsWeaponSprite(tspritetype const* const pSprite)
|
|
{
|
|
return pSprite->type >= kItemWeaponBase && pSprite->type < kItemWeaponMax;
|
|
}
|
|
|
|
bool IsAmmoSprite(tspritetype const* const pSprite)
|
|
{
|
|
return pSprite->type >= kItemAmmoBase && pSprite->type < kItemAmmoMax;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
tspritetype* viewInsertTSprite(tspriteArray& tsprites, sectortype* pSector, int nStatnum, tspritetype const* const parentTSprite)
|
|
{
|
|
tspritetype* pTSprite = tsprites.newTSprite();
|
|
memset(pTSprite, 0, sizeof(tspritetype));
|
|
pTSprite->cstat = CSTAT_SPRITE_YCENTER;
|
|
pTSprite->scale = DVector2(1, 1);
|
|
pTSprite->ownerActor = nullptr;
|
|
pTSprite->type = -int(tsprites.Size() - 1);
|
|
pTSprite->statnum = nStatnum;
|
|
pTSprite->sectp = pSector;
|
|
|
|
DVector3 pos = { 0,0,0 };
|
|
if (parentTSprite)
|
|
{
|
|
pos = parentTSprite->pos;
|
|
pTSprite->ownerActor = parentTSprite->ownerActor;
|
|
pTSprite->Angles.Yaw = parentTSprite->Angles.Yaw;
|
|
}
|
|
pos.XY() += gCameraAng.ToVector() * 2;
|
|
pTSprite->pos = pos;
|
|
return pTSprite;
|
|
}
|
|
|
|
static const int effectDetail[kViewEffectMax] = {
|
|
4, 4, 4, 4, 0, 0, 0, 0, 0, 1, 4, 4, 0, 0, 0, 1, 0, 0, 0
|
|
};
|
|
|
|
|
|
struct WEAPONICON {
|
|
int16_t nTile;
|
|
uint8_t zOffset;
|
|
FTextureID textureID() const { return aTexIds[nTile]; }
|
|
};
|
|
|
|
static const WEAPONICON gWeaponIcon[] = {
|
|
{ -1, 0 },
|
|
{ -1, 0 }, // 1: pitchfork
|
|
{ kTexICONFLAREGUN, 6 }, // 2: flare gun
|
|
{ kTexICONSHOTGUN, 6 }, // 3: shotgun
|
|
{ kTexICONTOMMY, 8 }, // 4: tommy gun
|
|
{ kTexICONNAPALM, 6 }, // 5: napalm launcher
|
|
{ kTexAmmoIcon5, 11 }, // 6: dynamite
|
|
{ kTexAmmoIcon6, 11 }, // 7: spray can
|
|
{ kTexICONTESLA, 6 }, // 8: tesla gun
|
|
{ kTexICONLEECH, 0 }, // 9: life leech
|
|
{ kTexAmmoIcon9, 11 }, // 10: voodoo doll
|
|
{ kTexAmmoIcon10, 11 }, // 11: proxy bomb
|
|
{ kTexAmmoIcon11, 11 }, // 12: remote bomb
|
|
{ -1, 0 },
|
|
};
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static tspritetype* viewAddEffect(tspriteArray& tsprites, int nTSprite, VIEW_EFFECT nViewEffect)
|
|
{
|
|
double s;
|
|
assert(nViewEffect >= 0 && nViewEffect < kViewEffectMax);
|
|
auto pTSprite = tsprites.get(nTSprite);
|
|
auto owneractor = static_cast<DBloodActor*>(pTSprite->ownerActor);
|
|
if (gDetail < effectDetail[nViewEffect]) return NULL;
|
|
auto pTTex = TexMan.GetGameTexture(pTSprite->spritetexture());
|
|
switch (nViewEffect)
|
|
{
|
|
case kViewEffectSpotProgress: {
|
|
int perc = (100 * owneractor->xspr.data3) / kMaxPatrolSpotValue;
|
|
int width = (94 * owneractor->xspr.data3) / kMaxPatrolSpotValue;
|
|
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
|
|
auto pNSprite2 = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite2)
|
|
break;
|
|
|
|
pNSprite2->setspritetexture(aTexIds[kTexSPOTPROGRESS]);
|
|
pNSprite2->scale = DVector2(width * REPEAT_SCALE, 0.3125);
|
|
|
|
pNSprite2->pal = 10;
|
|
if (perc >= 75) pNSprite2->pal = 0;
|
|
else if (perc >= 50) pNSprite2->pal = 6;
|
|
|
|
pNSprite2->pos.Z = (top - 8);
|
|
pNSprite2->shade = -128;
|
|
break;
|
|
}
|
|
case kViewEffectAtom:
|
|
for (int i = 0; i < 16; i++)
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
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];
|
|
ang += nRand3;
|
|
auto vect = DVector3(32 * ang.ToVector(), 0);
|
|
DVector2 pt(vect.Y, vect.Z);
|
|
pt = pt.Rotated(nRand1);
|
|
vect.Y = pt.X;
|
|
pt.X = vect.X;
|
|
pt = pt.Rotated(nRand2);
|
|
vect.X = pt.X;
|
|
vect.Z = pt.Y;
|
|
|
|
pNSprite->pos = pTSprite->pos + vect;
|
|
pNSprite->setspritetexture(aTexIds[kTexATOMEFFECT]);
|
|
pNSprite->shade = -128;
|
|
}
|
|
break;
|
|
case kViewEffectFlag:
|
|
case kViewEffectBigFlag:
|
|
{
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->shade = -128;
|
|
pNSprite->pal = 0;
|
|
pNSprite->pos.Z = top;
|
|
if (nViewEffect == kViewEffectFlag)
|
|
pNSprite->scale = DVector2(0.375, 0.375);
|
|
else
|
|
pNSprite->scale = DVector2(1, 1);
|
|
pNSprite->setspritetexture(aTexIds[kTexFLAGHAVE]);
|
|
return pNSprite;
|
|
}
|
|
case kViewEffectTesla:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->pos.Z = pTSprite->pos.Z;
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->shade = -128;
|
|
pNSprite->scale = pTSprite->scale;
|
|
pNSprite->setspritetexture(aTexIds[kTexTESLAEFFECT]);
|
|
break;
|
|
}
|
|
case kViewEffectShoot:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->shade = -128;
|
|
pNSprite->pal = 0;
|
|
pNSprite->scale = DVector2(1, 1);
|
|
pNSprite->setspritetexture(aTexIds[kTexSHOOTEFFECT]);
|
|
return pNSprite;
|
|
}
|
|
case kViewEffectReflectiveBall:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->shade = 26;
|
|
pNSprite->pal = 0;
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->scale = DVector2(1, 1);
|
|
pNSprite->setspritetexture(aTexIds[kTexBALLEFFECT]);
|
|
break;
|
|
}
|
|
case kViewEffectPhase:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
pNSprite->pos.Z = top;
|
|
pNSprite->shade = 26;
|
|
pNSprite->pal = 0;
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->scale = DVector2(0.375, 0.375);
|
|
pNSprite->setspritetexture(aTexIds[kTexPHASEEFFECT]);
|
|
break;
|
|
}
|
|
case kViewEffectTrail:
|
|
{
|
|
auto nAng = pTSprite->Angles.Yaw;
|
|
if (pTSprite->cstat & CSTAT_SPRITE_ALIGNMENT_WALL)
|
|
{
|
|
nAng += DAngle90;
|
|
}
|
|
else
|
|
{
|
|
nAng += DAngle180;
|
|
}
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
auto pSector = pTSprite->sectp;
|
|
auto pNSprite = viewInsertTSprite(tsprites, pSector, 32767, NULL);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
double nLen = 8.0 * (i + 1);
|
|
auto vect = nAng.ToVector() * nLen;
|
|
pNSprite->pos = pTSprite->pos + vect;
|
|
assert(pSector);
|
|
auto pSector2 = pSector;
|
|
updatesectorz(pNSprite->pos, &pSector2);
|
|
if (pSector2) pSector = pSector2;
|
|
pNSprite->sectp = pSector;
|
|
pNSprite->ownerActor = pTSprite->ownerActor;
|
|
pNSprite->setspritetexture(pTSprite->spritetexture());
|
|
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->scale = pTSprite->scale;
|
|
}
|
|
break;
|
|
}
|
|
case kViewEffectFlame:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->shade = -128;
|
|
pNSprite->pos.Z = pTSprite->pos.Z;
|
|
pNSprite->setspritetexture(aTexIds[kTexFLAMEEFFECT]);
|
|
pNSprite->statnum = kStatDecoration;
|
|
s = (pTTex->GetDisplayWidth() * pTSprite->scale.X) / 64.;
|
|
pNSprite->scale = DVector2(s, s);
|
|
break;
|
|
}
|
|
case kViewEffectSmokeHigh:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
pNSprite->pos.Z = top;
|
|
if (IsDudeSprite(pTSprite))
|
|
pNSprite->setspritetexture(aTexIds[kTexBIGSMOKEEFFECT]);
|
|
else
|
|
pNSprite->setspritetexture(aTexIds[kTexSMALLSMOKEEFFECT]);
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->shade = 8;
|
|
pNSprite->scale = pTSprite->scale;
|
|
break;
|
|
}
|
|
case kViewEffectSmokeLow:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
pNSprite->pos.Z = bottom;
|
|
if (pTSprite->type >= kDudeBase && pTSprite->type < kDudeMax)
|
|
pNSprite->setspritetexture(aTexIds[kTexBIGSMOKEEFFECT]);
|
|
else
|
|
pNSprite->setspritetexture(aTexIds[kTexSMALLSMOKEEFFECT]);
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->shade = 8;
|
|
pNSprite->scale = pTSprite->scale;
|
|
break;
|
|
}
|
|
case kViewEffectTorchHigh:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
pNSprite->pos.Z = top;
|
|
pNSprite->setspritetexture(aTexIds[kTexTORCHEFFECT]);
|
|
pNSprite->shade = -128;
|
|
s = (pTTex->GetDisplayWidth() * pTSprite->scale.X) / 32.;
|
|
pNSprite->scale = DVector2(s, s);
|
|
break;
|
|
}
|
|
case kViewEffectTorchLow:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
pNSprite->pos.Z = bottom;
|
|
pNSprite->setspritetexture(aTexIds[kTexTORCHEFFECT]);
|
|
pNSprite->shade = -128;
|
|
s = (pTTex->GetDisplayWidth() * pTSprite->scale.X) / 32.;
|
|
pNSprite->scale = DVector2(s, s);
|
|
break;
|
|
}
|
|
case kViewEffectShadow:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
pNSprite->pos.Z = getflorzofslopeptr(pTSprite->sectp, pNSprite->pos);
|
|
if (pNSprite->sectp->portalflags == PORTAL_SECTOR_FLOOR && !VanillaMode()) // if floor has ror, find actual floor
|
|
{
|
|
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 = getflorzofslopeptr(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->portalflags != PORTAL_SECTOR_FLOOR) // 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;
|
|
}
|
|
pNSprite->shade = 127;
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->scale.X = pTSprite->scale.X;
|
|
pNSprite->scale.Y = pTSprite->scale.Y * 0.25;
|
|
pNSprite->setspritetexture(pTSprite->spritetexture());
|
|
if (!VanillaMode() && (pTSprite->type == kThingDroppedLifeLeech)) // fix shadow for thrown lifeleech
|
|
pNSprite->setspritetexture(aTexIds[kTexICONLEECH]);
|
|
pNSprite->pal = 5;
|
|
auto tex = TexMan.GetGameTexture(pNSprite->spritetexture());
|
|
double height = tex->GetDisplayHeight();
|
|
double center = height / 2 + tex->GetDisplayTopOffset();
|
|
pNSprite->pos.Z -= (pNSprite->scale.Y) * (height - center);
|
|
break;
|
|
}
|
|
case kViewEffectFlareHalo:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->shade = -128;
|
|
pNSprite->pal = 2;
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->pos.Z = pTSprite->pos.Z;
|
|
pNSprite->scale = pTSprite->scale;
|
|
pNSprite->setspritetexture(aTexIds[kTexHALOEFFECT]);
|
|
break;
|
|
}
|
|
case kViewEffectCeilGlow:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
sectortype* pSector = pTSprite->sectp;
|
|
pNSprite->pos = { pTSprite->pos.X, pTSprite->pos.Y, pSector->ceilingz };
|
|
|
|
pNSprite->setspritetexture(aTexIds[kTexGLOWEFFECT]);
|
|
pNSprite->shade = int(pTSprite->pos.Z - pSector->ceilingz) - 64;
|
|
pNSprite->pal = 2;
|
|
pNSprite->scale = DVector2(1, 1);
|
|
pNSprite->cstat |= CSTAT_SPRITE_ONE_SIDE | CSTAT_SPRITE_ALIGNMENT_FLOOR | CSTAT_SPRITE_YFLIP | CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->Angles.Yaw = pTSprite->Angles.Yaw;
|
|
pNSprite->ownerActor = pTSprite->ownerActor;
|
|
break;
|
|
}
|
|
case kViewEffectFloorGlow:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
sectortype* pSector = pTSprite->sectp;
|
|
pNSprite->pos = { pTSprite->pos.X, pTSprite->pos.Y, pSector->floorz };
|
|
pNSprite->setspritetexture(aTexIds[kTexGLOWEFFECT]);
|
|
uint8_t nShade = (uint8_t)clamp(pSector->floorz - pTSprite->pos.Z, 0., 255.);
|
|
pNSprite->shade = nShade - 32;
|
|
pNSprite->pal = 2;
|
|
pNSprite->scale = DVector2(nShade * REPEAT_SCALE, nShade * REPEAT_SCALE);
|
|
pNSprite->cstat |= CSTAT_SPRITE_ONE_SIDE | CSTAT_SPRITE_ALIGNMENT_FLOOR | CSTAT_SPRITE_TRANSLUCENT;
|
|
pNSprite->Angles.Yaw = pTSprite->Angles.Yaw;
|
|
pNSprite->ownerActor = pTSprite->ownerActor;
|
|
break;
|
|
}
|
|
case kViewEffectSpear:
|
|
{
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->pos.Z = pTSprite->pos.Z;
|
|
if (gDetail > 1)
|
|
pNSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT | CSTAT_SPRITE_TRANS_FLIP;
|
|
pNSprite->shade = ClipLow(pTSprite->shade - 32, -128);
|
|
pNSprite->scale = DVector2(pTSprite->scale.X, 1);
|
|
pNSprite->setspritetexture(aTexIds[kTexSAWBLOOD]);
|
|
break;
|
|
}
|
|
case kViewEffectShowWeapon:
|
|
{
|
|
assert(pTSprite->type >= kDudePlayer1 && pTSprite->type <= kDudePlayer8);
|
|
DBloodPlayer* pPlayer = getPlayer(pTSprite->type - kDudePlayer1);
|
|
WEAPONICON weaponIcon = gWeaponIcon[pPlayer->curWeapon];
|
|
auto nTex = weaponIcon.textureID();
|
|
if (!nTex.isValid()) break;
|
|
auto pNSprite = viewInsertTSprite(tsprites, pTSprite->sectp, 32767, pTSprite);
|
|
if (!pNSprite)
|
|
break;
|
|
|
|
pNSprite->pos = pTSprite->pos.plusZ(-32 - weaponIcon.zOffset);
|
|
pNSprite->setspritetexture(nTex);
|
|
pNSprite->shade = pTSprite->shade;
|
|
pNSprite->scale = DVector2(0.5, 0.5);
|
|
int nVoxel = GetExtInfo(nTex).tiletovox;
|
|
if (cl_showweapon == 2 && r_voxels && nVoxel != -1)
|
|
{
|
|
auto gView = getPlayer(gViewIndex);
|
|
pNSprite->Angles.Yaw = gView->GetActor()->spr.Angles.Yaw + DAngle90; // always face viewer
|
|
pNSprite->cstat &= ~CSTAT_SPRITE_YFLIP;
|
|
if (pPlayer->curWeapon == kWeapLifeLeech) // position lifeleech behind player
|
|
{
|
|
pNSprite->pos.XY() += gView->GetActor()->spr.Angles.Yaw.ToVector() * 8;
|
|
}
|
|
if ((pPlayer->curWeapon == kWeapLifeLeech) || (pPlayer->curWeapon == kWeapVoodooDoll)) // make lifeleech/voodoo doll always face viewer like sprite
|
|
pNSprite->Angles.Yaw += DAngle90;
|
|
}
|
|
else
|
|
{
|
|
pNSprite->cstat2 |= CSTAT2_SPRITE_NOMODEL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static void viewApplyDefaultPal(tspritetype* pTSprite, sectortype const* pSector)
|
|
{
|
|
XSECTOR const* pXSector = pSector->hasX() ? &pSector->xs() : nullptr;
|
|
if (pXSector && pXSector->color && (VanillaMode() || pSector->floorpal != 0))
|
|
{
|
|
copyfloorpal(pTSprite, pSector);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
static int GetOctant(const DVector2& dPos)
|
|
{
|
|
static const uint8_t OctantTable[8] = { 5, 6, 2, 1, 4, 7, 3, 0 };
|
|
double vc = fabs(dPos.X) - fabs(dPos.Y);
|
|
return OctantTable[7 - (dPos.X < 0) - (dPos.Y < 0) * 2 - (vc < 0) * 4];
|
|
}
|
|
|
|
void viewProcessSprites(tspriteArray& tsprites, const DVector3& cPos, DAngle cA, double interpfrac)
|
|
{
|
|
DBloodPlayer* pPlayer = getPlayer(gViewIndex);
|
|
int nViewSprites = tsprites.Size();
|
|
// shift before interpolating to increase precision.
|
|
DAngle myclock = DAngle::fromBuild((PlayClock << 3) + (4 << 3) * interpfrac);
|
|
gCameraAng = cA;
|
|
for (int nTSprite = int(tsprites.Size()) - 1; nTSprite >= 0; nTSprite--)
|
|
{
|
|
tspritetype* pTSprite = tsprites.get(nTSprite);
|
|
auto owneractor = static_cast<DBloodActor*>(pTSprite->ownerActor);
|
|
if (owneractor->spr.detail > gDetail)
|
|
{
|
|
pTSprite->scale = DVector2(0, 0);
|
|
continue;
|
|
}
|
|
auto nTex = pTSprite->spritetexture();
|
|
if (!nTex.isValid())
|
|
{
|
|
pTSprite->scale = DVector2(0, 0);
|
|
continue;
|
|
}
|
|
|
|
if (cl_interpolate && owneractor->interpolated && !(pTSprite->flags & 512))
|
|
{
|
|
pTSprite->pos = owneractor->interpolatedpos(interpfrac);
|
|
pTSprite->Angles.Yaw = owneractor->interpolatedyaw(interpfrac);
|
|
}
|
|
int nAnim = 0;
|
|
int nAnimType = GetExtInfo(nTex).picanm.extra & 7;
|
|
switch (nAnimType)
|
|
{
|
|
case 0:
|
|
if (!owneractor->hasX()) break;
|
|
switch (pTSprite->type)
|
|
{
|
|
#ifdef NOONE_EXTENSIONS
|
|
case kModernCondition:
|
|
case kModernConditionFalse:
|
|
if (!gModernMap) break;
|
|
[[fallthrough]];
|
|
#endif
|
|
case kSwitchToggle:
|
|
case kSwitchOneWay:
|
|
if (owneractor->xspr.state) nAnim = 1;
|
|
break;
|
|
case kSwitchCombo:
|
|
nAnim = owneractor->xspr.data1;
|
|
break;
|
|
}
|
|
break;
|
|
case 1:
|
|
{
|
|
if (tilehasmodelorvoxel(pTSprite->spritetexture(), pTSprite->pal) && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
|
|
{
|
|
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
|
|
break;
|
|
}
|
|
nAnim = GetOctant(DVector2(cPos.XY() - pTSprite->pos).Rotated(DAngle22_5 - pTSprite->Angles.Yaw));
|
|
if (nAnim <= 4)
|
|
{
|
|
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
|
|
}
|
|
else
|
|
{
|
|
nAnim = 8 - nAnim;
|
|
pTSprite->cstat |= CSTAT_SPRITE_XFLIP;
|
|
}
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if (tilehasmodelorvoxel(pTSprite->spritetexture(), pTSprite->pal) && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
|
|
{
|
|
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
|
|
break;
|
|
}
|
|
nAnim = GetOctant(DVector2(cPos.XY() - pTSprite->pos).Rotated(DAngle22_5 - pTSprite->Angles.Yaw));
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
if (owneractor->hasX())
|
|
{
|
|
if (owneractor->hit.florhit.type == kHitNone)
|
|
nAnim = 1;
|
|
}
|
|
else
|
|
{
|
|
double top, bottom;
|
|
GetSpriteExtents(pTSprite, &top, &bottom);
|
|
if (getflorzofslopeptr(pTSprite->sectp, pTSprite->pos) > bottom)
|
|
nAnim = 1;
|
|
}
|
|
break;
|
|
}
|
|
case 6:
|
|
case 7:
|
|
{
|
|
if (hw_models && modelManager.CheckModel(pTSprite->spritetexture(), pTSprite->pal) && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
|
|
break;
|
|
|
|
// Can be overridden by def script
|
|
if (tilehasvoxel(pTSprite->spritetexture()) && !(owneractor->sprext.renderflags & SPREXT_NOTMD))
|
|
{
|
|
if ((pTSprite->flags & kHitagRespawn) == 0)
|
|
{
|
|
pTSprite->cstat &= ~(CSTAT_SPRITE_XFLIP | CSTAT_SPRITE_YFLIP);
|
|
auto tex = TexMan.GetGameTexture(pTSprite->spritetexture());
|
|
auto ofs = GetExtInfo(nTex).voxoffs;
|
|
pTSprite->yoffset += ofs;
|
|
if (nAnimType == 7)
|
|
{
|
|
pTSprite->Angles.Yaw = myclock.Normalized360();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
while (nAnim > 0)
|
|
{
|
|
pTSprite->setspritetexture(pTSprite->spritetexture() + GetExtInfo(pTSprite->spritetexture()).picanm.num + 1);
|
|
nAnim--;
|
|
}
|
|
|
|
sectortype* pSector = pTSprite->sectp;
|
|
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 += GetExtInfo(pSector->ceilingtexture).tileshade + pSector->ceilingshade;
|
|
}
|
|
else
|
|
{
|
|
nShade += GetExtInfo(pSector->floortexture).tileshade + pSector->floorshade;
|
|
}
|
|
nShade += GetExtInfo(pTSprite->spritetexture()).tileshade;
|
|
pTSprite->shade = ClipRange(nShade, -128, 127);
|
|
#if 0 // This was disabled because it seemingly cannot be activated (see comment below) and the sprites being used here are part of something else.
|
|
if ((pTSprite->flags & kHitagRespawn) && pTSprite->ownerActor->spr.intowner == 3 && owneractor->hasX()) // Where does this 3 come from? Nothing sets it.
|
|
{
|
|
pTSprite->scale = DVector2(0.75, 0.75);
|
|
pTSprite->shade = -128;
|
|
pTSprite->p icnum = 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->scale = DVector2(0.75, 0.75);
|
|
}
|
|
else
|
|
{
|
|
pTSprite->scale = DVector2(0, 0);
|
|
}
|
|
}
|
|
#endif
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
else {
|
|
pTSprite->shade = -8;
|
|
}
|
|
break;
|
|
case kDecorationTorch:
|
|
if (!owneractor->hasX() || owneractor->xspr.state == 1) {
|
|
pTSprite->setspritetexture(pTSprite->spritetexture() + 1);
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectTorchHigh);
|
|
}
|
|
else {
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectSmokeHigh);
|
|
}
|
|
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);
|
|
if (pNTSprite) pNTSprite->pal = 10;
|
|
}
|
|
break;
|
|
case kItemFlagBBase:
|
|
if (owneractor->hasX() && owneractor->xspr.state > 0 && gGameOptions.nGameType == 3) {
|
|
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectBigFlag);
|
|
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->scale.Y = (2);
|
|
pTSprite->cstat |= CSTAT_SPRITE_ALIGNMENT_FLOOR;
|
|
break;
|
|
case kMissileTeslaRegular:
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectTesla);
|
|
break;
|
|
case kMissileButcherKnife:
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectTrail);
|
|
break;
|
|
case kMissileFlareRegular:
|
|
case kMissileFlareAlt:
|
|
if (pTSprite->statnum == kStatFlare) {
|
|
if (owneractor->GetTarget() == pPlayer->GetActor())
|
|
{
|
|
pTSprite->scale = DVector2(0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectFlareHalo);
|
|
if (pTSprite->type != kMissileFlareRegular) break;
|
|
sectortype* pSector1 = pTSprite->sectp;
|
|
|
|
double zDiff = pTSprite->pos.Z - pSector1->ceilingz;
|
|
if ((pSector1->ceilingstat & CSTAT_SECTOR_SKY) == 0 && zDiff < 64)
|
|
{
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectCeilGlow);
|
|
}
|
|
|
|
zDiff = (pSector1->floorz - pTSprite->pos.Z);
|
|
if ((pSector1->floorstat & CSTAT_SECTOR_SKY) == 0 && zDiff < 64)
|
|
{
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectFloorGlow);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case kStatDude:
|
|
{
|
|
if (pTSprite->type == kDudeHand && owneractor->hasX() && owneractor->xspr.aiState == &hand13A3B4)
|
|
{
|
|
auto target = owneractor->GetTarget();
|
|
if (target && target->IsPlayerActor())
|
|
{
|
|
pTSprite->scale = DVector2(0, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pXSector && pXSector->color) copyfloorpal(pTSprite, pSector);
|
|
if (powerupCheck(pPlayer, kPwUpBeastVision) > 0) pTSprite->shade = -128;
|
|
|
|
if (IsPlayerSprite(pTSprite)) {
|
|
DBloodPlayer* thisPlayer = getPlayer(pTSprite->type - kDudePlayer1);
|
|
if (powerupCheck(thisPlayer, kPwUpShadowCloak) && !powerupCheck(pPlayer, kPwUpBeastVision)) {
|
|
pTSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT;
|
|
pTSprite->pal = 5;
|
|
}
|
|
else if (powerupCheck(thisPlayer, kPwUpDeathMask)) {
|
|
pTSprite->shade = -128;
|
|
pTSprite->pal = 5;
|
|
}
|
|
else if (powerupCheck(thisPlayer, kPwUpDoppleganger)) {
|
|
pTSprite->pal = 11 + (pPlayer->teamId & 3);
|
|
}
|
|
|
|
if (powerupCheck(thisPlayer, kPwUpReflectShots)) {
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectReflectiveBall);
|
|
}
|
|
|
|
if (cl_showweapon && gGameOptions.nGameType > 0 && pPlayer) {
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectShowWeapon);
|
|
}
|
|
|
|
if (thisPlayer->flashEffect && (pPlayer != thisPlayer || gViewPos != viewFirstPerson)) {
|
|
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectShoot);
|
|
if (pNTSprite) {
|
|
POSTURE* pPosture = &thisPlayer->pPosture[thisPlayer->lifeMode][thisPlayer->posture];
|
|
pNTSprite->pos.XY() += pTSprite->Angles.Yaw.ToVector() * pPosture->xOffset;
|
|
pNTSprite->pos.Z = thisPlayer->GetActor()->spr.pos.Z - pPosture->zOffset;
|
|
}
|
|
}
|
|
|
|
if (thisPlayer->hasFlag > 0 && gGameOptions.nGameType == 3) {
|
|
if (thisPlayer->hasFlag & 1) {
|
|
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectFlag);
|
|
if (pNTSprite)
|
|
{
|
|
pNTSprite->pal = 10;
|
|
pNTSprite->cstat |= CSTAT_SPRITE_XFLIP;
|
|
}
|
|
}
|
|
if (thisPlayer->hasFlag & 2) {
|
|
auto pNTSprite = viewAddEffect(tsprites, nTSprite, kViewEffectFlag);
|
|
if (pNTSprite)
|
|
{
|
|
pNTSprite->pal = 7;
|
|
pNTSprite->cstat |= CSTAT_SPRITE_XFLIP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pTSprite->ownerActor != pPlayer->GetActor() || gViewPos != viewFirstPerson) {
|
|
if (getflorzofslopeptr(pTSprite->sectp, pTSprite->pos) >= cPos.Z)
|
|
{
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectShadow);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
break;
|
|
}
|
|
case kStatTraps: {
|
|
if (pTSprite->type == kTrapSawCircular) {
|
|
if (owneractor->xspr.state) {
|
|
if (owneractor->xspr.data1) {
|
|
pTSprite->setspritetexture(aTexIds[kTexCIRCLESAW1]);
|
|
if (owneractor->xspr.data2)
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectSpear);
|
|
}
|
|
}
|
|
else if (owneractor->xspr.data1) pTSprite->setspritetexture(aTexIds[kTexCIRCLESAW2]);
|
|
else pTSprite->setspritetexture(aTexIds[kTexCIRCLESAWOFF]);
|
|
|
|
}
|
|
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) >= cPos.Z)
|
|
viewAddEffect(tsprites, nTSprite, kViewEffectShadow);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int nTSprite = int(tsprites.Size() - 1); nTSprite >= nViewSprites; nTSprite--)
|
|
{
|
|
tspritetype* pTSprite = tsprites.get(nTSprite);
|
|
int nAnim = 0;
|
|
switch (GetExtInfo(pTSprite->spritetexture()).picanm.extra & 7)
|
|
{
|
|
case 1:
|
|
{
|
|
nAnim = GetOctant(DVector2(cPos.XY() - pTSprite->pos).Rotated(DAngle22_5 - pTSprite->Angles.Yaw));
|
|
if (nAnim <= 4)
|
|
{
|
|
pTSprite->cstat &= ~CSTAT_SPRITE_XFLIP;
|
|
}
|
|
else
|
|
{
|
|
nAnim = 8 - nAnim;
|
|
pTSprite->cstat |= CSTAT_SPRITE_XFLIP;
|
|
}
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
nAnim = GetOctant(DVector2(cPos.XY() - pTSprite->pos).Rotated(DAngle22_5 - pTSprite->Angles.Yaw));
|
|
break;
|
|
}
|
|
}
|
|
while (nAnim > 0)
|
|
{
|
|
pTSprite->setspritetexture(pTSprite->spritetexture() + GetExtInfo(pTSprite->spritetexture()).picanm.num + 1);
|
|
nAnim--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void GameInterface::processSprites(tspriteArray& tsprites, const DVector3& view, DAngle viewang, double interpfrac)
|
|
{
|
|
viewProcessSprites(tsprites, view, viewang, interpfrac);
|
|
}
|
|
|
|
void GameInterface::EnterPortal(DCoreActor* viewer, int type)
|
|
{
|
|
if (type == PORTAL_WALL_MIRROR)
|
|
{
|
|
display_mirror++;
|
|
if (viewer) viewer->spr.cstat &= ~CSTAT_SPRITE_INVISIBLE;
|
|
}
|
|
}
|
|
|
|
void GameInterface::LeavePortal(DCoreActor* viewer, int type)
|
|
{
|
|
if (type == PORTAL_WALL_MIRROR)
|
|
{
|
|
display_mirror--;
|
|
if (viewer && display_mirror == 0 && !(viewer->spr.cstat & CSTAT_SPRITE_TRANSLUCENT)) viewer->spr.cstat |= CSTAT_SPRITE_INVISIBLE;
|
|
}
|
|
}
|
|
|
|
END_BLD_NS
|