SRB2/src/p_mobj.c
Lactozilla e7f581a958 Remove MAXRADIUS adjustment from bounding box calculations
This was added by the Doom developers to work around the issue where
objects were not linked into all blockmap cells they were touching, and
instead just the one at the object's center. Since this is no longer
the case after commit 41613d89a1,
this adjustment is no longer necessary.
2024-12-13 20:21:34 -03:00

14359 lines
383 KiB
C

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file p_mobj.c
/// \brief Moving object handling. Spawn functions
#include "dehacked.h"
#include "doomdef.h"
#include "g_game.h"
#include "g_input.h"
#include "st_stuff.h"
#include "hu_stuff.h"
#include "p_local.h"
#include "p_setup.h"
#include "r_fps.h"
#include "r_main.h"
#include "r_skins.h"
#include "r_sky.h"
#include "r_splats.h"
#include "s_sound.h"
#include "z_zone.h"
#include "m_random.h"
#include "m_cheat.h"
#include "m_misc.h"
#include "info.h"
#include "i_video.h"
#include "lua_hook.h"
#include "b_bot.h"
#include "p_slopes.h"
#include "f_finale.h"
#include "m_cond.h"
#include "netcode/net_command.h"
static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
actioncache_t actioncachehead;
static mobj_t *overlaycap = NULL;
mobj_t *mobjcache = NULL;
void P_InitCachedActions(void)
{
actioncachehead.prev = actioncachehead.next = &actioncachehead;
}
void P_RunCachedActions(void)
{
actioncache_t *ac;
actioncache_t *next;
for (ac = actioncachehead.next; ac != &actioncachehead; ac = next)
{
var1 = states[ac->statenum].var1;
var2 = states[ac->statenum].var2;
astate = &states[ac->statenum];
if (ac->mobj && !P_MobjWasRemoved(ac->mobj)) // just in case...
states[ac->statenum].action.acp1(ac->mobj);
next = ac->next;
Z_Free(ac);
}
}
void P_AddCachedAction(mobj_t *mobj, INT32 statenum)
{
actioncache_t *newaction = Z_Calloc(sizeof(actioncache_t), PU_LEVEL, NULL);
newaction->mobj = mobj;
newaction->statenum = statenum;
actioncachehead.prev->next = newaction;
newaction->next = &actioncachehead;
newaction->prev = actioncachehead.prev;
actioncachehead.prev = newaction;
}
//
// P_SetupStateAnimation
//
static void P_SetupStateAnimation(mobj_t *mobj, state_t *st)
{
INT32 animlength;
if (mobj->sprite == SPR_PLAY && mobj->skin)
{
spritedef_t *spritedef = P_GetSkinSpritedef(mobj->skin, mobj->sprite2);
animlength = (INT32)(spritedef->numframes) - 1;
}
else
animlength = st->var1;
if (!(st->frame & FF_ANIMATE))
return;
if (animlength <= 0 || st->var2 == 0)
{
mobj->frame &= ~FF_ANIMATE;
return; // Crash/stupidity prevention
}
mobj->anim_duration = (UINT16)st->var2;
if (st->frame & FF_GLOBALANIM)
{
// Attempt to account for the pre-ticker for objects spawned on load
if (!leveltime) return;
mobj->anim_duration -= (leveltime + 2) % st->var2; // Duration synced to timer
mobj->frame += ((leveltime + 2) / st->var2) % (animlength + 1); // Frame synced to timer (duration taken into account)
}
else if (st->frame & FF_RANDOMANIM)
{
mobj->frame += P_RandomKey(animlength + 1); // Random starting frame
mobj->anim_duration -= P_RandomKey(st->var2); // Random duration for first frame
}
}
//
// P_CycleStateAnimation
//
FUNCINLINE static ATTRINLINE void P_CycleStateAnimation(mobj_t *mobj)
{
// var2 determines delay between animation frames
if (!(mobj->frame & FF_ANIMATE) || --mobj->anim_duration != 0)
return;
mobj->anim_duration = (UINT16)mobj->state->var2;
if (mobj->sprite != SPR_PLAY)
{
// compare the current sprite frame to the one we started from
// if more than var1 away from it, swap back to the original
// else just advance by one
if (((++mobj->frame) & FF_FRAMEMASK) - (mobj->state->frame & FF_FRAMEMASK) > (UINT32)mobj->state->var1)
mobj->frame = (mobj->state->frame & FF_FRAMEMASK) | (mobj->frame & ~FF_FRAMEMASK);
return;
}
// sprite2 version of above
if (mobj->skin)
{
spritedef_t *spritedef = P_GetSkinSpritedef(mobj->skin, mobj->sprite2);
UINT32 anim_length = (UINT32)(spritedef->numframes);
if (((++mobj->frame) & FF_FRAMEMASK) >= anim_length)
mobj->frame &= ~FF_FRAMEMASK;
}
}
//
// P_CycleMobjState
//
static void P_CycleMobjState(mobj_t *mobj)
{
// state animations
P_CycleStateAnimation(mobj);
// 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 && mobj->state)
if (!P_SetMobjState(mobj, mobj->state->nextstate))
return; // freed itself
}
}
//
// P_CycleMobjState for players.
//
static void P_CyclePlayerMobjState(mobj_t *mobj)
{
// state animations
P_CycleStateAnimation(mobj);
// 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 && mobj->state)
if (!P_SetMobjState(mobj, mobj->state->nextstate))
return; // freed itself
}
}
//
// P_SetPlayerMobjState
// Returns true if the mobj is still present.
//
// Separate from P_SetMobjState because of the pw_flashing check and Super states
//
static boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
{
state_t *st;
player_t *player = mobj->player;
// remember states seen, to detect cycles:
static statenum_t seenstate_tab[NUMSTATES]; // fast transition table
statenum_t *seenstate = seenstate_tab; // pointer to table
static INT32 recursion; // detects recursion
statenum_t i; // initial state
statenum_t tempstate[NUMSTATES]; // for use with recursion
#ifdef PARANOIA
if (player == NULL)
I_Error("P_SetPlayerMobjState used for non-player mobj. Use P_SetMobjState instead!\n(Mobj type: %d, State: %d)", mobj->type, state);
#endif
// Catch falling for nojumpspin
if ((state == S_PLAY_JUMP) && (player->charflags & SF_NOJUMPSPIN) && (P_MobjFlip(mobj)*mobj->momz < 0))
return P_SetPlayerMobjState(mobj, S_PLAY_FALL);
// Catch swimming versus flying
if ((state == S_PLAY_FLY || (state == S_PLAY_GLIDE && skins[player->skin]->sprites[SPR2_SWIM].numframes))
&& player->mo->eflags & MFE_UNDERWATER && !player->skidtime)
return P_SetPlayerMobjState(player->mo, S_PLAY_SWIM);
else if (state == S_PLAY_SWIM && !(player->mo->eflags & MFE_UNDERWATER))
{
if (player->charability == CA_GLIDEANDCLIMB)
return P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
else
return P_SetPlayerMobjState(player->mo, S_PLAY_FLY);
}
// Catch SF_NOSUPERSPIN jumps for Supers
if (player->powers[pw_super] && (player->charflags & SF_NOSUPERSPIN))
{
if (state == S_PLAY_JUMP)
{
if (player->mo->state-states == S_PLAY_WALK)
return P_SetPlayerMobjState(mobj, S_PLAY_FLOAT);
return true;
}
else if (player->mo->state-states == S_PLAY_FLOAT && state == S_PLAY_STND)
return true;
}
// You were in pain state after taking a hit, and you're moving out of pain state now?
else if (mobj->state == &states[mobj->info->painstate] && player->powers[pw_flashing] == flashingtics && state != mobj->info->painstate)
{
// Start flashing, since you've landed.
player->powers[pw_flashing] = flashingtics-1;
P_DoPityCheck(player);
}
// Set animation state
// The pflags version of this was just as convoluted.
switch(state)
{
case S_PLAY_STND:
case S_PLAY_WAIT:
case S_PLAY_NIGHTS_STAND:
player->panim = PA_IDLE;
break;
case S_PLAY_EDGE:
player->panim = PA_EDGE;
break;
case S_PLAY_WALK:
case S_PLAY_SKID:
case S_PLAY_FLOAT:
player->panim = PA_WALK;
break;
case S_PLAY_RUN:
case S_PLAY_FLOAT_RUN:
player->panim = PA_RUN;
break;
case S_PLAY_DASH:
player->panim = PA_DASH;
break;
case S_PLAY_PAIN:
case S_PLAY_STUN:
player->panim = PA_PAIN;
break;
case S_PLAY_ROLL:
//case S_PLAY_SPINDASH: -- everyone can ROLL thanks to zoom tubes...
case S_PLAY_NIGHTS_ATTACK:
player->panim = PA_ROLL;
break;
case S_PLAY_JUMP:
player->panim = PA_JUMP;
break;
case S_PLAY_SPRING:
player->panim = PA_SPRING;
break;
case S_PLAY_FALL:
case S_PLAY_NIGHTS_FLOAT:
player->panim = PA_FALL;
break;
case S_PLAY_FLY:
case S_PLAY_FLY_TIRED:
case S_PLAY_SWIM:
case S_PLAY_GLIDE:
case S_PLAY_BOUNCE:
case S_PLAY_BOUNCE_LANDING:
case S_PLAY_TWINSPIN:
player->panim = PA_ABILITY;
break;
case S_PLAY_SPINDASH: // ...but the act of SPINDASHING is charability2 specific.
case S_PLAY_FIRE:
case S_PLAY_FIRE_FINISH:
case S_PLAY_MELEE:
case S_PLAY_MELEE_FINISH:
case S_PLAY_MELEE_LANDING:
player->panim = PA_ABILITY2;
break;
case S_PLAY_RIDE:
player->panim = PA_RIDE;
break;
default:
player->panim = PA_ETC;
break;
}
if (recursion++) // if recursion detected,
memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table
i = state;
do
{
if (state == S_NULL)
{ // Bad SOC!
CONS_Alert(CONS_ERROR, "Cannot remove player mobj by setting its state to S_NULL.\n");
//P_RemoveMobj(mobj);
return false;
}
st = &states[state];
mobj->state = st;
mobj->tics = st->tics;
// Adjust the player's animation speed to match their velocity.
if (player->panim == PA_EDGE && (player->charflags & SF_FASTEDGE))
mobj->tics = 2;
else if (!(disableSpeedAdjust || player->charflags & SF_NOSPEEDADJUST))
{
fixed_t speed;// = FixedDiv(player->speed, FixedMul(mobj->scale, player->mo->movefactor));
if (player->panim == PA_FALL)
{
speed = FixedDiv(abs(mobj->momz), mobj->scale);
if (speed < 10<<FRACBITS)
mobj->tics = 4;
else if (speed < 20<<FRACBITS)
mobj->tics = 3;
else if (speed < 30<<FRACBITS)
mobj->tics = 2;
else
mobj->tics = 1;
}
else if (player->panim == PA_ABILITY2 && player->charability2 == CA2_SPINDASH)
{
fixed_t step = (player->maxdash - player->mindash)/4;
speed = (player->dashspeed - player->mindash);
if (speed > 3*step)
mobj->tics = 1;
else if (speed > step)
mobj->tics = 2;
else
mobj->tics = 3;
}
else
{
speed = FixedDiv(player->speed, FixedMul(mobj->scale, player->mo->movefactor));
if (player->panim == PA_ROLL || player->panim == PA_JUMP)
{
if (speed > 16<<FRACBITS)
mobj->tics = 1;
else
mobj->tics = 2;
}
else if (P_IsObjectOnGround(mobj) || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]) // Only if on the ground or superflying.
{
if (player->panim == PA_WALK)
{
if (speed > 12<<FRACBITS)
mobj->tics = 2;
else if (speed > 6<<FRACBITS)
mobj->tics = 3;
else
mobj->tics = 4;
}
else if ((player->panim == PA_RUN) || (player->panim == PA_DASH))
{
if (speed > 52<<FRACBITS)
mobj->tics = 1;
else
mobj->tics = 2;
}
}
}
}
// Player animations
if (st->sprite == SPR_PLAY)
{
skin_t *skin = ((skin_t *)mobj->skin);
UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
UINT8 numframes;
UINT16 spr2;
if (skin)
{
spr2 = P_GetStateSprite2(st);
// Add or remove SPR2F_SUPER based on certain conditions
spr2 = P_ApplySuperFlagToSprite2(spr2, mobj);
// Get the needed sprite2 and frame number
spr2 = P_GetSkinSprite2(skin, spr2, mobj->player);
spritedef_t *sprdef = P_GetSkinSpritedef(skin, spr2);
numframes = sprdef->numframes;
if (state == S_PLAY_STND && (spr2 & SPR2F_SUPER) && sprdef[SPR2_WAIT].numframes == 0)
mobj->tics = -1; // If no super wait, don't wait at all
}
else
{
spr2 = 0;
frame = 0;
numframes = 0;
}
if (mobj->sprite != SPR_PLAY)
{
mobj->sprite = SPR_PLAY;
frame = P_GetSprite2StateFrame(st);
}
else if (mobj->sprite2 != spr2)
{
if (st->frame & FF_SPR2MIDSTART)
{
if (numframes && P_RandomChance(FRACUNIT/2))
frame = numframes/2;
else
frame = 0;
}
else if (numframes)
frame = P_GetSprite2StateFrame(st) % numframes;
else
frame = 0;
}
if (frame >= numframes)
{
if (st->frame & FF_SPR2ENDSTATE) // no frame advancement
{
if (st->var1 == mobj->state-states)
frame--;
else
{
if (mobj->frame & FF_FRAMEMASK)
mobj->frame--;
return P_SetPlayerMobjState(mobj, st->var1);
}
}
else
frame = 0;
}
mobj->sprite2 = spr2;
mobj->frame = frame|(st->frame&~FF_FRAMEMASK);
if (P_PlayerFullbright(player))
mobj->frame |= FF_FULLBRIGHT;
}
// Regular sprites
else
{
mobj->sprite = st->sprite;
mobj->frame = st->frame;
}
P_SetupStateAnimation(mobj, st);
// Modified handling.
// Call action functions when the state is set
if (st->action.acp1)
{
var1 = st->var1;
var2 = st->var2;
astate = st;
st->action.acp1(mobj);
// woah. a player was removed by an action.
// this sounds like a VERY BAD THING, but there's nothing we can do now...
if (P_MobjWasRemoved(mobj))
return false;
}
seenstate[state] = 1 + st->nextstate;
state = st->nextstate;
} while (!mobj->tics && !seenstate[state]);
if (!mobj->tics)
CONS_Alert(CONS_WARNING, M_GetText("State cycle detected, exiting.\n"));
if (!--recursion)
for (;(state = seenstate[i]) > S_NULL; i = state - 1)
seenstate[i] = S_NULL; // erase memory of states
return true;
}
boolean P_SetMobjState(mobj_t *mobj, statenum_t state)
{
state_t *st;
// remember states seen, to detect cycles:
static statenum_t seenstate_tab[NUMSTATES]; // fast transition table
statenum_t *seenstate = seenstate_tab; // pointer to table
static INT32 recursion; // detects recursion
statenum_t i = state; // initial state
statenum_t tempstate[NUMSTATES]; // for use with recursion
if (mobj->player != NULL)
return P_SetPlayerMobjState(mobj, state);
if (recursion++) // if recursion detected,
memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table
do
{
if (state == S_NULL)
{
P_RemoveMobj(mobj);
return false;
}
st = &states[state];
mobj->state = st;
mobj->tics = st->tics;
// Player animations
if (st->sprite == SPR_PLAY)
{
skin_t *skin = ((skin_t *)mobj->skin);
UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
UINT8 numframes;
UINT16 spr2;
if (skin)
{
spr2 = P_GetStateSprite2(st);
// Add or remove SPR2F_SUPER based on certain conditions
spr2 = P_ApplySuperFlagToSprite2(spr2, mobj);
// Get the needed sprite2 and frame number
spr2 = P_GetSkinSprite2(skin, spr2, NULL);
spritedef_t *sprdef = P_GetSkinSpritedef(skin, spr2);
numframes = sprdef->numframes;
if (state == S_PLAY_STND && (spr2 & SPR2F_SUPER) && sprdef[SPR2_WAIT].numframes == 0)
mobj->tics = -1; // If no super wait, don't wait at all
}
else
{
spr2 = 0;
frame = 0;
numframes = 0;
}
if (mobj->sprite != SPR_PLAY)
{
mobj->sprite = SPR_PLAY;
frame = P_GetSprite2StateFrame(st);
}
else if (mobj->sprite2 != spr2)
{
if (st->frame & FF_SPR2MIDSTART)
{
if (numframes && P_RandomChance(FRACUNIT/2))
frame = numframes/2;
else
frame = 0;
}
else if (numframes)
frame = P_GetSprite2StateFrame(st) % numframes;
else
frame = 0;
}
if (frame >= numframes)
{
if (st->frame & FF_SPR2ENDSTATE) // no frame advancement
{
if (st->var1 == mobj->state-states)
frame--;
else
{
if (mobj->frame & FF_FRAMEMASK)
mobj->frame--;
return P_SetMobjState(mobj, st->var1);
}
}
else
frame = 0;
}
mobj->sprite2 = spr2;
mobj->frame = frame|(st->frame&~FF_FRAMEMASK);
}
// Regular sprites
else
{
mobj->sprite = st->sprite;
mobj->frame = st->frame;
}
P_SetupStateAnimation(mobj, st);
// Modified handling.
// Call action functions when the state is set
if (st->action.acp1)
{
var1 = st->var1;
var2 = st->var2;
astate = st;
st->action.acp1(mobj);
if (P_MobjWasRemoved(mobj))
return false;
}
seenstate[state] = 1 + st->nextstate;
state = st->nextstate;
} while (!mobj->tics && !seenstate[state]);
if (!mobj->tics)
CONS_Alert(CONS_WARNING, M_GetText("State cycle detected, exiting.\n"));
if (!--recursion)
for (;(state = seenstate[i]) > S_NULL; i = state - 1)
seenstate[i] = S_NULL; // erase memory of states
return true;
}
//----------------------------------------------------------------------------
//
// FUNC P_SetMobjStateNF
//
// Same as P_SetMobjState, but does not call the state function.
//
//----------------------------------------------------------------------------
boolean P_SetMobjStateNF(mobj_t *mobj, statenum_t state)
{
state_t *st;
if (state == S_NULL)
{ // Remove mobj
P_RemoveMobj(mobj);
return false;
}
st = &states[state];
mobj->state = st;
mobj->tics = st->tics;
mobj->sprite = st->sprite;
mobj->frame = st->frame;
P_SetupStateAnimation(mobj, st);
return true;
}
static boolean P_SetPrecipMobjState(precipmobj_t *mobj, statenum_t state)
{
state_t *st;
if (state == S_NULL)
{ // Remove mobj
P_RemovePrecipMobj(mobj);
return false;
}
st = &states[state];
mobj->state = st;
mobj->tics = st->tics;
mobj->sprite = st->sprite;
mobj->frame = st->frame;
P_SetupStateAnimation((mobj_t*)mobj, st);
return true;
}
//
// P_MobjFlip
//
// Special utility to return +1 or -1 depending on mobj's gravity
//
SINT8 P_MobjFlip(mobj_t *mobj)
{
if (mobj && mobj->eflags & MFE_VERTICALFLIP)
return -1;
return 1;
}
//
// P_WeaponOrPanel
//
// Returns true if weapon ring/panel; otherwise returns false
//
boolean P_WeaponOrPanel(mobjtype_t type)
{
if (type == MT_BOUNCERING
|| type == MT_AUTOMATICRING
|| type == MT_INFINITYRING
|| type == MT_RAILRING
|| type == MT_EXPLOSIONRING
|| type == MT_SCATTERRING
|| type == MT_GRENADERING
|| type == MT_BOUNCEPICKUP
|| type == MT_RAILPICKUP
|| type == MT_AUTOPICKUP
|| type == MT_EXPLODEPICKUP
|| type == MT_SCATTERPICKUP
|| type == MT_GRENADEPICKUP)
return true;
return false;
}
//
// P_EmeraldManager
//
// Power Stone emerald management
//
void P_EmeraldManager(void)
{
thinker_t *think;
mobj_t *mo;
INT32 i,j;
INT32 numtospawn;
INT32 emeraldsspawned = 0;
boolean hasemerald[MAXHUNTEMERALDS];
INT32 numwithemerald = 0;
// record empty spawn points
mobj_t *spawnpoints[MAXHUNTEMERALDS];
INT32 numspawnpoints = 0;
for (i = 0; i < MAXHUNTEMERALDS; i++)
{
hasemerald[i] = false;
spawnpoints[i] = NULL;
}
for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
{
if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo = (mobj_t *)think;
if (mo->type == MT_EMERALDSPAWN)
{
if (mo->threshold || mo->target) // Either has the emerald spawned or is spawning
{
numwithemerald++;
emeraldsspawned |= mobjinfo[mo->reactiontime].speed;
}
else if (numspawnpoints < MAXHUNTEMERALDS)
spawnpoints[numspawnpoints++] = mo; // empty spawn points
}
else if (mo->type == MT_FLINGEMERALD)
{
numwithemerald++;
emeraldsspawned |= mo->threshold;
}
}
if (numspawnpoints == 0)
return;
// But wait! We need to check all the players too, to see if anyone has some of the emeralds.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (!players[i].mo)
continue;
if (players[i].powers[pw_emeralds] & EMERALD1)
{
numwithemerald++;
emeraldsspawned |= EMERALD1;
}
if (players[i].powers[pw_emeralds] & EMERALD2)
{
numwithemerald++;
emeraldsspawned |= EMERALD2;
}
if (players[i].powers[pw_emeralds] & EMERALD3)
{
numwithemerald++;
emeraldsspawned |= EMERALD3;
}
if (players[i].powers[pw_emeralds] & EMERALD4)
{
numwithemerald++;
emeraldsspawned |= EMERALD4;
}
if (players[i].powers[pw_emeralds] & EMERALD5)
{
numwithemerald++;
emeraldsspawned |= EMERALD5;
}
if (players[i].powers[pw_emeralds] & EMERALD6)
{
numwithemerald++;
emeraldsspawned |= EMERALD6;
}
if (players[i].powers[pw_emeralds] & EMERALD7)
{
numwithemerald++;
emeraldsspawned |= EMERALD7;
}
}
// All emeralds spawned, no worries
if (numwithemerald >= 7)
return;
// Set up spawning for the emeralds
numtospawn = 7 - numwithemerald;
#ifdef PARANOIA
if (numtospawn <= 0) // ???
I_Error("P_EmeraldManager: numtospawn is %d!\n", numtospawn);
#endif
for (i = 0, j = 0; i < numtospawn; i++)
{
INT32 tries = 0;
while (true)
{
tries++;
if (tries > 50)
break;
j = P_RandomKey(numspawnpoints);
if (hasemerald[j])
continue;
hasemerald[j] = true;
if (!(emeraldsspawned & EMERALD1))
{
spawnpoints[j]->reactiontime = MT_EMERALD1;
emeraldsspawned |= EMERALD1;
}
else if (!(emeraldsspawned & EMERALD2))
{
spawnpoints[j]->reactiontime = MT_EMERALD2;
emeraldsspawned |= EMERALD2;
}
else if (!(emeraldsspawned & EMERALD3))
{
spawnpoints[j]->reactiontime = MT_EMERALD3;
emeraldsspawned |= EMERALD3;
}
else if (!(emeraldsspawned & EMERALD4))
{
spawnpoints[j]->reactiontime = MT_EMERALD4;
emeraldsspawned |= EMERALD4;
}
else if (!(emeraldsspawned & EMERALD5))
{
spawnpoints[j]->reactiontime = MT_EMERALD5;
emeraldsspawned |= EMERALD5;
}
else if (!(emeraldsspawned & EMERALD6))
{
spawnpoints[j]->reactiontime = MT_EMERALD6;
emeraldsspawned |= EMERALD6;
}
else if (!(emeraldsspawned & EMERALD7))
{
spawnpoints[j]->reactiontime = MT_EMERALD7;
emeraldsspawned |= EMERALD7;
}
else
break;
spawnpoints[j]->threshold = emeraldspawndelay + P_RandomByte() * (TICRATE/5);
break;
}
}
emeraldspawndelay = 0;
}
//
// P_ExplodeMissile
//
void P_ExplodeMissile(mobj_t *mo)
{
mobj_t *explodemo;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
mo->momx = mo->momy = mo->momz = 0;
if (mo->flags & MF_NOCLIPTHING)
return;
if (mo->type == MT_DETON)
{
P_RadiusAttack(mo, mo, 96*FRACUNIT, 0, true);
explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
if (!P_MobjWasRemoved(explodemo))
{
P_SetScale(explodemo, mo->scale, true);
explodemo->destscale = mo->destscale;
explodemo->momx += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
explodemo->momy += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
S_StartSound(explodemo, sfx_pop);
}
explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
if (!P_MobjWasRemoved(explodemo))
{
P_SetScale(explodemo, mo->scale, true);
explodemo->destscale = mo->destscale;
explodemo->momx += (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
explodemo->momy -= (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
S_StartSound(explodemo, sfx_dmpain);
}
explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
if (!P_MobjWasRemoved(explodemo))
{
P_SetScale(explodemo, mo->scale, true);
explodemo->destscale = mo->destscale;
explodemo->momx -= (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
explodemo->momy += (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
S_StartSound(explodemo, sfx_pop);
}
explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
if (!P_MobjWasRemoved(explodemo))
{
P_SetScale(explodemo, mo->scale, true);
explodemo->destscale = mo->destscale;
explodemo->momx -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
explodemo->momy -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
S_StartSound(explodemo, sfx_cybdth);
}
}
mo->flags &= ~MF_MISSILE;
mo->flags |= MF_NOGRAVITY; // Dead missiles don't need to sink anymore.
mo->flags |= MF_NOCLIPTHING; // Dummy flag to indicate that this was already called.
if (mo->info->deathsound && !(mo->flags2 & MF2_DEBRIS))
S_StartSound(mo, mo->info->deathsound);
P_SetMobjState(mo, mo->info->deathstate);
}
// P_InsideANonSolidFFloor
//
// Returns TRUE if mobj is inside a non-solid 3d floor.
boolean P_InsideANonSolidFFloor(mobj_t *mobj, ffloor_t *rover)
{
fixed_t topheight, bottomheight;
if (!(rover->fofflags & FOF_EXISTS))
return false;
if ((((rover->fofflags & FOF_BLOCKPLAYER) && mobj->player)
|| ((rover->fofflags & FOF_BLOCKOTHERS) && !mobj->player)))
return false;
topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y);
bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y);
if (mobj->z > topheight)
return false;
if (mobj->z + mobj->height < bottomheight)
return false;
return true;
}
// P_GetFloorZ (and its ceiling counterpart)
// Gets the floor height (or ceiling height) of the mobj's contact point in sector, assuming object's center if moved to [x, y]
// If line is supplied, it's a divider line on the sector. Set it to NULL if you're not checking for collision with a line
// Supply boundsec ONLY when checking for specials! It should be the "in-level" sector, and sector the control sector (if separate).
// If set, then this function will iterate through boundsec's linedefs to find the highest contact point on the slope. Non-special-checking
// usage will handle that later.
static fixed_t HighestOnLine(fixed_t radius, fixed_t x, fixed_t y, line_t *line, pslope_t *slope, boolean actuallylowest)
{
// Alright, so we're sitting on a line that contains our slope sector, and need to figure out the highest point we're touching...
// The solution is simple! Get the line's vertices, and pull each one in along its line until it touches the object's bounding box
// (assuming it isn't already inside), then test each point's slope Z and return the higher of the two.
vertex_t v1, v2;
v1.x = line->v1->x;
v1.y = line->v1->y;
v2.x = line->v2->x;
v2.y = line->v2->y;
/*CONS_Printf("BEFORE: v1 = %f %f %f\n",
FIXED_TO_FLOAT(v1.x),
FIXED_TO_FLOAT(v1.y),
FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v1.x, v1.y))
);
CONS_Printf(" v2 = %f %f %f\n",
FIXED_TO_FLOAT(v2.x),
FIXED_TO_FLOAT(v2.y),
FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v2.x, v2.y))
);*/
if (abs(v1.x-x) > radius) {
// v1's x is out of range, so rein it in
fixed_t diff = abs(v1.x-x) - radius;
if (v1.x < x) { // Moving right
v1.x += diff;
v1.y += FixedMul(diff, FixedDiv(line->dy, line->dx));
} else { // Moving left
v1.x -= diff;
v1.y -= FixedMul(diff, FixedDiv(line->dy, line->dx));
}
}
if (abs(v1.y-y) > radius) {
// v1's y is out of range, so rein it in
fixed_t diff = abs(v1.y-y) - radius;
if (v1.y < y) { // Moving up
v1.y += diff;
v1.x += FixedMul(diff, FixedDiv(line->dx, line->dy));
} else { // Moving down
v1.y -= diff;
v1.x -= FixedMul(diff, FixedDiv(line->dx, line->dy));
}
}
if (abs(v2.x-x) > radius) {
// v1's x is out of range, so rein it in
fixed_t diff = abs(v2.x-x) - radius;
if (v2.x < x) { // Moving right
v2.x += diff;
v2.y += FixedMul(diff, FixedDiv(line->dy, line->dx));
} else { // Moving left
v2.x -= diff;
v2.y -= FixedMul(diff, FixedDiv(line->dy, line->dx));
}
}
if (abs(v2.y-y) > radius) {
// v2's y is out of range, so rein it in
fixed_t diff = abs(v2.y-y) - radius;
if (v2.y < y) { // Moving up
v2.y += diff;
v2.x += FixedMul(diff, FixedDiv(line->dx, line->dy));
} else { // Moving down
v2.y -= diff;
v2.x -= FixedMul(diff, FixedDiv(line->dx, line->dy));
}
}
/*CONS_Printf("AFTER: v1 = %f %f %f\n",
FIXED_TO_FLOAT(v1.x),
FIXED_TO_FLOAT(v1.y),
FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v1.x, v1.y))
);
CONS_Printf(" v2 = %f %f %f\n",
FIXED_TO_FLOAT(v2.x),
FIXED_TO_FLOAT(v2.y),
FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v2.x, v2.y))
);*/
// Return the higher of the two points
if (actuallylowest)
return min(
P_GetSlopeZAt(slope, v1.x, v1.y),
P_GetSlopeZAt(slope, v2.x, v2.y)
);
else
return max(
P_GetSlopeZAt(slope, v1.x, v1.y),
P_GetSlopeZAt(slope, v2.x, v2.y)
);
}
fixed_t P_MobjFloorZ(sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, fixed_t radius, line_t *line, boolean lowest, boolean perfect)
{
I_Assert(sector != NULL);
if (sector->f_slope) {
fixed_t testx, testy;
pslope_t *slope = sector->f_slope;
// Get the corner of the object that should be the highest on the slope
if (slope->d.x < 0)
testx = radius;
else
testx = -radius;
if (slope->d.y < 0)
testy = radius;
else
testy = -radius;
if ((slope->zdelta > 0) ^ !!(lowest)) {
testx = -testx;
testy = -testy;
}
testx += x;
testy += y;
// If the highest point is in the sector, then we have it easy! Just get the Z at that point
if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy))
return P_GetSlopeZAt(slope, testx, testy);
// If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
if (perfect && boundsec) {
size_t i;
line_t *ld;
fixed_t bbox[4];
fixed_t finalheight;
if (lowest)
finalheight = INT32_MAX;
else
finalheight = INT32_MIN;
bbox[BOXLEFT] = x-radius;
bbox[BOXRIGHT] = x+radius;
bbox[BOXTOP] = y+radius;
bbox[BOXBOTTOM] = y-radius;
for (i = 0; i < boundsec->linecount; i++) {
ld = boundsec->lines[i];
if (bbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || bbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
|| bbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || bbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
continue;
if (P_BoxOnLineSide(bbox, ld) != -1)
continue;
if (lowest)
finalheight = min(finalheight, HighestOnLine(radius, x, y, ld, slope, true));
else
finalheight = max(finalheight, HighestOnLine(radius, x, y, ld, slope, false));
}
return finalheight;
}
// If we're just testing for base sector location (no collision line), just go for the center's spot...
// It'll get fixed when we test for collision anyway, and the final result can't be lower than this
if (line == NULL)
return P_GetSlopeZAt(slope, x, y);
return HighestOnLine(radius, x, y, line, slope, lowest);
} else // Well, that makes it easy. Just get the floor height
return sector->floorheight;
}
fixed_t P_MobjCeilingZ(sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, fixed_t radius, line_t *line, boolean lowest, boolean perfect)
{
I_Assert(sector != NULL);
if (sector->c_slope) {
fixed_t testx, testy;
pslope_t *slope = sector->c_slope;
// Get the corner of the object that should be the highest on the slope
if (slope->d.x < 0)
testx = radius;
else
testx = -radius;
if (slope->d.y < 0)
testy = radius;
else
testy = -radius;
if ((slope->zdelta > 0) ^ !!(lowest)) {
testx = -testx;
testy = -testy;
}
testx += x;
testy += y;
// If the highest point is in the sector, then we have it easy! Just get the Z at that point
if (R_IsPointInSector(boundsec ? boundsec : sector, testx, testy))
return P_GetSlopeZAt(slope, testx, testy);
// If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
if (perfect && boundsec) {
size_t i;
line_t *ld;
fixed_t bbox[4];
fixed_t finalheight;
if (lowest)
finalheight = INT32_MAX;
else
finalheight = INT32_MIN;
bbox[BOXLEFT] = x-radius;
bbox[BOXRIGHT] = x+radius;
bbox[BOXTOP] = y+radius;
bbox[BOXBOTTOM] = y-radius;
for (i = 0; i < boundsec->linecount; i++) {
ld = boundsec->lines[i];
if (bbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || bbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
|| bbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || bbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
continue;
if (P_BoxOnLineSide(bbox, ld) != -1)
continue;
if (lowest)
finalheight = min(finalheight, HighestOnLine(radius, x, y, ld, slope, true));
else
finalheight = max(finalheight, HighestOnLine(radius, x, y, ld, slope, false));
}
return finalheight;
}
// If we're just testing for base sector location (no collision line), just go for the center's spot...
// It'll get fixed when we test for collision anyway, and the final result can't be lower than this
if (line == NULL)
return P_GetSlopeZAt(slope, x, y);
return HighestOnLine(radius, x, y, line, slope, lowest);
} else // Well, that makes it easy. Just get the ceiling height
return sector->ceilingheight;
}
static void P_PlayerFlip(mobj_t *mo)
{
if (!mo->player)
return;
G_GhostAddFlip();
// Flip aiming to match!
if (mo->player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS doesn't use flipcam
;
else if (mo->player->pflags & PF_FLIPCAM)
{
mo->player->aiming = InvAngle(mo->player->aiming);
if (mo->player-players == displayplayer)
{
localaiming = mo->player->aiming;
if (camera.chase) {
camera.aiming = InvAngle(camera.aiming);
camera.z = mo->z - camera.z + mo->z;
if (mo->eflags & MFE_VERTICALFLIP)
camera.z += FixedMul(20*FRACUNIT, mo->scale);
}
}
else if (mo->player-players == secondarydisplayplayer)
{
localaiming2 = mo->player->aiming;
if (camera2.chase) {
camera2.aiming = InvAngle(camera2.aiming);
camera2.z = mo->z - camera2.z + mo->z;
if (mo->eflags & MFE_VERTICALFLIP)
camera2.z += FixedMul(20*FRACUNIT, mo->scale);
}
}
}
}
//
// P_GetMobjGravity
//
// Returns the current gravity
// value of the object.
//
fixed_t P_GetMobjGravity(mobj_t *mo)
{
fixed_t gravityadd = 0;
sector_t *gravsector = NULL; // Custom gravity
boolean goopgravity = false;
boolean wasflip;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
wasflip = (mo->eflags & MFE_VERTICALFLIP) != 0;
mo->eflags &= ~MFE_VERTICALFLIP;
if (mo->subsector->sector->ffloors) // Check for 3D floor gravity too.
{
ffloor_t *rover;
for (rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS) || !P_InsideANonSolidFFloor(mo, rover)) // P_InsideANonSolidFFloor checks for FOF_EXISTS itself, but let's not always call this function
continue;
if ((rover->fofflags & (FOF_SWIMMABLE|FOF_GOOWATER)) == (FOF_SWIMMABLE|FOF_GOOWATER))
goopgravity = true;
if (P_GetSectorGravityFactor(rover->master->frontsector) == FRACUNIT)
continue;
gravsector = rover->master->frontsector;
break;
}
}
if (!gravsector) // If there is no 3D floor gravity, check sector's gravity
gravsector = mo->subsector->sector;
gravityadd = -FixedMul(gravity, P_GetSectorGravityFactor(gravsector));
if ((gravsector->flags & MSF_GRAVITYFLIP) && gravityadd > 0)
{
if (gravsector->specialflags & SSF_GRAVITYOVERRIDE)
mo->flags2 &= ~MF2_OBJECTFLIP;
mo->eflags |= MFE_VERTICALFLIP;
}
// Less gravity underwater.
if (mo->eflags & MFE_UNDERWATER && !goopgravity)
gravityadd = gravityadd/3;
if (mo->player)
{
if ((mo->player->pflags & PF_GLIDING)
|| (mo->player->charability == CA_FLY && mo->player->panim == PA_ABILITY))
gravityadd = gravityadd/3; // less gravity while flying/gliding
if (mo->player->climbing || (mo->player->powers[pw_carry] == CR_NIGHTSMODE))
gravityadd = 0;
if (!(mo->flags2 & MF2_OBJECTFLIP) != !(mo->player->powers[pw_gravityboots])) // negated to turn numeric into bool - would be double negated, but not needed if both would be
{
gravityadd = -gravityadd;
mo->eflags ^= MFE_VERTICALFLIP;
}
if (wasflip == !(mo->eflags & MFE_VERTICALFLIP)) // note!! == ! is not equivalent to != here - turns numeric into bool this way
P_PlayerFlip(mo);
}
else
{
// Objects with permanent reverse gravity (MF2_OBJECTFLIP)
if (mo->flags2 & MF2_OBJECTFLIP)
{
mo->eflags |= MFE_VERTICALFLIP;
if (mo->z + mo->height >= mo->ceilingz)
gravityadd = 0;
else if (gravityadd < 0) // Don't sink, only rise up
gravityadd *= -1;
}
else //Otherwise, sort through the other exceptions.
{
switch (mo->type)
{
case MT_WATERDROP:
case MT_CYBRAKDEMON:
gravityadd >>= 1;
break;
default:
break;
}
}
}
// Goop has slower, reversed gravity
if (goopgravity)
gravityadd = -((gravityadd/5) + (gravityadd/8));
gravityadd = FixedMul(gravityadd, mo->scale);
return gravityadd;
}
//
// P_CheckGravity
//
// Checks the current gravity state
// of the object. If affect is true,
// a gravity force will be applied.
//
void P_CheckGravity(mobj_t *mo, boolean affect)
{
fixed_t gravityadd = P_GetMobjGravity(mo);
if (!mo->momz) // mobj at stop, no floor, so feel the push of gravity!
gravityadd <<= 1;
if (affect)
mo->momz += gravityadd;
if (mo->type == MT_SKIM && mo->z + mo->momz <= mo->watertop && mo->z >= mo->watertop)
{
mo->momz = 0;
mo->flags |= MF_NOGRAVITY;
}
}
//
// P_SetPitchRollFromSlope
//
void P_SetPitchRollFromSlope(mobj_t *mo, pslope_t *slope)
{
#if 0
if (slope)
{
fixed_t tempz = slope->normal.z;
fixed_t tempy = slope->normal.y;
fixed_t tempx = slope->normal.x;
mo->pitch = R_PointToAngle2(0, 0, FixedSqrt(FixedMul(tempy, tempy) + FixedMul(tempz, tempz)), tempx);
mo->roll = R_PointToAngle2(0, 0, tempz, tempy);
}
else
{
mo->pitch = mo->roll = 0;
}
#else
(void)mo;
(void)slope;
#endif
}
#define STOPSPEED (FRACUNIT)
//
// P_SceneryXYFriction
//
static void P_SceneryXYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy)
{
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
if (abs(mo->momx) < FixedMul(STOPSPEED/32, mo->scale)
&& abs(mo->momy) < FixedMul(STOPSPEED/32, mo->scale))
{
mo->momx = 0;
mo->momy = 0;
}
else
{
if ((oldx == mo->x) && (oldy == mo->y)) // didn't go anywhere
{
mo->momx = FixedMul(mo->momx,ORIG_FRICTION);
mo->momy = FixedMul(mo->momy,ORIG_FRICTION);
}
else
{
mo->momx = FixedMul(mo->momx,mo->friction);
mo->momy = FixedMul(mo->momy,mo->friction);
}
if (mo->type == MT_CANNONBALLDECOR)
{
// Stolen from P_SpawnFriction
mo->friction = FRACUNIT - 0x100;
}
else
mo->friction = ORIG_FRICTION;
}
}
//
// P_XYFriction
//
// adds friction on the xy plane
//
static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy)
{
player_t *player;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
player = mo->player;
if (player) // valid only if player avatar
{
// spinning friction
if (player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH))
{
if (twodlevel || player->mo->flags2 & MF2_TWOD) // Otherwise handled in P_3DMovement
{
const fixed_t ns = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT);
mo->momx = FixedMul(mo->momx, ns);
mo->momy = FixedMul(mo->momy, ns);
}
}
else if (abs(player->rmomx) < FixedMul(STOPSPEED, mo->scale)
&& abs(player->rmomy) < FixedMul(STOPSPEED, mo->scale)
&& (!(player->cmd.forwardmove && !(twodlevel || mo->flags2 & MF2_TWOD)) && !player->cmd.sidemove && !(player->pflags & PF_SPINNING))
&& !(player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) && (abs(player->mo->standingslope->zdelta) >= FRACUNIT/2)))
{
// if in a walking frame, stop moving
if (player->panim == PA_WALK)
P_SetMobjState(mo, S_PLAY_STND);
mo->momx = player->cmomx;
mo->momy = player->cmomy;
}
else if (!(mo->eflags & MFE_SPRUNG))
{
if (oldx == mo->x && oldy == mo->y) // didn't go anywhere
{
mo->momx = FixedMul(mo->momx, ORIG_FRICTION);
mo->momy = FixedMul(mo->momy, ORIG_FRICTION);
}
else
{
mo->momx = FixedMul(mo->momx, mo->friction);
mo->momy = FixedMul(mo->momy, mo->friction);
}
}
}
else
P_SceneryXYFriction(mo, oldx, oldy);
}
static void P_PushableCheckBustables(mobj_t *mo)
{
msecnode_t *node;
fixed_t oldx;
fixed_t oldy;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
if (netgame && mo->player && mo->player->spectator)
return;
oldx = mo->x;
oldy = mo->y;
P_UnsetThingPosition(mo);
mo->x += mo->momx;
mo->y += mo->momy;
P_SetThingPosition(mo);
for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
{
ffloor_t *rover;
fixed_t topheight, bottomheight;
if (!node->m_sector)
break;
if (!node->m_sector->ffloors)
continue;
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS))
continue;
if (!(rover->fofflags & FOF_BUSTUP))
continue;
if (!(rover->bustflags & FB_PUSHABLES))
continue;
if (rover->master->frontsector->crumblestate != CRUMBLE_NONE)
continue;
topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
// Height checks
if (rover->bustflags & FB_ONLYBOTTOM)
{
if (mo->z + mo->momz + mo->height < bottomheight)
continue;
if (mo->z + mo->height > bottomheight)
continue;
}
else
{
switch (rover->busttype)
{
case BT_TOUCH:
if (mo->z + mo->momz > topheight)
continue;
if (mo->z + mo->momz + mo->height < bottomheight)
continue;
break;
case BT_SPINBUST:
if (mo->z + mo->momz > topheight)
continue;
if (mo->z + mo->height < bottomheight)
continue;
break;
default:
if (mo->z >= topheight)
continue;
if (mo->z + mo->height < bottomheight)
continue;
break;
}
}
EV_CrumbleChain(NULL, rover); // node->m_sector
// Run a linedef executor??
if (rover->bustflags & FB_EXECUTOR)
P_LinedefExecute(rover->busttag, mo, node->m_sector);
goto bustupdone;
}
}
bustupdone:
P_UnsetThingPosition(mo);
mo->x = oldx;
mo->y = oldy;
P_SetThingPosition(mo);
}
//
// P_CheckSkyHit
//
boolean P_CheckSkyHit(mobj_t *mo, line_t *line)
{
if (line && (line->special == 41 ||
(line->backsector
&& line->backsector->ceilingpic == skyflatnum
&& line->frontsector
&& line->frontsector->ceilingpic == skyflatnum
&& (mo->z >= line->frontsector->ceilingheight
|| mo->z >= line->backsector->ceilingheight))))
return true;
return false;
}
//
// P_XYMovement
//
void P_XYMovement(mobj_t *mo)
{
player_t *player;
fixed_t xmove, ymove;
fixed_t oldx, oldy; // reducing bobbing/momentum on ice when up against walls
boolean moved;
pslope_t *oldslope = NULL;
vector3_t slopemom = {0,0,0};
fixed_t predictedz = 0;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
// if it's stopped
if (!mo->momx && !mo->momy)
{
if (mo->flags2 & MF2_SKULLFLY)
{
// the skull slammed into something
mo->flags2 &= ~MF2_SKULLFLY;
mo->momx = mo->momy = mo->momz = 0;
// set in 'search new direction' state?
if (mo->type != MT_EGGMOBILE)
P_SetMobjState(mo, mo->info->spawnstate);
return;
}
}
player = mo->player; //valid only if player avatar
xmove = mo->momx;
ymove = mo->momy;
oldx = mo->x;
oldy = mo->y;
if (mo->flags & MF_NOCLIPHEIGHT)
mo->standingslope = NULL;
// adjust various things based on slope
if (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8) {
if (!P_IsObjectOnGround(mo)) { // We fell off at some point? Do the twisty thing!
P_SlopeLaunch(mo);
xmove = mo->momx;
ymove = mo->momy;
} else { // Still on the ground.
slopemom.x = xmove;
slopemom.y = ymove;
slopemom.z = 0;
P_QuantizeMomentumToSlope(&slopemom, mo->standingslope);
xmove = slopemom.x;
ymove = slopemom.y;
predictedz = mo->z + slopemom.z; // We'll use this later...
oldslope = mo->standingslope;
}
} else if (P_IsObjectOnGround(mo) && !mo->momz)
predictedz = mo->z;
// Pushables can break some blocks
if (CheckForBustableBlocks && ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse)))
P_PushableCheckBustables(mo);
if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true)
&& !(P_MobjWasRemoved(mo) || mo->eflags & MFE_SPRUNG))
{
// blocked move
moved = false;
if (player)
B_MoveBlocked(player);
if (LUA_HookMobjMoveBlocked(mo, tmhitthing, blockingline))
{
if (P_MobjWasRemoved(mo))
return;
}
else if (P_MobjWasRemoved(mo))
return;
else if (mo->flags & MF_BOUNCE)
{
P_BounceMove(mo);
xmove = ymove = 0;
S_StartSound(mo, mo->info->activesound);
// Bounce ring algorithm
if (mo->type == MT_THROWNBOUNCE)
{
mo->threshold++;
// Gain lower amounts of time on each bounce.
if (mo->fuse && mo->threshold < 5)
mo->fuse += ((5 - mo->threshold) * TICRATE);
// Check for hit against sky here
if (P_CheckSkyHit(mo, ceilingline))
{
// Hack to prevent missiles exploding
// against the sky.
// Does not handle sky floors.
// Check frontsector as well.
P_RemoveMobj(mo);
return;
}
}
}
else if (mo->flags & MF_STICKY)
{
S_StartSound(mo, mo->info->activesound);
mo->momx = mo->momy = mo->momz = 0; //Full stop!
mo->flags |= MF_NOGRAVITY; //Stay there!
mo->flags &= ~MF_STICKY; //Don't check again!
// Check for hit against sky here
if (P_CheckSkyHit(mo, ceilingline))
{
// Hack to prevent missiles exploding
// against the sky.
// Does not handle sky floors.
// Check frontsector as well.
P_RemoveMobj(mo);
return;
}
}
else if (player || mo->flags & (MF_SLIDEME|MF_PUSHABLE))
{ // try to slide along it
// Wall transfer part 1.
pslope_t *transferslope = NULL;
fixed_t transfermomz = 0;
if (oldslope && (P_MobjFlip(mo)*(predictedz - mo->z) > 0)) // Only for moving up (relative to gravity), otherwise there's a failed launch when going down slopes and hitting walls
{
transferslope = ((mo->standingslope) ? mo->standingslope : oldslope);
if (((transferslope->zangle < ANGLE_180) ? transferslope->zangle : InvAngle(transferslope->zangle)) >= ANGLE_45) // Prevent some weird stuff going on on shallow slopes.
transfermomz = P_GetWallTransferMomZ(mo, transferslope);
}
P_SlideMove(mo);
if (player)
player->powers[pw_pushing] = 3;
xmove = ymove = 0;
// Wall transfer part 2.
if (transfermomz && transferslope) // Are we "transferring onto the wall" (really just a disguised vertical launch)?
{
angle_t relation; // Scale transfer momentum based on how head-on it is to the slope.
if (mo->momx || mo->momy) // "Guess" the angle of the wall you hit using new momentum
relation = transferslope->xydirection - R_PointToAngle2(0, 0, mo->momx, mo->momy);
else // Give it for free, I guess.
relation = ANGLE_90;
transfermomz = FixedMul(transfermomz,
abs(FINESINE((relation >> ANGLETOFINESHIFT) & FINEMASK)));
if (P_MobjFlip(mo)*(transfermomz - mo->momz) > 2*FRACUNIT) // Do the actual launch!
{
mo->momz = transfermomz;
mo->standingslope = NULL;
if (player)
{
player->powers[pw_justlaunched] = 2;
if (player->pflags & PF_SPINNING)
player->pflags |= PF_THOKKED;
}
}
}
}
else if (mo->type == MT_SPINFIRE)
{
P_RemoveMobj(mo);
return;
}
else if (mo->flags & MF_MISSILE)
{
// explode a missile
if (P_CheckSkyHit(mo, ceilingline))
{
// Hack to prevent missiles exploding
// against the sky.
// Does not handle sky floors.
// Check frontsector as well.
P_RemoveMobj(mo);
return;
}
P_ExplodeMissile(mo);
return;
}
else
mo->momx = mo->momy = 0;
}
else
moved = true;
if (P_MobjWasRemoved(mo)) // MF_SPECIAL touched a player! O_o;;
return;
if (moved && oldslope && !(mo->flags & MF_NOCLIPHEIGHT)) { // Check to see if we ran off
if (oldslope != mo->standingslope) { // First, compare different slopes
angle_t oldangle, newangle;
angle_t moveangle = R_PointToAngle2(0, 0, mo->momx, mo->momy);
oldangle = FixedMul((signed)oldslope->zangle, FINECOSINE((moveangle - oldslope->xydirection) >> ANGLETOFINESHIFT));
if (mo->standingslope)
newangle = FixedMul((signed)mo->standingslope->zangle, FINECOSINE((moveangle - mo->standingslope->xydirection) >> ANGLETOFINESHIFT));
else
newangle = 0;
// Now compare the Zs of the different quantizations
if (oldangle-newangle > ANG30 && oldangle-newangle < ANGLE_180) { // Allow for a bit of sticking - this value can be adjusted later
mo->standingslope = oldslope;
P_SetPitchRollFromSlope(mo, mo->standingslope);
P_SlopeLaunch(mo);
//CONS_Printf("launched off of slope - ");
}
/*CONS_Printf("old angle %f - new angle %f = %f\n",
FIXED_TO_FLOAT(AngleFixed(oldangle)),
FIXED_TO_FLOAT(AngleFixed(newangle)),
FIXED_TO_FLOAT(AngleFixed(oldangle-newangle))
);*/
} else if (predictedz-mo->z > abs(slopemom.z/2)) { // Now check if we were supposed to stick to this slope
//CONS_Printf("%d-%d > %d\n", (predictedz), (mo->z), (slopemom.z/2));
P_SlopeLaunch(mo);
}
} else if (moved && mo->standingslope && predictedz) {
angle_t moveangle = R_PointToAngle2(0, 0, mo->momx, mo->momy);
angle_t newangle = FixedMul((signed)mo->standingslope->zangle, FINECOSINE((moveangle - mo->standingslope->xydirection) >> ANGLETOFINESHIFT));
/*CONS_Printf("flat to angle %f - predicted z of %f\n",
FIXED_TO_FLOAT(AngleFixed(ANGLE_MAX-newangle)),
FIXED_TO_FLOAT(predictedz)
);*/
if (ANGLE_MAX-newangle > ANG30 && newangle > ANGLE_180) {
mo->momz = P_MobjFlip(mo)*FRACUNIT/2;
mo->z = predictedz + P_MobjFlip(mo);
mo->standingslope = NULL;
//CONS_Printf("Launched off of flat surface running into downward slope\n");
}
}
// Check the gravity status.
P_CheckGravity(mo, false);
if (player && !moved && player->powers[pw_carry] == CR_NIGHTSMODE && mo->target)
{
angle_t fa;
P_UnsetThingPosition(mo);
player->angle_pos = player->old_angle_pos;
player->speed = FixedMul(player->speed, 4*FRACUNIT/5);
if (player->flyangle >= 0 && player->flyangle < 90)
player->flyangle = 135;
else if (player->flyangle >= 90 && player->flyangle < 180)
player->flyangle = 45;
else if (player->flyangle >= 180 && player->flyangle < 270)
player->flyangle = 315;
else
player->flyangle = 225;
player->flyangle %= 360;
if (player->pflags & PF_TRANSFERTOCLOSEST)
{
mo->x -= mo->momx;
mo->y -= mo->momy;
}
else
{
fa = player->old_angle_pos>>ANGLETOFINESHIFT;
mo->x = mo->target->x + FixedMul(FINECOSINE(fa),mo->target->radius);
mo->y = mo->target->y + FixedMul(FINESINE(fa),mo->target->radius);
}
mo->momx = mo->momy = 0;
P_SetThingPosition(mo);
}
if (mo->flags & MF_NOCLIPHEIGHT)
return; // no frictions for objects that can pass through floors
if (mo->flags & MF_MISSILE || mo->flags2 & MF2_SKULLFLY || mo->type == MT_SHELL || mo->type == MT_VULTURE || mo->type == MT_PENGUINATOR)
return; // no friction for missiles ever
if (player && player->homing) // no friction for homing
return;
if (player && player->powers[pw_carry] == CR_NIGHTSMODE)
return; // no friction for NiGHTS players
if ((mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED)
&& (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)) // Special exception for tumbleweeds on slopes
return;
if (((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz))
&& !(player && player->pflags & PF_SLIDING))
return; // no friction when airborne
P_XYFriction(mo, oldx, oldy);
}
void P_RingXYMovement(mobj_t *mo)
{
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy) && !P_MobjWasRemoved(mo))
P_SlideMove(mo);
}
void P_SceneryXYMovement(mobj_t *mo)
{
fixed_t oldx, oldy; // reducing bobbing/momentum on ice when up against walls
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
oldx = mo->x;
oldy = mo->y;
if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy) && !P_MobjWasRemoved(mo))
P_SlideMove(mo);
if (P_MobjWasRemoved(mo))
return;
if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz))
return; // no friction when airborne
if (mo->flags & MF_NOCLIPHEIGHT)
return; // no frictions for objects that can pass through floors
P_SceneryXYFriction(mo, oldx, oldy);
}
//
// P_AdjustMobjFloorZ_FFloors
//
// Utility function for P_ZMovement and related
// Adjusts mo->floorz/mo->ceiling accordingly for FFloors
//
// "motype" determines what behaviour to use exactly
// This is to keep things consistent in case these various object types NEED to be different
//
// motype options:
// 0 - normal
// 1 - forces false check for water (rings)
// 2 - forces false check for water + different quicksand behaviour (scenery)
//
void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype)
{
ffloor_t *rover;
fixed_t delta1, delta2, thingtop;
fixed_t topheight, bottomheight;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
thingtop = mo->z + mo->height;
for (rover = sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS))
continue;
topheight = P_GetFOFTopZ(mo, sector, rover, mo->x, mo->y, NULL);
bottomheight = P_GetFOFBottomZ(mo, sector, rover, mo->x, mo->y, NULL);
if (mo->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mo->player, rover))) // only the player should stand on lava or run on water
;
else if (motype != 0 && rover->fofflags & FOF_SWIMMABLE) // "scenery" only
continue;
else if (rover->fofflags & FOF_QUICKSAND) // quicksand
;
else if (!( // if it's not either of the following...
(rover->fofflags & (FOF_BLOCKPLAYER|FOF_MARIO) && mo->player) // ...solid to players? (mario blocks are always solid from beneath to players)
|| (rover->fofflags & FOF_BLOCKOTHERS && !mo->player) // ...solid to others?
)) // ...don't take it into account.
continue;
if (rover->fofflags & FOF_QUICKSAND)
{
switch (motype)
{
case 2: // scenery does things differently for some reason
if (mo->z < topheight && bottomheight < thingtop)
{
if (!(mo->eflags & MFE_VERTICALFLIP))
mo->floorz = mo->z;
else if (mo->eflags & MFE_VERTICALFLIP)
mo->ceilingz = thingtop;
continue;
}
break;
default:
if (mo->z < topheight && bottomheight < thingtop)
{
if (!(mo->eflags & MFE_VERTICALFLIP) && mo->floorz < mo->z)
mo->floorz = mo->z;
else if (mo->eflags & MFE_VERTICALFLIP && mo->ceilingz > thingtop)
mo->ceilingz = thingtop;
}
continue; // This is so you can jump/spring up through quicksand from below.
}
}
delta1 = mo->z - (bottomheight + ((topheight - bottomheight)/2));
delta2 = thingtop - (bottomheight + ((topheight - bottomheight)/2));
if (topheight > mo->floorz && abs(delta1) < abs(delta2)
&& (rover->fofflags & FOF_SOLID) // Non-FOF_SOLID Mario blocks are only solid from bottom
&& !(rover->fofflags & FOF_REVERSEPLATFORM)
&& ((P_MobjFlip(mo)*mo->momz >= 0) || (!(rover->fofflags & FOF_PLATFORM)))) // In reverse gravity, only clip for FOFs that are intangible from their bottom (the "top" you're falling through) if you're coming from above ("below" in your frame of reference)
{
mo->floorz = topheight;
}
if (bottomheight < mo->ceilingz && abs(delta1) >= abs(delta2)
&& !(rover->fofflags & FOF_PLATFORM)
&& ((P_MobjFlip(mo)*mo->momz >= 0) || ((rover->fofflags & FOF_SOLID) && !(rover->fofflags & FOF_REVERSEPLATFORM)))) // In normal gravity, only clip for FOFs that are intangible from the top if you're coming from below
{
mo->ceilingz = bottomheight;
}
}
}
//
// P_AdjustMobjFloorZ_PolyObjs
//
// Utility function for P_ZMovement and related
// Adjusts mo->floorz/mo->ceiling accordingly for PolyObjs
//
static void P_AdjustMobjFloorZ_PolyObjs(mobj_t *mo, subsector_t *subsec)
{
polyobj_t *po = subsec->polyList;
sector_t *polysec;
fixed_t delta1, delta2, thingtop;
fixed_t polytop, polybottom;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
thingtop = mo->z + mo->height;
while(po)
{
if (!P_MobjInsidePolyobj(po, mo) || !(po->flags & POF_SOLID))
{
po = (polyobj_t *)(po->link.next);
continue;
}
// We're inside it! Yess...
polysec = po->lines[0]->backsector;
if (po->flags & POF_CLIPPLANES)
{
polytop = polysec->ceilingheight;
polybottom = polysec->floorheight;
}
else
{
polytop = INT32_MAX;
polybottom = INT32_MIN;
}
delta1 = mo->z - (polybottom + ((polytop - polybottom)/2));
delta2 = thingtop - (polybottom + ((polytop - polybottom)/2));
if (polytop > mo->floorz && abs(delta1) < abs(delta2))
mo->floorz = polytop;
if (polybottom < mo->ceilingz && abs(delta1) >= abs(delta2))
mo->ceilingz = polybottom;
po = (polyobj_t *)(po->link.next);
}
}
void P_RingZMovement(mobj_t *mo)
{
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
// Intercept the stupid 'fall through 3dfloors' bug
if (mo->subsector->sector->ffloors)
P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 1);
if (mo->subsector->polyList)
P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
// adjust height
if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
{
mo->momz += mo->pmomz;
mo->pmomz = 0;
mo->eflags &= ~MFE_APPLYPMOMZ;
}
mo->z += mo->momz;
// clip movement
if (mo->z <= mo->floorz && !(mo->flags & MF_NOCLIPHEIGHT))
{
mo->z = mo->floorz;
mo->momz = 0;
}
else if (mo->z + mo->height > mo->ceilingz && !(mo->flags & MF_NOCLIPHEIGHT))
{
mo->z = mo->ceilingz - mo->height;
mo->momz = 0;
}
}
boolean P_CheckDeathPitCollide(mobj_t *mo)
{
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
if (mo->player && mo->player->pflags & PF_GODMODE)
return false;
fixed_t sectorFloor = P_GetSpecialBottomZ(mo, mo->subsector->sector, mo->subsector->sector);
fixed_t sectorCeiling = P_GetSpecialTopZ(mo, mo->subsector->sector, mo->subsector->sector);
if (((mo->z <= sectorFloor
&& ((mo->subsector->sector->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & MSF_FLIPSPECIAL_FLOOR))
|| (mo->z + mo->height >= sectorCeiling
&& ((mo->subsector->sector->flags & MSF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & MSF_FLIPSPECIAL_CEILING)))
&& (mo->subsector->sector->damagetype == SD_DEATHPITTILT
|| mo->subsector->sector->damagetype == SD_DEATHPITNOTILT))
return true;
return false;
}
boolean P_CheckSolidLava(ffloor_t *rover)
{
return (rover->fofflags & FOF_SWIMMABLE) && (rover->master->frontsector->damagetype == SD_LAVA);
}
//
// P_ZMovement
// Returns false if the mobj was killed/exploded/removed, true otherwise.
//
boolean P_ZMovement(mobj_t *mo)
{
fixed_t dist, delta;
boolean onground;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
// Intercept the stupid 'fall through 3dfloors' bug
if (mo->subsector->sector->ffloors)
P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 0);
if (mo->subsector->polyList)
P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
// adjust height
if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
{
mo->momz += mo->pmomz;
mo->pmomz = 0;
mo->eflags &= ~MFE_APPLYPMOMZ;
}
mo->z += mo->momz;
onground = P_IsObjectOnGround(mo);
if (mo->standingslope)
{
if (mo->flags & MF_NOCLIPHEIGHT)
mo->standingslope = NULL;
else if (!onground)
P_SlopeLaunch(mo);
}
if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health
&& !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD))
{
switch (mo->type)
{
case MT_GHOST:
case MT_METALSONIC_RACE:
case MT_EXPLODE:
case MT_BOSSEXPLODE:
case MT_SONIC3KBOSSEXPLODE:
break;
case MT_REDFLAG:
case MT_BLUEFLAG:
// Remove from death pits. DON'T FUCKING DESPAWN IT DAMMIT
mo->fuse = 1;
return false;
case MT_BOUNCERING:
case MT_INFINITYRING:
case MT_AUTOMATICRING:
case MT_RAILRING:
case MT_EXPLOSIONRING:
case MT_SCATTERRING:
case MT_GRENADERING:
case MT_BOUNCEPICKUP:
case MT_RAILPICKUP:
case MT_AUTOPICKUP:
case MT_EXPLODEPICKUP:
case MT_SCATTERPICKUP:
case MT_GRENADEPICKUP:
//Don't remove respawning ringslinger collectables on death pits
if (!(mo->flags2 & MF2_DONTRESPAWN))
break;
/* FALLTHRU */
default:
if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART)
{
// Kill enemies, bosses and minecarts that fall into death pits.
P_KillMobj(mo, NULL, NULL, 0);
return !P_MobjWasRemoved(mo); // allows explosion states to run
}
else
{
P_RemoveMobj(mo);
return false;
}
break;
}
}
switch (mo->type)
{
case MT_THROWNBOUNCE:
if ((mo->flags & MF_BOUNCE) && (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz))
{
mo->momz = -mo->momz;
mo->z += mo->momz;
S_StartSound(mo, mo->info->activesound);
mo->threshold++;
// Be sure to change the XY one too if you change this.
// Gain lower amounts of time on each bounce.
if (mo->fuse && mo->threshold < 5)
mo->fuse += ((5 - mo->threshold) * TICRATE);
}
break;
case MT_SKIM:
// skims don't bounce
if (mo->z > mo->watertop && mo->z - mo->momz <= mo->watertop)
{
mo->z = mo->watertop;
mo->momz = 0;
mo->flags |= MF_NOGRAVITY;
}
break;
case MT_GOOP:
if (mo->z <= mo->floorz && mo->momz)
{
P_SetMobjState(mo, mo->info->meleestate);
mo->momx = mo->momy = mo->momz = 0;
mo->z = mo->floorz;
if (mo->info->painsound)
S_StartSound(mo, mo->info->painsound);
}
break;
case MT_RING: // Ignore still rings
case MT_COIN:
case MT_BLUESPHERE:
case MT_BOMBSPHERE:
case MT_NIGHTSCHIP:
case MT_NIGHTSSTAR:
case MT_REDTEAMRING:
case MT_BLUETEAMRING:
case MT_FLINGRING:
case MT_FLINGCOIN:
case MT_FLINGBLUESPHERE:
case MT_FLINGNIGHTSCHIP:
case MT_FLINGEMERALD:
case MT_BOUNCERING:
case MT_INFINITYRING:
case MT_AUTOMATICRING:
case MT_RAILRING:
case MT_EXPLOSIONRING:
case MT_SCATTERRING:
case MT_GRENADERING:
case MT_BOUNCEPICKUP:
case MT_RAILPICKUP:
case MT_AUTOPICKUP:
case MT_EXPLODEPICKUP:
case MT_SCATTERPICKUP:
case MT_GRENADEPICKUP:
if (!(mo->momx || mo->momy || mo->momz))
return true;
break;
case MT_FLAMEJET:
case MT_VERTICALFLAMEJET:
if (!(mo->flags & MF_BOUNCE))
return true;
break;
case MT_SPIKE:
case MT_WALLSPIKE:
// Dead spike particles disappear upon ground contact
if (!mo->health && (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz))
{
P_RemoveMobj(mo);
return false;
}
break;
default:
break;
}
if (P_MobjFlip(mo)*mo->momz < 0
&& (mo->flags2 & MF2_CLASSICPUSH))
mo->momx = mo->momy = 0;
if (mo->flags & MF_FLOAT && mo->target && mo->health
&& !(mo->type == MT_EGGMOBILE) && mo->target->health > 0)
{
// float down towards target if too close
if (!(mo->flags2 & MF2_SKULLFLY) && !(mo->flags2 & MF2_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 -= FixedMul(FLOATSPEED, mo->scale);
else if (delta > 0 && dist < (delta*3))
mo->z += FixedMul(FLOATSPEED, mo->scale);
if (mo->type == MT_JETJAW && mo->z + mo->height > mo->watertop)
mo->z = mo->watertop - mo->height;
}
}
// clip movement
if (((mo->z <= mo->floorz && !(mo->eflags & MFE_VERTICALFLIP))
|| (mo->z + mo->height >= mo->ceilingz && mo->eflags & MFE_VERTICALFLIP))
&& !(mo->flags & MF_NOCLIPHEIGHT))
{
vector3_t mom;
mom.x = mo->momx;
mom.y = mo->momy;
mom.z = mo->momz;
if (mo->eflags & MFE_VERTICALFLIP)
mo->z = mo->ceilingz - mo->height;
else
mo->z = mo->floorz;
if (!(mo->flags & MF_MISSILE) && mo->standingslope) // You're still on the ground; why are we here?
{
mo->momz = 0;
return true;
}
P_CheckPosition(mo, mo->x, mo->y); // Sets mo->standingslope correctly
if (P_MobjWasRemoved(mo)) // mobjs can be removed by P_CheckPosition -- Monster Iestyn 31/07/21
return false;
if (((mo->eflags & MFE_VERTICALFLIP) ? tmceilingslope : tmfloorslope) && (mo->type != MT_STEAM))
{
mo->standingslope = (mo->eflags & MFE_VERTICALFLIP) ? tmceilingslope : tmfloorslope;
P_SetPitchRollFromSlope(mo, mo->standingslope);
P_ReverseQuantizeMomentumToSlope(&mom, mo->standingslope);
}
// hit the floor
if (mo->type == MT_FIREBALL) // special case for the fireball
mom.z = P_MobjFlip(mo)*FixedMul(5*FRACUNIT, mo->scale);
else if (mo->type == MT_SPINFIRE) // elemental shield fire is another exception here
;
else if (mo->flags & MF_MISSILE)
{
if (!(mo->flags & MF_NOCLIP))
{
// This is a really ugly hard-coded hack to prevent grenades
// from exploding the instant they hit the ground, and then
// another to prevent them from turning into hockey pucks.
// I'm sorry in advance. -SH
// PS: Oh, and Brak's napalm bombs too, now.
if (mo->flags & MF_GRENADEBOUNCE)
{
// Going down? (Or up in reverse gravity?)
if (P_MobjFlip(mo)*mom.z < 0)
{
// If going slower than a fracunit, just stop.
if (abs(mom.z) < FixedMul(FRACUNIT, mo->scale))
{
mom.x = mom.y = mom.z = 0;
// Napalm hack
if (mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE && mo->fuse)
mo->fuse = 1;
}
// Otherwise bounce up at half speed.
else
mom.z = -mom.z/2;
S_StartSound(mo, mo->info->activesound);
}
}
// Hack over. Back to your regularly scheduled detonation. -SH
else
{
// Don't explode on the sky!
if (!(mo->eflags & MFE_VERTICALFLIP)
&& mo->subsector->sector->floorpic == skyflatnum
&& mo->subsector->sector->floorheight == mo->floorz)
P_RemoveMobj(mo);
else if (mo->eflags & MFE_VERTICALFLIP
&& mo->subsector->sector->ceilingpic == skyflatnum
&& mo->subsector->sector->ceilingheight == mo->ceilingz)
P_RemoveMobj(mo);
else
P_ExplodeMissile(mo);
return false;
}
}
}
if (P_MobjFlip(mo)*mom.z < 0) // falling
{
mo->eflags |= MFE_JUSTHITFLOOR;
if (mo->flags2 & MF2_SKULLFLY) // the skull slammed into something
mom.z = -mom.z;
else
// Flingrings bounce
if (mo->type == MT_FLINGRING
|| mo->type == MT_FLINGCOIN
|| mo->type == MT_FLINGBLUESPHERE
|| mo->type == MT_FLINGNIGHTSCHIP
|| P_WeaponOrPanel(mo->type)
|| mo->type == MT_FLINGEMERALD
|| mo->type == MT_BIGTUMBLEWEED
|| mo->type == MT_LITTLETUMBLEWEED
|| mo->type == MT_CANNONBALLDECOR
|| mo->type == MT_FALLINGROCK)
{
if (maptol & TOL_NIGHTS)
mom.z = -FixedDiv(mom.z, 10*FRACUNIT);
else
mom.z = -FixedMul(mom.z, FixedDiv(17*FRACUNIT,20*FRACUNIT));
if (mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED)
{
if (abs(mom.x) < FixedMul(STOPSPEED, mo->scale)
&& abs(mom.y) < FixedMul(STOPSPEED, mo->scale)
&& abs(mom.z) < FixedMul(STOPSPEED*3, mo->scale))
{
if (mo->flags2 & MF2_AMBUSH)
{
// Give the tumbleweed another random kick if it runs out of steam.
mom.z += P_MobjFlip(mo)*FixedMul(6*FRACUNIT, mo->scale);
if (P_RandomChance(FRACUNIT/2))
mom.x += FixedMul(6*FRACUNIT, mo->scale);
else
mom.x -= FixedMul(6*FRACUNIT, mo->scale);
if (P_RandomChance(FRACUNIT/2))
mom.y += FixedMul(6*FRACUNIT, mo->scale);
else
mom.y -= FixedMul(6*FRACUNIT, mo->scale);
}
else if (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)
{
// Pop the object up a bit to encourage bounciness
//mom.z = P_MobjFlip(mo)*mo->scale;
}
else
{
mom.x = mom.y = mom.z = 0;
P_SetMobjState(mo, mo->info->spawnstate);
}
}
// Stolen from P_SpawnFriction
mo->friction = FRACUNIT - 0x100;
}
else if (mo->type == MT_FALLINGROCK)
{
if (P_MobjFlip(mo)*mom.z > FixedMul(2*FRACUNIT, mo->scale))
S_StartSound(mo, mo->info->activesound + P_RandomKey(mo->info->reactiontime));
mom.z /= 2; // Rocks not so bouncy
if (abs(mom.x) < FixedMul(STOPSPEED, mo->scale)
&& abs(mom.y) < FixedMul(STOPSPEED, mo->scale)
&& abs(mom.z) < FixedMul(STOPSPEED*3, mo->scale))
{
P_RemoveMobj(mo);
return false;
}
}
else if (mo->type == MT_CANNONBALLDECOR)
{
mom.z /= 2;
if (abs(mom.z) < FixedMul(STOPSPEED*3, mo->scale))
mom.z = 0;
}
}
else
mom.z = (tmfloorthing ? tmfloorthing->momz : 0);
}
else if (tmfloorthing)
mom.z = tmfloorthing->momz;
if (mo->standingslope) { // MT_STEAM will never have a standingslope, see above.
P_QuantizeMomentumToSlope(&mom, mo->standingslope);
}
mo->momx = mom.x;
mo->momy = mom.y;
mo->momz = mom.z;
if (mo->type == MT_STEAM)
return true;
}
else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here!
{
/// \todo may not be needed (done in P_MobjThinker normally)
mo->eflags &= ~MFE_JUSTHITFLOOR;
P_CheckGravity(mo, true);
}
if (((mo->z + mo->height > mo->ceilingz && !(mo->eflags & MFE_VERTICALFLIP))
|| (mo->z < mo->floorz && mo->eflags & MFE_VERTICALFLIP))
&& !(mo->flags & MF_NOCLIPHEIGHT))
{
if (mo->eflags & MFE_VERTICALFLIP)
mo->z = mo->floorz;
else
mo->z = mo->ceilingz - mo->height;
if (mo->type == MT_SPINFIRE)
;
else if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
{
// Hack 2: Electric Boogaloo -SH
if (mo->flags & MF_GRENADEBOUNCE)
{
if (P_MobjFlip(mo)*mo->momz >= 0)
{
mo->momz = -mo->momz;
S_StartSound(mo, mo->info->activesound);
}
}
else
{
// Don't explode on the sky!
if (!(mo->eflags & MFE_VERTICALFLIP)
&& mo->subsector->sector->ceilingpic == skyflatnum
&& mo->subsector->sector->ceilingheight == mo->ceilingz)
P_RemoveMobj(mo);
else if (mo->eflags & MFE_VERTICALFLIP
&& mo->subsector->sector->floorpic == skyflatnum
&& mo->subsector->sector->floorheight == mo->floorz)
P_RemoveMobj(mo);
else
P_ExplodeMissile(mo);
return false;
}
}
if (P_MobjFlip(mo)*mo->momz > 0) // hit the ceiling
{
if (mo->flags2 & MF2_SKULLFLY) // the skull slammed into something
mo->momz = -mo->momz;
else
// Flags bounce
if (mo->type == MT_REDFLAG || mo->type == MT_BLUEFLAG)
{
if (maptol & TOL_NIGHTS)
mo->momz = -FixedDiv(mo->momz, 10*FRACUNIT);
else
mo->momz = -FixedMul(mo->momz, FixedDiv(17*FRACUNIT,20*FRACUNIT));
}
else
mo->momz = 0;
}
}
return true;
}
// Check for "Mario" blocks to hit and bounce them
static void P_CheckMarioBlocks(mobj_t *mo)
{
msecnode_t *node;
if (netgame && mo->player->spectator)
return;
for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
{
ffloor_t *rover;
if (!node->m_sector->ffloors)
continue;
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS))
continue;
if (!(rover->fofflags & FOF_MARIO))
continue;
if (mo->eflags & MFE_VERTICALFLIP)
continue; // if you were flipped, your head isn't actually hitting your ceilingz is it?
if (*rover->bottomheight != mo->ceilingz)
continue;
if (rover->fofflags & FOF_GOOWATER) // Brick block!
EV_CrumbleChain(node->m_sector, rover);
else // Question block!
EV_MarioBlock(rover, node->m_sector, mo);
}
}
}
// Check if we're on a polyobject that triggers a linedef executor.
static boolean P_PlayerPolyObjectZMovement(mobj_t *mo)
{
msecnode_t *node;
boolean stopmovecut = false;
for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
{
sector_t *sec = node->m_sector;
subsector_t *newsubsec;
size_t i;
for (i = 0; i < numsubsectors; i++)
{
polyobj_t *po;
sector_t *polysec;
newsubsec = &subsectors[i];
if (newsubsec->sector != sec)
continue;
for (po = newsubsec->polyList; po; po = (polyobj_t *)(po->link.next))
{
if (!(po->flags & POF_SOLID))
continue;
if (!P_MobjInsidePolyobj(po, mo))
continue;
polysec = po->lines[0]->backsector;
// Moving polyobjects should act like conveyors if the player lands on one. (I.E. none of the momentum cut thing below) -Red
if ((mo->z == polysec->ceilingheight || mo->z + mo->height == polysec->floorheight) && po->thinker)
stopmovecut = true;
if (!(po->flags & POF_LDEXEC))
continue;
if (mo->z != polysec->ceilingheight)
continue;
// We're landing on a PO, so check for a linedef executor.
P_LinedefExecute(po->triggertag, mo, NULL);
}
}
}
return stopmovecut;
}
void P_PlayerZMovement(mobj_t *mo)
{
boolean onground;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
if (!mo->player)
return;
// Intercept the stupid 'fall through 3dfloors' bug
if (mo->subsector->sector->ffloors)
P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 0);
if (mo->subsector->polyList)
P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
// check for smooth step up
if ((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height > mo->ceilingz)
|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->z < mo->floorz))
{
if (mo->eflags & MFE_VERTICALFLIP)
mo->player->viewheight -= (mo->z+mo->height) - mo->ceilingz;
else
mo->player->viewheight -= mo->floorz - mo->z;
mo->player->deltaviewheight =
(FixedMul(41*mo->player->height/48, mo->scale) - mo->player->viewheight)>>3;
}
// adjust height
if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
{
mo->momz += mo->pmomz;
mo->pmomz = 0;
mo->eflags &= ~MFE_APPLYPMOMZ;
}
mo->z += mo->momz;
onground = P_IsObjectOnGround(mo);
// Have player fall through floor?
if (mo->player->playerstate == PST_DEAD
|| mo->player->playerstate == PST_REBORN)
return;
if (mo->standingslope)
{
if (mo->flags & MF_NOCLIPHEIGHT)
mo->standingslope = NULL;
else if (!onground)
P_SlopeLaunch(mo);
}
// clip movement
if (onground && !(mo->flags & MF_NOCLIPHEIGHT))
{
if (mo->eflags & MFE_VERTICALFLIP)
mo->z = mo->ceilingz - mo->height;
else
mo->z = mo->floorz;
if (mo->player->powers[pw_carry] == CR_NIGHTSMODE)
{
// bounce off floor if you were flying towards it
if ((mo->eflags & MFE_VERTICALFLIP && mo->player->flyangle > 0 && mo->player->flyangle < 180)
|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->player->flyangle > 180 && mo->player->flyangle <= 359))
{
if (mo->player->flyangle < 90 || mo->player->flyangle >= 270)
mo->player->flyangle += P_MobjFlip(mo)*90;
else
mo->player->flyangle -= P_MobjFlip(mo)*90;
mo->player->speed = FixedMul(mo->player->speed, 4*FRACUNIT/5);
}
goto nightsdone;
}
// Get up if you fell.
if (mo->player->panim == PA_PAIN)
P_SetMobjState(mo, S_PLAY_WALK);
if (!mo->standingslope && (mo->eflags & MFE_VERTICALFLIP ? tmceilingslope : tmfloorslope)) {
// Handle landing on slope during Z movement
P_HandleSlopeLanding(mo, (mo->eflags & MFE_VERTICALFLIP ? tmceilingslope : tmfloorslope));
}
if (P_MobjFlip(mo)*mo->momz < 0) // falling
{
boolean clipmomz;
mo->pmomz = 0; // We're on a new floor, don't keep doing platform movement.
// Squat down. Decrease viewheight for a moment after hitting the ground (hard),
if (P_MobjFlip(mo)*mo->momz < -FixedMul(8*FRACUNIT, mo->scale))
mo->player->deltaviewheight = (P_MobjFlip(mo)*mo->momz)>>3; // make sure momz is negative
mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
clipmomz = P_PlayerHitFloor(mo->player, true);
if (!P_PlayerPolyObjectZMovement(mo))
{
// Cut momentum in half when you hit the ground and
// aren't pressing any controls.
if (!(mo->player->cmd.forwardmove || mo->player->cmd.sidemove) && !mo->player->cmomx && !mo->player->cmomy && !(mo->player->pflags & PF_SPINNING))
{
mo->momx >>= 1;
mo->momy >>= 1;
}
}
if (!(mo->player->pflags & PF_SPINNING) && mo->player->powers[pw_carry] != CR_NIGHTSMODE)
mo->player->pflags &= ~PF_STARTDASH;
if (clipmomz)
mo->momz = (tmfloorthing ? tmfloorthing->momz : 0);
}
else if (tmfloorthing)
mo->momz = tmfloorthing->momz;
}
else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here!
{
if (P_IsObjectInGoop(mo) && !(mo->flags & MF_NOCLIPHEIGHT))
{
if (mo->z < mo->floorz)
{
mo->z = mo->floorz;
mo->momz = 0;
}
else if (mo->z + mo->height > mo->ceilingz)
{
mo->z = mo->ceilingz - mo->height;
mo->momz = 0;
}
}
/// \todo may not be needed (done in P_MobjThinker normally)
mo->eflags &= ~MFE_JUSTHITFLOOR;
P_CheckGravity(mo, true);
}
nightsdone:
if (((mo->eflags & MFE_VERTICALFLIP && mo->z < mo->floorz) || (!(mo->eflags & MFE_VERTICALFLIP) && mo->z + mo->height > mo->ceilingz))
&& !(mo->flags & MF_NOCLIPHEIGHT))
{
if (mo->eflags & MFE_VERTICALFLIP)
mo->z = mo->floorz;
else
mo->z = mo->ceilingz - mo->height;
if (mo->player->powers[pw_carry] == CR_NIGHTSMODE)
{
// bounce off ceiling if you were flying towards it
if ((mo->eflags & MFE_VERTICALFLIP && mo->player->flyangle > 180 && mo->player->flyangle <= 359)
|| (!(mo->eflags & MFE_VERTICALFLIP) && mo->player->flyangle > 0 && mo->player->flyangle < 180))
{
if (mo->player->flyangle < 90 || mo->player->flyangle >= 270)
mo->player->flyangle -= P_MobjFlip(mo)*90;
else
mo->player->flyangle += P_MobjFlip(mo)*90;
mo->player->flyangle %= 360;
mo->player->speed = FixedMul(mo->player->speed, 4*FRACUNIT/5);
}
}
if (P_MobjFlip(mo)*mo->momz > 0)
{
if (CheckForMarioBlocks)
P_CheckMarioBlocks(mo);
// hit the ceiling
if (mariomode)
S_StartSound(mo, sfx_mario1);
if (!mo->player->climbing)
mo->momz = 0;
}
}
}
boolean P_SceneryZMovement(mobj_t *mo)
{
// Intercept the stupid 'fall through 3dfloors' bug
if (mo->subsector->sector->ffloors)
P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 2);
if (mo->subsector->polyList)
P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
// adjust height
if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
{
mo->momz += mo->pmomz;
mo->pmomz = 0;
mo->eflags &= ~MFE_APPLYPMOMZ;
}
mo->z += mo->momz;
if (!mo->player && P_CheckDeathPitCollide(mo) && mo->health
&& !(mo->flags & MF_NOCLIPHEIGHT) && !(mo->flags2 & MF2_BOSSDEAD))
{
P_RemoveMobj(mo);
return false;
}
switch (mo->type)
{
case MT_SMALLBUBBLE:
if (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz) // Hit the floor, so POP!
{
// don't sounds stop when you kill the mobj..?
// yes, they do, making this entirely redundant
P_RemoveMobj(mo);
return false;
}
break;
case MT_MEDIUMBUBBLE:
if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
|| (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz)) // Hit the floor, so split!
{
// split
mobj_t *explodemo = NULL;
UINT8 prandom, i;
for (i = 0; i < 4; ++i) // split into four
{
prandom = P_RandomByte();
explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_SMALLBUBBLE);
if (P_MobjWasRemoved(explodemo))
continue;
explodemo->momx += ((prandom & 0x0F) << (FRACBITS-2)) * (i & 2 ? -1 : 1);
explodemo->momy += ((prandom & 0xF0) << (FRACBITS-6)) * (i & 1 ? -1 : 1);
P_SetScale(explodemo, mo->scale, true);
}
if (mo->threshold != 42) // Don't make pop sound if threshold is 42.
S_StartSound(explodemo, sfx_bubbl1 + P_RandomKey(5));
//note that we assign the bubble sound to one of the new bubbles.
// in other words, IT ACTUALLY GETS USED YAAAAAAAY
P_RemoveMobj(mo);
return false;
}
else if (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz) // Hit the ceiling instead? Just disappear anyway
{
P_RemoveMobj(mo);
return false;
}
break;
case MT_SEED: // now scenery
// Soniccd seed turns into a flower!
if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
|| (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz))
{
mobjtype_t flowertype = ((P_RandomChance(FRACUNIT/2)) ? MT_GFZFLOWER1 : MT_GFZFLOWER3);
mobj_t *flower = P_SpawnMobjFromMobj(mo, 0, 0, 0, flowertype);
if (flower)
{
P_SetScale(flower, mo->scale/16, true);
flower->destscale = mo->scale;
flower->scalespeed = mo->scale/8;
}
P_RemoveMobj(mo);
return false;
}
default:
break;
}
// clip movement
if (((mo->z <= mo->floorz && !(mo->eflags & MFE_VERTICALFLIP))
|| (mo->z + mo->height >= mo->ceilingz && mo->eflags & MFE_VERTICALFLIP))
&& !(mo->flags & MF_NOCLIPHEIGHT))
{
if (mo->eflags & MFE_VERTICALFLIP)
mo->z = mo->ceilingz - mo->height;
else
mo->z = mo->floorz;
if (P_MobjFlip(mo)*mo->momz < 0) // falling
{
mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
if (tmfloorthing)
mo->momz = tmfloorthing->momz;
else if (!tmfloorthing)
mo->momz = 0;
}
}
else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here!
{
/// \todo may not be needed (done in P_MobjThinker normally)
mo->eflags &= ~MFE_JUSTHITFLOOR;
P_CheckGravity(mo, true);
}
if (((mo->z + mo->height > mo->ceilingz && !(mo->eflags & MFE_VERTICALFLIP))
|| (mo->z < mo->floorz && mo->eflags & MFE_VERTICALFLIP))
&& !(mo->flags & MF_NOCLIPHEIGHT))
{
if (mo->eflags & MFE_VERTICALFLIP)
mo->z = mo->floorz;
else
mo->z = mo->ceilingz - mo->height;
if (P_MobjFlip(mo)*mo->momz > 0) // hit the ceiling
mo->momz = 0;
}
return true;
}
// P_CanRunOnWater
//
// Returns true if player can waterrun on the 3D floor
//
boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
{
boolean flip = player->mo->eflags & MFE_VERTICALFLIP;
fixed_t surfaceheight = flip ? P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y) : P_GetFFloorTopZAt(rover, player->mo->x, player->mo->y);
fixed_t playerbottom = flip ? (player->mo->z + player->mo->height) : player->mo->z;
boolean doifit = flip ? (surfaceheight - player->mo->floorz >= player->mo->height) : (player->mo->ceilingz - surfaceheight >= player->mo->height);
if (!player->powers[pw_carry] && !player->homing
&& ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= DASHMODE_THRESHOLD) && doifit)
&& (rover->fofflags & FOF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
&& !(player->pflags & PF_SLIDING)
&& abs(playerbottom - surfaceheight) < FixedMul(30*FRACUNIT, player->mo->scale))
return true;
return false;
}
//
// P_MobjCheckWater
//
// Check for water, set stuff in mobj_t struct for movement code later.
// This is called either by P_MobjThinker() or P_PlayerThink()
void P_MobjCheckWater(mobj_t *mobj)
{
boolean waterwasnotset = (mobj->watertop == INT32_MAX);
boolean wasinwater = (mobj->eflags & MFE_UNDERWATER) == MFE_UNDERWATER;
boolean wasingoo = (mobj->eflags & MFE_GOOWATER) == MFE_GOOWATER;
fixed_t thingtop = mobj->z + mobj->height;
sector_t *sector = mobj->subsector->sector;
ffloor_t *rover;
player_t *p = mobj->player; // Will just be null if not a player.
fixed_t height = (p ? P_GetPlayerHeight(p) : mobj->height); // for players, calculation height does not necessarily match actual height for gameplay reasons (spin, etc)
boolean wasgroundpounding = (p && ((p->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (p->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (p->pflags & PF_SHIELDABILITY));
// Default if no water exists.
mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
// Reset water state.
mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER|MFE_GOOWATER|MFE_TOUCHLAVA);
for (rover = sector->ffloors; rover; rover = rover->next)
{
fixed_t topheight, bottomheight;
if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_SWIMMABLE)
|| (((rover->fofflags & FOF_BLOCKPLAYER) && mobj->player)
|| ((rover->fofflags & FOF_BLOCKOTHERS) && !mobj->player)))
continue;
topheight = P_GetSpecialTopZ(mobj, sectors + rover->secnum, sector);
bottomheight = P_GetSpecialBottomZ(mobj, sectors + rover->secnum, sector);
if (mobj->eflags & MFE_VERTICALFLIP)
{
if (topheight < (thingtop - (height>>1))
|| bottomheight > thingtop)
continue;
}
else
{
if (topheight < mobj->z
|| bottomheight > (mobj->z + (height>>1)))
continue;
}
// Set the watertop and waterbottom
mobj->watertop = topheight;
mobj->waterbottom = bottomheight;
// Just touching the water?
if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - height < bottomheight)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + height > topheight))
mobj->eflags |= MFE_TOUCHWATER;
// Actually in the water?
if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - (height>>1) > bottomheight)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + (height>>1) < topheight))
mobj->eflags |= MFE_UNDERWATER;
if (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
{
if (rover->master->frontsector->damagetype == SD_FIRE || rover->master->frontsector->damagetype == SD_LAVA)
mobj->eflags |= MFE_TOUCHLAVA;
if (rover->fofflags & FOF_GOOWATER && !(mobj->flags & MF_NOGRAVITY))
mobj->eflags |= MFE_GOOWATER;
}
}
// Spectators and dead players don't get to do any of the things after this.
if (p && (p->spectator || p->playerstate != PST_LIVE))
return;
// Specific things for underwater players
if (p && (mobj->eflags & MFE_UNDERWATER) == MFE_UNDERWATER)
{
if (!((p->powers[pw_super]) || (p->powers[pw_invulnerability])))
{
boolean electric = !!(p->powers[pw_shield] & SH_PROTECTELECTRIC);
if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER) && !(mobj->eflags & MFE_TOUCHLAVA)))
{ // Water removes electric and non-water fire shields...
if (electric)
P_FlashPal(p, PAL_WHITE, 1);
p->powers[pw_shield] = p->powers[pw_shield] & SH_STACK;
}
}
// Drown timer setting
if ((p->powers[pw_shield] & SH_PROTECTWATER) // Has water protection
|| (p->exiting) || (p->pflags & PF_FINISHED) // Or finished/exiting
|| (maptol & TOL_NIGHTS) // Or in NiGHTS mode
|| (mariomode)) // Or in Mario mode...
{
// Can't drown.
p->powers[pw_underwater] = 0;
}
else if (p->powers[pw_underwater] <= 0) // No underwater timer set
{
// Then we'll set it!
p->powers[pw_underwater] = underwatertics + 1;
}
if ((wasgroundpounding = ((mobj->eflags & MFE_GOOWATER) && wasgroundpounding)))
{
p->pflags &= ~PF_SHIELDABILITY;
mobj->momz >>= 1;
}
}
// The rest of this code only executes on a water state change.
if (waterwasnotset || !!(mobj->eflags & MFE_UNDERWATER) == wasinwater)
return;
if ((p) // Players
|| (mobj->flags & MF_PUSHABLE) // Pushables
|| ((mobj->info->flags & MF_PUSHABLE) && mobj->fuse) // Previously pushable, might be moving still
)
{
// Check to make sure you didn't just cross into a sector to jump out of
// that has shallower water than the block you were originally in.
if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->watertop-mobj->floorz <= height>>1)
|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->ceilingz-mobj->waterbottom <= height>>1))
return;
if (mobj->eflags & MFE_GOOWATER || wasingoo) { // Decide what happens to your momentum when you enter/leave goopy water.
if (P_MobjFlip(mobj)*mobj->momz > 0)
{
mobj->momz -= (mobj->momz/8); // cut momentum a little bit to prevent multiple bobs
//CONS_Printf("leaving\n");
}
else
{
if (!wasgroundpounding)
mobj->momz >>= 1; // kill momentum significantly, to make the goo feel thick.
//CONS_Printf("entering\n");
}
}
else if (wasinwater && P_MobjFlip(mobj)*mobj->momz > 0)
mobj->momz = FixedMul(mobj->momz, FixedDiv(780*FRACUNIT, 457*FRACUNIT)); // Give the mobj a little out-of-water boost.
if (P_MobjFlip(mobj)*mobj->momz < 0)
{
if ((mobj->eflags & MFE_VERTICALFLIP && thingtop-(height>>1)-mobj->momz <= mobj->waterbottom)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z+(height>>1)-mobj->momz >= mobj->watertop))
{
// Spawn a splash
mobj_t *splish;
mobjtype_t splishtype = (mobj->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
if (mobj->eflags & MFE_VERTICALFLIP)
{
splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
if (!P_MobjWasRemoved(splish))
{
splish->flags2 |= MF2_OBJECTFLIP;
splish->eflags |= MFE_VERTICALFLIP;
}
}
else
splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
if (!P_MobjWasRemoved(splish))
P_SetScale(splish, mobj->scale, true);
}
// skipping stone!
if (p && p->speed/2 > abs(mobj->momz)
&& ((p->pflags & (PF_SPINNING|PF_JUMPED)) == PF_SPINNING)
&& ((!(mobj->eflags & MFE_VERTICALFLIP) && thingtop - mobj->momz > mobj->watertop)
|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z - mobj->momz < mobj->waterbottom)))
{
mobj->momz = -mobj->momz/2;
if (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->momz > FixedMul(6*FRACUNIT, mobj->scale))
mobj->momz = FixedMul(6*FRACUNIT, mobj->scale);
else if (mobj->eflags & MFE_VERTICALFLIP && mobj->momz < FixedMul(-6*FRACUNIT, mobj->scale))
mobj->momz = FixedMul(-6*FRACUNIT, mobj->scale);
}
}
else if (P_MobjFlip(mobj)*mobj->momz > 0)
{
if (((mobj->eflags & MFE_VERTICALFLIP && thingtop-(height>>1)-mobj->momz > mobj->waterbottom)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z+(height>>1)-mobj->momz < mobj->watertop))
&& !(mobj->eflags & MFE_UNDERWATER)) // underwater check to prevent splashes on opposite side
{
// Spawn a splash
mobj_t *splish;
mobjtype_t splishtype = (mobj->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
if (mobj->eflags & MFE_VERTICALFLIP)
{
splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
if (!P_MobjWasRemoved(splish))
{
splish->flags2 |= MF2_OBJECTFLIP;
splish->eflags |= MFE_VERTICALFLIP;
}
}
else
splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
if (!P_MobjWasRemoved(splish))
P_SetScale(splish, mobj->scale, true);
}
}
// Time to spawn the bubbles!
{
INT32 i;
INT32 bubblecount;
UINT8 prandom[4];
mobj_t *bubble;
mobjtype_t bubbletype;
if (mobj->eflags & MFE_GOOWATER || wasingoo)
S_StartSound(mobj, sfx_ghit);
else if (mobj->eflags & MFE_TOUCHLAVA)
S_StartSound(mobj, sfx_splash);
else
S_StartSound(mobj, sfx_splish); // And make a sound!
bubblecount = FixedDiv(abs(mobj->momz), mobj->scale)>>(FRACBITS-1);
// Max bubble count
if (bubblecount > 128)
bubblecount = 128;
// Create tons of bubbles
for (i = 0; i < bubblecount; i++)
{
// P_RandomByte()s are called individually to allow consistency
// across various compilers, since the order of function calls
// in C is not part of the ANSI specification.
prandom[0] = P_RandomByte();
prandom[1] = P_RandomByte();
prandom[2] = P_RandomByte();
prandom[3] = P_RandomByte();
bubbletype = MT_SMALLBUBBLE;
if (!(prandom[0] & 0x3)) // medium bubble chance up to 64 from 32
bubbletype = MT_MEDIUMBUBBLE;
bubble = P_SpawnMobj(
mobj->x + FixedMul((prandom[1]<<(FRACBITS-3)) * (prandom[0]&0x80 ? 1 : -1), mobj->scale),
mobj->y + FixedMul((prandom[2]<<(FRACBITS-3)) * (prandom[0]&0x40 ? 1 : -1), mobj->scale),
mobj->z + FixedMul((prandom[3]<<(FRACBITS-2)), mobj->scale), bubbletype);
if (bubble)
{
if (P_MobjFlip(mobj)*mobj->momz < 0)
bubble->momz = mobj->momz >> 4;
else
bubble->momz = 0;
P_SetScale(bubble, mobj->scale, true);
}
}
}
}
}
static void P_SceneryCheckWater(mobj_t *mobj)
{
sector_t *sector;
// Default if no water exists.
mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
// see if we are in water, and set some flags for later
sector = mobj->subsector->sector;
if (sector->ffloors)
{
ffloor_t *rover;
fixed_t topheight, bottomheight;
mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER);
for (rover = sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_SWIMMABLE) || rover->fofflags & FOF_BLOCKOTHERS)
continue;
topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y);
bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y);
if (topheight <= mobj->z
|| bottomheight > (mobj->z + (mobj->height>>1)))
continue;
if (mobj->z + mobj->height > topheight)
mobj->eflags |= MFE_TOUCHWATER;
else
mobj->eflags &= ~MFE_TOUCHWATER;
// Set the watertop and waterbottom
mobj->watertop = topheight;
mobj->waterbottom = bottomheight;
if (mobj->z + (mobj->height>>1) < topheight)
mobj->eflags |= MFE_UNDERWATER;
else
mobj->eflags &= ~MFE_UNDERWATER;
}
}
else
mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER);
}
static boolean P_CameraCheckHeat(camera_t *thiscam)
{
sector_t *sector;
fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
// see if we are in water
sector = thiscam->subsector->sector;
if (sector->flags & MSF_HEATWAVE)
return true;
if (sector->ffloors)
{
ffloor_t *rover;
for (rover = sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS))
continue;
if (halfheight >= P_GetFFloorTopZAt(rover, thiscam->x, thiscam->y))
continue;
if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
continue;
if (rover->master->frontsector->flags & MSF_HEATWAVE)
return true;
}
}
return false;
}
static boolean P_CameraCheckWater(camera_t *thiscam)
{
sector_t *sector;
fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
// see if we are in water
sector = thiscam->subsector->sector;
if (sector->ffloors)
{
ffloor_t *rover;
for (rover = sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_SWIMMABLE) || rover->fofflags & FOF_BLOCKOTHERS)
continue;
if (halfheight >= P_GetFFloorTopZAt(rover, thiscam->x, thiscam->y))
continue;
if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
continue;
return true;
}
}
return false;
}
void P_DestroyRobots(void)
{
// Search through all the thinkers for enemies.
mobj_t *mo;
thinker_t *think;
for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
{
if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo = (mobj_t *)think;
if (mo->health <= 0 || !(mo->flags & (MF_ENEMY|MF_BOSS)))
continue; // not a valid enemy
if (mo->type == MT_PLAYER) // Don't chase after other players!
continue;
// Found a target enemy
P_KillMobj(mo, players[consoleplayer].mo, players[consoleplayer].mo, 0);
}
}
// the below is chasecam only, if you're curious. check out P_CalcPostImg in p_user.c for first person
void P_CalcChasePostImg(player_t *player, camera_t *thiscam)
{
postimg_t postimg = postimg_none;
if (player->pflags & PF_FLIPCAM && !(player->powers[pw_carry] == CR_NIGHTSMODE) && player->mo->eflags & MFE_VERTICALFLIP)
postimg = postimg_flip;
else if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj)) // Camera must obviously exist
{
camera_t dummycam;
dummycam.subsector = player->awayviewmobj->subsector;
dummycam.x = player->awayviewmobj->x;
dummycam.y = player->awayviewmobj->y;
dummycam.z = player->awayviewmobj->z;
//dummycam.height = 40*FRACUNIT; // alt view height is 20*FRACUNIT
dummycam.height = 0; // Why? Remote viewpoint cameras have no height.
// Are we in water?
if (P_CameraCheckWater(&dummycam))
postimg = postimg_water;
else if (P_CameraCheckHeat(&dummycam))
postimg = postimg_heat;
}
else
{
// Are we in water?
if (P_CameraCheckWater(thiscam))
postimg = postimg_water;
else if (P_CameraCheckHeat(thiscam))
postimg = postimg_heat;
}
if (postimg == postimg_none)
return;
if (splitscreen && player == &players[secondarydisplayplayer])
postimgtype2 = postimg;
else
postimgtype = postimg;
}
// P_CameraThinker
//
// Process the mobj-ish required functions of the camera
boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled)
{
boolean itsatwodlevel = false;
if (twodlevel
|| (thiscam == &camera && players[displayplayer].mo && (players[displayplayer].mo->flags2 & MF2_TWOD))
|| (thiscam == &camera2 && players[secondarydisplayplayer].mo && (players[secondarydisplayplayer].mo->flags2 & MF2_TWOD)))
itsatwodlevel = true;
P_CalcChasePostImg(player, thiscam);
if (thiscam->momx || thiscam->momy)
{
if (!P_TryCameraMove(thiscam->x + thiscam->momx, thiscam->y + thiscam->momy, thiscam)) // Thanks for the greatly improved camera, Lach -- Sev
{ // Never fails for 2D mode.
mobj_t dummy;
dummy.thinker.function.acp1 = (actionf_p1)P_MobjThinker;
dummy.subsector = thiscam->subsector;
dummy.x = thiscam->x;
dummy.y = thiscam->y;
dummy.z = thiscam->z;
dummy.height = thiscam->height;
if (!resetcalled && !(player->pflags & PF_NOCLIP || player->powers[pw_carry] == CR_NIGHTSMODE) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead.
P_ResetCamera(player, thiscam);
else
{
fixed_t camspeed = P_AproxDistance(thiscam->momx, thiscam->momy);
P_SlideCameraMove(thiscam);
if (!resetcalled && P_AproxDistance(thiscam->momx, thiscam->momy) == camspeed)
{
P_ResetCamera(player, thiscam);
resetcalled = true;
}
}
if (resetcalled) // Okay this means the camera is fully reset.
return true;
}
}
if (!itsatwodlevel)
P_CheckCameraPosition(thiscam->x, thiscam->y, thiscam);
thiscam->subsector = R_PointInSubsector(thiscam->x, thiscam->y);
thiscam->floorz = tmfloorz;
thiscam->ceilingz = tmceilingz;
if (thiscam->momz || player->mo->pmomz)
{
// adjust height
thiscam->z += thiscam->momz + player->mo->pmomz;
if (!itsatwodlevel && !(player->pflags & PF_NOCLIP || player->powers[pw_carry] == CR_NIGHTSMODE))
{
// clip movement
if (thiscam->z <= thiscam->floorz) // hit the floor
{
fixed_t cam_height = cv_cam_height.value;
thiscam->z = thiscam->floorz;
if (player == &players[secondarydisplayplayer])
cam_height = cv_cam2_height.value;
if (thiscam->z > player->mo->z + player->mo->height + FixedMul(cam_height*FRACUNIT + 16*FRACUNIT, player->mo->scale))
{
if (!resetcalled)
P_ResetCamera(player, thiscam);
return true;
}
}
if (thiscam->z + thiscam->height > thiscam->ceilingz)
{
if (thiscam->momz > 0)
{
// hit the ceiling
thiscam->momz = 0;
}
thiscam->z = thiscam->ceilingz - thiscam->height;
if (thiscam->z + thiscam->height < player->mo->z - player->mo->height)
{
if (!resetcalled)
P_ResetCamera(player, thiscam);
return true;
}
}
}
}
if (itsatwodlevel
|| (thiscam->ceilingz - thiscam->z < thiscam->height
&& thiscam->ceilingz >= thiscam->z))
{
thiscam->ceilingz = thiscam->z + thiscam->height;
thiscam->floorz = thiscam->z;
}
return false;
}
static void P_CheckCrumblingPlatforms(mobj_t *mobj)
{
msecnode_t *node;
if (netgame && mobj->player->spectator)
return;
for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next)
{
ffloor_t *rover;
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS))
continue;
if (!(rover->fofflags & FOF_CRUMBLE))
continue;
if (mobj->eflags & MFE_VERTICALFLIP)
{
if (P_GetSpecialBottomZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z + mobj->height)
continue;
}
else
{
if (P_GetSpecialTopZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z)
continue;
}
EV_StartCrumble(rover->master->frontsector, rover, (rover->fofflags & FOF_FLOATBOB), mobj->player, rover->alpha, !(rover->fofflags & FOF_NORETURN));
}
}
}
static boolean P_MobjTouchesSectorWithWater(mobj_t *mobj)
{
msecnode_t *node;
for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next)
{
ffloor_t *rover;
if (!node->m_sector->ffloors)
continue;
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS))
continue;
if (!(rover->fofflags & FOF_SWIMMABLE))
continue;
return true;
}
}
return false;
}
// Check for floating water platforms and bounce them
static void P_CheckFloatbobPlatforms(mobj_t *mobj)
{
msecnode_t *node;
// Can't land on anything if you're not moving downwards
if (P_MobjFlip(mobj)*mobj->momz >= 0)
return;
if (!P_MobjTouchesSectorWithWater(mobj))
return;
for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next)
{
ffloor_t *rover;
if (!node->m_sector->ffloors)
continue;
for (rover = node->m_sector->ffloors; rover; rover = rover->next)
{
if (!(rover->fofflags & FOF_EXISTS))
continue;
if (!(rover->fofflags & FOF_FLOATBOB))
continue;
if (mobj->eflags & MFE_VERTICALFLIP)
{
if (abs(*rover->bottomheight - (mobj->z + mobj->height)) > abs(mobj->momz))
continue;
}
else
{
if (abs(*rover->topheight - mobj->z) > abs(mobj->momz))
continue;
}
// Initiate a 'bouncy' elevator function which slowly diminishes.
EV_BounceSector(rover->master->frontsector, -mobj->momz, rover->master);
}
}
}
static void P_PlayerMobjThinker(mobj_t *mobj)
{
I_Assert(mobj != NULL);
I_Assert(mobj->player != NULL);
I_Assert(!P_MobjWasRemoved(mobj));
P_MobjCheckWater(mobj);
P_ButteredSlope(mobj);
// momentum movement
mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN;
if (mobj->state-states == S_PLAY_BOUNCE_LANDING)
goto animonly; // no need for checkposition - doesn't move at ALL
// Zoom tube
if (mobj->tracer)
{
if (mobj->player->powers[pw_carry] == CR_ZOOMTUBE || mobj->player->powers[pw_carry] == CR_ROPEHANG)
{
P_UnsetThingPosition(mobj);
mobj->x += mobj->momx;
mobj->y += mobj->momy;
mobj->z += mobj->momz;
P_SetThingPosition(mobj);
P_CheckPosition(mobj, mobj->x, mobj->y);
mobj->floorz = tmfloorz;
mobj->ceilingz = tmceilingz;
goto animonly;
}
else if (mobj->player->powers[pw_carry] == CR_MACESPIN)
{
P_CheckPosition(mobj, mobj->x, mobj->y);
mobj->floorz = tmfloorz;
mobj->ceilingz = tmceilingz;
goto animonly;
}
}
// Needed for gravity boots
P_CheckGravity(mobj, false);
mobj->player->powers[pw_justlaunched] = 0;
if (mobj->momx || mobj->momy)
{
P_XYMovement(mobj);
if (P_MobjWasRemoved(mobj))
return;
}
else
P_TryMove(mobj, mobj->x, mobj->y, true);
if (P_MobjWasRemoved(mobj))
return;
P_CheckCrumblingPlatforms(mobj);
if (CheckForFloatBob)
P_CheckFloatbobPlatforms(mobj);
// always do the gravity bit now, that's simpler
// BUT CheckPosition only if wasn't done before.
if (mobj->momz
|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz)
|| P_IsObjectInGoop(mobj))
{
P_PlayerZMovement(mobj);
P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
if (P_MobjWasRemoved(mobj))
return;
}
else
{
mobj->eflags &= ~MFE_JUSTHITFLOOR;
}
animonly:
P_CyclePlayerMobjState(mobj);
}
static void CalculatePrecipFloor(precipmobj_t *mobj)
{
// recalculate floorz each time
const sector_t *mobjsecsubsec;
if (mobj && mobj->subsector && mobj->subsector->sector)
mobjsecsubsec = mobj->subsector->sector;
else
return;
mobj->floorz = P_GetSectorFloorZAt(mobjsecsubsec, mobj->x, mobj->y);
if (mobjsecsubsec->ffloors)
{
ffloor_t *rover;
fixed_t topheight;
for (rover = mobjsecsubsec->ffloors; rover; rover = rover->next)
{
// If it exists, it'll get rained on.
if (!(rover->fofflags & FOF_EXISTS))
continue;
if (!(rover->fofflags & FOF_BLOCKOTHERS) && !(rover->fofflags & FOF_SWIMMABLE))
continue;
topheight = P_GetFFloorTopZAt(rover, mobj->x, mobj->y);
if (topheight > mobj->floorz)
mobj->floorz = topheight;
}
}
}
void P_RecalcPrecipInSector(sector_t *sector)
{
mprecipsecnode_t *psecnode;
if (!sector)
return;
sector->moved = true; // Recalc lighting and things too, maybe
for (psecnode = sector->touching_preciplist; psecnode; psecnode = psecnode->m_thinglist_next)
CalculatePrecipFloor(psecnode->m_thing);
}
//
// P_NullPrecipThinker
//
// For "Blank" precipitation
//
void P_NullPrecipThinker(precipmobj_t *mobj)
{
//(void)mobj;
mobj->precipflags &= ~PCF_THUNK;
R_ResetPrecipitationMobjInterpolationState(mobj);
}
void P_SnowThinker(precipmobj_t *mobj)
{
P_CycleStateAnimation((mobj_t *)mobj);
// adjust height
if ((mobj->z += mobj->momz) <= mobj->floorz)
{
mobj->z = mobj->ceilingz;
R_ResetPrecipitationMobjInterpolationState(mobj);
}
}
void P_RainThinker(precipmobj_t *mobj)
{
P_CycleStateAnimation((mobj_t *)mobj);
if (mobj->state != &states[S_RAIN1])
{
// cycle through states,
// calling action functions at transitions
if (mobj->tics <= 0)
return;
if (--mobj->tics)
return;
if (!P_SetPrecipMobjState(mobj, mobj->state->nextstate))
return;
if (mobj->state != &states[S_RAINRETURN])
return;
mobj->z = mobj->ceilingz;
R_ResetPrecipitationMobjInterpolationState(mobj);
P_SetPrecipMobjState(mobj, S_RAIN1);
return;
}
// adjust height
if ((mobj->z += mobj->momz) > mobj->floorz)
return;
// no splashes on sky or bottomless pits
if (mobj->precipflags & PCF_PIT)
{
mobj->z = mobj->ceilingz;
return;
}
mobj->z = mobj->floorz;
P_SetPrecipMobjState(mobj, S_SPLASH1);
}
static void P_KillRingsInLava(mobj_t *mo)
{
msecnode_t *node;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
// go through all sectors being touched by the ring
for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
{
if (!node->m_sector)
break;
if (node->m_sector->ffloors)
{
ffloor_t *rover;
fixed_t topheight, bottomheight;
for (rover = node->m_sector->ffloors; rover; rover = rover->next) // go through all fofs in the sector
{
if (!(rover->fofflags & FOF_EXISTS)) continue; // fof must be real
if (!(rover->fofflags & FOF_SWIMMABLE))
continue; // fof must be water
if (rover->master->frontsector->damagetype != SD_FIRE && rover->master->frontsector->damagetype != SD_LAVA)
continue; // fof must have fire or lava damage
// find heights of FOF
topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
if (mo->z <= topheight && mo->z + mo->height >= bottomheight) // if ring touches it, KILL IT
{
P_KillMobj(mo, NULL, NULL, DMG_FIRE);
return;
}
}
}
}
}
static void P_RingThinker(mobj_t *mobj)
{
if (mobj->momx || mobj->momy)
{
P_RingXYMovement(mobj);
if (P_MobjWasRemoved(mobj))
return;
}
// always do the gravity bit now, that's simpler
// BUT CheckPosition only if wasn't done before.
if (mobj->momz)
{
P_RingZMovement(mobj);
P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
if (P_MobjWasRemoved(mobj))
return;
}
P_CycleMobjState(mobj);
}
//
// P_BossTargetPlayer
// If closest is true, find the closest player.
// Returns true if a player is targeted.
//
boolean P_BossTargetPlayer(mobj_t *actor, boolean closest)
{
INT32 stop = -1, c = 0;
player_t *player;
fixed_t dist, lastdist = 0;
// first time init, this allow minimum lastlook changes
if (actor->lastlook < 0)
actor->lastlook = P_RandomByte();
actor->lastlook &= PLAYERSMASK;
for( ; ; actor->lastlook = (actor->lastlook+1) & PLAYERSMASK)
{
// save the first look so we stop next time.
if (stop < 0)
stop = actor->lastlook;
// reached the beginning again, done looking.
else if (actor->lastlook == stop)
return (closest && lastdist > 0);
if (!playeringame[actor->lastlook])
continue;
if (!closest && c++ == 2)
return false;
player = &players[actor->lastlook];
if (player->pflags & PF_INVIS || player->bot == BOT_2PAI || player->bot == BOT_2PHUMAN || player->spectator)
continue; // ignore notarget
if (!player->mo || P_MobjWasRemoved(player->mo))
continue;
if (player->mo->health <= 0)
continue; //dead
if (!P_CheckSight(actor, player->mo))
continue; // out of sight
if (closest)
{
dist = P_AproxDistance(actor->x - player->mo->x, actor->y - player->mo->y);
if (!lastdist || dist < lastdist)
{
lastdist = dist+1;
P_SetTarget(&actor->target, player->mo);
}
continue;
}
P_SetTarget(&actor->target, player->mo);
return true;
}
}
// Finds the player no matter what they're hiding behind (even lead!)
boolean P_SupermanLook4Players(mobj_t *actor)
{
INT32 c, stop = 0;
player_t *playersinthegame[MAXPLAYERS];
for (c = 0; c < MAXPLAYERS; c++)
{
if (playeringame[c] && !players[c].spectator)
{
if (players[c].pflags & PF_INVIS)
continue; // ignore notarget
if (!players[c].mo || players[c].bot == BOT_2PAI || players[c].bot == BOT_2PHUMAN)
continue;
if (players[c].mo->health <= 0)
continue; // dead
playersinthegame[stop] = &players[c];
stop++;
}
}
if (!stop)
return false;
P_SetTarget(&actor->target, playersinthegame[P_RandomKey(stop)]->mo);
return true;
}
// AI for a generic boss.
static void P_GenericBossThinker(mobj_t *mobj)
{
if (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1)
mobj->flags2 &= ~MF2_FRET;
if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))
{
if (mobj->health <= 0)
return;
// look for a new target
if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound)
S_StartSound(mobj, mobj->info->seesound);
return;
}
// Don't call A_ functions here, let the SOC do the AI!
if (mobj->state == &states[mobj->info->meleestate]
|| (mobj->state == &states[mobj->info->missilestate]
&& mobj->health > mobj->info->damage))
{
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
}
}
// AI for the first boss.
static void P_Boss1Thinker(mobj_t *mobj)
{
if (mobj->flags2 & MF2_FRET && mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1) {
mobj->flags2 &= ~(MF2_FRET|MF2_SKULLFLY);
mobj->momx = mobj->momy = mobj->momz = 0;
}
if (!mobj->tracer)
{
var1 = 0;
A_BossJetFume(mobj);
}
if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))
{
if (mobj->target && mobj->target->health
&& mobj->target->type == MT_EGGMOBILE_TARGET) // Oh, we're just firing our laser.
return; // It's okay, then.
if (mobj->health <= 0)
return;
// look for a new target
if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound)
S_StartSound(mobj, mobj->info->seesound);
return;
}
if (mobj->flags2 & MF2_SKULLFLY)
{
fixed_t dist = (mobj->eflags & MFE_VERTICALFLIP)
? ((mobj->ceilingz-(2*mobj->height)) - (mobj->z+mobj->height))
: (mobj->z - (mobj->floorz+(2*mobj->height)));
if (dist > 0 && P_MobjFlip(mobj)*mobj->momz > 0)
mobj->momz = FixedMul(mobj->momz, FRACUNIT - (dist>>12));
}
else if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0 && mobj->flags & MF_FLOAT)
mobj->momz = FixedMul(mobj->momz,7*FRACUNIT/8);
if (mobj->state == &states[mobj->info->meleestate]
|| (mobj->state == &states[mobj->info->missilestate]
&& mobj->health > mobj->info->damage))
{
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
}
}
// AI for the second boss.
// No, it does NOT convert "Boss" to a "Thinker". =P
static void P_Boss2Thinker(mobj_t *mobj)
{
if (mobj->movecount)
mobj->movecount--;
if (!mobj->movecount)
mobj->flags2 &= ~MF2_FRET;
if (!mobj->tracer)
{
var1 = 0;
A_BossJetFume(mobj);
}
if (mobj->health <= mobj->info->damage && (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)))
{
if (mobj->health <= 0)
return;
// look for a new target
if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound)
S_StartSound(mobj, mobj->info->seesound);
return;
}
if (mobj->state == &states[mobj->info->spawnstate] && mobj->health > mobj->info->damage)
A_Boss2Chase(mobj);
else if (mobj->health > 0 && mobj->state != &states[mobj->info->painstate] && mobj->state != &states[mobjinfo[mobj->info->missilestate].raisestate])
{
mobj->flags &= ~MF_NOGRAVITY;
A_Boss2Pogo(mobj);
if (mobj->spawnpoint)
P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
}
}
// AI for the third boss.
//
// Notes for reminders:
// movedir = move 2x fast?
// movecount = fire missiles?
// reactiontime = shock the water?
// threshold = next waypoint #
// extravalue1 = previous time shock sound was used
//
static void P_Boss3Thinker(mobj_t *mobj)
{
if (mobj->state == &states[mobj->info->spawnstate])
mobj->flags2 &= ~MF2_FRET;
if (mobj->flags2 & MF2_FRET)
mobj->movedir = 1;
if (mobj->health <= 0)
return;
/*
{
mobj->movecount = 0;
mobj->reactiontime = 0;
if (mobj->state < &states[mobj->info->xdeathstate])
return;
if (mobj->threshold == -1)
{
mobj->momz = mobj->info->speed;
return;
}
else
{
mobj->flags |= MF_NOGRAVITY|MF_NOCLIP;
mobj->flags |= MF_NOCLIPHEIGHT;
mobj->threshold = -1;
return;
}
}*/
if (mobj->reactiontime) // At the bottom of the water
{
UINT32 i;
SINT8 curpath = mobj->threshold;
// Choose one of the paths you're not already on
mobj->threshold = P_RandomKey(8-1);
if (mobj->threshold >= curpath)
mobj->threshold++;
if (mobj->state != &states[mobj->info->spawnstate])
P_SetMobjState(mobj, mobj->info->spawnstate);
mobj->reactiontime--;
if (!mobj->reactiontime && mobj->health <= mobj->info->damage)
{ // Spawn pinch dummies from the center when we're leaving it.
thinker_t *th;
mobj_t *mo2;
mobj_t *dummy;
SINT8 way0 = mobj->threshold; // 0 through 4.
SINT8 way1, way2;
i = 0; // reset i to 0 so we can check how many clones we've removed
// scan the thinkers to make sure all the old pinch dummies are gone before making new ones
// this can happen if the boss was hurt earlier than expected
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo2 = (mobj_t *)th;
if (mo2->type != (mobjtype_t)mobj->info->mass)
continue;
if (mo2->tracer != mobj)
continue;
P_RemoveMobj(mo2);
if (++i == 2) // we've already removed 2 of these, let's stop now
break;
}
way1 = P_RandomKey(8-2);
if (way1 >= curpath)
way1++;
if (way1 >= way0)
{
way1++;
if (way1 == curpath)
way1++;
}
dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
if (!P_MobjWasRemoved(dummy))
{
dummy->angle = mobj->angle;
dummy->threshold = way1;
P_SetTarget(&dummy->tracer, mobj);
dummy->movefactor = mobj->movefactor;
dummy->cusval = mobj->cusval;
}
way2 = P_RandomKey(8-3);
if (way2 >= curpath)
way2++;
if (way2 >= way0)
{
way2++;
if (way2 == curpath)
way2++;
}
if (way2 >= way1)
{
way2++;
if (way2 == curpath || way2 == way0)
way2++;
}
dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
if (!P_MobjWasRemoved(dummy))
{
dummy->angle = mobj->angle;
dummy->threshold = way2;
P_SetTarget(&dummy->tracer, mobj);
dummy->movefactor = mobj->movefactor;
dummy->cusval = mobj->cusval;
}
CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", way0, way1, way2);
if (mobj->spawnpoint)
P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
}
}
else if (mobj->movecount) // Firing mode
{
// Check if the attack animation is running. If not, play it.
if (mobj->state < &states[mobj->info->missilestate] || mobj->state > &states[mobj->info->raisestate])
{
// look for a new target
P_BossTargetPlayer(mobj, true);
if (!mobj->target || !mobj->target->player)
return;
if (mobj->health <= mobj->info->damage) // pinch phase
mobj->movecount--; // limited number of shots before diving again
if (mobj->movecount)
P_SetMobjState(mobj, mobj->info->missilestate+1);
}
else if (mobj->target && mobj->target->player)
{
angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
if (diff > ANGLE_180)
diff = InvAngle(InvAngle(diff)/4);
else
diff /= 4;
mobj->angle += diff;
}
}
else if (mobj->threshold >= 0) // Traveling mode
{
fixed_t dist = 0;
fixed_t speed;
P_SetTarget(&mobj->target, NULL);
if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0
&& !(mobj->flags2 & MF2_FRET))
P_SetMobjState(mobj, mobj->info->spawnstate);
if (!(mobj->flags2 & MF2_STRONGBOX))
{
mobj_t *mo2;
INT32 i;
P_SetTarget(&mobj->tracer, NULL);
// Find waypoint
TAG_ITER_THINGS(mobj->cusval, i)
{
mo2 = mapthings[i].mobj;
if (!mo2)
continue;
if (mo2->type != MT_BOSS3WAYPOINT)
continue;
if (mapthings[i].args[0] != mobj->threshold)
continue;
P_SetTarget(&mobj->tracer, mo2);
break;
}
}
if (!mobj->tracer) // Should NEVER happen
{
CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d, %d\n", mobj->threshold, mobj->cusval);
return;
}
if ((mobj->movedir) || (mobj->health <= mobj->info->damage))
speed = mobj->info->speed * 2;
else
speed = mobj->info->speed;
if (mobj->tracer->x == mobj->x && mobj->tracer->y == mobj->y)
{
// apply ambush for old routing, otherwise whack a mole only
dist = P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z + mobj->movefactor - mobj->z);
if (dist < 1)
dist = 1;
mobj->momx = FixedMul(FixedDiv(mobj->tracer->x - mobj->x, dist), speed);
mobj->momy = FixedMul(FixedDiv(mobj->tracer->y - mobj->y, dist), speed);
mobj->momz = FixedMul(FixedDiv(mobj->tracer->z + mobj->movefactor - mobj->z, dist), speed);
if (mobj->momx != 0 || mobj->momy != 0)
mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
}
if (dist <= speed)
{
// If distance to point is less than travel in that frame, set XYZ of mobj to waypoint location
P_UnsetThingPosition(mobj);
mobj->x = mobj->tracer->x;
mobj->y = mobj->tracer->y;
mobj->z = mobj->tracer->z + mobj->movefactor;
mobj->momx = mobj->momy = mobj->momz = 0;
P_SetThingPosition(mobj);
if (!mobj->movefactor) // to firing mode
{
UINT8 i, numtospawn = 24;
angle_t ang = 0, interval = FixedAngle((360 << FRACBITS) / numtospawn);
mobj_t *shock = NULL, *sfirst = NULL, *sprev = NULL;
mobj->movecount = mobj->health+1;
mobj->movefactor = -512*FRACUNIT;
// shock the water!
for (i = 0; i < numtospawn; i++)
{
shock = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SHOCKWAVE);
if (P_MobjWasRemoved(shock))
continue;
P_SetTarget(&shock->target, mobj);
shock->fuse = shock->info->painchance;
if (i % 2 == 0)
P_SetMobjState(shock, shock->state->nextstate);
if (!sprev)
sfirst = shock;
else
{
if (i == numtospawn - 1)
P_SetTarget(&shock->hnext, sfirst);
P_SetTarget(&sprev->hnext, shock);
}
P_Thrust(shock, ang, shock->info->speed);
ang += interval;
sprev = shock;
}
if (!P_MobjWasRemoved(shock))
S_StartSound(mobj, shock->info->seesound);
// look for a new target
P_BossTargetPlayer(mobj, true);
if (mobj->target && mobj->target->player)
P_SetMobjState(mobj, mobj->info->missilestate);
}
else if (mobj->flags2 & (MF2_STRONGBOX|MF2_CLASSICPUSH)) // just hit the bottom of your tube
{
mobj->flags2 &= ~(MF2_STRONGBOX|MF2_CLASSICPUSH);
mobj->reactiontime = 1; // spawn pinch dummies
mobj->movedir = 0;
}
else // just shifted to another tube
{
mobj->flags2 |= MF2_STRONGBOX;
if (mobj->health > 0)
mobj->movefactor = 0;
}
}
}
}
// Move Boss4's sectors by delta.
static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
{
INT32 snum;
sector_t *sector;
boolean gotcage = false;
if (!mobj->spawnpoint)
return false;
TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum)
{
sector = &sectors[snum];
sector->floorheight += delta;
sector->ceilingheight += delta;
P_CheckSector(sector, true);
gotcage = true;
}
return gotcage;
}
// Move Boss4's arms to angle
static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
{
INT32 s;
mobj_t *base = mobj, *seg;
fixed_t dist, bz = mobj->watertop+(8<<FRACBITS);
while ((base = base->tracer))
{
for (seg = base, dist = 172*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 124*FRACUNIT, --s)
P_MoveOrigin(seg, mobj->x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), bz + FixedMul(fz, FixedDiv(s<<FRACBITS, 9<<FRACBITS)));
angle += ANGLE_MAX/3;
}
}
#define CEZ3TILT
// Pull them closer.
static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
{
INT32 s;
mobj_t *base = mobj, *seg;
fixed_t workx, worky, dx, dy, bz = mobj->watertop+(8<<FRACBITS);
fixed_t rad = (9*132)<<FRACBITS;
#ifdef CEZ3TILT
fixed_t originx, originy;
if (mobj->spawnpoint)
{
originx = mobj->spawnpoint->x << FRACBITS;
originy = mobj->spawnpoint->y << FRACBITS;
}
else
{
originx = mobj->x;
originy = mobj->y;
}
#else
if (mobj->spawnpoint)
{
rad -= R_PointToDist2(mobj->x, mobj->y,
(mobj->spawnpoint->x<<FRACBITS), (mobj->spawnpoint->y<<FRACBITS));
}
#endif
dz /= 9;
while ((base = base->tracer)) // there are 10 per spoke, remember that
{
#ifdef CEZ3TILT
dx = (originx + P_ReturnThrustX(mobj, angle, rad) - mobj->x)/9;
dy = (originy + P_ReturnThrustY(mobj, angle, rad) - mobj->y)/9;
#else
dx = P_ReturnThrustX(mobj, angle, rad)/9;
dy = P_ReturnThrustY(mobj, angle, rad)/9;
#endif
workx = mobj->x + P_ReturnThrustX(mobj, angle, (112)<<FRACBITS);
worky = mobj->y + P_ReturnThrustY(mobj, angle, (112)<<FRACBITS);
for (seg = base, s = 9; seg; seg = seg->hnext, --s)
{
seg->z = bz + (dz*(9-s));
P_TryMove(seg, workx + (dx*s), worky + (dy*s), true);
if (P_MobjWasRemoved(seg))
return;
}
angle += ANGLE_MAX/3;
}
}
// Destroy cage FOFs.
static void P_Boss4DestroyCage(mobj_t *mobj)
{
INT32 snum;
size_t a;
sector_t *sector, *rsec;
ffloor_t *rover;
if (!mobj->spawnpoint)
return;
TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum)
{
sector = &sectors[snum];
// Destroy the FOFs.
for (a = 0; a < sector->numattached; a++)
{
rsec = &sectors[sector->attached[a]];
for (rover = rsec->ffloors; rover; rover = rover->next)
if (rover->fofflags & FOF_EXISTS && rover->secnum == (size_t)snum)
{
if (rover->fofflags & FOF_RENDERALL) // checking for FF_RENDERANY.
EV_CrumbleChain(rsec, rover); // This FOF is visible to some extent? Crumble it.
else // Completely invisible FOF
{
// no longer exists (can't collide with again)
rover->fofflags &= ~FOF_EXISTS;
sector->moved = true;
rsec->moved = true;
}
}
}
}
}
// Destroy Boss4's arms
static void P_Boss4PopSpikeballs(mobj_t *mobj)
{
mobj_t *base = mobj->tracer, *seg, *next;
P_SetTarget(&mobj->tracer, NULL);
while(base)
{
next = base->tracer;
P_SetTarget(&base->tracer, NULL);
for (seg = base; seg; seg = seg->hnext)
if (seg->health)
P_KillMobj(seg, NULL, NULL, 0);
base = next;
}
}
//
// AI for the fourth boss.
//
static void P_Boss4Thinker(mobj_t *mobj)
{
fixed_t movespeed = 0;
if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate)
{
if (mobj->flags2 & MF2_FRET && (mobj->health > mobj->info->damage))
mobj->flags2 &= ~MF2_FRET;
mobj->reactiontime = 0; // Drop the cage immediately.
}
// Oh no, we dead? D:
if (!mobj->health)
{
if (mobj->tracer) // need to clean up!
{
P_Boss4DestroyCage(mobj); // Just in case pinch phase was skipped.
P_Boss4PopSpikeballs(mobj);
}
return;
}
if (mobj->movedir) // only not during init
{
INT32 oldmovecount = mobj->movecount;
if (mobj->movedir == 3) // pinch start
movespeed = -(210<<(FRACBITS>>1));
else if (mobj->movedir > 3) // pinch
{
movespeed = 420<<(FRACBITS>>1);
movespeed += (420*(mobj->info->damage-mobj->health)<<(FRACBITS>>1));
if (mobj->movedir == 4)
movespeed = -movespeed;
}
else // normal
{
movespeed = 170<<(FRACBITS>>1);
movespeed += ((50*(mobj->info->spawnhealth-mobj->health))<<(FRACBITS>>1));
if (mobj->movedir == 2)
movespeed = -movespeed;
if (mobj->movefactor)
movespeed /= 2;
else if (mobj->threshold)
{
// 1 -> 1.5 second timer
INT32 maxtimer = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
if (maxtimer < 1)
maxtimer = 1;
maxtimer = ((mobj->threshold*movespeed)/(2*maxtimer));
movespeed -= maxtimer;
}
}
mobj->movecount += movespeed + 360*FRACUNIT;
mobj->movecount %= 360*FRACUNIT;
if (((oldmovecount>>FRACBITS)%120 >= 60) && !((mobj->movecount>>FRACBITS)%120 >= 60))
S_StartSound(NULL, sfx_mswing);
}
// movedir == battle stage:
// 0: initialization
// 1: phase 1 forward
// 2: phase 1 reverse
// 3: pinch rise
// 4: pinch phase
switch(mobj->movedir)
{
// WELCOME to your DOOM!
case 0:
{
// For this stage only:
// movecount == cage height
// threshold == cage momz
if (mobj->movecount == 0) // Initialize stage!
{
fixed_t z;
INT32 i, arm;
mobj_t *seg, *base = mobj;
// First frame init, spawn all the things.
mobj->watertop = mobj->z;
z = mobj->z + mobj->height/2 - mobjinfo[MT_EGGMOBILE4_MACE].height/2;
for (arm = 0; arm <3 ; arm++)
{
seg = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE);
if (P_MobjWasRemoved(seg))
continue;
P_SetTarget(&base->tracer, seg);
base = seg;
P_SetTarget(&seg->target, mobj);
for (i = 0; i < 9; i++)
{
P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE));
if (P_MobjWasRemoved(seg->hnext))
continue;
P_SetTarget(&seg->hnext->hprev, seg);
seg = seg->hnext;
}
}
// Move the cage up to the sky.
mobj->movecount = 800*FRACUNIT;
if (!P_Boss4MoveCage(mobj, mobj->movecount))
{
mobj->movecount = 0;
//mobj->threshold = 3*TICRATE;
mobj->extravalue1 = 1;
mobj->movedir++; // We don't have a cage, just continue.
}
else
P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
}
else // Cage slams down over Eggman's head!
{
fixed_t oldz = mobj->movecount;
mobj->threshold -= 5*FRACUNIT;
mobj->movecount += mobj->threshold;
if (mobj->movecount <= 0)
{
mobj->flags2 &= ~MF2_INVERTAIMABLE;
mobj->movecount = 0;
mobj->movedir++; // Initialization complete, next phase!
}
P_Boss4MoveCage(mobj, mobj->movecount - oldz);
P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
}
return;
}
// Normal operation
case 1:
case 2:
break;
// Pinch phase init!
case 3:
{
fixed_t z;
if (mobj->z < mobj->watertop+(400<<FRACBITS))
mobj->momz = 8*FRACUNIT;
else
{
mobj->momz = mobj->movefactor = 0;
mobj->threshold = 1110<<FRACBITS;
S_StartSound(NULL, sfx_s3k60);
mobj->movedir++;
}
z = mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2;
if (z < (8<<FRACBITS)) // We haven't risen high enough to pull the spikeballs along yet
P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0); // So don't pull the spikeballs along yet.
else
P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), z);
return;
}
// Pinch phase!
case 4:
case 5:
{
mobj->angle -= FixedAngle(movespeed/8);
if (mobj->movefactor != mobj->threshold)
{
if (mobj->threshold - mobj->movefactor < FRACUNIT)
{
mobj->movefactor = mobj->threshold;
mobj->flags2 &= ~MF2_FRET;
}
else
mobj->movefactor += (mobj->threshold - mobj->movefactor)/8;
}
if (mobj->spawnpoint)
P_TryMove(mobj,
(mobj->spawnpoint->x<<FRACBITS) - P_ReturnThrustX(mobj, mobj->angle, mobj->movefactor),
(mobj->spawnpoint->y<<FRACBITS) - P_ReturnThrustY(mobj, mobj->angle, mobj->movefactor),
true);
if (P_MobjWasRemoved(mobj))
return;
P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2);
if (!mobj->target || !mobj->target->health)
P_SupermanLook4Players(mobj);
//A_FaceTarget(mobj);
return;
}
default: // ?????
return;
}
// Haahahahahaaa, and let the FUN.. BEGIN!
// movedir == arms direction
// movecount == arms angle
// threshold == countdown to next attack
// reactiontime == cage raise, speed burst
// movefactor == cage z
// friction == turns until helm lift
// Raise the cage!
if (mobj->reactiontime == 1)
{
fixed_t oldz = mobj->movefactor;
if (mobj->movefactor != 128*FRACUNIT)
{
if (mobj->movefactor < 128*FRACUNIT)
{
mobj->movefactor += 8*FRACUNIT;
if (!oldz)
{
// 5 -> 2.5 second timer
mobj->threshold = 5*TICRATE-(TICRATE*(mobj->info->spawnhealth-mobj->health)/2);
if (mobj->threshold < 1)
mobj->threshold = 1;
}
}
else
mobj->movefactor = 128*FRACUNIT;
P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
}
}
// Drop the cage!
else if (mobj->movefactor)
{
fixed_t oldz = mobj->movefactor;
mobj->movefactor -= 4*FRACUNIT;
if (mobj->movefactor < 0)
mobj->movefactor = 0;
P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
if (!mobj->movefactor)
{
if (mobj->health <= mobj->info->damage)
{ // Proceed to pinch phase!
P_Boss4DestroyCage(mobj);
mobj->movedir = 3;
if (mobj->spawnpoint)
P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0);
var1 = 3;
A_BossJetFume(mobj);
return;
}
if (mobj->spawnpoint)
P_LinedefExecute(mobj->spawnpoint->args[5] - (mobj->info->spawnhealth-mobj->health), mobj, NULL);
// 1 -> 1.5 second timer
mobj->threshold = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
if (mobj->threshold < 1)
mobj->threshold = 1;
}
}
P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->movefactor);
// Check for attacks, always tick the timer even while animating!!
if (mobj->threshold)
{
if (!(mobj->flags2 & MF2_FRET) && !(--mobj->threshold)) // but pause for pain so we don't interrupt pinch phase, eep!
{
if (mobj->reactiontime == 1) // Cage is raised?
{
P_SetMobjState(mobj, mobj->info->spawnstate);
mobj->reactiontime = 0; // Drop it!
}
}
}
// Leave if animating.
if ((statenum_t)(mobj->state-states) != mobj->info->spawnstate)
return;
// Map allows us to get killed despite cage being down?
if (mobj->health <= mobj->info->damage)
{ // Proceed to pinch phase!
P_Boss4DestroyCage(mobj);
mobj->movedir = 3;
if (mobj->spawnpoint)
P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
var1 = 3;
A_BossJetFume(mobj);
return;
}
mobj->reactiontime = 0; // Drop the cage if it hasn't been dropped already.
if (!mobj->target || !mobj->target->health)
P_SupermanLook4Players(mobj);
A_FaceTarget(mobj);
}
//
// AI for the fifth boss.
//
static void P_Boss5Thinker(mobj_t *mobj)
{
if (!mobj->health)
{
if (mobj->fuse)
{
if (mobj->flags2 & MF2_SLIDEPUSH)
{
INT32 trans = 10-((10*mobj->fuse)/70);
if (trans > 9)
trans = 9;
if (trans < 0)
trans = 0;
mobj->frame = (mobj->frame & ~FF_TRANSMASK)|(trans<<FF_TRANSSHIFT);
if (!(mobj->fuse & 1))
{
mobj->colorized = !mobj->colorized;
mobj->frame ^= FF_FULLBRIGHT;
}
}
return;
}
if (mobj->state == &states[mobj->info->xdeathstate])
mobj->momz -= (2*FRACUNIT)/3;
else if (mobj->tracer && P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y) < 2*mobj->radius)
mobj->flags &= ~MF_NOCLIP;
}
else
{
if (mobj->flags2 & MF2_FRET && (leveltime & 1)
&& mobj->state != &states[S_FANG_PAIN1] && mobj->state != &states[S_FANG_PAIN2])
mobj->flags2 |= MF2_DONTDRAW;
else
mobj->flags2 &= ~MF2_DONTDRAW;
}
if (mobj->state == &states[S_FANG_BOUNCE3]
|| mobj->state == &states[S_FANG_BOUNCE4]
|| mobj->state == &states[S_FANG_PINCHBOUNCE3]
|| mobj->state == &states[S_FANG_PINCHBOUNCE4])
{
if (P_MobjFlip(mobj)*mobj->momz > 0
&& abs(mobj->momx) < FRACUNIT/2 && abs(mobj->momy) < FRACUNIT/2
&& !P_IsObjectOnGround(mobj))
{
mobj_t *prevtarget = mobj->target;
P_SetTarget(&mobj->target, NULL);
var1 = var2 = 0;
A_DoNPCPain(mobj);
P_SetTarget(&mobj->target, prevtarget);
P_SetMobjState(mobj, S_FANG_WALLHIT);
mobj->extravalue2++;
}
}
}
//
// AI for Black Eggman
// Note: You CANNOT have more than ONE Black Eggman
// in a level! Just don't try it!
//
static void P_Boss7Thinker(mobj_t *mobj)
{
if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))
{
// look for a new target
if (P_BossTargetPlayer(mobj, false))
return; // got a new target
P_SetMobjStateNF(mobj, mobj->info->spawnstate);
return;
}
if (mobj->health >= mobj->info->spawnhealth && (leveltime & 14) == 0)
{
mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height, MT_SMOKE);
if (!P_MobjWasRemoved(smoke))
{
P_SetScale(smoke, mobj->destscale, true);
smoke->momz = FixedMul(FRACUNIT, smoke->scale);
}
}
if (mobj->state == &states[S_BLACKEGG_STND] && mobj->tics == mobj->state->tics)
{
mobj->reactiontime += P_RandomByte();
if (mobj->health <= mobj->info->damage)
mobj->reactiontime /= 4;
}
else if (mobj->state == &states[S_BLACKEGG_DIE4] && mobj->tics == mobj->state->tics)
A_BossDeath(mobj);
else if (mobj->state >= &states[S_BLACKEGG_WALK1]
&& mobj->state <= &states[S_BLACKEGG_WALK6])
A_Boss7Chase(mobj);
else if (mobj->state == &states[S_BLACKEGG_PAIN1] && mobj->tics == mobj->state->tics)
{
if (mobj->health > 0)
mobj->health--;
S_StartSound(0, (mobj->health) ? sfx_behurt : sfx_bedie2);
mobj->reactiontime /= 3;
if (mobj->health <= 0)
{
INT32 i;
P_KillMobj(mobj, NULL, NULL, 0);
// It was a team effort
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
P_AddPlayerScore(&players[i], 1000);
}
}
}
else if (mobj->state == &states[S_BLACKEGG_PAIN35] && mobj->tics == 1)
{
if (mobj->health == mobj->info->damage)
{
// Begin platform destruction
mobj->flags2 |= MF2_FRET;
P_SetMobjState(mobj, mobj->info->raisestate);
if (mobj->spawnpoint)
P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
}
}
else if (mobj->state == &states[S_BLACKEGG_HITFACE4] && mobj->tics == mobj->state->tics)
{
// This is where Black Eggman hits his face.
// If a player is on top of him, the player gets hurt.
// But, if the player has managed to escape,
// Black Eggman gets hurt!
INT32 i;
mobj->state->nextstate = mobj->info->painstate; // Reset
S_StartSound(0, sfx_bedeen);
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (!players[i].mo)
continue;
if (players[i].mo->health <= 0)
continue;
if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > (mobj->radius + players[i].mo->radius))
continue;
if (players[i].mo->z > mobj->z + mobj->height - FRACUNIT
&& players[i].mo->z < mobj->z + mobj->height + 128*FRACUNIT) // You can't be in the vicinity, either...
{
// Punch him!
P_DamageMobj(players[i].mo, mobj, mobj, 1, 0);
mobj->state->nextstate = mobj->info->spawnstate;
// Laugh
S_StartSound(0, sfx_bewar1 + P_RandomKey(4));
}
}
}
else if (mobj->state == &states[S_BLACKEGG_GOOP])
{
// Lob cannon balls
if (mobj->movecount-- <= 0 || !mobj->target)
{
P_SetMobjState(mobj, mobj->info->spawnstate);
return;
}
if ((leveltime & 15) == 0)
{
var1 = MT_CANNONBALL;
var2 = 2*TICRATE + (80<<16);
A_LobShot(mobj);
S_StartSound(0, sfx_begoop);
}
}
else if (mobj->state == &states[S_BLACKEGG_SHOOT2])
{
// Chaingun goop
mobj_t *missile;
if (mobj->movecount-- <= 0 || !mobj->target)
{
P_SetMobjState(mobj, mobj->info->spawnstate);
return;
}
A_FaceTarget(mobj);
missile = P_SpawnXYZMissile(mobj, mobj->target, MT_BLACKEGGMAN_GOOPFIRE,
mobj->x + P_ReturnThrustX(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)),
mobj->y + P_ReturnThrustY(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)),
mobj->z + FixedDiv(mobj->height, 3*FRACUNIT/2));
S_StopSound(missile);
if (leveltime & 1)
S_StartSound(0, sfx_beshot);
}
else if (mobj->state == &states[S_BLACKEGG_JUMP1] && mobj->tics == 1)
{
mobj_t *hitspot = NULL, *mo2;
angle_t an;
fixed_t dist, closestdist;
fixed_t vertical, horizontal;
fixed_t airtime = 5*TICRATE;
INT32 waypointNum = 0;
INT32 i, j;
boolean foundgoop = false;
INT32 closestNum;
UINT8 bossid = (mobj->spawnpoint ? mobj->spawnpoint->args[0] : 0);
// Looks for players in goop. If you find one, try to jump on him.
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (!players[i].mo)
continue;
if (players[i].mo->health <= 0)
continue;
if (players[i].powers[pw_carry] == CR_BRAKGOOP)
{
closestNum = -1;
closestdist = INT32_MAX; // Just in case...
// Find waypoint he is closest to
TAG_ITER_THINGS(bossid, j)
{
mo2 = mapthings[j].mobj;
if (!mo2)
continue;
if (mo2->type != MT_BOSS3WAYPOINT)
continue;
if (mobj->health <= mobj->info->damage && !mapthings[j].args[1])
continue; // don't jump to center
dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y);
if (!(closestNum == -1 || dist < closestdist))
continue;
closestNum = mapthings[j].args[1];
closestdist = dist;
foundgoop = true;
}
waypointNum = closestNum;
break;
}
}
if (!foundgoop)
{
// Don't jump to the center when health is low.
// Force the player to beat you with missiles.
if (mobj->z <= 1056*FRACUNIT || mobj->health <= mobj->info->damage)
waypointNum = 1 + P_RandomKey(4);
else
waypointNum = 0;
}
if (mobj->tracer && mobj->tracer->type == MT_BOSS3WAYPOINT
&& mobj->tracer->spawnpoint && mobj->tracer->spawnpoint->args[1] == waypointNum)
{
if (P_RandomChance(FRACUNIT/2))
waypointNum++;
else
waypointNum--;
if (mobj->health <= mobj->info->damage)
waypointNum = ((waypointNum + 3) % 4) + 1; // plus four to avoid modulo being negative, minus one to avoid waypoint #0
else
waypointNum = ((waypointNum + 5) % 5);
}
// Scan mapthings to find the waypoint to use
TAG_ITER_THINGS(bossid, i)
{
mo2 = mapthings[i].mobj;
if (!mo2)
continue;
if (mo2->type != MT_BOSS3WAYPOINT)
continue;
if (mapthings[i].args[1] != waypointNum)
continue;
hitspot = mo2;
break;
}
if (!hitspot)
{
CONS_Debug(DBG_GAMELOGIC, "BlackEggman unable to find waypoint #%d!\n", waypointNum);
P_SetMobjState(mobj, mobj->info->spawnstate);
return;
}
P_SetTarget(&mobj->tracer, hitspot);
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, hitspot->x, hitspot->y);
an = mobj->angle;
an >>= ANGLETOFINESHIFT;
dist = P_AproxDistance(hitspot->x - mobj->x, hitspot->y - mobj->y);
horizontal = dist / airtime;
vertical = (gravity*airtime)/2;
mobj->momx = FixedMul(horizontal, FINECOSINE(an));
mobj->momy = FixedMul(horizontal, FINESINE(an));
mobj->momz = vertical;
// mobj->momz = 10*FRACUNIT;
}
else if (mobj->state == &states[S_BLACKEGG_JUMP2] && mobj->z <= mobj->floorz)
{
// BANG! onto the ground
INT32 i,j;
fixed_t ns;
fixed_t x,y,z;
mobj_t *mo2;
S_StartSound(0, sfx_befall);
z = mobj->floorz;
for (j = 0; j < 2; j++)
{
for (i = 0; i < 32; i++)
{
const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
ns = 64 * FRACUNIT;
x = mobj->x + FixedMul(FINESINE(fa),ns);
y = mobj->y + FixedMul(FINECOSINE(fa),ns);
mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
if (P_MobjWasRemoved(mo2))
continue;
ns = 16 * FRACUNIT;
mo2->momx = FixedMul(FINESINE(fa),ns);
mo2->momy = FixedMul(FINECOSINE(fa),ns);
}
z -= 32*FRACUNIT;
}
// Hurt player??
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (!players[i].mo)
continue;
if (players[i].mo->health <= 0)
continue;
if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > mobj->radius*4)
continue;
if (players[i].mo->z > mobj->z + 128*FRACUNIT)
continue;
if (players[i].mo->z < mobj->z - 64*FRACUNIT)
continue;
P_DamageMobj(players[i].mo, mobj, mobj, 1, 0);
// Laugh
S_StartSound(0, sfx_bewar1 + P_RandomKey(4));
}
P_SetMobjState(mobj, mobj->info->spawnstate);
}
else if (mobj->state == &states[mobj->info->deathstate] && mobj->tics == mobj->state->tics)
S_StartSound(0, sfx_bedie1 + (P_RandomFixed() & 1));
}
#define vectorise mobj->movedir = ANGLE_11hh - FixedAngle(FixedMul(AngleFixed(ANGLE_11hh), FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS)));\
if (P_RandomChance(FRACUNIT/2))\
mobj->movedir = InvAngle(mobj->movedir);\
mobj->threshold = 6 + (FixedMul(24<<FRACBITS, FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS))>>FRACBITS);\
if (mobj->info->activesound)\
S_StartSound(mobj, mobj->info->activesound);\
if (mobj->info->painchance)\
P_SetMobjState(mobj, mobj->info->painchance);\
mobj->flags2 &= ~MF2_INVERTAIMABLE;\
// Metal Sonic battle boss
// You CAN put multiple Metal Sonics in a single map
// because I am a totally competent programmer who can do shit right.
static void P_Boss9Thinker(mobj_t *mobj)
{
if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate)
mobj->flags2 &= ~MF2_FRET;
if (!mobj->tracer)
{
thinker_t *th;
mobj_t *mo2;
mobj_t *last=NULL;
// Initialize the boss, spawn jet fumes, etc.
mobj->threshold = 0;
mobj->reactiontime = 0;
mobj->watertop = mobj->floorz + 32*FRACUNIT;
var1 = 2;
A_BossJetFume(mobj);
// Run through the thinkers ONCE and find all of the MT_BOSS9GATHERPOINT in the map.
// Build a hoop linked list of 'em!
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo2 = (mobj_t *)th;
if (mo2->type == MT_BOSS9GATHERPOINT)
{
if (last)
P_SetTarget(&last->hnext, mo2);
else
P_SetTarget(&mobj->hnext, mo2);
P_SetTarget(&mo2->hprev, last);
last = mo2;
}
}
}
if (mobj->health <= 0)
return;
if ((statenum_t)(mobj->state-states) == mobj->info->meleestate)
{
P_InstaThrust(mobj, mobj->angle, -4*FRACUNIT);
P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true);
if (P_MobjWasRemoved(mobj))
return;
mobj->momz -= gravity;
if (mobj->z < mobj->watertop || mobj->z < (mobj->floorz + 16*FRACUNIT))
{
mobj->watertop = mobj->floorz + 32*FRACUNIT;
P_SetMobjState(mobj, mobj->info->spawnstate);
}
return;
}
if ((!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)))
{
if (mobj->hprev)
{
P_RemoveMobj(mobj->hprev);
P_SetTarget(&mobj->hprev, NULL);
}
P_BossTargetPlayer(mobj, false);
if (mobj->target && (!P_IsObjectOnGround(mobj->target) || mobj->target->player->pflags & PF_SPINNING))
P_SetTarget(&mobj->target, NULL); // Wait for them to hit the ground first
if (!mobj->target) // Still no target, aww.
{
// Reset the boss.
if (mobj->hprev)
{
P_RemoveMobj(mobj->hprev);
P_SetTarget(&mobj->hprev, NULL);
}
P_SetMobjState(mobj, mobj->info->spawnstate);
mobj->fuse = 0;
mobj->momx = FixedDiv(mobj->momx, FRACUNIT + (FRACUNIT>>2));
mobj->momy = FixedDiv(mobj->momy, FRACUNIT + (FRACUNIT>>2));
mobj->momz = FixedDiv(mobj->momz, FRACUNIT + (FRACUNIT>>2));
mobj->watertop = mobj->floorz + 32*FRACUNIT;
mobj->momz = (mobj->watertop - mobj->z)>>3;
mobj->threshold = 0;
mobj->movecount = 0;
mobj->flags = mobj->info->flags;
return;
}
else if (!mobj->fuse)
mobj->fuse = 8*TICRATE;
}
// AI goes here.
{
angle_t angle;
if (mobj->threshold || mobj->movecount)
mobj->momz = (mobj->watertop-mobj->z)/16; // Float to your desired position FASTER
else
mobj->momz = (mobj->watertop-mobj->z)/40; // Float to your desired position
if (mobj->movecount == 2)
{
mobj_t *spawner;
fixed_t dist = 0;
angle = 0x06000000*leveltime; // wtf?
// Face your target
P_BossTargetPlayer(mobj, true);
angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle
angle = (angle-mobj->angle); // relative angle
if (angle < ANGLE_180)
mobj->angle += angle/8;
else
mobj->angle -= InvAngle(angle)/8;
// Alter your energy bubble's size/position
if (mobj->health > mobj->info->damage)
{
if (mobj->hprev)
{
mobj->hprev->destscale = FRACUNIT + (2*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2);
P_SetScale(mobj->hprev, mobj->hprev->destscale, false);
P_MoveOrigin(mobj->hprev, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->hprev->height/2);
mobj->hprev->momx = mobj->momx;
mobj->hprev->momy = mobj->momy;
mobj->hprev->momz = mobj->momz;
}
// Firin' mah lazors - INDICATOR
if (mobj->fuse > TICRATE/2)
{
tic_t shoottime, worktime, calctime;
shoottime = (TICRATE/((mobj->extravalue1 == 3) ? 8 : 4));
shoottime += (shoottime>>1);
worktime = shoottime*(mobj->threshold/2);
calctime = mobj->fuse-(TICRATE/2);
if (calctime <= worktime && (calctime % shoottime == 0))
{
mobj_t *missile;
missile = P_SpawnMissile(mobj, mobj->target, MT_MSGATHER);
if (!P_MobjWasRemoved(missile))
{
S_StopSound(missile);
if (mobj->extravalue1 >= 2)
P_SetScale(missile, FRACUNIT/2, true);
missile->destscale = missile->scale/2;
missile->fuse = TICRATE/2;
missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse;
missile->z -= missile->height/2;
missile->momx *= -1;
missile->momy *= -1;
missile->momz *= -1;
if (mobj->extravalue1 == 2)
{
UINT8 i;
mobj_t *spread;
for (i = 0; i < 5; i++)
{
if (i == 2)
continue;
spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
if (P_MobjWasRemoved(spread))
continue;
spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
P_InstaThrust(spread,spread->angle,-spread->info->speed);
spread->momz = missile->momz;
P_SetScale(spread, missile->scale, true);
spread->destscale = missile->destscale;
spread->scalespeed = missile->scalespeed;
spread->fuse = missile->fuse;
P_UnsetThingPosition(spread);
spread->x -= spread->fuse*spread->momx;
spread->y -= spread->fuse*spread->momy;
spread->z -= spread->fuse*spread->momz;
P_SetThingPosition(spread);
}
P_InstaThrust(missile,missile->angle,-missile->info->speed);
}
else if (mobj->extravalue1 >= 3)
{
UINT8 i;
mobj_t *spread;
mobj->target->z -= (4*missile->height);
for (i = 0; i < 5; i++)
{
if (i != 2)
{
spread = P_SpawnMissile(mobj, mobj->target, missile->type);
if (P_MobjWasRemoved(spread))
continue;
P_SetScale(spread, missile->scale, true);
spread->destscale = missile->destscale;
spread->fuse = missile->fuse;
spread->z -= spread->height/2;
spread->momx *= -1;
spread->momy *= -1;
spread->momz *= -1;
P_UnsetThingPosition(spread);
spread->x -= spread->fuse*spread->momx;
spread->y -= spread->fuse*spread->momy;
spread->z -= spread->fuse*spread->momz;
P_SetThingPosition(spread);
}
mobj->target->z += missile->height*2;
}
mobj->target->z -= (6*missile->height);
}
P_UnsetThingPosition(missile);
missile->x -= missile->fuse*missile->momx;
missile->y -= missile->fuse*missile->momy;
missile->z -= missile->fuse*missile->momz;
P_SetThingPosition(missile);
S_StartSound(mobj, sfx_s3kb3);
}
}
}
}
// up...
mobj->z += mobj->height/2;
// Spawn energy particles
for (spawner = mobj->hnext; spawner; spawner = spawner->hnext)
{
dist = P_AproxDistance(spawner->x - mobj->x, spawner->y - mobj->y);
if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1)
break;
}
if (spawner && dist)
{
mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER);
if (!P_MobjWasRemoved(missile))
{
missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
if (missile->fuse <= 0) // Prevents a division by zero when calculating missile->scalespeed
missile->fuse = 1;
if (missile->fuse > mobj->fuse)
{
P_RemoveMobj(missile);
}
else
{
if (mobj->health > mobj->info->damage)
{
P_SetScale(missile, FRACUNIT/3, true);
missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
}
else
{
P_SetScale(missile, FRACUNIT/5, true);
missile->color = SKINCOLOR_SUNSET; // sonic cd electric power
}
missile->destscale = missile->scale*2;
missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse;
missile->colorized = true;
}
}
}
// ...then down. easier than changing the missile's momz after-the-fact
mobj->z -= mobj->height/2;
}
// Pre-threshold reactiontime stuff for attack phases
if (mobj->reactiontime && mobj->movecount == 3)
{
mobj->reactiontime--;
if (mobj->movedir == 0 || mobj->movedir == 2) { // Pausing between bounces in the pinball phase
if (mobj->target->player->powers[pw_tailsfly]) // Trying to escape, eh?
mobj->watertop = mobj->target->z + mobj->target->momz*6; // Readjust your aim. >:3
else if (mobj->target->floorz > mobj->floorz)
mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
else
mobj->watertop = mobj->floorz + 16*FRACUNIT;
if (!(mobj->threshold%4)) {
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
if (!mobj->reactiontime)
S_StartSound(mobj, sfx_zoom); // zoom!
}
}
// else -- Pausing between energy ball shots
return;
}
// threshold is used for attacks/maneuvers.
if (mobj->threshold && mobj->movecount != 2) {
mobj_t *ghost;
fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, mobj->info->spawnhealth<<FRACBITS));
UINT8 tries = 0;
// Firin' mah lazors
if (mobj->movecount == 3 && mobj->movedir == 1)
{
if (!(mobj->threshold & 1))
{
mobj_t *missile;
if (mobj->info->seesound)
S_StartSound(mobj, mobj->info->seesound);
P_SetMobjState(mobj, mobj->info->missilestate);
if (mobj->extravalue1 == 3)
mobj->reactiontime = TICRATE/16;
else
mobj->reactiontime = TICRATE/8;
A_FaceTarget(mobj);
missile = P_SpawnMissile(mobj, mobj->target, mobj->info->speed);
if (!P_MobjWasRemoved(missile))
{
if (mobj->extravalue1 >= 2)
P_SetScale(missile, FRACUNIT/2, true);
missile->fuse = 3*TICRATE;
missile->z -= missile->height/2;
if (mobj->extravalue1 == 2)
{
UINT8 i;
mobj_t *spread;
for (i = 0; i < 5; i++)
{
if (i == 2)
continue;
spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
if (P_MobjWasRemoved(spread))
continue;
spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
P_InstaThrust(spread,spread->angle,spread->info->speed);
spread->momz = missile->momz;
P_SetScale(spread, FRACUNIT/2, true);
spread->fuse = missile->fuse;
}
P_InstaThrust(missile,missile->angle,missile->info->speed);
}
else if (mobj->extravalue1 >= 3)
{
UINT8 i;
mobj_t *spread;
mobj->target->z -= (2*missile->height);
for (i = 0; i < 5; i++)
{
if (i != 2)
{
spread = P_SpawnMissile(mobj, mobj->target, missile->type);
if (!P_MobjWasRemoved(spread))
{
P_SetScale(spread, FRACUNIT/2, true);
spread->fuse = missile->fuse;
spread->z -= spread->height/2;
}
}
mobj->target->z += missile->height;
}
mobj->target->z -= (3*missile->height);
}
}
}
else
{
P_SetMobjState(mobj, mobj->state->nextstate);
if (mobj->extravalue1 == 3)
mobj->reactiontime = TICRATE/8;
else
mobj->reactiontime = TICRATE/4;
}
mobj->threshold--;
return;
}
// Pinball attack!
if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2))
{
if ((statenum_t)(mobj->state-states) != mobj->info->seestate)
P_SetMobjState(mobj, mobj->info->seestate);
if (mobj->movedir == 0) // mobj health == 1
P_InstaThrust(mobj, mobj->angle, 38*FRACUNIT);
else if (mobj->health == 3)
P_InstaThrust(mobj, mobj->angle, 22*FRACUNIT);
else // mobj health == 2
P_InstaThrust(mobj, mobj->angle, 30*FRACUNIT);
if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true))
{ // Hit a wall? Find a direction to bounce
if (P_MobjWasRemoved(mobj))
return;
mobj->threshold--;
if (!mobj->threshold) { // failed bounce!
S_StartSound(mobj, sfx_mspogo);
P_BounceMove(mobj);
mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
mobj->momz = 4*FRACUNIT;
mobj->flags &= ~MF_PAIN;
mobj->fuse = 8*TICRATE;
mobj->movecount = 0;
P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION);
P_SetMobjState(mobj, mobj->info->meleestate);
}
else if (!(mobj->threshold%4))
{ // We've decided to lock onto the player this bounce.
P_SetMobjState(mobj, mobj->state->nextstate);
S_StartSound(mobj, sfx_s3k5a);
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
mobj->reactiontime = TICRATE - 5*(mobj->info->damage - mobj->health); // targetting time
}
else
{ // No homing, just use P_BounceMove
S_StartSound(mobj, sfx_s3kaa); // make the bounces distinct...
P_BounceMove(mobj);
mobj->angle = R_PointToAngle2(0,0,mobj->momx,mobj->momy);
mobj->reactiontime = 1; // TICRATE/4; // just a pause before you bounce away
}
mobj->momx = mobj->momy = 0;
}
return;
}
ghost = P_SpawnGhostMobj(mobj);
if (!P_MobjWasRemoved(ghost))
ghost->colorized = false;
// Vector form dodge!
mobj->angle += mobj->movedir;
P_InstaThrust(mobj, mobj->angle, -speed);
while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true) && tries++ < 16)
{
if (P_MobjWasRemoved(mobj))
return;
S_StartSound(mobj, sfx_mspogo);
P_BounceMove(mobj);
mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
}
mobj->momx = mobj->momy = 0;
mobj->threshold--;
if (!mobj->threshold)
{ // Go into stun after dodge.
// from 3*TICRATE down to 1.25*TICRATE
//mobj->reactiontime = 5*TICRATE/4 + (FixedMul((7*TICRATE/4)<<FRACBITS, FixedDiv((mobj->health-1)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS))>>FRACBITS);
// from 3*TICRATE down to 2*TICRATE
mobj->reactiontime = 2*TICRATE + (FixedMul((1*TICRATE)<<FRACBITS, FixedDiv((mobj->health-1)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS))>>FRACBITS);
mobj->flags |= MF_SPECIAL|MF_SHOOTABLE;
P_SetMobjState(mobj, mobj->state->nextstate);
}
return;
}
angle = 0x06000000*leveltime;
mobj->momz += FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),2*FRACUNIT); // Use that "angle" to bob gently in the air
// This is below threshold because we don't want to bob while zipping around
// Ohh you're in for it now..
if (mobj->flags2 & MF2_FRET && mobj->health <= mobj->info->damage)
mobj->fuse = 0;
// reactiontime is used for delays.
if (mobj->reactiontime)
{
// Stunned after vector form
if (mobj->movedir > ANGLE_180)
mobj->angle -= FixedAngle(FixedMul(AngleFixed(InvAngle(mobj->movedir)),FixedDiv(mobj->reactiontime<<FRACBITS,24<<FRACBITS)));
else
mobj->angle += FixedAngle(FixedMul(AngleFixed(mobj->movedir),FixedDiv(mobj->reactiontime<<FRACBITS,24<<FRACBITS)));
mobj->reactiontime--;
if (!mobj->reactiontime)
// Out of stun.
P_SetMobjState(mobj, mobj->state->nextstate);
return;
}
// Not stunned? Can hit.
// Here because stun won't always get the chance to complete due to pinch phase activating, being hit, etc.
mobj->flags &= ~(MF_SPECIAL|MF_SHOOTABLE);
if (mobj->health <= mobj->info->damage && mobj->fuse && !(mobj->fuse%TICRATE))
{
var1 = 1;
var2 = 0;
A_BossScream(mobj);
}
// Don't move if we're still in pain!
if (mobj->flags2 & MF2_FRET)
return;
if (mobj->movecount == 1 || mobj->movecount == 2)
{ // Charging energy
if (mobj->momx != 0 || mobj->momy != 0) { // Apply the air breaks
if (abs(mobj->momx)+abs(mobj->momy) < FRACUNIT)
mobj->momx = mobj->momy = 0;
else
P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -6*FRACUNIT/8);
}
if (mobj->state == states+mobj->info->raisestate)
return;
}
if (mobj->fuse == 0)
{
mobj->flags2 &= ~MF2_INVERTAIMABLE;
// It's time to attack! What are we gonna do?!
switch(mobj->movecount)
{
case 0:
default:
// Fly up and prepare for an attack!
// We have to charge up first, so let's go up into the air
S_StartSound(mobj, sfx_beflap);
P_SetMobjState(mobj, mobj->info->raisestate);
if (mobj->floorz >= mobj->target->floorz)
mobj->watertop = mobj->floorz + 256*FRACUNIT;
else
mobj->watertop = mobj->target->floorz + 256*FRACUNIT;
break;
case 1:
// Okay, we're up? Good, time to gather energy...
if (mobj->health > mobj->info->damage)
{ // No more bubble if we're broken (pinch phase)
mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
if (!P_MobjWasRemoved(shield))
{
P_SetTarget(&mobj->hprev, shield);
P_SetTarget(&shield->target, mobj);
}
// Attack 2: Energy shot!
switch (mobj->health)
{
case 8: // shoot once
default:
mobj->extravalue1 = 0;
mobj->threshold = 2;
break;
case 7: // spread shot (vertical)
mobj->extravalue1 = 4;
mobj->threshold = 2;
break;
case 6: // three shots
mobj->extravalue1 = 1;
mobj->threshold = 3*2;
break;
case 5: // spread shot (horizontal)
mobj->extravalue1 = 2;
mobj->threshold = 2;
break;
case 4: // machine gun
mobj->extravalue1 = 3;
mobj->threshold = 5*2;
break;
}
}
else
{
/*mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
P_SetTarget(&mobj->tracer, shield);
P_SetTarget(&shield->target, mobj);
shield->height -= 20*FRACUNIT; // different offset...
P_SetMobjState(shield, S_MSSHIELD_F2);*/
P_SetMobjState(mobj, S_METALSONIC_BOUNCE);
//P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); -- why does this happen twice? see case 2...
}
mobj->fuse = 3*TICRATE;
mobj->flags |= MF_PAIN;
if (mobj->info->attacksound)
S_StartSound(mobj, mobj->info->attacksound);
A_FaceTarget(mobj);
break;
case 2:
{
// We're all charged and ready now! Unleash the fury!!
S_StopSound(mobj);
if (mobj->hprev)
{
P_RemoveMobj(mobj->hprev);
P_SetTarget(&mobj->hprev, NULL);
}
if (mobj->health <= mobj->info->damage)
{
// Attack 1: Pinball dash!
if (mobj->health == 1)
mobj->movedir = 0;
else
mobj->movedir = 2;
if (mobj->info->seesound)
S_StartSound(mobj, mobj->info->seesound);
P_SetMobjState(mobj, mobj->info->seestate);
if (mobj->movedir == 2)
mobj->threshold = 12; // bounce 12 times
else
mobj->threshold = 24; // bounce 24 times
if (mobj->floorz >= mobj->target->floorz)
mobj->watertop = mobj->floorz + 16*FRACUNIT;
else
mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
if (mobj->spawnpoint)
P_LinedefExecute(mobj->spawnpoint->args[4], mobj, NULL);
#if 0
whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
whoosh->frame = FF_FULLBRIGHT;
whoosh->sprite = SPR_ARMA;
whoosh->destscale = whoosh->scale<<1;
whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
whoosh->height = 38*whoosh->scale;
whoosh->fuse = 10;
whoosh->color = SKINCOLOR_SUNSET;
whoosh->colorized = true;
whoosh->flags |= MF_NOCLIPHEIGHT;
#endif
if (!P_MobjWasRemoved(mobj->tracer))
{
P_SetMobjState(mobj->tracer, S_JETFUMEFLASH);
P_SetScale(mobj->tracer, 2*mobj->scale, false);
mobj->tracer->old_scale = mobj->tracer->scale;
}
}
else
{
// Attack 2: Energy shot!
mobj->movedir = 1;
// looking for the number of things to fire? that's done in case 1 now
}
break;
}
case 3:
// Return to idle.
if (mobj->floorz >= mobj->target->floorz)
mobj->watertop = mobj->floorz + 32*FRACUNIT;
else
mobj->watertop = mobj->target->floorz + 32*FRACUNIT;
P_SetMobjState(mobj, mobj->info->spawnstate);
mobj->flags &= ~MF_PAIN;
mobj->fuse = 8*TICRATE;
break;
}
mobj->movecount++;
mobj->movecount %= 4;
return;
}
// Idle AI
if (mobj->state == &states[mobj->info->spawnstate])
{
fixed_t dist;
// Target the closest player
P_BossTargetPlayer(mobj, true);
// Face your target
angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle
angle = (angle-mobj->angle); // relative angle
if (angle < ANGLE_180)
mobj->angle += angle/8;
else
mobj->angle -= InvAngle(angle)/8;
//A_FaceTarget(mobj);
if (mobj->flags2 & MF2_CLASSICPUSH)
mobj->flags2 &= ~MF2_CLASSICPUSH; // a missile caught us in PIT_CheckThing!
else
{
// Check if we're being attacked
if (!mobj->target || !mobj->target->player || !P_PlayerCanDamage(mobj->target->player, mobj))
goto nodanger;
if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius)
goto nodanger;
if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius)
goto nodanger;
if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius)
goto nodanger;
if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius)
goto nodanger;
if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z)
goto nodanger;
if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height)
goto nodanger;
}
// An incoming attack is detected! What should we do?!
// Go into vector form!
vectorise;
return;
nodanger:
mobj->flags2 |= MF2_INVERTAIMABLE;
// Move normally: Approach the player using normal thrust and simulated friction.
dist = P_AproxDistance(mobj->x-mobj->target->x, mobj->y-mobj->target->y);
P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -3*FRACUNIT/8);
if (dist < 64*FRACUNIT && !(mobj->target->player && mobj->target->player->homing))
P_Thrust(mobj, mobj->angle, -4*FRACUNIT);
else if (dist > 180*FRACUNIT)
P_Thrust(mobj, mobj->angle, FRACUNIT);
else
P_Thrust(mobj, mobj->angle + ANGLE_90, FINECOSINE((((angle_t)(leveltime*ANG1))>>ANGLETOFINESHIFT) & FINEMASK)>>1);
mobj->momz += P_AproxDistance(mobj->momx, mobj->momy)/12; // Move up higher the faster you're going.
}
}
}
#undef vectorise
//
// P_GetClosestAxis
//
// Finds the CLOSEST axis to the source mobj
mobj_t *P_GetClosestAxis(mobj_t *source)
{
thinker_t *th;
mobj_t *mo2;
mobj_t *closestaxis = NULL;
fixed_t dist1, dist2 = 0;
// scan the thinkers to find the closest axis point
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo2 = (mobj_t *)th;
if (mo2->type == MT_AXIS)
{
if (closestaxis == NULL)
{
closestaxis = mo2;
dist2 = R_PointToDist2(source->x, source->y, mo2->x, mo2->y)-mo2->radius;
}
else
{
dist1 = R_PointToDist2(source->x, source->y, mo2->x, mo2->y)-mo2->radius;
if (dist1 < dist2)
{
closestaxis = mo2;
dist2 = dist1;
}
}
}
}
if (closestaxis == NULL)
CONS_Debug(DBG_NIGHTS, "ERROR: No axis points found!\n");
return closestaxis;
}
static void P_GimmeAxisXYPos(mobj_t *closestaxis, degenmobj_t *mobj)
{
const angle_t fa = R_PointToAngle2(closestaxis->x, closestaxis->y, mobj->x, mobj->y)>>ANGLETOFINESHIFT;
mobj->x = closestaxis->x + FixedMul(FINECOSINE(fa),closestaxis->radius);
mobj->y = closestaxis->y + FixedMul(FINESINE(fa),closestaxis->radius);
}
static void P_MoveHoop(mobj_t *mobj)
{
const fixed_t fuse = (mobj->fuse*mobj->extravalue2);
const angle_t fa = mobj->movedir*(FINEANGLES/mobj->extravalue1);
matrix_t m;
vector4_t v;
vector4_t res;
fixed_t finalx, finaly, finalz;
fixed_t x, y, z;
//I_Assert(mobj->target != NULL);
if (!mobj->target) /// \todo DEBUG ME! Target was P_RemoveMobj'd at some point, and therefore no longer valid!
return;
x = mobj->target->x;
y = mobj->target->y;
z = mobj->target->z+mobj->target->height/2;
// Make the sprite travel towards the center of the hoop
v.x = FixedMul(FINECOSINE(fa),fuse);
v.y = 0;
v.z = FixedMul(FINESINE(fa),fuse);
v.a = FRACUNIT;
FM_RotateX(&m, FixedAngle(mobj->target->movedir*FRACUNIT));
FV4_Copy(&v, FM_MultMatrixVec4(&m, &v, &res));
FM_RotateZ(&m, FixedAngle(mobj->target->movecount*FRACUNIT));
FV4_Copy(&v, FM_MultMatrixVec4(&m, &v, &res));
finalx = x + v.x;
finaly = y + v.y;
finalz = z + v.z;
P_UnsetThingPosition(mobj);
mobj->x = finalx;
mobj->y = finaly;
P_SetThingPosition(mobj);
mobj->z = finalz - mobj->height/2;
}
void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle)
{
mobj_t *mobj;
INT32 i;
matrix_t m;
vector4_t v;
vector4_t res;
fixed_t finalx, finaly, finalz;
mobj_t hoopcenter;
mobj_t *axis;
degenmobj_t xypos;
angle_t degrees, fa, closestangle;
hoopcenter.x = x;
hoopcenter.y = y;
hoopcenter.z = z;
axis = P_GetClosestAxis(&hoopcenter);
if (!axis)
{
CONS_Debug(DBG_NIGHTS, "You forgot to put axis points in the map!\n");
return;
}
xypos.x = x;
xypos.y = y;
P_GimmeAxisXYPos(axis, &xypos);
x = xypos.x;
y = xypos.y;
hoopcenter.z = z - mobjinfo[type].height/2;
hoopcenter.x = x;
hoopcenter.y = y;
closestangle = R_PointToAngle2(x, y, axis->x, axis->y);
degrees = FINEANGLES/number;
radius >>= FRACBITS;
// Create the hoop!
for (i = 0; i < number; i++)
{
fa = (i*degrees);
v.x = FixedMul(FINECOSINE(fa),radius);
v.y = 0;
v.z = FixedMul(FINESINE(fa),radius);
v.a = FRACUNIT;
FM_RotateX(&m, rotangle);
FV4_Copy(&v, FM_MultMatrixVec4(&m, &v, &res));
FM_RotateZ(&m, closestangle);
FV4_Copy(&v, FM_MultMatrixVec4(&m, &v, &res));
finalx = x + v.x;
finaly = y + v.y;
finalz = z + v.z;
mobj = P_SpawnMobj(finalx, finaly, finalz, type);
if (!P_MobjWasRemoved(mobj))
mobj->z -= mobj->height/2;
}
}
void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, statenum_t nstate, angle_t rotangle, boolean spawncenter)
{
mobj_t *mobj;
INT32 i;
matrix_t m;
vector4_t v;
vector4_t res;
fixed_t finalx, finaly, finalz, dist;
angle_t degrees, fa, closestangle;
fixed_t mobjx, mobjy, mobjz;
degrees = FINEANGLES/number;
radius = FixedDiv(radius,5*(FRACUNIT/4));
closestangle = 0;
// Create the hoop!
for (i = 0; i < number; i++)
{
fa = (i*degrees);
v.x = FixedMul(FINECOSINE(fa),radius);
v.y = 0;
v.z = FixedMul(FINESINE(fa),radius);
v.a = FRACUNIT;
FM_RotateX(&m, rotangle);
FV4_Copy(&v, FM_MultMatrixVec4(&m, &v, &res));
FM_RotateZ(&m, closestangle);
FV4_Copy(&v, FM_MultMatrixVec4(&m, &v, &res));
finalx = x + v.x;
finaly = y + v.y;
finalz = z + v.z;
mobj = P_SpawnMobj(finalx, finaly, finalz, type);
if (P_MobjWasRemoved(mobj))
continue;
mobj->z -= mobj->height>>1;
// change angle
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, x, y);
// change slope
dist = P_AproxDistance(P_AproxDistance(x - mobj->x, y - mobj->y), z - mobj->z);
if (dist < 1)
dist = 1;
mobjx = mobj->x;
mobjy = mobj->y;
mobjz = mobj->z;
// set to special state
if (nstate != S_NULL)
P_SetMobjState(mobj, nstate);
mobj->momx = FixedMul(FixedDiv(x - mobjx, dist), 5*FRACUNIT);
mobj->momy = FixedMul(FixedDiv(y - mobjy, dist), 5*FRACUNIT);
mobj->momz = FixedMul(FixedDiv(z - mobjz, dist), 5*FRACUNIT);
mobj->fuse = (radius>>(FRACBITS+2)) + 1;
if (spawncenter)
P_SetOrigin(mobj, x, y, z);
if (mobj->fuse <= 1)
mobj->fuse = 2;
mobj->flags |= MF_NOCLIPTHING;
mobj->flags &= ~MF_SPECIAL;
if (mobj->fuse > 7)
mobj->tics = mobj->fuse - 7;
else
mobj->tics = 1;
}
}
//
// P_SetScale
//
// Sets the sprite scaling
//
void P_SetScale(mobj_t *mobj, fixed_t newscale, boolean instant)
{
if (!mobj)
return;
if (mobj->player)
{
G_GhostAddScale(newscale);
// Nonono don't calculate viewheight elsewhere, this is the best place for it!
mobj->player->viewheight = FixedMul(FixedDiv(mobj->player->viewheight, mobj->scale), newscale);
}
mobj->radius = FixedMul(FixedDiv(mobj->radius, mobj->scale), newscale);
mobj->height = FixedMul(FixedDiv(mobj->height, mobj->scale), newscale);
mobj->scale = newscale;
if (instant)
mobj->destscale = mobj->old_scale = newscale;
}
void P_Attract(mobj_t *source, mobj_t *dest, boolean nightsgrab) // Home in on your target
{
fixed_t dist, ndist, speedmul;
angle_t vangle;
fixed_t tx = dest->x;
fixed_t ty = dest->y;
fixed_t tz = dest->z + (dest->height/2); // Aim for center
fixed_t xydist = P_AproxDistance(tx - source->x, ty - source->y);
if (!dest || dest->health <= 0 || !dest->player || !source->tracer)
return;
// change angle
source->angle = R_PointToAngle2(source->x, source->y, tx, ty);
// change slope
dist = P_AproxDistance(xydist, tz - source->z);
if (dist < 1)
dist = 1;
if (nightsgrab && source->movefactor)
{
source->movefactor += FRACUNIT/2;
if (dist < source->movefactor)
{
source->momx = source->momy = source->momz = 0;
P_MoveOrigin(source, tx, ty, tz);
}
else
{
vangle = R_PointToAngle2(source->z, 0, tz, xydist);
source->momx = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINECOSINE(source->angle >> ANGLETOFINESHIFT), source->movefactor));
source->momy = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINESINE(source->angle >> ANGLETOFINESHIFT), source->movefactor));
source->momz = FixedMul(FINECOSINE(vangle >> ANGLETOFINESHIFT), source->movefactor);
}
}
else
{
if (nightsgrab)
speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(8*FRACUNIT, source->scale);
else
speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(source->info->speed, source->scale);
source->momx = FixedMul(FixedDiv(tx - source->x, dist), speedmul);
source->momy = FixedMul(FixedDiv(ty - source->y, dist), speedmul);
source->momz = FixedMul(FixedDiv(tz - source->z, dist), speedmul);
}
// Instead of just unsetting NOCLIP like an idiot, let's check the distance to our target.
ndist = P_AproxDistance(P_AproxDistance(tx - (source->x+source->momx),
ty - (source->y+source->momy)),
tz - (source->z+source->momz));
if (ndist > dist) // gone past our target
{
// place us on top of them then.
source->momx = source->momy = source->momz = 0;
P_UnsetThingPosition(source);
source->x = tx;
source->y = ty;
source->z = tz;
P_SetThingPosition(source);
}
}
static void P_NightsItemChase(mobj_t *thing)
{
if (!thing->tracer)
{
P_SetTarget(&thing->tracer, NULL);
thing->flags2 &= ~MF2_NIGHTSPULL;
thing->movefactor = 0;
return;
}
if (!thing->tracer->player)
return;
P_Attract(thing, thing->tracer, true);
}
//
// P_MaceRotate
// Spins a hnext-chain of objects around its centerpoint, side to side or periodically.
//
void P_MaceRotate(mobj_t *center, INT32 baserot, INT32 baseprevrot)
{
matrix_t m;
vector4_t unit_lengthways, unit_sideways, pos_lengthways, pos_sideways;
vector4_t res;
fixed_t radius, dist = 0, zstore;
angle_t fa;
boolean dosound = false;
mobj_t *mobj = center->hnext, *hnext = NULL;
INT32 lastthreshold = -1; // needs to never be equal at start of loop
fixed_t lastfriction = INT32_MIN; // ditto; almost certainly never, but...
INT32 rot;
INT32 prevrot;
FV4_Load(&pos_sideways, 0, 0, 0, 0);
FV4_Load(&unit_sideways, 0, 0, 0, 0);
FV4_Load(&pos_lengthways, 0, 0, 0, 0);
while (mobj)
{
if (P_MobjWasRemoved(mobj) || !mobj->health)
{
mobj = mobj->hnext;
continue;
}
mobj->momx = mobj->momy = mobj->momz = 0;
if (mobj->threshold != lastthreshold
|| mobj->friction != lastfriction)
{
rot = (baserot + mobj->threshold) & FINEMASK;
prevrot = (baseprevrot + mobj->threshold) & FINEMASK;
FV4_Load(&pos_lengthways, 0, 0, 0, 0);
dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
dist = ((center->scale == FRACUNIT) ? dist : FixedMul(dist, center->scale));
fa = (FixedAngle(center->movefactor*FRACUNIT) >> ANGLETOFINESHIFT);
radius = FixedMul(dist, FINECOSINE(fa));
unit_lengthways.y = -FixedMul(dist, FINESINE(fa));
unit_lengthways.a = FRACUNIT;
// Swinging Chain.
if (center->flags2 & MF2_STRONGBOX)
{
fixed_t swingmag = FixedMul(FINECOSINE(rot), center->lastlook << FRACBITS);
fixed_t prevswingmag = FINECOSINE(prevrot);
if ((prevswingmag > 0) != (swingmag > 0)) // just passed its lowest point
dosound = true;
fa = ((FixedAngle(swingmag) >> ANGLETOFINESHIFT) + mobj->friction) & FINEMASK;
unit_lengthways.x = FixedMul(FINESINE(fa), -radius);
unit_lengthways.z = FixedMul(FINECOSINE(fa), -radius);
}
// Rotating Chain.
else
{
angle_t prevfa = (prevrot + mobj->friction) & FINEMASK;
fa = (rot + mobj->friction) & FINEMASK;
if (!(prevfa > (FINEMASK/2)) && (fa > (FINEMASK/2))) // completed a full swing
dosound = true;
unit_lengthways.x = FixedMul(FINECOSINE(fa), radius);
unit_lengthways.z = FixedMul(FINESINE(fa), radius);
}
// Calculate the angle matrixes for the link.
FM_RotateX(&m, center->threshold << ANGLETOFINESHIFT);
FV4_Copy(&unit_lengthways, FM_MultMatrixVec4(&m, &unit_lengthways, &res));
FM_RotateZ(&m, center->angle);
FV4_Copy(&unit_lengthways, FM_MultMatrixVec4(&m, &unit_lengthways, &res));
lastthreshold = mobj->threshold;
lastfriction = mobj->friction;
}
if (dosound && (mobj->flags2 & MF2_BOSSNOTRAP))
{
S_StartSound(mobj, mobj->info->activesound);
dosound = false;
}
if (pos_sideways.a != mobj->movefactor)
{
if (!unit_sideways.a)
{
unit_sideways.y = dist;
unit_sideways.x = unit_sideways.z = 0;
unit_sideways.a = FRACUNIT;
FM_RotateX(&m, center->threshold << ANGLETOFINESHIFT);
FV4_Copy(&unit_sideways, FM_MultMatrixVec4(&m, &unit_sideways, &res));
FM_RotateZ(&m, center->angle);
FV4_Copy(&unit_sideways, FM_MultMatrixVec4(&m, &unit_sideways, &res));
}
if (pos_sideways.a > mobj->movefactor)
{
do
{
pos_sideways.x -= unit_sideways.x;
pos_sideways.y -= unit_sideways.y;
pos_sideways.z -= unit_sideways.z;
}
while ((--pos_sideways.a) != mobj->movefactor);
}
else
{
do
{
pos_sideways.x += unit_sideways.x;
pos_sideways.y += unit_sideways.y;
pos_sideways.z += unit_sideways.z;
}
while ((++pos_sideways.a) != mobj->movefactor);
}
}
hnext = mobj->hnext; // just in case the mobj is removed
if (pos_lengthways.a > mobj->movecount)
{
do
{
pos_lengthways.x -= unit_lengthways.x;
pos_lengthways.y -= unit_lengthways.y;
pos_lengthways.z -= unit_lengthways.z;
}
while ((--pos_lengthways.a) != mobj->movecount);
}
else if (pos_lengthways.a < mobj->movecount)
{
do
{
pos_lengthways.x += unit_lengthways.x;
pos_lengthways.y += unit_lengthways.y;
pos_lengthways.z += unit_lengthways.z;
}
while ((++pos_lengthways.a) != mobj->movecount);
}
P_UnsetThingPosition(mobj);
mobj->x = center->x;
mobj->y = center->y;
mobj->z = center->z;
// Add on the appropriate distances to the center's co-ordinates.
if (pos_lengthways.a)
{
mobj->x += pos_lengthways.x;
mobj->y += pos_lengthways.y;
zstore = pos_lengthways.z + pos_sideways.z;
}
else
zstore = pos_sideways.z;
mobj->x += pos_sideways.x;
mobj->y += pos_sideways.y;
// Cut the height to align the link with the axis.
if (mobj->type == MT_SMALLMACECHAIN || mobj->type == MT_BIGMACECHAIN || mobj->type == MT_SMALLGRABCHAIN || mobj->type == MT_BIGGRABCHAIN)
zstore -= P_MobjFlip(mobj)*mobj->height/4;
else
zstore -= P_MobjFlip(mobj)*mobj->height/2;
mobj->z += zstore;
#if 0 // toaster's testing flashie!
if (!(mobj->movecount & 1) && !(leveltime & TICRATE)) // I had a brainfart and the flashing isn't exactly what I expected it to be, but it's actually much more useful.
mobj->flags2 ^= MF2_DONTDRAW;
#endif
P_SetThingPosition(mobj);
#if 0 // toaster's height-clipping dealie!
if (!pos_lengthways.a || P_MobjWasRemoved(mobj) || (mobj->flags & MF_NOCLIPHEIGHT))
goto cont;
if ((fa = ((center->threshold & (FINEMASK/2)) << ANGLETOFINESHIFT)) > ANGLE_45 && fa < ANGLE_135) // only move towards center when the motion is towards/away from the ground, rather than alongside it
goto cont;
if (mobj->subsector->sector->ffloors)
P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 2);
if (mobj->floorz > mobj->z)
zstore = (mobj->floorz - zstore);
else if (mobj->ceilingz < mobj->z)
zstore = (mobj->ceilingz - mobj->height - zstore);
else
goto cont;
zstore = FixedDiv(zstore, dist); // Still needs work... scaling factor is wrong!
P_UnsetThingPosition(mobj);
mobj->x -= FixedMul(unit_lengthways.x, zstore);
mobj->y -= FixedMul(unit_lengthways.y, zstore);
P_SetThingPosition(mobj);
cont:
#endif
mobj = hnext;
}
}
static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield)
{
if (!thing->target || thing->target->health <= 0 || !thing->target->player
|| (thing->target->player->powers[pw_shield] & SH_NOSTACK) == SH_NONE || thing->target->player->powers[pw_super]
|| thing->target->player->powers[pw_invulnerability] > 1)
{
P_RemoveMobj(thing);
return false;
}
// TODO: Make an MT_SHIELDORB which changes color/states to always match the appropriate shield,
// instead of having completely seperate mobjtypes.
if (!(shield & SH_FORCE))
{ // Regular shields check for themselves only
if ((shieldtype_t)(thing->target->player->powers[pw_shield] & SH_NOSTACK) != shield)
{
P_RemoveMobj(thing);
return false;
}
}
else if (!(thing->target->player->powers[pw_shield] & SH_FORCE))
{ // Force shields check for any force shield
P_RemoveMobj(thing);
return false;
}
if (shield & SH_FORCE && thing->movecount != (thing->target->player->powers[pw_shield] & SH_FORCEHP))
{
thing->movecount = (thing->target->player->powers[pw_shield] & SH_FORCEHP);
if (thing->movecount < 1)
{
if (thing->info->painstate)
P_SetMobjState(thing,thing->info->painstate);
else
thing->flags2 |= MF2_SHADOW;
}
else
{
if (thing->info->painstate)
P_SetMobjState(thing,thing->info->spawnstate);
else
thing->flags2 &= ~MF2_SHADOW;
}
}
thing->flags |= MF_NOCLIPHEIGHT;
thing->eflags = (thing->eflags & ~MFE_VERTICALFLIP)|(thing->target->eflags & MFE_VERTICALFLIP);
//Set the shield's scale based on shieldscale, hide it if we're too small!
fixed_t scale = FixedMul(thing->target->scale, thing->target->player->shieldscale);
if (scale < 1) {
P_SetScale(thing, thing->target->scale, true);
thing->old_scale = thing->target->old_scale;
thing->flags2 |= (MF2_DONTDRAW|MF2_JUSTATTACKED); //Hide and indicate we're hidden
} else {
P_SetScale(thing, scale, true);
thing->old_scale = FixedMul(thing->target->old_scale, thing->target->player->shieldscale);
//Only unhide if we were hidden by the above code
if (thing->flags2 & MF2_JUSTATTACKED)
thing->flags2 &= ~(MF2_DONTDRAW|MF2_JUSTATTACKED);
}
#define NewMH(mobj) mobj->height // Ugly mobj-height and player-height defines, for the sake of prettier code
#define NewPH(player) P_GetPlayerHeight(player)
#define OldMH(mobj) FixedMul(mobj->height, FixedDiv(mobj->old_scale, mobj->scale))
#define OldPH(player) FixedMul(player->height, player->mo->old_scale)
P_UnsetThingPosition(thing);
thing->x = thing->target->x;
thing->y = thing->target->y;
thing->old_x = thing->target->old_x;
thing->old_y = thing->target->old_y;
if (thing->eflags & MFE_VERTICALFLIP)
{
thing->z = thing->target->z + NewMH(thing->target) - NewMH(thing) + ((NewPH(thing->target->player) - NewMH(thing->target)) / 3) - (thing->target->scale * 2);
thing->old_z = thing->target->old_z + OldMH(thing->target) - OldMH(thing) + ((OldPH(thing->target->player) - OldMH(thing->target)) / 3) - (thing->target->old_scale * 2);
}
else
{
thing->z = thing->target->z - ((NewPH(thing->target->player) - NewMH(thing->target)) / 3) + (thing->target->scale * 2);
thing->old_z = thing->target->old_z - ((OldPH(thing->target->player) - OldMH(thing->target)) / 3) + (thing->target->old_scale * 2);
}
P_SetThingPosition(thing);
P_CheckPosition(thing, thing->x, thing->y);
#undef NewMH
#undef NewPH
#undef OldMH
#undef OldPH
if (P_MobjWasRemoved(thing))
return false;
// if (thing->z < thing->floorz)
// thing->z = thing->floorz;
return true;
}
mobj_t *shields[MAXPLAYERS*2];
INT32 numshields = 0;
void P_RunShields(void)
{
INT32 i;
// run shields
for (i = 0; i < numshields; i++)
{
if (!P_MobjWasRemoved(shields[i]))
P_ShieldLook(shields[i], shields[i]->threshold);
P_SetTarget(&shields[i], NULL);
}
numshields = 0;
}
static boolean P_AddShield(mobj_t *thing)
{
shieldtype_t shield = thing->threshold;
if (!thing->target || thing->target->health <= 0 || !thing->target->player
|| (thing->target->player->powers[pw_shield] & SH_NOSTACK) == SH_NONE || thing->target->player->powers[pw_super]
|| thing->target->player->powers[pw_invulnerability] > 1)
{
P_RemoveMobj(thing);
return false;
}
if (!(shield & SH_FORCE))
{ // Regular shields check for themselves only
if ((shieldtype_t)(thing->target->player->powers[pw_shield] & SH_NOSTACK) != shield)
{
P_RemoveMobj(thing);
return false;
}
}
else if (!(thing->target->player->powers[pw_shield] & SH_FORCE))
{ // Force shields check for any force shield
P_RemoveMobj(thing);
return false;
}
// Queue has been hit... why?!?
if (numshields >= MAXPLAYERS*2)
return P_ShieldLook(thing, thing->info->speed);
P_SetTarget(&shields[numshields++], thing);
return true;
}
void P_RunOverlays(void)
{
// run overlays
mobj_t *mo, *next = NULL;
for (mo = overlaycap; mo; mo = next)
{
fixed_t zoffs;
I_Assert(!P_MobjWasRemoved(mo));
// grab next in chain, then unset the chain target
next = mo->hnext;
P_SetTarget(&mo->hnext, NULL);
if (!mo->target)
continue;
if (P_MobjWasRemoved(mo->target))
{
P_RemoveMobj(mo);
continue;
}
mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP);
mo->scale = mo->destscale = mo->target->scale;
mo->old_scale = mo->target->old_scale;
mo->angle = (mo->target->player ? mo->target->player->drawangle : mo->target->angle) + mo->movedir;
mo->old_angle = (mo->target->player ? mo->target->player->old_drawangle : mo->target->old_angle) + mo->movedir;
if (!(mo->state->frame & FF_ANIMATE))
zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale);
// if you're using FF_ANIMATE on an overlay,
// then you're on your own.
else
zoffs = 0;
P_UnsetThingPosition(mo);
mo->x = mo->target->x;
mo->y = mo->target->y;
mo->old_x = mo->target->old_x;
mo->old_y = mo->target->old_y;
mo->radius = mo->target->radius;
mo->height = mo->target->height;
if (mo->eflags & MFE_VERTICALFLIP)
{
mo->z = mo->target->z + mo->target->height - mo->height - zoffs;
if (mo->scale == mo->old_scale)
mo->old_z = mo->target->old_z + mo->target->height - mo->height - zoffs;
else // Interpolate height scale changes - mo and mo->target have the same scales here, so don't interpolate them individually
mo->old_z = mo->target->old_z + FixedMul(mo->target->height - mo->height, FixedDiv(mo->old_scale, mo->scale)) - zoffs;
}
else
{
mo->z = mo->target->z + zoffs;
mo->old_z = mo->target->old_z + zoffs;
}
if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1)
P_SetUnderlayPosition(mo);
else
P_SetThingPosition(mo);
P_CheckPosition(mo, mo->x, mo->y);
}
P_SetTarget(&overlaycap, NULL);
}
// Called only when MT_OVERLAY thinks.
static void P_AddOverlay(mobj_t *thing)
{
I_Assert(thing != NULL);
if (overlaycap == NULL)
P_SetTarget(&overlaycap, thing);
else {
mobj_t *mo;
for (mo = overlaycap; mo && mo->hnext; mo = mo->hnext)
;
I_Assert(mo != NULL);
I_Assert(mo->hnext == NULL);
P_SetTarget(&mo->hnext, thing);
}
P_SetTarget(&thing->hnext, NULL);
}
// Called only when MT_OVERLAY (or anything else in the overlaycap list) is removed.
// Keeps the hnext list from corrupting.
static void P_RemoveOverlay(mobj_t *thing)
{
mobj_t *mo;
if (overlaycap == thing)
{
P_SetTarget(&overlaycap, thing->hnext);
P_SetTarget(&thing->hnext, NULL);
return;
}
for (mo = overlaycap; mo; mo = mo->hnext)
{
if (mo->hnext != thing)
continue;
P_SetTarget(&mo->hnext, thing->hnext);
P_SetTarget(&thing->hnext, NULL);
return;
}
}
// AI for the Koopa boss.
static void P_KoopaThinker(mobj_t *koopa)
{
P_MobjCheckWater(koopa);
if (koopa->watertop > koopa->z + koopa->height + FixedMul(128*FRACUNIT, koopa->scale) && koopa->health > 0)
{
if (koopa->spawnpoint)
EV_DoCeiling(koopa->spawnpoint->args[0], NULL, raiseToHighest);
P_RemoveMobj(koopa);
return;
}
// Koopa moves ONLY on the X axis!
if (koopa->threshold > 0)
{
koopa->threshold--;
koopa->momx = FixedMul(FRACUNIT, koopa->scale);
if (!koopa->threshold)
koopa->threshold = -TICRATE*2;
}
else if (koopa->threshold < 0)
{
koopa->threshold++;
koopa->momx = FixedMul(-FRACUNIT, koopa->scale);
if (!koopa->threshold)
koopa->threshold = TICRATE*2;
}
else
koopa->threshold = TICRATE*2;
P_XYMovement(koopa);
if (P_RandomChance(FRACUNIT/32) && koopa->z <= koopa->floorz)
koopa->momz = FixedMul(5*FRACUNIT, koopa->scale);
if (koopa->z > koopa->floorz)
koopa->momz += FixedMul(FRACUNIT/4, koopa->scale);
if (P_RandomChance(FRACUNIT/64))
{
mobj_t *flame;
flame = P_SpawnMobj(koopa->x - koopa->radius + FixedMul(5*FRACUNIT, koopa->scale), koopa->y, koopa->z + (P_RandomByte()<<(FRACBITS-2)), MT_KOOPAFLAME);
if (P_MobjWasRemoved(flame))
return;
flame->momx = -FixedMul(flame->info->speed, flame->scale);
S_StartSound(flame, sfx_koopfr);
}
else if (P_RandomChance(5*FRACUNIT/256))
{
mobj_t *hammer;
hammer = P_SpawnMobj(koopa->x - koopa->radius, koopa->y, koopa->z + koopa->height, MT_HAMMER);
if (P_MobjWasRemoved(hammer))
return;
hammer->momx = FixedMul(-5*FRACUNIT, hammer->scale);
hammer->momz = FixedMul(7*FRACUNIT, hammer->scale);
}
}
// Spawns and chains the minecart sides.
static void P_SpawnMinecartSegments(mobj_t *mobj, boolean mode)
{
fixed_t x = mobj->x;
fixed_t y = mobj->y;
fixed_t z = mobj->z;
mobj_t *prevseg = mobj;
mobj_t *seg;
UINT8 i;
for (i = 0; i < 4; i++)
{
seg = P_SpawnMobj(x, y, z, MT_MINECARTSEG);
if (P_MobjWasRemoved(seg))
continue;
P_SetMobjState(seg, (statenum_t)(S_MINECARTSEG_FRONT + i));
if (i >= 2)
seg->extravalue1 = (i == 2) ? -18 : 18; // make -20/20 when papersprite projection fixed
else
{
seg->extravalue2 = (i == 0) ? 24 : -24;
seg->cusval = -90;
}
if (!mode)
seg->frame &= ~FF_ANIMATE;
P_SetTarget(&prevseg->tracer, seg);
prevseg = seg;
}
}
// Updates the chained segments.
static void P_UpdateMinecartSegments(mobj_t *mobj)
{
mobj_t *seg = mobj->tracer;
fixed_t x = mobj->x;
fixed_t y = mobj->y;
fixed_t z = mobj->z;
angle_t ang = mobj->angle;
angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
fixed_t c = FINECOSINE(fa);
fixed_t s = FINESINE(fa);
INT32 dx, dy;
INT32 sang;
while (seg)
{
dx = seg->extravalue1;
dy = seg->extravalue2;
sang = seg->cusval;
P_MoveOrigin(seg, x + s*dx + c*dy, y - c*dx + s*dy, z);
seg->angle = ang + FixedAngle(FRACUNIT*sang);
seg->flags2 = (seg->flags2 & ~MF2_DONTDRAW) | (mobj->flags2 & MF2_DONTDRAW);
seg = seg->tracer;
}
}
void P_HandleMinecartSegments(mobj_t *mobj)
{
if (!mobj->tracer)
P_SpawnMinecartSegments(mobj, (mobj->type == MT_MINECART));
P_UpdateMinecartSegments(mobj);
}
static void P_PyreFlyBurn(mobj_t *mobj, fixed_t hoffs, INT16 vrange, mobjtype_t mobjtype, fixed_t momz)
{
angle_t fa = (FixedAngle(P_RandomKey(360)*FRACUNIT) >> ANGLETOFINESHIFT) & FINEMASK;
fixed_t xoffs = FixedMul(FINECOSINE(fa), mobj->radius + hoffs);
fixed_t yoffs = FixedMul(FINESINE(fa), mobj->radius + hoffs);
fixed_t zoffs = P_RandomRange(-vrange, vrange)*FRACUNIT;
mobj_t *particle = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, zoffs, mobjtype);
if (P_MobjWasRemoved(particle))
return;
particle->momz = momz;
particle->flags2 |= MF2_LINKDRAW;
P_SetTarget(&particle->tracer, mobj);
}
static void P_MobjScaleThink(mobj_t *mobj)
{
fixed_t oldheight = mobj->height;
UINT8 correctionType = 0; // Don't correct Z position, just gain height
if (mobj->flags & MF_NOCLIPHEIGHT || (mobj->z > mobj->floorz && mobj->z + mobj->height < mobj->ceilingz))
correctionType = 1; // Correct Z position by centering
else if (mobj->eflags & MFE_VERTICALFLIP)
correctionType = 2; // Correct Z position by moving down
if (abs(mobj->scale - mobj->destscale) < mobj->scalespeed)
P_SetScale(mobj, mobj->destscale, false);
else if (mobj->scale < mobj->destscale)
P_SetScale(mobj, mobj->scale + mobj->scalespeed, false);
else if (mobj->scale > mobj->destscale)
P_SetScale(mobj, mobj->scale - mobj->scalespeed, false);
if (correctionType == 1)
mobj->z -= (mobj->height - oldheight)/2;
else if (correctionType == 2)
mobj->z -= mobj->height - oldheight;
if (mobj->scale == mobj->destscale)
/// \todo Lua hook for "reached destscale"?
switch (mobj->type)
{
default:
break;
}
}
static void P_MaceSceneryThink(mobj_t *mobj)
{
angle_t oldmovedir = mobj->movedir;
// Always update movedir to prevent desyncing (in the traditional sense, not the netplay sense).
mobj->movedir = (mobj->movedir + mobj->lastlook) & FINEMASK;
// If too far away and not deliberately spitting in the face of optimisation, don't think!
if (!(mobj->flags2 & MF2_BOSSNOTRAP))
{
UINT8 i;
// Quick! Look through players! Don't move unless a player is relatively close by.
// The below is selected based on CEZ2's first room. I promise you it is a coincidence that it looks like the weed number.
for (i = 0; i < MAXPLAYERS; ++i)
if (playeringame[i] && players[i].mo
&& P_AproxDistance(P_AproxDistance(mobj->x - players[i].mo->x, mobj->y - players[i].mo->y), mobj->z - players[i].mo->z) < (4200 << FRACBITS))
break; // Stop looking.
if (i == MAXPLAYERS)
{
if (!(mobj->flags2 & MF2_BEYONDTHEGRAVE))
{
mobj_t *ref = mobj;
// stop/hide all your babies
while ((ref = ref->hnext))
{
ref->eflags = (((ref->flags & MF_NOTHINK) ? 0 : 1)
| ((ref->flags & MF_NOCLIPTHING) ? 0 : 2)
| ((ref->flags2 & MF2_DONTDRAW) ? 0 : 4)); // oh my god this is nasty.
ref->flags |= MF_NOTHINK|MF_NOCLIPTHING;
ref->flags2 |= MF2_DONTDRAW;
ref->momx = ref->momy = ref->momz = 0;
}
mobj->flags2 |= MF2_BEYONDTHEGRAVE;
}
return; // don't make bubble!
}
else if (mobj->flags2 & MF2_BEYONDTHEGRAVE)
{
mobj_t *ref = mobj;
// start/show all your babies
while ((ref = ref->hnext))
{
if (ref->eflags & 1)
ref->flags &= ~MF_NOTHINK;
if (ref->eflags & 2)
ref->flags &= ~MF_NOCLIPTHING;
if (ref->eflags & 4)
ref->flags2 &= ~MF2_DONTDRAW;
ref->eflags = 0; // le sign
}
mobj->flags2 &= ~MF2_BEYONDTHEGRAVE;
}
}
// Okay, time to MOVE
P_MaceRotate(mobj, mobj->movedir, oldmovedir);
}
static boolean P_DrownNumbersSceneryThink(mobj_t *mobj)
{
if (!mobj->target)
{
P_RemoveMobj(mobj);
return false;
}
if (!mobj->target->player || !(mobj->target->player->powers[pw_underwater] || mobj->target->player->powers[pw_spacetime]))
{
P_RemoveMobj(mobj);
return false;
}
mobj->x = mobj->target->x;
mobj->y = mobj->target->y;
P_SetScale(mobj, mobj->target->scale, false);
mobj->destscale = mobj->target->destscale;
mobj->old_scale = mobj->target->old_scale;
if (mobj->target->eflags & MFE_VERTICALFLIP)
{
mobj->z = mobj->target->z - FixedMul(16*FRACUNIT, mobj->target->scale) - mobj->height;
if (mobj->target->player->pflags & PF_FLIPCAM)
mobj->eflags |= MFE_VERTICALFLIP;
}
else
mobj->z = mobj->target->z + (mobj->target->height) + FixedMul(8*FRACUNIT, mobj->target->scale); // Adjust height for height changes
if (mobj->threshold <= 35)
mobj->flags2 |= MF2_DONTDRAW;
else
mobj->flags2 &= ~MF2_DONTDRAW;
if (mobj->threshold <= 30)
mobj->threshold = 40;
mobj->threshold--;
return true;
}
static void P_FlameJetSceneryThink(mobj_t *mobj)
{
mobj_t *flame;
fixed_t strength;
if (!(mobj->flags2 & MF2_FIRING))
return;
if ((leveltime & 3) != 0)
return;
// Wave the flames back and forth. Reactiontime determines which direction it's going.
if (mobj->fuse <= -16)
mobj->reactiontime = 1;
else if (mobj->fuse >= 16)
mobj->reactiontime = 0;
if (mobj->reactiontime)
mobj->fuse += 2;
else
mobj->fuse -= 2;
flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
if (P_MobjWasRemoved(flame))
return;
P_SetMobjState(flame, S_FLAMEJETFLAME4);
flame->angle = mobj->angle;
if (mobj->flags2 & MF2_AMBUSH) // Wave up and down instead of side-to-side
flame->momz = mobj->fuse << (FRACBITS - 2);
else
flame->angle += FixedAngle(mobj->fuse<<FRACBITS);
strength = (mobj->movedir ? mobj->movedir : 80)<<(FRACBITS-2);
P_InstaThrust(flame, flame->angle, strength);
S_StartSound(flame, sfx_fire);
}
static void P_VerticalFlameJetSceneryThink(mobj_t *mobj)
{
mobj_t *flame;
fixed_t strength;
if (!(mobj->flags2 & MF2_FIRING))
return;
if ((leveltime & 3) != 0)
return;
// Wave the flames back and forth. Reactiontime determines which direction it's going.
if (mobj->fuse <= -16)
mobj->reactiontime = 1;
else if (mobj->fuse >= 16)
mobj->reactiontime = 0;
if (mobj->reactiontime)
mobj->fuse++;
else
mobj->fuse--;
flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
if (P_MobjWasRemoved(flame))
return;
strength = (mobj->movedir ? mobj->movedir : 80)<<(FRACBITS-2);
// If deaf'd, the object spawns on the ceiling.
if (mobj->flags2 & MF2_AMBUSH)
{
mobj->z = mobj->ceilingz - mobj->height;
flame->momz = -strength;
}
else
{
flame->momz = strength;
P_SetMobjState(flame, S_FLAMEJETFLAME7);
}
P_InstaThrust(flame, mobj->angle, FixedDiv(mobj->fuse*FRACUNIT, 3*FRACUNIT));
S_StartSound(flame, sfx_fire);
}
static boolean P_ParticleGenSceneryThink(mobj_t *mobj)
{
if (!mobj->lastlook)
return false;
if (!mobj->threshold)
return false;
if (--mobj->fuse <= 0)
{
INT32 i = 0;
mobj_t *spawn;
fixed_t bottomheight, topheight;
INT32 type = mobj->threshold, line = mobj->cvmem;
mobj->fuse = (tic_t)mobj->reactiontime;
if (line != -1)
{
bottomheight = lines[line].frontsector->floorheight;
topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
}
else if (mobj->flags2 & MF2_OBJECTFLIP)
{
bottomheight = mobj->z - mobj->extravalue1;
topheight = mobj->z - mobjinfo[(mobjtype_t)type].height;
}
else
{
bottomheight = mobj->z;
topheight = mobj->z + mobj->extravalue1 - mobjinfo[(mobjtype_t)type].height;
}
if (mobj->waterbottom != bottomheight || mobj->watertop != topheight)
{
if (mobj->movefactor && (topheight > bottomheight))
mobj->health = (tic_t)(FixedDiv((topheight - bottomheight), abs(mobj->movefactor)) >> FRACBITS);
else
mobj->health = 0;
if (line != -1)
mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
}
if (!mobj->health)
return false;
for (i = 0; i < mobj->lastlook; i++)
{
spawn = P_SpawnMobj(
mobj->x + FixedMul(FixedMul(mobj->friction, mobj->scale), FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)),
mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle >> ANGLETOFINESHIFT)),
mobj->z,
(mobjtype_t)mobj->threshold);
if (!P_MobjWasRemoved(spawn))
{
P_SetScale(spawn, mobj->scale, true);
spawn->destscale = spawn->scale/100;
spawn->scalespeed = spawn->scale/mobj->health;
spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
spawn->tics = (tic_t)mobj->health;
spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
}
mobj->angle += mobj->movedir;
}
mobj->angle += (angle_t)mobj->movecount;
}
return true;
}
static void P_RosySceneryThink(mobj_t *mobj)
{
UINT8 i;
fixed_t pdist = 1700*mobj->scale, work, actualwork;
player_t *player = NULL;
statenum_t stat = (mobj->state - states);
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
if (!players[i].mo)
continue;
if (players[i].bot == BOT_2PAI || players[i].bot == BOT_2PHUMAN)
continue;
if (!players[i].mo->health)
continue;
actualwork = work = FixedHypot(mobj->x - players[i].mo->x, mobj->y - players[i].mo->y);
if (player)
{
if (players[i].skin == 0 || players[i].skin == 5)
work = (2*work)/3;
if (work >= pdist)
continue;
}
pdist = actualwork;
player = &players[i];
}
if (stat == S_ROSY_JUMP || stat == S_ROSY_PAIN)
{
if (P_IsObjectOnGround(mobj))
{
mobj->momx = mobj->momy = 0;
if (player && mobj->cvmem < (-2*TICRATE))
stat = S_ROSY_UNHAPPY;
else
stat = S_ROSY_WALK;
P_SetMobjState(mobj, stat);
}
else if (P_MobjFlip(mobj)*mobj->momz < 0)
mobj->frame = mobj->state->frame + mobj->state->var1;
}
if (!player)
{
if ((stat < S_ROSY_IDLE1 || stat > S_ROSY_IDLE4) && stat != S_ROSY_JUMP)
{
mobj->momx = mobj->momy = 0;
P_SetMobjState(mobj, S_ROSY_IDLE1);
}
}
else
{
boolean dojump = false, targonground, love, makeheart = false;
if (mobj->target != player->mo)
P_SetTarget(&mobj->target, player->mo);
// Tatsuru: Don't try to hug them if they're above or below you!
targonground = (P_IsObjectOnGround(mobj->target) && (player->panim == PA_IDLE || player->panim == PA_WALK || player->panim == PA_RUN) && player->mo->z == mobj->z);
love = (player->skin == 0 || player->skin == 5);
switch (stat)
{
case S_ROSY_IDLE1:
case S_ROSY_IDLE2:
case S_ROSY_IDLE3:
case S_ROSY_IDLE4:
dojump = true;
break;
case S_ROSY_JUMP:
case S_ROSY_PAIN:
// handled above
break;
case S_ROSY_WALK:
{
fixed_t x = mobj->x, y = mobj->y, z = mobj->z;
angle_t angletoplayer = R_PointToAngle2(x, y, mobj->target->x, mobj->target->y);
boolean allowed = P_TryMove(mobj, mobj->target->x, mobj->target->y, false);
if (P_MobjWasRemoved(mobj))
return;
P_UnsetThingPosition(mobj);
mobj->x = x;
mobj->y = y;
mobj->z = z;
P_SetThingPosition(mobj);
if (allowed)
{
fixed_t mom, max;
P_Thrust(mobj, angletoplayer, (3*FRACUNIT) >> 1);
mom = FixedHypot(mobj->momx, mobj->momy);
max = pdist;
if ((--mobj->extravalue1) <= 0)
{
if (++mobj->frame > mobj->state->frame + mobj->state->var1)
mobj->frame = mobj->state->frame;
if (mom > 12*mobj->scale)
mobj->extravalue1 = 2;
else if (mom > 6*mobj->scale)
mobj->extravalue1 = 3;
else
mobj->extravalue1 = 4;
}
if (max < (mobj->radius + mobj->target->radius))
{
mobj->momx = mobj->target->player->cmomx;
mobj->momy = mobj->target->player->cmomy;
if ((mobj->cvmem > TICRATE && !player->exiting) || !targonground)
P_SetMobjState(mobj, (stat = S_ROSY_STND));
else
{
mobj->target->momx = mobj->momx;
mobj->target->momy = mobj->momy;
P_SetMobjState(mobj, (stat = S_ROSY_HUG));
S_StartSound(mobj, sfx_cdpcm6);
mobj->angle = angletoplayer;
}
}
else
{
max /= 3;
if (max > 30*mobj->scale)
max = 30*mobj->scale;
if (mom > max && max > mobj->scale)
{
max = FixedDiv(max, mom);
mobj->momx = FixedMul(mobj->momx, max);
mobj->momy = FixedMul(mobj->momy, max);
}
if (abs(mobj->momx) > mobj->scale || abs(mobj->momy) > mobj->scale)
mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
}
}
else
dojump = true;
}
break;
case S_ROSY_HUG:
if (targonground)
{
player->pflags |= PF_STASIS;
if (mobj->cvmem < 5*TICRATE)
mobj->cvmem++;
if (love && !(leveltime & 7))
makeheart = true;
}
else
{
if (mobj->cvmem < (love ? 5*TICRATE : 0))
{
P_SetMobjState(mobj, (stat = S_ROSY_PAIN));
S_StartSound(mobj, sfx_cdpcm7);
}
else
P_SetMobjState(mobj, (stat = S_ROSY_JUMP));
var1 = var2 = 0;
A_DoNPCPain(mobj);
mobj->cvmem -= TICRATE;
}
break;
case S_ROSY_STND:
if ((pdist > (mobj->radius + mobj->target->radius + 3*(mobj->scale + mobj->target->scale))))
P_SetMobjState(mobj, (stat = S_ROSY_WALK));
else if (!targonground)
;
else
{
if (love && !(leveltime & 15))
makeheart = true;
if (player->exiting || --mobj->cvmem < TICRATE)
{
P_SetMobjState(mobj, (stat = S_ROSY_HUG));
S_StartSound(mobj, sfx_cdpcm6);
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
mobj->target->momx = mobj->momx;
mobj->target->momy = mobj->momy;
}
}
break;
case S_ROSY_UNHAPPY:
default:
break;
}
if (stat == S_ROSY_HUG)
{
if (player->panim != PA_IDLE)
P_SetMobjState(mobj->target, S_PLAY_STND);
player->pflags |= PF_STASIS;
}
if (dojump)
{
P_SetMobjState(mobj, S_ROSY_JUMP);
mobj->z += P_MobjFlip(mobj);
mobj->momx = mobj->momy = 0;
P_SetObjectMomZ(mobj, 6 << FRACBITS, false);
S_StartSound(mobj, sfx_cdfm02);
}
if (makeheart)
{
mobj_t *cdlhrt = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_CDLHRT);
if (!P_MobjWasRemoved(cdlhrt))
{
P_SetScale(cdlhrt, (5*mobj->scale) >> 4, true);
cdlhrt->fuse = (5*TICRATE) >> 1;
cdlhrt->momz = mobj->scale;
P_SetTarget(&cdlhrt->target, mobj);
cdlhrt->extravalue1 = mobj->x;
cdlhrt->extravalue2 = mobj->y;
}
}
}
}
static void P_MobjSceneryThink(mobj_t *mobj)
{
if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker)))
return;
if (P_MobjWasRemoved(mobj))
return;
if ((mobj->flags2 & MF2_SHIELD) && !P_AddShield(mobj))
return;
switch (mobj->type)
{
case MT_BOSSJUNK:
mobj->flags2 ^= MF2_DONTDRAW;
break;
case MT_MACEPOINT:
case MT_CHAINMACEPOINT:
case MT_SPRINGBALLPOINT:
case MT_CHAINPOINT:
case MT_FIREBARPOINT:
case MT_CUSTOMMACEPOINT:
case MT_HIDDEN_SLING:
P_MaceSceneryThink(mobj);
break;
case MT_HOOP:
if (mobj->fuse > 1)
P_MoveHoop(mobj);
else if (mobj->fuse == 1)
mobj->movecount = 1;
if (mobj->movecount)
{
mobj->fuse++;
if (mobj->fuse > 32)
{
// Don't kill the hoop center. For the sake of respawning.
//if (mobj->target)
// P_RemoveMobj(mobj->target);
P_RemoveMobj(mobj);
}
}
else
mobj->fuse--;
return;
case MT_NIGHTSPARKLE:
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
}
P_UnsetThingPosition(mobj);
mobj->x += mobj->momx;
mobj->y += mobj->momy;
mobj->z += mobj->momz;
P_SetThingPosition(mobj);
return;
case MT_NIGHTSLOOPHELPER:
if (--mobj->tics <= 0)
P_RemoveMobj(mobj);
// Don't touch my fuse!
return;
case MT_OVERLAY:
if (!mobj->target)
{
P_RemoveMobj(mobj);
return;
}
else
P_AddOverlay(mobj);
break;
case MT_PITY_ORB:
case MT_WHIRLWIND_ORB:
case MT_ARMAGEDDON_ORB:
if (!(mobj->flags2 & MF2_SHIELD))
return;
break;
case MT_ATTRACT_ORB:
if (!(mobj->flags2 & MF2_SHIELD))
return;
if (/*(mobj->target) -- the following is implicit by P_AddShield
&& (mobj->target->player)
&& */ (mobj->target->player->homing) && (mobj->target->player->pflags & PF_SHIELDABILITY))
{
P_SetMobjState(mobj, mobj->info->painstate);
mobj->tics++;
}
break;
case MT_ELEMENTAL_ORB:
if (!(mobj->flags2 & MF2_SHIELD))
return;
if (mobj->tracer
/* && mobj->target -- the following is implicit by P_AddShield
&& mobj->target->player
&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL */
&& mobj->target->player->pflags & PF_SHIELDABILITY
&& ((statenum_t)(mobj->tracer->state - states) < mobj->info->raisestate
|| (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
{
P_SetMobjState(mobj, mobj->info->painstate);
mobj->tics++;
P_SetMobjState(mobj->tracer, mobj->info->raisestate);
mobj->tracer->tics++;
}
break;
case MT_FORCE_ORB:
if (!(mobj->flags2 & MF2_SHIELD))
return;
if (/*
&& mobj->target -- the following is implicit by P_AddShield
&& mobj->target->player
&& (mobj->target->player->powers[pw_shield] & SH_FORCE)
&& */ (mobj->target->player->pflags & PF_SHIELDABILITY))
{
mobj_t *whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
if (!P_MobjWasRemoved(whoosh))
{
P_SetMobjState(whoosh, mobj->info->raisestate);
whoosh->destscale = whoosh->scale << 1;
whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
whoosh->height = 38*whoosh->scale;
whoosh->fuse = 10;
whoosh->flags |= MF_NOCLIPHEIGHT;
whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames
}
mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal whoosh
}
/* FALLTHRU */
case MT_FLAMEAURA_ORB:
if (!(mobj->flags2 & MF2_SHIELD))
return;
if ((statenum_t)(mobj->state - states) < mobj->info->painstate)
mobj->angle = mobj->target->angle; // implicitly okay because of P_AddShield
if (mobj->tracer
/* && mobj->target -- the following is implicit by P_AddShield
&& mobj->target->player
&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_FLAMEAURA */
&& mobj->target->player->pflags & PF_SHIELDABILITY
&& ((statenum_t)(mobj->tracer->state - states) < mobj->info->raisestate
|| (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
{
P_SetMobjState(mobj, mobj->info->painstate);
mobj->tics++;
P_SetMobjState(mobj->tracer, mobj->info->raisestate);
mobj->tracer->tics++;
}
break;
case MT_BUBBLEWRAP_ORB:
if (!(mobj->flags2 & MF2_SHIELD))
return;
if (mobj->tracer
/* && mobj->target -- the following is implicit by P_AddShield
&& mobj->target->player
&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP */
)
{
if (mobj->target->player->pflags & PF_SHIELDABILITY
&& ((statenum_t)(mobj->state - states) < mobj->info->painstate
|| (mobj->state->nextstate < mobj->info->painstate && mobj->tics == 1)))
{
P_SetMobjState(mobj, mobj->info->painstate);
mobj->tics++;
P_SetMobjState(mobj->tracer, mobj->info->raisestate);
mobj->tracer->tics++;
}
else if (mobj->target->eflags & MFE_JUSTHITFLOOR
&& (statenum_t)(mobj->state - states) == mobj->info->painstate)
{
P_SetMobjState(mobj, mobj->info->painstate + 1);
mobj->tics++;
P_SetMobjState(mobj->tracer, mobj->info->raisestate + 1);
mobj->tracer->tics++;
}
}
break;
case MT_THUNDERCOIN_ORB:
if (!(mobj->flags2 & MF2_SHIELD))
return;
if (mobj->tracer
/* && mobj->target -- the following is implicit by P_AddShield
&& mobj->target->player
&& (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_THUNDERCOIN */
&& (mobj->target->player->pflags & PF_SHIELDABILITY))
{
P_SetMobjState(mobj, mobj->info->painstate);
mobj->tics++;
P_SetMobjState(mobj->tracer, mobj->info->raisestate);
mobj->tracer->tics++;
mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal spark
}
break;
case MT_WATERDROP:
P_SceneryCheckWater(mobj);
if (((!(mobj->eflags & MFE_VERTICALFLIP) && (mobj->z <= mobj->floorz || mobj->z <= mobj->watertop))
|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z + mobj->height >= mobj->ceilingz))
&& mobj->health > 0)
{
mobj->health = 0;
P_SetMobjState(mobj, mobj->info->deathstate);
S_StartSound(mobj, mobj->info->deathsound + P_RandomKey(mobj->info->mass));
return;
}
break;
case MT_BUBBLES:
P_SceneryCheckWater(mobj);
break;
case MT_SMALLBUBBLE:
case MT_MEDIUMBUBBLE:
case MT_EXTRALARGEBUBBLE: // start bubble dissipate
P_SceneryCheckWater(mobj);
if (P_MobjWasRemoved(mobj)) // bubble was removed by not being in water
return;
if (!(mobj->eflags & MFE_UNDERWATER)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz)
|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z <= mobj->floorz)
|| --mobj->fuse <= 0) // Bubbles eventually dissipate if they can't reach the surface.
{
// no playing sound: no point; the object is being removed
P_RemoveMobj(mobj);
return;
}
break;
case MT_LOCKON:
if (!mobj->target)
{
P_RemoveMobj(mobj);
return;
}
mobj->flags2 &= ~MF2_DONTDRAW;
mobj->x = mobj->target->x;
mobj->y = mobj->target->y;
mobj->eflags |= (mobj->target->eflags & MFE_VERTICALFLIP);
P_SetScale(mobj, mobj->target->scale, false);
mobj->destscale = mobj->target->destscale;
mobj->old_scale = mobj->target->old_scale;
if (!(mobj->eflags & MFE_VERTICALFLIP))
mobj->z = mobj->target->z + mobj->target->height + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale);
else
mobj->z = mobj->target->z - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale) - mobj->height;
mobj->old_z = mobj->z;
break;
case MT_LOCKONINF:
if (!(mobj->flags2 & MF2_STRONGBOX))
{
mobj->threshold = mobj->z;
mobj->flags2 |= MF2_STRONGBOX;
}
if (!(mobj->eflags & MFE_VERTICALFLIP))
mobj->z = mobj->threshold + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
else
mobj->z = mobj->threshold - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
mobj->old_z = mobj->z;
break;
case MT_DROWNNUMBERS:
if (!P_DrownNumbersSceneryThink(mobj))
return;
break;
case MT_FLAMEJET:
P_FlameJetSceneryThink(mobj);
break;
case MT_VERTICALFLAMEJET:
P_VerticalFlameJetSceneryThink(mobj);
break;
case MT_FLICKY_01_CENTER:
case MT_FLICKY_02_CENTER:
case MT_FLICKY_03_CENTER:
case MT_FLICKY_04_CENTER:
case MT_FLICKY_05_CENTER:
case MT_FLICKY_06_CENTER:
case MT_FLICKY_07_CENTER:
case MT_FLICKY_08_CENTER:
case MT_FLICKY_09_CENTER:
case MT_FLICKY_10_CENTER:
case MT_FLICKY_11_CENTER:
case MT_FLICKY_12_CENTER:
case MT_FLICKY_13_CENTER:
case MT_FLICKY_14_CENTER:
case MT_FLICKY_15_CENTER:
case MT_FLICKY_16_CENTER:
case MT_SECRETFLICKY_01_CENTER:
case MT_SECRETFLICKY_02_CENTER:
if (mobj->tracer && (mobj->flags & MF_NOCLIPTHING)
&& (mobj->flags & MF_GRENADEBOUNCE))
// for now: only do this bounce routine if flicky is in-place. \todo allow in all movements
{
if (!(mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z <= mobj->tracer->floorz)
mobj->tracer->momz = 7*FRACUNIT;
else if ((mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z >= mobj->tracer->ceilingz - mobj->tracer->height)
mobj->tracer->momz = -7*FRACUNIT;
}
break;
case MT_SEED:
if (P_MobjFlip(mobj)*mobj->momz < mobj->info->speed)
mobj->momz = P_MobjFlip(mobj)*mobj->info->speed;
break;
case MT_ROCKCRUMBLE1:
case MT_ROCKCRUMBLE2:
case MT_ROCKCRUMBLE3:
case MT_ROCKCRUMBLE4:
case MT_ROCKCRUMBLE5:
case MT_ROCKCRUMBLE6:
case MT_ROCKCRUMBLE7:
case MT_ROCKCRUMBLE8:
case MT_ROCKCRUMBLE9:
case MT_ROCKCRUMBLE10:
case MT_ROCKCRUMBLE11:
case MT_ROCKCRUMBLE12:
case MT_ROCKCRUMBLE13:
case MT_ROCKCRUMBLE14:
case MT_ROCKCRUMBLE15:
case MT_ROCKCRUMBLE16:
case MT_WOODDEBRIS:
case MT_BRICKDEBRIS:
case MT_BROKENROBOT:
if (((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height))
|| (mobj->eflags & MFE_VERTICALFLIP && mobj->z + mobj->height >= P_CeilingzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)))
&& mobj->state != &states[mobj->info->deathstate])
{
P_SetMobjState(mobj, mobj->info->deathstate);
return;
}
break;
case MT_PARTICLEGEN:
if (!P_ParticleGenSceneryThink(mobj))
return;
break;
case MT_FSGNA:
if (mobj->movedir)
mobj->angle += mobj->movedir;
break;
case MT_ROSY:
P_RosySceneryThink(mobj);
break;
case MT_CDLHRT:
{
if (mobj->cvmem < 24)
mobj->cvmem++;
mobj->movedir += ANG10;
P_UnsetThingPosition(mobj);
mobj->x = mobj->extravalue1 + P_ReturnThrustX(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
mobj->y = mobj->extravalue2 + P_ReturnThrustY(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
P_SetThingPosition(mobj);
if (!mobj->fuse)
{
if (!LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse)))
P_RemoveMobj(mobj);
return;
}
if (mobj->fuse < 0)
return;
if (mobj->fuse < 6)
mobj->frame = (mobj->frame & ~FF_TRANSMASK) | ((10 - (mobj->fuse*2)) << (FF_TRANSSHIFT));
mobj->fuse--;
}
break;
case MT_FINISHFLAG:
{
if (!mobj->target || mobj->target->player->playerstate == PST_DEAD || !cv_exitmove.value)
{
P_RemoveMobj(mobj);
return;
}
P_UnsetThingPosition(mobj);
{
fixed_t radius = FixedMul(10*mobj->info->speed, mobj->target->scale);
angle_t fa;
mobj->angle += FixedAngle(mobj->info->speed);
fa = mobj->angle >> ANGLETOFINESHIFT;
mobj->x = mobj->target->x + FixedMul(FINECOSINE(fa),radius);
mobj->y = mobj->target->y + FixedMul(FINESINE(fa),radius);
mobj->z = mobj->target->z + mobj->target->height/2;
}
P_SetThingPosition(mobj);
P_SetScale(mobj, mobj->target->scale, false);
mobj->destscale = mobj->target->destscale;
mobj->old_scale = mobj->target->old_scale;
}
break;
case MT_TUTORIALFLOWER:
mobj->angle += FixedAngle(3*FRACUNIT);
break;
case MT_VWREF:
case MT_VWREB:
{
INT32 strength;
++mobj->movedir;
mobj->frame &= ~FF_TRANSMASK;
strength = min(mobj->fuse, (INT32)mobj->movedir)*3;
if (strength < 10)
mobj->frame |= ((10 - strength) << (FF_TRANSSHIFT));
}
/* FALLTHRU */
default:
if (mobj->fuse)
{ // Scenery object fuse! Very basic!
mobj->fuse--;
if (!mobj->fuse)
{
if (!LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse)))
P_RemoveMobj(mobj);
return;
}
}
break;
}
if (!P_MobjWasRemoved(mobj))
P_SceneryThinker(mobj);
}
static boolean P_MobjPushableThink(mobj_t *mobj)
{
P_MobjCheckWater(mobj);
P_PushableThinker(mobj);
// Extinguish fire objects in water. (Yes, it's extraordinarily rare to have a pushable flame object, but Brak uses such a case.)
if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
&& (mobj->eflags & (MFE_UNDERWATER | MFE_TOUCHWATER)))
{
P_KillMobj(mobj, NULL, NULL, 0);
return false;
}
return true;
}
static boolean P_MobjBossThink(mobj_t *mobj)
{
if (LUA_HookMobj(mobj, MOBJ_HOOK(BossThinker)))
{
if (P_MobjWasRemoved(mobj))
return false;
}
else if (P_MobjWasRemoved(mobj))
return false;
else
switch (mobj->type)
{
case MT_EGGMOBILE:
if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
{
fixed_t rad = mobj->radius >> FRACBITS;
fixed_t hei = mobj->height >> FRACBITS;
mobj_t *particle = P_SpawnMobjFromMobj(mobj,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(hei / 2, hei) << FRACBITS,
MT_SMOKE);
if (!P_MobjWasRemoved(particle))
{
P_SetObjectMomZ(particle, 2 << FRACBITS, false);
particle->momz += mobj->momz;
}
}
if (mobj->flags2 & MF2_SKULLFLY)
#if 1
P_SpawnGhostMobj(mobj);
#else // all the way back from final demo... MT_THOK isn't even the same size anymore!
{
mobj_t *spawnmobj;
spawnmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->painchance);
if (!P_MobjWasRemoved(spawnmobj))
{
P_SetTarget(&spawnmobj->target, mobj);
spawnmobj->color = SKINCOLOR_GREY;
}
}
#endif
P_Boss1Thinker(mobj);
break;
case MT_EGGMOBILE2:
if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
{
fixed_t rad = mobj->radius >> FRACBITS;
fixed_t hei = mobj->height >> FRACBITS;
mobj_t *particle = P_SpawnMobjFromMobj(mobj,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(hei/2, hei) << FRACBITS,
MT_SMOKE);
if (!P_MobjWasRemoved(particle))
{
P_SetObjectMomZ(particle, 2 << FRACBITS, false);
particle->momz += mobj->momz;
}
}
P_Boss2Thinker(mobj);
break;
case MT_EGGMOBILE3:
if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
{
fixed_t rad = mobj->radius >> FRACBITS;
fixed_t hei = mobj->height >> FRACBITS;
mobj_t *particle = P_SpawnMobjFromMobj(mobj,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(hei/2, hei) << FRACBITS,
MT_SMOKE);
if (!P_MobjWasRemoved(particle))
{
P_SetObjectMomZ(particle, 2 << FRACBITS, false);
particle->momz += mobj->momz;
}
}
P_Boss3Thinker(mobj);
break;
case MT_EGGMOBILE4:
if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
{
fixed_t rad = mobj->radius >> FRACBITS;
fixed_t hei = mobj->height >> FRACBITS;
mobj_t* particle = P_SpawnMobjFromMobj(mobj,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(rad, -rad) << FRACBITS,
P_RandomRange(hei/2, hei) << FRACBITS,
MT_SMOKE);
if (!P_MobjWasRemoved(particle))
{
P_SetObjectMomZ(particle, 2 << FRACBITS, false);
particle->momz += mobj->momz;
}
}
P_Boss4Thinker(mobj);
break;
case MT_FANG:
P_Boss5Thinker(mobj);
break;
case MT_BLACKEGGMAN:
P_Boss7Thinker(mobj);
break;
case MT_METALSONIC_BATTLE:
P_Boss9Thinker(mobj);
break;
default: // Generic SOC-made boss
if (mobj->flags2 & MF2_SKULLFLY)
P_SpawnGhostMobj(mobj);
P_GenericBossThinker(mobj);
break;
}
if (mobj->flags2 & MF2_BOSSFLEE)
{
if (mobj->extravalue1)
{
if (!(--mobj->extravalue1))
{
if (mobj->target)
{
mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y)), mobj->scale << 1);
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
}
else
mobj->momz = 8*mobj->scale;
}
else
mobj->angle += mobj->movedir;
}
else if (mobj->target)
P_InstaThrust(mobj, mobj->angle, FixedMul(12*FRACUNIT, mobj->scale));
}
if (mobj->type == MT_CYBRAKDEMON && !mobj->health)
{
if (!(mobj->tics & 1))
{
var1 = 2;
var2 = 0;
A_BossScream(mobj);
}
if (P_CheckDeathPitCollide(mobj))
{
P_RemoveMobj(mobj);
return false;
}
if (mobj->momz && mobj->z + mobj->momz <= mobj->floorz)
{
S_StartSound(mobj, sfx_befall);
if (mobj->state != states + S_CYBRAKDEMON_DIE8)
P_SetMobjState(mobj, S_CYBRAKDEMON_DIE8);
}
}
return true;
}
static boolean P_MobjDeadThink(mobj_t *mobj)
{
switch (mobj->type)
{
case MT_BLUESPHERE:
if ((mobj->tics >> 2) + 1 > 0 && (mobj->tics >> 2) + 1 <= tr_trans60) // tr_trans50 through tr_trans90, shifting once every second frame
mobj->frame = (NUMTRANSMAPS - ((mobj->tics >> 2) + 1)) << FF_TRANSSHIFT;
else // tr_trans60 otherwise
mobj->frame = tr_trans60 << FF_TRANSSHIFT;
break;
case MT_EGGCAPSULE:
if (mobj->z <= mobj->floorz)
{
P_RemoveMobj(mobj);
return false;
}
break;
case MT_FAKEMOBILE:
if (mobj->scale == mobj->destscale)
{
if (!mobj->fuse)
{
S_StartSound(mobj, sfx_s3k77);
mobj->flags2 |= MF2_DONTDRAW;
mobj->fuse = TICRATE;
}
return false;
}
if (!mobj->reactiontime)
{
if (P_RandomChance(FRACUNIT/2))
mobj->movefactor = FRACUNIT;
else
mobj->movefactor = -FRACUNIT;
if (P_RandomChance(FRACUNIT/2))
mobj->movedir = ANG20;
else
mobj->movedir = -ANG20;
mobj->reactiontime = 5;
}
mobj->momz += mobj->movefactor;
mobj->angle += mobj->movedir;
P_InstaThrust(mobj, mobj->angle, -mobj->info->speed);
mobj->reactiontime--;
break;
case MT_EGGSHIELD:
mobj->flags2 ^= MF2_DONTDRAW;
break;
case MT_EGGTRAP: // Egg Capsule animal release
if (mobj->fuse > 0)// && mobj->fuse < TICRATE-(TICRATE/7))
{
INT32 i;
fixed_t x, y, z;
fixed_t ns;
mobj_t* mo2;
mobj_t* flicky;
z = mobj->subsector->sector->floorheight + FRACUNIT + (P_RandomKey(64) << FRACBITS);
for (i = 0; i < 3; i++)
{
const angle_t fa = P_RandomKey(FINEANGLES) & FINEMASK;
ns = 64*FRACUNIT;
x = mobj->x + FixedMul(FINESINE(fa), ns);
y = mobj->y + FixedMul(FINECOSINE(fa), ns);
mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
if (P_MobjWasRemoved(mo2))
continue;
P_SetMobjStateNF(mo2, S_XPLD_EGGTRAP); // so the flickies don't lose their target if they spawn
ns = 4*FRACUNIT;
mo2->momx = FixedMul(FINESINE(fa), ns);
mo2->momy = FixedMul(FINECOSINE(fa), ns);
mo2->angle = fa << ANGLETOFINESHIFT;
if (!i && !(mobj->fuse & 2))
S_StartSound(mo2, mobj->info->deathsound);
flicky = P_InternalFlickySpawn(mo2, 0, 8*FRACUNIT, false, -1);
if (!flicky)
break;
P_SetTarget(&flicky->target, mo2);
flicky->momx = mo2->momx;
flicky->momy = mo2->momy;
}
mobj->fuse--;
}
break;
case MT_PLAYER:
/// \todo Have the player's dead body completely finish its animation even if they've already respawned.
if (!mobj->fuse)
{ // Go away.
/// \todo Actually go ahead and remove mobj completely, and fix any bugs and crashes doing this creates. Chasecam should stop moving, and F12 should never return to it.
mobj->momz = 0;
if (mobj->player)
mobj->flags2 |= MF2_DONTDRAW;
else // safe to remove, nobody's going to complain!
{
P_RemoveMobj(mobj);
return false;
}
}
else // Apply gravity to fall downwards.
{
if (mobj->player && !(mobj->fuse % 8) && (mobj->player->charflags & SF_MACHINE))
{
fixed_t r = mobj->radius >> FRACBITS;
mobj_t *explosion = P_SpawnMobj(
mobj->x + (P_RandomRange(r, -r) << FRACBITS),
mobj->y + (P_RandomRange(r, -r) << FRACBITS),
mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
MT_SONIC3KBOSSEXPLODE);
if (!P_MobjWasRemoved(explosion))
S_StartSound(explosion, sfx_s3kb4);
}
if (mobj->movedir == DMG_DROWNED)
P_SetObjectMomZ(mobj, -FRACUNIT/2, true); // slower fall from drowning
else
P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
}
break;
case MT_METALSONIC_RACE:
{
if (!(mobj->fuse % 8))
{
fixed_t r = mobj->radius >> FRACBITS;
mobj_t *explosion = P_SpawnMobj(
mobj->x + (P_RandomRange(r, -r) << FRACBITS),
mobj->y + (P_RandomRange(r, -r) << FRACBITS),
mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
MT_SONIC3KBOSSEXPLODE);
if (!P_MobjWasRemoved(explosion))
S_StartSound(explosion, sfx_s3kb4);
}
P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
}
break;
default:
break;
}
return true;
}
// Angle-to-tracer to trigger a linedef exec
// See Linedef Exec 457 (Track mobj angle to point)
static void P_TracerAngleThink(mobj_t *mobj)
{
angle_t looking;
angle_t ang;
if (!mobj->tracer)
return;
if (!mobj->extravalue2)
return;
// mobj->lastlook - Don't disable behavior after first failure
// mobj->extravalue1 - Angle tolerance
// mobj->extravalue2 - Exec tag upon failure
// mobj->cvval - Allowable failure delay
// mobj->cvmem - Failure timer
if (mobj->player)
looking = ( mobj->player->cmd.angleturn << 16 );/* fixes CS_LMAOGALOG */
else
looking = mobj->angle;
ang = looking - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
// \todo account for distance between mobj and tracer
// Because closer mobjs can be facing beyond the angle tolerance
// yet tracer is still in the camera view
// failure state: mobj is not facing tracer
// Reasaonable defaults: ANGLE_67h, ANGLE_292h
if (ang >= (angle_t)mobj->extravalue1 && ang <= ANGLE_MAX - (angle_t)mobj->extravalue1)
{
if (mobj->cvmem)
mobj->cvmem--;
else
{
INT32 exectag = mobj->extravalue2; // remember this before we erase the values
if (mobj->lastlook)
mobj->cvmem = mobj->cusval; // reset timer for next failure
else
{
// disable after first failure
mobj->eflags &= ~MFE_TRACERANGLE;
mobj->lastlook = mobj->extravalue1 = mobj->extravalue2 = mobj->cvmem = mobj->cusval = 0;
}
P_LinedefExecute(exectag, mobj, NULL);
}
}
else
mobj->cvmem = mobj->cusval; // reset failure timer
}
static void P_ArrowThink(mobj_t *mobj)
{
if (mobj->flags & MF_MISSILE)
{
// Calculate the angle of movement.
/*
momz
/ |
/ |
/ |
0------dist(momx,momy)
*/
fixed_t dist = P_AproxDistance(mobj->momx, mobj->momy);
angle_t angle = R_PointToAngle2(0, 0, dist, mobj->momz);
if (angle > ANG20 && angle <= ANGLE_180)
mobj->frame = 2;
else if (angle < ANG340 && angle > ANGLE_180)
mobj->frame = 0;
else
mobj->frame = 1;
if (!(mobj->extravalue1) && (mobj->momz < 0))
{
mobj->extravalue1 = 1;
S_StartSound(mobj, mobj->info->activesound);
}
if (leveltime & 1)
{
mobj_t *dust = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_PARTICLE);
if (!P_MobjWasRemoved(dust))
{
dust->tics = 18;
dust->destscale = FRACUNIT/32;
dust->scalespeed = FRACUNIT/16;
}
}
}
else
mobj->flags2 ^= MF2_DONTDRAW;
}
static void P_BumbleboreThink(mobj_t *mobj)
{
statenum_t st = mobj->state - states;
if (st == S_BUMBLEBORE_FLY1 || st == S_BUMBLEBORE_FLY2)
{
if (!mobj->target)
P_SetMobjState(mobj, mobj->info->spawnstate);
else if (P_MobjFlip(mobj)*((mobj->z + (mobj->height >> 1)) - (mobj->target->z + (mobj->target->height >> 1))) > 0
&& R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) <= 32*FRACUNIT)
{
mobj->momx >>= 1;
mobj->momy >>= 1;
if (++mobj->movefactor == 4)
{
S_StartSound(mobj, mobj->info->seesound);
mobj->momx = mobj->momy = mobj->momz = 0;
mobj->flags = (mobj->flags|MF_PAIN) & ~MF_NOGRAVITY;
P_SetMobjState(mobj, mobj->info->meleestate);
}
}
else
mobj->movefactor = 0;
}
else if (st == S_BUMBLEBORE_RAISE || st == S_BUMBLEBORE_FALL2) // no _FALL1 because it's an 0-tic
{
if (P_IsObjectOnGround(mobj))
{
S_StopSound(mobj);
S_StartSound(mobj, mobj->info->attacksound);
mobj->flags = (mobj->flags | MF_NOGRAVITY) & ~MF_PAIN;
mobj->momx = mobj->momy = mobj->momz = 0;
P_SetMobjState(mobj, mobj->info->painstate);
}
else
{
mobj->angle += ANGLE_22h;
mobj->frame = mobj->state->frame + ((mobj->tics & 2) >> 1);
}
}
else if (st == S_BUMBLEBORE_STUCK2 && mobj->tics < TICRATE)
mobj->frame = mobj->state->frame + ((mobj->tics & 2) >> 1);
}
static boolean P_HangsterThink(mobj_t *mobj)
{
statenum_t st = mobj->state - states;
//ghost image trail when flying down
if (st == S_HANGSTER_SWOOP1 || st == S_HANGSTER_SWOOP2)
{
P_SpawnGhostMobj(mobj);
//curve when in line with target, otherwise curve to avoid crashing into floor
if ((mobj->z - mobj->floorz <= 80*FRACUNIT) || (mobj->target && (mobj->z - mobj->target->z <= 80*FRACUNIT)))
P_SetMobjState(mobj, (st = S_HANGSTER_ARC1));
}
//swoop arc movement stuff
if (st == S_HANGSTER_ARC1)
{
A_FaceTarget(mobj);
P_Thrust(mobj, mobj->angle, 1*FRACUNIT);
}
else if (st == S_HANGSTER_ARC2)
P_Thrust(mobj, mobj->angle, 2*FRACUNIT);
else if (st == S_HANGSTER_ARC3)
P_Thrust(mobj, mobj->angle, 4*FRACUNIT);
//if movement has stopped while flying (like hitting a wall), fly up immediately
else if (st == S_HANGSTER_FLY1 && !mobj->momx && !mobj->momy)
{
mobj->extravalue1 = 0;
P_SetMobjState(mobj, S_HANGSTER_ARCUP1);
}
//after swooping back up, check for ceiling
else if ((st == S_HANGSTER_RETURN1 || st == S_HANGSTER_RETURN2) && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height))
{
P_SetMobjState(mobj, (st = S_HANGSTER_RETURN3));
mobj->momx = mobj->momy = 0;
}
//should you roost on a ceiling with F_SKY1 as its flat, disappear forever
if (st == S_HANGSTER_RETURN3 && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height)
&& mobj->subsector->sector->ceilingpic == skyflatnum
&& mobj->subsector->sector->ceilingheight == mobj->ceilingz)
{
P_RemoveMobj(mobj);
return false;
}
return true;
}
static boolean P_JetFume1Think(mobj_t *mobj)
{
fixed_t jetx, jety;
if (!mobj->target // if you have no target
|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
{ // then remove yourself as well!
P_RemoveMobj(mobj);
return false;
}
jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
if (mobj->fuse == 56) // First one
{
P_UnsetThingPosition(mobj);
mobj->x = jetx;
mobj->y = jety;
if (mobj->target->eflags & MFE_VERTICALFLIP)
mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale);
else
mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale);
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z + mobj->height;
P_SetThingPosition(mobj);
}
else if (mobj->fuse == 57)
{
P_UnsetThingPosition(mobj);
mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle - ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle - ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
if (mobj->target->eflags & MFE_VERTICALFLIP)
mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
else
mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z + mobj->height;
P_SetThingPosition(mobj);
}
else if (mobj->fuse == 58)
{
P_UnsetThingPosition(mobj);
mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle + ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle + ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
if (mobj->target->eflags & MFE_VERTICALFLIP)
mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
else
mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z + mobj->height;
P_SetThingPosition(mobj);
}
else if (mobj->fuse == 59)
{
boolean dashmod = ((mobj->target->flags & MF_PAIN) && (mobj->target->health <= mobj->target->info->damage));
jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius);
jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius);
P_UnsetThingPosition(mobj);
mobj->x = jetx;
mobj->y = jety;
mobj->destscale = mobj->target->scale;
if (!(dashmod && mobj->target->state == states + S_METALSONIC_BOUNCE))
{
mobj->destscale = (mobj->destscale + FixedDiv(R_PointToDist2(0, 0, mobj->target->momx, mobj->target->momy), 36*mobj->target->scale))/3;
}
if (mobj->target->eflags & MFE_VERTICALFLIP)
mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2;
else
mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2;
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z + mobj->height;
P_SetThingPosition(mobj);
if (dashmod)
{
mobj->color = SKINCOLOR_SUNSET;
if (mobj->target->movecount == 3 && !mobj->target->reactiontime && (mobj->target->movedir == 0 || mobj->target->movedir == 2))
P_SpawnGhostMobj(mobj);
}
else
mobj->color = SKINCOLOR_ICY;
}
mobj->fuse++;
return true;
}
static boolean P_EggRobo1Think(mobj_t *mobj)
{
#define SPECTATORRADIUS (96*mobj->scale)
if (!(mobj->flags2 & MF2_STRONGBOX))
{
mobj->cusval = mobj->x; // eat my SOCs, p_mobj.h warning, we have lua now
mobj->cvmem = mobj->y; // ditto
mobj->movedir = mobj->angle;
mobj->threshold = P_MobjFlip(mobj)*10*mobj->scale;
if (mobj->threshold < 0)
mobj->threshold += (mobj->ceilingz - mobj->height);
else
mobj->threshold += mobj->floorz;
var1 = 4;
A_BossJetFume(mobj);
mobj->flags2 |= MF2_STRONGBOX;
}
if (mobj->state == &states[mobj->info->deathstate]) // todo: make map actually set health to 0 for these
{
if (mobj->movecount)
{
if (!(--mobj->movecount))
S_StartSound(mobj, mobj->info->deathsound);
}
else
{
mobj->momz += P_MobjFlip(mobj)*mobj->scale;
if (mobj->momz > 0)
{
if (mobj->z + mobj->momz > mobj->ceilingz + (1000 << FRACBITS))
{
P_RemoveMobj(mobj);
return false;
}
}
else if (mobj->z + mobj->height + mobj->momz < mobj->floorz - (1000 << FRACBITS))
{
P_RemoveMobj(mobj);
return false;
}
}
}
else
{
fixed_t basex = mobj->cusval, basey = mobj->cvmem;
if (mobj->spawnpoint && mobj->spawnpoint->args[0] != TMED_NONE)
{
angle_t sideang = mobj->movedir + ((mobj->spawnpoint->args[0] == TMED_LEFT) ? ANGLE_90 : -ANGLE_90);
fixed_t oscillate = FixedMul(FINESINE(((leveltime * ANG1) >> (ANGLETOFINESHIFT + 2)) & FINEMASK), 250*mobj->scale);
basex += P_ReturnThrustX(mobj, sideang, oscillate);
basey += P_ReturnThrustY(mobj, sideang, oscillate);
}
mobj->z = mobj->threshold + FixedMul(FINESINE(((leveltime + mobj->movecount)*ANG2 >> (ANGLETOFINESHIFT - 2)) & FINEMASK), 8*mobj->scale);
if (mobj->state != &states[mobj->info->meleestate])
{
boolean didmove = false;
if (mobj->state == &states[mobj->info->spawnstate])
{
UINT8 i;
fixed_t dist = INT32_MAX;
for (i = 0; i < MAXPLAYERS; i++)
{
fixed_t compdist;
if (!playeringame[i])
continue;
if (players[i].spectator)
continue;
if (!players[i].mo)
continue;
if (!players[i].mo->health)
continue;
if (P_PlayerInPain(&players[i]))
continue;
if (players[i].mo->z > mobj->z + mobj->height + 8*mobj->scale)
continue;
if (players[i].mo->z + players[i].mo->height < mobj->z - 8*mobj->scale)
continue;
compdist = P_AproxDistance(
players[i].mo->x + players[i].mo->momx - basex,
players[i].mo->y + players[i].mo->momy - basey);
if (compdist >= dist)
continue;
dist = compdist;
P_SetTarget(&mobj->target, players[i].mo);
}
if (dist < (SPECTATORRADIUS << 1))
{
didmove = true;
mobj->frame = 3 + ((leveltime & 2) >> 1);
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
if (P_AproxDistance(
mobj->x - basex,
mobj->y - basey)
< mobj->scale)
S_StartSound(mobj, mobj->info->seesound);
P_MoveOrigin(mobj,
(15*(mobj->x >> 4)) + (basex >> 4) + P_ReturnThrustX(mobj, mobj->angle, SPECTATORRADIUS >> 4),
(15*(mobj->y >> 4)) + (basey >> 4) + P_ReturnThrustY(mobj, mobj->angle, SPECTATORRADIUS >> 4),
mobj->z);
}
else
{
angle_t diff = (mobj->movedir - mobj->angle);
if (diff > ANGLE_180)
diff = InvAngle(InvAngle(diff)/8);
else
diff /= 8;
mobj->angle += diff;
dist = FINECOSINE(((leveltime + mobj->movecount)*ANG2 >> (ANGLETOFINESHIFT - 2)) & FINEMASK);
if (abs(dist) < FRACUNIT/2)
mobj->frame = 0;
else
mobj->frame = (dist > 0) ? 1 : 2;
}
}
if (!didmove)
{
if (P_AproxDistance(mobj->x - basex, mobj->y - basey) < mobj->scale)
P_MoveOrigin(mobj, basex, basey, mobj->z);
else
P_MoveOrigin(mobj,
(15*(mobj->x >> 4)) + (basex >> 4),
(15*(mobj->y >> 4)) + (basey >> 4),
mobj->z);
}
}
}
return true;
#undef SPECTATORRADIUS
}
static void P_NiGHTSDroneThink(mobj_t *mobj)
{
mobj_t *goalpost = NULL;
mobj_t *sparkle = NULL;
mobj_t *droneman = NULL;
boolean flip = mobj->flags2 & MF2_OBJECTFLIP;
boolean topaligned = (mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
boolean middlealigned = (mobj->flags & MF_GRENADEBOUNCE) && !(mobj->flags & MF_SLIDEME);
boolean bottomoffsetted = !(mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
boolean flipchanged = false;
fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
if (mobj->target && mobj->target->type == MT_NIGHTSDRONE_GOAL)
{
goalpost = mobj->target;
if (goalpost->target && goalpost->target->type == MT_NIGHTSDRONE_SPARKLING)
sparkle = goalpost->target;
if (goalpost->tracer && goalpost->tracer->type == MT_NIGHTSDRONE_MAN)
droneman = goalpost->tracer;
}
if (!goalpost || !sparkle || !droneman)
return;
// did NIGHTSDRONE position, scale, flip, or flags change? all elements need to be synced
droneboxmandiff = max(mobj->height - droneman->height, 0);
dronemangoaldiff = max(droneman->height - goalpost->height, 0);
if (!(goalpost->flags2 & MF2_OBJECTFLIP) && (mobj->flags2 & MF2_OBJECTFLIP))
{
goalpost->eflags |= MFE_VERTICALFLIP;
goalpost->flags2 |= MF2_OBJECTFLIP;
sparkle->eflags |= MFE_VERTICALFLIP;
sparkle->flags2 |= MF2_OBJECTFLIP;
droneman->eflags |= MFE_VERTICALFLIP;
droneman->flags2 |= MF2_OBJECTFLIP;
flipchanged = true;
}
else if ((goalpost->flags2 & MF2_OBJECTFLIP) && !(mobj->flags2 & MF2_OBJECTFLIP))
{
goalpost->eflags &= ~MFE_VERTICALFLIP;
goalpost->flags2 &= ~MF2_OBJECTFLIP;
sparkle->eflags &= ~MFE_VERTICALFLIP;
sparkle->flags2 &= ~MF2_OBJECTFLIP;
droneman->eflags &= ~MFE_VERTICALFLIP;
droneman->flags2 &= ~MF2_OBJECTFLIP;
flipchanged = true;
}
if (goalpost->destscale != mobj->destscale
|| goalpost->movefactor != mobj->z
|| goalpost->friction != mobj->height
|| flipchanged
|| goalpost->threshold != (INT32)(mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE)))
{
goalpost->destscale = sparkle->destscale = droneman->destscale = mobj->destscale;
// straight copy-pasta from P_SpawnMapThing, case MT_NIGHTSDRONE
if (!flip)
{
if (topaligned) // Align droneman to top of hitbox
{
dronemanoffset = droneboxmandiff;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
}
else if (middlealigned) // Align droneman to center of hitbox
{
dronemanoffset = droneboxmandiff/2;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
}
else if (bottomoffsetted)
{
dronemanoffset = 24*FRACUNIT;
goaloffset = dronemangoaldiff + dronemanoffset;
}
else
{
dronemanoffset = 0;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
}
sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
}
else
{
if (topaligned) // Align droneman to top of hitbox
{
dronemanoffset = 0;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
}
else if (middlealigned) // Align droneman to center of hitbox
{
dronemanoffset = droneboxmandiff/2;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
}
else if (bottomoffsetted)
{
dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
goaloffset = dronemangoaldiff + dronemanoffset;
}
else
{
dronemanoffset = droneboxmandiff;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
}
sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
}
P_MoveOrigin(goalpost, mobj->x, mobj->y, mobj->z + goaloffset);
P_MoveOrigin(sparkle, mobj->x, mobj->y, mobj->z + sparkleoffset);
if (goalpost->movefactor != mobj->z || goalpost->friction != mobj->height)
{
P_MoveOrigin(droneman, mobj->x, mobj->y, mobj->z + dronemanoffset);
goalpost->movefactor = mobj->z;
goalpost->friction = mobj->height;
}
goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
}
else
{
if (goalpost->x != mobj->x || goalpost->y != mobj->y)
{
P_MoveOrigin(goalpost, mobj->x, mobj->y, goalpost->z);
P_MoveOrigin(sparkle, mobj->x, mobj->y, sparkle->z);
}
if (droneman->x != mobj->x || droneman->y != mobj->y)
P_MoveOrigin(droneman, mobj->x, mobj->y,
droneman->z >= mobj->floorz && droneman->z <= mobj->ceilingz ? droneman->z : mobj->z);
}
// now toggle states!
// GOAL mode?
if (sparkle->state >= &states[S_NIGHTSDRONE_SPARKLING1] && sparkle->state <= &states[S_NIGHTSDRONE_SPARKLING16])
{
INT32 i;
boolean bonustime = false;
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
{
bonustime = true;
break;
}
if (!bonustime)
{
CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
if (goalpost && goalpost->state != &states[S_INVISIBLE])
P_SetMobjState(goalpost, S_INVISIBLE);
if (sparkle && sparkle->state != &states[S_INVISIBLE])
P_SetMobjState(sparkle, S_INVISIBLE);
}
}
// Invisible/bouncing mode.
else
{
INT32 i;
boolean bonustime = false;
fixed_t zcomp;
// Bouncy bouncy!
if (!flip)
{
if (topaligned)
zcomp = droneboxmandiff + mobj->z;
else if (middlealigned)
zcomp = (droneboxmandiff/2) + mobj->z;
else if (bottomoffsetted)
zcomp = mobj->z + FixedMul(24*FRACUNIT, mobj->scale);
else
zcomp = mobj->z;
}
else
{
if (topaligned)
zcomp = mobj->z;
else if (middlealigned)
zcomp = (droneboxmandiff/2) + mobj->z;
else if (bottomoffsetted)
zcomp = mobj->z + droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
else
zcomp = mobj->z + droneboxmandiff;
}
droneman->angle += ANG10;
if (!flip && droneman->z <= zcomp)
droneman->momz = FixedMul(5*FRACUNIT, droneman->scale);
else if (flip && droneman->z >= zcomp)
droneman->momz = FixedMul(-5*FRACUNIT, droneman->scale);
// state switching logic
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
{
bonustime = true;
break;
}
if (bonustime)
{
CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
if (!(droneman->flags2 & MF2_DONTDRAW))
droneman->flags2 |= MF2_DONTDRAW;
if (goalpost->state == &states[S_INVISIBLE])
P_SetMobjState(goalpost, mobjinfo[goalpost->type].meleestate);
if (sparkle->state == &states[S_INVISIBLE])
P_SetMobjState(sparkle, mobjinfo[sparkle->type].meleestate);
}
else if (!G_IsSpecialStage(gamemap))
{
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
{
bonustime = true; // variable reuse
break;
}
if (bonustime)
{
// show droneman if at least one player is non-nights
if (goalpost->state != &states[S_INVISIBLE])
P_SetMobjState(goalpost, S_INVISIBLE);
if (sparkle->state != &states[S_INVISIBLE])
P_SetMobjState(sparkle, S_INVISIBLE);
if (droneman->state != &states[mobjinfo[droneman->type].meleestate])
P_SetMobjState(droneman, mobjinfo[droneman->type].meleestate);
if (droneman->flags2 & MF2_DONTDRAW)
droneman->flags2 &= ~MF2_DONTDRAW;
}
else
{
// else, hide it
if (!(droneman->flags2 & MF2_DONTDRAW))
droneman->flags2 |= MF2_DONTDRAW;
}
}
}
}
static boolean P_TurretThink(mobj_t *mobj)
{
P_MobjCheckWater(mobj);
P_CheckPosition(mobj, mobj->x, mobj->y);
if (P_MobjWasRemoved(mobj))
return false;
mobj->floorz = tmfloorz;
mobj->ceilingz = tmceilingz;
mobj->floorrover = tmfloorrover;
mobj->ceilingrover = tmceilingrover;
if ((mobj->eflags & MFE_UNDERWATER) && mobj->health > 0)
{
P_SetMobjState(mobj, mobj->info->deathstate);
mobj->health = 0;
mobj->flags2 &= ~MF2_FIRING;
}
else if (mobj->health > 0 && mobj->z + mobj->height > mobj->ceilingz) // Crushed
{
INT32 i, j;
fixed_t ns;
fixed_t x, y, z;
mobj_t* mo2;
z = mobj->subsector->sector->floorheight + FixedMul(64*FRACUNIT, mobj->scale);
for (j = 0; j < 2; j++)
{
for (i = 0; i < 32; i++)
{
const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
ns = FixedMul(64*FRACUNIT, mobj->scale);
x = mobj->x + FixedMul(FINESINE(fa), ns);
y = mobj->y + FixedMul(FINECOSINE(fa), ns);
mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
if (P_MobjWasRemoved(mo2))
continue;
ns = FixedMul(16*FRACUNIT, mobj->scale);
mo2->momx = FixedMul(FINESINE(fa), ns);
mo2->momy = FixedMul(FINECOSINE(fa), ns);
}
z -= FixedMul(32*FRACUNIT, mobj->scale);
}
P_SetMobjState(mobj, mobj->info->deathstate);
mobj->health = 0;
mobj->flags2 &= ~MF2_FIRING;
}
return true;
}
static void P_SaloonDoorThink(mobj_t *mobj)
{
fixed_t x = mobj->tracer->x;
fixed_t y = mobj->tracer->y;
fixed_t z = mobj->tracer->z;
angle_t oang = FixedAngle(mobj->extravalue1);
angle_t fa = (oang >> ANGLETOFINESHIFT) & FINEMASK;
fixed_t c0 = -96*FINECOSINE(fa);
fixed_t s0 = -96*FINESINE(fa);
angle_t fma;
fixed_t c, s;
angle_t angdiff;
// Adjust angular speed
fixed_t da = AngleFixed(mobj->angle - oang);
if (da > 180*FRACUNIT)
da -= 360*FRACUNIT;
mobj->extravalue2 = 8*(mobj->extravalue2 - da/32)/9;
// Update angle
mobj->angle += FixedAngle(mobj->extravalue2);
angdiff = mobj->angle - FixedAngle(mobj->extravalue1);
if (angdiff > (ANGLE_90 - ANG2) && angdiff < ANGLE_180)
{
mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_90 - ANG2);
mobj->extravalue2 /= 2;
}
else if (angdiff < (ANGLE_270 + ANG2) && angdiff >= ANGLE_180)
{
mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_270 + ANG2);
mobj->extravalue2 /= 2;
}
// Update position
fma = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
c = 48*FINECOSINE(fma);
s = 48*FINESINE(fma);
P_MoveOrigin(mobj, x + c0 + c, y + s0 + s, z);
}
static void P_PyreFlyThink(mobj_t *mobj)
{
fixed_t hdist;
mobj->extravalue1 = (mobj->extravalue1 + 3) % 360;
mobj->z += FINESINE(((mobj->extravalue1 * ANG1) >> ANGLETOFINESHIFT) & FINEMASK);
if (!(mobj->flags2 & MF2_BOSSNOTRAP))
P_LookForPlayers(mobj, true, false, 1500*FRACUNIT);
if (!mobj->target)
return;
if (mobj->extravalue2 == 1)
P_PyreFlyBurn(mobj, 0, 20, MT_SMOKE, 4*FRACUNIT);
else if (mobj->extravalue2 == 2)
{
INT32 fireradius = min(100 - mobj->fuse, 52);
P_PyreFlyBurn(mobj, P_RandomRange(0, fireradius) << FRACBITS, 20, MT_FLAMEPARTICLE, 4*FRACUNIT);
P_PyreFlyBurn(mobj, fireradius*FRACUNIT, 40, MT_PYREFLY_FIRE, 0);
}
hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
if (hdist > 1500*FRACUNIT)
{
mobj->flags2 &= ~MF2_BOSSNOTRAP;
P_SetTarget(&mobj->target, NULL);
return;
}
if (!(mobj->flags2 & MF2_BOSSNOTRAP) && hdist <= 450*FRACUNIT)
mobj->flags2 |= MF2_BOSSNOTRAP;
if (!(mobj->flags2 & MF2_BOSSNOTRAP))
return;
if (hdist < 1000*FRACUNIT)
{
//Aim for player z position. If too close to floor/ceiling, aim just above/below them.
fixed_t destz = min(max(mobj->target->z, mobj->target->floorz + 70*FRACUNIT), mobj->target->ceilingz - 80*FRACUNIT - mobj->height);
fixed_t dist = P_AproxDistance(hdist, destz - mobj->z);
P_InstaThrust(mobj, R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y), 2*FRACUNIT);
mobj->momz = FixedMul(FixedDiv(destz - mobj->z, dist), 2*FRACUNIT);
}
else
{
mobj->momx = 0;
mobj->momy = 0;
mobj->momz = 0;
}
}
static void P_PterabyteThink(mobj_t *mobj)
{
if (mobj->extravalue1 & 4) // Cooldown after grabbing
{
if (mobj->movefactor)
mobj->movefactor--;
else
{
P_SetTarget(&mobj->target, NULL);
mobj->extravalue1 &= 3;
}
}
if ((mobj->extravalue1 & 3) == 0) // Hovering
{
fixed_t vdist, hdist, time;
fixed_t hspeed = 3*mobj->info->speed;
angle_t fa;
var1 = 1;
var2 = 0;
A_CapeChase(mobj);
if (mobj->target)
return; // Still carrying a player or in cooldown
P_LookForPlayers(mobj, true, false, 256*FRACUNIT);
if (!mobj->target)
return;
if (mobj->target->player->powers[pw_flashing])
{
P_SetTarget(&mobj->target, NULL);
return;
}
vdist = mobj->z - mobj->target->z - mobj->target->height;
if (P_MobjFlip(mobj)*vdist <= 0)
{
P_SetTarget(&mobj->target, NULL);
return;
}
hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
if (hdist > 450*FRACUNIT)
{
P_SetTarget(&mobj->target, NULL);
return;
}
P_SetMobjState(mobj, S_PTERABYTE_SWOOPDOWN);
mobj->extravalue1++;
S_StartSound(mobj, mobj->info->attacksound);
time = FixedDiv(hdist, hspeed);
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
fa = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
mobj->momx = FixedMul(FINECOSINE(fa), hspeed);
mobj->momy = FixedMul(FINESINE(fa), hspeed);
mobj->momz = -2*FixedDiv(vdist, time);
mobj->extravalue2 = -FixedDiv(mobj->momz, time); //Z accel
mobj->movecount = time >> FRACBITS;
mobj->reactiontime = mobj->movecount;
}
else if ((mobj->extravalue1 & 3) == 1) // Swooping
{
mobj->reactiontime--;
mobj->momz += mobj->extravalue2;
if (mobj->reactiontime)
return;
if (mobj->state - states == S_PTERABYTE_SWOOPDOWN)
{
P_SetMobjState(mobj, S_PTERABYTE_SWOOPUP);
mobj->reactiontime = mobj->movecount;
}
else if (mobj->state - states == S_PTERABYTE_SWOOPUP)
{
P_SetMobjState(mobj, S_PTERABYTE_FLY1);
mobj->extravalue1++;
if (mobj->target && mobj->target->tracer != mobj)
P_SetTarget(&mobj->target, NULL); // Failed to grab the target
mobj->momx = mobj->momy = mobj->momz = 0;
}
}
else // Returning
{
var1 = 2*mobj->info->speed;
var2 = 1;
A_HomingChase(mobj);
if (P_AproxDistance(mobj->x - mobj->tracer->x, mobj->y - mobj->tracer->y) <= mobj->info->speed)
{
mobj->extravalue1 -= 2;
mobj->momx = mobj->momy = mobj->momz = 0;
}
}
}
static void P_DragonbomberThink(mobj_t *mobj)
{
#define DRAGONTURNSPEED ANG2
mobj->movecount = (mobj->movecount + 9) % 360;
P_SetObjectMomZ(mobj, 4*FINESINE(((mobj->movecount*ANG1) >> ANGLETOFINESHIFT) & FINEMASK), false);
if (mobj->threshold > 0) // are we dropping mines?
{
mobj->threshold--;
if (mobj->threshold == 0) // if the timer hits 0, look for a mine to drop!
{
mobj_t *segment = mobj;
while (segment->tracer != NULL && !P_MobjWasRemoved(segment->tracer) && segment->tracer->state == &states[segment->tracer->info->spawnstate])
segment = segment->tracer;
if (segment != mobj) // found an unactivated segment?
{
mobj_t *mine = P_SpawnMobjFromMobj(segment, 0, 0, 0, segment->info->painchance);
if (!P_MobjWasRemoved(mine))
{
mine->angle = segment->angle;
P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1);
P_SetObjectMomZ(mine, -2*FRACUNIT, true);
S_StartSound(mine, mine->info->seesound);
}
P_SetMobjState(segment, segment->info->raisestate);
mobj->threshold = mobj->info->painchance;
}
}
}
if (mobj->target) // Are we chasing a player?
{
fixed_t dist = P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y);
if (dist > 2000*mobj->scale) // Not anymore!
P_SetTarget(&mobj->target, NULL);
else
{
fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->eflags & MFE_VERTICALFLIP ? -128*mobj->scale : (128*mobj->scale + mobj->target->height));
angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
if (diff > ANGLE_180)
mobj->angle -= DRAGONTURNSPEED;
else
mobj->angle += DRAGONTURNSPEED;
if (!mobj->threshold && dist < 512*mobj->scale) // Close enough to drop bombs
{
mobj->threshold = mobj->info->painchance;
}
mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
}
}
else // Can we find a player to chase?
{
if (mobj->tracer == NULL || mobj->tracer->state != &states[mobj->tracer->info->spawnstate]
|| !P_LookForPlayers(mobj, true, false, 2000*mobj->scale)) // if not, circle around the spawnpoint
{
if (!mobj->spawnpoint) // unless we don't have one, in which case uhhh just circle around wherever we currently are I guess??
mobj->angle += DRAGONTURNSPEED;
else
{
boolean flip = mobj->spawnpoint->options & MTF_OBJECTFLIP;
fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
fixed_t x = mobj->spawnpoint->x << FRACBITS;
fixed_t y = mobj->spawnpoint->y << FRACBITS;
fixed_t z = (flip ? P_GetSectorCeilingZAt : P_GetSectorFloorZAt)(R_PointInSubsector(x, y)->sector, x, y) + (flip ? -1 : 1)*(mobj->spawnpoint->z << FRACBITS);
angle_t diff = R_PointToAngle2(mobj->x, mobj->y, x, y) - mobj->angle;
if (diff > ANGLE_180)
mobj->angle -= DRAGONTURNSPEED;
else
mobj->angle += DRAGONTURNSPEED;
mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
}
}
}
P_InstaThrust(mobj, mobj->angle, FixedMul(mobj->info->speed, mobj->scale));
#undef DRAGONTURNSPEED
}
static mobj_t *pushmobj;
#define PUSH_FACTOR 7
static inline boolean PIT_PushThing(mobj_t *thing)
{
if (thing->eflags & MFE_PUSHED)
return false;
if (thing->player && thing->player->powers[pw_carry] == CR_ROPEHANG)
return false;
if (!pushmobj)
return false;
if (!pushmobj->spawnpoint)
return false;
// Allow this to affect pushable objects at some point?
if (thing->player && !(thing->flags & (MF_NOGRAVITY|MF_NOCLIP)))
{
INT32 dist;
INT32 speed;
INT32 sx = pushmobj->x;
INT32 sy = pushmobj->y;
INT32 sz = pushmobj->z;
fixed_t radius = pushmobj->spawnpoint->args[0] << FRACBITS;
if (pushmobj->spawnpoint->args[2] & TMPP_NOZFADE)
dist = P_AproxDistance(thing->x - sx, thing->y - sy);
else
{
// Make sure the Z is in range
if (thing->z < sz - radius || thing->z > sz + radius)
return false;
dist = P_AproxDistance(P_AproxDistance(thing->x - sx, thing->y - sy), thing->z - sz);
}
speed = (abs(pushmobj->spawnpoint->args[1]) - ((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.
// Written with bits and pieces of P_HomingAttack
if ((speed > 0) && (P_CheckSight(thing, pushmobj)))
{
fixed_t tmpmomx, tmpmomy, tmpmomz;
if (pushmobj->spawnpoint->args[2] & TMPP_PUSHZ)
{
tmpmomx = FixedMul(FixedDiv(sx - thing->x, dist), speed);
tmpmomy = FixedMul(FixedDiv(sy - thing->y, dist), speed);
tmpmomz = FixedMul(FixedDiv(sz - thing->z, dist), speed);
}
else
{
angle_t pushangle = R_PointToAngle2(thing->x, thing->y, sx, sy) >> ANGLETOFINESHIFT;
tmpmomx = FixedMul(speed, FINECOSINE(pushangle));
tmpmomy = FixedMul(speed, FINESINE(pushangle));
tmpmomz = 0;
}
if (pushmobj->spawnpoint->args[1] > 0) // away!
{
tmpmomx *= -1;
tmpmomy *= -1;
tmpmomz *= -1;
}
thing->momx += tmpmomx;
thing->momy += tmpmomy;
thing->momz += tmpmomz;
if (thing->player)
{
thing->player->cmomx += tmpmomx;
thing->player->cmomy += tmpmomy;
thing->player->cmomx = FixedMul(thing->player->cmomx, 0xe800);
thing->player->cmomy = FixedMul(thing->player->cmomy, 0xe800);
}
}
}
if (!(pushmobj->spawnpoint->args[2] & TMPP_NONEXCLUSIVE))
thing->eflags |= MFE_PUSHED;
return true;
}
static void P_PointPushThink(mobj_t *mobj)
{
INT32 xl, xh, yl, yh;
fixed_t radius;
if (!mobj->spawnpoint)
return;
// Seek out all pushable things within the force radius of this
// point pusher. Crosses sectors, so use blockmap.
radius = mobj->spawnpoint->args[0] << FRACBITS;
pushmobj = mobj;
xl = (unsigned)(mobj->x - radius - bmaporgx)>>MAPBLOCKSHIFT;
xh = (unsigned)(mobj->x + radius - bmaporgx)>>MAPBLOCKSHIFT;
yl = (unsigned)(mobj->y - radius - bmaporgy)>>MAPBLOCKSHIFT;
yh = (unsigned)(mobj->y + radius - bmaporgy)>>MAPBLOCKSHIFT;
P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_PushThing);
}
static boolean P_MobjRegularThink(mobj_t *mobj)
{
if ((mobj->flags & MF_ENEMY) && (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1))
mobj->flags2 &= ~MF2_FRET;
if (mobj->eflags & MFE_TRACERANGLE)
P_TracerAngleThink(mobj);
switch (mobj->type)
{
case MT_WALLSPIKEBASE:
if (!mobj->target)
{
P_RemoveMobj(mobj);
return false;
}
mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
#if 0
if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
{
mobj_t* target = mobj->target; // shortcut
const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
P_UnsetThingPosition(mobj);
mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
P_SetThingPosition(mobj);
mobj->angle = target->angle + ANGLE_90;
}
#endif
break;
case MT_FALLINGROCK:
// Despawn rocks here in case zmovement code can't do so (blame slopes)
if (!mobj->momx && !mobj->momy && !mobj->momz
&& ((mobj->eflags & MFE_VERTICALFLIP) ?
mobj->z + mobj->height >= mobj->ceilingz
: mobj->z <= mobj->floorz))
{
P_RemoveMobj(mobj);
return false;
}
P_MobjCheckWater(mobj);
break;
case MT_ARROW:
P_ArrowThink(mobj);
break;
case MT_EMERALDSPAWN:
if (mobj->threshold)
{
mobj->threshold--;
if (!mobj->threshold && !mobj->target && mobj->reactiontime)
{
mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime);
if (!P_MobjWasRemoved(emerald))
{
emerald->threshold = 42;
P_SetTarget(&mobj->target, emerald);
P_SetTarget(&emerald->target, mobj);
}
}
}
break;
case MT_BUGGLE:
mobj->eflags |= MFE_UNDERWATER; //P_MobjCheckWater(mobj); // solely for MFE_UNDERWATER for A_FlickySpawn
{
if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
&& P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius*16)
{
var1 = mobj->info->speed;
var2 = 1;
// Home in on the target.
A_HomingChase(mobj);
if (mobj->z < mobj->floorz)
mobj->z = mobj->floorz;
if (leveltime % mobj->info->painchance == 0)
S_StartSound(mobj, mobj->info->activesound);
if ((statenum_t)(mobj->state - states) != mobj->info->seestate)
P_SetMobjState(mobj, mobj->info->seestate);
}
else
{
// Try to find a player
P_LookForPlayers(mobj, true, true, mobj->radius*16);
mobj->momx >>= 1;
mobj->momy >>= 1;
mobj->momz >>= 1;
if ((statenum_t)(mobj->state - states) != mobj->info->spawnstate)
P_SetMobjState(mobj, mobj->info->spawnstate);
}
}
break;
case MT_BUMBLEBORE:
P_BumbleboreThink(mobj);
break;
case MT_BIGMINE:
mobj->extravalue1 += 3;
mobj->extravalue1 %= 360;
P_UnsetThingPosition(mobj);
mobj->z += FINESINE(mobj->extravalue1*(FINEMASK + 1)/360);
P_SetThingPosition(mobj);
break;
case MT_FLAME:
if (mobj->flags2 & MF2_BOSSNOTRAP)
{
if (!mobj->target || P_MobjWasRemoved(mobj->target))
{
if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
P_RemoveMobj(mobj->tracer);
P_RemoveMobj(mobj);
return false;
}
mobj->z = mobj->target->z + mobj->target->momz;
if (!(mobj->eflags & MFE_VERTICALFLIP))
mobj->z += mobj->target->height;
}
if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
{
mobj->tracer->z = mobj->z + P_MobjFlip(mobj)*20*mobj->scale;
if (mobj->eflags & MFE_VERTICALFLIP)
mobj->tracer->z += mobj->height;
}
break;
case MT_WAVINGFLAG1:
case MT_WAVINGFLAG2:
{
fixed_t base = (leveltime << (FRACBITS + 1));
mobj_t *seg = mobj->tracer, *prev = mobj;
mobj->movedir = mobj->angle
+ ((((FINESINE((FixedAngle(base << 1) >> ANGLETOFINESHIFT) & FINEMASK)
+ FINESINE((FixedAngle(base << 4) >> ANGLETOFINESHIFT) & FINEMASK)) >> 1)
+ FINESINE((FixedAngle(base*9) >> ANGLETOFINESHIFT) & FINEMASK)
+ FINECOSINE(((FixedAngle(base*9)) >> ANGLETOFINESHIFT) & FINEMASK)) << 12); //*2^12
while (seg)
{
seg->movedir = seg->angle;
seg->angle = prev->movedir;
P_UnsetThingPosition(seg);
seg->x = prev->x + P_ReturnThrustX(prev, prev->angle, prev->radius);
seg->y = prev->y + P_ReturnThrustY(prev, prev->angle, prev->radius);
seg->z = prev->z + prev->height - (seg->scale >> 1);
P_SetThingPosition(seg);
prev = seg;
seg = seg->tracer;
}
}
break;
case MT_SPINCUSHION:
if (mobj->target && mobj->state - states >= S_SPINCUSHION_AIM1 && mobj->state - states <= S_SPINCUSHION_AIM5)
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
break;
case MT_CRUSHCLAW:
if (mobj->state - states == S_CRUSHCLAW_STAY && mobj->target)
{
mobj_t *chain = mobj->target->target;
SINT8 sign = ((mobj->tics & 1) ? mobj->tics : -(SINT8)(mobj->tics));
while (chain)
{
chain->z = chain->movefactor + sign*mobj->scale;
sign = -sign;
chain = chain->target;
}
}
break;
case MT_SMASHINGSPIKEBALL:
mobj->momx = mobj->momy = 0;
if (mobj->state - states == S_SMASHSPIKE_FALL && P_IsObjectOnGround(mobj))
{
P_SetMobjState(mobj, S_SMASHSPIKE_STOMP1);
S_StartSound(mobj, sfx_spsmsh);
}
else if (mobj->state - states == S_SMASHSPIKE_RISE2 && P_MobjFlip(mobj)*(mobj->z - mobj->movecount) >= 0)
{
mobj->momz = 0;
P_SetMobjState(mobj, S_SMASHSPIKE_FLOAT);
}
break;
case MT_HANGSTER:
if (!P_HangsterThink(mobj))
return false;
break;
case MT_LHRT:
mobj->momx = FixedMul(mobj->momx, mobj->extravalue2);
mobj->momy = FixedMul(mobj->momy, mobj->extravalue2);
break;
case MT_EGGCAPSULE:
if (!mobj->reactiontime)
{
// Target nearest player on your mare.
// (You can make it float up/down by adding MF_FLOAT,
// but beware level design pitfalls.)
fixed_t shortest = 1024*FRACUNIT;
INT32 i;
P_SetTarget(&mobj->target, NULL);
for (i = 0; i < MAXPLAYERS; i++)
if (playeringame[i] && players[i].mo
&& players[i].mare == mobj->threshold && players[i].spheres > 0)
{
fixed_t dist = P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y);
if (dist < shortest)
{
P_SetTarget(&mobj->target, players[i].mo);
shortest = dist;
}
}
}
break;
case MT_EGGMOBILE2_POGO:
if (!mobj->target
|| !mobj->target->health
|| mobj->target->state == &states[mobj->target->info->spawnstate]
|| mobj->target->state == &states[mobj->target->info->raisestate])
{
P_RemoveMobj(mobj);
return false;
}
P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z - mobj->height);
break;
case MT_HAMMER:
if (mobj->z <= mobj->floorz)
{
P_RemoveMobj(mobj);
return false;
}
break;
case MT_KOOPA:
P_KoopaThinker(mobj);
break;
case MT_FIREBALL:
if (P_AproxDistance(mobj->momx, mobj->momy) <= 16*FRACUNIT) // Once fireballs lose enough speed, kill them
{
P_KillMobj(mobj, NULL, NULL, 0);
return false;
}
break;
case MT_REDRING:
if (((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
&& mobj->flags & MF_MISSILE)
{
P_ExplodeMissile(mobj);
return false;
}
break;
case MT_BOSSFLYPOINT:
return false;
case MT_NIGHTSCORE:
mobj->color = linkColor[mobj->extravalue2][(leveltime + mobj->extravalue1) % NUMLINKCOLORS];
break;
case MT_JETFUME1:
if (!P_JetFume1Think(mobj))
return false;
break;
case MT_JETFLAME:
{
if (!mobj->target // if you have no target
|| (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
{ // then remove yourself as well!
P_RemoveMobj(mobj);
return false;
}
P_UnsetThingPosition(mobj);
mobj->x = mobj->target->x;
mobj->y = mobj->target->y;
mobj->z = mobj->target->z - 50*mobj->target->scale;
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z + mobj->height;
P_SetThingPosition(mobj);
}
break;
case MT_EGGROBO1:
if (!P_EggRobo1Think(mobj))
return false;
break;
case MT_EGGROBO1JET:
{
if (!mobj->target || P_MobjWasRemoved(mobj->target) // if you have no target
|| (mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
{ // then remove yourself as well!
P_RemoveMobj(mobj);
return false;
}
mobj->flags2 ^= MF2_DONTDRAW;
P_UnsetThingPosition(mobj);
mobj->x = mobj->target->x + P_ReturnThrustX(mobj, mobj->target->angle + ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustX(mobj, mobj->target->angle, 19*mobj->target->scale);
mobj->y = mobj->target->y + P_ReturnThrustY(mobj, mobj->target->angle + ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustY(mobj, mobj->target->angle, 19*mobj->target->scale);
mobj->z = mobj->target->z;
if (mobj->target->eflags & MFE_VERTICALFLIP)
mobj->z += (mobj->target->height - mobj->height);
mobj->floorz = mobj->z;
mobj->ceilingz = mobj->z + mobj->height;
P_SetThingPosition(mobj);
}
break;
case MT_NIGHTSDRONE:
P_NiGHTSDroneThink(mobj);
break;
case MT_PLAYER:
if (mobj->player)
P_PlayerMobjThinker(mobj);
return false;
case MT_SKIM:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);
// Keep Skim at water surface
if (mobj->z <= mobj->watertop)
{
mobj->flags |= MF_NOGRAVITY;
if (mobj->z < mobj->watertop)
{
if (mobj->watertop - mobj->z <= FixedMul(mobj->info->speed*FRACUNIT, mobj->scale))
mobj->z = mobj->watertop;
else
mobj->momz = FixedMul(mobj->info->speed*FRACUNIT, mobj->scale);
}
}
else
{
mobj->flags &= ~MF_NOGRAVITY;
if (mobj->z > mobj->watertop && mobj->z - mobj->watertop < FixedMul(MAXSTEPMOVE, mobj->scale))
mobj->z = mobj->watertop;
}
break;
case MT_RING:
case MT_REDTEAMRING:
case MT_BLUETEAMRING:
P_KillRingsInLava(mobj);
if (P_MobjWasRemoved(mobj))
return false;
/* FALLTHRU */
case MT_COIN:
case MT_BLUESPHERE:
case MT_BOMBSPHERE:
case MT_NIGHTSCHIP:
case MT_NIGHTSSTAR:
// No need to check water. Who cares?
P_RingThinker(mobj);
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
else if (mobj->type != MT_BOMBSPHERE) // prevent shields from attracting bomb spheres
A_AttractChase(mobj);
return false;
// Flung items
case MT_FLINGRING:
P_KillRingsInLava(mobj);
if (P_MobjWasRemoved(mobj))
return false;
/* FALLTHRU */
case MT_FLINGCOIN:
case MT_FLINGBLUESPHERE:
case MT_FLINGNIGHTSCHIP:
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
else
A_AttractChase(mobj);
break;
case MT_EMBLEM:
if (P_EmblemWasCollected(mobj->health - 1) || !P_CanPickupEmblem(&players[consoleplayer], mobj->health - 1))
mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
else
mobj->frame &= ~FF_TRANSMASK;
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
break;
case MT_SHELL:
if (mobj->threshold && mobj->threshold != TICRATE)
mobj->threshold--;
if (mobj->threshold >= TICRATE)
{
mobj->angle += ((mobj->movedir == 1) ? ANGLE_22h : ANGLE_337h);
P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), (mobj->info->speed*mobj->scale));
}
break;
case MT_TURRET:
if (!P_TurretThink(mobj))
return false;
break;
case MT_BLUEFLAG:
case MT_REDFLAG:
if (P_MobjTouchingSectorSpecialFlag(mobj, SSF_RETURNFLAG))
mobj->fuse = 1; // Return to base.
break;
case MT_SPINDUST: // Spindash dust
mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
//mobj->momz = mobj->momz+P_MobjFlip(mobj)/3; // no meaningful change in value to be frank
if (mobj->state >= &states[S_SPINDUST_BUBBLE1] && mobj->state <= &states[S_SPINDUST_BUBBLE4]) // bubble dust!
{
P_MobjCheckWater(mobj);
if (mobj->watertop != mobj->subsector->sector->floorheight - 1000*FRACUNIT
&& mobj->z + mobj->height >= mobj->watertop - 5*FRACUNIT)
mobj->flags2 |= MF2_DONTDRAW;
}
break;
case MT_TRAINDUSTSPAWNER:
if (leveltime % 5 == 0) {
mobj_t* traindust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PARTICLE);
if (P_MobjWasRemoved(traindust))
break;
traindust->flags = MF_SCENERY;
P_SetMobjState(traindust, S_TRAINDUST);
traindust->frame = P_RandomRange(0, 8)|FF_TRANS90;
traindust->angle = mobj->angle;
traindust->tics = TICRATE*4;
P_SetScale(traindust, FRACUNIT*6, true);
traindust->destscale = FRACUNIT*64;
traindust->scalespeed = FRACUNIT/24;
}
break;
case MT_TRAINSTEAMSPAWNER:
if (leveltime % 5 == 0) {
mobj_t *steam = P_SpawnMobj(mobj->x + FRACUNIT*P_SignedRandom()/2, mobj->y + FRACUNIT*P_SignedRandom()/2, mobj->z, MT_PARTICLE);
if (P_MobjWasRemoved(steam))
break;
P_SetMobjState(steam, S_TRAINSTEAM);
steam->frame = P_RandomRange(0, 1)|FF_TRANS90;
steam->tics = TICRATE*8;
P_SetScale(steam, FRACUNIT*16, true);
steam->destscale = FRACUNIT*64;
steam->scalespeed = FRACUNIT/8;
steam->momx = P_SignedRandom()*32;
steam->momy = -64*FRACUNIT;
steam->momz = 2*FRACUNIT;
}
break;
case MT_CANARIVORE_GAS:
{
fixed_t momz;
if (mobj->flags2 & MF2_AMBUSH)
{
mobj->momx = FixedMul(mobj->momx, 50*FRACUNIT/51);
mobj->momy = FixedMul(mobj->momy, 50*FRACUNIT/51);
break;
}
if (mobj->eflags & MFE_VERTICALFLIP)
{
if ((mobj->z + mobj->height + mobj->momz) <= mobj->ceilingz)
break;
}
else
{
if ((mobj->z + mobj->momz) >= mobj->floorz)
break;
}
momz = abs(mobj->momz);
if (R_PointToDist2(0, 0, mobj->momx, mobj->momy) < momz)
P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), momz);
mobj->flags2 |= MF2_AMBUSH;
break;
}
case MT_SALOONDOOR:
if (!mobj->tracer) // Door center is gone or not spawned?
{
P_RemoveMobj(mobj); // Die
return false;
}
P_SaloonDoorThink(mobj);
break;
case MT_MINECARTSPAWNER:
P_HandleMinecartSegments(mobj);
if (!mobj->fuse || mobj->fuse > TICRATE)
break;
if (mobj->fuse == 2)
{
mobj->fuse = 0;
break;
}
mobj->flags2 ^= MF2_DONTDRAW;
break;
case MT_LAVAFALLROCK:
if (P_IsObjectOnGround(mobj))
P_RemoveMobj(mobj);
break;
case MT_PYREFLY:
P_PyreFlyThink(mobj);
break;
case MT_PTERABYTE:
P_PterabyteThink(mobj);
break;
case MT_DRAGONBOMBER:
P_DragonbomberThink(mobj);
break;
case MT_MINUS:
if (P_IsObjectOnGround(mobj))
mobj->spriteroll = 0;
else
mobj->spriteroll = R_PointToAngle2(0, 0, P_MobjFlip(mobj)*mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
break;
case MT_PUSH:
P_PointPushThink(mobj);
break;
case MT_SPINFIRE:
if (mobj->flags & MF_NOGRAVITY)
{
if (mobj->eflags & MFE_VERTICALFLIP)
mobj->z = mobj->ceilingz - mobj->height;
else
mobj->z = mobj->floorz;
}
else if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= mobj->floorz)
|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz))
{
mobj->flags |= MF_NOGRAVITY;
mobj->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
mobj->momy = mobj->momz = 0;
mobj->z = ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->ceilingz - mobj->height : mobj->floorz);
}
/* FALLTHRU */
default:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);
// Extinguish fire objects in water
if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
&& (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
{
P_KillMobj(mobj, NULL, NULL, 0);
return false;
}
break;
}
return true;
}
static void P_FiringThink(mobj_t *mobj)
{
if (!mobj->target)
return;
if (mobj->health <= 0)
return;
if (mobj->state->action.acp1 == (actionf_p1)A_Boss1Laser)
{
if (mobj->state->tics > 1)
{
var1 = mobj->state->var1;
var2 = mobj->state->var2 & 65535;
mobj->state->action.acp1(mobj);
}
}
else if (leveltime & 1) // Fire mode
{
mobj_t *missile;
if (mobj->target->player && mobj->target->player->powers[pw_carry] == CR_NIGHTSMODE)
{
fixed_t oldval = mobjinfo[mobj->extravalue1].speed;
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx, mobj->target->y + mobj->target->momy);
mobjinfo[mobj->extravalue1].speed = FixedMul(60*FRACUNIT, mobj->scale);
missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
mobjinfo[mobj->extravalue1].speed = oldval;
}
else
{
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
}
if (missile)
{
if (mobj->flags2 & MF2_SUPERFIRE)
missile->flags2 |= MF2_SUPERFIRE;
if (mobj->info->attacksound)
S_StartSound(missile, mobj->info->attacksound);
}
}
else
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
}
static void P_MonitorFuseThink(mobj_t *mobj)
{
mobj_t *newmobj;
// Special case for ALL monitors outside of co-op.
// If a box's speed is nonzero, it's allowed to respawn as a WRM/SRM.
if (!G_CoopGametype() && mobj->info->speed != 0
&& (mobj->flags2 & (MF2_AMBUSH|MF2_STRONGBOX)))
{
mobjtype_t spawnchance[64];
INT32 numchoices = 0, i = 0;
// This define should make it a lot easier to organize and change monitor weights
#define SETMONITORCHANCES(type, strongboxamt, weakboxamt) \
for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) spawnchance[numchoices++] = type
// Type SRM WRM
SETMONITORCHANCES(MT_SNEAKERS_BOX, 0, 10); // Super Sneakers
SETMONITORCHANCES(MT_INVULN_BOX, 2, 0); // Invincibility
SETMONITORCHANCES(MT_WHIRLWIND_BOX, 3, 8); // Whirlwind Shield
SETMONITORCHANCES(MT_ELEMENTAL_BOX, 3, 8); // Elemental Shield
SETMONITORCHANCES(MT_ATTRACT_BOX, 2, 0); // Attraction Shield
SETMONITORCHANCES(MT_FORCE_BOX, 3, 3); // Force Shield
SETMONITORCHANCES(MT_ARMAGEDDON_BOX, 2, 0); // Armageddon Shield
SETMONITORCHANCES(MT_MIXUP_BOX, 0, 1); // Teleporters
SETMONITORCHANCES(MT_RECYCLER_BOX, 0, 1); // Recycler
SETMONITORCHANCES(MT_1UP_BOX, 1, 1); // 1-Up
// =======================================
// Total 16 32
#undef SETMONITORCHANCES
i = P_RandomKey(numchoices); // Gotta love those random numbers!
newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, spawnchance[i]);
}
else
newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
// Transfer flags2 (ambush, strongbox, objectflip)
if (!P_MobjWasRemoved(newmobj))
newmobj->flags2 = mobj->flags2;
P_RemoveMobj(mobj); // make sure they disappear
}
static void P_FlagFuseThink(mobj_t *mobj)
{
subsector_t *ss;
fixed_t x, y, z;
mobj_t* flagmo;
if (!mobj->spawnpoint)
return;
x = mobj->spawnpoint->x << FRACBITS;
y = mobj->spawnpoint->y << FRACBITS;
z = mobj->spawnpoint->z << FRACBITS;
ss = R_PointInSubsector(x, y);
if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
z = ss->sector->ceilingheight - mobjinfo[mobj->type].height - z;
else
z = ss->sector->floorheight + z;
flagmo = P_SpawnMobj(x, y, z, mobj->type);
if (P_MobjWasRemoved(flagmo))
return;
flagmo->spawnpoint = mobj->spawnpoint;
if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
{
flagmo->eflags |= MFE_VERTICALFLIP;
flagmo->flags2 |= MF2_OBJECTFLIP;
}
if (mobj->type == MT_REDFLAG)
{
if (!(mobj->flags2 & MF2_JUSTATTACKED))
CONS_Printf(M_GetText("The \205Red flag\200 has returned to base.\n"));
// Assumedly in splitscreen players will be on opposing teams
if (players[consoleplayer].ctfteam == 1 || splitscreen)
S_StartSound(NULL, sfx_hoop1);
else if (players[consoleplayer].ctfteam == 2)
S_StartSound(NULL, sfx_hoop3);
redflag = flagmo;
}
else // MT_BLUEFLAG
{
if (!(mobj->flags2 & MF2_JUSTATTACKED))
CONS_Printf(M_GetText("The \204Blue flag\200 has returned to base.\n"));
// Assumedly in splitscreen players will be on opposing teams
if (players[consoleplayer].ctfteam == 2 || splitscreen)
S_StartSound(NULL, sfx_hoop1);
else if (players[consoleplayer].ctfteam == 1)
S_StartSound(NULL, sfx_hoop3);
blueflag = flagmo;
}
}
static boolean P_FuseThink(mobj_t *mobj)
{
if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG)
mobj->flags2 ^= MF2_DONTDRAW;
mobj->fuse--;
if (mobj->fuse)
return true;
if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse)) || P_MobjWasRemoved(mobj))
;
else if (mobj->info->flags & MF_MONITOR)
{
P_MonitorFuseThink(mobj);
return false;
}
else switch (mobj->type)
{
// gargoyle and snowman handled in P_PushableThinker, not here
case MT_THROWNGRENADE:
case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
P_SetMobjState(mobj, mobj->info->deathstate);
break;
case MT_LHRT:
P_KillMobj(mobj, NULL, NULL, 0);
break;
case MT_BLUEFLAG:
case MT_REDFLAG:
P_FlagFuseThink(mobj);
P_RemoveMobj(mobj);
return false;
case MT_FANG:
if (mobj->flags2 & MF2_SLIDEPUSH)
{
var1 = 0;
var2 = 0;
A_BossDeath(mobj);
return false;
}
P_SetMobjState(mobj, mobj->state->nextstate);
if (P_MobjWasRemoved(mobj))
return false;
break;
case MT_METALSONIC_BATTLE:
break; // don't remove
case MT_SPIKE:
case MT_WALLSPIKE:
P_SetMobjState(mobj, mobj->state->nextstate);
if (P_MobjWasRemoved(mobj))
return false;
mobj->fuse = mobj->spawnpoint ? mobj->spawnpoint->args[0] : mobj->info->speed;
break;
case MT_NIGHTSCORE:
P_RemoveMobj(mobj);
return false;
case MT_LAVAFALL:
if (mobj->state - states == S_LAVAFALL_DORMANT)
{
mobj->fuse = 30;
P_SetMobjState(mobj, S_LAVAFALL_TELL);
S_StartSound(mobj, mobj->info->seesound);
}
else if (mobj->state - states == S_LAVAFALL_TELL)
{
mobj->fuse = 40;
P_SetMobjState(mobj, S_LAVAFALL_SHOOT);
S_StopSound(mobj);
S_StartSound(mobj, mobj->info->attacksound);
}
else
{
mobj->fuse = 30;
P_SetMobjState(mobj, S_LAVAFALL_DORMANT);
S_StopSound(mobj);
}
return false;
case MT_PYREFLY:
if (mobj->health <= 0)
break;
mobj->extravalue2 = (mobj->extravalue2 + 1) % 3;
if (mobj->extravalue2 == 0)
{
P_SetMobjState(mobj, mobj->info->spawnstate);
mobj->fuse = 100;
S_StopSound(mobj);
S_StartSound(mobj, sfx_s3k8c);
}
else if (mobj->extravalue2 == 1)
{
mobj->fuse = 50;
S_StartSound(mobj, sfx_s3ka3);
}
else
{
P_SetMobjState(mobj, mobj->info->meleestate);
mobj->fuse = 100;
S_StopSound(mobj);
S_StartSound(mobj, sfx_s3kc2l);
}
return false;
case MT_PLAYER:
break; // don't remove
default:
P_SetMobjState(mobj, mobj->info->xdeathstate); // will remove the mobj if S_NULL.
break;
// Looking for monitors? They moved to a special condition above.
}
return !P_MobjWasRemoved(mobj);
}
//
// P_MobjThinker
//
void P_MobjThinker(mobj_t *mobj)
{
I_Assert(mobj != NULL);
I_Assert(!P_MobjWasRemoved(mobj));
if (mobj->flags & MF_NOTHINK)
return;
if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->args[0])))
return;
// Remove dead target/tracer.
if (mobj->target && P_MobjWasRemoved(mobj->target))
P_SetTarget(&mobj->target, NULL);
if (mobj->tracer && P_MobjWasRemoved(mobj->tracer))
P_SetTarget(&mobj->tracer, NULL);
if (mobj->hnext && P_MobjWasRemoved(mobj->hnext))
P_SetTarget(&mobj->hnext, NULL);
if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
P_SetTarget(&mobj->hprev, NULL);
if (mobj->dontdrawforviewmobj && P_MobjWasRemoved(mobj->dontdrawforviewmobj))
P_SetTarget(&mobj->dontdrawforviewmobj, NULL);
mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG);
tmfloorthing = tmhitthing = NULL;
// Sector flag MSF_TRIGGERLINE_MOBJ allows ANY mobj to trigger a linedef exec
P_CheckMobjTrigger(mobj, false);
if (mobj->scale != mobj->destscale)
P_MobjScaleThink(mobj); // Slowly scale up/down to reach your destscale.
if ((mobj->type == MT_GHOST || mobj->type == MT_THOK) && mobj->fuse > 0) // Not guaranteed to be MF_SCENERY or not MF_SCENERY!
{
if (mobj->flags2 & MF2_BOSSNOTRAP) // "fast" flag
{
if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - (2*mobj->fuse)/3)
// fade out when nearing the end of fuse...
mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - (2*mobj->fuse)/3) << FF_TRANSSHIFT);
}
else
{
if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - mobj->fuse / 2)
// fade out when nearing the end of fuse...
mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - mobj->fuse / 2) << FF_TRANSSHIFT);
}
}
// Special thinker for scenery objects
if (mobj->flags & MF_SCENERY)
{
P_MobjSceneryThink(mobj);
return;
}
// Check for a Lua thinker first
if (!mobj->player)
{
if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker)) || P_MobjWasRemoved(mobj))
return;
}
else if (!mobj->player->spectator)
{
// You cannot short-circuit the player thinker like you can other thinkers.
LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker));
if (P_MobjWasRemoved(mobj))
return;
}
// if it's pushable, or if it would be pushable other than temporary disablement, use the
// separate thinker
if (mobj->flags & MF_PUSHABLE || (mobj->info->flags & MF_PUSHABLE && mobj->fuse))
{
if (!P_MobjPushableThink(mobj))
return;
}
else if (mobj->flags & MF_BOSS)
{
if (!P_MobjBossThink(mobj))
return;
}
else if (mobj->health <= 0) // Dead things think differently than the living.
{
if (!P_MobjDeadThink(mobj))
return;
}
else
{
if (!P_MobjRegularThink(mobj))
return;
}
if (P_MobjWasRemoved(mobj))
return;
if (mobj->flags2 & MF2_FIRING)
P_FiringThink(mobj);
if (mobj->flags & MF_AMBIENT)
{
if (leveltime % mobj->health)
return;
if (mobj->threshold)
S_StartSound(mobj, mobj->threshold);
return;
}
// Check fuse
if (mobj->fuse && !P_FuseThink(mobj))
return;
I_Assert(mobj != NULL);
I_Assert(!P_MobjWasRemoved(mobj));
if (mobj->momx || mobj->momy || (mobj->flags2 & MF2_SKULLFLY))
{
P_XYMovement(mobj);
if (P_MobjWasRemoved(mobj))
return;
}
// always do the gravity bit now, that's simpler
// BUT CheckPosition only if wasn't done before.
if (!(mobj->eflags & MFE_ONGROUND) || mobj->momz
|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz)
|| P_IsObjectInGoop(mobj))
{
if (!P_ZMovement(mobj))
return; // mobj was removed
P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
if (P_MobjWasRemoved(mobj))
return;
}
else
{
mobj->pmomz = 0; // to prevent that weird rocketing gargoyle bug
mobj->eflags &= ~MFE_JUSTHITFLOOR;
}
// Sliding physics for slidey mobjs!
if (mobj->type == MT_FLINGRING
|| mobj->type == MT_FLINGCOIN
|| mobj->type == MT_FLINGBLUESPHERE
|| mobj->type == MT_FLINGNIGHTSCHIP
|| P_WeaponOrPanel(mobj->type)
|| mobj->type == MT_FLINGEMERALD
|| mobj->type == MT_BIGTUMBLEWEED
|| mobj->type == MT_LITTLETUMBLEWEED
|| mobj->type == MT_CANNONBALLDECOR
|| mobj->type == MT_FALLINGROCK) {
P_TryMove(mobj, mobj->x, mobj->y, true); // Sets mo->standingslope correctly
if (P_MobjWasRemoved(mobj))
return;
//if (mobj->standingslope) CONS_Printf("slope physics on mobj\n");
P_ButteredSlope(mobj);
}
if (mobj->flags & (MF_ENEMY|MF_BOSS) && mobj->health
&& P_CheckDeathPitCollide(mobj)) // extra pit check in case these didn't have momz
{
P_KillMobj(mobj, NULL, NULL, DMG_DEATHPIT);
return;
}
// Crush enemies!
if (mobj->ceilingz - mobj->floorz < mobj->height)
{
if ((
(mobj->flags & (MF_ENEMY|MF_BOSS)
&& mobj->flags & MF_SHOOTABLE)
|| mobj->type == MT_EGGSHIELD)
&& !(mobj->flags & MF_NOCLIPHEIGHT)
&& mobj->health > 0)
{
P_KillMobj(mobj, NULL, NULL, DMG_CRUSHED);
return;
}
}
// Can end up here if a player dies.
if (mobj->player)
P_CyclePlayerMobjState(mobj);
else
P_CycleMobjState(mobj);
if (P_MobjWasRemoved(mobj))
return;
switch (mobj->type)
{
case MT_BOUNCEPICKUP:
case MT_RAILPICKUP:
case MT_AUTOPICKUP:
case MT_EXPLODEPICKUP:
case MT_SCATTERPICKUP:
case MT_GRENADEPICKUP:
if (mobj->health == 0) // Fading tile
{
// TODO: Maybe use mobj->alpha instead of messing with frame flags
INT32 value = mobj->info->damage/10;
value = mobj->fuse/value;
value = 10-value;
value--;
if (value <= 0)
value = 1;
mobj->frame &= ~FF_TRANSMASK;
mobj->frame |= value << FF_TRANSSHIFT;
}
break;
default:
break;
}
}
// Quick, optimized function for the Rail Rings
// Returns true if move failed or mobj was removed by movement (death pit, missile hits wall, etc.)
boolean P_RailThinker(mobj_t *mobj)
{
fixed_t x, y, z;
I_Assert(mobj != NULL);
I_Assert(!P_MobjWasRemoved(mobj));
x = mobj->x, y = mobj->y, z = mobj->z;
if (mobj->momx || mobj->momy)
{
P_XYMovement(mobj);
if (P_MobjWasRemoved(mobj))
return true;
}
if (mobj->momz)
{
if (!P_ZMovement(mobj))
return true; // mobj was removed
//P_CheckPosition(mobj, mobj->x, mobj->y);
}
return P_MobjWasRemoved(mobj) || (x == mobj->x && y == mobj->y && z == mobj->z);
}
// Unquick, unoptimized function for pushables
void P_PushableThinker(mobj_t *mobj)
{
I_Assert(mobj != NULL);
I_Assert(!P_MobjWasRemoved(mobj));
P_CheckMobjTrigger(mobj, true);
// it has to be pushable RIGHT NOW for this part to happen
if (mobj->flags & MF_PUSHABLE && !(mobj->momx || mobj->momy))
P_TryMove(mobj, mobj->x, mobj->y, true);
if (P_MobjWasRemoved(mobj))
return;
if (mobj->type == MT_MINECART && mobj->health)
{
// If player is ded, remove this minecart
if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->health || !mobj->target->player || mobj->target->player->powers[pw_carry] != CR_MINECART)
{
P_KillMobj(mobj, NULL, NULL, 0);
return;
}
}
if (mobj->fuse == 1) // it would explode in the MobjThinker code
{
mobj_t *spawnmo;
fixed_t x, y, z;
subsector_t *ss;
// Left here just in case we'd
// want to make pushable bombs
// or something in the future.
switch (mobj->type)
{
case MT_SNOWMAN:
case MT_GARGOYLE:
x = mobj->spawnpoint->x << FRACBITS;
y = mobj->spawnpoint->y << FRACBITS;
ss = R_PointInSubsector(x, y);
if (mobj->spawnpoint->z != 0)
z = mobj->spawnpoint->z << FRACBITS;
else
z = ss->sector->floorheight;
spawnmo = P_SpawnMobj(x, y, z, mobj->type);
if (!P_MobjWasRemoved(spawnmo))
{
spawnmo->spawnpoint = mobj->spawnpoint;
P_UnsetThingPosition(spawnmo);
spawnmo->flags = mobj->flags;
P_SetThingPosition(spawnmo);
spawnmo->flags2 = mobj->flags2;
spawnmo->flags |= MF_PUSHABLE;
}
P_RemoveMobj(mobj);
break;
default:
break;
}
}
}
// Quick, optimized function for scenery
void P_SceneryThinker(mobj_t *mobj)
{
if (mobj->flags & MF_BOXICON)
{
if (!(mobj->eflags & MFE_VERTICALFLIP))
{
if (mobj->z < mobj->floorz + FixedMul(mobj->info->damage, mobj->scale))
mobj->momz = FixedMul(mobj->info->speed, mobj->scale);
else
mobj->momz = 0;
}
else
{
if (mobj->z + FixedMul(mobj->info->height, mobj->scale) > mobj->ceilingz - FixedMul(mobj->info->damage, mobj->scale))
mobj->momz = -FixedMul(mobj->info->speed, mobj->scale);
else
mobj->momz = 0;
}
}
// momentum movement
if (mobj->momx || mobj->momy)
{
P_SceneryXYMovement(mobj);
if (P_MobjWasRemoved(mobj))
return;
}
// always do the gravity bit now, that's simpler
// BUT CheckPosition only if wasn't done before.
if (!(mobj->eflags & MFE_ONGROUND) || mobj->momz
|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz)
|| (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz)
|| P_IsObjectInGoop(mobj))
{
if (!P_SceneryZMovement(mobj))
return; // mobj was removed
P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
if (P_MobjWasRemoved(mobj))
return;
mobj->floorz = tmfloorz;
mobj->ceilingz = tmceilingz;
mobj->floorrover = tmfloorrover;
mobj->ceilingrover = tmceilingrover;
}
else
{
mobj->pmomz = 0; // to prevent that weird rocketing gargoyle bug
mobj->eflags &= ~MFE_JUSTHITFLOOR;
}
P_CycleMobjState(mobj);
}
//
// GAME SPAWN FUNCTIONS
//
static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
{
switch (thing->type)
{
case MT_PLAYER:
case MT_METALSONIC_RACE:
case MT_ROLLOUTROCK:
case MT_EGGMOBILE4_MACE:
case MT_SMALLMACE:
case MT_BIGMACE:
case MT_SMALLGRABCHAIN:
case MT_BIGGRABCHAIN:
case MT_BLUESPRINGBALL:
case MT_YELLOWSPRINGBALL:
case MT_REDSPRINGBALL:
return FRACUNIT;
case MT_RING:
case MT_FLINGRING:
case MT_COIN:
case MT_FLINGCOIN:
case MT_BLUESPHERE:
case MT_FLINGBLUESPHERE:
case MT_BOMBSPHERE:
case MT_REDTEAMRING:
case MT_BLUETEAMRING:
case MT_REDFLAG:
case MT_BLUEFLAG:
case MT_BOUNCERING:
case MT_AUTOMATICRING:
case MT_INFINITYRING:
case MT_RAILRING:
case MT_EXPLOSIONRING:
case MT_SCATTERRING:
case MT_GRENADERING:
case MT_BOUNCEPICKUP:
case MT_RAILPICKUP:
case MT_AUTOPICKUP:
case MT_EXPLODEPICKUP:
case MT_SCATTERPICKUP:
case MT_GRENADEPICKUP:
case MT_REDRING:
case MT_THROWNBOUNCE:
case MT_THROWNINFINITY:
case MT_THROWNAUTOMATIC:
case MT_THROWNSCATTER:
case MT_THROWNEXPLOSION:
case MT_THROWNGRENADE:
case MT_EMBLEM:
case MT_TOKEN:
case MT_EMERALD1:
case MT_EMERALD2:
case MT_EMERALD3:
case MT_EMERALD4:
case MT_EMERALD5:
case MT_EMERALD6:
case MT_EMERALD7:
case MT_EMERHUNT:
case MT_FLINGEMERALD:
return 2*FRACUNIT/3;
case MT_FLICKY_01:
case MT_FLICKY_02:
case MT_FLICKY_03:
case MT_FLICKY_04:
case MT_FLICKY_05:
case MT_FLICKY_06:
case MT_FLICKY_07:
case MT_FLICKY_08:
case MT_FLICKY_09:
case MT_FLICKY_10:
case MT_FLICKY_11:
case MT_FLICKY_12:
case MT_FLICKY_13:
case MT_FLICKY_14:
case MT_FLICKY_15:
case MT_FLICKY_16:
case MT_SECRETFLICKY_01:
case MT_SECRETFLICKY_02:
return FRACUNIT;
default:
if (thing->flags & (MF_ENEMY|MF_BOSS))
return FRACUNIT;
else
return 0;
}
}
//
// P_SpawnMobj
//
mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type, ...)
{
const mobjinfo_t *info = &mobjinfo[type];
SINT8 sc = -1;
state_t *st;
mobj_t *mobj;
int status;
va_list args;
if (type == MT_NULL)
return NULL;
if (mobjcache != NULL)
{
mobj = mobjcache;
mobjcache = mobjcache->hnext;
memset(mobj, 0, sizeof(*mobj));
}
else
{
mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
}
// this is officially a mobj, declared as soon as possible.
mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
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->health = (info->spawnhealth ? info->spawnhealth : 1);
mobj->reactiontime = info->reactiontime;
mobj->dispoffset = info->dispoffset;
mobj->lastlook = -1; // stuff moved in P_enemy.P_LookForPlayer
// 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; // FF_FRAMEMASK for frame, and other bits..
P_SetupStateAnimation(mobj, st);
mobj->friction = ORIG_FRICTION;
mobj->movefactor = FRACUNIT;
// All mobjs are created at 100% scale.
mobj->scale = FRACUNIT;
mobj->destscale = mobj->scale;
mobj->scalespeed = FRACUNIT/12;
// TODO: Make this a special map header
if ((maptol & TOL_ERZ3) && !(mobj->type == MT_BLACKEGGMAN))
mobj->destscale = FRACUNIT/2;
// Sprite rendering
mobj->blendmode = AST_TRANSLUCENT;
mobj->alpha = FRACUNIT;
mobj->spritexscale = mobj->spriteyscale = mobj->scale;
mobj->spritexoffset = mobj->spriteyoffset = 0;
mobj->floorspriteslope = NULL;
// set subsector and/or block links
P_SetThingPosition(mobj);
I_Assert(mobj->subsector != NULL);
mobj->floorz = P_GetSectorFloorZAt (mobj->subsector->sector, x, y);
mobj->ceilingz = P_GetSectorCeilingZAt(mobj->subsector->sector, x, y);
mobj->floorrover = NULL;
mobj->ceilingrover = NULL;
// Tells MobjCheckWater that the water height was not set.
mobj->watertop = INT32_MAX;
if (z == ONFLOORZ)
{
mobj->z = mobj->floorz;
if (mobj->type == MT_UNIDUS)
mobj->z += FixedMul(mobj->info->mass, mobj->scale);
// defaults onground
if (mobj->z == mobj->floorz)
mobj->eflags |= MFE_ONGROUND;
}
else if (z == ONCEILINGZ)
{
mobj->z = mobj->ceilingz - mobj->height;
if (mobj->type == MT_UNIDUS)
mobj->z -= FixedMul(mobj->info->mass, mobj->scale);
// defaults onground
if (mobj->z + mobj->height == mobj->ceilingz)
mobj->eflags |= MFE_ONGROUND;
}
else
mobj->z = z;
// Set shadowscale here, before spawn hook so that Lua can change it
mobj->shadowscale = P_DefaultMobjShadowScale(mobj);
// A monitor can't respawn if we're not in multiplayer,
// or if we're in co-op and it's score or a 1up
if (mobj->flags & MF_MONITOR && (!(netgame || multiplayer)
|| (G_CoopGametype()
&& (mobj->type == MT_1UP_BOX
|| mobj->type == MT_SCORE1K_BOX
|| mobj->type == MT_SCORE10K_BOX)
)))
mobj->flags2 |= MF2_DONTRESPAWN;
if (type == MT_PLAYER)
{
// when spawning MT_PLAYER, set mobj->player before calling MobjSpawn hook to prevent P_RemoveMobj from succeeding on player mobj.
va_start(args, type);
mobj->player = va_arg(args, player_t *);
if (mobj->player)
mobj->player->mo = mobj;
va_end(args);
}
if (!(mobj->flags & MF_NOTHINK) || (titlemapinaction && mobj->type == MT_ALTVIEWMAN))
P_AddThinker(THINK_MOBJ, &mobj->thinker);
// increment mobj reference, so we don't get a dangling reference in case MobjSpawn calls P_RemoveMobj
mobj->thinker.references++;
// DANGER! This can cause P_SpawnMobj to return NULL!
// Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks!
status = LUA_HookMobj(mobj, MOBJ_HOOK(MobjSpawn));
mobj->thinker.references--;
if (status)
{
if (P_MobjWasRemoved(mobj))
return NULL;
}
else if (P_MobjWasRemoved(mobj))
return NULL;
else
switch (mobj->type)
{
case MT_ALTVIEWMAN:
if (titlemapinaction) mobj->flags &= ~MF_NOTHINK;
break;
case MT_LOCKONINF:
P_SetScale(mobj, 3*mobj->scale, true);
break;
case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
mobj->fuse = mobj->info->painchance;
break;
case MT_BLACKEGGMAN:
{
mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z+mobj->height-16*FRACUNIT, MT_BLACKEGGMAN_HELPER);
if (P_MobjWasRemoved(spawn))
break;
P_SetScale(spawn, mobj->scale, true);
P_SetTarget(&spawn->target, mobj);
}
break;
case MT_FAKEMOBILE:
case MT_EGGSHIELD:
mobj->flags2 |= MF2_INVERTAIMABLE;
break;
case MT_DETON:
mobj->movedir = 0;
break;
case MT_EGGGUARD:
{
mobj_t *spawn = P_SpawnMobj(x, y, z, MT_EGGSHIELD);
if (P_MobjWasRemoved(spawn))
break;
P_SetScale(spawn, mobj->scale, true);
P_SetTarget(&mobj->tracer, spawn);
P_SetTarget(&spawn->target, mobj);
}
break;
case MT_UNIDUS:
{
INT32 i;
mobj_t *ball;
// Spawn "damage" number of "painchance" spikeball mobjs
// threshold is the distance they should keep from the MT_UNIDUS (touching radius + ball painchance)
for (i = 0; i < mobj->info->damage; i++)
{
ball = P_SpawnMobj(x, y, z, mobj->info->painchance);
if (P_MobjWasRemoved(ball))
continue;
P_SetScale(ball, mobj->scale, true);
P_SetTarget(&ball->target, mobj);
ball->movedir = FixedAngle(FixedMul(FixedDiv(i<<FRACBITS, mobj->info->damage<<FRACBITS), 360<<FRACBITS));
ball->threshold = ball->radius + mobj->radius + FixedMul(ball->info->painchance, ball->scale);
var1 = ball->state->var1, var2 = ball->state->var2;
ball->state->action.acp1(ball);
}
}
break;
case MT_POINTY:
{
INT32 q;
mobj_t *ball, *lastball = mobj;
for (q = 0; q < mobj->info->painchance; q++)
{
ball = P_SpawnMobj(x, y, z, mobj->info->mass);
if (P_MobjWasRemoved(ball))
continue;
P_SetScale(ball, mobj->scale, true);
P_SetTarget(&lastball->tracer, ball);
P_SetTarget(&ball->target, mobj);
lastball = ball;
}
}
break;
case MT_CRUSHSTACEAN:
{
mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CRUSHCLAW);
if (!P_MobjWasRemoved(bigmeatyclaw))
{
bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
P_SetTarget(&mobj->tracer, bigmeatyclaw);
P_SetTarget(&bigmeatyclaw->tracer, mobj);
}
mobj->reactiontime >>= 1;
}
break;
case MT_BANPYURA:
{
mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BANPSPRING);
if (!P_MobjWasRemoved(bigmeatyclaw))
{
bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);
P_SetTarget(&mobj->tracer, bigmeatyclaw);
P_SetTarget(&bigmeatyclaw->tracer, mobj);
}
mobj->reactiontime >>= 1;
}
break;
case MT_BIGMINE:
mobj->extravalue1 = FixedHypot(mobj->x, mobj->y)>>FRACBITS;
break;
case MT_WAVINGFLAG1:
case MT_WAVINGFLAG2:
{
mobj_t *prev = mobj, *cur;
UINT8 i;
for (i = 0; i <= 16; i++) // probably should be < but staying authentic to the Lua version
{
cur = P_SpawnMobjFromMobj(mobj, 0, 0, 0, ((mobj->type == MT_WAVINGFLAG1) ? MT_WAVINGFLAGSEG1 : MT_WAVINGFLAGSEG2));;
if (P_MobjWasRemoved(cur))
continue;
P_SetTarget(&prev->tracer, cur);
cur->extravalue1 = i;
prev = cur;
}
}
break;
case MT_EGGMOBILE2:
// Special condition for the 2nd boss.
mobj->watertop = mobj->info->speed;
break;
case MT_EGGMOBILE3:
mobj->movefactor = -512*FRACUNIT;
mobj->flags2 |= MF2_CLASSICPUSH;
break;
case MT_EGGMOBILE4:
mobj->flags2 |= MF2_INVERTAIMABLE;
break;
case MT_FLICKY_08:
mobj->color = (P_RandomChance(FRACUNIT/2) ? SKINCOLOR_RED : SKINCOLOR_AQUA);
break;
case MT_BALLOON:
mobj->color = SKINCOLOR_RED;
break;
case MT_EGGROBO1:
mobj->movecount = P_RandomKey(13);
mobj->color = SKINCOLOR_RUBY + P_RandomKey(numskincolors - SKINCOLOR_RUBY);
break;
case MT_HIVEELEMENTAL:
mobj->extravalue1 = 5;
break;
case MT_SMASHINGSPIKEBALL:
mobj->movecount = mobj->z;
break;
case MT_SPINBOBERT:
{
mobj_t *fire;
fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE1);
if (!P_MobjWasRemoved(fire))
{
P_SetTarget(&fire->target, mobj);
P_SetTarget(&mobj->hnext, fire);
}
fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE2);
if (!P_MobjWasRemoved(fire))
{
P_SetTarget(&fire->target, mobj);
P_SetTarget(&mobj->hprev, fire);
}
}
break;
case MT_REDRING: // Make MT_REDRING red by default
mobj->color = skincolor_redring;
break;
case MT_SMALLBUBBLE: // Bubbles eventually dissipate, in case they get caught somewhere.
case MT_MEDIUMBUBBLE:
case MT_EXTRALARGEBUBBLE:
mobj->fuse += 30 * TICRATE;
break;
case MT_NIGHTSDRONE:
nummaprings = -1; // no perfect bonus, rings are free
break;
case MT_EGGCAPSULE:
mobj->reactiontime = 0;
mobj->extravalue1 = mobj->cvmem =\
mobj->cusval = mobj->movecount =\
mobj->lastlook = mobj->extravalue2 = -1;
break;
case MT_REDTEAMRING:
mobj->color = skincolor_redteam;
break;
case MT_BLUETEAMRING:
mobj->color = skincolor_blueteam;
break;
case MT_RING:
case MT_COIN:
case MT_NIGHTSSTAR:
if (nummaprings >= 0)
nummaprings++;
break;
case MT_METALSONIC_RACE:
mobj->skin = skins[5];
/* FALLTHRU */
case MT_METALSONIC_BATTLE:
mobj->color = skins[5]->prefcolor;
sc = 5;
break;
case MT_FANG:
sc = 4;
break;
case MT_ROSY:
sc = 3;
break;
case MT_CORK:
mobj->flags2 |= MF2_SUPERFIRE;
break;
case MT_FBOMB:
mobj->flags2 |= MF2_EXPLOSION;
break;
case MT_OILLAMP:
{
mobj_t* overlay = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
if (P_MobjWasRemoved(overlay))
break;
P_SetTarget(&overlay->target, mobj);
P_SetMobjState(overlay, S_OILLAMPFLARE);
break;
}
case MT_TNTBARREL:
mobj->momx = 1; //stack hack
mobj->flags2 |= MF2_INVERTAIMABLE;
break;
case MT_MINECARTEND:
{
mobj_t *mcsolid = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID);
if (P_MobjWasRemoved(mcsolid))
break;
P_SetTarget(&mobj->tracer, mcsolid);
mcsolid->angle = mobj->angle + ANGLE_90;
}
break;
case MT_TORCHFLOWER:
{
mobj_t *fire = P_SpawnMobjFromMobj(mobj, 0, 0, 46*FRACUNIT, MT_FLAME);
if (!P_MobjWasRemoved(fire))
P_SetTarget(&mobj->target, fire);
break;
}
case MT_PYREFLY:
mobj->extravalue1 = (FixedHypot(mobj->x, mobj->y)/FRACUNIT) % 360;
mobj->extravalue2 = 0;
mobj->fuse = 100;
break;
case MT_SIGN:
{
mobj_t *sign = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
if (P_MobjWasRemoved(sign))
break;
P_SetTarget(&mobj->tracer, sign);
P_SetTarget(&sign->target, mobj);
P_SetMobjState(sign, S_SIGNBOARD);
sign->movedir = ANGLE_90;
}
default:
break;
}
if (sc != -1 && !(mobj->flags2 & MF2_SLIDEPUSH))
{
UINT8 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator)
continue;
if (players[i].skin == sc)
{
mobj->color = SKINCOLOR_SILVER;
mobj->colorized = true;
mobj->flags2 |= MF2_SLIDEPUSH;
break;
}
}
}
if (mobj->skin) // correct inadequecies above.
{
mobj->sprite2 = P_GetSkinSprite2(mobj->skin, P_GetStateSprite2(mobj->state), NULL);
mobj->frame &= ~FF_FRAMEMASK;
}
// Call action functions when the state is set
if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC))
{
if (levelloading)
{
// Cache actions in a linked list
// with function pointer, and
// var1 & var2, which will be executed
// when the level finishes loading.
P_AddCachedAction(mobj, mobj->info->spawnstate);
}
else
{
var1 = st->var1;
var2 = st->var2;
astate = st;
st->action.acp1(mobj);
// DANGER! This can cause P_SpawnMobj to return NULL!
// Avoid using MF_RUNSPAWNFUNC on mobjs whose spawn state expects target or tracer to already be set!
if (P_MobjWasRemoved(mobj))
return NULL;
}
}
if (CheckForReverseGravity && !(mobj->flags & MF_NOBLOCKMAP))
P_CheckGravity(mobj, false);
R_AddMobjInterpolator(mobj);
return mobj;
}
static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
{
state_t *st;
precipmobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
fixed_t starting_floorz;
mobj->x = x;
mobj->y = y;
mobj->flags = mobjinfo[type].flags;
// do not set the state with P_SetMobjState,
// because action routines can not be called yet
st = &states[mobjinfo[type].spawnstate];
mobj->state = st;
mobj->tics = st->tics;
mobj->sprite = st->sprite;
mobj->frame = st->frame; // FF_FRAMEMASK for frame, and other bits..
P_SetupStateAnimation((mobj_t*)mobj, st);
// set subsector and/or block links
P_SetPrecipitationThingPosition(mobj);
mobj->floorz = starting_floorz = P_GetSectorFloorZAt (mobj->subsector->sector, x, y);
mobj->ceilingz = P_GetSectorCeilingZAt(mobj->subsector->sector, x, y);
mobj->floorrover = NULL;
mobj->ceilingrover = NULL;
mobj->z = z;
mobj->momz = mobjinfo[type].speed;
mobj->thinker.function.acp1 = (actionf_p1)P_NullPrecipThinker;
P_AddThinker(THINK_PRECIP, &mobj->thinker);
CalculatePrecipFloor(mobj);
if (mobj->floorz != starting_floorz)
mobj->precipflags |= PCF_FOF;
else if (mobj->subsector->sector->damagetype == SD_DEATHPITNOTILT
|| mobj->subsector->sector->damagetype == SD_DEATHPITTILT
|| mobj->subsector->sector->floorpic == skyflatnum)
mobj->precipflags |= PCF_PIT;
R_ResetPrecipitationMobjInterpolationState(mobj);
return mobj;
}
static inline precipmobj_t *P_SpawnRainMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
{
precipmobj_t *mo = P_SpawnPrecipMobj(x,y,z,type);
mo->precipflags |= PCF_RAIN;
//mo->thinker.function.acp1 = (actionf_p1)P_RainThinker;
return mo;
}
static inline precipmobj_t *P_SpawnSnowMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
{
precipmobj_t *mo = P_SpawnPrecipMobj(x,y,z,type);
//mo->thinker.function.acp1 = (actionf_p1)P_SnowThinker;
return mo;
}
void *P_CreateFloorSpriteSlope(mobj_t *mobj)
{
if (mobj->floorspriteslope)
Z_Free(mobj->floorspriteslope);
mobj->floorspriteslope = Z_Calloc(sizeof(pslope_t), PU_LEVEL, NULL);
mobj->floorspriteslope->normal.z = FRACUNIT;
return (void *)mobj->floorspriteslope;
}
void P_RemoveFloorSpriteSlope(mobj_t *mobj)
{
if (mobj->floorspriteslope)
Z_Free(mobj->floorspriteslope);
mobj->floorspriteslope = NULL;
}
//
// P_RemoveMobj
//
mapthing_t *itemrespawnque[ITEMQUESIZE];
tic_t itemrespawntime[ITEMQUESIZE];
size_t iquehead, iquetail;
#ifdef PARANOIA
#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed
#endif
void P_RemoveMobj(mobj_t *mobj)
{
I_Assert(mobj != NULL);
if (P_MobjWasRemoved(mobj))
return; // something already removing this mobj.
mobj->thinker.function.acp1 = (actionf_p1)P_RemoveThinkerDelayed; // shh. no recursing.
LUA_HookMobj(mobj, MOBJ_HOOK(MobjRemoved));
mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; // needed for P_UnsetThingPosition, etc. to work.
// Rings only, please!
if (mobj->spawnpoint &&
(mobj->type == MT_RING
|| mobj->type == MT_COIN
|| mobj->type == MT_NIGHTSSTAR
|| mobj->type == MT_REDTEAMRING
|| mobj->type == MT_BLUETEAMRING
|| P_WeaponOrPanel(mobj->type))
&& !(mobj->flags2 & MF2_DONTRESPAWN))
{
itemrespawnque[iquehead] = mobj->spawnpoint;
itemrespawntime[iquehead] = leveltime;
iquehead = (iquehead+1)&(ITEMQUESIZE-1);
// lose one off the end?
if (iquehead == iquetail)
iquetail = (iquetail+1)&(ITEMQUESIZE-1);
}
if (mobj->type == MT_OVERLAY)
P_RemoveOverlay(mobj);
if (mobj->player && mobj->player->followmobj)
{
P_RemoveMobj(mobj->player->followmobj);
P_SetTarget(&mobj->player->followmobj, NULL);
}
mobj->health = 0; // Just because
// unlink from sector and block lists
P_UnsetThingPosition(mobj);
if (sector_list)
{
P_DelSeclist(sector_list);
sector_list = NULL;
}
mobj->flags |= MF_NOSECTOR|MF_NOBLOCKMAP;
mobj->subsector = NULL;
mobj->state = NULL;
mobj->player = NULL;
P_RemoveFloorSpriteSlope(mobj);
// stop any playing sound
S_StopSound(mobj);
// killough 11/98:
//
// Remove any references to other mobjs.
P_SetTarget(&mobj->target, P_SetTarget(&mobj->tracer, NULL));
if (mobj->hnext && !P_MobjWasRemoved(mobj->hnext))
P_SetTarget(&mobj->hnext->hprev, mobj->hprev);
if (mobj->hprev && !P_MobjWasRemoved(mobj->hprev))
P_SetTarget(&mobj->hprev->hnext, mobj->hnext);
P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
// clear the reference from the mapthing
if (mobj->spawnpoint)
mobj->spawnpoint->mobj = NULL;
R_RemoveMobjInterpolator(mobj);
// free block
if (!mobj->thinker.next)
{ // Uh-oh, the mobj doesn't think, P_RemoveThinker would never go through!
INT32 prevreferences;
if (!mobj->thinker.references)
{
// no references, dump it directly in the mobj cache
mobj->hnext = mobjcache;
mobjcache = mobj;
return;
}
prevreferences = mobj->thinker.references;
P_AddThinker(THINK_MOBJ, (thinker_t *)mobj);
mobj->thinker.references = prevreferences;
}
P_RemoveThinker((thinker_t *)mobj);
#ifdef PARANOIA
// Saved to avoid being scrambled like below...
mobj->thinker.debug_mobjtype = mobj->type;
#endif
// DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
#ifdef SCRAMBLE_REMOVED
// Invalidate mobj_t data to cause crashes if accessed!
memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
#endif
}
// This does not need to be added to Lua.
// To test it in Lua, check mobj.valid
boolean P_MobjWasRemoved(mobj_t *mobj)
{
if (mobj && mobj->thinker.function.acp1 == (actionf_p1)P_MobjThinker)
return false;
return true;
}
void P_RemovePrecipMobj(precipmobj_t *mobj)
{
// unlink from sector and block lists
P_UnsetPrecipThingPosition(mobj);
if (precipsector_list)
{
P_DelPrecipSeclist(precipsector_list);
precipsector_list = NULL;
}
// free block
P_RemoveThinker((thinker_t *)mobj);
}
// Clearing out stuff for savegames
void P_RemoveSavegameMobj(mobj_t *mobj)
{
// unlink from sector and block lists
if (((thinker_t *)mobj)->function.acp1 == (actionf_p1)P_NullPrecipThinker)
{
P_UnsetPrecipThingPosition((precipmobj_t *)mobj);
if (precipsector_list)
{
P_DelPrecipSeclist(precipsector_list);
precipsector_list = NULL;
}
}
else
{
// unlink from sector and block lists
P_UnsetThingPosition(mobj);
// Remove touching_sectorlist from mobj.
if (sector_list)
{
P_DelSeclist(sector_list);
sector_list = NULL;
}
}
// stop any playing sound
S_StopSound(mobj);
R_RemoveMobjInterpolator(mobj);
// free block
// Here we use the same code as R_RemoveThinkerDelayed, but without reference counting (we're removing everything so it shouldn't matter) and without touching currentthinker since we aren't in P_RunThinkers
{
thinker_t *thinker = (thinker_t *)mobj;
thinker_t *next = thinker->next;
(next->prev = thinker->prev)->next = next;
Z_Free(thinker);
}
}
static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}};
consvar_t cv_itemrespawntime = CVAR_INIT ("respawnitemtime", "30", CV_SAVE|CV_NETVAR|CV_CHEAT|CV_ALLOWLUA, respawnitemtime_cons_t, NULL);
consvar_t cv_itemrespawn = CVAR_INIT ("respawnitem", "On", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, CV_OnOff, NULL);
static CV_PossibleValue_t flagtime_cons_t[] = {{0, "MIN"}, {300, "MAX"}, {0, NULL}};
consvar_t cv_flagtime = CVAR_INIT ("flagtime", "30", CV_SAVE|CV_NETVAR|CV_CHEAT|CV_ALLOWLUA, flagtime_cons_t, NULL);
void P_SpawnPrecipitation(void)
{
INT32 i, mrand;
fixed_t basex, basey, x, y, height;
subsector_t *precipsector = NULL;
precipmobj_t *rainmo = NULL;
if (dedicated || !(cv_drawdist_precip.value) || curWeather == PRECIP_NONE || curWeather == PRECIP_STORM_NORAIN)
return;
// Use the blockmap to narrow down our placing patterns
for (i = 0; i < bmapwidth*bmapheight; ++i)
{
basex = bmaporgx + (i % bmapwidth) * MAPBLOCKSIZE;
basey = bmaporgy + (i / bmapwidth) * MAPBLOCKSIZE;
x = basex + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
y = basey + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
precipsector = R_PointInSubsectorOrNull(x, y);
// No sector? Stop wasting time,
// move on to the next entry in the blockmap
if (!precipsector)
continue;
// Exists, but is too small for reasonable precipitation.
if (!(precipsector->sector->floorheight <= precipsector->sector->ceilingheight - (32<<FRACBITS)))
continue;
// Don't set height yet...
height = precipsector->sector->ceilingheight;
if (curWeather == PRECIP_SNOW)
{
// Not in a sector with visible sky -- exception for NiGHTS.
if ((!(maptol & TOL_NIGHTS) && (precipsector->sector->ceilingpic != skyflatnum)) == !(precipsector->sector->flags & MSF_INVERTPRECIP))
continue;
rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
mrand = M_RandomByte();
if (mrand < 64)
P_SetPrecipMobjState(rainmo, S_SNOW3);
else if (mrand < 144)
P_SetPrecipMobjState(rainmo, S_SNOW2);
}
else // everything else.
{
// Not in a sector with visible sky.
if ((precipsector->sector->ceilingpic != skyflatnum) == !(precipsector->sector->flags & MSF_INVERTPRECIP))
continue;
rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
if (curWeather == PRECIP_BLANK)
rainmo->precipflags |= PCF_INVISIBLE;
}
// Randomly assign a height, now that floorz is set.
rainmo->z = M_RandomRange(rainmo->floorz>>FRACBITS, rainmo->ceilingz>>FRACBITS)<<FRACBITS;
}
}
//
// P_PrecipitationEffects
//
void P_PrecipitationEffects(void)
{
INT16 thunderchance = INT16_MAX;
INT32 volume;
size_t i;
boolean sounds_rain = true;
boolean sounds_thunder = true;
boolean effects_lightning = true;
boolean lightningStrike = false;
// No thunder except every other tic.
if (leveltime & 1);
// Before, consistency failures were possible if a level started
// with global rain and switched players to anything else ...
// If the global weather has lightning strikes,
// EVERYONE gets them at the SAME time!
else if (globalweather == PRECIP_STORM
|| globalweather == PRECIP_STORM_NORAIN)
thunderchance = (P_RandomKey(8192));
// But on the other hand, if the global weather is ANYTHING ELSE,
// don't sync lightning strikes.
// It doesn't matter whatever curWeather is, we'll only use
// the variable if we care about it.
else
thunderchance = (M_RandomKey(8192));
if (thunderchance < 70)
lightningStrike = true;
switch (curWeather)
{
case PRECIP_RAIN: // no lightning or thunder whatsoever
sounds_thunder = false;
/* FALLTHRU */
case PRECIP_STORM_NOSTRIKES: // no lightning strikes specifically
effects_lightning = false;
break;
case PRECIP_STORM_NORAIN: // no rain, lightning and thunder allowed
sounds_rain = false;
case PRECIP_STORM: // everything.
break;
default:
// Other weathers need not apply.
return;
}
// Currently thunderstorming with lightning, and we're sounding the thunder...
// and where there's thunder, there's gotta be lightning!
if (effects_lightning && lightningStrike)
{
sector_t *ss = sectors;
for (i = 0; i < numsectors; i++, ss++)
if (ss->ceilingpic == skyflatnum) // Only for the sky.
P_SpawnLightningFlash(ss); // Spawn a quick flash thinker
}
// Local effects from here on out!
// If we're not in game fully yet, we don't worry about them.
if (!playeringame[displayplayer] || !players[displayplayer].mo)
return;
if (sound_disabled)
return; // Sound off? D'aw, no fun.
if (players[displayplayer].mo->subsector->sector->ceilingpic == skyflatnum)
volume = 255; // Sky above? We get it full blast.
else
{
fixed_t x, y, yl, yh, xl, xh;
fixed_t closedist, newdist;
// Essentially check in a 1024 unit radius of the player for an outdoor area.
yl = players[displayplayer].mo->y - 1024*FRACUNIT;
yh = players[displayplayer].mo->y + 1024*FRACUNIT;
xl = players[displayplayer].mo->x - 1024*FRACUNIT;
xh = players[displayplayer].mo->x + 1024*FRACUNIT;
closedist = 2048*FRACUNIT;
for (y = yl; y <= yh; y += FRACUNIT*64)
for (x = xl; x <= xh; x += FRACUNIT*64)
{
if (R_PointInSubsector(x, y)->sector->ceilingpic == skyflatnum) // Found the outdoors!
{
newdist = S_CalculateSoundDistance(players[displayplayer].mo->x, players[displayplayer].mo->y, 0, x, y, 0);
if (newdist < closedist)
closedist = newdist;
}
}
volume = 255 - (closedist>>(FRACBITS+2));
}
if (volume < 0)
volume = 0;
else if (volume > 255)
volume = 255;
if (sounds_rain && (!leveltime || leveltime % 80 == 1))
S_StartSoundAtVolume(players[displayplayer].mo, sfx_rainin, volume);
if (!sounds_thunder)
return;
if (effects_lightning && lightningStrike && volume)
{
// Large, close thunder sounds to go with our lightning.
S_StartSoundAtVolume(players[displayplayer].mo, sfx_litng1 + M_RandomKey(4), volume);
}
else if (thunderchance < 20)
{
// You can always faintly hear the thunder...
if (volume < 80)
volume = 80;
S_StartSoundAtVolume(players[displayplayer].mo, sfx_athun1 + M_RandomKey(2), volume);
}
}
/** Returns corresponding mobj type from mapthing number.
* \param mthingtype Mapthing number in question.
* \return Mobj type; MT_UNKNOWN if nothing found.
*/
mobjtype_t P_GetMobjtype(UINT16 mthingtype)
{
mobjtype_t i;
for (i = 0; i < NUMMOBJTYPES; i++)
if (mthingtype == mobjinfo[i].doomednum)
return i;
return MT_UNKNOWN;
}
//
// P_RespawnSpecials
//
void P_RespawnSpecials(void)
{
mapthing_t *mthing = NULL;
// only respawn items when cv_itemrespawn is on
if (!(netgame || multiplayer) // Never respawn in single player
|| (maptol & TOL_NIGHTS) // Never respawn in NiGHTs
|| !cv_itemrespawn.value) // cvar is turned off
return;
// Don't respawn in special stages!
if (G_IsSpecialStage(gamemap))
return;
// nothing left to respawn?
if (iquehead == iquetail)
return;
// the first item in the queue is the first to respawn
// wait at least 30 seconds
if (leveltime - itemrespawntime[iquetail] < (tic_t)cv_itemrespawntime.value*TICRATE)
return;
mthing = itemrespawnque[iquetail];
#ifdef PARANOIA
if (!mthing)
I_Error("itemrespawnque[iquetail] is NULL!");
#endif
if (mthing)
P_SpawnMapThing(mthing);
// 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.
//
void P_SpawnPlayer(INT32 playernum)
{
player_t *p = &players[playernum];
mobj_t *mobj;
if (p->playerstate == PST_REBORN)
G_PlayerReborn(playernum, false);
// spawn as spectator determination
if (!G_GametypeHasSpectators())
{
p->spectator = p->outofcoop =
(((multiplayer || netgame) && G_CoopGametype()) // only question status in coop
&& ((leveltime > 0
&& ((G_IsSpecialStage(gamemap)) // late join special stage
|| (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop
|| (!P_GetLives(p) && p->lives <= 0))); // game over and can't redistribute lives
}
else
{
p->outofcoop = false;
if (netgame && p->jointime < 1)
{
// Averted by GTR_NOSPECTATORSPAWN.
p->spectator = (gametyperules & GTR_NOSPECTATORSPAWN) ? false : true;
}
else if (multiplayer && !netgame)
{
// If you're in a team game and you don't have a team assigned yet...
if (G_GametypeHasTeams() && p->ctfteam == 0)
{
changeteam_union NetPacket;
UINT16 usvalue;
NetPacket.value.l = NetPacket.value.b = 0;
// Spawn as a spectator,
// yes even in splitscreen mode
p->spectator = true;
// but immediately send a team change packet.
NetPacket.packet.playernum = playernum;
NetPacket.packet.verification = true;
NetPacket.packet.newteam = !(playernum&1) + 1;
usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
}
else // Otherwise, never spectator.
p->spectator = false;
}
}
if (G_GametypeHasTeams())
{
// Fix stupid non spectator spectators.
if (!p->spectator && !p->ctfteam)
p->spectator = true;
}
if ((netgame || multiplayer) && ((gametyperules & GTR_SPAWNINVUL) || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS))
p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
// MT_PLAYER cannot be removed, so this shouldn't be able to return NULL.
mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER, p);
I_Assert(mobj != NULL);
mobj->angle = 0;
// set color translations for player sprites
mobj->color = P_GetPlayerColor(p);
// set 'spritedef' override in mobj for player skins.. (see ProjectSprite)
// (usefulness: when body mobj is detached from player (who respawns),
// the dead body mobj retains the skin through the 'spritedef' override).
mobj->skin = skins[p->skin];
P_SetupStateAnimation(mobj, mobj->state);
mobj->health = 1;
p->playerstate = PST_LIVE;
p->bonustime = false;
p->realtime = leveltime;
p->followitem = skins[p->skin]->followitem;
// Make sure player's stats are reset if they were in dashmode!
if (p->dashmode)
{
p->dashmode = 0;
p->normalspeed = skins[p->skin]->normalspeed;
p->jumpfactor = skins[p->skin]->jumpfactor;
}
// Clear lastlinehit and lastsidehit
p->lastsidehit = -1;
p->lastlinehit = -1;
//awayview stuff
p->awayviewmobj = NULL;
p->awayviewtics = 0;
// set the scale to the mobj's destscale so settings get correctly set. if we don't, they sometimes don't.
P_SetScale(mobj, mobj->destscale, true);
P_FlashPal(p, 0, 0); // Resets
// Set bounds accurately.
mobj->radius = FixedMul(skins[p->skin]->radius, mobj->scale);
mobj->height = P_GetPlayerHeight(p);
if (!leveltime && !p->spectator && ((maptol & TOL_NIGHTS) == TOL_NIGHTS) != (G_IsSpecialStage(gamemap))) // non-special NiGHTS stage or special non-NiGHTS stage
{
if (maptol & TOL_NIGHTS)
{
if (p == players) // this is totally the wrong place to do this aaargh.
{
mobj_t *idya = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_GOTEMERALD);
if (!P_MobjWasRemoved(idya))
{
idya->health = 0; // for identification
P_SetTarget(&idya->target, mobj);
P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate);
P_SetTarget(&mobj->tracer, idya);
}
}
}
else if (sstimer)
p->nightstime = sstimer;
}
// Spawn with a pity shield if necessary.
P_DoPityCheck(p);
}
void P_AfterPlayerSpawn(INT32 playernum)
{
player_t *p = &players[playernum];
mobj_t *mobj = p->mo;
P_SetPlayerAngle(p, mobj->angle);
p->viewheight = 41*p->height/48;
if (p->mo->eflags & MFE_VERTICALFLIP)
p->viewz = p->mo->z + p->mo->height - p->viewheight;
else
p->viewz = p->mo->z + p->viewheight;
if (playernum == consoleplayer)
{
// wake up the status bar
ST_Start();
// wake up the heads up text
HU_Start();
}
p->drawangle = mobj->angle;
if (camera.chase)
{
if (displayplayer == playernum)
P_ResetCamera(p, &camera);
}
if (camera2.chase && splitscreen)
{
if (secondarydisplayplayer == playernum)
P_ResetCamera(p, &camera2);
}
if (CheckForReverseGravity)
P_CheckGravity(mobj, false);
if (p->pflags & PF_FINISHED && !(p->exiting))
P_GiveFinishFlags(p);
}
// spawn it at a playerspawn mapthing
void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
{
fixed_t x = 0, y = 0;
angle_t angle = 0;
fixed_t z;
sector_t *sector;
fixed_t floor, ceiling, ceilingspawn;
player_t *p = &players[playernum];
mobj_t *mobj = p->mo;
I_Assert(mobj != NULL);
if (mthing)
{
x = mthing->x << FRACBITS;
y = mthing->y << FRACBITS;
angle = FixedAngle(mthing->angle<<FRACBITS);
}
//spawn at the origin as a desperation move if there is no mapthing
// set Z height
sector = R_PointInSubsector(x, y)->sector;
floor = P_GetSectorFloorZAt (sector, x, y);
ceiling = P_GetSectorCeilingZAt(sector, x, y);
ceilingspawn = ceiling - mobjinfo[MT_PLAYER].height;
if (mthing)
{
fixed_t offset = mthing->z << FRACBITS;
// Setting the spawnpoint's args[0] will make the player start on the ceiling
// Objectflip inverts
if (!!(mthing->args[0]) ^ !!(mthing->options & MTF_OBJECTFLIP))
z = ceilingspawn - offset;
else
z = floor + offset;
if (mthing->options & MTF_OBJECTFLIP) // flip the player!
{
mobj->eflags |= MFE_VERTICALFLIP;
mobj->flags2 |= MF2_OBJECTFLIP;
}
if (mthing->args[0])
P_SetMobjState(mobj, S_PLAY_FALL);
else if (metalrecording)
P_SetMobjState(mobj, S_PLAY_WAIT);
}
else
z = floor;
if (z < floor)
z = floor;
else if (z > ceilingspawn)
z = ceilingspawn;
mobj->floorz = floor;
mobj->ceilingz = ceiling;
P_UnsetThingPosition(mobj);
mobj->x = x;
mobj->y = y;
P_SetThingPosition(mobj);
mobj->z = z;
if (mobj->flags2 & MF2_OBJECTFLIP)
{
if (mobj->z + mobj->height == mobj->ceilingz)
mobj->eflags |= MFE_ONGROUND;
}
else if (mobj->z == mobj->floorz)
mobj->eflags |= MFE_ONGROUND;
mobj->angle = angle;
P_AfterPlayerSpawn(playernum);
}
void P_MovePlayerToStarpost(INT32 playernum)
{
fixed_t z;
sector_t *sector;
fixed_t floor, ceiling;
player_t *p = &players[playernum];
mobj_t *mobj = p->mo;
I_Assert(mobj != NULL);
P_UnsetThingPosition(mobj);
mobj->x = p->starpostx << FRACBITS;
mobj->y = p->starposty << FRACBITS;
P_SetThingPosition(mobj);
sector = R_PointInSubsector(mobj->x, mobj->y)->sector;
floor = P_GetSectorFloorZAt (sector, mobj->x, mobj->y);
ceiling = P_GetSectorCeilingZAt(sector, mobj->x, mobj->y);
z = p->starpostz << FRACBITS;
P_SetScale(mobj, abs(p->starpostscale), true);
if (p->starpostscale < 0)
{
mobj->flags2 |= MF2_OBJECTFLIP;
if (z >= ceiling)
{
mobj->eflags |= MFE_ONGROUND;
z = ceiling;
}
z -= mobj->height;
}
else if (z <= floor)
{
mobj->eflags |= MFE_ONGROUND;
z = floor;
}
mobj->floorz = floor;
mobj->ceilingz = ceiling;
mobj->z = z;
mobj->angle = p->starpostangle;
P_AfterPlayerSpawn(playernum);
if (!(netgame || multiplayer))
leveltime = p->starposttime;
}
#define MAXHUNTEMERALDS 64
mapthing_t *huntemeralds[MAXHUNTEMERALDS];
INT32 numhuntemeralds;
fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale, const boolean absolutez)
{
const subsector_t *ss = R_PointInSubsector(x, y);
// Axis objects snap to the floor.
if (mobjtype == MT_AXIS || mobjtype == MT_AXISTRANSFER || mobjtype == MT_AXISTRANSFERLINE)
return ONFLOORZ;
// Establish height.
if (flip)
return (absolutez ? dz : P_GetSectorCeilingZAt(ss->sector, x, y) - dz) - FixedMul(scale, offset + mobjinfo[mobjtype].height);
else
return (absolutez ? dz : P_GetSectorFloorZAt(ss->sector, x, y) + dz) + FixedMul(scale, offset);
}
fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y)
{
fixed_t dz = mthing->z << FRACBITS; // Base offset from the floor.
fixed_t offset = 0; // Specific scaling object offset.
boolean flip = (!!(mobjinfo[mobjtype].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP));
boolean absolutez = !!(mthing->options & MTF_ABSOLUTEZ);
switch (mobjtype)
{
// Objects with a non-zero default height.
case MT_CRAWLACOMMANDER:
case MT_DETON:
case MT_JETTBOMBER:
case MT_JETTGUNNER:
case MT_EGGMOBILE2:
if (!dz)
dz = 33*FRACUNIT;
break;
case MT_EGGMOBILE:
if (!dz)
dz = 128*FRACUNIT;
break;
case MT_GOLDBUZZ:
case MT_REDBUZZ:
if (!dz)
dz = 288*FRACUNIT;
break;
// Horizontal springs, float additional units unless args[0] is set.
case MT_YELLOWHORIZ:
case MT_REDHORIZ:
case MT_BLUEHORIZ:
offset += mthing->args[0] ? 0 : 16*FRACUNIT;
break;
// Ring-like items, float additional units unless args[0] is set.
case MT_SPIKEBALL:
case MT_EMERHUNT:
case MT_EMERALDSPAWN:
case MT_TOKEN:
case MT_RING:
case MT_REDTEAMRING:
case MT_BLUETEAMRING:
case MT_COIN:
case MT_BLUESPHERE:
case MT_BOMBSPHERE:
case MT_NIGHTSCHIP:
case MT_NIGHTSSTAR:
offset += mthing->args[0] ? 0 : 24*FRACUNIT;
break;
case MT_EMBLEM:
offset += mthing->args[1] ? 0 : 24 * FRACUNIT;
break;
// Remaining objects.
default:
if (P_WeaponOrPanel(mobjtype))
offset += mthing->args[0] ? 0 : 24*FRACUNIT;
}
if (!(dz + offset) && !absolutez) // Snap to the surfaces when there's no offset set.
{
if (flip)
return ONCEILINGZ;
else
return ONFLOORZ;
}
return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale, absolutez);
}
static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
{
#if MAXPLAYERS > 32
You should think about modifying the deathmatch starts to take full advantage of this!
#endif
if (mthing->type <= MAXPLAYERS) // Player starts
{
// save spots for respawning in network games
if (!metalrecording)
playerstarts[mthing->type - 1] = mthing;
return true;
}
else if (mthing->type == 33) // Match starts
{
if (numdmstarts < MAX_DM_STARTS)
{
deathmatchstarts[numdmstarts] = mthing;
mthing->type = 0;
numdmstarts++;
}
return true;
}
else if (mthing->type == 34) // Red CTF starts
{
if (numredctfstarts < MAXPLAYERS)
{
redctfstarts[numredctfstarts] = mthing;
mthing->type = 0;
numredctfstarts++;
}
return true;
}
else if (mthing->type == 35) // Blue CTF starts
{
if (numbluectfstarts < MAXPLAYERS)
{
bluectfstarts[numbluectfstarts] = mthing;
mthing->type = 0;
numbluectfstarts++;
}
return true;
}
else if (metalrecording && mthing->type == mobjinfo[MT_METALSONIC_RACE].doomednum)
{ // If recording, you ARE Metal Sonic. Do not spawn it, do not save normal spawnpoints.
playerstarts[0] = mthing;
return true;
}
else if (mthing->type == 750 // Slope vertex point (formerly chaos spawn)
|| (mthing->type >= 600 && mthing->type <= 611) // Special placement patterns
|| mthing->type == 1713) // Hoops
return true; // These are handled elsewhere.
else if (mthing->type == mobjinfo[MT_EMERHUNT].doomednum)
{
// Emerald Hunt is Coop only. Don't spawn the emerald yet, but save the spawnpoint for later.
if ((gametyperules & GTR_EMERALDHUNT) && numhuntemeralds < MAXHUNTEMERALDS)
huntemeralds[numhuntemeralds++] = mthing;
return true;
}
return false;
}
static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
{
switch (i)
{
case MT_EMERALD1:
case MT_EMERALD2:
case MT_EMERALD3:
case MT_EMERALD4:
case MT_EMERALD5:
case MT_EMERALD6:
case MT_EMERALD7:
if (!G_CoopGametype()) // Don't place emeralds in non-coop modes
return false;
if (metalrecording)
return false; // Metal Sonic isn't for collecting emeralds.
if (emeralds & mobjinfo[i].speed) // You already have this emerald!
return false;
break;
case MT_EMERALDSPAWN:
if (!cv_powerstones.value)
return false;
if (!(gametyperules & GTR_POWERSTONES))
return false;
runemeraldmanager = true;
break;
case MT_ROSY:
if (!(G_CoopGametype() || mthing->args[0]))
return false; // she doesn't hang out here
if (!(netgame || multiplayer) && players[consoleplayer].skin == 3)
return false; // no doubles
break;
case MT_TOKEN:
if (!(gametyperules & GTR_EMERALDTOKENS))
return false; // Gametype's not right
if (tokenbits == 30)
return false; // Too many tokens
if (tokenlist & (1 << tokenbits++))
return false; // You already got this token
break;
case MT_EMBLEM:
if (!G_CoopGametype())
return false; // Gametype's not right
break;
default:
break;
}
if (metalrecording) // Metal Sonic can't use these things.
{
if ((mobjinfo[i].flags & (MF_ENEMY|MF_BOSS)) || i == MT_TOKEN || i == MT_STARPOST
|| i == MT_RING || i == MT_BLUETEAMRING || i == MT_REDTEAMRING || i == MT_COIN
|| i == MT_BLUESPHERE || i == MT_BOMBSPHERE || i == MT_NIGHTSCHIP || i == MT_NIGHTSSTAR)
return false;
}
if (((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS)) && !(gametyperules & GTR_SPAWNENEMIES))
return false; // No enemies in ringslinger modes
if (!(gametyperules & GTR_ALLOWEXIT) && (i == MT_SIGN))
return false; // Don't spawn exit signs in wrong game modes
if (!G_PlatformGametype() && (i == MT_STARPOST))
return false; // Don't spawn starposts in wrong game modes
if (!G_RingSlingerGametype() || !cv_specialrings.value)
if (P_WeaponOrPanel(i))
return false; // Don't place weapons/panels in non-ringslinger modes
if (!(gametyperules & GTR_TEAMFLAGS)) // CTF specific things
{
if (i == MT_BLUEFLAG || i == MT_REDFLAG)
return false; // No flags in non-CTF modes!
}
else
{
if ((i == MT_BLUEFLAG && blueflag) || (i == MT_REDFLAG && redflag))
{
CONS_Alert(CONS_ERROR, M_GetText("Only one flag per team allowed in CTF!\n"));
return false;
}
}
if (modeattacking) // Record Attack special stuff
{
// Don't spawn starposts that wouldn't be usable
if (i == MT_STARPOST)
return false;
}
if (ultimatemode)
{
if (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING
|| i == MT_COIN || i == MT_NIGHTSSTAR || i == MT_NIGHTSCHIP
|| i == MT_PITY_BOX || i == MT_ELEMENTAL_BOX || i == MT_ATTRACT_BOX
|| i == MT_FORCE_BOX || i == MT_ARMAGEDDON_BOX || i == MT_WHIRLWIND_BOX
|| i == MT_FLAMEAURA_BOX || i == MT_BUBBLEWRAP_BOX || i == MT_THUNDERCOIN_BOX
|| i == MT_RING_BOX || i == MT_STARPOST)
return false; // No rings or shields in Ultimate mode
// Don't include the gold repeating boxes here please.
// They're likely facets of the level's design and therefore required to progress.
}
return true;
}
#define nightsreplace ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap))
static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
{
// Altering monitor spawns via cvars
// If MF_GRENADEBOUNCE is set in the monitor's info,
// skip this step. (Used for gold monitors)
// Yeah, this is a dirty hack.
if ((mobjinfo[i].flags & (MF_MONITOR|MF_GRENADEBOUNCE)) == MF_MONITOR)
{
if (gametyperules & GTR_RACE)
{
// Set powerup boxes to user settings for competition.
switch (cv_competitionboxes.value)
{
case 1: // Mystery
return MT_MYSTERY_BOX;
case 2: // Teleport
return MT_MIXUP_BOX;
case 3: // None
return MT_NULL; // Don't spawn!
default:
return i;
}
}
// Set powerup boxes to user settings for other netplay modes
else if (!G_CoopGametype())
{
switch (cv_matchboxes.value)
{
case 1: // Mystery
return MT_MYSTERY_BOX;
case 2: // Unchanging
if (i == MT_MYSTERY_BOX)
return MT_NULL; // don't spawn
mthing->args[1] = TMMR_SAME; // no random respawning!
return i;
case 3: // Don't spawn
return MT_NULL;
default:
return i;
}
}
}
if (nightsreplace)
{
if (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN)
return MT_NIGHTSSTAR;
if (i == MT_BLUESPHERE)
return MT_NIGHTSCHIP;
}
if (!(gametyperules & GTR_TEAMS))
{
if (i == MT_BLUETEAMRING || i == MT_REDTEAMRING)
return MT_RING;
if (i == MT_RING_BLUEBOX || i == MT_RING_REDBOX)
return MT_RING_BOX;
}
if (modeattacking && i == MT_1UP_BOX) // 1UPs -->> Score TVs
{
if (mthing->args[2])
return MT_SCORE10K_BOX; // 10,000
else
return MT_SCORE1K_BOX; // 1,000
}
return i;
}
static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
{
INT32 j;
emblem_t* emblem = M_GetLevelEmblems(gamemap);
skincolornum_t emcolor;
while (emblem)
{
if ((emblem->type == ET_GLOBAL || emblem->type == ET_SKIN) && emblem->tag == Tag_FGet(&mthing->tags))
break;
emblem = M_GetLevelEmblems(-1);
}
if (!emblem)
{
CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags));
return false;
}
j = emblem - emblemlocations;
I_Assert(emblemlocations[j].sprite >= 'A' && emblemlocations[j].sprite <= 'Z');
P_SetMobjState(mobj, mobj->info->spawnstate + (emblemlocations[j].sprite - 'A'));
mobj->health = j + 1;
emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
mobj->color = (UINT16)emcolor;
mobj->frame &= ~FF_TRANSMASK;
if (emblemlocations[j].type == ET_GLOBAL)
{
mobj->reactiontime = emblemlocations[j].var;
if (emblemlocations[j].var & GE_NIGHTSITEM)
{
mobj->flags |= MF_NIGHTSITEM;
mobj->flags &= ~MF_SPECIAL;
mobj->flags2 |= MF2_DONTDRAW;
}
}
return true;
}
static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
{
fixed_t mlength, mmaxlength, mlengthset, mspeed, mphase, myaw, mpitch, mminlength, mnumspokes, mpinch, mroll, mnumnospokes, mwidth, mwidthset, mmin, msound, radiusfactor, widthfactor;
angle_t mspokeangle;
mobjtype_t chainlink, macetype, firsttype, linktype;
boolean mdosound, mdocenter, mchainlike = false;
mobj_t *spawnee = NULL, *hprev = mobj;
mobjflag_t mflagsapply;
mobjflag2_t mflags2apply;
mobjeflag_t meflagsapply;
const size_t mthingi = (size_t)(mthing - mapthings);
mlength = abs(mthing->args[0]);
mnumspokes = mthing->args[1] + 1;
mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes) >> ANGLETOFINESHIFT;
mwidth = max(0, mthing->args[2]);
mspeed = abs(mthing->args[3] << 4);
mphase = mthing->args[4] % 360;
mpinch = mthing->args[5] % 360;
mnumnospokes = mthing->args[6];
mminlength = max(0, min(mlength - 1, mthing->args[7]));
mpitch = mthing->pitch % 360;
myaw = mthing->angle % 360;
mroll = mthing->roll % 360;
CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
"Length is %d (minus %d)\n"
"Speed is %d\n"
"Phase is %d\n"
"Yaw is %d\n"
"Pitch is %d\n"
"No. of spokes is %d (%d antispokes)\n"
"Pinch is %d\n"
"Roll is %d\n"
"Width is %d\n",
sizeu1(mthingi), mlength, mminlength, mspeed, mphase, myaw, mpitch, mnumspokes, mnumnospokes, mpinch, mroll, mwidth);
if (mnumnospokes > 0 && (mnumnospokes < mnumspokes))
mnumnospokes = mnumspokes/mnumnospokes;
else
mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes) : 0);
mobj->lastlook = mspeed;
mobj->movecount = mobj->lastlook;
mobj->angle = FixedAngle(myaw << FRACBITS);
*doangle = false;
mobj->threshold = (FixedAngle(mpitch << FRACBITS) >> ANGLETOFINESHIFT);
mobj->movefactor = mpinch;
mobj->movedir = 0;
// Mobjtype selection
switch (mobj->type)
{
case MT_SPRINGBALLPOINT:
macetype = ((mthing->args[8] & TMM_DOUBLESIZE)
? MT_REDSPRINGBALL
: MT_YELLOWSPRINGBALL);
chainlink = MT_SMALLMACECHAIN;
break;
case MT_FIREBARPOINT:
macetype = ((mthing->args[8] & TMM_DOUBLESIZE)
? MT_BIGFIREBAR
: MT_SMALLFIREBAR);
chainlink = MT_NULL;
break;
case MT_CUSTOMMACEPOINT:
macetype = mthing->stringargs[0] ? get_number(mthing->stringargs[0]) : MT_NULL;
chainlink = mthing->stringargs[1] ? get_number(mthing->stringargs[1]) : MT_NULL;
break;
case MT_CHAINPOINT:
if (mthing->args[8] & TMM_DOUBLESIZE)
{
macetype = MT_BIGGRABCHAIN;
chainlink = MT_BIGMACECHAIN;
}
else
{
macetype = MT_SMALLGRABCHAIN;
chainlink = MT_SMALLMACECHAIN;
}
mchainlike = true;
break;
default:
if (mthing->args[8] & TMM_DOUBLESIZE)
{
macetype = MT_BIGMACE;
chainlink = MT_BIGMACECHAIN;
}
else
{
macetype = MT_SMALLMACE;
chainlink = MT_SMALLMACECHAIN;
}
break;
}
if (!macetype && !chainlink)
return true;
if (mobj->type == MT_CHAINPOINT)
{
if (!mlength)
return true;
}
else
mlength++;
firsttype = macetype;
// Adjustable direction
if (mthing->args[8] & TMM_ALLOWYAWCONTROL)
mobj->flags |= MF_SLIDEME;
// Swinging
if (mthing->args[8] & TMM_SWING)
{
mobj->flags2 |= MF2_STRONGBOX;
mmin = ((mnumnospokes > 1) ? 1 : 0);
}
else
mmin = mnumspokes;
// If over distance away, don't move UNLESS this flag is applied
if (mthing->args[8] & TMM_ALWAYSTHINK)
mobj->flags2 |= MF2_BOSSNOTRAP;
// Make the links the same type as the end - repeated below
if ((mobj->type != MT_CHAINPOINT) && (((mthing->args[8] & TMM_MACELINKS) == TMM_MACELINKS) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
{
linktype = macetype;
radiusfactor = 2; // Double the radius.
}
else
radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
if (!mchainlike)
mchainlike = (firsttype == chainlink);
widthfactor = (mchainlike ? 1 : 2);
mflagsapply = (mthing->args[8] & TMM_CLIP) ? 0 : (MF_NOCLIP|MF_NOCLIPHEIGHT);
mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0);
meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
msound = (mchainlike ? 0 : (mwidth & 1));
// Quick and easy preparatory variable setting
mphase = (FixedAngle(mphase << FRACBITS) >> ANGLETOFINESHIFT);
mroll = (FixedAngle(mroll << FRACBITS) >> ANGLETOFINESHIFT);
#define makemace(mobjtype, dist, moreflags2) do {\
spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
if (P_MobjWasRemoved(spawnee))\
break;\
P_SetTarget(&spawnee->tracer, mobj);\
spawnee->threshold = mphase;\
spawnee->friction = mroll;\
spawnee->movefactor = mwidthset;\
spawnee->movecount = dist;\
spawnee->angle = myaw;\
spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
spawnee->flags2 |= (mflags2apply|moreflags2);\
spawnee->eflags |= meflagsapply;\
P_SetTarget(&hprev->hnext, spawnee);\
P_SetTarget(&spawnee->hprev, hprev);\
hprev = spawnee;\
} while (0)
mdosound = (mspeed && !(mthing->args[8] & TMM_SILENT));
mdocenter = (macetype && (mthing->args[8] & TMM_CENTERLINK));
// The actual spawning of spokes
while (mnumspokes-- > 0)
{
// Offsets
if (mthing->args[8] & TMM_SWING) // Swinging
mroll = (mroll - mspokeangle) & FINEMASK;
else // Spinning
mphase = (mphase - mspokeangle) & FINEMASK;
if (mnumnospokes && !(mnumspokes % mnumnospokes)) // Skipping a "missing" spoke
{
if (mobj->type != MT_CHAINMACEPOINT)
continue;
linktype = chainlink;
firsttype = ((mthing->args[8] & TMM_DOUBLESIZE) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN);
mmaxlength = 1 + (mlength - 1) * radiusfactor;
radiusfactor = widthfactor = 1;
}
else
{
if (mobj->type == MT_CHAINMACEPOINT)
{
// Make the links the same type as the end - repeated above
if (mthing->args[8] & TMM_MACELINKS)
{
linktype = macetype;
radiusfactor = 2;
}
else
radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
firsttype = macetype;
widthfactor = 2;
}
mmaxlength = mlength;
}
mwidthset = mwidth;
mlengthset = mminlength;
if (mdocenter) // Innermost link
makemace(linktype, 0, 0);
// Out from the center...
if (linktype)
{
while ((++mlengthset) < mmaxlength)
makemace(linktype, radiusfactor*mlengthset, 0);
}
else
mlengthset = mmaxlength;
// Outermost mace/link
if (firsttype)
makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
if (!mwidth)
{
if (!P_MobjWasRemoved(spawnee) && mdosound && mnumspokes <= mmin) // Can it make a sound?
spawnee->flags2 |= MF2_BOSSNOTRAP;
}
else
{
// Across the bar!
if (!firsttype)
mwidthset = -mwidth;
else if (mwidth > 0)
{
while ((mwidthset -= widthfactor) > -mwidth)
{
makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
if (!P_MobjWasRemoved(spawnee) && mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
spawnee->flags2 |= MF2_BOSSNOTRAP;
}
}
else
{
while ((mwidthset += widthfactor) < -mwidth)
{
makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
if (!P_MobjWasRemoved(spawnee) && mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
spawnee->flags2 |= MF2_BOSSNOTRAP;
}
}
mwidth = -mwidth;
// Outermost mace/link again!
if (firsttype)
makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
// ...and then back into the center!
if (linktype)
while (mlengthset > mminlength)
makemace(linktype, radiusfactor*(mlengthset--), 0);
if (mdocenter) // Innermost link
makemace(linktype, 0, 0);
}
}
#undef makemace
return true;
}
static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
{
fixed_t radius, speed, zdist;
INT32 type, numdivisions, anglespeed, ticcount;
angle_t angledivision;
INT32 line;
const size_t mthingi = (size_t)(mthing - mapthings);
// Find the corresponding linedef special, using args[6] as tag
line = mthing->args[6] ? Tag_FindLineSpecial(15, mthing->args[6]) : -1;
type = mthing->stringargs[0] ? get_number(mthing->stringargs[0]) : MT_PARTICLE;
ticcount = mthing->args[4];
if (ticcount < 1)
ticcount = 3;
numdivisions = mthing->args[0];
if (numdivisions)
{
radius = mthing->args[1] << FRACBITS;
anglespeed = (mthing->args[3]) % 360;
angledivision = 360/numdivisions;
}
else
{
numdivisions = 1; // Simple trick to make P_ParticleGenSceneryThink simpler.
radius = 0;
anglespeed = 0;
angledivision = 0;
}
speed = abs(mthing->args[2]) << FRACBITS;
if (mthing->options & MTF_OBJECTFLIP)
speed *= -1;
zdist = abs(mthing->args[5]) << FRACBITS;
CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n"
"Radius is %d\n"
"Speed is %d\n"
"Anglespeed is %d\n"
"Numdivisions is %d\n"
"Angledivision is %d\n"
"Type is %d\n"
"Tic separation is %d\n",
sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
if (line == -1)
CONS_Debug(DBG_GAMELOGIC, "Spawn Z is %d\nZ dist is %d\n", mobj->z, zdist);
else
CONS_Debug(DBG_GAMELOGIC, "Heights are taken from control sector\n");
mobj->angle = 0;
mobj->movefactor = speed;
mobj->lastlook = numdivisions;
mobj->movedir = angledivision*ANG1;
mobj->movecount = anglespeed*ANG1;
mobj->friction = radius;
mobj->threshold = type;
mobj->reactiontime = ticcount;
mobj->extravalue1 = zdist;
mobj->cvmem = line;
mobj->watertop = mobj->waterbottom = 0;
return true;
}
static boolean P_SetupNiGHTSDrone(mapthing_t *mthing, mobj_t *mobj)
{
boolean flip = mthing->options & MTF_OBJECTFLIP;
INT16 timelimit = mthing->args[0];
fixed_t hitboxheight = mthing->args[1] << FRACBITS;
fixed_t hitboxradius = mthing->args[2] << FRACBITS;
INT32 dronemanalignment = mthing->args[3];
fixed_t oldheight = mobj->height;
fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
if (timelimit > 0)
mobj->health = timelimit;
if (hitboxradius > 0)
mobj->radius = hitboxradius;
if (hitboxheight > 0)
mobj->height = hitboxheight;
else
mobj->height = mobjinfo[MT_NIGHTSDRONE].height;
if (mthing->args[4])
mobj->flags2 |= MF2_AMBUSH; //Kill player upon time up
droneboxmandiff = max(mobj->height - mobjinfo[MT_NIGHTSDRONE_MAN].height, 0);
dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0);
if (flip && mobj->height != oldheight)
P_SetOrigin(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight));
if (!flip)
{
switch (dronemanalignment)
{
case TMDA_BOTTOMOFFSET:
default:
dronemanoffset = FixedMul(24*FRACUNIT, mobj->scale);
goaloffset = dronemangoaldiff + dronemanoffset;
break;
case TMDA_BOTTOM:
dronemanoffset = 0;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
break;
case TMDA_MIDDLE:
dronemanoffset = droneboxmandiff/2;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
break;
case TMDA_TOP:
dronemanoffset = droneboxmandiff;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
break;
}
sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
}
else
{
mobj->eflags |= MFE_VERTICALFLIP;
mobj->flags2 |= MF2_OBJECTFLIP;
switch (dronemanalignment)
{
case TMDA_BOTTOMOFFSET:
default:
dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
goaloffset = dronemangoaldiff + dronemanoffset;
break;
case TMDA_BOTTOM:
dronemanoffset = droneboxmandiff;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
break;
case TMDA_MIDDLE:
dronemanoffset = droneboxmandiff/2;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
break;
case TMDA_TOP:
dronemanoffset = 0;
goaloffset = dronemangoaldiff/2 + dronemanoffset;
break;
}
sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
}
// spawn visual elements
{
mobj_t *goalpost = P_SpawnMobjFromMobj(mobj, 0, 0, goaloffset, MT_NIGHTSDRONE_GOAL);
mobj_t *sparkle = P_SpawnMobjFromMobj(mobj, 0, 0, sparkleoffset, MT_NIGHTSDRONE_SPARKLING);
mobj_t *droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
P_SetTarget(&mobj->target, goalpost);
if (!P_MobjWasRemoved(goalpost))
{
P_SetTarget(&goalpost->target, sparkle);
P_SetTarget(&goalpost->tracer, droneman);
}
// correct Z position
if (flip)
{
if (!P_MobjWasRemoved(goalpost))
P_MoveOrigin(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset);
if (!P_MobjWasRemoved(sparkle))
P_MoveOrigin(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset);
if (!P_MobjWasRemoved(droneman))
P_MoveOrigin(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset);
}
// Remember position preference for later
mobj->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE);
switch (dronemanalignment)
{
case TMDA_BOTTOMOFFSET:
default:
mobj->flags |= MF_SLIDEME|MF_GRENADEBOUNCE;
break;
case TMDA_BOTTOM:
break;
case TMDA_MIDDLE:
mobj->flags |= MF_GRENADEBOUNCE;
break;
case TMDA_TOP:
mobj->flags |= MF_SLIDEME;
break;
}
// Remember old Z position and flags for correction detection
if (!P_MobjWasRemoved(goalpost))
{
goalpost->movefactor = mobj->z;
goalpost->friction = mobj->height;
goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
}
}
return true;
}
static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong)
{
angle_t angle = FixedAngle(mthing->angle << FRACBITS);
fixed_t x1 = FINECOSINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
fixed_t y1 = FINESINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
fixed_t x2 = FINECOSINE(((angle + ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
fixed_t y2 = FINESINE(((angle + ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
statenum_t facestate = strong ? S_REDBOOSTERSEG_FACE : S_YELLOWBOOSTERSEG_FACE;
statenum_t leftstate = strong ? S_REDBOOSTERSEG_LEFT : S_YELLOWBOOSTERSEG_LEFT;
statenum_t rightstate = strong ? S_REDBOOSTERSEG_RIGHT : S_YELLOWBOOSTERSEG_RIGHT;
statenum_t rollerstate = strong ? S_REDBOOSTERROLLER : S_YELLOWBOOSTERROLLER;
mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle - ANGLE_90;
P_SetMobjState(seg, facestate);
}
seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle + ANGLE_90;
P_SetMobjState(seg, facestate);
}
seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle;
P_SetMobjState(seg, leftstate);
}
seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle;
P_SetMobjState(seg, rightstate);
}
seg = P_SpawnMobjFromMobj(mobj, 13*(x1 + x2), 13*(y1 + y2), 0, MT_BOOSTERROLLER);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
}
seg = P_SpawnMobjFromMobj(mobj, 13*(x1 - x2), 13*(y1 - y2), 0, MT_BOOSTERROLLER);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
}
seg = P_SpawnMobjFromMobj(mobj, -13*(x1 + x2), -13*(y1 + y2), 0, MT_BOOSTERROLLER);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
}
seg = P_SpawnMobjFromMobj(mobj, -13*(x1 - x2), -13*(y1 - y2), 0, MT_BOOSTERROLLER);
if (!P_MobjWasRemoved(seg))
{
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
}
return true;
}
static mobj_t *P_MakeSoftwareCorona(mobj_t *mo, INT32 height)
{
mobj_t *corona = P_SpawnMobjFromMobj(mo, 0, 0, height<<FRACBITS, MT_PARTICLE);
if (P_MobjWasRemoved(corona))
return NULL;
corona->sprite = SPR_FLAM;
corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12);
corona->tics = -1;
return corona;
}
static boolean P_MapAlreadyHasStarPost(mobj_t *mobj)
{
thinker_t *th;
mobj_t *mo2;
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
mo2 = (mobj_t *)th;
if (mo2 == mobj)
continue;
if (mo2->type == MT_STARPOST && mo2->health == mobj->health)
return true;
}
return false;
}
static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
{
boolean override = LUA_HookMapThingSpawn(mobj, mthing);
if (P_MobjWasRemoved(mobj))
return false;
if (override)
return true;
switch (mobj->type)
{
case MT_EMBLEM:
{
if (!P_SetupEmblem(mthing, mobj))
return false;
break;
}
case MT_SKYBOX:
{
mtag_t tag = Tag_FGet(&mthing->tags);
if (tag < 0 || tag > 15)
{
CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings)));
break;
}
if (mthing->args[0])
skyboxcenterpnts[tag] = mobj;
else
skyboxviewpnts[tag] = mobj;
break;
}
case MT_EGGSTATUE:
if (mthing->args[1])
{
mobj->color = SKINCOLOR_GOLD;
mobj->colorized = true;
}
break;
case MT_EGGMOBILE2:
if (!mthing->args[5])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_EGGMOBILE3:
mobj->cusval = mthing->args[0];
break;
case MT_BUBBLES:
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_FAN:
if (mthing->args[1] & TMF_INVISIBLE)
{
P_UnsetThingPosition(mobj);
if (sector_list)
{
P_DelSeclist(sector_list);
sector_list = NULL;
}
mobj->flags |= MF_NOSECTOR; // this flag basically turns it invisible
P_SetThingPosition(mobj);
}
if (mthing->args[1] & TMF_NODISTANCECHECK)
mobj->flags2 |= MF2_AMBUSH;
if (mthing->args[0])
mobj->health = mthing->args[0];
else
mobj->health = FixedMul(mobj->subsector->sector->ceilingheight - mobj->subsector->sector->floorheight, 3*(FRACUNIT/4)) >> FRACBITS;
break;
case MT_FANG:
if (mthing->args[5] & TMF_GRAYSCALE)
{
mobj->color = SKINCOLOR_SILVER;
mobj->colorized = true;
mobj->flags2 |= MF2_SLIDEPUSH;
}
if (mthing->args[5] & TMF_SKIPINTRO)
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_METALSONIC_BATTLE:
if (mthing->args[5])
{
mobj->color = SKINCOLOR_SILVER;
mobj->colorized = true;
mobj->flags2 |= MF2_SLIDEPUSH;
}
break;
case MT_METALSONIC_RACE:
case MT_ROSY:
if (mthing->args[0])
{
mobj->color = SKINCOLOR_SILVER;
mobj->colorized = true;
mobj->flags2 |= MF2_SLIDEPUSH;
}
break;
case MT_BALLOON:
if (mthing->stringargs[0])
mobj->color = get_number(mthing->stringargs[0]);
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_FLAME:
if (mthing->args[0])
{
mobj_t *corona = P_MakeSoftwareCorona(mobj, 20);
if (P_MobjWasRemoved(corona))
break;
P_SetTarget(&mobj->tracer, corona);
P_SetScale(corona, 3*mobj->scale, true);
}
break;
case MT_FLAMEHOLDER:
if (!(mthing->args[0] & TMFH_NOFLAME)) // Spawn the fire
{
mobj_t *flame = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_FLAME);
if (P_MobjWasRemoved(flame))
break;
P_SetTarget(&flame->target, mobj);
flame->flags2 |= MF2_BOSSNOTRAP;
if (mthing->args[0] & TMFH_CORONA)
{
mobj_t *corona = P_MakeSoftwareCorona(flame, 20);
if (P_MobjWasRemoved(corona))
break;
P_SetTarget(&flame->tracer, corona);
P_SetScale(corona, 3*flame->scale, true);
}
}
break;
case MT_CANDLE:
case MT_CANDLEPRICKET:
if (mthing->args[0])
P_MakeSoftwareCorona(mobj, ((mobj->type == MT_CANDLE) ? 42 : 176));
break;
case MT_JACKO1:
case MT_JACKO2:
case MT_JACKO3:
if (!(mthing->args[0])) // take the torch out of the crafting recipe
{
mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
if (P_MobjWasRemoved(overlay))
break;
P_SetTarget(&overlay->target, mobj);
P_SetMobjState(overlay, mobj->info->raisestate);
}
break;
case MT_WATERDRIP:
mobj->tics = 3*TICRATE + mthing->args[0];
break;
case MT_FLAMEJET:
case MT_VERTICALFLAMEJET:
mobj->movecount = mthing->args[0];
mobj->threshold = mthing->args[1];
mobj->movedir = mthing->args[2];
if (mthing->args[3])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_MACEPOINT:
case MT_CHAINMACEPOINT:
case MT_SPRINGBALLPOINT:
case MT_CHAINPOINT:
case MT_FIREBARPOINT:
case MT_CUSTOMMACEPOINT:
if (!P_SetupMace(mthing, mobj, doangle))
return false;
break;
case MT_PARTICLEGEN:
if (!P_SetupParticleGen(mthing, mobj))
return false;
break;
case MT_POPUPTURRET:
mobj->threshold = mthing->args[0] ? mthing->args[0] : (TICRATE*2)-1;
break;
case MT_NIGHTSBUMPER:
// Pitch of the bumper is set in 30 degree increments.
mobj->threshold = ((mthing->pitch/30) + 3) % 12;
P_SetMobjState(mobj, mobj->info->spawnstate + mobj->threshold);
break;
case MT_EGGCAPSULE:
mobj->threshold = min(mthing->args[0], 7);
mobj->health = mthing->args[1];
if (mobj->health <= 0)
mobj->health = 20; // prevent 0 health
break;
case MT_TUBEWAYPOINT:
{
UINT8 sequence = mthing->args[0];
UINT8 id = mthing->args[1];
mobj->health = id;
mobj->threshold = sequence;
P_AddWaypoint(sequence, id, mobj);
break;
}
case MT_IDEYAANCHOR:
mobj->health = mthing->args[0];
break;
case MT_NIGHTSDRONE:
if (!P_SetupNiGHTSDrone(mthing, mobj))
return false;
break;
case MT_HIVEELEMENTAL:
if (mthing->args[0])
mobj->extravalue1 = mthing->args[0];
break;
case MT_GLAREGOYLE:
case MT_GLAREGOYLEUP:
case MT_GLAREGOYLEDOWN:
case MT_GLAREGOYLELONG:
mobj->tics += mthing->args[1]; // starting delay
break;
case MT_DSZSTALAGMITE:
case MT_DSZ2STALAGMITE:
case MT_KELP:
if (mthing->args[0]) // make mobj twice as big as normal
P_SetScale(mobj, 2*mobj->scale, true); // not 2*FRACUNIT in case of something like the old ERZ3 mode
break;
case MT_THZTREE:
{ // Spawn the branches
angle_t mobjangle = FixedAngle((mthing->angle % 113) << FRACBITS);
mobj_t *branch = P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH);
if (!P_MobjWasRemoved(branch))
branch->angle = mobjangle + ANGLE_22h;
branch = P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH);
if (!P_MobjWasRemoved(branch))
branch->angle = mobjangle + ANGLE_157h;
branch = P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH);
if (!P_MobjWasRemoved(branch))
branch->angle = mobjangle + ANGLE_270;
}
break;
case MT_TUTORIALPLANT:
{
INT32 i;
mobj_t *segment;
for (i = 0; i < 6; i++)
{
segment = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_TUTORIALLEAF);
if (P_MobjWasRemoved(segment))
continue;
segment->angle = mobj->angle + FixedAngle(i*60*FRACUNIT);
P_SetMobjState(segment, S_TUTORIALLEAF1 + mthing->args[0]);
}
for (i = 0; i < 3; i++)
{
segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWER);
if (P_MobjWasRemoved(segment))
continue;
segment->angle = mobj->angle + FixedAngle(i*120*FRACUNIT);
P_SetMobjState(segment, S_TUTORIALFLOWER1 + mthing->args[0]);
}
segment = P_SpawnMobjFromMobj(mobj, 0, 0, 112*FRACUNIT, MT_TUTORIALFLOWERF);
if (!P_MobjWasRemoved(segment))
P_SetMobjState(segment, S_TUTORIALFLOWERF1 + mthing->args[0]);
}
break;
case MT_CEZPOLE1:
case MT_CEZPOLE2:
{ // Spawn the banner
angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS);
mobj_t *banner = P_SpawnMobjFromMobj(mobj,
P_ReturnThrustX(mobj, mobjangle, 4 << FRACBITS),
P_ReturnThrustY(mobj, mobjangle, 4 << FRACBITS),
0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2));
if (!P_MobjWasRemoved(banner))
banner->angle = mobjangle + ANGLE_90;
}
break;
case MT_HHZTREE_TOP:
{ // Spawn the branches
angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS) & (ANGLE_90 - 1);
mobj_t* leaf;
#define doleaf(x, y) \
leaf = P_SpawnMobjFromMobj(mobj, x, y, 0, MT_HHZTREE_PART);\
if (!P_MobjWasRemoved(leaf))\
{\
leaf->angle = mobjangle;\
P_SetMobjState(leaf, leaf->info->seestate);\
}\
mobjangle += ANGLE_90
doleaf(FRACUNIT, 0);
doleaf(0, FRACUNIT);
doleaf(-FRACUNIT, 0);
doleaf(0, -FRACUNIT);
#undef doleaf
}
break;
case MT_SMASHINGSPIKEBALL:
if (mthing->args[0] > 0)
mobj->tics += mthing->args[0];
break;
case MT_LAVAFALL:
mobj->fuse = 30 + mthing->args[0];
if (mthing->args[1])
P_SetScale(mobj, 2*mobj->scale, true);
break;
case MT_PYREFLY:
//start on fire if args[0], otherwise behave normally
if (mthing->args[0])
{
P_SetMobjState(mobj, mobj->info->meleestate);
mobj->extravalue2 = 2;
S_StartSound(mobj, sfx_s3kc2l);
}
break;
case MT_BIGFERN:
{
angle_t angle = FixedAngle(mthing->angle << FRACBITS);
UINT8 j;
for (j = 0; j < 8; j++)
{
angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
fixed_t xoffs = FINECOSINE(fa);
fixed_t yoffs = FINESINE(fa);
mobj_t *leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF);
if (!P_MobjWasRemoved(leaf))
leaf->angle = angle;
angle += ANGLE_45;
}
break;
}
case MT_REDBOOSTER:
case MT_YELLOWBOOSTER:
if (!P_SetupBooster(mthing, mobj, mobj->type == MT_REDBOOSTER))
return false;
break;
case MT_AXIS:
// Inverted if args[3] is set
if (mthing->args[3])
mobj->flags2 |= MF2_AMBUSH;
mobj->radius = abs(mthing->args[2]) << FRACBITS;
// FALLTHRU
case MT_AXISTRANSFER:
case MT_AXISTRANSFERLINE:
// Mare it belongs to
mobj->threshold = min(mthing->args[0], 7);
// # in the mare
mobj->health = mthing->args[1];
mobj->flags2 |= MF2_AXIS;
break;
case MT_TOKEN:
// We advanced tokenbits earlier due to the return check.
// Subtract 1 here for the correct value.
mobj->health = 1 << (tokenbits - 1);
break;
case MT_CYBRAKDEMON:
if (mthing->args[6] & TMB_BARRIER)
{
mobj_t* elecmobj;
elecmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_CYBRAKDEMON_ELECTRIC_BARRIER);
if (P_MobjWasRemoved(elecmobj))
break;
P_SetTarget(&elecmobj->target, mobj);
elecmobj->angle = FixedAngle(mthing->angle << FRACBITS);
P_SetScale(elecmobj, 2*mobj->scale, true);
}
break;
case MT_STARPOST:
mobj->health = mthing->args[0] + 1;
if (!P_MapAlreadyHasStarPost(mobj))
numstarposts++;
break;
case MT_SPIKE:
// Pop up spikes!
if (mthing->args[0])
{
mobj->flags &= ~MF_SCENERY;
mobj->fuse = mthing->args[1];
}
if (mthing->args[2] & TMSF_RETRACTED)
P_SetMobjState(mobj, mobj->info->meleestate);
// Use per-thing collision for spikes unless the intangible flag is checked.
if (!(mthing->args[2] & TMSF_INTANGIBLE) && !metalrecording)
{
P_UnsetThingPosition(mobj);
mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
mobj->flags |= MF_SOLID;
P_SetThingPosition(mobj);
}
break;
case MT_WALLSPIKE:
// Pop up spikes!
if (mthing->args[0])
{
mobj->flags &= ~MF_SCENERY;
mobj->fuse = mthing->args[1];
}
if (mthing->args[2] & TMSF_RETRACTED)
P_SetMobjState(mobj, mobj->info->meleestate);
// Use per-thing collision for spikes unless the intangible flag is checked.
if (!(mthing->args[2] & TMSF_INTANGIBLE) && !metalrecording)
{
P_UnsetThingPosition(mobj);
mobj->flags &= ~(MF_NOBLOCKMAP | MF_NOCLIPHEIGHT);
mobj->flags |= MF_SOLID;
P_SetThingPosition(mobj);
}
// spawn base
{
const angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); // the mobj's own angle hasn't been set quite yet so...
const fixed_t baseradius = mobj->radius - mobj->scale;
mobj_t* base = P_SpawnMobj(
mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius),
mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius),
mobj->z, MT_WALLSPIKEBASE);
if (P_MobjWasRemoved(base))
{
// if we can't spawn the base, don't spawn the spike at all.
P_RemoveMobj(mobj);
return false;
}
base->angle = mobjangle + ANGLE_90;
P_SetScale(base, mobj->scale, true);
base->destscale = mobj->destscale;
P_SetTarget(&base->target, mobj);
P_SetTarget(&mobj->tracer, base);
}
break;
case MT_RING_BOX:
//count 10 ring boxes into the number of rings equation too.
if (nummaprings >= 0)
nummaprings += 10;
break;
case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED:
if (mthing->args[0])
{
fixed_t offset = FixedMul(16*FRACUNIT, mobj->scale);
mobj->momx += P_RandomChance(FRACUNIT/2) ? offset : -offset;
mobj->momy += P_RandomChance(FRACUNIT/2) ? offset : -offset;
mobj->momz += offset;
mobj->flags2 |= MF2_AMBUSH;
}
break;
case MT_REDFLAG:
redflag = mobj;
rflagpoint = mobj->spawnpoint;
break;
case MT_BLUEFLAG:
blueflag = mobj;
bflagpoint = mobj->spawnpoint;
break;
case MT_NIGHTSSTAR:
if (maptol & TOL_XMAS)
P_SetMobjState(mobj, mobj->info->seestate);
break;
case MT_YELLOWDIAG:
case MT_REDDIAG:
case MT_BLUEDIAG:
mobj->angle = FixedAngle(mthing->angle << FRACBITS);
if (mthing->args[0] & TMDS_NOGRAVITY)
mobj->flags |= MF_NOGRAVITY;
if (mthing->args[0] & TMDS_ROTATEEXTRA)
mobj->angle += ANGLE_22h;
*doangle = false;
break;
case MT_AMBIENT:
if (mthing->stringargs[0])
mobj->threshold = get_number(mthing->stringargs[0]);
mobj->health = mthing->args[0] ? mthing->args[0] : TICRATE;
break;
case MT_GOLDBUZZ:
case MT_REDBUZZ:
case MT_JETTBOMBER:
case MT_JETTGUNNER:
case MT_ROBOHOOD:
case MT_CRUSHSTACEAN:
case MT_BANPYURA:
case MT_BUMBLEBORE:
case MT_CACOLANTERN:
case MT_PIAN:
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_EGGGUARD:
if (mthing->args[1])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_STEAM:
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_SALOONDOORCENTER:
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_MINECARTSWITCHPOINT:
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
break;
case MT_ROLLOUTSPAWN:
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
break;
default:
break;
}
if (mobj->flags & MF_BOSS)
{
if (mthing->args[1]) // No egg trap for this boss
mobj->flags2 |= MF2_BOSSNOTRAP;
}
if (mobj->flags & MF_NIGHTSITEM)
{
// Requires you to be in bonus time to activate
if (mthing->args[0] & TMNI_BONUSONLY)
mobj->flags2 |= MF2_STRONGBOX;
// Spawn already displayed
if (mthing->args[0] & TMNI_REVEAL)
{
mobj->flags |= MF_SPECIAL;
mobj->flags &= ~MF_NIGHTSITEM;
}
}
if (mobj->flags & MF_PUSHABLE)
{
switch (mthing->args[0])
{
case TMP_NORMAL:
default:
break;
case TMP_SLIDE:
mobj->flags2 |= MF2_SLIDEPUSH;
mobj->flags |= MF_BOUNCE;
break;
case TMP_IMMOVABLE:
mobj->flags &= ~MF_PUSHABLE;
break;
case TMP_CLASSIC:
mobj->flags2 |= MF2_CLASSICPUSH;
break;
}
}
if (mobj->flags & MF_SPRING && mobj->info->painchance == 3)
{
if (mthing->args[0])
mobj->flags2 |= MF2_AMBUSH;
}
if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
{
switch (mthing->args[1])
{
case TMMR_SAME:
default:
break;
case TMMR_WEAK:
mobj->flags2 |= MF2_AMBUSH;
break;
case TMMR_STRONG:
mobj->flags2 |= MF2_STRONGBOX;
}
}
// Custom ambient sounds
if ((mobj->flags & MF_AMBIENT) && mobj->type != MT_AMBIENT)
{
mobj->threshold = mobj->info->seesound;
mobj->health = mobj->info->spawnhealth;
}
return true;
}
// TODO: 2.3: Delete (Pre-UDMF backwards compatibility stuff)
static void P_SetAmbush(mapthing_t *mthing, mobj_t *mobj)
{
if (mobj->type == MT_NIGHTSBUMPER
|| mobj->type == MT_AXIS
|| mobj->type == MT_AXISTRANSFER
|| mobj->type == MT_AXISTRANSFERLINE
|| mobj->type == MT_NIGHTSBUMPER
|| mobj->type == MT_STARPOST)
return;
if ((mthing->options & MTF_OBJECTSPECIAL) && (mobj->flags & MF_PUSHABLE))
return;
mobj->flags2 |= MF2_AMBUSH;
}
static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i)
{
mobj_t *mobj = NULL;
boolean doangle = true;
mobj = P_SpawnMobj(x, y, z, i);
if (mobj == NULL)
return NULL;
mobj->spawnpoint = mthing;
P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale), false);
mobj->destscale = FixedMul(mobj->destscale, mthing->scale);
mobj->old_scale = FixedMul(mobj->old_scale, mthing->scale);
mobj->spritexscale = mthing->spritexscale;
mobj->spriteyscale = mthing->spriteyscale;
if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
return mobj;
if (doangle)
mobj->angle = FixedAngle(mthing->angle << FRACBITS);
mobj->pitch = FixedAngle(mthing->pitch << FRACBITS);
mobj->roll = FixedAngle(mthing->roll << FRACBITS);
mthing->mobj = mobj;
if (!udmf && (mthing->options & MTF_AMBUSH))
P_SetAmbush(mthing, mobj);
// Generic reverse gravity for individual objects flag.
if (mthing->options & MTF_OBJECTFLIP)
{
mobj->eflags |= MFE_VERTICALFLIP;
mobj->flags2 |= MF2_OBJECTFLIP;
}
// Final set of not being able to draw nightsitems.
if (mobj->flags & MF_NIGHTSITEM)
mobj->flags2 |= MF2_DONTDRAW;
return mobj;
}
//
// P_SpawnMapThing
// The fields of the mapthing should
// already be in host byte order.
//
mobj_t *P_SpawnMapThing(mapthing_t *mthing)
{
mobjtype_t i;
mobj_t *mobj = NULL;
fixed_t x, y, z;
if (!mthing->type)
return mobj; // Ignore type-0 things as NOPs
if (mthing->type == 3328) // 3D Mode start Thing
return mobj;
if (!objectplacing && P_SpawnNonMobjMapThing(mthing))
return mobj;
i = P_GetMobjtype(mthing->type);
if (i == MT_UNKNOWN)
CONS_Alert(CONS_WARNING, M_GetText("Unknown thing type %d placed at (%d, %d)\n"), mthing->type, mthing->x, mthing->y);
// Skip all returning/substitution code in objectplace.
if (!objectplacing)
{
if (!P_AllowMobjSpawn(mthing, i))
return mobj;
i = P_GetMobjtypeSubstitute(mthing, i);
if (i == MT_NULL) // Don't spawn mobj
return mobj;
}
x = mthing->x << FRACBITS;
y = mthing->y << FRACBITS;
z = P_GetMapThingSpawnHeight(i, mthing, x, y);
return P_SpawnMobjFromMapThing(mthing, x, y, z, i);
}
void P_SpawnHoop(mapthing_t *mthing)
{
if (metalrecording)
return;
mobj_t *mobj = NULL;
mobj_t *nextmobj = NULL;
mobj_t *hoopcenter;
matrix_t pitchmatrix, yawmatrix;
fixed_t radius = mthing->args[0] << FRACBITS;
fixed_t sizefactor = 4*FRACUNIT;
fixed_t hoopsize = radius/sizefactor;
INT32 i;
angle_t fa;
vector4_t v, res;
fixed_t x = mthing->x << FRACBITS;
fixed_t y = mthing->y << FRACBITS;
fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale, mthing->options & MTF_ABSOLUTEZ);
hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER);
if (P_MobjWasRemoved(hoopcenter))
return;
hoopcenter->spawnpoint = mthing;
hoopcenter->z -= hoopcenter->height/2;
P_UnsetThingPosition(hoopcenter);
hoopcenter->x = x;
hoopcenter->y = y;
P_SetThingPosition(hoopcenter);
hoopcenter->movedir = mthing->pitch;
FM_RotateX(&pitchmatrix, FixedAngle(hoopcenter->movedir << FRACBITS));
hoopcenter->movecount = mthing->angle;
FM_RotateZ(&yawmatrix, FixedAngle(hoopcenter->movecount << FRACBITS));
// For the hoop when it flies away
hoopcenter->extravalue1 = hoopsize;
hoopcenter->extravalue2 = radius/12;
// Create the hoop!
for (i = 0; i < hoopsize; i++)
{
fa = i*(FINEANGLES/hoopsize);
v.x = FixedMul(FINECOSINE(fa), radius);
v.y = 0;
v.z = FixedMul(FINESINE(fa), radius);
v.a = FRACUNIT;
FV4_Copy(&v, FM_MultMatrixVec4(&pitchmatrix, &v, &res));
FV4_Copy(&v, FM_MultMatrixVec4(&yawmatrix, &v, &res));
mobj = P_SpawnMobj(x + v.x, y + v.y, z + v.z, MT_HOOP);
if (P_MobjWasRemoved(mobj))
continue;
mobj->z -= mobj->height/2;
if (maptol & TOL_XMAS)
P_SetMobjState(mobj, mobj->info->seestate + (i & 1));
P_SetTarget(&mobj->target, hoopcenter); // Link the sprite to the center.
mobj->fuse = 0;
// Link all the sprites in the hoop together
if (nextmobj)
{
P_SetTarget(&mobj->hprev, nextmobj);
P_SetTarget(&mobj->hprev->hnext, mobj);
}
else
P_SetTarget(&mobj->hprev, P_SetTarget(&mobj->hnext, NULL));
nextmobj = mobj;
}
// Create the collision detectors!
// Create them until the size is less than 8
// But always create at least ONE set of collision detectors
do
{
if (hoopsize >= 32)
hoopsize -= 16;
else
hoopsize /= 2;
radius = hoopsize*sizefactor;
for (i = 0; i < hoopsize; i++)
{
fa = i*(FINEANGLES/hoopsize);
v.x = FixedMul(FINECOSINE(fa), radius);
v.y = 0;
v.z = FixedMul(FINESINE(fa), radius);
v.a = FRACUNIT;
FV4_Copy(&v, FM_MultMatrixVec4(&pitchmatrix, &v, &res));
FV4_Copy(&v, FM_MultMatrixVec4(&yawmatrix, &v, &res));
mobj = P_SpawnMobj(x + v.x, y + v.y, z + v.z, MT_HOOPCOLLIDE);
if (P_MobjWasRemoved(mobj))
continue;
mobj->z -= mobj->height/2;
// Link all the collision sprites together.
P_SetTarget(&mobj->hnext, NULL);
P_SetTarget(&mobj->hprev, nextmobj);
P_SetTarget(&mobj->hprev->hnext, mobj);
nextmobj = mobj;
}
} while (hoopsize >= 8);
}
void P_SetBonusTime(mobj_t *mobj)
{
if (!mobj)
return;
if (mobj->type != MT_BLUESPHERE && mobj->type != MT_NIGHTSCHIP)
return;
P_SetMobjState(mobj, mobj->info->raisestate);
}
static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
{
mapthing_t dummything;
mobj_t *mobj = NULL;
fixed_t x = mthing->x << FRACBITS;
fixed_t y = mthing->y << FRACBITS;
fixed_t z = mthing->z << FRACBITS;
INT32 r;
angle_t angle = FixedAngle(fixedangle << FRACBITS);
angle_t fineangle = (angle >> ANGLETOFINESHIFT) & FINEMASK;
for (r = 0; r < numitemtypes; r++)
{
dummything = *mthing;
dummything.type = mobjinfo[itemtypes[r]].doomednum;
// Skip all returning/substitution code in objectplace.
if (!objectplacing)
{
if (!P_AllowMobjSpawn(&dummything, itemtypes[r]))
{
itemtypes[r] = MT_NULL;
continue;
}
itemtypes[r] = P_GetMobjtypeSubstitute(&dummything, itemtypes[r]);
}
}
z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale, mthing->options & MTF_ABSOLUTEZ);
for (r = 0; r < numitems; r++)
{
mobjtype_t itemtype = itemtypes[r % numitemtypes];
if (itemtype == MT_NULL)
continue;
dummything.type = mobjinfo[itemtype].doomednum;
x += FixedMul(horizontalspacing, FINECOSINE(fineangle));
y += FixedMul(horizontalspacing, FINESINE(fineangle));
z += (mthing->options & MTF_OBJECTFLIP) ? -verticalspacing : verticalspacing;
mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype);
if (!mobj)
continue;
mobj->spawnpoint = NULL;
if (bonustime)
P_SetBonusTime(mobj);
}
}
static void P_SpawnSingularItemRow(mapthing_t *mthing, mobjtype_t itemtype, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
{
mobjtype_t itemtypes[1] = { itemtype };
P_SpawnItemRow(mthing, itemtypes, 1, numitems, horizontalspacing, verticalspacing, fixedangle, bonustime);
}
static void P_SpawnItemCircle(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t size, boolean bonustime)
{
mapthing_t dummything;
mobj_t* mobj = NULL;
fixed_t x = mthing->x << FRACBITS;
fixed_t y = mthing->y << FRACBITS;
fixed_t z = mthing->z << FRACBITS;
angle_t angle = FixedAngle(mthing->angle << FRACBITS);
angle_t fa;
INT32 i;
matrix_t m;
vector4_t v, res;
for (i = 0; i < numitemtypes; i++)
{
dummything = *mthing;
dummything.type = mobjinfo[itemtypes[i]].doomednum;
// Skip all returning/substitution code in objectplace.
if (!objectplacing)
{
if (!P_AllowMobjSpawn(&dummything, itemtypes[i]))
{
itemtypes[i] = MT_NULL;
continue;
}
itemtypes[i] = P_GetMobjtypeSubstitute(&dummything, itemtypes[i]);
}
}
z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, false, mthing->scale, mthing->options & MTF_ABSOLUTEZ);
for (i = 0; i < numitems; i++)
{
mobjtype_t itemtype = itemtypes[i % numitemtypes];
if (itemtype == MT_NULL)
continue;
dummything.type = mobjinfo[itemtype].doomednum;
fa = i*FINEANGLES/numitems;
v.x = FixedMul(FINECOSINE(fa), size);
v.y = 0;
v.z = FixedMul(FINESINE(fa), size);
v.a = FRACUNIT;
FM_RotateZ(&m, angle);
FV4_Copy(&v, FM_MultMatrixVec4(&m, &v, &res));
mobj = P_SpawnMobjFromMapThing(&dummything, x + v.x, y + v.y, z + v.z, itemtype);
if (!mobj)
continue;
mobj->z -= mobj->height/2;
mobj->spawnpoint = NULL;
if (bonustime)
P_SetBonusTime(mobj);
}
}
static void P_ParseItemTypes(char *itemstring, mobjtype_t *itemtypes, UINT8 *numitemtypes)
{
char *tok;
*numitemtypes = 0;
if (itemstring)
{
char *stringcopy = Z_Malloc(strlen(itemstring) + 1, PU_LEVEL, NULL);
M_Memcpy(stringcopy, itemstring, strlen(itemstring));
stringcopy[strlen(itemstring)] = '\0';
tok = strtok(stringcopy, " ");
while (tok && *numitemtypes < 128)
{
itemtypes[*numitemtypes] = get_number(tok);
tok = strtok(NULL, " ");
(*numitemtypes)++;
}
Z_Free(stringcopy);
}
else
{
//If no types are supplied, default to ring
itemtypes[0] = MT_RING;
*numitemtypes = 1;
}
}
void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime)
{
switch (mthing->type)
{
// Special placement patterns
case 600: // 5 vertical rings (yellow spring)
P_SpawnSingularItemRow(mthing, MT_RING, 5, 0, 64*FRACUNIT, 0, bonustime);
return;
case 601: // 5 vertical rings (red spring)
P_SpawnSingularItemRow(mthing, MT_RING, 5, 0, 128*FRACUNIT, 0, bonustime);
return;
case 602: // 5 diagonal rings (yellow spring)
P_SpawnSingularItemRow(mthing, MT_RING, 5, 64*FRACUNIT, 64*FRACUNIT, mthing->angle, bonustime);
return;
case 603: // 10 diagonal rings (red spring)
P_SpawnSingularItemRow(mthing, MT_RING, 10, 64*FRACUNIT, 64*FRACUNIT, mthing->angle, bonustime);
return;
case 604: // Circle of rings (8 items)
case 605: // Circle of rings (16 items)
case 606: // Circle of blue spheres (8 items)
case 607: // Circle of blue spheres (16 items)
{
INT32 numitems = (mthing->type & 1) ? 16 : 8;
fixed_t size = (mthing->type & 1) ? 192*FRACUNIT : 96*FRACUNIT;
mobjtype_t itemtypes[1] = { (mthing->type < 606) ? MT_RING : MT_BLUESPHERE };
P_SpawnItemCircle(mthing, itemtypes, 1, numitems, size, bonustime);
return;
}
case 608: // Circle of rings and blue spheres (8 items)
case 609: // Circle of rings and blue spheres (16 items)
{
INT32 numitems = (mthing->type & 1) ? 16 : 8;
fixed_t size = (mthing->type & 1) ? 192*FRACUNIT : 96*FRACUNIT;
mobjtype_t itemtypes[2] = { MT_RING, MT_BLUESPHERE };
P_SpawnItemCircle(mthing, itemtypes, 2, numitems, size, bonustime);
return;
}
case 610: // Generic item row
{
mobjtype_t itemtypes[128]; //If you want to have a row with more than 128 different object types, you're crazy.
UINT8 numitemtypes;
if (!udmf)
return;
P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes);
P_SpawnItemRow(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, mthing->args[2] << FRACBITS, mthing->angle, bonustime);
return;
}
case 611: // Generic item circle
{
mobjtype_t itemtypes[128]; //If you want to have a circle with more than 128 different object types, you're crazy.
UINT8 numitemtypes;
if (!udmf)
return;
P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes);
P_SpawnItemCircle(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, bonustime);
return;
}
default:
return;
}
}
//
// P_CheckMissileSpawn
// Moves the missile forward a bit and possibly explodes it right there.
//
boolean P_CheckMissileSpawn(mobj_t *th)
{
// move a little forward so an angle can be computed if it immediately explodes
if (!(th->flags & MF_GRENADEBOUNCE)) // hack: bad! should be a flag.
{
th->x += th->momx>>1;
th->y += th->momy>>1;
th->z += th->momz>>1;
}
if (!P_TryMove(th, th->x, th->y, true))
{
if (!P_MobjWasRemoved(th))
P_ExplodeMissile(th);
return false;
}
return true;
}
//
// P_SpawnXYZMissile
//
// Spawns missile at specific coords
//
mobj_t *P_SpawnXYZMissile(mobj_t *source, mobj_t *dest, mobjtype_t type,
fixed_t x, fixed_t y, fixed_t z)
{
mobj_t *th;
angle_t an;
INT32 dist;
fixed_t speed;
I_Assert(source != NULL);
I_Assert(dest != NULL);
if (source->eflags & MFE_VERTICALFLIP)
z -= FixedMul(mobjinfo[type].height, source->scale);
th = P_SpawnMobj(x, y, z, type);
if (P_MobjWasRemoved(th))
return NULL;
if (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
P_SetScale(th, source->scale, true);
speed = FixedMul(th->info->speed, th->scale);
if (speed == 0)
{
CONS_Debug(DBG_GAMELOGIC, "P_SpawnXYZMissile - projectile has 0 speed! (mobj type %d)\n", type);
speed = mobjinfo[MT_ROCKET].speed;
}
if (th->info->seesound)
S_StartSound(th, th->info->seesound);
P_SetTarget(&th->target, source); // where it came from
an = R_PointToAngle2(x, y, dest->x, dest->y);
th->angle = an;
an >>= ANGLETOFINESHIFT;
th->momx = FixedMul(speed, FINECOSINE(an));
th->momy = FixedMul(speed, FINESINE(an));
dist = P_AproxDistance(dest->x - x, dest->y - y);
dist = dist / speed;
if (dist < 1)
dist = 1;
th->momz = (dest->z - z) / dist;
if (th->flags & MF_MISSILE)
dist = P_CheckMissileSpawn(th);
else
dist = 1;
return dist ? th : NULL;
}
//
// P_SpawnAlteredDirectionMissile
//
// Spawns a missile with same source as spawning missile yet an altered direction
//
mobj_t *P_SpawnAlteredDirectionMissile(mobj_t *source, mobjtype_t type, fixed_t x, fixed_t y, fixed_t z, INT32 shiftingAngle)
{
mobj_t *th;
angle_t an;
fixed_t dist, speed;
I_Assert(source != NULL);
if (!(source->target) || !(source->flags & MF_MISSILE))
return NULL;
if (source->eflags & MFE_VERTICALFLIP)
z -= FixedMul(mobjinfo[type].height, source->scale);
th = P_SpawnMobj(x, y, z, type);
if (P_MobjWasRemoved(th))
return NULL;
if (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
P_SetScale(th, source->scale, true);
speed = FixedMul(th->info->speed, th->scale);
if (speed == 0) // Backwards compatibility with 1.09.2
{
CONS_Printf("P_SpawnAlteredDirectionMissile - projectile has 0 speed! (mobj type %d)\nPlease update this SOC.", type);
speed = mobjinfo[MT_ROCKET].speed;
}
if (th->info->seesound)
S_StartSound(th, th->info->seesound);
P_SetTarget(&th->target, source->target); // where it came from
an = R_PointToAngle2(0, 0, source->momx, source->momy) + (ANG1*shiftingAngle);
th->angle = an;
an >>= ANGLETOFINESHIFT;
th->momx = FixedMul(speed, FINECOSINE(an));
th->momy = FixedMul(speed, FINESINE(an));
dist = P_AproxDistance(source->momx*800, source->momy*800);
dist = dist / speed;
if (dist < 1)
dist = 1;
th->momz = (source->momz*800) / dist;
if (th->flags & MF_MISSILE)
{
dist = P_CheckMissileSpawn(th);
th->x -= th->momx>>1;
th->y -= th->momy>>1;
th->z -= th->momz>>1;
}
else
dist = 1;
return dist ? th : NULL;
}
//
// P_SpawnPointMissile
//
// Spawns a missile at specific coords
//
mobj_t *P_SpawnPointMissile(mobj_t *source, fixed_t xa, fixed_t ya, fixed_t za, mobjtype_t type,
fixed_t x, fixed_t y, fixed_t z)
{
mobj_t *th;
angle_t an;
fixed_t dist, speed;
I_Assert(source != NULL);
if (source->eflags & MFE_VERTICALFLIP)
z -= FixedMul(mobjinfo[type].height, source->scale);
th = P_SpawnMobj(x, y, z, type);
if (P_MobjWasRemoved(th))
return NULL;
if (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
P_SetScale(th, source->scale, true);
speed = FixedMul(th->info->speed, th->scale);
if (speed == 0) // Backwards compatibility with 1.09.2
{
CONS_Printf("P_SpawnPointMissile - projectile has 0 speed! (mobj type %d)\nPlease update this SOC.", type);
speed = mobjinfo[MT_ROCKET].speed;
}
if (th->info->seesound)
S_StartSound(th, th->info->seesound);
P_SetTarget(&th->target, source); // where it came from
an = R_PointToAngle2(x, y, xa, ya);
th->angle = an;
an >>= ANGLETOFINESHIFT;
th->momx = FixedMul(speed, FINECOSINE(an));
th->momy = FixedMul(speed, FINESINE(an));
dist = P_AproxDistance(xa - x, ya - y);
dist = dist / speed;
if (dist < 1)
dist = 1;
th->momz = (za - z) / dist;
if (th->flags & MF_MISSILE)
dist = P_CheckMissileSpawn(th);
else
dist = 1;
return dist ? th : NULL;
}
//
// P_SpawnMissile
//
mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type)
{
mobj_t *th;
angle_t an;
INT32 dist;
fixed_t z;
const fixed_t gsf = (fixed_t)6;
fixed_t speed;
I_Assert(source != NULL);
I_Assert(dest != NULL);
if (source->type == MT_JETTGUNNER)
{
if (source->eflags & MFE_VERTICALFLIP)
z = source->z + source->height - FixedMul(4*FRACUNIT, source->scale);
else
z = source->z + FixedMul(4*FRACUNIT, source->scale);
}
else
z = source->z + source->height/2;
if (source->eflags & MFE_VERTICALFLIP)
z -= FixedMul(mobjinfo[type].height, source->scale);
th = P_SpawnMobj(source->x, source->y, z, type);
if (P_MobjWasRemoved(th))
return NULL;
if (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
P_SetScale(th, source->scale, true);
if (source->type == MT_METALSONIC_BATTLE && source->health < 4)
speed = FixedMul(FixedMul(th->info->speed, 3*FRACUNIT/2), th->scale);
else
speed = FixedMul(th->info->speed, th->scale);
if (speed == 0)
{
CONS_Debug(DBG_GAMELOGIC, "P_SpawnMissile - projectile has 0 speed! (mobj type %d)\n", type);
speed = FixedMul(mobjinfo[MT_TURRETLASER].speed, th->scale);
}
if (th->info->seesound)
S_StartSound(source, th->info->seesound);
P_SetTarget(&th->target, source); // where it came from
if (type == MT_TURRETLASER || type == MT_ENERGYBALL) // More accurate!
an = R_PointToAngle2(source->x, source->y,
dest->x + (dest->momx*gsf),
dest->y + (dest->momy*gsf));
else
an = R_PointToAngle2(source->x, source->y, dest->x, dest->y);
th->angle = an;
an >>= ANGLETOFINESHIFT;
th->momx = FixedMul(speed, FINECOSINE(an));
th->momy = FixedMul(speed, FINESINE(an));
if (type == MT_TURRETLASER || type == MT_ENERGYBALL) // More accurate!
dist = P_AproxDistance(dest->x+(dest->momx*gsf) - source->x, dest->y+(dest->momy*gsf) - source->y);
else
dist = P_AproxDistance(dest->x - source->x, dest->y - source->y);
dist = dist / speed;
if (dist < 1)
dist = 1;
if (type == MT_TURRETLASER || type == MT_ENERGYBALL) // More accurate!
th->momz = (dest->z + (dest->momz*gsf) - z) / dist;
else
th->momz = (dest->z - z) / dist;
if (th->flags & MF_MISSILE)
dist = P_CheckMissileSpawn(th);
else
dist = 1;
return dist ? th : NULL;
}
//
// P_ColorTeamMissile
// Colors a player's ring based on their team
//
void P_ColorTeamMissile(mobj_t *missile, player_t *source)
{
if (G_GametypeHasTeams())
{
if (source->ctfteam == 2)
missile->color = skincolor_bluering;
else if (source->ctfteam == 1)
missile->color = skincolor_redring;
}
/*
else
missile->color = player->mo->color; //copy color
*/
}
//
// P_SPMAngle
// Fires missile at angle from what is presumably a player
//
mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 allowaim, UINT32 flags2)
{
mobj_t *th;
angle_t an;
fixed_t x, y, z, slope = 0, speed;
// angle at which you fire, is player angle
an = angle;
if (allowaim) // aiming allowed?
slope = AIMINGTOSLOPE(source->player->aiming);
x = source->x;
y = source->y;
if (source->eflags & MFE_VERTICALFLIP)
z = source->z + 2*source->height/3 - FixedMul(mobjinfo[type].height, source->scale);
else
z = source->z + source->height/3;
th = P_SpawnMobj(x, y, z, type);
if (P_MobjWasRemoved(th))
return NULL;
if (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
P_SetScale(th, source->scale, true);
th->flags2 |= flags2;
// The rail ring has no unique thrown object, so we must do this.
if (th->info->seesound && !(th->flags2 & MF2_RAILRING))
S_StartSound(source, th->info->seesound);
P_SetTarget(&th->target, source);
speed = th->info->speed;
if (source->player && source->player->charability == CA_FLY)
speed = FixedMul(speed, 3*FRACUNIT/2);
th->angle = an;
th->momx = FixedMul(speed, FINECOSINE(an>>ANGLETOFINESHIFT));
th->momy = FixedMul(speed, FINESINE(an>>ANGLETOFINESHIFT));
if (allowaim)
{
th->momx = FixedMul(th->momx,FINECOSINE(source->player->aiming>>ANGLETOFINESHIFT));
th->momy = FixedMul(th->momy,FINECOSINE(source->player->aiming>>ANGLETOFINESHIFT));
}
th->momz = FixedMul(speed, slope);
//scaling done here so it doesn't clutter up the code above
th->momx = FixedMul(th->momx, th->scale);
th->momy = FixedMul(th->momy, th->scale);
th->momz = FixedMul(th->momz, th->scale);
slope = P_CheckMissileSpawn(th);
return slope ? th : NULL;
}
//
// P_FlashPal
// Flashes a player's palette. ARMAGEDDON BLASTS!
//
void P_FlashPal(player_t *pl, UINT16 type, UINT16 duration)
{
if (!pl)
return;
pl->flashcount = duration;
pl->flashpal = type;
}
//
// P_SpawnMobjFromMobj
// Spawns an object with offsets relative to the position of another object.
// Scale, gravity flip, etc. is taken into account automatically.
//
mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zofs, mobjtype_t type)
{
mobj_t *newmobj;
xofs = FixedMul(xofs, mobj->scale);
yofs = FixedMul(yofs, mobj->scale);
zofs = FixedMul(zofs, mobj->scale);
newmobj = P_SpawnMobj(mobj->x + xofs, mobj->y + yofs, mobj->z + zofs, type, NULL);
if (!newmobj)
return NULL;
if (mobj->eflags & MFE_VERTICALFLIP)
{
fixed_t elementheight = FixedMul(newmobj->info->height, mobj->scale);
newmobj->eflags |= MFE_VERTICALFLIP;
newmobj->flags2 |= MF2_OBJECTFLIP;
newmobj->z = mobj->z + mobj->height - zofs - elementheight;
newmobj->old_z = mobj->old_z + mobj->height - zofs - elementheight;
newmobj->old_z2 = mobj->old_z2 + mobj->height - zofs - elementheight;
}
else
{
newmobj->old_z = mobj->old_z + zofs;
newmobj->old_z2 = mobj->old_z2 + zofs;
}
P_SetScale(newmobj, mobj->scale, false);
newmobj->destscale = mobj->destscale;
newmobj->old_x2 = mobj->old_x2 + xofs;
newmobj->old_y2 = mobj->old_y2 + yofs;
newmobj->old_x = mobj->old_x + xofs;
newmobj->old_y = mobj->old_y + yofs;
// This angle hack is needed for Lua scripts that set the angle after
// spawning, to avoid erroneous interpolation.
if (mobj->player)
{
newmobj->old_angle2 = mobj->player->old_drawangle2;
newmobj->old_angle = mobj->player->old_drawangle;
}
else
{
newmobj->old_angle2 = mobj->old_angle2;
newmobj->old_angle = mobj->old_angle;
}
newmobj->old_pitch2 = mobj->old_pitch2;
newmobj->old_pitch = mobj->old_pitch;
newmobj->old_roll2 = mobj->old_roll2;
newmobj->old_roll = mobj->old_roll;
newmobj->old_spriteroll2 = mobj->old_spriteroll2;
newmobj->old_spriteroll = mobj->old_spriteroll;
newmobj->old_scale2 = mobj->old_scale2;
newmobj->old_scale = mobj->old_scale;
newmobj->old_spritexscale2 = mobj->old_spritexscale2;
newmobj->old_spritexscale = mobj->old_spritexscale;
newmobj->old_spriteyscale2 = mobj->old_spriteyscale2;
newmobj->old_spriteyscale = mobj->old_spriteyscale;
newmobj->old_spritexoffset2 = mobj->old_spritexoffset2;
newmobj->old_spritexoffset = mobj->old_spritexoffset;
newmobj->old_spriteyoffset2 = mobj->old_spriteyoffset2;
newmobj->old_spriteyoffset = mobj->old_spriteyoffset;
return newmobj;
}