qzdoom-gpl/src/g_shared/a_decals.cpp

770 lines
17 KiB
C++
Raw Normal View History

/*
** a_decals.cpp
** Implements the actor that represents decals in the level
**
**---------------------------------------------------------------------------
** Copyright 1998-2005 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "actor.h"
#include "a_sharedglobal.h"
#include "r_defs.h"
#include "p_local.h"
#include "v_video.h"
#include "p_trace.h"
#include "decallib.h"
#include "statnums.h"
#include "c_dispatch.h"
// Decals overload snext and sprev to keep a list of decals attached to a wall.
// They also overload floorclip to be the fractional distance from the
// left edge of the side. This distance is stored as a 2.30 fixed pt number.
IMPLEMENT_STATELESS_ACTOR (ADecal, Any, 9200, 0)
PROP_Flags (MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOGRAVITY)
END_DEFAULTS
void ADecal::Destroy ()
{
Remove ();
Super::Destroy ();
}
void ADecal::Remove ()
{
AActor **prev = sprev;
AActor *next = snext;
if (prev && (*prev = next))
next->sprev = prev;
sprev = NULL;
snext = NULL;
}
void ADecal::SerializeChain (FArchive &arc, ADecal **first)
{
DWORD numInChain;
AActor *fresh;
AActor **firstptr = (AActor **)first;
if (arc.IsLoading ())
{
numInChain = arc.ReadCount ();
while (numInChain--)
{
arc << fresh;
*firstptr = fresh;
fresh->sprev = firstptr;
firstptr = &fresh->snext;
}
}
else
{
numInChain = 0;
fresh = *firstptr;
while (fresh != NULL)
{
fresh = fresh->snext;
++numInChain;
}
arc.WriteCount (numInChain);
fresh = *firstptr;
while (numInChain--)
{
arc << fresh;
fresh = fresh->snext;
}
}
}
void ADecal::MoveChain (ADecal *first, fixed_t x, fixed_t y)
{
while (first != NULL)
{
first->x += x;
first->y += y;
first = (ADecal *)first->snext;
}
}
void ADecal::FixForSide (side_t *wall)
{
AActor *decal = wall->BoundActors;
line_t *line = &lines[wall->linenum];
int wallnum = int(wall - sides);
vertex_t *v1, *v2;
if (line->sidenum[0] == wallnum)
{
v1 = line->v1;
v2 = line->v2;
}
else
{
v1 = line->v2;
v2 = line->v1;
}
fixed_t dx = v2->x - v1->x;
fixed_t dy = v2->y - v1->y;
while (decal != NULL)
{
decal->x = v1->x + MulScale2 (decal->floorclip, dx);
decal->y = v1->y + MulScale2 (decal->floorclip, dy);
decal = decal->snext;
}
}
void ADecal::BeginPlay ()
{
Super::BeginPlay ();
// Decals do not think.
ChangeStatNum (STAT_DECAL);
// Find a wall to attach to, and set renderflags to keep
// the decal at its current height. If the decal cannot find a wall
// within 64 units, it destroys itself.
//
// Subclasses can set special1 if they don't want this sticky logic.
if (special1 == 0)
{
DoTrace ();
}
if (args[0] != 0)
{
const FDecal *decal = DecalLibrary.GetDecalByNum (args[0]);
if (decal != NULL)
{
decal->ApplyToActor (this);
}
}
}
void ADecal::DoTrace ()
{
FTraceResults trace;
Trace (x, y, z, Sector,
finecosine[(angle+ANGLE_180)>>ANGLETOFINESHIFT],
finesine[(angle+ANGLE_180)>>ANGLETOFINESHIFT], 0,
64*FRACUNIT, 0, 0, NULL, trace, TRACE_NoSky);
if (trace.HitType == TRACE_HitWall)
{
x = trace.X;
y = trace.Y;
StickToWall (sides + trace.Line->sidenum[trace.Side]);
}
else
{
Destroy ();
}
}
// Returns the texture the decal stuck to.
int ADecal::StickToWall (side_t *wall)
{
// Stick the decal at the end of the chain so it appears on top
AActor *next, **prev;
prev = (AActor **)&wall->BoundActors;
while (*prev != NULL)
{
next = *prev;
prev = &next->snext;
}
*prev = this;
snext = NULL;
sprev = prev;
/*
snext = wall->BoundActors;
sprev = &wall->BoundActors;
if (snext)
snext->sprev = &snext;
wall->BoundActors = this;
*/
sector_t *front, *back;
line_t *line;
int tex;
line = &lines[wall->linenum];
if (line->sidenum[0] == wall - sides)
{
front = line->frontsector;
back = line->backsector;
}
else
{
front = line->backsector;
back = line->frontsector;
}
if (back == NULL)
{
renderflags |= RF_RELMID;
if (line->flags & ML_DONTPEGBOTTOM)
z -= front->floortexz;
else
z -= front->ceilingtexz;
tex = wall->midtexture;
}
else if (back->floorplane.ZatPoint (x, y) >= z)
{
renderflags |= RF_RELLOWER|RF_CLIPLOWER;
if (line->flags & ML_DONTPEGBOTTOM)
z -= front->ceilingtexz;
else
z -= back->floortexz;
tex = wall->bottomtexture;
}
else
{
renderflags |= RF_RELUPPER|RF_CLIPUPPER;
if (line->flags & ML_DONTPEGTOP)
z -= front->ceilingtexz;
else
z -= back->ceilingtexz;
tex = wall->toptexture;
}
CalcFracPos (wall);
// Face the decal away from the wall
angle = R_PointToAngle2 (0, 0, line->dx, line->dy);
if (line->frontsector == front)
{
angle -= ANGLE_90;
}
else
{
angle += ANGLE_90;
}
return tex;
}
fixed_t ADecal::GetRealZ (const side_t *wall) const
{
const line_t *line = &lines[wall->linenum];
const sector_t *front, *back;
if (line->sidenum[0] == wall - sides)
{
front = line->frontsector;
back = line->backsector;
}
else
{
front = line->backsector;
back = line->frontsector;
}
if (back == NULL)
{
back = front;
}
switch (renderflags & RF_RELMASK)
{
default:
return z;
case RF_RELUPPER:
if (curline->linedef->flags & ML_DONTPEGTOP)
{
return z + front->ceilingtexz;
}
else
{
return z + back->ceilingtexz;
}
case RF_RELLOWER:
if (curline->linedef->flags & ML_DONTPEGBOTTOM)
{
return z + front->ceilingtexz;
}
else
{
return z + back->floortexz;
}
case RF_RELMID:
if (curline->linedef->flags & ML_DONTPEGBOTTOM)
{
return z + front->floortexz;
}
else
{
return z + front->ceilingtexz;
}
}
}
void ADecal::Relocate (fixed_t ix, fixed_t iy, fixed_t iz)
{
Remove ();
x = ix;
y = iy;
z = iz;
DoTrace ();
}
void ADecal::CalcFracPos (side_t *wall)
{
line_t *line = &lines[wall->linenum];
int wallnum = int(wall - sides);
vertex_t *v1, *v2;
if (line->sidenum[0] == wallnum)
{
v1 = line->v1;
v2 = line->v2;
}
else
{
v1 = line->v2;
v2 = line->v1;
}
fixed_t dx = v2->x - v1->x;
fixed_t dy = v2->y - v1->y;
if (abs(dx) > abs(dy))
{
floorclip = SafeDivScale2 (x - v1->x, dx);
}
else if (dy != 0)
{
floorclip = SafeDivScale2 (y - v1->y, dy);
}
else
{
floorclip = 0;
}
}
static int ImpactCount;
static AActor *FirstImpact; // (but not the Second or Third Impact :-)
static AActor *LastImpact;
CVAR (Bool, cl_spreaddecals, true, CVAR_ARCHIVE)
CUSTOM_CVAR (Int, cl_maxdecals, 1024, CVAR_ARCHIVE)
{
if (self < 0)
{
self = 0;
}
else
{
while (ImpactCount > self)
{
FirstImpact->Destroy ();
}
}
}
// Uses: target points to previous impact decal
// tracer points to next impact decal
//
// Note that this means we can't simply serialize an impact decal as-is
// because doing so when many are present in a level could result in
// a lot of recursion and we would run out of stack. Not nice. So instead,
// the save game code calls AImpactDecal::SerializeAll to serialize a
// list of impact decals.
IMPLEMENT_STATELESS_ACTOR (AImpactDecal, Any, -1, 0)
END_DEFAULTS
void AImpactDecal::SerializeTime (FArchive &arc)
{
if (arc.IsLoading ())
{
ImpactCount = 0;
FirstImpact = LastImpact = NULL;
}
}
void AImpactDecal::Serialize (FArchive &arc)
{
if (arc.IsStoring ())
{
// NULL the next pointer so that the serializer will not follow it
// and possibly run out of stack space. NULLing target isn't
// required; it just makes the archive smaller.
AActor *saved1 = tracer, *saved2 = target;
tracer = NULL;
target = NULL;
Super::Serialize (arc);
tracer = saved1;
target = saved2;
}
else
{
Super::Serialize (arc);
ImpactCount++;
target = LastImpact;
if (target != NULL)
{
target->tracer = this;
}
else
{
FirstImpact = this;
}
LastImpact = this;
}
}
void AImpactDecal::BeginPlay ()
{
special1 = 1; // Don't want ADecal to find a wall to stick to
Super::BeginPlay ();
target = LastImpact;
if (target != NULL)
{
target->tracer = this;
}
else
{
FirstImpact = this;
}
LastImpact = this;
}
AImpactDecal *AImpactDecal::StaticCreate (const char *name, fixed_t x, fixed_t y, fixed_t z, side_t *wall, PalEntry color)
{
if (cl_maxdecals > 0)
{
const FDecal *decal = DecalLibrary.GetDecalByName (name);
if (decal != NULL && (decal = decal->GetDecal()) != NULL)
{
return StaticCreate (decal, x, y, z, wall, color);
}
}
return NULL;
}
static void GetWallStuff (side_t *wall, vertex_t *&v1, fixed_t &ldx, fixed_t &ldy)
{
line_t *line = &lines[wall->linenum];
if (line->sidenum[0] == wall - sides)
{
v1 = line->v1;
ldx = line->dx;
ldy = line->dy;
}
else
{
v1 = line->v2;
ldx = -line->dx;
ldy = -line->dy;
}
}
static fixed_t Length (fixed_t dx, fixed_t dy)
{
return (fixed_t)sqrtf ((float)dx*(float)dx+(float)dy*(float)dy);
}
static side_t *NextWall (const side_t *wall)
{
line_t *line = &lines[wall->linenum];
int wallnum = int(wall - sides);
if (line->sidenum[0] == wallnum)
{
if (line->sidenum[1] != NO_SIDE)
{
return sides + line->sidenum[1];
}
}
else if (line->sidenum[1] == wallnum)
{
return sides + line->sidenum[0];
}
return NULL;
}
static fixed_t DecalWidth, DecalLeft, DecalRight;
static fixed_t SpreadZ;
static const AImpactDecal *SpreadSource;
static const FDecal *SpreadDecal;
static TArray<side_t *> SpreadStack;
void AImpactDecal::SpreadLeft (fixed_t r, vertex_t *v1, side_t *feelwall)
{
fixed_t ldx, ldy;
SpreadStack.Push (feelwall);
while (r < 0 && feelwall->LeftSide != NO_SIDE)
{
fixed_t startr = r;
fixed_t x = v1->x;
fixed_t y = v1->y;
feelwall = &sides[feelwall->LeftSide];
GetWallStuff (feelwall, v1, ldx, ldy);
fixed_t wallsize = Length (ldx, ldy);
r += DecalLeft;
x += Scale (r, ldx, wallsize);
y += Scale (r, ldy, wallsize);
r = wallsize + startr;
SpreadSource->CloneSelf (SpreadDecal, x, y, SpreadZ, feelwall);
SpreadStack.Push (feelwall);
side_t *nextwall = NextWall (feelwall);
if (nextwall != NULL && nextwall->LeftSide != NO_SIDE)
{
int i;
for (i = SpreadStack.Size(); i-- > 0; )
{
if (SpreadStack[i] == nextwall)
break;
}
if (i == -1)
{
vertex_t *v2;
GetWallStuff (nextwall, v2, ldx, ldy);
SpreadLeft (startr, v2, nextwall);
}
}
}
}
void AImpactDecal::SpreadRight (fixed_t r, side_t *feelwall, fixed_t wallsize)
{
vertex_t *v1;
fixed_t x, y, ldx, ldy;
SpreadStack.Push (feelwall);
while (r > wallsize && feelwall->RightSide != NO_SIDE)
{
feelwall = &sides[feelwall->RightSide];
side_t *nextwall = NextWall (feelwall);
if (nextwall != NULL && nextwall->LeftSide != NO_SIDE)
{
int i;
for (i = SpreadStack.Size(); i-- > 0; )
{
if (SpreadStack[i] == nextwall)
break;
}
if (i == -1)
{
SpreadRight (r, nextwall, wallsize);
}
}
r = DecalWidth - r + wallsize - DecalLeft;
GetWallStuff (feelwall, v1, ldx, ldy);
x = v1->x;
y = v1->y;
wallsize = Length (ldx, ldy);
x -= Scale (r, ldx, wallsize);
y -= Scale (r, ldy, wallsize);
r = DecalRight - r;
SpreadSource->CloneSelf (SpreadDecal, x, y, SpreadZ, feelwall);
SpreadStack.Push (feelwall);
}
}
AImpactDecal *AImpactDecal::StaticCreate (const FDecal *decal, fixed_t x, fixed_t y, fixed_t z, side_t *wall, PalEntry color)
{
AImpactDecal *actor = NULL;
if (decal != NULL && cl_maxdecals > 0 &&
!(wall->Flags & WALLF_NOAUTODECALS))
{
if (decal->LowerDecal)
{
StaticCreate (decal->LowerDecal->GetDecal(), x, y, z, wall);
}
if (ImpactCount >= cl_maxdecals)
{
FirstImpact->Destroy ();
}
ImpactCount++;
actor = Spawn<AImpactDecal> (x, y, z);
if (actor == NULL)
return NULL;
int stickypic = actor->StickToWall (wall);
FTexture *tex = TexMan[stickypic];
if (tex != NULL && tex->bNoDecals)
{
actor->Destroy ();
return NULL;
}
decal->ApplyToActor (actor);
if (color != 0) actor->SetShade (color.r, color.g, color.b);
if (!cl_spreaddecals || actor->picnum == 0xffff)
return actor;
// Spread decal to nearby walls if it does not all fit on this one
vertex_t *v1;
fixed_t rorg, ldx, ldy;
int xscale, yscale;
GetWallStuff (wall, v1, ldx, ldy);
rorg = Length (x - v1->x, y - v1->y);
tex = TexMan[actor->picnum];
int dwidth = tex->GetWidth ();
xscale = (actor->xscale + 1) << (FRACBITS - 6);
yscale = (actor->yscale + 1) << (FRACBITS - 6);
DecalWidth = dwidth * xscale;
DecalLeft = tex->LeftOffset * xscale;
DecalRight = DecalWidth - DecalLeft;
SpreadSource = actor;
SpreadDecal = decal;
SpreadZ = z;
// Try spreading left first
SpreadLeft (rorg - DecalLeft, v1, wall);
SpreadStack.Clear ();
// Then try spreading right
SpreadRight (rorg + DecalRight, wall,
Length (lines[wall->linenum].dx, lines[wall->linenum].dy));
SpreadStack.Clear ();
}
return actor;
}
AImpactDecal *AImpactDecal::CloneSelf (const FDecal *decal, fixed_t ix, fixed_t iy, fixed_t iz, side_t *wall) const
{
AImpactDecal *actor = NULL;
// [SO] We used to have an 'else' here -- but that means we create an actor without increasing the
// count!!!
// [RH] Moved this before the destroy, just so it can't go negative temporarily.
ImpactCount++;
if (ImpactCount >= cl_maxdecals)
{
FirstImpact->Destroy ();
}
actor = Spawn<AImpactDecal> (ix, iy, iz);
if (actor != NULL)
{
actor->StickToWall (wall);
decal->ApplyToActor (actor);
actor->alphacolor = alphacolor;
actor->renderflags = (actor->renderflags & RF_DECALMASK) |
(this->renderflags & ~RF_DECALMASK);
}
return actor;
}
void AImpactDecal::Destroy ()
{
if (target != NULL)
{
target->tracer = tracer;
}
if (tracer != NULL)
{
tracer->target = target;
}
if (LastImpact == this)
{
LastImpact = target;
}
if (FirstImpact == this)
{
FirstImpact = tracer;
}
ImpactCount--;
Super::Destroy ();
}
CCMD (countdecals)
{
Printf ("%d impact decals\n", ImpactCount);
}
CCMD (countdecalsreal)
{
TThinkerIterator<AImpactDecal> iterator (STAT_DECAL);
int count = 0;
while (iterator.Next())
count++;
Printf ("Counted %d impact decals\n", count);
}
CCMD (spray)
{
if (argv.argc() < 2 || m_Instigator == NULL)
{
Printf ("Usage: spray <decal>\n");
return;
}
FTraceResults trace;
angle_t ang = m_Instigator->angle >> ANGLETOFINESHIFT;
angle_t pitch = (angle_t)(m_Instigator->pitch) >> ANGLETOFINESHIFT;
fixed_t vx = FixedMul (finecosine[pitch], finecosine[ang]);
fixed_t vy = FixedMul (finecosine[pitch], finesine[ang]);
fixed_t vz = -finesine[pitch];
if (Trace (m_Instigator->x, m_Instigator->y,
m_Instigator->z + m_Instigator->height - (m_Instigator->height>>2),
m_Instigator->Sector,
vx, vy, vz, 172*FRACUNIT, 0, ML_BLOCKEVERYTHING, m_Instigator,
trace, TRACE_NoSky))
{
if (trace.HitType == TRACE_HitWall)
{
AImpactDecal::StaticCreate (argv[1],
trace.X, trace.Y, trace.Z,
sides + trace.Line->sidenum[trace.Side]);
}
}
}