// 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: // Moving object handling. Spawn functions. // //----------------------------------------------------------------------------- #include "m_alloc.h" #include "i_system.h" #include "z_zone.h" #include "m_random.h" #include "doomdef.h" #include "p_local.h" #include "p_lnspec.h" #include "p_effect.h" #include "st_stuff.h" #include "hu_stuff.h" #include "s_sound.h" #include "doomstat.h" #include "v_video.h" #include "c_cvars.h" cvar_t *sv_gravity; cvar_t *sv_friction; void G_PlayerReborn (int player); fixed_t FloatBobOffsets[64] = { 0, 51389, 102283, 152192, 200636, 247147, 291278, 332604, 370727, 405280, 435929, 462380, 484378, 501712, 514213, 521763, 524287, 521763, 514213, 501712, 484378, 462380, 435929, 405280, 370727, 332604, 291278, 247147, 200636, 152192, 102283, 51389, -1, -51390, -102284, -152193, -200637, -247148, -291279, -332605, -370728, -405281, -435930, -462381, -484380, -501713, -514215, -521764, -524288, -521764, -514214, -501713, -484379, -462381, -435930, -405280, -370728, -332605, -291279, -247148, -200637, -152193, -102284, -51389 }; // // P_SetMobjState // Returns true if the mobj is still present. // BOOL P_SetMobjState (mobj_t *mobj, statenum_t state) { state_t *st; // killough 4/9/98: remember states seen, to detect cycles: static statenum_t seenstate_tab[NUMSTATES]; // fast transition table statenum_t *seenstate = seenstate_tab; // pointer to table static int recursion; // detects recursion statenum_t i = state; // initial state BOOL ret = true; // return value statenum_t tempstate[NUMSTATES]; // for use with recursion if (recursion++) // if recursion detected, memset(seenstate=tempstate,0,sizeof tempstate); // clear state table do { if (state == S_NULL) { mobj->state = (state_t *) S_NULL; P_RemoveMobj (mobj); ret = false; break; // killough 4/9/98 } st = &states[state]; mobj->state = st; mobj->tics = st->tics; if (!mobj->player || st->sprite != SPR_PLAY) mobj->sprite = st->sprite; // [RH] Only change sprite if not a player mobj->frame = st->frame; // Modified handling. // Call action functions when the state is set if (st->action.acp1) st->action.acp1(mobj); seenstate[state] = 1 + st->nextstate; // killough 4/9/98 state = st->nextstate; } while (!mobj->tics && !seenstate[state]); // killough 4/9/98 if (ret && !mobj->tics) // killough 4/9/98: detect state cycles Printf (PRINT_HIGH, "Warning: State Cycle Detected\n"); if (!--recursion) for (;(state=seenstate[i]);i=state-1) seenstate[i] = 0; // killough 4/9/98: erase memory of states return ret; } // // P_ExplodeMissile // void P_ExplodeMissile (mobj_t* mo) { mo->momx = mo->momy = mo->momz = 0; P_SetMobjState (mo, mobjinfo[mo->type].deathstate); // [RH] If the object isn't translucent, don't change it. // Otherwise, make it 75% translucent. if (TransTable && !(mo->flags & MF_TRANSLUCBITS)) mo->flags |= MF_TRANSLUC75; mo->tics -= P_Random (pr_explodemissile) & 3; if (mo->tics < 1) mo->tics = 1; mo->flags &= ~MF_MISSILE; if (mo->info->deathsound) S_Sound (mo, CHAN_VOICE, mo->info->deathsound, 1, ATTN_NORM); mo->effects = 0; // [RH] } //---------------------------------------------------------------------------- // // PROC P_FloorBounceMissile // //---------------------------------------------------------------------------- void P_FloorBounceMissile(mobj_t *mo) { /* if(P_HitFloor(mo) >= FLOOR_LIQUID) { switch(mo->type) { case MT_SORCFX1: case MT_SORCBALL1: case MT_SORCBALL2: case MT_SORCBALL3: break; default: P_RemoveMobj(mo); return; } } */ switch(mo->type) { /* case MT_SORCFX1: mo->momz = -mo->momz; // no energy absorbed break; case MT_SGSHARD1: case MT_SGSHARD2: case MT_SGSHARD3: case MT_SGSHARD4: case MT_SGSHARD5: case MT_SGSHARD6: case MT_SGSHARD7: case MT_SGSHARD8: case MT_SGSHARD9: case MT_SGSHARD0: mo->momz = FixedMul(mo->momz, (fixed_t)(-0.3*FRACUNIT)); if(abs(mo->momz) < (FRACUNIT/2)) { P_SetMobjState(mo, S_NULL); return; } break; */ default: mo->momz = FixedMul(mo->momz, (fixed_t)(-0.7*FRACUNIT)); break; } mo->momx = 2*mo->momx/3; mo->momy = 2*mo->momy/3; if(mo->info->seesound) { S_Sound (mo, CHAN_VOICE, mo->info->seesound, 1, ATTN_IDLE); } if (!(mo->flags & MF_NOGRAVITY) && (mo->momz < 3*FRACUNIT)) mo->flags2 &= ~MF2_FLOORBOUNCE; // P_SetMobjState(mo, mobjinfo[mo->type].deathstate); } // // P_XYMovement // extern int numspechit; extern line_t **spechit; #define STOPSPEED 0x1000 #define FRICTION 0xe800 void P_XYMovement (mobj_t *mo) { angle_t angle; fixed_t ptryx, ptryy; player_t *player; fixed_t xmove, ymove; if (!mo->momx && !mo->momy) { if (mo->flags & MF_SKULLFLY) { // the skull slammed into something mo->flags &= ~MF_SKULLFLY; mo->momx = mo->momy = mo->momz = 0; P_SetMobjState (mo, mo->info->spawnstate); } return; } player = mo->player; if (mo->momx > MAXMOVE) mo->momx = MAXMOVE; else if (mo->momx < -MAXMOVE) mo->momx = -MAXMOVE; if (mo->momy > MAXMOVE) mo->momy = MAXMOVE; else if (mo->momy < -MAXMOVE) mo->momy = -MAXMOVE; xmove = mo->momx; ymove = mo->momy; do { if (xmove > MAXMOVE/2 || ymove > MAXMOVE/2 || xmove < -MAXMOVE/2 || ymove < -MAXMOVE/2) { ptryx = mo->x + xmove/2; ptryy = mo->y + ymove/2; xmove >>= 1; ymove >>= 1; } else { ptryx = mo->x + xmove; ptryy = mo->y + ymove; xmove = ymove = 0; } // killough 3/15/98: Allow objects to drop off if (!P_TryMove (mo, ptryx, ptryy, true)) { // blocked move if (mo->flags2 & MF2_SLIDE) { // try to slide along it if (BlockingMobj == NULL) { // slide against wall P_SlideMove (mo); } else { // slide against mobj if (P_TryMove (mo, mo->x, ptryy, true)) { mo->momx = 0; } else if (P_TryMove (mo, ptryx, mo->y, true)) { mo->momy = 0; } else { mo->momx = mo->momy = 0; } if (player && player->mo == mo) { if (mo->momx == 0) player->momx = 0; if (mo->momy == 0) player->momy = 0; } } } else if (mo->flags & MF_MISSILE) { if (mo->flags2 & MF2_FLOORBOUNCE) { if (BlockingMobj) { if ((BlockingMobj->flags2 & MF2_REFLECTIVE) || ((!BlockingMobj->player) && (!(BlockingMobj->flags & MF_COUNTKILL)))) { fixed_t speed; angle = R_PointToAngle2 (BlockingMobj->x, BlockingMobj->y, mo->x, mo->y) +ANGLE_1*((P_Random(pr_bounce)%16)-8); speed = P_AproxDistance (mo->momx, mo->momy); speed = FixedMul (speed, (fixed_t)(0.75*FRACUNIT)); mo->angle = angle; angle >>= ANGLETOFINESHIFT; mo->momx = FixedMul (speed, finecosine[angle]); mo->momy = FixedMul (speed, finesine[angle]); if (mo->info->seesound) { S_Sound (mo, CHAN_VOICE, mo->info->seesound, 1, ATTN_IDLE); } return; } else { // Struck a player/creature P_ExplodeMissile (mo); } } else { // Struck a wall P_BounceWall (mo); if (mo->info->seesound) { S_Sound (mo, CHAN_VOICE, mo->info->seesound, 1, ATTN_IDLE); } return; } } if(BlockingMobj && (BlockingMobj->flags2 & MF2_REFLECTIVE)) { angle = R_PointToAngle2(BlockingMobj->x, BlockingMobj->y, mo->x, mo->y); // Change angle for delflection/reflection angle += ANGLE_1 * ((P_Random(pr_bounce)%16)-8); // Reflect the missile along angle mo->angle = angle; angle >>= ANGLETOFINESHIFT; mo->momx = FixedMul(mo->info->speed>>1, finecosine[angle]); mo->momy = FixedMul(mo->info->speed>>1, finesine[angle]); // mo->momz = -mo->momz; mo->target = BlockingMobj; return; } // explode a missile if (ceilingline && ceilingline->backsector && ceilingline->backsector->ceilingpic == skyflatnum && mo->z >= ceilingline->backsector->ceilingheight) //killough { // Hack to prevent missiles exploding against the sky. // Does not handle sky floors. P_RemoveMobj (mo); return; } P_ExplodeMissile (mo); } else { mo->momx = mo->momy = 0; } } } while (xmove || ymove); // Friction if (player && player->mo == mo && player->cheats & CF_NOMOMENTUM) { // debug option for no sliding at all mo->momx = mo->momy = 0; player->momx = player->momy = 0; return; } if (mo->flags & (MF_MISSILE | MF_SKULLFLY)) return; // no friction for missiles if (mo->z > mo->floorz && !(mo->flags2 & MF2_ONMOBJ)) return; // no friction when falling if (mo->flags & MF_CORPSE) { // do not stop sliding // if halfway off a step with some momentum if (mo->momx > FRACUNIT/4 || mo->momx < -FRACUNIT/4 || mo->momy > FRACUNIT/4 || mo->momy < -FRACUNIT/4) { if (mo->floorz != mo->subsector->sector->floorheight) return; } } // killough 11/98: // Stop voodoo dolls that have come to rest, despite any // moving corresponding player: if (mo->momx > -STOPSPEED && mo->momx < STOPSPEED && mo->momy > -STOPSPEED && mo->momy < STOPSPEED && (!player || (player->mo != mo) || !(player->cmd.ucmd.forwardmove | player->cmd.ucmd.sidemove))) { // if in a walking frame, stop moving // killough 10/98: // Don't affect main player when voodoo dolls stop: if (player && (unsigned)((player->mo->state - states) - S_PLAY_RUN1) < 4 && (player->mo == mo)) { P_SetMobjState (player->mo, S_PLAY); } mo->momx = mo->momy = 0; // killough 10/98: kill any bobbing momentum too (except in voodoo dolls) if (player && player->mo == mo) player->momx = player->momy = 0; } else { // phares 3/17/98 // Friction will have been adjusted by friction thinkers for icy // or muddy floors. Otherwise it was never touched and // remained set at ORIG_FRICTION // // killough 8/28/98: removed inefficient thinker algorithm, // instead using touching_sectorlist in P_GetFriction() to // determine friction (and thus only when it is needed). // // killough 10/98: changed to work with new bobbing method. // Reducing player momentum is no longer needed to reduce // bobbing, so ice works much better now. fixed_t friction = P_GetFriction (mo, NULL); mo->momx = FixedMul (mo->momx, friction); mo->momy = FixedMul (mo->momy, friction); // killough 10/98: Always decrease player bobbing by ORIG_FRICTION. // This prevents problems with bobbing on ice, where it was not being // reduced fast enough, leading to all sorts of kludges being developed. if (player && player->mo == mo) // Not voodoo dolls { player->momx = FixedMul (player->momx, ORIG_FRICTION); player->momy = FixedMul (player->momy, ORIG_FRICTION); } } } // // P_ZMovement // void P_ZMovement (mobj_t *mo) { fixed_t dist; fixed_t delta; // check for smooth step up if (mo->player && mo->z < mo->floorz) { mo->player->viewheight -= mo->floorz-mo->z; mo->player->deltaviewheight = (VIEWHEIGHT - mo->player->viewheight)>>3; } // adjust height mo->z += mo->momz; if ((mo->flags & MF_FLOAT) && mo->target) { // float down towards target if too close if (!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT)) { dist = P_AproxDistance (mo->x - mo->target->x, mo->y - mo->target->y); delta =(mo->target->z + (mo->height>>1)) - mo->z; if (delta<0 && dist < -(delta*3) ) mo->z -= FLOATSPEED; else if (delta>0 && dist < (delta*3) ) mo->z += FLOATSPEED; } } if (mo->player && (mo->flags & MF2_FLY) && !(mo->z <= mo->floorz) && (level.time & 2)) { mo->z += finesine[(FINEANGLES/20*level.time>>2)&FINEMASK]; } // clip movement if (mo->z <= mo->floorz) { // hit the floor if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP)) { mo->z = mo->floorz; if (mo->flags2 & MF2_FLOORBOUNCE) { P_FloorBounceMissile (mo); return; } else { if (mo->subsector->sector->floorpic == skyflatnum) { // [RH] Just remove the missile without exploding it // if this is a sky floor. P_RemoveMobj (mo); return; } P_HitFloor (mo); P_ExplodeMissile (mo); return; } } /* if (mo->flags & MF_COUNTKILL) // Blasted mobj falling { if (mo->momz < -(23*FRACUNIT)) { P_MonsterFallingDamage (mo); } } */ if (mo->z-mo->momz > mo->floorz) { // Spawn splashes, etc. P_HitFloor (mo); } mo->z = mo->floorz; if (mo->momz < 0) { if (mo->player) { // [RH] avoid integer roundoff by doing comparisons with floats float minmom = sv_gravity->value * mo->subsector->sector->gravity * -655.36f; float mom = (float)mo->momz; mo->player->jumpTics = 7; // delay any jumping for a short while if (mom < minmom && !(mo->flags2 & MF2_FLY)) { // Squat down. // Decrease viewheight for a moment after hitting the ground (hard), // and utter appropriate sound. mo->player->deltaviewheight = mo->momz >> 3; S_Sound (mo, CHAN_AUTO, "*land1", 1, ATTN_NORM); } mo->momz = 0; } } if (mo->flags & MF_SKULLFLY) { // The skull slammed into something mo->momz = -mo->momz; } } else if (mo->flags2 & MF2_LOGRAV) { if (mo->momz == 0) mo->momz = (fixed_t)(sv_gravity->value * mo->subsector->sector->gravity * -20.48); else mo->momz -= (fixed_t)(sv_gravity->value * mo->subsector->sector->gravity * 10.24); } else if (! (mo->flags & MF_NOGRAVITY) ) { if (mo->momz == 0) mo->momz = (fixed_t)(sv_gravity->value * mo->subsector->sector->gravity * -163.84); else mo->momz -= (fixed_t)(sv_gravity->value * mo->subsector->sector->gravity * 81.92); } if (mo->z + mo->height > mo->ceilingz) { // hit the ceiling mo->z = mo->ceilingz - mo->height; if (mo->flags2 & MF2_FLOORBOUNCE) { // reverse momentum here for ceiling bounce mo->momz = FixedMul (mo->momz, (fixed_t)(-0.75*FRACUNIT)); if (mo->info->seesound) { S_Sound (mo, CHAN_BODY, mo->info->seesound, 1, ATTN_IDLE); } return; } if (mo->momz > 0) mo->momz = 0; if (mo->flags & MF_SKULLFLY) { // the skull slammed into something mo->momz = -mo->momz; } if (mo->flags & MF_MISSILE && !(mo->flags & MF_NOCLIP)) { if (mo->subsector->sector->ceilingpic == skyflatnum) { P_RemoveMobj (mo); return; } P_ExplodeMissile (mo); return; } } } //=========================================================================== // // PlayerLandedOnThing // //=========================================================================== static void PlayerLandedOnThing(mobj_t *mo, mobj_t *onmobj) { mo->player->deltaviewheight = mo->momz>>3; S_Sound (mo, CHAN_AUTO, "*land1", 1, ATTN_IDLE); // mo->player->centering = true; } // // P_NightmareRespawn // void P_NightmareRespawn (mobj_t *mobj) { fixed_t x; fixed_t y; subsector_t* ss; mobj_t* mo; mapthing2_t* mthing; x = mobj->spawnpoint.x << FRACBITS; y = mobj->spawnpoint.y << FRACBITS; // something is occupying it's position? if (!P_CheckPosition (mobj, x, y)) return; // no respawn // spawn a teleport fog at old spot // because of removal of the body? mo = P_SpawnMobj (mobj->x, mobj->y, ONFLOORZ, MT_TFOG); // initiate teleport sound S_Sound (mo, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM); ss = R_PointInSubsector (x,y); // spawn a teleport fog at the new spot mo = P_SpawnMobj (x, y, ONFLOORZ, MT_TFOG); S_Sound (mo, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM); // spawn the new monster mthing = &mobj->spawnpoint; // spawn it // inherit attributes from deceased one mo = P_SpawnMobj (x, y, ONFLOORZ, mobj->type); mo->spawnpoint = mobj->spawnpoint; mo->angle = ANG45 * (mthing->angle/45); if (mthing->flags & MTF_AMBUSH) mo->flags |= MF_AMBUSH; mo->reactiontime = 18; // remove the old monster, P_RemoveMobj (mobj); } // // [RH] Some new functions to work with Thing IDs. -------> // static mobj_t *tidhash[128]; #define TIDHASH(i) ((i)&127) // // P_ClearTidHashes // // Clears the tid hashtable. // void P_ClearTidHashes (void) { int i; for (i = 0; i < 128; i++) tidhash[i] = NULL; } // // P_AddMobjToHash // // Inserts an mobj into the correct chain based on its tid. // If its tid is 0, this function does nothing. // void P_AddMobjToHash (mobj_t *mobj) { if (mobj->tid == 0) { mobj->inext = mobj->iprev = NULL; return; } else { int hash = TIDHASH(mobj->tid); mobj->inext = tidhash[hash]; mobj->iprev = NULL; tidhash[hash] = mobj; } } // // P_RemoveMobjFromHash // // Removes an mobj from its hash chain. // void P_RemoveMobjFromHash (mobj_t *mobj) { if (mobj->tid == 0) return; else { if (mobj->iprev == NULL) { // First mobj in the chain (probably) int hash = TIDHASH(mobj->tid); if (tidhash[hash] == mobj) tidhash[hash] = mobj->inext; if (mobj->inext) { mobj->inext->iprev = NULL; mobj->inext = NULL; } } else { // Not the first mobj in the chain mobj->iprev->inext = mobj->inext; if (mobj->inext) { mobj->inext->iprev = mobj->iprev; mobj->inext = NULL; } mobj->iprev = NULL; } } } // // P_FindMobjByTid // // Returns the next mobj with the tid after the one given, // or the first with that tid if no mobj is passed. Returns // NULL if there are no more. // mobj_t *P_FindMobjByTid (mobj_t *mobj, int tid) { // Mobjs without tid are never stored. if (tid == 0) return NULL; if (!mobj) mobj = tidhash[TIDHASH(tid)]; else mobj = mobj->inext; while (mobj && mobj->tid != tid) mobj = mobj->inext; return mobj; } // // P_FindGoal // // Like P_FindMobjByTid except it also matcheds on type. // mobj_t *P_FindGoal (mobj_t *mobj, int tid, int kind) { mobj_t *goal; do { goal = P_FindMobjByTid (mobj, tid); } while (goal && goal->type != kind); return goal; } // <------- [RH] End new functions // // P_MobjThinker // void P_MobjThinker (mobj_t *mobj) { mobj_t *onmo; // [RH] Decrement targettic if (mobj->targettic) mobj->targettic--; // Handle X and Y momemtums BlockingMobj = NULL; if (mobj->momx || mobj->momy || (mobj->flags & MF_SKULLFLY)) { P_XYMovement (mobj); if (mobj->thinker.function.acv == (actionf_v) (-1)) return; // mobj was removed } if(mobj->flags2&MF2_FLOATBOB) { // Floating item bobbing motion (special1 is height) mobj->z = mobj->floorz + mobj->special1 + FloatBobOffsets[(mobj->health++)&63]; } else if ((mobj->z != mobj->floorz) || mobj->momz || BlockingMobj) { // Handle Z momentum and gravity if (mobj->flags2 & MF2_PASSMOBJ) { if (!(onmo = P_CheckOnmobj (mobj))) { P_ZMovement (mobj); if (mobj->player && mobj->flags2 & MF2_ONMOBJ) { mobj->flags2 &= ~MF2_ONMOBJ; } } else { if (mobj->player) { if (mobj->momz < (fixed_t)(sv_gravity->value * mobj->subsector->sector->gravity * -655.36f) && !(mobj->flags2&MF2_FLY)) { PlayerLandedOnThing(mobj, onmo); } if (onmo->z + onmo->height - mobj->z <= 24 * FRACUNIT) { mobj->player->viewheight -= onmo->z + onmo->height - mobj->z; mobj->player->deltaviewheight = (VIEWHEIGHT - mobj->player->viewheight)>>3; mobj->z = onmo->z + onmo->height; mobj->flags2 |= MF2_ONMOBJ; mobj->momz = 0; } else { // hit the bottom of the blocking mobj mobj->momz = 0; } } } } else { P_ZMovement (mobj); } if (mobj->thinker.function.acv == (actionf_v) (-1)) return; // mobj was removed } if (mobj->flags2 & MF2_DORMANT) return; // cycle through states, calling action functions at transitions if (mobj->tics != -1) { mobj->tics--; // you can cycle through multiple states in a tic if (!mobj->tics) if (!P_SetMobjState (mobj, mobj->state->nextstate) ) return; // freed itself } else { // check for nightmare respawn if (!(mobj->flags & MF_COUNTKILL) || !respawnmonsters) return; mobj->movecount++; if (mobj->movecount < 12*TICRATE) return; if ( level.time&31 ) return; if (P_Random (pr_mobjthinker) > 4) return; P_NightmareRespawn (mobj); } } // // P_SpawnMobj // [RH] Since MapThings can now be stored with their own z-position, // we now use a separate parameter to indicate if it should be // spawned relative to the floor or ceiling. // mobj_t *P_SpawnMobj (fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) { mobj_t* mobj; state_t* st; mobjinfo_t* info; fixed_t space; mobj = Z_Malloc (sizeof(*mobj), PU_LEVEL, NULL); memset (mobj, 0, sizeof (*mobj)); info = &mobjinfo[type]; mobj->type = type; mobj->info = info; mobj->x = x; mobj->y = y; mobj->radius = info->radius; mobj->height = info->height; mobj->flags = info->flags; mobj->flags2 = info->flags2; mobj->health = info->spawnhealth; if (mobj->flags & MF_STEALTH) // [RH] Stealth monsters start out invisible mobj->flags2 |= MF2_DONTDRAW; if (gameskill->value != sk_nightmare) mobj->reactiontime = info->reactiontime; mobj->lastlook = P_Random (pr_spawnmobj) % MAXPLAYERS; // do not set the state with P_SetMobjState, // because action routines can not be called yet st = &states[info->spawnstate]; mobj->state = st; mobj->tics = st->tics; mobj->sprite = st->sprite; mobj->frame = st->frame; mobj->touching_sectorlist = NULL; // NULL head of sector list // phares 3/13/98 // set subsector and/or block links P_SetThingPosition (mobj); mobj->floorz = mobj->subsector->sector->floorheight; mobj->ceilingz = mobj->subsector->sector->ceilingheight; if (z == ONFLOORZ) { mobj->z = mobj->floorz; } else if (z == ONCEILINGZ) { mobj->z = mobj->ceilingz - mobj->height; } else if(z == FLOATRANDZ) { space = ((mobj->ceilingz)-(mobj->info->height))-mobj->floorz; if(space > 48*FRACUNIT) { space -= 40*FRACUNIT; mobj->z = ((space*P_Random(pr_spawnmobj))>>8)+mobj->floorz+40*FRACUNIT; } else { mobj->z = mobj->floorz; } } else if (mobj->flags2&MF2_FLOATBOB) { mobj->z = mobj->floorz + z; // artifact z passed in as height } else { mobj->z = z; } mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; P_AddThinker (&mobj->thinker); return mobj; } // // P_RemoveMobj // mapthing2_t itemrespawnque[ITEMQUESIZE]; int itemrespawntime[ITEMQUESIZE]; int iquehead; int iquetail; void P_RemoveMobj (mobj_t* mobj) { if ((mobj->flags & MF_SPECIAL) && !(mobj->flags & MF_DROPPED) && (mobj->type != MT_INV) && (mobj->type != MT_INS)) { itemrespawnque[iquehead] = mobj->spawnpoint; itemrespawntime[iquehead] = level.time; iquehead = (iquehead+1)&(ITEMQUESIZE-1); // lose one off the end? if (iquehead == iquetail) iquetail = (iquetail+1)&(ITEMQUESIZE-1); } // unlink from sector and block lists P_UnsetThingPosition (mobj); // [RH] Unlink from tid chain P_RemoveMobjFromHash (mobj); // Delete all nodes on the current sector_list phares 3/16/98 if (sector_list) { P_DelSeclist(sector_list); sector_list = NULL; } // stop any playing sound S_RelinkSound (mobj, NULL); // free block P_RemoveThinker ((thinker_t*)mobj); } // // P_RespawnSpecials // void P_RespawnSpecials (void) { fixed_t x; fixed_t y; fixed_t z; subsector_t* ss; mobj_t* mo; mapthing2_t* mthing; int i; // only respawn items in deathmatch if (!deathmatch->value || !(dmflags & DF_ITEMS_RESPAWN)) return; // nothing left to respawn? if (iquehead == iquetail) return; // wait at least 30 seconds if (level.time - itemrespawntime[iquetail] < 30*TICRATE) return; mthing = &itemrespawnque[iquetail]; x = mthing->x << FRACBITS; y = mthing->y << FRACBITS; // find which type to spawn for (i=0 ; i< NUMMOBJTYPES ; i++) { if (mthing->type == mobjinfo[i].doomednum) break; } if (mobjinfo[i].flags & MF_SPAWNCEILING) z = ONCEILINGZ; else if (mobjinfo[i].flags2 & MF2_SPAWNFLOAT) z = FLOATRANDZ; else if (mobjinfo[i].flags2 & MF2_FLOATBOB) z = mthing->z << FRACBITS; else z = ONFLOORZ; // spawn a teleport fog at the new spot ss = R_PointInSubsector (x, y); mo = P_SpawnMobj (x, y, z, MT_IFOG); S_Sound (mo, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE); // spawn it mo = P_SpawnMobj (x, y, z, i); mo->spawnpoint = *mthing; mo->angle = ANG45 * (mthing->angle/45); if (z == ONFLOORZ) mo->z += mthing->z << FRACBITS; else if (z == ONCEILINGZ) mo->z -= mthing->z << FRACBITS; if (mo->flags2 & MF2_FLOATBOB) { // Seed random starting index for bobbing motion mo->health = M_Random(); mo->special1 = mthing->z << FRACBITS; } // [RH] Set the thing's special mo->special = mthing->special; memcpy (mo->args, mthing->args, sizeof(mo->args)); // pull it from the que iquetail = (iquetail+1)&(ITEMQUESIZE-1); } // // P_SpawnPlayer // Called when a player is spawned on the level. // Most of the player structure stays unchanged // between levels. // extern cvar_t *chasedemo; extern BOOL demonew; void P_SpawnPlayer (mapthing2_t *mthing) { int playernum; player_t *p; mobj_t *mobj; int i; // [RH] Things 4001-? are also multiplayer starts. Just like 1-4. // To make things simpler, figure out which player is being // spawned here. playernum = (mthing->type > 4000) ? mthing->type - 4001 + 4 : mthing->type - 1; // not playing? if (playernum >= MAXPLAYERS || !playeringame[playernum]) return; p = &players[playernum]; if (p->playerstate == PST_REBORN) G_PlayerReborn (playernum); mobj = P_SpawnMobj (mthing->x << FRACBITS, mthing->y << FRACBITS, ONFLOORZ, MT_PLAYER); // set color translations for player sprites // [RH] Different now: MF_TRANSLATION is not used. mobj->translation = translationtables + 256*playernum; mobj->angle = ANG45 * (mthing->angle/45); mobj->pitch = mobj->roll = 0; mobj->player = p; mobj->health = p->health; // [RH] Set player sprite based on skin mobj->sprite = skins[p->userinfo.skin].sprite; p->mo = p->camera = mobj; p->playerstate = PST_LIVE; p->refire = 0; p->damagecount = 0; p->bonuscount = 0; p->extralight = 0; p->fixedcolormap = 0; p->viewheight = VIEWHEIGHT; p->momx = p->momy = 0; // killough 10/98: initialize bobbing to 0. players[consoleplayer].camera = players[displayplayer].mo; // [RH] Allow chasecam for demo watching if ((demoplayback || demonew) && chasedemo->value) p->cheats = CF_CHASECAM; // setup gun psprite P_SetupPsprites (p); // give all cards in death match mode if (deathmatch->value) for (i = 0; i < NUMCARDS; i++) p->cards[i] = true; if (players[consoleplayer].camera == p->mo) { // wake up the status bar ST_Start (); } // [RH] If someone is in the way, kill them P_TeleportMove (mobj, mobj->x, mobj->y, mobj->z, true); } // // P_SpawnMapThing // The fields of the mapthing should // already be in host byte order. // // [RH] position is used to weed out unwanted start spots void P_SpawnMapThing (mapthing2_t *mthing, int position) { int i; int bit; mobj_t* mobj; fixed_t x; fixed_t y; fixed_t z; if (mthing->type == 9026) mthing->type = 9026; // count deathmatch start positions if (mthing->type == 11) { if (deathmatch_p == &deathmatchstarts[MaxDeathmatchStarts]) { // [RH] Get more deathmatchstarts MaxDeathmatchStarts *= 2; deathmatchstarts = Realloc (deathmatchstarts, MaxDeathmatchStarts * sizeof(mapthing2_t)); deathmatch_p = &deathmatchstarts[MaxDeathmatchStarts - 8]; } memcpy (deathmatch_p, mthing, sizeof(*mthing)); deathmatch_p++; return; } // [RH] Record polyobject-related things if (HexenHack) { switch (mthing->type) { case PO_HEX_ANCHOR_TYPE: mthing->type = PO_ANCHOR_TYPE; break; case PO_HEX_SPAWN_TYPE: mthing->type = PO_SPAWN_TYPE; break; case PO_HEX_SPAWNCRUSH_TYPE: mthing->type = PO_SPAWNCRUSH_TYPE; break; } } if (mthing->type == PO_ANCHOR_TYPE || mthing->type == PO_SPAWN_TYPE || mthing->type == PO_SPAWNCRUSH_TYPE) { polyspawns_t *polyspawn = Malloc (sizeof(polyspawns_t)); polyspawn->next = polyspawns; polyspawn->x = mthing->x << FRACBITS; polyspawn->y = mthing->y << FRACBITS; polyspawn->angle = mthing->angle; polyspawn->type = mthing->type; polyspawns = polyspawn; if (mthing->type != PO_ANCHOR_TYPE) po_NumPolyobjs++; return; } // check for players specially if (mthing->type <= 4 && mthing->type > 0) { // [RH] Only spawn spots that match position. if (mthing->args[0] != position) return; // save spots for respawning in network games playerstarts[mthing->type-1] = *mthing; if (!deathmatch->value) P_SpawnPlayer (mthing); return; } else if (mthing->type >= 4001 && mthing->type <= 4001 + MAXPLAYERS - 4) { // [RH] Multiplayer starts for players 5-? // [RH] Only spawn spots that match position. if (mthing->args[0] != position) return; playerstarts[mthing->type - 4001 + 4] = *mthing; if (!deathmatch->value) P_SpawnPlayer (mthing); return; } // [RH] sound sequence overrides if (mthing->type >= 1400 && mthing->type < 1410) { R_PointInSubsector (mthing->x<y<sector->seqType = mthing->type - 1400; return; } else if (mthing->type == 1411) { int type; if (mthing->args[0] == 255) type = -1; else type = mthing->args[0]; if (type > 63) { Printf (PRINT_HIGH, "Sound sequence %d out of range\n", type); } else { R_PointInSubsector (mthing->x << FRACBITS, mthing->y << FRACBITS)->sector->seqType = type; } return; } if (netgame) { if (deathmatch->value) { if (!(mthing->flags & MTF_DEATHMATCH)) return; } else { if (!(mthing->flags & MTF_COOPERATIVE)) return; } } else { if (!(mthing->flags & MTF_SINGLE)) return; } // check for apropriate skill level if (gameskill->value == sk_baby) bit = 1; else if (gameskill->value == sk_nightmare) bit = 4; else bit = 1 << ((int)gameskill->value - 1); if (!(mthing->flags & bit)) return; // [RH] Determine if it is an old ambient thing, and if so, // map it to MT_AMBIENT with the proper parameter. if (mthing->type >= 14001 && mthing->type <= 14064) { mthing->args[0] = mthing->type - 14000; mthing->type = 14065; i = MT_AMBIENT; } // [RH] Check if it's a particle fountain else if (mthing->type >= 9027 && mthing->type <= 9033) { mthing->args[0] = mthing->type - 9026; i = MT_FOUNTAIN; } else { // find which type to spawn for (i=0 ; i< NUMMOBJTYPES ; i++) if (mthing->type == mobjinfo[i].doomednum) break; } if (i == NUMMOBJTYPES) { // [RH] Don't die if the map tries to spawn an unknown thing Printf (PRINT_HIGH, "Unknown type %i at (%i, %i)\n", mthing->type, mthing->x, mthing->y); i = MT_UNKNOWNTHING; } // [RH] If the thing's corresponding sprite has no frames, also map // it to the unknown thing. else if (sprites[states[mobjinfo[i].spawnstate].sprite].numframes == 0) { Printf (PRINT_HIGH, "Type %i at (%i, %i) has no frames\n", mthing->type, mthing->x, mthing->y); i = MT_UNKNOWNTHING; } // don't spawn keycards and players in deathmatch if (deathmatch->value && mobjinfo[i].flags & MF_NOTDMATCH) return; // [RH] don't spawn extra weapons in coop if (netgame && !deathmatch->value) { switch (i) { case MT_CHAINGUN: case MT_SHOTGUN: case MT_SUPERSHOTGUN: case MT_MISC25: // BFG case MT_MISC26: // chainsaw case MT_MISC27: // rocket launcher case MT_MISC28: // plasma gun if ((mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH) return; break; default: break; } } // don't spawn any monsters if -nomonsters if (dmflags & DF_NO_MONSTERS && ( i == MT_SKULL || (mobjinfo[i].flags & MF_COUNTKILL)) ) { return; } // [RH] Other things that shouldn't be spawned depending on dmflags if (deathmatch->value) { spritenum_t sprite = states[mobjinfo[i].spawnstate].sprite; if (dmflags & DF_NO_HEALTH) { switch (sprite) { case SPR_STIM: case SPR_MEDI: case SPR_PSTR: case SPR_BON1: case SPR_SOUL: case SPR_MEGA: return; default: break; } } if (dmflags & DF_NO_ITEMS) { switch (sprite) { case SPR_PINV: case SPR_PSTR: case SPR_PINS: case SPR_SUIT: case SPR_PMAP: case SPR_PVIS: return; default: break; } } if (dmflags & DF_NO_ARMOR) { switch (sprite) { case SPR_ARM1: case SPR_ARM2: case SPR_BON2: case SPR_MEGA: return; default: break; } } } // spawn it x = mthing->x << FRACBITS; y = mthing->y << FRACBITS; if (mobjinfo[i].flags & MF_SPAWNCEILING) z = ONCEILINGZ; else if (mobjinfo[i].flags2 & MF2_SPAWNFLOAT) z = FLOATRANDZ; else if (mobjinfo[i].flags2 & MF2_FLOATBOB) z = mthing->z << FRACBITS; else z = ONFLOORZ; mobj = P_SpawnMobj (x, y, z, i); if (z == ONFLOORZ) mobj->z += mthing->z << FRACBITS; else if (z == ONCEILINGZ) mobj->z -= mthing->z << FRACBITS; mobj->spawnpoint = *mthing; if (mobj->flags2 & MF2_FLOATBOB) { // Seed random starting index for bobbing motion mobj->health = M_Random(); mobj->special1 = mthing->z << FRACBITS; } // [RH] Set the thing's special mobj->special = mthing->special; memcpy (mobj->args, mthing->args, sizeof(mobj->args)); // [RH] If it's an ambient sound, activate it if (i == MT_AMBIENT) S_ActivateAmbient (mobj, mobj->args[0]); // [RH] If a fountain and not dormant, start it if (i == MT_FOUNTAIN && !(mthing->flags & MTF_DORMANT)) mobj->effects = mobj->args[0] << FX_FOUNTAINSHIFT; if (mobj->tics > 0) mobj->tics = 1 + (P_Random (pr_spawnmapthing) % mobj->tics); if (mobj->flags & MF_COUNTKILL) level.total_monsters++; if (mobj->flags & MF_COUNTITEM) level.total_items++; if (i != MT_SPARK) mobj->angle = ANG45 * (mthing->angle/45); if (mthing->flags & MTF_AMBUSH) mobj->flags |= MF_AMBUSH; // [RH] Add ThingID to mobj and link it in with the others mobj->tid = mthing->thingid; P_AddMobjToHash (mobj); // [RH] Go dormant as needed if (mthing->flags & MTF_DORMANT) P_DeactivateMobj (mobj); } // // GAME SPAWN FUNCTIONS // // // P_SpawnPuff // extern fixed_t attackrange; cvar_t *cl_pufftype, *cl_bloodtype; void P_SpawnPuff (fixed_t x, fixed_t y, fixed_t z, angle_t dir, int updown) { mobj_t *th; int t; if (!cl_pufftype->value || (updown == 3)) { t = P_Random (pr_spawnpuff); z += (t - P_Random (pr_spawnpuff)) << 10; th = P_SpawnMobj (x, y, z, MT_PUFF); th->momz = FRACUNIT; th->tics -= P_Random (pr_spawnpuff) & 3; if (th->tics < 1) th->tics = 1; // don't make punches spark on the wall if (attackrange == MELEERANGE) P_SetMobjState (th, S_PUFF3); } else { if (attackrange != MELEERANGE) P_DrawSplash2 (32, x, y, z, dir, updown, 1); } } // // P_SpawnBlood // void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, angle_t dir, int damage) { mobj_t *th; int t; if (cl_bloodtype->value <= 1) { t = P_Random (pr_spawnblood); z += (t - P_Random (pr_spawnblood)) << 10; th = P_SpawnMobj (x, y, z, MT_BLOOD); th->momz = FRACUNIT*2; th->tics -= P_Random (pr_spawnblood) & 3; if (th->tics < 1) th->tics = 1; if (damage <= 12 && damage >= 9) P_SetMobjState (th, S_BLOOD2); else if (damage < 9) P_SetMobjState (th, S_BLOOD3); } if (cl_bloodtype->value >= 1) P_DrawSplash2 (32, x, y, z, dir, 2, 0); } //--------------------------------------------------------------------------- // // FUNC P_HitFloor // //--------------------------------------------------------------------------- #define SMALLSPLASHCLIP 12<floorz != thing->subsector->sector->floorheight) { // don't splash if landing on the edge above water/lava/etc.... return(FLOOR_SOLID); } // Things that don't splash go here switch(thing->type) { case MT_LEAF1: case MT_LEAF2: // case MT_BLOOD: // I set these to low mass -- pm // case MT_BLOODSPLATTER: case MT_SPLASH: case MT_SLUDGECHUNK: return(FLOOR_SOLID); default: break; } // Small splash for small masses if (thing->info->mass < 10) smallsplash = true; switch(P_GetThingFloorType(thing)) { case FLOOR_WATER: if (smallsplash) { mo=P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASHBASE); if (mo) mo->floorclip += SMALLSPLASHCLIP; S_StartSound(mo, SFX_AMBIENT10); // small drip } else { mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASH); mo->target = thing; mo->momx = (P_Random()-P_Random())<<8; mo->momy = (P_Random()-P_Random())<<8; mo->momz = 2*FRACUNIT+(P_Random()<<8); mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SPLASHBASE); if (thing->player) P_NoiseAlert(thing, thing); S_StartSound(mo, SFX_WATER_SPLASH); } return(FLOOR_WATER); case FLOOR_LAVA: if (smallsplash) { mo=P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_LAVASPLASH); if (mo) mo->floorclip += SMALLSPLASHCLIP; } else { mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_LAVASMOKE); mo->momz = FRACUNIT+(P_Random()<<7); mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_LAVASPLASH); if (thing->player) P_NoiseAlert(thing, thing); } S_StartSound(mo, SFX_LAVA_SIZZLE); if(thing->player && leveltime&31) { P_DamageMobj(thing, &LavaInflictor, NULL, 5); } return(FLOOR_LAVA); case FLOOR_SLUDGE: if (smallsplash) { mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SLUDGESPLASH); if (mo) mo->floorclip += SMALLSPLASHCLIP; } else { mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SLUDGECHUNK); mo->target = thing; mo->momx = (P_Random()-P_Random())<<8; mo->momy = (P_Random()-P_Random())<<8; mo->momz = FRACUNIT+(P_Random()<<8); mo = P_SpawnMobj(thing->x, thing->y, ONFLOORZ, MT_SLUDGESPLASH); if (thing->player) P_NoiseAlert(thing, thing); } S_StartSound(mo, SFX_SLUDGE_GLOOP); return(FLOOR_SLUDGE); } return(FLOOR_SOLID); */ return 0; } // // P_CheckMissileSpawn // Moves the missile forward a bit // and possibly explodes it right there. // BOOL P_CheckMissileSpawn (mobj_t* th) { th->tics -= P_Random (pr_checkmissilespawn) & 3; if (th->tics < 1) th->tics = 1; // [RH] Give the missile time to get away from the shooter th->targettic = 10; // move a little forward so an angle can // be computed if it immediately explodes th->x += th->momx>>1; th->y += th->momy>>1; th->z += th->momz>>1; // killough 3/15/98: no dropoff (really = don't care for missiles) if (!P_TryMove (th, th->x, th->y, false)) { P_ExplodeMissile (th); return false; } return true; } // // P_SpawnMissile // mobj_t *P_SpawnMissile (mobj_t *source, mobj_t *dest, mobjtype_t type) { mobj_t* th; angle_t an; int dist; th = P_SpawnMobj (source->x, source->y, source->z + 4*8*FRACUNIT, type); if (th->info->seesound) S_Sound (th, CHAN_VOICE, th->info->seesound, 1, ATTN_NORM); th->target = source; // where it came from an = R_PointToAngle2 (source->x, source->y, dest->x, dest->y); // fuzzy player if (dest->flags & MF_SHADOW) { int t = P_Random (pr_spawnmissile); an += (t - P_Random (pr_spawnmissile)) << 20; } th->angle = an; an >>= ANGLETOFINESHIFT; th->momx = FixedMul (th->info->speed, finecosine[an]); th->momy = FixedMul (th->info->speed, finesine[an]); dist = P_AproxDistance (dest->x - source->x, dest->y - source->y); dist = dist / th->info->speed; if (dist < 1) dist = 1; th->momz = (dest->z - source->z) / dist; P_CheckMissileSpawn (th); return th; } // // P_SpawnPlayerMissile // Tries to aim at a nearby monster // void P_SpawnPlayerMissile (mobj_t *source, mobjtype_t type) { mobj_t* th; angle_t an; fixed_t x; fixed_t y; fixed_t z; fixed_t slope; fixed_t pitchslope; pitchslope = FixedMul (source->pitch, -40960); // see which target is to be aimed at an = source->angle; slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); if (!linetarget) { an += 1<<26; slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); if (!linetarget) { an -= 2<<26; slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); } if (!linetarget) { an = source->angle; // [RH] Use pitch to calculate slope instead of 0. slope = pitchslope; } } if (linetarget && source->player) if (!(dmflags & DF_NO_FREELOOK) && abs(slope - pitchslope) > source->player->userinfo.aimdist) { an = source->angle; slope = pitchslope; } x = source->x; y = source->y; z = source->z + 4*8*FRACUNIT; th = P_SpawnMobj (x, y, z, type); if (th->info->seesound) S_Sound (th, CHAN_VOICE, th->info->seesound, 1, ATTN_NORM); th->target = source; th->angle = an; th->momx = FixedMul( th->info->speed, finecosine[an>>ANGLETOFINESHIFT]); th->momy = FixedMul( th->info->speed, finesine[an>>ANGLETOFINESHIFT]); th->momz = FixedMul( th->info->speed, slope); if (type == MT_ROCKET) th->effects = FX_ROCKET; // [RH] leave a trail P_CheckMissileSpawn (th); } // [RH] Throw gibs around (based on Q2 code) #if 0 // Not used right now (Note that this code considers vectors to // be expressed as fixed_t's.) void VelocityForDamage (int damage, vec3_t v) { v[0] = 8 * crandom(pr_vel4dmg); v[1] = 8 * crandom(pr_vel4dmg); v[2] = 8 * FRACUNIT + (fixed_t)(P_Random(pr_vel4dmg) * (6553600/256)); if (damage < 50) VectorScale (v, 0.7f, v); else VectorScale (v, 1.2f, v); } void ClipGibVelocity (mobj_t *ent) { if (ent->momx < -15 * FRACUNIT) ent->momx = -15 * FRACUNIT; else if (ent->momx > 15 * FRACUNIT) ent->momx = 15 * FRACUNIT; if (ent->momy < -15 * FRACUNIT) ent->momy = -15 * FRACUNIT; else if (ent->momy > 15 * FRACUNIT) ent->momy = 15 * FRACUNIT; if (ent->momz < 6 * FRACUNIT) ent->momz = 6 * FRACUNIT; // always some upwards else if (ent->momz > 10 * FRACUNIT) ent->momz = 10 * FRACUNIT; } void ThrowGib (mobj_t *self, mobjtype_t gibtype, int damage) { mobj_t *gib; vec3_t vd; vec3_t selfvel; vec3_t gibvel; gib = P_SpawnMobj (self->x + crandom(pr_throwgib) * (self->radius >> (FRACBITS + 1)), self->y + crandom(pr_throwgib) * (self->radius >> (FRACBITS + 1)), self->z + (self->height >> 1) + crandom(pr_throwgib) * (self->height >> (FRACBITS + 1)), gibtype); VelocityForDamage (damage, vd); selfvel[0] = self->momx; selfvel[1] = self->momy; selfvel[2] = self->momz; VectorMA (selfvel, 1.0, vd, gibvel); gib->momx = gibvel[0]; gib->momy = gibvel[1]; gib->momz = gibvel[2]; ClipGibVelocity (gib); } #else void ThrowGib (mobj_t *self, mobjtype_t gibtype, int damage) { } #endif