raze/source/games/sw/src/draw.cpp
2022-12-11 18:42:00 +01:00

1483 lines
43 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 1997, 2005 - 3D Realms Entertainment
This file is part of Shadow Warrior version 1.2
Shadow Warrior is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Original Source: 1997 - Frank Maddin and Jim Norwood
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------
#include "ns.h"
#define QUIET
#include "build.h"
#include "automap.h"
#include "names2.h"
#include "panel.h"
#include "game.h"
#include "jsector.h"
#include "gamecontrol.h"
#include "gamefuncs.h"
#include "network.h"
#include "pal.h"
#include "player.h"
#include "jtags.h"
#include "parent.h"
#include "misc.h"
#include "menus.h"
#include "interpolate.h"
#include "interpso.h"
#include "sector.h"
#include "razemenu.h"
#include "v_2ddrawer.h"
#include "v_video.h"
#include "v_draw.h"
#include "render.h"
#include "razefont.h"
#include "models/modeldata.h"
extern DCoreActor* wall_to_sprite_actors[8];
BEGIN_SW_NS
int display_mirror;
static int OverlapDraw = false;
extern bool QuitFlag, SpriteInfo;
extern bool Voxel;
extern int f_c;
extern TILE_INFO_TYPE aVoxelArray[MAXTILES];
void PreDrawStackedWater(void);
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
#if 1
void ShadeSprite(tspritetype* tsp)
{
// set shade of sprite
tsp->shade = tsp->sectp->floorshade - 25;
if (tsp->shade > -3)
tsp->shade = -3;
if (tsp->shade < -30)
tsp->shade = -30;
}
#else
#endif
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
int GetRotation(tspriteArray& tsprites, int tSpriteNum, const DVector2& view)
{
static const uint8_t RotTable8[] = {0, 7, 6, 5, 4, 3, 2, 1};
static const uint8_t RotTable5[] = {0, 1, 2, 3, 4, 3, 2, 1};
int rotation;
tspritetype* tsp = tsprites.get(tSpriteNum);
auto ownerActor = static_cast<DSWActor*>(tsp->ownerActor);
if (!ownerActor->hasU() || ownerActor->user.RotNum == 0)
return 0;
// Get which of the 8 angles of the sprite to draw (0-7)
// rotation ranges from 0-7
DAngle angle2 = (tsp->pos - view).Angle();
rotation = (tsp->Angles.Yaw + DAngle180 + DAngle22_5 * 0.5 - angle2).Buildang() & 2047;
rotation = (rotation >> 8) & 7;
if (ownerActor->user.RotNum == 5)
{
if ((ownerActor->user.Flags & SPR_XFLIP_TOGGLE))
{
if (rotation <= 4)
{
// leave rotation alone
tsp->cstat &= ~(CSTAT_SPRITE_XFLIP);
}
else
{
rotation = (8 - rotation);
tsp->cstat |= (CSTAT_SPRITE_XFLIP); // clear x-flipping bit
}
}
else
{
if (rotation > 3 || rotation == 0)
{
// leave rotation alone
tsp->cstat &= ~(CSTAT_SPRITE_XFLIP); // clear x-flipping bit
}
else
{
rotation = (8 - rotation);
tsp->cstat |= (CSTAT_SPRITE_XFLIP); // set
}
}
// Special case bunk
int ID = ownerActor->user.ID;
if (ID == TOILETGIRL_R0 || ID == WASHGIRL_R0 || ID == TRASHCAN ||
ID == CARGIRL_R0 || ID == MECHANICGIRL_R0 || ID == PRUNEGIRL_R0 ||
ID == SAILORGIRL_R0)
tsp->cstat &= ~(CSTAT_SPRITE_XFLIP); // clear x-flipping bit
return RotTable5[rotation];
}
return RotTable8[rotation];
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
/*
!AIC - At draw time this is called for actor rotation. GetRotation() is more
complex than needs to be in part because importing of actor rotations and x-flip
directions was not standardized.
*/
int SetActorRotation(tspriteArray& tsprites, int tSpriteNum, const DVector2& viewpos)
{
tspritetype* tsp = tsprites.get(tSpriteNum);
auto ownerActor = static_cast<DSWActor*>(tsp->ownerActor);
int StateOffset, Rotation;
if (!ownerActor->hasU()) return 0;
// don't modify ANY tu vars - back them up!
STATE* State = ownerActor->user.State;
STATE* StateStart = ownerActor->user.StateStart;
if (ownerActor->user.RotNum == 0)
return 0;
// Get the offset into the State animation
StateOffset = int(State - StateStart);
// Get the rotation angle
Rotation = GetRotation(tsprites, tSpriteNum, viewpos);
ASSERT(Rotation < 5);
// Reset the State animation start based on the Rotation
StateStart = ownerActor->user.Rot[Rotation];
// Set the sprites state
State = StateStart + StateOffset;
// set the picnum here - may be redundant, but we just changed states and
// thats a big deal
tsp->picnum = State->Pic;
return 0;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
double DoShadowFindGroundPoint(tspritetype* tspr)
{
// USES TSPRITE !!!!!
auto ownerActor = static_cast<DSWActor*>(tspr->ownerActor);
Collision ceilhit, florhit;
double hiz, loz = ownerActor->user.loz;
ESpriteFlags save_cstat, bak_cstat;
// recursive routine to find the ground - either sector or floor sprite
// skips over enemy and other types of sprites
// IMPORTANT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This will return invalid FAF ceiling and floor heights inside of analyzesprite
// because the ceiling and floors get moved out of the way for drawing.
save_cstat = tspr->cstat;
tspr->cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);
FAFgetzrangepoint(tspr->pos, tspr->sectp, &hiz, &ceilhit, &loz, &florhit);
tspr->cstat = save_cstat;
switch (florhit.type)
{
case kHitSprite:
{
auto hitactor = florhit.actor();
if ((hitactor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_FLOOR))
{
// found a sprite floor
return loz;
}
else
{
// reset the blocking bit of what you hit and try again -
// recursive
bak_cstat = hitactor->spr.cstat;
hitactor->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);
loz = DoShadowFindGroundPoint(tspr);
hitactor->spr.cstat = bak_cstat;
}
break;
}
case kHitSector:
break;
default:
ASSERT(true == false);
break;
}
return loz;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void DoShadows(tspriteArray& tsprites, tspritetype* tsp, double viewz)
{
auto ownerActor = static_cast<DSWActor*>(tsp->ownerActor);
int ground_dist = 0;
int view_dist = 0;
double loz;
DVector2 scale;
if (!ownerActor->hasU()) return;
auto sect = tsp->sectp;
// make sure its the correct sector
// DoShadowFindGroundPoint calls FAFgetzrangepoint and this is sensitive
updatesector(tsp->pos, &sect);
if (sect == nullptr)
{
return;
}
tsp->sectp = sect;
if (tsp->scale.Y > 0.25)
{
double sizey = tileHeight(tsp->picnum) * tsp->scale.Y;
scale.Y = (tsp->scale.Y * 0.25) - (sizey / 2048.);
scale.X = tsp->scale.X;
}
else
{
scale = tsp->scale;
}
loz = ownerActor->user.loz;
if (ownerActor->user.lowActor)
{
if (!(ownerActor->user.lowActor->spr.cstat & (CSTAT_SPRITE_ALIGNMENT_WALL | CSTAT_SPRITE_ALIGNMENT_FLOOR)))
{
loz = DoShadowFindGroundPoint(tsp);
}
}
// need to find the ground here
// if below or close to sprites z don't bother to draw it
if ((viewz - loz) > -8)
{
return;
}
tspritetype* tSpr = tsprites.newTSprite();
*tSpr = *tsp;
// shadow is ALWAYS draw last - status is priority
tSpr->statnum = MAXSTATUS;
tSpr->shade = 127;
tSpr->cstat |= CSTAT_SPRITE_TRANSLUCENT;
tSpr->pos.Z = loz;
// if close to shadows z shrink it
view_dist = int(abs(loz - viewz));
if (view_dist < 32)
view_dist = 256/view_dist;
else
view_dist = 0;
// make shadow smaller depending on height from ground
ground_dist = int(abs(loz - GetSpriteZOfBottom(tsp)) * (1./16));
double scaleofs = (ground_dist - view_dist) * REPEAT_SCALE;
scale.X = clamp(scale.X + scaleofs, 0.0625, 4.);
scale.Y = clamp(scale.Y + scaleofs, 0.0625, 4.);
tSpr->scale = scale;
if (tilehasmodelorvoxel(tsp->picnum,tsp->pal))
{
tSpr->scale.Y = (0);
// cstat: trans reverse
// clipdist: tell mdsprite.cpp to use Z-buffer hacks to hide overdraw issues
tSpr->clipdist |= TSPR_FLAGS_MDHACK;
tSpr->cstat |= CSTAT_SPRITE_TRANS_FLIP;
}
else
{
// Alter the shadow's position so that it appears behind the sprite itself.
auto look = (tSpr->pos.XY() - Player[screenpeek].si.XY()).Angle();
tSpr->pos.XY() += look.ToVector() * 2;
}
// Check for voxel items and use a round generic pic if so
//DoVoxelShadow(New);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void DoMotionBlur(tspriteArray& tsprites, tspritetype const * const tsp)
{
auto ownerActor = static_cast<DSWActor*>(tsp->ownerActor);
DVector3 npos(0, 0, 0), dpos(0, 0, 0);
int i;
double repeat_adj = 0;
DVector2 scale;
double z_amt_per_pixel;
auto angle = tsp->Angles.Yaw + DAngle180;
if (!ownerActor->hasU() || ownerActor->vel.X == 0)
{
return;
}
if ((tsp->extra & SPRX_PLAYER_OR_ENEMY))
{
z_amt_per_pixel = (- ownerActor->user.jump_speed * ACTORMOVETICS) * maptoworld / tsp->ownerActor->vel.X;
}
else
{
z_amt_per_pixel = -ownerActor->vel.Z / tsp->ownerActor->vel.X;
}
dpos.XY() = npos.XY() = angle.ToVector() * ownerActor->user.motion_blur_dist;
dpos.Z = npos.Z = z_amt_per_pixel * ownerActor->user.motion_blur_dist * (1./16);
scale = tsp->scale;
switch ((ownerActor->user.Flags2 & SPR2_BLUR_TAPER))
{
case 0:
repeat_adj = 0;
break;
case SPR2_BLUR_TAPER_SLOW:
repeat_adj = scale.X / (ownerActor->user.motion_blur_num*2);
break;
case SPR2_BLUR_TAPER_FAST:
repeat_adj = scale.X / ownerActor->user.motion_blur_num;
break;
}
for (i = 0; i < ownerActor->user.motion_blur_num; i++)
{
tspritetype* tSpr = tsprites.newTSprite();
*tSpr = *tsp;
tSpr->cstat |= CSTAT_SPRITE_TRANSLUCENT|CSTAT_SPRITE_TRANS_FLIP;
tSpr->pos += dpos;
dpos += npos;
tSpr->scale = scale;
scale.X -= repeat_adj;
scale.Y -= repeat_adj;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void WarpCopySprite(tspriteArray& tsprites)
{
int spnum;
int match;
// look for the first one
SWStatIterator it(STAT_WARP_COPY_SPRITE1);
while (auto itActor = it.Next())
{
match = itActor->spr.lotag;
// look for the second one
SWStatIterator it1(STAT_WARP_COPY_SPRITE2);
while (auto itActor1 = it.Next())
{
if (itActor1->spr.lotag == match)
{
auto sect1 = itActor->sector();
auto sect2 = itActor1->sector();
SWSectIterator it2(sect1);
while (auto itActor2 = it.Next())
{
if (itActor2 == itActor)
continue;
if (itActor2->spr.picnum == ST1)
continue;
tspritetype* newTSpr = renderAddTsprite(tsprites, itActor2);
newTSpr->statnum = 0;
newTSpr->pos += itActor1->spr.pos - itActor->spr.pos;
newTSpr->sectp = itActor1->sector();
}
it2.Reset(sect2);
while (auto itActor2 = it2.Next())
{
if (itActor2 == itActor1)
continue;
if (itActor2->spr.picnum == ST1)
continue;
tspritetype* newTSpr = renderAddTsprite(tsprites, itActor2);
newTSpr->statnum = 0;
newTSpr->pos += itActor->spr.pos - itActor1->spr.pos;
newTSpr->sectp = itActor->sector();
}
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void DoStarView(tspritetype* tsp, DSWActor* tActor, double viewz)
{
extern STATE s_Star[], s_StarDown[];
extern STATE s_StarStuck[], s_StarDownStuck[];
double zdiff = viewz - tsp->pos.Z;
if (abs(zdiff) > 24)
{
if (tActor->user.StateStart == s_StarStuck)
tsp->picnum = s_StarDownStuck[tActor->user.State - s_StarStuck].Pic;
else
tsp->picnum = s_StarDown[tActor->user.State - s_Star].Pic;
if (zdiff > 0)
tsp->cstat |= (CSTAT_SPRITE_YFLIP);
}
else
{
if (zdiff > 0)
tsp->cstat |= (CSTAT_SPRITE_YFLIP);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DSWActor* CopySprite(spritetypebase const* tsp, sectortype* newsector)
{
auto actorNew = insertActor(newsector, STAT_FAF_COPY);
actorNew->spr.pos = tsp->pos;
actorNew->spr.cstat = tsp->cstat;
actorNew->spr.picnum = tsp->picnum;
actorNew->spr.pal = tsp->pal;
actorNew->spr.scale = tsp->scale;
actorNew->spr.xoffset = tsp->xoffset;
actorNew->spr.yoffset = tsp->yoffset;
actorNew->spr.Angles.Yaw = tsp->Angles.Yaw;
actorNew->spr.xint = tsp->xint;
actorNew->spr.yint = tsp->yint;
actorNew->spr.inittype = tsp->inittype;
actorNew->spr.shade = tsp->shade;
// this later also needs to copy the real velocity, because the sprite renderer accesses it. :(
actorNew->spr.cstat &= ~(CSTAT_SPRITE_BLOCK | CSTAT_SPRITE_BLOCK_HITSCAN);
return actorNew;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
DSWActor* ConnectCopySprite(spritetypebase const* tsp)
{
sectortype* newsector;
double testz;
if (FAF_ConnectCeiling(tsp->sectp))
{
newsector = tsp->sectp;
testz = GetSpriteZOfTop(tsp) - 10;
if (testz < tsp->sectp->ceilingz)
updatesectorz(DVector3(tsp->pos, testz), &newsector);
if (newsector != nullptr && newsector != tsp->sectp)
{
return CopySprite(tsp, newsector);
}
}
if (FAF_ConnectFloor(tsp->sectp))
{
newsector = tsp->sectp;
testz = GetSpriteZOfBottom(tsp) + 10;
if (testz > tsp->sectp->floorz)
updatesectorz(DVector3(tsp->pos, testz), &newsector);
if (newsector != nullptr && newsector != tsp->sectp)
{
return CopySprite(tsp, newsector);
}
}
return nullptr;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void analyzesprites(tspriteArray& tsprites, const DVector3& viewpos, double interpfrac)
{
int tSpriteNum;
static int ang = 0;
PLAYER* pp = Player + screenpeek;
int newshade=0;
const int DART_PIC = 2526;
const double DART_REPEAT = 0.25;
ang = NORM_ANGLE(ang + 12);
double smr4 = interpfrac + MoveSkip4;
double smr2 = interpfrac + MoveSkip2;
for (tSpriteNum = (int)tsprites.Size() - 1; tSpriteNum >= 0; tSpriteNum--)
{
tspritetype* tsp = tsprites.get(tSpriteNum);
auto tActor = static_cast<DSWActor*>(tsp->ownerActor);
auto tsectp = tsp->sectp;
#if 0
// Brighten up the sprite if set somewhere else to do so
if (tu && tActor->user.Vis > 0)
{
int tmpshade; // Having this prevent overflow
tmpshade = tsp->shade - tActor->user.Vis;
if (tmpshade < -128) tmpshade = -128;
tsp->shade = tmpshade;
tActor->user.Vis -= 8;
}
#endif
// don't draw these
if (tsp->statnum >= STAT_DONT_DRAW)
{
tsp->ownerActor = nullptr;
continue;
}
if (tActor->hasU())
{
if (tsp->statnum != STAT_DEFAULT)
{
if ((tActor->user.Flags & SPR_SKIP4))
{
if (tsp->statnum <= STAT_SKIP4_INTERP_END)
{
tsp->pos = tActor->interpolatedpos(smr4 * 0.25);
}
}
if ((tActor->user.Flags & SPR_SKIP2))
{
if (tsp->statnum <= STAT_SKIP2_INTERP_END)
{
tsp->pos = tActor->interpolatedpos(smr2 * 0.5);
}
}
}
// workaround for mines and floor decals beneath the floor
if (tsp->picnum == BETTY_R0 || tsp->picnum == FLOORBLOOD1)
{
double const florz = getflorzofslopeptr(tActor->sector(), tActor->spr.pos);
if (tActor->spr.pos.Z > florz)
tsp->pos.Z = florz;
}
if (r_shadows && (tActor->user.Flags & SPR_SHADOW))
{
DoShadows(tsprites, tsp, viewpos.Z);
}
if (sw_darts)
if (tActor->user.ID == 1793 || tsp->picnum == 1793)
{
tsp->picnum = 2519;
tsp->scale = DVector2(0.421875, 0.453125);
}
if (tActor->user.ID == STAR1)
{
if (sw_darts)
{
tsp->picnum = DART_PIC;
tsp->Angles.Yaw -= DAngle90 + mapangle(24);
tsp->scale = DVector2(DART_REPEAT, DART_REPEAT);
tsp->cstat |= (CSTAT_SPRITE_ALIGNMENT_WALL);
}
else
DoStarView(tsp, tActor, viewpos.Z);
}
// rotation
if (tActor->user.RotNum > 0)
SetActorRotation(tsprites, tSpriteNum, viewpos.XY());
if (tActor->user.motion_blur_num)
{
DoMotionBlur(tsprites, tsp);
}
// set palette lookup correctly
if (tsp->pal != tsectp->floorpal)
{
if (tsectp->floorpal == PALETTE_DEFAULT)
{
// default pal for sprite is stored in tActor->user.spal
// mostly for players and other monster types
tsp->pal = tActor->user.spal;
}
else
{
// if sector pal is something other than default
uint8_t pal = tsectp->floorpal;
bool nosectpal=false;
// sprite does not take on the new pal if sector flag is set
if (tsectp->hasU() && (tsectp->flags & SECTFU_DONT_COPY_PALETTE))
{
pal = PALETTE_DEFAULT;
nosectpal = true;
}
//if(tActor->user.spal == PALETTE_DEFAULT)
if (tsp->hitag != SECTFU_DONT_COPY_PALETTE && tsp->hitag != LUMINOUS
&& !nosectpal
&& pal != PALETTE_FOG && pal != PALETTE_DIVE &&
pal != PALETTE_DIVE_LAVA)
tsp->pal = pal;
else
tsp->pal = tActor->user.spal;
}
}
// Sprite debug information mode
if (tsp->hitag == 9997)
{
tsp->pal = PALETTE_RED_LIGHTING;
// Turn it off, it gets reset by PrintSpriteInfo
tActor->spr.hitag = 0;
}
}
if (sw_darts)
if (tsp->statnum == STAT_STAR_QUEUE)
{
tsp->picnum = DART_PIC;
tsp->Angles.Yaw -= DAngle90;
tsp->scale = DVector2(DART_REPEAT, DART_REPEAT);
tsp->cstat |= (CSTAT_SPRITE_ALIGNMENT_WALL);
}
// Call my sprite handler
// Does autosizing and voxel handling
JAnalyzeSprites(tsp);
// only do this of you are a player sprite
//if (tsp->statnum >= STAT_PLAYER0 && tsp->statnum < STAT_PLAYER0 + MAX_SW_PLAYERS)
if (tActor->hasU() && tActor->user.PlayerP)
{
// Shadow spell
if (!(tsp->cstat & CSTAT_SPRITE_TRANSLUCENT))
ShadeSprite(tsp);
// sw if its your playersprite
if (Player[screenpeek].actor == tActor)
{
pp = Player + screenpeek;
if (display_mirror || (pp->Flags & (PF_VIEW_FROM_OUTSIDE|PF_VIEW_FROM_CAMERA)))
{
if (pp->Flags & (PF_VIEW_FROM_OUTSIDE))
tsp->cstat |= (CSTAT_SPRITE_TRANSLUCENT);
DVector3 pos;
if (pp->Flags & (PF_CLIMBING))
{
// move sprite forward some so he looks like he's
// climbing
pos.XY() = pp->si.XY() + tsp->Angles.Yaw.ToVector() * 13;
}
else
{
pos.X = pp->si.X;
pos.Y = pp->si.Y;
}
pos.Z = tsp->pos.Z + pp->si.Z + pp->getViewHeightDiff();
tsp->pos = pos;
tsp->Angles.Yaw = pp->siang;
//continue;
}
else
{
// dont draw your sprite
tsp->ownerActor = nullptr;
//tsp->cstat |= (CSTAT_SPRITE_INVISIBLE);
}
}
else // Otherwise just interpolate the player sprite
{
pp = tActor->user.PlayerP;
tsp->pos = pp->actor->getRenderPos(interpfrac);
tsp->Angles.Yaw = pp->actor->interpolatedyaw(interpfrac);
}
}
if (OverlapDraw && FAF_ConnectArea(tsp->sectp) && tsp->ownerActor)
{
ConnectCopySprite(tsp);
}
//
// kens original sprite shade code he moved out of the engine
//
switch (tsp->statnum)
{
case STAT_ENEMY:
case STAT_DEAD_ACTOR:
case STAT_FAF_COPY:
break;
default:
newshade = tsp->shade;
newshade += 6;
if (newshade > 127) newshade = 127;
tsp->shade = int8_t(newshade);
}
if ((tsectp->ceilingstat & CSTAT_SECTOR_SKY))
{
newshade = tsp->shade;
newshade += tsectp->ceilingshade;
if (newshade > 127) newshade = 127;
if (newshade < -128) newshade = -128;
tsp->shade = int8_t(newshade);
}
else
{
newshade = tsp->shade;
newshade += tsectp->floorshade;
if (newshade > 127) newshade = 127;
if (newshade < -128) newshade = -128;
tsp->shade = int8_t(newshade);
}
if (tsp->hitag == 9998)
tsp->shade = 127; // Invisible enemy ninjas
// Correct shades for luminous sprites
if (tsp->hitag == LUMINOUS)
{
tsp->shade = -128;
}
if (pp->NightVision && (tsp->extra & SPRX_PLAYER_OR_ENEMY))
{
if (tActor->hasU() && tActor->user.ID == TRASHCAN) continue; // Don't light up trashcan
tsp->pal = PALETTE_ILLUMINATE; // Make sprites REALLY bright green.
tsp->shade = -128;
}
if (tActor->hasU() && tActor->user.PlayerP)
{
if ((tActor->user.Flags2 & SPR2_VIS_SHADING))
{
if (Player[screenpeek].actor != tActor)
{
if (!(tActor->user.PlayerP->Flags & PF_VIEW_FROM_OUTSIDE))
{
tsp->cstat &= ~(CSTAT_SPRITE_TRANSLUCENT);
}
}
tsp->shade = 12 - StdRandomRange(30);
}
}
}
WarpCopySprite(tsprites);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
tspritetype* get_tsprite(tspriteArray& tsprites, DSWActor* actor)
{
int tSpriteNum;
for (tSpriteNum = (int)tsprites.Size() - 1; tSpriteNum >= 0; tSpriteNum--)
{
if (tsprites.get(tSpriteNum)->ownerActor == actor)
return tsprites.get(tSpriteNum);
}
return nullptr;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void post_analyzesprites(tspriteArray& tsprites)
{
int tSpriteNum;
for (tSpriteNum = (int)tsprites.Size() - 1; tSpriteNum >= 0; tSpriteNum--)
{
auto actor = static_cast<DSWActor*>(tsprites.get(tSpriteNum)->ownerActor);
if (!actor) continue; // JBF: verify this is safe
tspritetype* tsp = tsprites.get(tSpriteNum);
if (actor->hasU())
{
if (actor->user.ID == FIREBALL_FLAMES && actor->user.attachActor != nullptr)
{
tspritetype* const atsp = get_tsprite(tsprites, actor->user.attachActor);
if (!atsp)
{
continue;
}
tsp->pos.X = atsp->pos.X;
tsp->pos.Y = atsp->pos.Y;
// statnum is priority - draw this ALWAYS first at 0
// statnum is priority - draw this ALWAYS last at MAXSTATUS
if ((atsp->extra & SPRX_BURNABLE))
{
atsp->statnum = 1;
tsp->statnum = 0;
}
else
tsp->statnum = MAXSTATUS;
}
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
std::pair<DVector3, DAngle> GameInterface::GetCoordinates()
{
auto ppActor = Player[myconnectindex].actor;
if (!ppActor) return std::make_pair(DVector3(DBL_MAX, 0, 0), nullAngle);
return std::make_pair(ppActor->spr.pos, ppActor->spr.Angles.Yaw);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void PrintSpriteInfo(PLAYER* pp)
{
const int Y_STEP = 7;
//if (SpriteInfo && !LocationInfo)
{
auto actor = DoPickTarget(pp->actor, DAngle22_5/4, 2);
actor->spr.hitag = 9997; // Special tag to make the actor glow red for one frame
if (actor == nullptr)
{
Printf("SPRITENUM: NONE TARGETED\n");
return;
}
else
Printf("SPRITENUM:%d\n", actor->GetIndex());
if (actor->hasU())
{
Printf("ID:%d, ", actor->user.ID);
Printf("PALETTE:%d, ", actor->user.spal);
Printf("HEALTH:%d, ", actor->user.Health);
Printf("WAITTICS:%d, ", actor->user.WaitTics);
Printf("COUNTER:%d, ", actor->user.Counter);
Printf("COUNTER2:%d\n", actor->user.Counter);
}
{
Printf("POSX:%2.3f, ", actor->spr.pos.X);
Printf("POSY:%2.3f, ", actor->spr.pos.Y);
Printf("POSZ:%2.3f,", actor->spr.pos.Z);
Printf("ANG:%2.0f\n", actor->spr.Angles.Yaw.Degrees());
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
static void DrawCrosshair(PLAYER* pp, const double interpfrac)
{
auto offsets = pp->Angles.getCrosshairOffsets(interpfrac);
::DrawCrosshair(2326, pp->actor->user.Health, offsets.first.X, offsets.first.Y + ((pp->Flags & PF_VIEW_FROM_OUTSIDE) ? 5 : 0), 2, offsets.second, shadeToLight(10));
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void PreDraw(void)
{
int i;
PreDrawStackedWater();
SWStatIterator it(STAT_FLOOR_SLOPE_DONT_DRAW);
while (auto actor = it.Next())
{
actor->sector()->floorstat &= ~(CSTAT_SECTOR_SLOPE);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void PostDraw(void)
{
int i;
SWStatIterator it(STAT_FLOOR_SLOPE_DONT_DRAW);
while (auto actor = it.Next())
{
actor->sector()->floorstat |= (CSTAT_SECTOR_SLOPE);
}
it.Reset(STAT_FAF_COPY);
while (auto actor = it.Next())
{
actor->clearUser();
actor->Destroy();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void PreDrawStackedWater(void)
{
SWStatIterator it(STAT_CEILING_FLOOR_PIC_OVERRIDE);
while (auto itActor = it.Next())
{
SWSectIterator it2(itActor->sector());
while (auto itActor2 = it2.Next())
{
if (itActor2->hasU())
{
if (itActor2->spr.statnum == STAT_ITEM)
continue;
if (itActor2->spr.statnum <= STAT_DEFAULT || itActor2->spr.statnum > STAT_PLAYER0 + MAX_SW_PLAYERS)
continue;
// code so that a copied sprite will not make another copy
if (itActor2->spr.intangle == 0x4711)
continue;
auto actorNew = ConnectCopySprite(&itActor2->spr);
if (actorNew != nullptr)
{
// spawn a user
actorNew->allocUser();
actorNew->spr.intangle = 0x4711;
// copy everything reasonable from the user that
// analyzesprites() needs to draw the image
actorNew->user.State = itActor2->user.State;
actorNew->user.Rot = itActor2->user.Rot;
actorNew->user.StateStart = itActor2->user.StateStart;
actorNew->user.StateEnd = itActor2->user.StateEnd;
actorNew->user.Flags = itActor2->user.Flags;
actorNew->user.Flags2 = itActor2->user.Flags2;
actorNew->user.RotNum = itActor2->user.RotNum;
actorNew->user.ID = itActor2->user.ID;
actorNew->user.PlayerP = itActor2->user.PlayerP;
actorNew->user.spal = itActor2->user.spal;
}
}
}
}
}
void DoPlayerDiveMeter(PLAYER* pp);
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void UpdateWallPortalState()
{
// This is too obtuse to be maintained statically, but with 8 mirrors at most easy to be kept up to date.
for (int i = 0; i < mirrorcnt; i++)
{
if (mirror[i].mirrorWall == nullptr) {
continue;
}
walltype* wal = mirror[i].mirrorWall;
if (wal->picnum != MIRRORLABEL + i)
{
wal->portalflags = 0;
continue;
}
wal->portalflags = 0;
wal->portalnum = 0;
if (!mirror[i].ismagic)
{
// a simple mirror
wal->portalflags = PORTAL_WALL_MIRROR;
}
else
{
DSWActor* cam = mirror[i].cameraActor;
if (cam)
{
if (!TEST_BOOL1(cam))
{
wal->portalflags = PORTAL_WALL_TO_SPRITE;
wal->portalnum = i;
wall_to_sprite_actors[i] = cam;
}
}
}
}
SWStatIterator it(STAT_CEILING_FLOOR_PIC_OVERRIDE);
while (auto actor = it.Next())
{
if (SP_TAG3(actor) == 0)
{
// back up ceilingpicnum and ceilingstat
SP_TAG5(actor) = actor->sector()->ceilingpicnum;
actor->sector()->ceilingpicnum = SP_TAG2(actor);
SP_TAG4(actor) = actor->sector()->ceilingstat;
actor->sector()->ceilingstat |= (ESectorFlags::FromInt(SP_TAG6(actor)));
actor->sector()->ceilingstat &= ~(CSTAT_SECTOR_SKY);
}
else if (SP_TAG3(actor) == 1)
{
SP_TAG5(actor) = actor->sector()->floorpicnum;
actor->sector()->floorpicnum = SP_TAG2(actor);
SP_TAG4(actor) = actor->sector()->floorstat;
actor->sector()->floorstat |= (ESectorFlags::FromInt(SP_TAG6(actor)));
actor->sector()->floorstat &= ~(CSTAT_SECTOR_SKY);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void RestorePortalState()
{
SWStatIterator it(STAT_CEILING_FLOOR_PIC_OVERRIDE);
while (auto actor = it.Next())
{
if (SP_TAG3(actor) == 0)
{
// restore ceilingpicnum and ceilingstat
actor->sector()->ceilingpicnum = SP_TAG5(actor);
actor->sector()->ceilingstat = ESectorFlags::FromInt(SP_TAG4(actor));
actor->sector()->ceilingstat &= ~(CSTAT_SECTOR_SKY);
}
else if (SP_TAG3(actor) == 1)
{
actor->sector()->floorpicnum = SP_TAG5(actor);
actor->sector()->floorstat = ESectorFlags::FromInt(SP_TAG4(actor));
actor->sector()->floorstat &= ~(CSTAT_SECTOR_SKY);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void drawscreen(PLAYER* pp, double interpfrac, bool sceneonly)
{
// prediction player if prediction is on, else regular player
PLAYER* camerapp = (PredictionOn && CommEnabled && pp == Player+myconnectindex) ? ppp : pp;
PreDraw();
PreUpdatePanel(interpfrac);
if (!sceneonly)
{
// Stick at beginning of drawscreen
DoInterpolations(interpfrac);
if (cl_sointerpolation) so_dointerpolations(interpfrac);
}
// Get initial player position, interpolating if required.
DVector3 tpos = camerapp->actor->getRenderPos(interpfrac);
DRotator tangles = camerapp->Angles.getRenderAngles(interpfrac);
sectortype* tsect = camerapp->cursector;
updatesector(tpos, &tsect);
if (pp->sop_riding || pp->sop_control)
{
if (pp->sop_control && (!cl_sointerpolation || (CommEnabled && !pp->sop_remote)))
{
tpos = pp->actor->getPosWithOffsetZ();
tangles.Yaw = pp->actor->spr.Angles.Yaw;
}
tsect = pp->cursector;
updatesectorz(tpos, &tsect);
}
pp->si = tpos.plusZ(-pp->actor->getOffsetZ());
pp->siang = tangles.Yaw;
QuakeViewChange(camerapp, tpos, tangles.Yaw);
int vis = g_visibility;
VisViewChange(camerapp, &vis);
g_relvisibility = vis - g_visibility;
if (pp->sop_remote)
{
DSWActor* ractor = pp->remoteActor;
tangles.Yaw = TEST_BOOL1(ractor) ? ractor->spr.Angles.Yaw : (pp->sop_remote->pmid.XY() - tpos.XY()).Angle();
}
if (pp->Flags & (PF_VIEW_FROM_OUTSIDE))
{
tpos.Z -= 33;
if (!calcChaseCamPos(tpos, pp->actor, &tsect, tangles, interpfrac, 128.))
{
tpos.Z += 33;
calcChaseCamPos(tpos, pp->actor, &tsect, tangles, interpfrac, 128.);
}
}
if (!(pp->Flags & (PF_VIEW_FROM_CAMERA|PF_VIEW_FROM_OUTSIDE)))
{
if (cl_viewbob)
{
tpos.Z += interpolatedvalue(pp->obob_z + pp->opbob_amt, pp->bob_z + pp->pbob_amt, interpfrac);
}
// recoil only when not in camera
tangles.Pitch -= interpolatedvalue(pp->recoil_ohorizoff, pp->recoil_horizoff, interpfrac);
}
if (automapMode != am_full)
{
// Cameras must be done before the main loop.
JS_CameraParms(pp, tpos);
}
if (!sceneonly)
UpdatePanel(interpfrac);
UpdateWallPortalState();
render_drawrooms(pp->actor, tpos, tsect, tangles, interpfrac);
RestorePortalState();
if (sceneonly)
{
PostDraw();
return;
}
MarkSectorSeen(pp->cursector);
if ((automapMode != am_off) && pp == Player+myconnectindex)
{
SWSpriteIterator it;
while (auto actor = it.Next())
{
// Don't show sprites tagged with 257
if (actor->spr.lotag == 257)
{
if (actor->spr.cstat & (CSTAT_SPRITE_ALIGNMENT_FLOOR))
{
actor->spr.cstat &= ~(CSTAT_SPRITE_ALIGNMENT_FLOOR);
actor->spr.intowner = -2;
}
}
}
DrawOverheadMap(tpos.XY(), tangles.Yaw, interpfrac);
}
SWSpriteIterator it;
while (auto actor = it.Next())
{
// Don't show sprites tagged with 257
if (actor->spr.lotag == 257 && actor->spr.intowner == -2)
{
actor->spr.cstat |= (CSTAT_SPRITE_ALIGNMENT_FLOOR);
actor->spr.intowner = -1;
}
}
UpdateStatusBar();
DrawCrosshair(pp, interpfrac);
// Do the underwater breathing bar
DoPlayerDiveMeter(pp);
// Boss Health Meter, if Boss present
BossHealthMeter();
// Stick at end of drawscreen
RestoreInterpolations();
if (cl_sointerpolation) so_restoreinterpolations();
if (paused && !M_Active())
{
auto str = GStrings("Game Paused");
auto font = PickSmallFont(str);
int w = font->StringWidth(str);
DrawText(twod, font, CR_UNTRANSLATED, 160-w, 100, str, DTA_FullscreenScale, FSMode_Fit320x200, TAG_DONE);
}
if (!CommEnabled && (pp->Flags & PF_DEAD) && ReloadPrompt)
ReloadPrompt = false;
PostDraw();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool GameInterface::GenerateSavePic()
{
drawscreen(Player + myconnectindex, 65536, true);
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
bool GameInterface::DrawAutomapPlayer(const DVector2& mxy, const DVector2& cpos, const DAngle cang, const DVector2& xydim, const double czoom, double const interpfrac)
{
static int pspr_ndx[8] = { 0,0,0,0,0,0,0,0 };
bool sprisplayer = false;
// Pre-caculate incoming angle vector.
auto cangvect = cang.ToVector();
// Draw sprites
if (gFullMap)
{
for (unsigned i = 0; i < sector.Size(); i++)
{
SWSectIterator it(i);
while (auto actor = it.Next())
{
if (actor->spr.cstat2 & CSTAT2_SPRITE_MAPPED)
{
// 1=white / 31=black / 44=green / 56=pink / 128=yellow / 210=blue / 248=orange / 255=purple
PalEntry col = (actor->spr.cstat & CSTAT_SPRITE_BLOCK) > 0 ? GPalette.BaseColors[248] : actor == Player[screenpeek].actor ? GPalette.BaseColors[31] : GPalette.BaseColors[56];
auto statnum = actor->spr.statnum;
auto sprxy = ((statnum >= 1) && (statnum <= 8) && (statnum != 2) ? actor->interpolatedpos(interpfrac) : actor->spr.pos).XY() - cpos;
switch (actor->spr.cstat & CSTAT_SPRITE_ALIGNMENT_MASK)
{
case CSTAT_SPRITE_ALIGNMENT_FACING: // Regular sprite
DrawAutomapAlignmentFacing(actor->spr, sprxy, cangvect, czoom, xydim, col);
break;
case CSTAT_SPRITE_ALIGNMENT_WALL: // Rotated sprite
DrawAutomapAlignmentWall(actor->spr, sprxy, cangvect, czoom, xydim, col);
break;
case CSTAT_SPRITE_ALIGNMENT_FLOOR: // Floor sprite
if (automapMode == am_overlay) DrawAutomapAlignmentFloor(actor->spr, sprxy, cangvect, czoom, xydim, col);
break;
}
}
}
}
}
for (int p = connecthead; p >= 0; p = connectpoint2[p])
{
if (p == screenpeek)
{
auto actor = Player[p].actor;
if (actor->vel.X > 1) pspr_ndx[myconnectindex] = ((PlayClock >> 4) & 3);
sprisplayer = true;
if (czoom > 0.1875)
{
// Special case tiles
if (actor->spr.picnum == 3123) break;
int spnum = -1;
if (sprisplayer)
{
if (gNet.MultiGameType != MULTI_GAME_COMMBAT || actor == Player[screenpeek].actor)
spnum = 1196 + pspr_ndx[myconnectindex];
}
else spnum = actor->spr.picnum;
if (spnum >= 0)
{
const auto daang = -((!SyncInput() ? actor->spr.Angles.Yaw : actor->interpolatedyaw(interpfrac)) - cang).Normalized360().Degrees();
auto vect = OutAutomapVector(mxy - cpos, cangvect, czoom, xydim);
// This repeat scale is correct.
double sc = czoom * actor->spr.scale.Y * 2;
DrawTexture(twod, tileGetTexture(1196 + pspr_ndx[myconnectindex], true), vect.X, vect.Y, DTA_ScaleX, sc, DTA_ScaleY, sc, DTA_Rotate, daang,
DTA_CenterOffsetRel, 2, DTA_TranslationIndex, TRANSLATION(Translation_Remap, actor->spr.pal), DTA_Color, shadeToLight(actor->spr.shade),
DTA_Alpha, (actor->spr.cstat & CSTAT_SPRITE_TRANSLUCENT) ? 0.33 : 1., TAG_DONE);
}
}
}
}
return true;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void GameInterface::processSprites(tspriteArray& tsprites, const DVector3& view, DAngle viewang, double interpfrac)
{
analyzesprites(tsprites, view, interpfrac);
post_analyzesprites(tsprites);
}
END_SW_NS