mirror of
https://github.com/ZDoom/gzdoom.git
synced 2025-04-25 20:20:57 +00:00
2251 lines
59 KiB
C
2251 lines
59 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 "m_alloc.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "doomdef.h"
|
|
#include "doomstat.h"
|
|
#include "dstrings.h"
|
|
|
|
#include "i_system.h"
|
|
#include "z_zone.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 "g_game.h"
|
|
|
|
#include "s_sound.h"
|
|
|
|
// State.
|
|
#include "r_state.h"
|
|
|
|
#include "c_consol.h"
|
|
|
|
// [RH] Needed for sky scrolling
|
|
#include "r_sky.h"
|
|
|
|
//
|
|
// Animating textures and planes
|
|
// There is another anim_t used in wi_stuff, unrelated.
|
|
//
|
|
// [RH] Expanded to work with a Hexen ANIMDEFS lump
|
|
//
|
|
#define MAX_ANIM_FRAMES 32
|
|
|
|
typedef struct
|
|
{
|
|
short basepic;
|
|
short numframes;
|
|
byte istexture;
|
|
byte uniqueframes;
|
|
byte countdown;
|
|
byte curframe;
|
|
byte speedmin[MAX_ANIM_FRAMES];
|
|
byte speedmax[MAX_ANIM_FRAMES];
|
|
short framepic[MAX_ANIM_FRAMES];
|
|
} anim_t;
|
|
|
|
|
|
|
|
#define MAXANIMS 32 // Really just a starting point
|
|
|
|
static anim_t* lastanim;
|
|
static anim_t* anims;
|
|
static size_t maxanims;
|
|
|
|
|
|
// Factor to scale scrolling effect into mobj-carrying properties = 3/32.
|
|
// (This is so scrolling floors and objects on them can move at same speed.)
|
|
#define CARRYFACTOR ((fixed_t)(FRACUNIT*.09375))
|
|
|
|
static void Add_Scroller(int type, fixed_t dx, fixed_t dy,
|
|
int control, int affectee, int accel);
|
|
// killough 3/7/98: Initialize generalized scrolling
|
|
static void P_SpawnScrollers(void);
|
|
|
|
static void P_SpawnFriction(void); // phares 3/16/98
|
|
static void P_SpawnPushers(void); // phares 3/20/98
|
|
|
|
//
|
|
// Animating line specials
|
|
//
|
|
//#define MAXLINEANIMS 64
|
|
|
|
//extern short numlinespecials;
|
|
//extern line_t* linespeciallist[MAXLINEANIMS];
|
|
|
|
//
|
|
// [RH] P_InitAnimDefs
|
|
//
|
|
// This uses a Hexen ANIMDEFS lump to define the animation sequences
|
|
//
|
|
static void P_InitAnimDefs (void)
|
|
{
|
|
int lump = W_CheckNumForName ("ANIMDEFS");
|
|
enum {
|
|
limbo,
|
|
newflat,
|
|
readingflat,
|
|
newtexture,
|
|
readingtexture
|
|
} state = limbo, newstate = limbo;
|
|
int frame, min, max;
|
|
char name[9];
|
|
|
|
if (lump >= 0) {
|
|
byte *animdefs = W_CacheLumpNum (lump, PU_CACHE);
|
|
byte *data;
|
|
|
|
while ( (data = COM_Parse (animdefs)) ) {
|
|
if (com_token[0] == ';') {
|
|
// Handle comments from Hexen ANIMDEFS lumps
|
|
while (*animdefs && *animdefs != ';')
|
|
animdefs++;
|
|
while (*animdefs && *animdefs != '\n')
|
|
animdefs++;
|
|
continue;
|
|
}
|
|
animdefs = data;
|
|
|
|
if (!stricmp (com_token, "flat")) {
|
|
newstate = newflat;
|
|
} else if (!stricmp (com_token, "texture")) {
|
|
newstate = newtexture;
|
|
} else if (!stricmp (com_token, "pic")) {
|
|
animdefs = COM_Parse (animdefs);
|
|
frame = atoi (com_token);
|
|
animdefs = COM_Parse (animdefs);
|
|
if (!stricmp (com_token, "tics")) {
|
|
animdefs = COM_Parse (animdefs);
|
|
min = max = atoi (com_token);
|
|
} else if (!stricmp (com_token, "rand")) {
|
|
animdefs = COM_Parse (animdefs);
|
|
min = atoi (com_token);
|
|
animdefs = COM_Parse (animdefs);
|
|
max = atoi (com_token);
|
|
}
|
|
}
|
|
|
|
if (newstate == newtexture || newstate == newflat) {
|
|
if (state != limbo) {
|
|
if (lastanim->numframes < 2)
|
|
I_FatalError ("P_InitAnimDefs: %s needs at least 2 frames", name);
|
|
|
|
lastanim->countdown = lastanim->speedmin[0];
|
|
lastanim++;
|
|
}
|
|
|
|
// 1/11/98 killough -- removed limit by array-doubling
|
|
if (lastanim >= anims + maxanims)
|
|
{
|
|
size_t newmax = maxanims ? maxanims*2 : MAXANIMS;
|
|
anims = Realloc(anims, newmax*sizeof(*anims)); // killough
|
|
lastanim = anims + maxanims;
|
|
maxanims = newmax;
|
|
}
|
|
|
|
lastanim->uniqueframes = 1;
|
|
lastanim->curframe = 0;
|
|
lastanim->numframes = 0;
|
|
lastanim->istexture = (newstate == newtexture);
|
|
memset (lastanim->speedmin, 1, MAX_ANIM_FRAMES * sizeof(*lastanim->speedmin));
|
|
memset (lastanim->speedmax, 1, MAX_ANIM_FRAMES * sizeof(*lastanim->speedmax));
|
|
|
|
animdefs = COM_Parse (animdefs);
|
|
|
|
if (lastanim->istexture)
|
|
lastanim->basepic = R_TextureNumForName (com_token);
|
|
else
|
|
lastanim->basepic = R_FlatNumForName (com_token);
|
|
|
|
strncpy (name, com_token, 8);
|
|
name[8] = 0;
|
|
|
|
state = (newstate == newflat) ? readingflat : readingtexture;
|
|
newstate = limbo;
|
|
} else if (state == readingflat || state == readingtexture) {
|
|
if (lastanim->numframes < MAX_ANIM_FRAMES) {
|
|
lastanim->speedmin[lastanim->numframes] = min;
|
|
lastanim->speedmax[lastanim->numframes] = max;
|
|
lastanim->framepic[lastanim->numframes] = frame + lastanim->basepic - 1;
|
|
lastanim->numframes++;
|
|
}
|
|
}
|
|
}
|
|
if (state == readingflat || state == readingtexture) {
|
|
lastanim->countdown = lastanim->speedmin[0];
|
|
lastanim++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// P_InitPicAnims
|
|
//
|
|
// Load the table of animation definitions, checking for existence of
|
|
// the start and end of each frame. If the start doesn't exist the sequence
|
|
// is skipped, if the last doesn't exist, BOOM exits.
|
|
//
|
|
// Wall/Flat animation sequences, defined by name of first and last frame,
|
|
// The full animation sequence is given using all lumps between the start
|
|
// and end entry, in the order found in the WAD file.
|
|
//
|
|
// This routine modified to read its data from a predefined lump or
|
|
// PWAD lump called ANIMATED rather than a static table in this module to
|
|
// allow wad designers to insert or modify animation sequences.
|
|
//
|
|
// Lump format is an array of byte packed animdef_t structures, terminated
|
|
// by a structure with istexture == -1. The lump can be generated from a
|
|
// text source file using SWANTBLS.EXE, distributed with the BOOM utils.
|
|
// The standard list of switches and animations is contained in the example
|
|
// source text file DEFSWANI.DAT also in the BOOM util distribution.
|
|
//
|
|
// [RH] Rewritten to support BOOM ANIMATED lump but also make absolutely
|
|
// no assumptions about how the compiler packs the animdefs array.
|
|
//
|
|
void P_InitPicAnims (void)
|
|
{
|
|
byte *animdefs, *anim_p;
|
|
|
|
// [RH] Load an ANIMDEFS lump first
|
|
P_InitAnimDefs ();
|
|
|
|
if (W_CheckNumForName ("ANIMATED") == -1)
|
|
return;
|
|
|
|
animdefs = W_CacheLumpName ("ANIMATED", PU_STATIC);
|
|
|
|
// Init animation
|
|
|
|
for (anim_p = animdefs; *anim_p != 255; anim_p += 23)
|
|
{
|
|
// 1/11/98 killough -- removed limit by array-doubling
|
|
if (lastanim >= anims + maxanims)
|
|
{
|
|
size_t newmax = maxanims ? maxanims*2 : MAXANIMS;
|
|
anims = Realloc(anims, newmax*sizeof(*anims)); // killough
|
|
lastanim = anims + maxanims;
|
|
maxanims = newmax;
|
|
}
|
|
|
|
if (*anim_p /* .istexture */)
|
|
{
|
|
// different episode ?
|
|
if (R_CheckTextureNumForName (anim_p + 10 /* .startname */) == -1)
|
|
continue;
|
|
|
|
lastanim->basepic = R_TextureNumForName (anim_p + 10 /* .startname */);
|
|
lastanim->numframes = R_TextureNumForName (anim_p + 1 /* .endname */)
|
|
- lastanim->basepic + 1;
|
|
}
|
|
else
|
|
{
|
|
if ((W_CheckNumForName)(anim_p + 10 /* .startname */, ns_flats) == -1)
|
|
continue;
|
|
|
|
lastanim->basepic = R_FlatNumForName (anim_p + 10 /* .startname */);
|
|
lastanim->numframes = R_FlatNumForName (anim_p + 1 /* .endname */)
|
|
- lastanim->basepic + 1;
|
|
}
|
|
|
|
lastanim->istexture = *anim_p /* .istexture */;
|
|
lastanim->uniqueframes = 0;
|
|
lastanim->curframe = 0;
|
|
|
|
if (lastanim->numframes < 2)
|
|
I_FatalError ("P_InitPicAnims: bad cycle from %s to %s",
|
|
anim_p + 10 /* .startname */,
|
|
anim_p + 1 /* .endname */);
|
|
|
|
lastanim->speedmin[0] = lastanim->speedmax[0] = lastanim->countdown =
|
|
/* .speed */
|
|
(anim_p[19] << 0) |
|
|
(anim_p[20] << 8) |
|
|
(anim_p[21] << 16) |
|
|
(anim_p[22] << 24);
|
|
|
|
lastanim->countdown--;
|
|
|
|
lastanim++;
|
|
}
|
|
Z_Free (animdefs);
|
|
}
|
|
|
|
|
|
// [RH] Check dmflags for noexit and respond accordingly
|
|
BOOL CheckIfExitIsGood (mobj_t *self)
|
|
{
|
|
if (self == NULL)
|
|
return false;
|
|
|
|
if (deathmatch->value && dmflags & DF_NO_EXIT && self) {
|
|
while (self->health > 0)
|
|
P_DamageMobj (self, self, self, 10000, MOD_EXIT);
|
|
return false;
|
|
}
|
|
if (deathmatch->value)
|
|
Printf (PRINT_HIGH, "%s exited the level.\n", self->player->userinfo.netname);
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// UTILITIES
|
|
//
|
|
|
|
|
|
|
|
//
|
|
// getSide()
|
|
// Will return a side_t*
|
|
// given the number of the current sector,
|
|
// the line number, and the side (0/1) that you want.
|
|
//
|
|
side_t *getSide (int currentSector, int line, int side)
|
|
{
|
|
return &sides[ (sectors[currentSector].lines[line])->sidenum[side] ];
|
|
}
|
|
|
|
|
|
//
|
|
// getSector()
|
|
// Will return a sector_t*
|
|
// given the number of the current sector,
|
|
// the line number and the side (0/1) that you want.
|
|
//
|
|
sector_t *getSector (int currentSector, int line, int side)
|
|
{
|
|
return sides[ (sectors[currentSector].lines[line])->sidenum[side] ].sector;
|
|
}
|
|
|
|
|
|
//
|
|
// twoSided()
|
|
// Given the sector number and the line number,
|
|
// it will tell you whether the line is two-sided or not.
|
|
//
|
|
int twoSided (int sector, int line)
|
|
{
|
|
return (sectors[sector].lines[line])->flags & ML_TWOSIDED;
|
|
}
|
|
|
|
//
|
|
// getNextSector()
|
|
// Return sector_t * of sector next to current.
|
|
// NULL if not two-sided line
|
|
//
|
|
sector_t *getNextSector (line_t *line, sector_t *sec)
|
|
{
|
|
if (!(line->flags & ML_TWOSIDED))
|
|
return NULL;
|
|
|
|
return (line->frontsector == sec) ? line->backsector : line->frontsector;
|
|
|
|
return line->frontsector;
|
|
}
|
|
|
|
// [RH]
|
|
// P_NextSpecialSector()
|
|
//
|
|
// Returns the next special sector attached to this sector
|
|
// with a certain special.
|
|
sector_t *P_NextSpecialSector (sector_t *sec, int type, sector_t *nogood)
|
|
{
|
|
sector_t *tsec;
|
|
int i;
|
|
|
|
for (i = 0; i < sec->linecount; i++) {
|
|
line_t *ln = sec->lines[i];
|
|
|
|
if (!(ln->flags & ML_TWOSIDED) ||
|
|
!(tsec = ln->frontsector))
|
|
continue;
|
|
|
|
if (sec == tsec) {
|
|
tsec = ln->backsector;
|
|
if (sec == tsec)
|
|
continue;
|
|
}
|
|
|
|
if (tsec == nogood)
|
|
continue;
|
|
|
|
if ((tsec->special & 0x00ff) == type) {
|
|
return tsec;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// P_SectorActive()
|
|
//
|
|
// Passed a linedef special class (floor, ceiling, lighting) and a sector
|
|
// returns whether the sector is already busy with a linedef special of the
|
|
// same class. If old demo compatibility true, all linedef special classes
|
|
// are the same.
|
|
//
|
|
// jff 2/23/98 added to prevent old demos from
|
|
// succeeding in starting multiple specials on one sector
|
|
//
|
|
int P_SectorActive(special_e t,sector_t *sec)
|
|
{
|
|
switch (t) // return whether thinker of same type is active
|
|
{
|
|
case floor_special:
|
|
return (int)sec->floordata;
|
|
case ceiling_special:
|
|
return (int)sec->ceilingdata;
|
|
case lighting_special:
|
|
return (int)sec->lightingdata;
|
|
}
|
|
return 1; // don't know which special, must be active, shouldn't be here
|
|
}
|
|
|
|
|
|
//
|
|
// P_FindLowestFloorSurrounding()
|
|
// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS
|
|
//
|
|
fixed_t P_FindLowestFloorSurrounding(sector_t* sec)
|
|
{
|
|
int i;
|
|
line_t* check;
|
|
sector_t* other;
|
|
fixed_t floor = sec->floorheight;
|
|
|
|
for (i=0 ;i < sec->linecount ; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check,sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->floorheight < floor)
|
|
floor = other->floorheight;
|
|
}
|
|
return floor;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// P_FindHighestFloorSurrounding()
|
|
// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS
|
|
//
|
|
fixed_t P_FindHighestFloorSurrounding(sector_t *sec)
|
|
{
|
|
int i;
|
|
line_t* check;
|
|
sector_t* other;
|
|
fixed_t floor = MININT;
|
|
|
|
for (i=0 ;i < sec->linecount ; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check,sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->floorheight > floor)
|
|
floor = other->floorheight;
|
|
}
|
|
return floor;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// P_FindNextHighestFloor()
|
|
//
|
|
// Passed a sector and a floor height, returns the fixed point value
|
|
// of the smallest floor height in a surrounding sector larger than
|
|
// the floor height passed. If no such height exists the floorheight
|
|
// passed is returned.
|
|
//
|
|
// Rewritten by Lee Killough to avoid fixed array and to be faster
|
|
//
|
|
fixed_t P_FindNextHighestFloor(sector_t *sec, int currentheight)
|
|
{
|
|
sector_t *other;
|
|
int i;
|
|
|
|
for (i = 0; i < sec->linecount; i++) {
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->floorheight > currentheight) {
|
|
int height = other->floorheight;
|
|
|
|
while (++i < sec->linecount) {
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->floorheight < height &&
|
|
other->floorheight > currentheight) {
|
|
height = other->floorheight;
|
|
}
|
|
}
|
|
return height;
|
|
}
|
|
}
|
|
return currentheight;
|
|
}
|
|
|
|
|
|
//
|
|
// P_FindNextLowestFloor()
|
|
//
|
|
// Passed a sector and a floor height, returns the fixed point value
|
|
// of the largest floor height in a surrounding sector smaller than
|
|
// the floor height passed. If no such height exists the floorheight
|
|
// passed is returned.
|
|
//
|
|
// jff 02/03/98 Twiddled Lee's P_FindNextHighestFloor to make this
|
|
//
|
|
fixed_t P_FindNextLowestFloor(sector_t *sec, int currentheight)
|
|
{
|
|
sector_t *other;
|
|
int i;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->floorheight < currentheight) {
|
|
int height = other->floorheight;
|
|
|
|
while (++i < sec->linecount) {
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->floorheight > height &&
|
|
other->floorheight < currentheight) {
|
|
height = other->floorheight;
|
|
}
|
|
}
|
|
return height;
|
|
}
|
|
return currentheight;
|
|
}
|
|
|
|
//
|
|
// P_FindNextLowestCeiling()
|
|
//
|
|
// Passed a sector and a ceiling height, returns the fixed point value
|
|
// of the largest ceiling height in a surrounding sector smaller than
|
|
// the ceiling height passed. If no such height exists the ceiling height
|
|
// passed is returned.
|
|
//
|
|
// jff 02/03/98 Twiddled Lee's P_FindNextHighestFloor to make this
|
|
//
|
|
fixed_t P_FindNextLowestCeiling (sector_t *sec, int currentheight)
|
|
{
|
|
sector_t *other;
|
|
int i;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->ceilingheight < currentheight)
|
|
{
|
|
int height = other->ceilingheight;
|
|
while (++i < sec->linecount)
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->ceilingheight > height &&
|
|
other->ceilingheight < currentheight)
|
|
height = other->ceilingheight;
|
|
return height;
|
|
}
|
|
return currentheight;
|
|
}
|
|
|
|
|
|
//
|
|
// P_FindNextHighestCeiling()
|
|
//
|
|
// Passed a sector and a ceiling height, returns the fixed point value
|
|
// of the smallest ceiling height in a surrounding sector larger than
|
|
// the ceiling height passed. If no such height exists the ceiling height
|
|
// passed is returned.
|
|
//
|
|
// jff 02/03/98 Twiddled Lee's P_FindNextHighestFloor to make this
|
|
//
|
|
fixed_t P_FindNextHighestCeiling (sector_t *sec, int currentheight)
|
|
{
|
|
sector_t *other;
|
|
int i;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->ceilingheight > currentheight)
|
|
{
|
|
int height = other->ceilingheight;
|
|
while (++i < sec->linecount)
|
|
if ((other = getNextSector(sec->lines[i],sec)) &&
|
|
other->ceilingheight < height &&
|
|
other->ceilingheight > currentheight)
|
|
height = other->ceilingheight;
|
|
return height;
|
|
}
|
|
return currentheight;
|
|
}
|
|
|
|
//
|
|
// FIND LOWEST CEILING IN THE SURROUNDING SECTORS
|
|
//
|
|
fixed_t P_FindLowestCeilingSurrounding (sector_t *sec)
|
|
{
|
|
int i;
|
|
line_t* check;
|
|
sector_t* other;
|
|
fixed_t height = MAXINT;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check,sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->ceilingheight < height)
|
|
height = other->ceilingheight;
|
|
}
|
|
return height;
|
|
}
|
|
|
|
|
|
//
|
|
// FIND HIGHEST CEILING IN THE SURROUNDING SECTORS
|
|
//
|
|
fixed_t P_FindHighestCeilingSurrounding (sector_t *sec)
|
|
{
|
|
int i;
|
|
line_t* check;
|
|
sector_t* other;
|
|
fixed_t height = MININT;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
other = getNextSector(check,sec);
|
|
|
|
if (!other)
|
|
continue;
|
|
|
|
if (other->ceilingheight > height)
|
|
height = other->ceilingheight;
|
|
}
|
|
return height;
|
|
}
|
|
|
|
|
|
//
|
|
// P_FindShortestTextureAround()
|
|
//
|
|
// Passed a sector number, returns the shortest lower texture on a
|
|
// linedef bounding the sector.
|
|
//
|
|
// jff 02/03/98 Add routine to find shortest lower texture
|
|
//
|
|
fixed_t P_FindShortestTextureAround (int secnum)
|
|
{
|
|
int minsize = MAXINT;
|
|
side_t *side;
|
|
int i;
|
|
sector_t *sec = §ors[secnum];
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
if (twoSided(secnum, i))
|
|
{
|
|
side = getSide(secnum,i,0);
|
|
if (side->bottomtexture >= 0)
|
|
if (textureheight[side->bottomtexture] < minsize)
|
|
minsize = textureheight[side->bottomtexture];
|
|
side = getSide(secnum,i,1);
|
|
if (side->bottomtexture >= 0)
|
|
if (textureheight[side->bottomtexture] < minsize)
|
|
minsize = textureheight[side->bottomtexture];
|
|
}
|
|
}
|
|
return minsize;
|
|
}
|
|
|
|
|
|
//
|
|
// P_FindShortestUpperAround()
|
|
//
|
|
// Passed a sector number, returns the shortest upper texture on a
|
|
// linedef bounding the sector.
|
|
//
|
|
// Note: If no upper texture exists 32000*FRACUNIT is returned.
|
|
// but if compatibility then MAXINT is returned
|
|
//
|
|
// jff 03/20/98 Add routine to find shortest upper texture
|
|
//
|
|
fixed_t P_FindShortestUpperAround (int secnum)
|
|
{
|
|
int minsize = MAXINT;
|
|
side_t *side;
|
|
int i;
|
|
sector_t *sec = §ors[secnum];
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
if (twoSided(secnum, i))
|
|
{
|
|
side = getSide(secnum,i,0);
|
|
if (side->toptexture >= 0)
|
|
if (textureheight[side->toptexture] < minsize)
|
|
minsize = textureheight[side->toptexture];
|
|
side = getSide(secnum,i,1);
|
|
if (side->toptexture >= 0)
|
|
if (textureheight[side->toptexture] < minsize)
|
|
minsize = textureheight[side->toptexture];
|
|
}
|
|
}
|
|
return minsize;
|
|
}
|
|
|
|
|
|
//
|
|
// P_FindModelFloorSector()
|
|
//
|
|
// Passed a floor height and a sector number, return a pointer to a
|
|
// a sector with that floor height across the lowest numbered two sided
|
|
// line surrounding the sector.
|
|
//
|
|
// Note: If no sector at that height bounds the sector passed, return NULL
|
|
//
|
|
// jff 02/03/98 Add routine to find numeric model floor
|
|
// around a sector specified by sector number
|
|
// jff 3/14/98 change first parameter to plain height to allow call
|
|
// from routine not using floormove_t
|
|
//
|
|
sector_t *P_FindModelFloorSector (fixed_t floordestheight, int secnum)
|
|
{
|
|
int i;
|
|
sector_t *sec = §ors[secnum];
|
|
int linecount;
|
|
|
|
//jff 5/23/98 don't disturb sec->linecount while searching
|
|
// but allow early exit in old demos
|
|
linecount = sec->linecount;
|
|
for (i = 0; i < linecount; i++)
|
|
{
|
|
if (twoSided(secnum, i))
|
|
{
|
|
if (getSide (secnum,i,0)->sector-sectors == secnum)
|
|
sec = getSector(secnum,i,1);
|
|
else
|
|
sec = getSector(secnum,i,0);
|
|
|
|
if (sec->floorheight == floordestheight)
|
|
return sec;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// P_FindModelCeilingSector()
|
|
//
|
|
// Passed a ceiling height and a sector number, return a pointer to a
|
|
// a sector with that ceiling height across the lowest numbered two sided
|
|
// line surrounding the sector.
|
|
//
|
|
// Note: If no sector at that height bounds the sector passed, return NULL
|
|
//
|
|
// jff 02/03/98 Add routine to find numeric model ceiling
|
|
// around a sector specified by sector number
|
|
// used only from generalized ceiling types
|
|
// jff 3/14/98 change first parameter to plain height to allow call
|
|
// from routine not using ceiling_t
|
|
//
|
|
sector_t *P_FindModelCeilingSector (fixed_t ceildestheight, int secnum)
|
|
{
|
|
int i;
|
|
sector_t *sec = §ors[secnum];
|
|
int linecount;
|
|
|
|
//jff 5/23/98 don't disturb sec->linecount while searching
|
|
linecount = sec->linecount;
|
|
for (i = 0; i < linecount; i++)
|
|
{
|
|
if (twoSided (secnum, i))
|
|
{
|
|
if (getSide (secnum,i,0)->sector-sectors == secnum)
|
|
sec = getSector (secnum,i,1);
|
|
else
|
|
sec = getSector (secnum,i,0);
|
|
|
|
if (sec->ceilingheight == ceildestheight)
|
|
return sec;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
// Hash the sector tags across the sectors and linedefs.
|
|
static void P_InitTagLists (void)
|
|
{
|
|
register int i;
|
|
|
|
for (i=numsectors; --i>=0; ) // Initially make all slots empty.
|
|
sectors[i].firsttag = -1;
|
|
for (i=numsectors; --i>=0; ) // Proceed from last to first sector
|
|
{ // so that lower sectors appear first
|
|
int j = (unsigned) sectors[i].tag % (unsigned) numsectors; // Hash func
|
|
sectors[i].nexttag = sectors[j].firsttag; // Prepend sector to chain
|
|
sectors[j].firsttag = i;
|
|
}
|
|
|
|
// killough 4/17/98: same thing, only for linedefs
|
|
|
|
for (i=numlines; --i>=0; ) // Initially make all slots empty.
|
|
lines[i].firstid = -1;
|
|
for (i=numlines; --i>=0; ) // Proceed from last to first linedef
|
|
{ // so that lower linedefs appear first
|
|
int j = (unsigned) lines[i].id % (unsigned) numlines; // Hash func
|
|
lines[i].nextid = lines[j].firstid; // Prepend linedef to chain
|
|
lines[j].firstid = i;
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Find minimum light from an adjacent sector
|
|
//
|
|
int P_FindMinSurroundingLight (sector_t *sector, int max)
|
|
{
|
|
int i;
|
|
int min;
|
|
line_t* line;
|
|
sector_t* check;
|
|
|
|
min = max;
|
|
for (i=0 ; i < sector->linecount ; i++)
|
|
{
|
|
line = sector->lines[i];
|
|
check = getNextSector(line,sector);
|
|
|
|
if (!check)
|
|
continue;
|
|
|
|
if (check->lightlevel < min)
|
|
min = check->lightlevel;
|
|
}
|
|
return min;
|
|
}
|
|
|
|
// [RH] P_CheckKeys
|
|
//
|
|
// Returns true if the player has the desired key,
|
|
// false otherwise.
|
|
|
|
BOOL P_CheckKeys (player_t *p, keytype_t lock, BOOL remote)
|
|
{
|
|
int keytrysound;
|
|
|
|
if ((lock & 0x7f) == NoKey)
|
|
return true;
|
|
|
|
if (!p)
|
|
return false;
|
|
|
|
{
|
|
char *msg;
|
|
BOOL bc, rc, yc, bs, rs, ys;
|
|
BOOL equiv = lock & 0x80;
|
|
|
|
lock = lock & 0x7f;
|
|
|
|
bc = p->cards[it_bluecard];
|
|
rc = p->cards[it_redcard];
|
|
yc = p->cards[it_yellowcard];
|
|
bs = p->cards[it_blueskull];
|
|
rs = p->cards[it_redskull];
|
|
ys = p->cards[it_yellowskull];
|
|
|
|
if (equiv) {
|
|
bc = bs = (bc || bs);
|
|
rc = rs = (rc || rs);
|
|
yc = yc = (yc || ys);
|
|
}
|
|
|
|
switch (lock) {
|
|
default: // Unknown key; assume we have it
|
|
return true;
|
|
|
|
case AnyKey:
|
|
if (bc || bs || rc || rs || yc || ys)
|
|
return true;
|
|
msg = PD_ANY;
|
|
break;
|
|
|
|
case AllKeys:
|
|
if (bc && bs && rc && rs && yc && ys)
|
|
return true;
|
|
msg = equiv ? PD_ALL3 : PD_ALL6;
|
|
break;
|
|
|
|
case RCard:
|
|
if (rc)
|
|
return true;
|
|
msg = equiv ? (remote ? PD_REDO : PD_REDK) : PD_REDC;
|
|
break;
|
|
|
|
case BCard:
|
|
if (bc)
|
|
return true;
|
|
msg = equiv ? (remote ? PD_BLUEO : PD_BLUEK) : PD_BLUEC;
|
|
break;
|
|
|
|
case YCard:
|
|
if (yc)
|
|
return true;
|
|
msg = equiv ? (remote ? PD_YELLOWO : PD_YELLOWK) : PD_YELLOWC;
|
|
break;
|
|
|
|
case RSkull:
|
|
if (rs)
|
|
return true;
|
|
msg = equiv ? (remote ? PD_REDO : PD_REDK) : PD_REDS;
|
|
break;
|
|
|
|
case BSkull:
|
|
if (bs)
|
|
return true;
|
|
msg = equiv ? (remote ? PD_BLUEO : PD_BLUEK) : PD_BLUES;
|
|
break;
|
|
|
|
case YSkull:
|
|
if (ys)
|
|
return true;
|
|
msg = equiv ? (remote ? PD_YELLOWO : PD_YELLOWK) : PD_YELLOWS;
|
|
break;
|
|
}
|
|
|
|
// If we get here, we don't have the right key,
|
|
// so print an appropriate message and grunt.
|
|
C_MidPrint (msg);
|
|
keytrysound = S_FindSound ("misc/keytry");
|
|
if (keytrysound > -1)
|
|
S_Sound (p->mo, CHAN_VOICE, "misc/keytry", 1, ATTN_NORM);
|
|
else
|
|
S_Sound (p->mo, CHAN_VOICE, "*grunt1", 1, ATTN_NORM);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
//
|
|
// P_ActivateLine
|
|
//
|
|
//============================================================================
|
|
|
|
BOOL P_ActivateLine (line_t *line, mobj_t *mo, int side, int activationType)
|
|
{
|
|
int lineActivation;
|
|
BOOL repeat;
|
|
BOOL buttonSuccess;
|
|
|
|
lineActivation = GET_SPAC(line->flags);
|
|
if (lineActivation == SPAC_USETHROUGH)
|
|
lineActivation = SPAC_USE;
|
|
if (lineActivation != activationType &&
|
|
!(activationType == SPAC_MCROSS && lineActivation == SPAC_CROSS))
|
|
{
|
|
return false;
|
|
}
|
|
if (!mo->player && !(mo->flags & MF_MISSILE))
|
|
{
|
|
if ((activationType == SPAC_USE || activationType == SPAC_PUSH)
|
|
&& (line->flags & ML_SECRET))
|
|
return false; // never open secret doors
|
|
if (!(line->flags & ML_MONSTERSCANACTIVATE))
|
|
{ // [RH] monsters' ability to activate this line depends on its type
|
|
// (in hexen, only MCROSS lines could be activated by monsters)
|
|
BOOL noway = true;
|
|
|
|
switch (lineActivation) {
|
|
case SPAC_IMPACT:
|
|
case SPAC_PCROSS:
|
|
// shouldn't really be here if not a missile
|
|
case SPAC_MCROSS:
|
|
noway = false;
|
|
break;
|
|
case SPAC_CROSS:
|
|
switch (line->special) {
|
|
case Teleport:
|
|
case Teleport_NoFog:
|
|
case Teleport_Line:
|
|
case Door_Raise:
|
|
case Plat_DownWaitUpStayLip:
|
|
case Plat_DownWaitUpStay:
|
|
noway = false;
|
|
}
|
|
break;
|
|
case SPAC_USE:
|
|
case SPAC_PUSH:
|
|
switch (line->special) {
|
|
case Door_Raise:
|
|
if (line->args[0] == 0)
|
|
noway = false;
|
|
break;
|
|
case Teleport:
|
|
case Teleport_NoFog:
|
|
noway = false;
|
|
}
|
|
break;
|
|
}
|
|
if (noway)
|
|
return false;
|
|
}
|
|
}
|
|
repeat = line->flags & ML_REPEAT_SPECIAL;
|
|
buttonSuccess = false;
|
|
TeleportSide = side;
|
|
buttonSuccess = LineSpecials[line->special]
|
|
(line, mo, line->args[0],
|
|
line->args[1], line->args[2],
|
|
line->args[3], line->args[4]);
|
|
|
|
if (!repeat && buttonSuccess)
|
|
{ // clear the special on non-retriggerable lines
|
|
line->special = 0;
|
|
}
|
|
if ((lineActivation == SPAC_USE || lineActivation == SPAC_IMPACT)
|
|
&& buttonSuccess)
|
|
{
|
|
P_ChangeSwitchTexture (line, repeat);
|
|
}
|
|
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 = player->mo->subsector->sector;
|
|
int special = sector->special & ~SECRET_MASK;
|
|
|
|
// Falling, not all the way down yet?
|
|
if (player->mo->z != sector->floorheight)
|
|
return;
|
|
|
|
// Has hitten ground.
|
|
// [RH] Normal DOOM special or BOOM specialized?
|
|
if (special >= dLight_Flicker && special <= dLight_FireFlicker) {
|
|
switch (special)
|
|
{
|
|
case dDamage_Hellslime:
|
|
// HELLSLIME DAMAGE
|
|
if (!player->powers[pw_ironfeet])
|
|
if (!(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, 10, MOD_SLIME);
|
|
break;
|
|
|
|
case dDamage_Nukage:
|
|
// NUKAGE DAMAGE
|
|
if (!player->powers[pw_ironfeet])
|
|
if (!(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, 5, MOD_LAVA);
|
|
break;
|
|
|
|
case dDamage_SuperHellslime:
|
|
// SUPER HELLSLIME DAMAGE
|
|
case dLight_Strobe_Hurt:
|
|
// STROBE HURT
|
|
if (!player->powers[pw_ironfeet]
|
|
|| (P_Random (pr_playerinspecialsector)<5) )
|
|
{
|
|
if (!(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, 20, MOD_SLIME);
|
|
}
|
|
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, MOD_UNKNOWN);
|
|
|
|
if (player->health <= 10 && (!deathmatch->value || !(dmflags & DF_NO_EXIT)))
|
|
G_ExitLevel(0);
|
|
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 (!player->powers[pw_ironfeet])
|
|
if (!(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, 5, MOD_LAVA);
|
|
break;
|
|
case 0x200: // 5/10 damage per 31 ticks
|
|
if (!player->powers[pw_ironfeet])
|
|
if (!(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, 10, MOD_SLIME);
|
|
break;
|
|
case 0x300: // 10/20 damage per 31 ticks
|
|
if (!player->powers[pw_ironfeet]
|
|
|| (P_Random(pr_playerinspecialsector)<5)) // take damage even with suit
|
|
{
|
|
if (!(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, 20, MOD_SLIME);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// [RH] Apply any customizable damage
|
|
if (sector->damage) {
|
|
if (sector->damage < 20) {
|
|
if (!player->powers[pw_ironfeet] && !(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, sector->damage, sector->mod);
|
|
} else if (sector->damage < 50) {
|
|
if ((!player->powers[pw_ironfeet] || (P_Random(pr_playerinspecialsector)<5))
|
|
&& !(level.time&0x1f))
|
|
P_DamageMobj (player->mo, NULL, NULL, sector->damage, sector->mod);
|
|
} else {
|
|
P_DamageMobj (player->mo, NULL, NULL, sector->damage, sector->mod);
|
|
}
|
|
}
|
|
|
|
if (sector->special & SECRET_MASK) {
|
|
player->secretcount++;
|
|
level.found_secrets++;
|
|
sector->special &= ~SECRET_MASK;
|
|
if (player->mo == players[consoleplayer].camera) {
|
|
C_MidPrint ("A secret is revealed!");
|
|
S_Sound (player->mo, CHAN_AUTO, "misc/secret", 1, ATTN_NORM);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// P_UpdateSpecials
|
|
// Animate planes, scroll walls, etc.
|
|
//
|
|
extern cvar_t *timelimit;
|
|
|
|
void P_UpdateSpecials (void)
|
|
{
|
|
anim_t *anim;
|
|
button_t *button, **prev;
|
|
int i;
|
|
|
|
// LEVEL TIMER
|
|
if (deathmatch->value && timelimit->value)
|
|
{
|
|
if (level.time >= (int)(timelimit->value * TICRATE * 60)) {
|
|
Printf (PRINT_HIGH, "Timelimit hit.\n");
|
|
G_ExitLevel(0);
|
|
}
|
|
}
|
|
|
|
// ANIMATE FLATS AND TEXTURES GLOBALLY
|
|
// [RH] Changed significantly to work with ANIMDEFS lumps
|
|
for (anim = anims; anim < lastanim; anim++)
|
|
{
|
|
if (--anim->countdown == 0) {
|
|
int speedframe;
|
|
|
|
anim->curframe = (anim->curframe + 1) % anim->numframes;
|
|
|
|
speedframe = (anim->uniqueframes) ? anim->curframe : 0;
|
|
|
|
if (anim->speedmin[speedframe] == anim->speedmax[speedframe])
|
|
anim->countdown = anim->speedmin[speedframe];
|
|
else
|
|
anim->countdown = P_Random(pr_animatepictures) %
|
|
(anim->speedmax[speedframe] - anim->speedmin[speedframe]) +
|
|
anim->speedmin[speedframe];
|
|
}
|
|
|
|
if (anim->uniqueframes) {
|
|
int pic = anim->framepic[anim->curframe];
|
|
|
|
if (anim->istexture)
|
|
for (i = 0; i < anim->numframes; i++)
|
|
texturetranslation[anim->framepic[i]] = pic;
|
|
else
|
|
for (i = 0; i < anim->numframes; i++)
|
|
flattranslation[anim->framepic[i]] = pic;
|
|
} else {
|
|
for (i = anim->basepic; i < anim->basepic + anim->numframes; i++) {
|
|
int pic = anim->basepic + (anim->curframe + i) % anim->numframes;
|
|
|
|
if (anim->istexture)
|
|
texturetranslation[i] = pic;
|
|
else
|
|
flattranslation[i] = pic;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [RH] Scroll the sky
|
|
sky1pos = (sky1pos + level.skyspeed1) & 0xffffff;
|
|
sky2pos = (sky2pos + level.skyspeed2) & 0xffffff;
|
|
|
|
// DO BUTTONS
|
|
// [RH] Rewritten to remove MAXBUTTONS limit
|
|
button = buttonlist;
|
|
prev = &buttonlist;
|
|
|
|
while (button) {
|
|
if (0 >= --button->btimer) {
|
|
switch(button->where)
|
|
{
|
|
case top:
|
|
sides[button->line->sidenum[0]].toptexture = button->btexture;
|
|
break;
|
|
|
|
case middle:
|
|
sides[button->line->sidenum[0]].midtexture = button->btexture;
|
|
break;
|
|
|
|
case bottom:
|
|
sides[button->line->sidenum[0]].bottomtexture = button->btexture;
|
|
break;
|
|
}
|
|
S_PositionedSound (button->x, button->y, CHAN_VOICE, "switches/normbutn", 1, ATTN_STATIC);
|
|
*prev = button->next;
|
|
Z_Free (button);
|
|
button = *prev;
|
|
} else {
|
|
prev = &button->next;
|
|
button = button->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// SPECIAL SPAWNING
|
|
//
|
|
|
|
//
|
|
// P_SpawnSpecials
|
|
// After the map has been loaded, scan for specials
|
|
// that spawn thinkers
|
|
//
|
|
|
|
void P_SpawnSpecials (void)
|
|
{
|
|
sector_t* sector;
|
|
int i;
|
|
int episode;
|
|
|
|
episode = 1;
|
|
if (W_CheckNumForName("texture2") >= 0)
|
|
episode = 2;
|
|
|
|
|
|
// Init special SECTORs.
|
|
sector = sectors;
|
|
for (i = 0; i < numsectors; i++, sector++)
|
|
{
|
|
if (!sector->special)
|
|
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
|
|
P_SpawnLightFlash (sector, -1, -1);
|
|
sector->special &= 0xff00;
|
|
break;
|
|
|
|
case dLight_StrobeFast:
|
|
// STROBE FAST
|
|
P_SpawnStrobeFlash (sector, -1, -1, STROBEBRIGHT, FASTDARK, 0);
|
|
sector->special &= 0xff00;
|
|
break;
|
|
|
|
case dLight_StrobeSlow:
|
|
// STROBE SLOW
|
|
P_SpawnStrobeFlash (sector, -1, -1, STROBEBRIGHT, SLOWDARK, 0);
|
|
sector->special &= 0xff00;
|
|
break;
|
|
|
|
case dLight_Strobe_Hurt:
|
|
// STROBE FAST/DEATH SLIME
|
|
P_SpawnStrobeFlash (sector, -1, -1, STROBEBRIGHT, FASTDARK, 0);
|
|
break;
|
|
|
|
case dLight_Glow:
|
|
// GLOWING LIGHT
|
|
P_SpawnGlowingLight(sector);
|
|
sector->special &= 0xff00;
|
|
break;
|
|
|
|
case dSector_DoorCloseIn30:
|
|
// DOOR CLOSE IN 30 SECONDS
|
|
P_SpawnDoorCloseIn30 (sector);
|
|
break;
|
|
|
|
case dLight_StrobeSlowSync:
|
|
// SYNC STROBE SLOW
|
|
P_SpawnStrobeFlash (sector, -1, -1, STROBEBRIGHT, SLOWDARK, 1);
|
|
sector->special &= 0xff00;
|
|
break;
|
|
|
|
case dLight_StrobeFastSync:
|
|
// SYNC STROBE FAST
|
|
P_SpawnStrobeFlash (sector, -1, -1, STROBEBRIGHT, FASTDARK, 1);
|
|
sector->special &= 0xff00;
|
|
break;
|
|
|
|
case dSector_DoorRaiseIn5Mins:
|
|
// DOOR RAISE IN 5 MINUTES
|
|
P_SpawnDoorRaiseIn5Mins (sector);
|
|
break;
|
|
|
|
case dLight_FireFlicker:
|
|
// fire flickering
|
|
P_SpawnFireFlicker (sector);
|
|
sector->special &= 0xff00;
|
|
break;
|
|
|
|
// [RH] Hexen-like phased lighting
|
|
case LightSequenceStart:
|
|
P_SpawnLightSequence (sector);
|
|
break;
|
|
|
|
case Light_Phased:
|
|
P_SpawnLightPhased (sector);
|
|
break;
|
|
|
|
default:
|
|
// [RH] Try for normal Hexen scroller
|
|
if ((sector->special & 0xff) >= Scroll_North_Slow &&
|
|
(sector->special & 0xff) <= Scroll_SouthWest_Fast) {
|
|
static 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;
|
|
int dx = hexenScrollies[i][0] * (FRACUNIT/2);
|
|
int dy = hexenScrollies[i][1] * (FRACUNIT/2);
|
|
|
|
Add_Scroller (sc_floor, dx, dy, -1, sector-sectors, 0);
|
|
// Hexen scrolling floors cause the player to move
|
|
// faster than they scroll. I do this just for compatibility
|
|
// with Hexen and recommend using Killough's more-versatile
|
|
// scrollers instead.
|
|
dx = FixedMul (-dx, CARRYFACTOR*2);
|
|
dy = FixedMul (dy, CARRYFACTOR*2);
|
|
Add_Scroller (sc_carry, dx, dy, -1, sector-sectors, 0);
|
|
sector->special &= 0xff00;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Init other misc stuff
|
|
|
|
// [RH] Plats and ceilings get stored in doubly-linked lists,
|
|
// but not in quite the same way as BOOM.
|
|
|
|
activeceilings = NULL;
|
|
activeplats = NULL;
|
|
|
|
buttonlist = NULL; // [RH] And buttonlist is also a singly-linked list.
|
|
|
|
ActiveQuakes = NULL; // [RH] Clear out any earthquakes.
|
|
|
|
// P_InitTagLists() must be called before P_FindSectorFromTag()
|
|
// or P_FindLineFromID() can be called.
|
|
|
|
P_InitTagLists(); // killough 1/30/98: Create xref tables for tags
|
|
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, sec;
|
|
|
|
// killough 3/7/98:
|
|
// support for drawn heights coming from different sector
|
|
case Transfer_Heights:
|
|
sec = sides[*lines[i].sidenum].sector-sectors;
|
|
for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
|
|
sectors[s].heightsec = sec;
|
|
break;
|
|
|
|
// killough 3/16/98: Add support for setting
|
|
// floor lighting independently (e.g. lava)
|
|
case Transfer_FloorLight:
|
|
sec = sides[*lines[i].sidenum].sector-sectors;
|
|
for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
|
|
sectors[s].floorlightsec = sec;
|
|
break;
|
|
|
|
// killough 4/11/98: Add support for setting
|
|
// ceiling lighting independently
|
|
case Transfer_CeilingLight:
|
|
sec = sides[*lines[i].sidenum].sector-sectors;
|
|
for (s = -1; (s = P_FindSectorFromTag(lines[i].args[0],s)) >= 0;)
|
|
sectors[s].ceilinglightsec = sec;
|
|
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 = MOD_UNKNOWN;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// [RH] Start running any open scripts on this map
|
|
P_StartOpenScripts ();
|
|
}
|
|
|
|
// 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 T_Scroll (scroll_t *s)
|
|
{
|
|
fixed_t dx = s->dx, dy = s->dy;
|
|
|
|
if (s->control != -1)
|
|
{ // compute scroll amounts based on a sector's height changes
|
|
fixed_t height = sectors[s->control].floorheight +
|
|
sectors[s->control].ceilingheight;
|
|
fixed_t delta = height - s->last_height;
|
|
s->last_height = height;
|
|
dx = FixedMul(dx, delta);
|
|
dy = FixedMul(dy, delta);
|
|
}
|
|
|
|
// killough 3/14/98: Add acceleration
|
|
if (s->accel)
|
|
{
|
|
s->vdx = dx += s->vdx;
|
|
s->vdy = dy += s->vdy;
|
|
}
|
|
|
|
if (!(dx | dy)) // no-op if both (x,y) offsets 0
|
|
return;
|
|
|
|
switch (s->type)
|
|
{
|
|
side_t *side;
|
|
sector_t *sec;
|
|
fixed_t height, waterheight; // killough 4/4/98: add waterheight
|
|
msecnode_t *node;
|
|
mobj_t *thing;
|
|
|
|
case sc_side: // killough 3/7/98: Scroll wall texture
|
|
side = sides + s->affectee;
|
|
side->textureoffset += dx;
|
|
side->rowoffset += dy;
|
|
break;
|
|
|
|
case sc_floor: // killough 3/7/98: Scroll floor texture
|
|
sec = sectors + s->affectee;
|
|
sec->floor_xoffs += dx;
|
|
sec->floor_yoffs += dy;
|
|
break;
|
|
|
|
case sc_ceiling: // killough 3/7/98: Scroll ceiling texture
|
|
sec = sectors + s->affectee;
|
|
sec->ceiling_xoffs += dx;
|
|
sec->ceiling_yoffs += dy;
|
|
break;
|
|
|
|
case sc_carry:
|
|
|
|
// killough 3/7/98: Carry things on floor
|
|
// killough 3/20/98: use new sector list which reflects true members
|
|
// killough 3/27/98: fix carrier bug
|
|
// killough 4/4/98: Underwater, carry things even w/o gravity
|
|
|
|
sec = sectors + s->affectee;
|
|
height = sec->floorheight;
|
|
waterheight = sec->heightsec != -1 &&
|
|
sectors[sec->heightsec].floorheight > height ?
|
|
sectors[sec->heightsec].floorheight : MININT;
|
|
|
|
for (node = sec->touching_thinglist; node; node = node->m_snext)
|
|
if (!((thing = node->m_thing)->flags & MF_NOCLIP) &&
|
|
(!(thing->flags & MF_NOGRAVITY || thing->z > height) ||
|
|
thing->z < waterheight))
|
|
{
|
|
// Move objects only if on floor or underwater,
|
|
// non-floating, and clipped.
|
|
thing->momx += dx;
|
|
thing->momy += 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
|
|
//
|
|
|
|
static void Add_Scroller(int type, fixed_t dx, fixed_t dy,
|
|
int control, int affectee, int accel)
|
|
{
|
|
scroll_t *s = Z_Malloc(sizeof *s, PU_LEVSPEC, 0);
|
|
s->thinker.function.acp1 = (actionf_p1) T_Scroll;
|
|
s->type = type;
|
|
s->dx = dx;
|
|
s->dy = dy;
|
|
s->accel = accel;
|
|
s->vdx = s->vdy = 0;
|
|
if ((s->control = control) != -1)
|
|
s->last_height =
|
|
sectors[control].floorheight + sectors[control].ceilingheight;
|
|
s->affectee = affectee;
|
|
P_AddThinker(&s->thinker);
|
|
}
|
|
|
|
// 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
|
|
|
|
static void Add_WallScroller(fixed_t dx, fixed_t dy, const line_t *l,
|
|
int control, int accel)
|
|
{
|
|
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);
|
|
Add_Scroller(sc_side, x, y, control, *l->sidenum, accel);
|
|
}
|
|
|
|
// Amount (dx,dy) vector linedef is shifted right to get scroll amount
|
|
#define SCROLL_SHIFT 5
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
|
|
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 = sides[*l->sidenum].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;)
|
|
Add_Scroller (sc_ceiling, -dx, dy, control, s, accel);
|
|
break;
|
|
|
|
case Scroll_Floor:
|
|
if (l->args[2] != 1)
|
|
// scroll the floor
|
|
for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;)
|
|
Add_Scroller (sc_floor, -dx, dy, control, s, accel);
|
|
|
|
if (l->args[2] > 0) {
|
|
// carry objects on the floor
|
|
dx = FixedMul(dx,CARRYFACTOR);
|
|
dy = FixedMul(dy,CARRYFACTOR);
|
|
for (s=-1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0;)
|
|
Add_Scroller (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)
|
|
Add_WallScroller (dx, dy, lines+s, control, accel);
|
|
break;
|
|
|
|
case Scroll_Texture_Offsets:
|
|
// killough 3/2/98: scroll according to sidedef offsets
|
|
s = lines[i].sidenum[0];
|
|
Add_Scroller (sc_side, -sides[s].textureoffset,
|
|
sides[s].rowoffset, -1, s, accel);
|
|
break;
|
|
|
|
case Scroll_Texture_Left:
|
|
Add_Scroller (sc_side, l->args[0] * (FRACUNIT/64), 0,
|
|
-1, lines[i].sidenum[0], accel);
|
|
break;
|
|
|
|
case Scroll_Texture_Right:
|
|
Add_Scroller (sc_side, l->args[0] * (-FRACUNIT/64), 0,
|
|
-1, lines[i].sidenum[0], accel);
|
|
break;
|
|
|
|
case Scroll_Texture_Up:
|
|
Add_Scroller (sc_side, 0, l->args[0] * (FRACUNIT/64),
|
|
-1, lines[i].sidenum[0], accel);
|
|
break;
|
|
|
|
case Scroll_Texture_Down:
|
|
Add_Scroller (sc_side, 0, l->args[0] * (-FRACUNIT/64),
|
|
-1, lines[i].sidenum[0], accel);
|
|
break;
|
|
|
|
case Scroll_Texture_Both:
|
|
if (l->args[0] == 0) {
|
|
dx = (l->args[1] - l->args[2]) * (FRACUNIT/64);
|
|
dy = (l->args[4] - l->args[3]) * (FRACUNIT/64);
|
|
Add_Scroller (sc_side, dx, dy, -1, lines[i].sidenum[0], 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
|
|
// momentum 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;
|
|
|
|
// killough 8/28/98: initialize all sectors to normal friction first
|
|
for (i = 0; i < numsectors; i++)
|
|
{
|
|
sectors[i].friction = ORIG_FRICTION;
|
|
sectors[i].movefactor = ORIG_FRICTION_FACTOR;
|
|
}
|
|
|
|
for (i = 0 ; i < numlines ; i++,l++)
|
|
{
|
|
if (l->special == Sector_SetFriction)
|
|
{
|
|
int length, friction, movefactor, s;
|
|
|
|
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;
|
|
}
|
|
friction = (0x1EB8*length)/0x80 + 0xD000;
|
|
|
|
// 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'.
|
|
|
|
if (friction > ORIG_FRICTION) // ice
|
|
movefactor = ((0x10092 - friction)*(0x70))/0x158;
|
|
else
|
|
movefactor = ((friction - 0xDB34)*(0xA))/0x80;
|
|
|
|
// killough 8/28/98: prevent odd situations
|
|
if (friction > FRACUNIT)
|
|
friction = FRACUNIT;
|
|
if (friction < 0)
|
|
friction = 0;
|
|
if (movefactor < 32)
|
|
movefactor = 32;
|
|
|
|
for (s = -1; (s = P_FindSectorFromTag(l->args[0],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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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
|
|
|
|
static void Add_Pusher (int type, line_t *l, int magnitude, int angle,
|
|
mobj_t *source, int affectee)
|
|
{
|
|
pusher_t *p = Z_Malloc (sizeof(*p), PU_LEVSPEC, 0);
|
|
|
|
p->thinker.function.acp1 = (actionf_p1) T_Pusher;
|
|
p->source = source;
|
|
p->type = type;
|
|
if (l) {
|
|
p->x_mag = l->dx>>FRACBITS;
|
|
p->y_mag = l->dy>>FRACBITS;
|
|
p->magnitude = P_AproxDistance (p->x_mag, p->y_mag);
|
|
} else {
|
|
// [RH] Allow setting magnitude and angle with parameters
|
|
angle_t ang = (angle<<24) >> ANGLETOFINESHIFT;
|
|
p->x_mag = (magnitude * finecosine[ang]) >> FRACBITS;
|
|
p->y_mag = (magnitude * finesine[ang]) >> FRACBITS;
|
|
p->magnitude = magnitude;
|
|
}
|
|
if (source) // point source exist?
|
|
{
|
|
p->radius = (p->magnitude) << (FRACBITS+1); // where force goes to zero
|
|
p->x = p->source->x;
|
|
p->y = p->source->y;
|
|
}
|
|
p->affectee = affectee;
|
|
P_AddThinker (&p->thinker);
|
|
}
|
|
|
|
/////////////////////////////
|
|
//
|
|
// PIT_PushThing determines the angle and magnitude of the effect.
|
|
// The object's x and y momentum values are changed.
|
|
//
|
|
// tmpusher belongs to the point source (MT_PUSH/MT_PULL).
|
|
//
|
|
|
|
pusher_t *tmpusher; // pusher structure for blockmap searches
|
|
|
|
BOOL PIT_PushThing (mobj_t *thing)
|
|
{
|
|
if (thing->player &&
|
|
!(thing->flags & (MF_NOGRAVITY | MF_NOCLIP)))
|
|
{
|
|
int sx = tmpusher->x;
|
|
int sy = tmpusher->y;
|
|
int dist = P_AproxDistance (thing->x - sx,thing->y - sy);
|
|
int speed = (tmpusher->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, tmpusher->source, true)))
|
|
{
|
|
angle_t pushangle = R_PointToAngle2 (thing->x, thing->y, sx, sy);
|
|
if (tmpusher->source->type == MT_PUSH)
|
|
pushangle += ANG180; // away
|
|
pushangle >>= ANGLETOFINESHIFT;
|
|
thing->momx += FixedMul (speed, finecosine[pushangle]);
|
|
thing->momy += FixedMul (speed, finesine[pushangle]);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////
|
|
//
|
|
// T_Pusher looks for all objects that are inside the radius of
|
|
// the effect.
|
|
//
|
|
extern fixed_t tmbbox[4];
|
|
|
|
void T_Pusher (pusher_t *p)
|
|
{
|
|
sector_t *sec;
|
|
mobj_t *thing;
|
|
msecnode_t *node;
|
|
int xspeed,yspeed;
|
|
int xl,xh,yl,yh,bx,by;
|
|
int radius;
|
|
int ht = 0;
|
|
|
|
if (!boom_pushers->value)
|
|
return;
|
|
|
|
sec = sectors + p->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.
|
|
|
|
if (p->type == p_push)
|
|
{
|
|
|
|
// Seek out all pushable things within the force radius of this
|
|
// point pusher. Crosses sectors, so use blockmap.
|
|
|
|
tmpusher = p; // MT_PUSH/MT_PULL point source
|
|
radius = p->radius; // where force goes to zero
|
|
tmbbox[BOXTOP] = p->y + radius;
|
|
tmbbox[BOXBOTTOM] = p->y - radius;
|
|
tmbbox[BOXRIGHT] = p->x + radius;
|
|
tmbbox[BOXLEFT] = p->x - radius;
|
|
|
|
xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT;
|
|
for (bx=xl ; bx<=xh ; bx++)
|
|
for (by=yl ; by<=yh ; by++)
|
|
P_BlockThingsIterator (bx, by, PIT_PushThing);
|
|
return;
|
|
}
|
|
|
|
// constant pushers p_wind and p_current
|
|
|
|
if (sec->heightsec != -1) // special water sector?
|
|
ht = sectors[sec->heightsec].floorheight;
|
|
node = sec->touching_thinglist; // things touching this sector
|
|
for ( ; node ; node = node->m_snext)
|
|
{
|
|
thing = node->m_thing;
|
|
if (!thing->player || (thing->flags & (MF_NOGRAVITY | MF_NOCLIP)))
|
|
continue;
|
|
if (p->type == p_wind)
|
|
{
|
|
if (sec->heightsec == -1) // NOT special water sector
|
|
{
|
|
if (thing->z > thing->floorz) // above ground
|
|
{
|
|
xspeed = p->x_mag; // full force
|
|
yspeed = p->y_mag;
|
|
}
|
|
else // on ground
|
|
{
|
|
xspeed = (p->x_mag)>>1; // half force
|
|
yspeed = (p->y_mag)>>1;
|
|
}
|
|
}
|
|
else // special water sector
|
|
{
|
|
if (thing->z > ht) // above ground
|
|
{
|
|
xspeed = p->x_mag; // full force
|
|
yspeed = p->y_mag;
|
|
}
|
|
else if (thing->player->viewz < ht) // underwater
|
|
xspeed = yspeed = 0; // no force
|
|
else // wading in water
|
|
{
|
|
xspeed = (p->x_mag)>>1; // half force
|
|
yspeed = (p->y_mag)>>1;
|
|
}
|
|
}
|
|
}
|
|
else // p_current
|
|
{
|
|
if (sec->heightsec == -1) { // NOT special water sector
|
|
if (thing->z > sec->floorheight) // above ground
|
|
xspeed = yspeed = 0; // no force
|
|
else // on ground
|
|
{
|
|
xspeed = p->x_mag; // full force
|
|
yspeed = p->y_mag;
|
|
}
|
|
} else { // special water sector
|
|
if (thing->z > ht) // above ground
|
|
xspeed = yspeed = 0; // no force
|
|
else // underwater
|
|
{
|
|
xspeed = p->x_mag; // full force
|
|
yspeed = p->y_mag;
|
|
}
|
|
}
|
|
}
|
|
thing->momx += xspeed<<(FRACBITS-PUSH_FACTOR);
|
|
thing->momy += yspeed<<(FRACBITS-PUSH_FACTOR);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////
|
|
//
|
|
// P_GetPushThing() returns a pointer to an MT_PUSH or MT_PULL thing,
|
|
// NULL otherwise.
|
|
|
|
mobj_t *P_GetPushThing (int s)
|
|
{
|
|
mobj_t* thing;
|
|
sector_t* sec;
|
|
|
|
sec = sectors + s;
|
|
thing = sec->thinglist;
|
|
while (thing)
|
|
{
|
|
switch (thing->type)
|
|
{
|
|
case MT_PUSH:
|
|
case MT_PULL:
|
|
return thing;
|
|
default:
|
|
break;
|
|
}
|
|
thing = thing->snext;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/////////////////////////////
|
|
//
|
|
// Initialize the sectors where pushers are present
|
|
//
|
|
|
|
static void P_SpawnPushers(void)
|
|
{
|
|
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 ; )
|
|
Add_Pusher (p_wind, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
|
|
break;
|
|
case Sector_SetCurrent: // current
|
|
for (s = -1; (s = P_FindSectorFromTag (l->args[0],s)) >= 0 ; )
|
|
Add_Pusher (p_current, l->args[3] ? l : NULL, l->args[1], l->args[2], NULL, s);
|
|
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 ; )
|
|
{
|
|
mobj_t *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)
|
|
Add_Pusher (p_push, l->args[3] ? l : NULL, l->args[2],
|
|
0, thing, s);
|
|
}
|
|
}
|
|
} else { // [RH] Find thing by tid
|
|
mobj_t *thing = NULL;
|
|
|
|
while ( (thing = P_FindMobjByTid (thing, l->args[1])) ) {
|
|
if (thing->type == MT_PUSH || thing->type == MT_PULL)
|
|
Add_Pusher (p_push, l->args[3] ? l : NULL, l->args[2],
|
|
0, thing, thing->subsector->sector - sectors);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// phares 3/20/98: End of Pusher effects
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|