// 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 #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= 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 // ////////////////////////////////////////////////////////////////////////////