gzdoom/src/p_spec.cpp
2010-02-06 15:31:26 +00:00

2124 lines
57 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
// Implements special effects:
// Texture animation, height or lighting changes
// according to adjacent sectors, respective
// utility functions, etc.
// Line Tag handling. Line and Sector triggers.
// Implements donut linedef triggers
// Initializes and implements BOOM linedef triggers for
// Scrollers/Conveyors
// Friction
// Wind/Current
//
//-----------------------------------------------------------------------------
#include <stdlib.h>
#include "templates.h"
#include "doomdef.h"
#include "doomstat.h"
#include "gstrings.h"
#include "i_system.h"
#include "m_argv.h"
#include "m_random.h"
#include "m_bbox.h"
#include "w_wad.h"
#include "r_local.h"
#include "p_local.h"
#include "p_lnspec.h"
#include "p_terrain.h"
#include "p_acs.h"
#include "p_3dmidtex.h"
#include "g_game.h"
#include "s_sound.h"
#include "sc_man.h"
#include "gi.h"
#include "statnums.h"
#include "g_level.h"
#include "v_font.h"
#include "a_sharedglobal.h"
// State.
#include "r_state.h"
#include "c_console.h"
#include "r_interpolate.h"
static FRandom pr_playerinspecialsector ("PlayerInSpecialSector");
void P_SetupPortals();
// [GrafZahl] Make this message changable by the user! ;)
CVAR(String, secretmessage, "A Secret is revealed!", CVAR_ARCHIVE)
IMPLEMENT_POINTY_CLASS (DScroller)
DECLARE_POINTER (m_Interpolations[0])
DECLARE_POINTER (m_Interpolations[1])
DECLARE_POINTER (m_Interpolations[2])
END_POINTERS
IMPLEMENT_POINTY_CLASS (DPusher)
DECLARE_POINTER (m_Source)
END_POINTERS
DScroller::DScroller ()
{
}
void DScroller::Serialize (FArchive &arc)
{
Super::Serialize (arc);
arc << m_Type
<< m_dx << m_dy
<< m_Affectee
<< m_Control
<< m_LastHeight
<< m_vdx << m_vdy
<< m_Accel
<< m_Parts
<< m_Interpolations[0]
<< m_Interpolations[1]
<< m_Interpolations[2];
}
DPusher::DPusher ()
{
}
void DPusher::Serialize (FArchive &arc)
{
Super::Serialize (arc);
arc << m_Type
<< m_Source
<< m_Xmag
<< m_Ymag
<< m_Magnitude
<< m_Radius
<< m_X
<< m_Y
<< m_Affectee;
}
// killough 3/7/98: Initialize generalized scrolling
static void P_SpawnScrollers();
static void P_SpawnFriction (); // phares 3/16/98
static void P_SpawnPushers (); // phares 3/20/98
// [RH] Check dmflags for noexit and respond accordingly
bool CheckIfExitIsGood (AActor *self, level_info_t *info)
{
cluster_info_t *cluster;
// The world can always exit itself.
if (self == NULL)
return true;
// We must kill all monsters to exit the level.
if ((dmflags2 & DF2_KILL_MONSTERS) && level.killed_monsters != level.total_monsters)
return false;
// Is this a deathmatch game and we're not allowed to exit?
if ((deathmatch || alwaysapplydmflags) && (dmflags & DF_NO_EXIT))
{
P_DamageMobj (self, self, self, TELEFRAG_DAMAGE, NAME_Exit);
return false;
}
// Is this a singleplayer game and the next map is part of the same hub and we're dead?
if (self->health <= 0 &&
!multiplayer &&
info != NULL &&
info->cluster == level.cluster &&
(cluster = FindClusterInfo(level.cluster)) != NULL &&
cluster->flags & CLUSTER_HUB)
{
return false;
}
if (deathmatch)
{
Printf ("%s exited the level.\n", self->player->userinfo.netname);
}
return true;
}
//
// UTILITIES
//
//
// RETURN NEXT SECTOR # THAT LINE TAG REFERS TO
//
// Find the next sector with a specified tag.
// Rewritten by Lee Killough to use chained hashing to improve speed
int P_FindSectorFromTag (int tag, int start)
{
start = start >= 0 ? sectors[start].nexttag :
sectors[(unsigned) tag % (unsigned) numsectors].firsttag;
while (start >= 0 && sectors[start].tag != tag)
start = sectors[start].nexttag;
return start;
}
// killough 4/16/98: Same thing, only for linedefs
int P_FindLineFromID (int id, int start)
{
start = start >= 0 ? lines[start].nextid :
lines[(unsigned) id % (unsigned) numlines].firstid;
while (start >= 0 && lines[start].id != id)
start = lines[start].nextid;
return start;
}
//============================================================================
//
// P_ActivateLine
//
//============================================================================
bool P_ActivateLine (line_t *line, AActor *mo, int side, int activationType)
{
int lineActivation;
INTBOOL repeat;
INTBOOL buttonSuccess;
BYTE special;
if (!P_TestActivateLine (line, mo, side, activationType))
{
return false;
}
lineActivation = line->activation;
repeat = line->flags & ML_REPEAT_SPECIAL;
buttonSuccess = false;
buttonSuccess = LineSpecials[line->special]
(line, mo, side == 1, line->args[0],
line->args[1], line->args[2],
line->args[3], line->args[4]);
special = line->special;
if (!repeat && buttonSuccess)
{ // clear the special on non-retriggerable lines
line->special = 0;
}
if (buttonSuccess)
{
if (activationType == SPAC_Use || activationType == SPAC_Impact)
{
P_ChangeSwitchTexture (line->sidedef[0], repeat, special);
}
}
// some old WADs use this method to create walls that change the texture when shot.
else if (activationType == SPAC_Impact && // only for shootable triggers
(level.flags2 & LEVEL2_DUMMYSWITCHES) && // this is only a compatibility setting for an old hack!
!repeat && // only non-repeatable triggers
(special<Generic_Floor || special>Generic_Crusher) && // not for Boom's generalized linedefs
special && // not for lines without a special
line->args[0] == line->id && // Safety check: exclude edited UDMF linedefs or ones that don't map the tag to args[0]
line->args[0] && // only if there's a tag (which is stored in the first arg)
P_FindSectorFromTag (line->args[0], -1) == -1) // only if no sector is tagged to this linedef
{
P_ChangeSwitchTexture (line->sidedef[0], repeat, special);
line->special = 0;
}
// end of changed code
if (developer && buttonSuccess)
{
Printf ("Line special %d activated\n", special);
}
return true;
}
//============================================================================
//
// P_TestActivateLine
//
//============================================================================
bool P_TestActivateLine (line_t *line, AActor *mo, int side, int activationType)
{
int lineActivation = line->activation;
if (line->flags & ML_FIRSTSIDEONLY && side == 1)
{
return false;
}
if (lineActivation & SPAC_UseThrough)
{
lineActivation |= SPAC_Use;
}
else if (line->special == Teleport &&
(lineActivation & SPAC_Cross) &&
activationType == SPAC_PCross &&
mo != NULL &&
mo->flags & MF_MISSILE)
{ // Let missiles use regular player teleports
lineActivation |= SPAC_PCross;
}
// BOOM's generalized line types that allow monster use can actually be
// activated by anything except projectiles.
if (lineActivation & SPAC_AnyCross)
{
lineActivation |= SPAC_Cross|SPAC_MCross;
}
if (activationType == SPAC_Use)
{
if (!P_CheckSwitchRange(mo, line, side))
{
return false;
}
}
if ((lineActivation & activationType) == 0)
{
if (activationType == SPAC_Use && (lineActivation & SPAC_MUse) && !mo->player && mo->flags4 & MF4_CANUSEWALLS)
{
return true;
}
if (activationType == SPAC_Push && (lineActivation & SPAC_MPush) && !mo->player && mo->flags2 & MF2_PUSHWALL)
{
return true;
}
if (activationType != SPAC_MCross || lineActivation != SPAC_Cross)
{
return false;
}
}
if (activationType == SPAC_AnyCross && (lineActivation & activationType))
{
return true;
}
if (mo && !mo->player &&
!(mo->flags & MF_MISSILE) &&
!(line->flags & ML_MONSTERSCANACTIVATE) &&
(activationType != SPAC_MCross || (!(lineActivation & SPAC_MCross))))
{
// [RH] monsters' ability to activate this line depends on its type
// In Hexen, only MCROSS lines could be activated by monsters. With
// lax activation checks, monsters can also activate certain lines
// even without them being marked as monster activate-able. This is
// the default for non-Hexen maps in Hexen format.
if (!(level.flags2 & LEVEL2_LAXMONSTERACTIVATION))
{
return false;
}
if ((activationType == SPAC_Use || activationType == SPAC_Push)
&& (line->flags & ML_SECRET))
return false; // never open secret doors
bool noway = true;
switch (activationType)
{
case SPAC_Use:
case SPAC_Push:
switch (line->special)
{
case Door_Raise:
if (line->args[0] == 0 && line->args[1] < 64)
noway = false;
break;
case Teleport:
case Teleport_NoFog:
noway = false;
}
break;
case SPAC_MCross:
if (!(lineActivation & SPAC_MCross))
{
switch (line->special)
{
case Door_Raise:
if (line->args[1] >= 64)
{
break;
}
case Teleport:
case Teleport_NoFog:
case Teleport_Line:
case Plat_DownWaitUpStayLip:
case Plat_DownWaitUpStay:
noway = false;
}
}
else noway = false;
break;
default:
noway = false;
}
return !noway;
}
if (activationType == SPAC_MCross && !(lineActivation & SPAC_MCross) &&
!(line->flags & ML_MONSTERSCANACTIVATE))
{
return false;
}
return true;
}
//
// P_PlayerInSpecialSector
// Called every tic frame
// that the player origin is in a special sector
//
void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
{
if (sector == NULL)
{
// Falling, not all the way down yet?
sector = player->mo->Sector;
if (player->mo->z != sector->floorplane.ZatPoint (player->mo->x, player->mo->y)
&& !player->mo->waterlevel)
{
return;
}
}
int special = sector->special & ~SECRET_MASK;
// Has hit ground.
AInventory *ironfeet;
// Allow subclasses. Better would be to implement it as armor and let that reduce
// the damage as part of the normal damage procedure. Unfortunately, I don't have
// different damage types yet, so that's not happening for now.
for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
{
if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet)))
break;
}
// [RH] Normal DOOM special or BOOM specialized?
if (special >= dLight_Flicker && special <= 255)
{
switch (special)
{
case Sector_Heal:
// CoD's healing sector
if (!(level.time & 0x1f))
P_GiveBody (player->mo, 1);
break;
case Damage_InstantDeath:
// Strife's instant death sector
P_DamageMobj (player->mo, NULL, NULL, 999, NAME_InstantDeath);
break;
case dDamage_Hellslime:
// HELLSLIME DAMAGE
if (ironfeet == NULL && !(level.time&0x1f))
P_DamageMobj (player->mo, NULL, NULL, 10, NAME_Slime);
break;
case dDamage_Nukage:
// NUKAGE DAMAGE
case sLight_Strobe_Hurt:
if (ironfeet == NULL && !(level.time&0x1f))
P_DamageMobj (player->mo, NULL, NULL, 5, NAME_Slime);
break;
case dDamage_SuperHellslime:
// SUPER HELLSLIME DAMAGE
case dLight_Strobe_Hurt:
// STROBE HURT
if (ironfeet == NULL || pr_playerinspecialsector() < 5)
{
if (!(level.time&0x1f))
P_DamageMobj (player->mo, NULL, NULL, 20, NAME_Slime);
}
break;
case sDamage_Hellslime:
if (ironfeet == NULL)
player->hazardcount += 2;
break;
case sDamage_SuperHellslime:
if (ironfeet == NULL)
player->hazardcount += 4;
break;
case dDamage_End:
// EXIT SUPER DAMAGE! (for E1M8 finale)
player->cheats &= ~CF_GODMODE;
if (!(level.time & 0x1f))
P_DamageMobj (player->mo, NULL, NULL, 20, NAME_None);
if (player->health <= 10 && (!deathmatch || !(dmflags & DF_NO_EXIT)))
G_ExitLevel(0, false);
break;
case dDamage_LavaWimpy:
case dScroll_EastLavaDamage:
if (!(level.time & 15))
{
P_DamageMobj(player->mo, NULL, NULL, 5, NAME_Fire);
P_HitFloor(player->mo);
}
break;
case dDamage_LavaHefty:
if(!(level.time & 15))
{
P_DamageMobj(player->mo, NULL, NULL, 8, NAME_Fire);
P_HitFloor(player->mo);
}
break;
default:
// [RH] Ignore unknown specials
break;
}
}
else
{
//jff 3/14/98 handle extended sector types for secrets and damage
switch (special & DAMAGE_MASK)
{
case 0x000: // no damage
break;
case 0x100: // 2/5 damage per 31 ticks
if (ironfeet == NULL && !(level.time&0x1f))
P_DamageMobj (player->mo, NULL, NULL, 5, NAME_Fire);
break;
case 0x200: // 5/10 damage per 31 ticks
if (ironfeet == NULL && !(level.time&0x1f))
P_DamageMobj (player->mo, NULL, NULL, 10, NAME_Slime);
break;
case 0x300: // 10/20 damage per 31 ticks
if (ironfeet == NULL
|| pr_playerinspecialsector() < 5) // take damage even with suit
{
if (!(level.time&0x1f))
P_DamageMobj (player->mo, NULL, NULL, 20, NAME_Slime);
}
break;
}
}
// [RH] Apply any customizable damage
if (sector->damage)
{
if (sector->damage < 20)
{
if (ironfeet == NULL && !(level.time&0x1f))
P_DamageMobj (player->mo, NULL, NULL, sector->damage, MODtoDamageType (sector->mod));
}
else if (sector->damage < 50)
{
if ((ironfeet == NULL || (pr_playerinspecialsector()<5))
&& !(level.time&0x1f))
{
P_DamageMobj (player->mo, NULL, NULL, sector->damage, MODtoDamageType (sector->mod));
}
}
else
{
P_DamageMobj (player->mo, NULL, NULL, sector->damage, MODtoDamageType (sector->mod));
}
}
if (sector->special & SECRET_MASK)
{
player->secretcount++;
level.found_secrets++;
sector->special &= ~SECRET_MASK;
if (player->mo->CheckLocalView (consoleplayer))
{
C_MidPrint (SmallFont, secretmessage);
S_Sound (CHAN_AUTO, "misc/secret", 1, ATTN_NORM);
}
}
}
//============================================================================
//
// P_PlayerOnSpecialFlat
//
//============================================================================
void P_PlayerOnSpecialFlat (player_t *player, int floorType)
{
if (player->mo->z > player->mo->Sector->floorplane.ZatPoint (
player->mo->x, player->mo->y) &&
!player->mo->waterlevel)
{ // Player is not touching the floor
return;
}
if (Terrains[floorType].DamageAmount &&
!(level.time & Terrains[floorType].DamageTimeMask))
{
AInventory *ironfeet = NULL;
if (Terrains[floorType].AllowProtection)
{
for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
{
if (ironfeet->IsKindOf (RUNTIME_CLASS(APowerIronFeet)))
break;
}
}
if (ironfeet == NULL)
{
P_DamageMobj (player->mo, NULL, NULL, Terrains[floorType].DamageAmount,
Terrains[floorType].DamageMOD);
}
if (Terrains[floorType].Splash != -1)
{
S_Sound (player->mo, CHAN_AUTO,
Splashes[Terrains[floorType].Splash].NormalSplashSound, 1,
ATTN_IDLE);
}
}
}
//
// P_UpdateSpecials
// Animate planes, scroll walls, etc.
//
EXTERN_CVAR (Float, timelimit)
void P_UpdateSpecials ()
{
// LEVEL TIMER
if (deathmatch && timelimit)
{
if (level.maptime >= (int)(timelimit * TICRATE * 60))
{
Printf ("%s\n", GStrings("TXT_TIMELIMIT"));
G_ExitLevel(0, false);
}
}
}
//
// SPECIAL SPAWNING
//
CUSTOM_CVAR (Bool, forcewater, false, CVAR_ARCHIVE|CVAR_SERVERINFO)
{
if (gamestate == GS_LEVEL)
{
int i;
for (i = 0; i < numsectors; i++)
{
sector_t *hsec = sectors[i].GetHeightSec();
if (hsec &&
!(sectors[i].heightsec->MoreFlags & SECF_UNDERWATER))
{
if (self)
{
hsec->MoreFlags |= SECF_FORCEDUNDERWATER;
}
else
{
hsec->MoreFlags &= ~SECF_FORCEDUNDERWATER;
}
}
}
}
}
class DLightTransfer : public DThinker
{
DECLARE_CLASS (DLightTransfer, DThinker)
DLightTransfer() {}
public:
DLightTransfer (sector_t *srcSec, int target, bool copyFloor);
void Serialize (FArchive &arc);
void Tick ();
protected:
static void DoTransfer (BYTE level, int target, bool floor);
BYTE LastLight;
sector_t *Source;
int TargetTag;
bool CopyFloor;
};
IMPLEMENT_CLASS (DLightTransfer)
void DLightTransfer::Serialize (FArchive &arc)
{
Super::Serialize (arc);
arc << LastLight << Source << TargetTag << CopyFloor;
}
DLightTransfer::DLightTransfer (sector_t *srcSec, int target, bool copyFloor)
{
int secnum;
Source = srcSec;
TargetTag = target;
CopyFloor = copyFloor;
DoTransfer (LastLight = srcSec->lightlevel, target, copyFloor);
if (copyFloor)
{
for (secnum = -1; (secnum = P_FindSectorFromTag (target, secnum)) >= 0; )
sectors[secnum].ChangeFlags(sector_t::floor, 0, PLANEF_ABSLIGHTING);
}
else
{
for (secnum = -1; (secnum = P_FindSectorFromTag (target, secnum)) >= 0; )
sectors[secnum].ChangeFlags(sector_t::ceiling, 0, PLANEF_ABSLIGHTING);
}
ChangeStatNum (STAT_LIGHTTRANSFER);
}
void DLightTransfer::Tick ()
{
BYTE light = Source->lightlevel;
if (light != LastLight)
{
LastLight = light;
DoTransfer (light, TargetTag, CopyFloor);
}
}
void DLightTransfer::DoTransfer (BYTE level, int target, bool floor)
{
int secnum;
if (floor)
{
for (secnum = -1; (secnum = P_FindSectorFromTag (target, secnum)) >= 0; )
sectors[secnum].SetPlaneLight(sector_t::floor, level);
}
else
{
for (secnum = -1; (secnum = P_FindSectorFromTag (target, secnum)) >= 0; )
sectors[secnum].SetPlaneLight(sector_t::ceiling, level);
}
}
class DWallLightTransfer : public DThinker
{
enum
{
WLF_SIDE1=1,
WLF_SIDE2=2,
WLF_NOFAKECONTRAST=4
};
DECLARE_CLASS (DWallLightTransfer, DThinker)
DWallLightTransfer() {}
public:
DWallLightTransfer (sector_t *srcSec, int target, BYTE flags);
void Serialize (FArchive &arc);
void Tick ();
protected:
static void DoTransfer (BYTE level, int target, BYTE flags);
BYTE LastLight;
BYTE Flags;
sector_t *Source;
int TargetID;
};
IMPLEMENT_CLASS (DWallLightTransfer)
void DWallLightTransfer::Serialize (FArchive &arc)
{
Super::Serialize (arc);
arc << LastLight << Source << TargetID << Flags;
}
DWallLightTransfer::DWallLightTransfer (sector_t *srcSec, int target, BYTE flags)
{
int linenum;
int wallflags;
Source = srcSec;
TargetID = target;
Flags = flags;
DoTransfer (LastLight = srcSec->lightlevel, target, Flags);
if (!(flags&WLF_NOFAKECONTRAST)) wallflags = WALLF_ABSLIGHTING;
else wallflags = WALLF_NOFAKECONTRAST|WALLF_ABSLIGHTING;
for (linenum = -1; (linenum = P_FindLineFromID (target, linenum)) >= 0; )
{
if (flags & WLF_SIDE1 && lines[linenum].sidedef[0] != NULL)
{
lines[linenum].sidedef[0]->Flags |= wallflags;
}
if (flags & WLF_SIDE2 && lines[linenum].sidedef[1] != NULL)
{
lines[linenum].sidedef[1]->Flags |= wallflags;
}
}
ChangeStatNum(STAT_LIGHTTRANSFER);
}
void DWallLightTransfer::Tick ()
{
BYTE light = Source->lightlevel;
if (light != LastLight)
{
LastLight = light;
DoTransfer (light, TargetID, Flags);
}
}
void DWallLightTransfer::DoTransfer (BYTE lightlevel, int target, BYTE flags)
{
int linenum;
for (linenum = -1; (linenum = P_FindLineFromID (target, linenum)) >= 0; )
{
line_t * line = &lines[linenum];
if (flags & WLF_SIDE1 && line->sidedef[0] != NULL)
{
line->sidedef[0]->SetLight(lightlevel);
}
if (flags & WLF_SIDE2 && line->sidedef[1] != NULL)
{
line->sidedef[1]->SetLight(lightlevel);
}
}
}
//-----------------------------------------------------------------------------
//
// Portals
//
//-----------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Upper stacks go in the top sector. Lower stacks go in the bottom sector.
static void SetupFloorPortal (AStackPoint *point)
{
NActorIterator it (NAME_LowerStackLookOnly, point->tid);
sector_t *Sector = point->Sector;
Sector->FloorSkyBox = static_cast<ASkyViewpoint*>(it.Next());
if (Sector->FloorSkyBox != NULL)
{
Sector->FloorSkyBox->Mate = point;
Sector->FloorSkyBox->PlaneAlpha = Scale (point->args[0], OPAQUE, 255);
}
}
static void SetupCeilingPortal (AStackPoint *point)
{
NActorIterator it (NAME_UpperStackLookOnly, point->tid);
sector_t *Sector = point->Sector;
Sector->CeilingSkyBox = static_cast<ASkyViewpoint*>(it.Next());
if (Sector->CeilingSkyBox != NULL)
{
Sector->CeilingSkyBox->Mate = point;
Sector->CeilingSkyBox->PlaneAlpha = Scale (point->args[0], OPAQUE, 255);
}
}
void P_SetupPortals()
{
TThinkerIterator<AStackPoint> it;
AStackPoint *pt;
TArray<AStackPoint *> points;
while ((pt = it.Next()))
{
FName nm = pt->GetClass()->TypeName;
if (nm == NAME_UpperStackLookOnly)
{
SetupFloorPortal(pt);
}
else if (nm == NAME_LowerStackLookOnly)
{
SetupCeilingPortal(pt);
}
pt->special1 = 0;
points.Push(pt);
}
for(unsigned i=0;i<points.Size(); i++)
{
if (points[i]->special1 == 0 && points[i]->Mate != NULL)
{
for(unsigned j=1;j<points.Size(); j++)
{
if (points[j]->special1 == 0 && points[j]->Mate != NULL && points[i]->GetClass() == points[j]->GetClass())
{
fixed_t deltax1 = points[i]->Mate->x - points[i]->x;
fixed_t deltay1 = points[i]->Mate->y - points[i]->y;
fixed_t deltax2 = points[j]->Mate->x - points[j]->x;
fixed_t deltay2 = points[j]->Mate->y - points[j]->y;
if (deltax1 == deltax2 && deltay1 == deltay2)
{
if (points[j]->Sector->FloorSkyBox == points[j]->Mate)
points[j]->Sector->FloorSkyBox = points[i]->Mate;
if (points[j]->Sector->CeilingSkyBox == points[j]->Mate)
points[j]->Sector->CeilingSkyBox = points[i]->Mate;
points[j]->special1 = 1;
}
}
}
}
}
}
inline void SetPortal(sector_t *sector, int plane, AStackPoint *portal)
{
// plane: 0=floor, 1=ceiling, 2=both
if (plane > 0)
{
if (sector->CeilingSkyBox == NULL) sector->CeilingSkyBox = portal;
}
if (plane == 2 || plane == 0)
{
if (sector->FloorSkyBox == NULL) sector->FloorSkyBox = portal;
}
}
void P_SpawnPortal(line_t *line, int sectortag, int plane, int alpha)
{
for (int i=0;i<numlines;i++)
{
// We must look for the reference line with a linear search unless we want to waste the line ID for it
// which is not a good idea.
if (lines[i].special == Sector_SetPortal &&
lines[i].args[0] == sectortag &&
lines[i].args[1] == 0 &&
lines[i].args[2] == plane &&
lines[i].args[3] == 1)
{
fixed_t x1 = (line->v1->x + line->v2->x) >> 1;
fixed_t y1 = (line->v1->y + line->v2->y) >> 1;
fixed_t x2 = (lines[i].v1->x + lines[i].v2->x) >> 1;
fixed_t y2 = (lines[i].v1->y + lines[i].v2->y) >> 1;
AStackPoint *anchor = Spawn<AStackPoint>(x1, y1, 0, NO_REPLACE);
AStackPoint *reference = Spawn<AStackPoint>(x2, y2, 0, NO_REPLACE);
reference->Mate = anchor;
anchor->Mate = reference;
// This is so that the renderer can distinguish these portals from
// the ones spawned with the '*StackLookOnly' things.
reference->flags |= MF_JUSTATTACKED;
anchor->flags |= MF_JUSTATTACKED;
for (int s=-1; (s = P_FindSectorFromTag(sectortag,s)) >= 0;)
{
SetPortal(&sectors[s], plane, reference);
}
for (int j=0;j<numlines;j++)
{
// Check if this portal needs to be copied to other sectors
// This must be done here to ensure that it gets done only after the portal is set up
if (lines[i].special == Sector_SetPortal &&
lines[i].args[1] == 1 &&
lines[i].args[2] == plane &&
lines[i].args[3] == sectortag)
{
if (lines[i].args[0] == 0)
{
SetPortal(lines[i].frontsector, plane, reference);
}
else
{
for (int s=-1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
{
SetPortal(&sectors[s], plane, reference);
}
}
}
}
return;
}
}
}
//
// P_SpawnSpecials
//
// After the map has been loaded, scan for specials that spawn thinkers
//
void P_SpawnSpecials (void)
{
sector_t *sector;
int i;
P_SetupPortals();
// Init special SECTORs.
sector = sectors;
for (i = 0; i < numsectors; i++, sector++)
{
if (sector->special == 0)
continue;
// [RH] All secret sectors are marked with a BOOM-ish bitfield
if (sector->special & SECRET_MASK)
level.total_secrets++;
switch (sector->special & 0xff)
{
// [RH] Normal DOOM/Hexen specials. We clear off the special for lights
// here instead of inside the spawners.
case dLight_Flicker:
// FLICKERING LIGHTS
new DLightFlash (sector);
sector->special &= 0xff00;
break;
case dLight_StrobeFast:
// STROBE FAST
new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
sector->special &= 0xff00;
break;
case dLight_StrobeSlow:
// STROBE SLOW
new DStrobe (sector, STROBEBRIGHT, SLOWDARK, false);
sector->special &= 0xff00;
break;
case dLight_Strobe_Hurt:
case sLight_Strobe_Hurt:
// STROBE FAST/DEATH SLIME
new DStrobe (sector, STROBEBRIGHT, FASTDARK, false);
break;
case dLight_Glow:
// GLOWING LIGHT
new DGlow (sector);
sector->special &= 0xff00;
break;
case dSector_DoorCloseIn30:
// DOOR CLOSE IN 30 SECONDS
P_SpawnDoorCloseIn30 (sector);
break;
case dLight_StrobeSlowSync:
// SYNC STROBE SLOW
new DStrobe (sector, STROBEBRIGHT, SLOWDARK, true);
sector->special &= 0xff00;
break;
case dLight_StrobeFastSync:
// SYNC STROBE FAST
new DStrobe (sector, STROBEBRIGHT, FASTDARK, true);
sector->special &= 0xff00;
break;
case dSector_DoorRaiseIn5Mins:
// DOOR RAISE IN 5 MINUTES
P_SpawnDoorRaiseIn5Mins (sector);
break;
case dLight_FireFlicker:
// fire flickering
new DFireFlicker (sector);
sector->special &= 0xff00;
break;
case dFriction_Low:
sector->friction = FRICTION_LOW;
sector->movefactor = 0x269;
sector->special &= 0xff00;
sector->special |= FRICTION_MASK;
break;
// [RH] Hexen-like phased lighting
case LightSequenceStart:
new DPhased (sector);
break;
case Light_Phased:
new DPhased (sector, 48, 63 - (sector->lightlevel & 63));
break;
case Sky2:
sector->sky = PL_SKYFLAT;
break;
case dScroll_EastLavaDamage:
new DScroller (DScroller::sc_floor, (-FRACUNIT/2)<<3,
0, -1, int(sector-sectors), 0);
break;
default:
if ((sector->special & 0xff) >= Scroll_North_Slow &&
(sector->special & 0xff) <= Scroll_SouthWest_Fast)
{ // Hexen scroll special
static const char hexenScrollies[24][2] =
{
{ 0, 1 }, { 0, 2 }, { 0, 4 },
{ -1, 0 }, { -2, 0 }, { -4, 0 },
{ 0, -1 }, { 0, -2 }, { 0, -4 },
{ 1, 0 }, { 2, 0 }, { 4, 0 },
{ 1, 1 }, { 2, 2 }, { 4, 4 },
{ -1, 1 }, { -2, 2 }, { -4, 4 },
{ -1, -1 }, { -2, -2 }, { -4, -4 },
{ 1, -1 }, { 2, -2 }, { 4, -4 }
};
int i = (sector->special & 0xff) - Scroll_North_Slow;
fixed_t dx = hexenScrollies[i][0] * (FRACUNIT/2);
fixed_t dy = hexenScrollies[i][1] * (FRACUNIT/2);
new DScroller (DScroller::sc_floor, dx, dy, -1, int(sector-sectors), 0);
}
else if ((sector->special & 0xff) >= Carry_East5 &&
(sector->special & 0xff) <= Carry_East35)
{ // Heretic scroll special
// Only east scrollers also scroll the texture
new DScroller (DScroller::sc_floor,
(-FRACUNIT/2)<<((sector->special & 0xff) - Carry_East5),
0, -1, int(sector-sectors), 0);
}
break;
}
}
// Init other misc stuff
P_SpawnScrollers(); // killough 3/7/98: Add generalized scrollers
P_SpawnFriction(); // phares 3/12/98: New friction model using linedefs
P_SpawnPushers(); // phares 3/20/98: New pusher model using linedefs
for (i = 0; i < numlines; i++)
{
switch (lines[i].special)
{
int s;
sector_t *sec;
// killough 3/7/98:
// support for drawn heights coming from different sector
case Transfer_Heights:
sec = lines[i].frontsector;
if (lines[i].args[1] & 2)
{
sec->MoreFlags |= SECF_FAKEFLOORONLY;
}
if (lines[i].args[1] & 4)
{
sec->MoreFlags |= SECF_CLIPFAKEPLANES;
}
if (lines[i].args[1] & 8)
{
sec->MoreFlags |= SECF_UNDERWATER;
}
else if (forcewater)
{
sec->MoreFlags |= SECF_FORCEDUNDERWATER;
}
if (lines[i].args[1] & 16)
{
sec->MoreFlags |= SECF_IGNOREHEIGHTSEC;
}
if (lines[i].args[1] & 32)
{
sec->MoreFlags |= SECF_NOFAKELIGHT;
}
for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
{
sectors[s].heightsec = sec;
sec->e->FakeFloor.Sectors.Push(&sectors[s]);
sectors[s].AdjustFloorClip();
}
break;
// killough 3/16/98: Add support for setting
// floor lighting independently (e.g. lava)
case Transfer_FloorLight:
new DLightTransfer (lines[i].frontsector, lines[i].args[0], true);
break;
// killough 4/11/98: Add support for setting
// ceiling lighting independently
case Transfer_CeilingLight:
new DLightTransfer (lines[i].frontsector, lines[i].args[0], false);
break;
// [Graf Zahl] Add support for setting lighting
// per wall independently
case Transfer_WallLight:
new DWallLightTransfer (lines[i].frontsector, lines[i].args[0], lines[i].args[1]);
break;
case Sector_Attach3dMidtex:
P_Attach3dMidtexLinesToSector(lines[i].frontsector, lines[i].args[0], lines[i].args[1], !!lines[i].args[2]);
break;
case Sector_SetLink:
if (lines[i].args[0] == 0)
{
P_AddSectorLinks(lines[i].frontsector, lines[i].args[1], lines[i].args[2], lines[i].args[3]);
}
break;
case Sector_SetPortal:
// arg 0 = sector tag
// arg 1 = type
// - 0: normal (handled here)
// - 1: copy (handled by the portal they copy)
// - 2: EE-style skybox (handled by the camera object)
// other values reserved for later use
// arg 2 = 0:floor, 1:ceiling, 2:both
// arg 3 = 0: anchor, 1: reference line
// arg 4 = for the anchor only: alpha
if (lines[i].args[1] == 0 && lines[i].args[3] == 0)
{
P_SpawnPortal(&lines[i], lines[i].args[0], lines[i].args[2], lines[i].args[4]);
}
break;
// [RH] ZDoom Static_Init settings
case Static_Init:
switch (lines[i].args[1])
{
case Init_Gravity:
{
float grav = ((float)P_AproxDistance (lines[i].dx, lines[i].dy)) / (FRACUNIT * 100.0f);
for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
sectors[s].gravity = grav;
}
break;
//case Init_Color:
// handled in P_LoadSideDefs2()
case Init_Damage:
{
int damage = P_AproxDistance (lines[i].dx, lines[i].dy) >> FRACBITS;
for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
{
sectors[s].damage = damage;
sectors[s].mod = 0;//MOD_UNKNOWN;
}
}
break;
case Init_SectorLink:
if (lines[i].args[3] == 0)
P_AddSectorLinksByID(lines[i].frontsector, lines[i].args[0], lines[i].args[2]);
break;
// killough 10/98:
//
// Support for sky textures being transferred from sidedefs.
// Allows scrolling and other effects (but if scrolling is
// used, then the same sector tag needs to be used for the
// sky sector, the sky-transfer linedef, and the scroll-effect
// linedef). Still requires user to use F_SKY1 for the floor
// or ceiling texture, to distinguish floor and ceiling sky.
case Init_TransferSky:
for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
sectors[s].sky = (i+1) | PL_SKYFLAT;
break;
}
break;
}
}
// [RH] Start running any open scripts on this map
FBehavior::StaticStartTypedScripts (SCRIPT_Open, NULL, false);
}
// killough 2/28/98:
//
// This function, with the help of r_plane.c and r_bsp.c, supports generalized
// scrolling floors and walls, with optional mobj-carrying properties, e.g.
// conveyor belts, rivers, etc. A linedef with a special type affects all
// tagged sectors the same way, by creating scrolling and/or object-carrying
// properties. Multiple linedefs may be used on the same sector and are
// cumulative, although the special case of scrolling a floor and carrying
// things on it, requires only one linedef. The linedef's direction determines
// the scrolling direction, and the linedef's length determines the scrolling
// speed. This was designed so that an edge around the sector could be used to
// control the direction of the sector's scrolling, which is usually what is
// desired.
//
// Process the active scrollers.
//
// This is the main scrolling code
// killough 3/7/98
void DScroller::Tick ()
{
fixed_t dx = m_dx, dy = m_dy;
if (m_Control != -1)
{ // compute scroll amounts based on a sector's height changes
fixed_t height = sectors[m_Control].CenterFloor () +
sectors[m_Control].CenterCeiling ();
fixed_t delta = height - m_LastHeight;
m_LastHeight = height;
dx = FixedMul(dx, delta);
dy = FixedMul(dy, delta);
}
// killough 3/14/98: Add acceleration
if (m_Accel)
{
m_vdx = dx += m_vdx;
m_vdy = dy += m_vdy;
}
if (!(dx | dy)) // no-op if both (x,y) offsets are 0
return;
switch (m_Type)
{
case sc_side: // killough 3/7/98: Scroll wall texture
if (m_Parts & scw_top)
{
sides[m_Affectee].AddTextureXOffset(side_t::top, dx);
sides[m_Affectee].AddTextureYOffset(side_t::top, dy);
}
if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
!(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
{
sides[m_Affectee].AddTextureXOffset(side_t::mid, dx);
sides[m_Affectee].AddTextureYOffset(side_t::mid, dy);
}
if (m_Parts & scw_bottom)
{
sides[m_Affectee].AddTextureXOffset(side_t::bottom, dx);
sides[m_Affectee].AddTextureYOffset(side_t::bottom, dy);
}
break;
case sc_floor: // killough 3/7/98: Scroll floor texture
sectors[m_Affectee].AddXOffset(sector_t::floor, dx);
sectors[m_Affectee].AddYOffset(sector_t::floor, dy);
break;
case sc_ceiling: // killough 3/7/98: Scroll ceiling texture
sectors[m_Affectee].AddXOffset(sector_t::ceiling, dx);
sectors[m_Affectee].AddYOffset(sector_t::ceiling, dy);
break;
// [RH] Don't actually carry anything here. That happens later.
case sc_carry:
level.Scrolls[m_Affectee].ScrollX += dx;
level.Scrolls[m_Affectee].ScrollY += dy;
break;
case sc_carry_ceiling: // to be added later
break;
}
}
//
// Add_Scroller()
//
// Add a generalized scroller to the thinker list.
//
// type: the enumerated type of scrolling: floor, ceiling, floor carrier,
// wall, floor carrier & scroller
//
// (dx,dy): the direction and speed of the scrolling or its acceleration
//
// control: the sector whose heights control this scroller's effect
// remotely, or -1 if no control sector
//
// affectee: the index of the affected object (sector or sidedef)
//
// accel: non-zero if this is an accelerative effect
//
DScroller::DScroller (EScrollType type, fixed_t dx, fixed_t dy,
int control, int affectee, int accel, int scrollpos)
: DThinker (STAT_SCROLLER)
{
m_Type = type;
m_dx = dx;
m_dy = dy;
m_Accel = accel;
m_Parts = scrollpos;
m_vdx = m_vdy = 0;
if ((m_Control = control) != -1)
m_LastHeight =
sectors[control].CenterFloor () + sectors[control].CenterCeiling ();
m_Affectee = affectee;
m_Interpolations[0] = m_Interpolations[1] = m_Interpolations[2] = NULL;
switch (type)
{
case sc_carry:
level.AddScroller (this, affectee);
break;
case sc_side:
sides[affectee].Flags |= WALLF_NOAUTODECALS;
if (m_Parts & scw_top)
{
m_Interpolations[0] = sides[m_Affectee].SetInterpolation(side_t::top);
}
if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
!(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
{
m_Interpolations[1] = sides[m_Affectee].SetInterpolation(side_t::mid);
}
if (m_Parts & scw_bottom)
{
m_Interpolations[2] = sides[m_Affectee].SetInterpolation(side_t::bottom);
}
break;
case sc_floor:
m_Interpolations[0] = sectors[affectee].SetInterpolation(sector_t::FloorScroll, false);
break;
case sc_ceiling:
m_Interpolations[0] = sectors[affectee].SetInterpolation(sector_t::CeilingScroll, false);
break;
default:
break;
}
}
void DScroller::Destroy ()
{
for(int i=0;i<3;i++)
{
if (m_Interpolations[i] != NULL)
{
m_Interpolations[i]->DelRef();
m_Interpolations[i] = NULL;
}
}
Super::Destroy();
}
// Adds wall scroller. Scroll amount is rotated with respect to wall's
// linedef first, so that scrolling towards the wall in a perpendicular
// direction is translated into vertical motion, while scrolling along
// the wall in a parallel direction is translated into horizontal motion.
//
// killough 5/25/98: cleaned up arithmetic to avoid drift due to roundoff
DScroller::DScroller (fixed_t dx, fixed_t dy, const line_t *l,
int control, int accel, int scrollpos)
: DThinker (STAT_SCROLLER)
{
fixed_t x = abs(l->dx), y = abs(l->dy), d;
if (y > x)
d = x, x = y, y = d;
d = FixedDiv (x, finesine[(tantoangle[FixedDiv(y,x) >> DBITS] + ANG90)
>> ANGLETOFINESHIFT]);
x = -FixedDiv (FixedMul(dy, l->dy) + FixedMul(dx, l->dx), d);
y = -FixedDiv (FixedMul(dx, l->dy) - FixedMul(dy, l->dx), d);
m_Type = sc_side;
m_dx = x;
m_dy = y;
m_vdx = m_vdy = 0;
m_Accel = accel;
m_Parts = scrollpos;
if ((m_Control = control) != -1)
m_LastHeight = sectors[control].CenterFloor() + sectors[control].CenterCeiling();
m_Affectee = int(l->sidedef[0] - sides);
sides[m_Affectee].Flags |= WALLF_NOAUTODECALS;
m_Interpolations[0] = m_Interpolations[1] = m_Interpolations[2] = NULL;
if (m_Parts & scw_top)
{
m_Interpolations[0] = sides[m_Affectee].SetInterpolation(side_t::top);
}
if (m_Parts & scw_mid && (sides[m_Affectee].linedef->backsector == NULL ||
!(sides[m_Affectee].linedef->flags&ML_3DMIDTEX)))
{
m_Interpolations[1] = sides[m_Affectee].SetInterpolation(side_t::mid);
}
if (m_Parts & scw_bottom)
{
m_Interpolations[2] = sides[m_Affectee].SetInterpolation(side_t::bottom);
}
}
// Amount (dx,dy) vector linedef is shifted right to get scroll amount
#define SCROLL_SHIFT 5
#define SCROLLTYPE(i) (((i) <= 0) || ((i) & ~7) ? 7 : (i))
// Initialize the scrollers
static void P_SpawnScrollers(void)
{
int i;
line_t *l = lines;
for (i = 0; i < numlines; i++, l++)
{
fixed_t dx; // direction and speed of scrolling
fixed_t dy;
int control = -1, accel = 0; // no control sector or acceleration
int special = l->special;
// Check for undefined parameters that are non-zero and output messages for them.
// We don't report for specials we don't understand.
if (special != 0)
{
int max = LineSpecialsInfo[special] != NULL ? LineSpecialsInfo[special]->map_args : countof(l->args);
for (unsigned arg = max; arg < countof(l->args); ++arg)
{
if (l->args[arg] != 0)
{
Printf("Line %d (type %d:%s), arg %u is %d (should be 0)\n",
i, special, LineSpecialsInfo[special]->name, arg+1, l->args[arg]);
}
}
}
// killough 3/7/98: Types 245-249 are same as 250-254 except that the
// first side's sector's heights cause scrolling when they change, and
// this linedef controls the direction and speed of the scrolling. The
// most complicated linedef since donuts, but powerful :)
//
// killough 3/15/98: Add acceleration. Types 214-218 are the same but
// are accelerative.
// [RH] Assume that it's a scroller and zero the line's special.
l->special = 0;
dx = dy = 0; // Shut up, GCC
if (special == Scroll_Ceiling ||
special == Scroll_Floor ||
special == Scroll_Texture_Model)
{
if (l->args[1] & 3)
{
// if 1, then displacement
// if 2, then accelerative (also if 3)
control = int(l->sidedef[0]->sector - sectors);
if (l->args[1] & 2)
accel = 1;
}
if (special == Scroll_Texture_Model ||
l->args[1] & 4)
{
// The line housing the special controls the
// direction and speed of scrolling.
dx = l->dx >> SCROLL_SHIFT;
dy = l->dy >> SCROLL_SHIFT;
}
else
{
// The speed and direction are parameters to the special.
dx = (l->args[3] - 128) * (FRACUNIT / 32);
dy = (l->args[4] - 128) * (FRACUNIT / 32);
}
}
switch (special)
{
register int s;
case Scroll_Ceiling:
for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;)
new DScroller (DScroller::sc_ceiling, -dx, dy, control, s, accel);
break;
case Scroll_Floor:
if (l->args[2] != 1)
{ // scroll the floor texture
for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;)
new DScroller (DScroller::sc_floor, -dx, dy, control, s, accel);
}
if (l->args[2] > 0)
{ // carry objects on the floor
for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;)
new DScroller (DScroller::sc_carry, dx, dy, control, s, accel);
}
break;
// killough 3/1/98: scroll wall according to linedef
// (same direction and speed as scrolling floors)
case Scroll_Texture_Model:
for (s=-1; (s = P_FindLineFromID (l->args[0],s)) >= 0;)
if (s != i)
new DScroller (dx, dy, lines+s, control, accel);
break;
case Scroll_Texture_Offsets:
// killough 3/2/98: scroll according to sidedef offsets
s = int(lines[i].sidedef[0] - sides);
new DScroller (DScroller::sc_side, -sides[s].GetTextureXOffset(side_t::mid),
sides[s].GetTextureYOffset(side_t::mid), -1, s, accel, SCROLLTYPE(l->args[0]));
break;
case Scroll_Texture_Left:
s = int(lines[i].sidedef[0] - sides);
new DScroller (DScroller::sc_side, l->args[0] * (FRACUNIT/64), 0,
-1, s, accel, SCROLLTYPE(l->args[1]));
break;
case Scroll_Texture_Right:
s = int(lines[i].sidedef[0] - sides);
new DScroller (DScroller::sc_side, l->args[0] * (-FRACUNIT/64), 0,
-1, s, accel, SCROLLTYPE(l->args[1]));
break;
case Scroll_Texture_Up:
s = int(lines[i].sidedef[0] - sides);
new DScroller (DScroller::sc_side, 0, l->args[0] * (FRACUNIT/64),
-1, s, accel, SCROLLTYPE(l->args[1]));
break;
case Scroll_Texture_Down:
s = int(lines[i].sidedef[0] - sides);
new DScroller (DScroller::sc_side, 0, l->args[0] * (-FRACUNIT/64),
-1, s, accel, SCROLLTYPE(l->args[1]));
break;
case Scroll_Texture_Both:
s = int(lines[i].sidedef[0] - sides);
if (l->args[0] == 0) {
dx = (l->args[1] - l->args[2]) * (FRACUNIT/64);
dy = (l->args[4] - l->args[3]) * (FRACUNIT/64);
new DScroller (DScroller::sc_side, dx, dy, -1, s, accel);
}
break;
default:
// [RH] It wasn't a scroller after all, so restore the special.
l->special = special;
break;
}
}
}
// killough 3/7/98 -- end generalized scroll effects
////////////////////////////////////////////////////////////////////////////
//
// FRICTION EFFECTS
//
// phares 3/12/98: Start of friction effects
// As the player moves, friction is applied by decreasing the x and y
// velocity values on each tic. By varying the percentage of decrease,
// we can simulate muddy or icy conditions. In mud, the player slows
// down faster. In ice, the player slows down more slowly.
//
// The amount of friction change is controlled by the length of a linedef
// with type 223. A length < 100 gives you mud. A length > 100 gives you ice.
//
// Also, each sector where these effects are to take place is given a
// new special type _______. Changing the type value at runtime allows
// these effects to be turned on or off.
//
// Sector boundaries present problems. The player should experience these
// friction changes only when his feet are touching the sector floor. At
// sector boundaries where floor height changes, the player can find
// himself still 'in' one sector, but with his feet at the floor level
// of the next sector (steps up or down). To handle this, Thinkers are used
// in icy/muddy sectors. These thinkers examine each object that is touching
// their sectors, looking for players whose feet are at the same level as
// their floors. Players satisfying this condition are given new friction
// values that are applied by the player movement code later.
//
// killough 8/28/98:
//
// Completely redid code, which did not need thinkers, and which put a heavy
// drag on CPU. Friction is now a property of sectors, NOT objects inside
// them. All objects, not just players, are affected by it, if they touch
// the sector's floor. Code simpler and faster, only calling on friction
// calculations when an object needs friction considered, instead of doing
// friction calculations on every sector during every tic.
//
// Although this -might- ruin Boom demo sync involving friction, it's the only
// way, short of code explosion, to fix the original design bug. Fixing the
// design bug in Boom's original friction code, while maintaining demo sync
// under every conceivable circumstance, would double or triple code size, and
// would require maintenance of buggy legacy code which is only useful for old
// demos. Doom demos, which are more important IMO, are not affected by this
// change.
//
// [RH] On the other hand, since I've given up on trying to maintain demo
// sync between versions, these considerations aren't a big deal to me.
//
/////////////////////////////
//
// Initialize the sectors where friction is increased or decreased
static void P_SpawnFriction(void)
{
int i;
line_t *l = lines;
for (i = 0 ; i < numlines ; i++,l++)
{
if (l->special == Sector_SetFriction)
{
int length;
if (l->args[1])
{ // [RH] Allow setting friction amount from parameter
length = l->args[1] <= 200 ? l->args[1] : 200;
}
else
{
length = P_AproxDistance(l->dx,l->dy)>>FRACBITS;
}
P_SetSectorFriction (l->args[0], length, false);
l->special = 0;
}
}
}
void P_SetSectorFriction (int tag, int amount, bool alterFlag)
{
int s;
fixed_t friction, movefactor;
// An amount of 100 should result in a friction of
// ORIG_FRICTION (0xE800)
friction = (0x1EB8*amount)/0x80 + 0xD001;
// killough 8/28/98: prevent odd situations
if (friction > FRACUNIT)
friction = FRACUNIT;
if (friction < 0)
friction = 0;
// The following check might seem odd. At the time of movement,
// the move distance is multiplied by 'friction/0x10000', so a
// higher friction value actually means 'less friction'.
// [RH] Twiddled these values so that velocity on ice (with
// friction 0xf900) is the same as in Heretic/Hexen.
if (friction >= ORIG_FRICTION) // ice
// movefactor = ((0x10092 - friction)*(0x70))/0x158;
movefactor = ((0x10092 - friction) * 1024) / 4352 + 568;
else
movefactor = ((friction - 0xDB34)*(0xA))/0x80;
// killough 8/28/98: prevent odd situations
if (movefactor < 32)
movefactor = 32;
for (s = -1; (s = P_FindSectorFromTag (tag,s)) >= 0; )
{
// killough 8/28/98:
//
// Instead of spawning thinkers, which are slow and expensive,
// modify the sector's own friction values. Friction should be
// a property of sectors, not objects which reside inside them.
// Original code scanned every object in every friction sector
// on every tic, adjusting its friction, putting unnecessary
// drag on CPU. New code adjusts friction of sector only once
// at level startup, and then uses this friction value.
sectors[s].friction = friction;
sectors[s].movefactor = movefactor;
if (alterFlag)
{
// When used inside a script, the sectors' friction flags
// can be enabled and disabled at will.
if (friction == ORIG_FRICTION)
{
sectors[s].special &= ~FRICTION_MASK;
}
else
{
sectors[s].special |= FRICTION_MASK;
}
}
}
}
//
// phares 3/12/98: End of friction effects
//
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
//
// PUSH/PULL EFFECT
//
// phares 3/20/98: Start of push/pull effects
//
// This is where push/pull effects are applied to objects in the sectors.
//
// There are four kinds of push effects
//
// 1) Pushing Away
//
// Pushes you away from a point source defined by the location of an
// MT_PUSH Thing. The force decreases linearly with distance from the
// source. This force crosses sector boundaries and is felt w/in a circle
// whose center is at the MT_PUSH. The force is felt only if the point
// MT_PUSH can see the target object.
//
// 2) Pulling toward
//
// Same as Pushing Away except you're pulled toward an MT_PULL point
// source. This force crosses sector boundaries and is felt w/in a circle
// whose center is at the MT_PULL. The force is felt only if the point
// MT_PULL can see the target object.
//
// 3) Wind
//
// Pushes you in a constant direction. Full force above ground, half
// force on the ground, nothing if you're below it (water).
//
// 4) Current
//
// Pushes you in a constant direction. No force above ground, full
// force if on the ground or below it (water).
//
// The magnitude of the force is controlled by the length of a controlling
// linedef. The force vector for types 3 & 4 is determined by the angle
// of the linedef, and is constant.
//
// For each sector where these effects occur, the sector special type has
// to have the PUSH_MASK bit set. If this bit is turned off by a switch
// at run-time, the effect will not occur. The controlling sector for
// types 1 & 2 is the sector containing the MT_PUSH/MT_PULL Thing.
#define PUSH_FACTOR 7
/////////////////////////////
//
// Add a push thinker to the thinker list
DPusher::DPusher (DPusher::EPusher type, line_t *l, int magnitude, int angle,
AActor *source, int affectee)
{
m_Source = source;
m_Type = type;
if (l)
{
m_Xmag = l->dx>>FRACBITS;
m_Ymag = l->dy>>FRACBITS;
m_Magnitude = P_AproxDistance (m_Xmag, m_Ymag);
}
else
{ // [RH] Allow setting magnitude and angle with parameters
ChangeValues (magnitude, angle);
}
if (source) // point source exist?
{
m_Radius = (m_Magnitude) << (FRACBITS+1); // where force goes to zero
m_X = m_Source->x;
m_Y = m_Source->y;
}
m_Affectee = affectee;
}
/////////////////////////////
//
// T_Pusher looks for all objects that are inside the radius of
// the effect.
//
void DPusher::Tick ()
{
sector_t *sec;
AActor *thing;
msecnode_t *node;
int xspeed,yspeed;
int ht;
if (!var_pushers)
return;
sec = sectors + m_Affectee;
// Be sure the special sector type is still turned on. If so, proceed.
// Else, bail out; the sector type has been changed on us.
if (!(sec->special & PUSH_MASK))
return;
// For constant pushers (wind/current) there are 3 situations:
//
// 1) Affected Thing is above the floor.
//
// Apply the full force if wind, no force if current.
//
// 2) Affected Thing is on the ground.
//
// Apply half force if wind, full force if current.
//
// 3) Affected Thing is below the ground (underwater effect).
//
// Apply no force if wind, full force if current.
//
// Apply the effect to clipped players only for now.
//
// In Phase II, you can apply these effects to Things other than players.
// [RH] No Phase II, but it works with anything having MF2_WINDTHRUST now.
if (m_Type == p_push)
{
// Seek out all pushable things within the force radius of this
// point pusher. Crosses sectors, so use blockmap.
FBlockThingsIterator it(FBoundingBox(m_X, m_Y, m_Radius));
AActor *thing;
while ((thing = it.Next()))
{
// Normal ZDoom is based only on the WINDTHRUST flag, with the noclip cheat as an exemption.
bool pusharound = ((thing->flags2 & MF2_WINDTHRUST) && !(thing->flags & MF_NOCLIP));
// MBF allows any sentient or shootable thing to be affected, but players with a fly cheat aren't.
if (compatflags & COMPATF_MBFMONSTERMOVE)
{
pusharound = ((pusharound || (thing->IsSentient()) || (thing->flags & MF_SHOOTABLE)) // Add categories here
&& (!(thing->player && (thing->flags & (MF_NOGRAVITY))))); // Exclude flying players here
}
if ((pusharound) )
{
int sx = m_X;
int sy = m_Y;
int dist = P_AproxDistance (thing->x - sx,thing->y - sy);
int speed = (m_Magnitude - ((dist>>FRACBITS)>>1))<<(FRACBITS-PUSH_FACTOR-1);
// If speed <= 0, you're outside the effective radius. You also have
// to be able to see the push/pull source point.
if ((speed > 0) && (P_CheckSight (thing, m_Source, 1)))
{
angle_t pushangle = R_PointToAngle2 (thing->x, thing->y, sx, sy);
if (m_Source->GetClass()->TypeName == NAME_PointPusher)
pushangle += ANG180; // away
pushangle >>= ANGLETOFINESHIFT;
thing->velx += FixedMul (speed, finecosine[pushangle]);
thing->vely += FixedMul (speed, finesine[pushangle]);
}
}
}
return;
}
// constant pushers p_wind and p_current
node = sec->touching_thinglist; // things touching this sector
for ( ; node ; node = node->m_snext)
{
thing = node->m_thing;
if (!(thing->flags2 & MF2_WINDTHRUST) || (thing->flags & MF_NOCLIP))
continue;
sector_t *hsec = sec->GetHeightSec();
if (m_Type == p_wind)
{
if (hsec == NULL)
{ // NOT special water sector
if (thing->z > thing->floorz) // above ground
{
xspeed = m_Xmag; // full force
yspeed = m_Ymag;
}
else // on ground
{
xspeed = (m_Xmag)>>1; // half force
yspeed = (m_Ymag)>>1;
}
}
else // special water sector
{
ht = hsec->floorplane.ZatPoint (thing->x, thing->y);
if (thing->z > ht) // above ground
{
xspeed = m_Xmag; // full force
yspeed = m_Ymag;
}
else if (thing->player->viewz < ht) // underwater
{
xspeed = yspeed = 0; // no force
}
else // wading in water
{
xspeed = (m_Xmag)>>1; // half force
yspeed = (m_Ymag)>>1;
}
}
}
else // p_current
{
const secplane_t *floor;
if (hsec == NULL)
{ // NOT special water sector
floor = &sec->floorplane;
}
else
{ // special water sector
floor = &hsec->floorplane;
}
if (thing->z > floor->ZatPoint (thing->x, thing->y))
{ // above ground
xspeed = yspeed = 0; // no force
}
else
{ // on ground/underwater
xspeed = m_Xmag; // full force
yspeed = m_Ymag;
}
}
thing->velx += xspeed<<(FRACBITS-PUSH_FACTOR);
thing->vely += yspeed<<(FRACBITS-PUSH_FACTOR);
}
}
/////////////////////////////
//
// P_GetPushThing() returns a pointer to an MT_PUSH or MT_PULL thing,
// NULL otherwise.
AActor *P_GetPushThing (int s)
{
AActor* thing;
sector_t* sec;
sec = sectors + s;
thing = sec->thinglist;
while (thing &&
thing->GetClass()->TypeName != NAME_PointPusher &&
thing->GetClass()->TypeName != NAME_PointPuller)
{
thing = thing->snext;
}
return thing;
}
/////////////////////////////
//
// Initialize the sectors where pushers are present
//
static void P_SpawnPushers ()
{
int i;
line_t *l = lines;
register int s;
for (i = 0; i < numlines; i++, l++)
{
switch (l->special)
{
case Sector_SetWind: // wind
for (s = -1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0 ; )
new DPusher (DPusher::p_wind, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
l->special = 0;
break;
case Sector_SetCurrent: // current
for (s = -1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0 ; )
new DPusher (DPusher::p_current, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
l->special = 0;
break;
case PointPush_SetForce: // push/pull
if (l->args[0]) { // [RH] Find thing by sector
for (s = -1; (s = P_FindSectorFromTag (l->args[0], s)) >= 0 ; )
{
AActor *thing = P_GetPushThing (s);
if (thing) { // No MT_P* means no effect
// [RH] Allow narrowing it down by tid
if (!l->args[1] || l->args[1] == thing->tid)
new DPusher (DPusher::p_push, l->args[3] ? l : NULL, l->args[2],
0, thing, s);
}
}
} else { // [RH] Find thing by tid
AActor *thing;
FActorIterator iterator (l->args[1]);
while ( (thing = iterator.Next ()) )
{
if (thing->GetClass()->TypeName == NAME_PointPusher ||
thing->GetClass()->TypeName == NAME_PointPuller)
{
new DPusher (DPusher::p_push, l->args[3] ? l : NULL, l->args[2],
0, thing, int(thing->Sector - sectors));
}
}
}
l->special = 0;
break;
}
}
}
//
// phares 3/20/98: End of Pusher effects
//
////////////////////////////////////////////////////////////////////////////
void sector_t::AdjustFloorClip () const
{
msecnode_t *node;
for (node = touching_thinglist; node; node = node->m_snext)
{
if (node->m_thing->flags2 & MF2_FLOORCLIP)
{
node->m_thing->AdjustFloorClip();
}
}
}