blankart/src/p_mobj.c
Sally Coolatta cc2704bc87 Death animation
Art is missing, but functionality is complete.

The kart corpse is also placeable as type 4095. No way to set its color currently, it simply defaults to SKINCOLOR_RED.
2021-02-02 22:36:56 -05:00

12740 lines
333 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-2020 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 "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_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 "p_slopes.h"
#include "f_finale.h"
#include "m_cond.h"
// SRB2kart
#include "k_kart.h"
#include "k_battle.h"
#include "k_color.h"
#include "k_respawn.h"
#include "k_bot.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);
#ifdef WALLSPLATS
consvar_t cv_splats = CVAR_INIT ("splats", "On", CV_SAVE, CV_OnOff, NULL);
#endif
actioncache_t actioncachehead;
static mobj_t *overlaycap = NULL;
mobj_t *kitemcap = NULL; // Used for Kart offensive items (the ones that can get removed by sizedown)
mobj_t *waypointcap = 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
//
FUNCINLINE static ATTRINLINE void P_SetupStateAnimation(mobj_t *mobj, state_t *st)
{
INT32 animlength = (mobj->sprite == SPR_PLAY && mobj->skin)
? (INT32)(((skin_t *)mobj->skin)->sprites[mobj->sprite2].numframes) - 1
: 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 && (((++mobj->frame) & FF_FRAMEMASK) >= (UINT32)(((skin_t *)mobj->skin)->sprites[mobj->sprite2].numframes)))
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_SetPlayerMobjState(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
//
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
// Set animation state
// The pflags version of this was just as convoluted.
switch(state)
{
case S_KART_STILL:
case S_KART_STILL_L:
case S_KART_STILL_R:
player->panim = PA_IDLE;
break;
case S_KART_SLOW:
case S_KART_SLOW_L:
case S_KART_SLOW_R:
player->panim = PA_WALK;
break;
case S_KART_FAST:
case S_KART_FAST_L:
case S_KART_FAST_R:
player->panim = PA_RUN;
break;
case S_KART_DRIFT_L:
case S_KART_DRIFT_L_OUT:
case S_KART_DRIFT_L_IN:
case S_KART_DRIFT_R:
case S_KART_DRIFT_R_OUT:
case S_KART_DRIFT_R_IN:
player->panim = PA_DASH;
break;
case S_KART_SPINOUT:
case S_KART_DEAD:
player->panim = PA_PAIN;
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;
// Player animations
if (st->sprite == SPR_PLAY)
{
skin_t *skin = ((skin_t *)mobj->skin);
UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
UINT8 numframes, spr2;
if (skin)
{
spr2 = P_GetSkinSprite2(skin, ((player->powers[pw_super] ? FF_SPR2SUPER : 0)|st->frame) & FF_FRAMEMASK, mobj->player);
numframes = skin->sprites[spr2].numframes;
}
else
{
spr2 = 0;
frame = 0;
numframes = 0;
}
if (mobj->sprite != SPR_PLAY)
{
mobj->sprite = SPR_PLAY;
frame = 0;
}
else if (mobj->sprite2 != spr2)
{
if ((st->frame & FF_SPR2MIDSTART) && numframes && P_RandomChance(FRACUNIT/2))
frame = numframes/2;
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
#ifdef PARANOIA
if (mobj->player != NULL)
I_Error("P_SetMobjState used for player mobj. Use P_SetPlayerMobjState instead!\n(State called: %d)", state);
#endif
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, spr2;
if (skin)
{
spr2 = P_GetSkinSprite2(skin, st->frame & FF_FRAMEMASK, mobj->player);
numframes = skin->sprites[spr2].numframes;
}
else
{
spr2 = 0;
frame = 0;
numframes = 0;
}
if (mobj->sprite != SPR_PLAY)
{
mobj->sprite = SPR_PLAY;
frame = 0;
}
else if (mobj->sprite2 != spr2)
{
if ((st->frame & FF_SPR2MIDSTART) && numframes && P_RandomChance(FRACUNIT/2))
frame = numframes/2;
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_ExplodeMissile
//
void P_ExplodeMissile(mobj_t *mo)
{
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
mo->momx = mo->momy = mo->momz = 0;
if (mo->flags & MF_NOCLIPTHING)
return;
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->flags & FF_EXISTS))
return false;
if ((((rover->flags & FF_BLOCKPLAYER) && mobj->player)
|| ((rover->flags & FF_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(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
{
I_Assert(mobj != NULL);
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 = mobj->radius;
else
testx = -mobj->radius;
if (slope->d.y < 0)
testy = mobj->radius;
else
testy = -mobj->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_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
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) {
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-mobj->radius;
bbox[BOXRIGHT] = x+mobj->radius;
bbox[BOXTOP] = y+mobj->radius;
bbox[BOXBOTTOM] = y-mobj->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(mobj->radius, x, y, ld, slope, true));
else
finalheight = max(finalheight, HighestOnLine(mobj->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(mobj->radius, x, y, line, slope, lowest);
} else // Well, that makes it easy. Just get the floor height
return sector->floorheight;
}
fixed_t P_MobjCeilingZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
{
I_Assert(mobj != NULL);
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 = mobj->radius;
else
testx = -mobj->radius;
if (slope->d.y < 0)
testy = mobj->radius;
else
testy = -mobj->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_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
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) {
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-mobj->radius;
bbox[BOXRIGHT] = x+mobj->radius;
bbox[BOXTOP] = y+mobj->radius;
bbox[BOXBOTTOM] = y-mobj->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(mobj->radius, x, y, ld, slope, true));
else
finalheight = max(finalheight, HighestOnLine(mobj->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(mobj->radius, x, y, line, slope, lowest);
} else // Well, that makes it easy. Just get the ceiling height
return sector->ceilingheight;
}
// Now do the same as all above, but for cameras because apparently cameras are special?
fixed_t P_CameraFloorZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
{
I_Assert(mobj != NULL);
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 = mobj->radius;
else
testx = -mobj->radius;
if (slope->d.y < 0)
testy = mobj->radius;
else
testy = -mobj->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_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
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) {
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-mobj->radius;
bbox[BOXRIGHT] = x+mobj->radius;
bbox[BOXTOP] = y+mobj->radius;
bbox[BOXBOTTOM] = y-mobj->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(mobj->radius, x, y, ld, slope, true));
else
finalheight = max(finalheight, HighestOnLine(mobj->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(mobj->radius, x, y, line, slope, lowest);
} else // Well, that makes it easy. Just get the floor height
return sector->floorheight;
}
fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
{
I_Assert(mobj != NULL);
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 = mobj->radius;
else
testx = -mobj->radius;
if (slope->d.y < 0)
testy = mobj->radius;
else
testy = -mobj->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_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
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) {
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-mobj->radius;
bbox[BOXRIGHT] = x+mobj->radius;
bbox[BOXTOP] = y+mobj->radius;
bbox[BOXBOTTOM] = y-mobj->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(mobj->radius, x, y, ld, slope, true));
else
finalheight = max(finalheight, HighestOnLine(mobj->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(mobj->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((INT32) (mo->player - players));
// Flip aiming to match!
}
//
// P_GetMobjGravity
//
// Returns the current gravity
// value of the object.
//
fixed_t P_GetMobjGravity(mobj_t *mo)
{
fixed_t gravityadd = 0;
boolean no3dfloorgrav = true; // Custom gravity
boolean goopgravity = false;
boolean wasflip;
I_Assert(mo != NULL);
I_Assert(!P_MobjWasRemoved(mo));
wasflip = (mo->eflags & MFE_VERTICALFLIP) != 0;
if (mo->type != MT_SPINFIRE)
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->flags & FF_EXISTS) || !P_InsideANonSolidFFloor(mo, rover)) // P_InsideANonSolidFFloor checks for FF_EXISTS itself, but let's not always call this function
continue;
if ((rover->flags & (FF_SWIMMABLE|FF_GOOWATER)) == (FF_SWIMMABLE|FF_GOOWATER))
goopgravity = true;
if (!(rover->master->frontsector->gravity))
continue;
gravityadd = -FixedMul(gravity,
(FixedDiv(*rover->master->frontsector->gravity>>FRACBITS, 1000)));
if (rover->master->frontsector->verticalflip && gravityadd > 0)
mo->eflags |= MFE_VERTICALFLIP;
no3dfloorgrav = false;
break;
}
}
if (no3dfloorgrav)
{
if (mo->subsector->sector->gravity)
gravityadd = -FixedMul(gravity,
(FixedDiv(*mo->subsector->sector->gravity>>FRACBITS, 1000)));
else
gravityadd = -gravity;
if (mo->subsector->sector->verticalflip && gravityadd > 0)
mo->eflags |= MFE_VERTICALFLIP;
}
// Less gravity underwater.
if ((mo->eflags & MFE_UNDERWATER) && !goopgravity)
{
if (mo->momz * P_MobjFlip(mo) <= 0)
{
gravityadd = 4*gravityadd/3;
}
else
{
gravityadd = gravityadd/3;
}
}
if (mo->player)
{
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);
}
if (mo->player->kartstuff[k_waterskip])
{
gravityadd = (4*gravityadd)/3;
}
if (mo->player->trickpanel == 2 || mo->player->trickpanel == 3)
{
gravityadd = (5*gravityadd)/2;
}
if (mo->player->tumbleBounces > 0)
{
gravityadd = (5*gravityadd)/2;
}
}
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 = -gravityadd;
}
}
else //Otherwise, sort through the other exceptions.
{
switch (mo->type)
{
case MT_FLINGRING:
case MT_FLINGCOIN:
case MT_FLINGBLUESPHERE:
case MT_FLINGNIGHTSCHIP:
case MT_BOUNCERING:
case MT_RAILRING:
case MT_INFINITYRING:
case MT_AUTOMATICRING:
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_REDFLAG:
case MT_BLUEFLAG:
if (mo->target)
{
// Flung items copy the gravity of their tosser.
if ((mo->target->eflags & MFE_VERTICALFLIP) && !(mo->eflags & MFE_VERTICALFLIP))
{
gravityadd = -gravityadd;
mo->eflags |= MFE_VERTICALFLIP;
}
}
break;
case MT_WATERDROP:
case MT_CYBRAKDEMON:
case MT_BATTLEBUMPER:
gravityadd /= 2;
break;
case MT_BANANA:
case MT_EGGMANITEM:
case MT_SSMINE:
case MT_LANDMINE:
case MT_SINK:
case MT_EMERALD:
if (mo->extravalue2 > 0)
{
gravityadd *= mo->extravalue2;
}
gravityadd = (5*gravityadd)/2;
break;
case MT_KARMAFIREWORK:
gravityadd /= 3;
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 (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;
}
}
#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
{
if (FixedHypot(player->rmomx, player->rmomy) < FixedMul(STOPSPEED, mo->scale) && (K_GetForwardMove(player) == 0)
&& !(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_SetPlayerMobjState(mo, S_KART_STILL);
}
mo->momx = player->cmomx;
mo->momy = player->cmomy;
}
else
{
if (oldx == mo->x && oldy == mo->y)
{
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);
}
mo->friction = ORIG_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->flags & FF_EXISTS))
continue;
if (!(rover->flags & FF_BUSTUP))
continue;
// Needs ML_EFFECT4 flag for pushables to break it
if (!(rover->master->flags & ML_EFFECT4))
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->flags & FF_SHATTERBOTTOM)
{
if (mo->z + mo->momz + mo->height < bottomheight)
continue;
if (mo->z + mo->height > bottomheight)
continue;
}
else if (rover->flags & FF_SPINBUST)
{
if (mo->z + mo->momz > topheight)
continue;
if (mo->z + mo->height < bottomheight)
continue;
}
else if (rover->flags & FF_SHATTER)
{
if (mo->z + mo->momz > topheight)
continue;
if (mo->z + mo->momz + mo->height < bottomheight)
continue;
}
else
{
if (mo->z >= topheight)
continue;
if (mo->z + mo->height < bottomheight)
continue;
}
EV_CrumbleChain(NULL, rover); // node->m_sector
// Run a linedef executor??
if (rover->master->flags & ML_EFFECT5)
P_LinedefExecute((INT16)(P_AproxDistance(rover->master->dx, rover->master->dy)>>FRACBITS), mo, node->m_sector);
goto bustupdone;
}
}
bustupdone:
P_UnsetThingPosition(mo);
mo->x = oldx;
mo->y = oldy;
P_SetThingPosition(mo);
}
//
// P_CheckSkyHit
//
static boolean P_CheckSkyHit(mobj_t *mo)
{
if (ceilingline && ceilingline->backsector
&& ceilingline->backsector->ceilingpic == skyflatnum
&& ceilingline->frontsector
&& ceilingline->frontsector->ceilingpic == skyflatnum
&& (mo->z >= ceilingline->frontsector->ceilingheight
|| mo->z >= ceilingline->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);
//{ SRB2kart - Ballhogs
if (mo->type == MT_BALLHOG)
{
if (mo->health)
{
mo->health--;
if (mo->health == 0)
mo->destscale = 0;
}
}
//}
if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true)
&& !(P_MobjWasRemoved(mo) || mo->eflags & MFE_SPRUNG))
{
// blocked move
moved = false;
if (LUAh_MobjMoveBlocked(mo))
{
if (P_MobjWasRemoved(mo))
return;
}
else if (P_MobjWasRemoved(mo))
return;
//{ SRB2kart - Jawz
if (mo->type == MT_JAWZ || mo->type == MT_JAWZ_DUD)
{
if (mo->health == 1)
{
// This Item Damage
S_StartSound(mo, mo->info->deathsound);
P_KillMobj(mo, NULL, NULL, DMG_NORMAL);
P_SetObjectMomZ(mo, 8*FRACUNIT, false);
P_InstaThrust(mo, R_PointToAngle2(mo->x, mo->y, mo->x + xmove, mo->y + ymove)+ANGLE_90, 16*FRACUNIT);
}
}
//}
else if (mo->flags & MF_MISSILE)
{
// explode a missile
if (P_CheckSkyHit(mo))
{
// Hack to prevent missiles exploding
// against the sky.
// Does not handle sky floors.
// Check frontsector as well.
P_RemoveMobj(mo);
return;
}
// draw damage on wall
//SPLAT TEST ----------------------------------------------------------
#ifdef WALLSPLATS
if (blockingline && mo->type != MT_REDRING && mo->type != MT_FIREBALL
&& !(mo->flags2 & (MF2_AUTOMATIC|MF2_RAILRING|MF2_BOUNCERING|MF2_EXPLOSION|MF2_SCATTER)))
// set by last P_TryMove() that failed
{
divline_t divl;
divline_t misl;
fixed_t frac;
P_MakeDivline(blockingline, &divl);
misl.x = mo->x;
misl.y = mo->y;
misl.dx = mo->momx;
misl.dy = mo->momy;
frac = P_InterceptVector(&divl, &misl);
R_AddWallSplat(blockingline, P_PointOnLineSide(mo->x,mo->y,blockingline),
"A_DMG3", mo->z, frac, SPLATDRAWMODE_SHADE);
}
#endif
// --------------------------------------------------------- SPLAT TEST
P_ExplodeMissile(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))
{
// Hack to prevent missiles exploding
// against the sky.
// Does not handle sky floors.
// Check frontsector as well.
P_RemoveMobj(mo);
return;
}
}
else
{
boolean walltransferred = false;
if (player || mo->flags & MF_SLIDEME)
{ // 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);
}
// 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.
walltransferred = true;
P_SlideMove(mo);
if (player)
player->powers[pw_pushing] = 3;
xmove = ymove = 0;
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 (walltransferred == false)
{
if (mo->flags & MF_SLIDEME)
{
P_SlideMove(mo);
xmove = ymove = 0;
}
else
{
P_BounceMove(mo);
xmove = ymove = 0;
S_StartSound(mo, mo->info->activesound);
//{ SRB2kart - Orbinaut, Ballhog
// Bump sparks
if (mo->type == MT_ORBINAUT || mo->type == MT_BALLHOG)
{
mobj_t *fx;
fx = P_SpawnMobj(mo->x, mo->y, mo->z, MT_BUMP);
if (mo->eflags & MFE_VERTICALFLIP)
fx->eflags |= MFE_VERTICALFLIP;
else
fx->eflags &= ~MFE_VERTICALFLIP;
fx->scale = mo->scale;
}
if (mo->type == MT_ORBINAUT) // Orbinaut speed decreasing
{
if (mo->health > 1)
{
S_StartSound(mo, mo->info->attacksound);
mo->health--;
mo->threshold = 0;
}
else if (mo->health == 1)
{
// This Item Damage
S_StartSound(mo, mo->info->deathsound);
P_KillMobj(mo, NULL, NULL, DMG_NORMAL);
P_SetObjectMomZ(mo, 8*FRACUNIT, false);
P_InstaThrust(mo, R_PointToAngle2(mo->x, mo->y, mo->x + xmove, mo->y + ymove)+ANGLE_90, 16*FRACUNIT);
}
}
// Bubble bounce
if (mo->type == MT_BUBBLESHIELDTRAP)
S_StartSound(mo, sfx_s3k44);
}
}
}
}
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 = K_MomentumAngle(mo);
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))
);*/
// Sryder 2018-11-26: Don't launch here if it's a slope without physics, we stick to those like glue anyway
} else if (predictedz-mo->z > abs(slopemom.z/2)
&& !(mo->standingslope->flags & SL_NOPHYSICS)) { // 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 = K_MomentumAngle(mo);
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 (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 ((mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED)
&& (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)) // Special exception for tumbleweeds on slopes
return;
//{ SRB2kart stuff
if (mo->type == MT_FLINGRING || mo->type == MT_BALLHOG || mo->type == MT_BUBBLESHIELDTRAP)
return;
if (player && (player->kartstuff[k_spinouttimer] && !player->kartstuff[k_wipeoutslow])
&& player->speed <= FixedDiv(20*mapobjectscale, player->kartstuff[k_offroad] + FRACUNIT))
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_BounceMove(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_BounceMove(mo);
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->flags & FF_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)) // only the player should stand on lava
;
else if (motype != 0 && rover->flags & FF_SWIMMABLE) // "scenery" only
continue;
else if (rover->flags & FF_QUICKSAND) // quicksand
;
else if (!( // if it's not either of the following...
(rover->flags & (FF_BLOCKPLAYER|FF_MARIO) && mo->player) // ...solid to players? (mario blocks are always solid from beneath to players)
|| (rover->flags & FF_BLOCKOTHERS && !mo->player) // ...solid to others?
)) // ...don't take it into account.
continue;
if (rover->flags & FF_QUICKSAND)
{
switch (motype)
{
case 2: // scenery does things differently for some reason
if (mo->z < topheight && bottomheight < thingtop)
{
mo->floorz = mo->z;
continue;
}
break;
default:
if (mo->z < topheight && bottomheight < thingtop)
{
if (mo->floorz < mo->z)
mo->floorz = mo->z;
}
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->flags & FF_SOLID) // Non-FF_SOLID Mario blocks are only solid from bottom
&& !(rover->flags & FF_REVERSEPLATFORM)
&& ((P_MobjFlip(mo)*mo->momz >= 0) || (!(rover->flags & FF_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->flags & FF_PLATFORM)
&& ((P_MobjFlip(mo)*mo->momz >= 0) || ((rover->flags & FF_SOLID) && !(rover->flags & FF_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;
if (((mo->z <= mo->subsector->sector->floorheight
&& ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_FLOOR))
|| (mo->z + mo->height >= mo->subsector->sector->ceilingheight
&& ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_CEILING)))
&& (GETSECSPECIAL(mo->subsector->sector->special, 1) == 6
|| GETSECSPECIAL(mo->subsector->sector->special, 1) == 7))
return true;
return false;
}
boolean P_CheckSolidLava(ffloor_t *rover)
{
if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
&& !(rover->master->flags & ML_BLOCKPLAYERS))
return true;
return false;
}
//
// 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);
}
switch (mo->type)
{
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_SPINFIRE:
if (P_CheckDeathPitCollide(mo))
{
P_RemoveMobj(mo);
return false;
}
break;
case MT_GOOP:
if (P_CheckDeathPitCollide(mo))
{
P_RemoveMobj(mo);
return false;
}
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_FALLINGROCK:
case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED:
case MT_SHELL:
// SRB2kart stuff that should die in pits
// Shouldn't stop moving along the Z if there's no speed though!
case MT_EGGMANITEM:
case MT_BANANA:
case MT_ORBINAUT:
case MT_JAWZ:
case MT_JAWZ_DUD:
case MT_BALLHOG:
case MT_SSMINE:
case MT_LANDMINE:
case MT_BUBBLESHIELDTRAP:
// Remove stuff from death pits.
if (P_CheckDeathPitCollide(mo))
{
P_RemoveMobj(mo);
return false;
}
break;
case MT_REDFLAG:
case MT_BLUEFLAG:
// Remove from death pits. DON'T FUCKING DESPAWN IT DAMMIT
if (P_CheckDeathPitCollide(mo))
{
mo->fuse = 1;
return false;
}
break;
case MT_EMERALD:
if (P_CheckDeathPitCollide(mo))
{
P_RemoveMobj(mo);
return false;
}
if (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz)
{
// Stop when hitting the floor
mo->momx = mo->momy = 0;
}
break;
case MT_RING: // Ignore still rings
case MT_BLUESPHERE:
case MT_FLINGRING:
// Remove flinged stuff from death pits.
if (P_CheckDeathPitCollide(mo))
{
P_RemoveMobj(mo);
return false;
}
if (!(mo->momx || mo->momy || mo->momz))
return true;
break;
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:
// Remove flinged stuff from death pits.
if (P_CheckDeathPitCollide(mo) && (mo->flags2 & MF2_DONTRESPAWN))
{
P_RemoveMobj(mo);
return false;
}
if (!(mo->momx || mo->momy || mo->momz))
return true;
break;
case MT_FLAMEJET:
case MT_VERTICALFLAMEJET:
if (mo->flags & MF_SLIDEME)
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 (!mo->player && P_CheckDeathPitCollide(mo))
{
if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART)
{
// Kill enemies, bosses and minecarts that fall into death pits.
if (mo->health)
{
P_KillMobj(mo, NULL, NULL, DMG_NORMAL);
}
return false;
}
}
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 (((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->type == MT_DRIFTCLIP)
{
mom.z = -mom.z/2;
if (abs(mom.z) > 4 * mo->scale / 3)
{
K_SpawnDriftBoostClipSpark(mo);
S_StartSound(mo, sfx_tink);
}
else
mo->flags2 ^= MFD_DONTDRAW;
}
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) < 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 if (mo->type == MT_KART_LEFTOVER)
{
if (mo->health > 1)
{
const fixed_t tireOffset = 32;
const angle_t aOffset = ANGLE_22h;
UINT8 i;
angle_t tireAngle;
mobj_t *tire;
// Spawn tires!
mo->health = 1;
P_SetMobjState(mo, S_KART_LEFTOVER_NOTIRES);
// Front tires
tireAngle = mo->angle - aOffset;
for (i = 0; i < 2; i++)
{
tire = P_SpawnMobjFromMobj(
mo,
tireOffset * FINECOSINE(tireAngle >> ANGLETOFINESHIFT),
tireOffset * FINESINE(tireAngle >> ANGLETOFINESHIFT),
0,
MT_KART_TIRE
);
tire->angle = mo->angle;
tire->fuse = 3*TICRATE;
P_InstaThrust(tire, tireAngle, 4 * mo->scale);
P_SetObjectMomZ(tire, 4*FRACUNIT, false);
tireAngle += (aOffset * 2);
}
// Back tires
tireAngle = (mo->angle + ANGLE_180) - aOffset;
for (i = 0; i < 2; i++)
{
tire = P_SpawnMobjFromMobj(
mo,
tireOffset * FINECOSINE(tireAngle >> ANGLETOFINESHIFT),
tireOffset * FINESINE(tireAngle >> ANGLETOFINESHIFT),
0,
MT_KART_TIRE
);
tire->angle = mo->angle;
tire->fuse = 3*TICRATE;
P_InstaThrust(tire, tireAngle, 4 * mo->scale);
P_SetObjectMomZ(tire, 4*FRACUNIT, false);
P_SetMobjState(tire, S_KART_TIRE2);
tireAngle += (aOffset * 2);
}
}
}
else if (mo->type == MT_KART_TIRE)
{
mom.z = -mom.z;
}
else if (mo->type == MT_BIGTUMBLEWEED
|| mo->type == MT_LITTLETUMBLEWEED
|| mo->type == MT_CANNONBALLDECOR
|| mo->type == MT_FALLINGROCK)
{
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)
{
// If deafed, 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 (!mo->fuse
&& abs(mom.x) < FixedMul(STOPSPEED*2, mo->scale)
&& abs(mom.y) < FixedMul(STOPSPEED*2, mo->scale)
&& abs(mom.z) < FixedMul(STOPSPEED*2*3, mo->scale))
{
//P_RemoveMobj(mo);
//return false;
mo->fuse = TICRATE;
}
}
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)
{
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->flags & FF_EXISTS))
continue;
if (!(rover->flags & FF_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->flags & FF_SHATTERBOTTOM) // 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 =
(P_GetPlayerViewHeight(mo->player) - 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;
// Get up if you fell.
if (mo->player->panim == PA_PAIN && mo->player->kartstuff[k_spinouttimer] == 0 && mo->player->tumbleBounces == 0)
P_SetPlayerMobjState(mo, S_KART_STILL);
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 = !(P_CheckDeathPitCollide(mo));
mo->pmomz = 0; // We're on a new floor, don't keep doing platform movement.
mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
clipmomz = P_PlayerHitFloor(mo->player, true);
P_PlayerPolyObjectZMovement(mo);
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);
}
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 (P_MobjFlip(mo)*mo->momz > 0)
{
if (CheckForMarioBlocks)
P_CheckMarioBlocks(mo);
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;
switch (mo->type)
{
case MT_BOOMEXPLODE:
case MT_BOOMPARTICLE:
if (!(mo->flags & MF_SLIDEME) && (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz))
{
// set standingslope
P_TryMove(mo, mo->x, mo->y, true);
mo->momz = -mo->momz;
if (mo->standingslope)
{
if (mo->flags & MF_NOCLIPHEIGHT)
mo->standingslope = NULL;
else if (!P_IsObjectOnGround(mo))
P_SlopeLaunch(mo);
}
S_StartSound(mo, mo->info->activesound);
}
break;
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 (P_CheckDeathPitCollide(mo)) // Don't split if you fell in a pit
{
P_RemoveMobj(mo);
return false;
}
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);
explodemo->momx += ((prandom & 0x0F) << (FRACBITS-2)) * (i & 2 ? -1 : 1);
explodemo->momy += ((prandom & 0xF0) << (FRACBITS-6)) * (i & 1 ? -1 : 1);
explodemo->destscale = mo->scale;
P_SetScale(explodemo, mo->scale);
}
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
if (P_CheckDeathPitCollide(mo)) // No flowers for death pits
{
P_RemoveMobj(mo);
return false;
}
// 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);
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_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 = mobj->height;
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->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE)
|| (((rover->flags & FF_BLOCKPLAYER) && mobj->player)
|| ((rover->flags & FF_BLOCKOTHERS) && !mobj->player)))
continue;
topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y);
bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y);
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 (GETSECSPECIAL(rover->master->frontsector->special, 1) == 3)
mobj->eflags |= MFE_TOUCHLAVA;
if (rover->flags & FF_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...
P_FlashPal(p,
electric
? PAL_WHITE
: PAL_NUKE,
1);
p->powers[pw_shield] = p->powers[pw_shield] & SH_STACK;
}
}
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);
splish->flags2 |= MF2_OBJECTFLIP;
splish->eflags |= MFE_VERTICALFLIP;
}
else
splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
splish->destscale = mobj->scale;
P_SetScale(splish, mobj->scale);
}
// skipping stone!
if (p && p->kartstuff[k_waterskip] < 2
&& ((p->speed/3 > abs(mobj->momz)) // Going more forward than horizontal, so you can skip across the water.
|| (p->speed > 20*mapobjectscale && p->kartstuff[k_waterskip])) // Already skipped once, so you can skip once more!
&& ((!(mobj->eflags & MFE_VERTICALFLIP) && thingtop - mobj->momz > mobj->watertop)
|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z - mobj->momz < mobj->waterbottom)))
{
const fixed_t hop = 5<<FRACBITS;
mobj->momx = (4*mobj->momx)/5;
mobj->momy = (4*mobj->momy)/5;
if (mobj->eflags & MFE_VERTICALFLIP)
mobj->momz = FixedMul(-hop, mobj->scale);
else
mobj->momz = FixedMul(hop, mobj->scale);
p->kartstuff[k_waterskip]++;
}
}
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);
splish->flags2 |= MF2_OBJECTFLIP;
splish->eflags |= MFE_VERTICALFLIP;
}
else
splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
splish->destscale = mobj->scale;
P_SetScale(splish, mobj->scale);
}
}
// 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;
bubble->destscale = mobj->scale;
P_SetScale(bubble, mobj->scale);
}
}
}
}
}
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->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || rover->flags & FF_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 (P_FindSpecialLineFromTag(13, sector->tag, -1) != -1)
return true;
if (sector->ffloors)
{
ffloor_t *rover;
for (rover = sector->ffloors; rover; rover = rover->next)
{
if (!(rover->flags & FF_EXISTS))
continue;
if (halfheight >= P_GetFFloorTopZAt(rover, thiscam->x, thiscam->y))
continue;
if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
continue;
if (P_FindSpecialLineFromTag(13, rover->master->frontsector->tag, -1) != -1)
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->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || rover->flags & FF_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, DMG_NORMAL);
}
}
// 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;
UINT8 i;
// This can happen when joining
if (thiscam->subsector == NULL || thiscam->subsector->sector == NULL)
return;
if (encoremode)
{
postimg = postimg_mirror;
}
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)
{
for (i = 0; i <= r_splitscreen; i++)
{
if (player == &players[displayplayers[i]])
postimgtype[i] = postimg;
}
}
}
// P_CameraThinker
//
// Process the mobj-ish required functions of the camera
boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled)
{
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 ((player->pflags & PF_GAMETYPEOVER) && (gametyperules & GTR_CIRCUIT))
{
player->karthud[khud_timeovercam] = (2*TICRATE)+1;
}
if (!resetcalled && !(player->pflags & PF_NOCLIP || leveltime < introtime) && !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;
}
}
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 (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->flags & FF_EXISTS))
continue;
if (!(rover->flags & FF_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->flags & FF_FLOATBOB), mobj->player, rover->alpha, !(rover->flags & FF_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->flags & FF_EXISTS))
continue;
if (!(rover->flags & FF_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->flags & FF_EXISTS))
continue;
if (!(rover->flags & FF_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;
// Zoom tube
if ((mobj->player->powers[pw_carry] == CR_ZOOMTUBE && mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
|| mobj->player->respawn.state == RESPAWNST_MOVE)
{
P_HitSpecialLines(mobj, mobj->x, mobj->y, mobj->momx, mobj->momy);
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;
}
// 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);
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->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))
{
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->flags & FF_EXISTS))
continue;
if (!(rover->flags & FF_BLOCKOTHERS) && !(rover->flags & FF_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;
}
void P_PrecipThinker(precipmobj_t *mobj)
{
P_CycleStateAnimation((mobj_t *)mobj);
if (mobj->state == &states[S_RAINRETURN])
{
// Reset to ceiling!
P_SetPrecipMobjState(mobj, mobj->info->spawnstate);
mobj->z = mobj->ceilingz;
mobj->momz = mobj->info->speed;
mobj->precipflags &= ~PCF_SPLASH;
}
if (mobj->tics != -1)
{
if (mobj->tics)
{
mobj->tics--;
}
if (mobj->tics == 0)
{
if ((mobj->precipflags & PCF_SPLASH) && (mobj->state->nextstate == S_NULL))
{
// HACK: sprite changes are 1 tic late, so you would see splashes on the ceiling if not for this state.
// We need to use the settings from the previous state, since some of those are NOT 1 tic late.
INT32 frame = (mobj->frame & ~FF_FRAMEMASK);
P_SetPrecipMobjState(mobj, S_RAINRETURN);
mobj->frame = frame;
return;
}
else
{
if (!P_SetPrecipMobjState(mobj, mobj->state->nextstate))
return;
}
}
}
if (mobj->precipflags & PCF_SPLASH)
return;
// adjust height
if ((mobj->z += mobj->momz) <= mobj->floorz)
{
if ((mobj->info->deathstate == S_NULL) || (mobj->precipflags & PCF_PIT)) // no splashes on sky or bottomless pits
{
mobj->z = mobj->ceilingz;
}
else
{
P_SetPrecipMobjState(mobj, mobj->info->deathstate);
mobj->z = mobj->floorz;
mobj->precipflags |= PCF_SPLASH;
}
}
}
static void P_RingThinker(mobj_t *mobj)
{
mobj_t *spark; // Ring Fuse
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;
}
// This thinker splits apart before the regular fuse handling so we need to handle it here instead.
if (mobj->fuse)
{
mobj->fuse--;
if (mobj->fuse < TICRATE*3)
{
if (leveltime & 1)
mobj->drawflags |= MFD_DONTDRAW;
else
mobj->drawflags &= ~MFD_DONTDRAW;
}
if (!mobj->fuse)
{
if (!LUAh_MobjFuse(mobj))
{
mobj->drawflags &= ~MFD_DONTDRAW;
spark = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SIGNSPARKLE); // Spawn a fancy sparkle
K_MatchGenericExtraFlags(spark, mobj);
spark->colorized = true;
spark->color = mobj->color ? : SKINCOLOR_YELLOW; // Use yellow if the ring doesn't use a skin color. (It should be red for SPB rings, but let normal rings look fancy too!)
P_RemoveMobj(mobj); // Adieu, monde cruel!
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 || 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)
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);
}
}
//
// 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);
TVector v;
TVector *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[0] = FixedMul(FINECOSINE(fa),fuse);
v[1] = 0;
v[2] = FixedMul(FINESINE(fa),fuse);
v[3] = FRACUNIT;
res = VectorMatrixMultiply(v, *RotateXMatrix(FixedAngle(mobj->target->movedir*FRACUNIT)));
M_Memcpy(&v, res, sizeof (v));
res = VectorMatrixMultiply(v, *RotateZMatrix(FixedAngle(mobj->target->movecount*FRACUNIT)));
M_Memcpy(&v, res, sizeof (v));
finalx = x + v[0];
finaly = y + v[1];
finalz = z + v[2];
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;
TVector v;
TVector *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[0] = FixedMul(FINECOSINE(fa),radius);
v[1] = 0;
v[2] = FixedMul(FINESINE(fa),radius);
v[3] = FRACUNIT;
res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle));
M_Memcpy(&v, res, sizeof (v));
res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle));
M_Memcpy(&v, res, sizeof (v));
finalx = x + v[0];
finaly = y + v[1];
finalz = z + v[2];
mobj = P_SpawnMobj(finalx, finaly, finalz, type);
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;
TVector v;
TVector *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[0] = FixedMul(FINECOSINE(fa),radius);
v[1] = 0;
v[2] = FixedMul(FINESINE(fa),radius);
v[3] = FRACUNIT;
res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle));
M_Memcpy(&v, res, sizeof (v));
res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle));
M_Memcpy(&v, res, sizeof (v));
finalx = x + v[0];
finaly = y + v[1];
finalz = z + v[2];
mobj = P_SpawnMobj(finalx, finaly, finalz, type);
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)
{
mobj->x = x;
mobj->y = y;
mobj->z = 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)
{
player_t *player;
fixed_t oldscale;
if (!mobj)
return;
oldscale = mobj->scale; //keep for adjusting stuff below
mobj->scale = newscale;
mobj->radius = FixedMul(FixedDiv(mobj->radius, oldscale), newscale);
mobj->height = FixedMul(FixedDiv(mobj->height, oldscale), newscale);
player = mobj->player;
if (player)
{
G_GhostAddScale((INT32) (player - players), newscale);
player->viewheight = FixedMul(FixedDiv(player->viewheight, oldscale), newscale); // Nonono don't calculate viewheight elsewhere, this is the best place for it!
}
}
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_TeleportMove(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)
{
TVector unit_lengthways, unit_sideways, pos_lengthways, pos_sideways;
TVector *res;
fixed_t radius, dist, 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;
dist = pos_sideways[0] = pos_sideways[1] = pos_sideways[2] = pos_sideways[3] = unit_sideways[3] =\
pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 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;
pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 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[1] = -FixedMul(dist, FINESINE(fa));
unit_lengthways[3] = 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[0] = FixedMul(FINESINE(fa), -radius);
unit_lengthways[2] = 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[0] = FixedMul(FINECOSINE(fa), radius);
unit_lengthways[2] = FixedMul(FINESINE(fa), radius);
}
// Calculate the angle matrixes for the link.
res = VectorMatrixMultiply(unit_lengthways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
res = VectorMatrixMultiply(unit_lengthways, *RotateZMatrix(center->angle));
M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
lastthreshold = mobj->threshold;
lastfriction = mobj->friction;
}
if (dosound && (mobj->flags2 & MF2_BOSSNOTRAP))
{
S_StartSound(mobj, mobj->info->activesound);
dosound = false;
}
if (pos_sideways[3] != mobj->movefactor)
{
if (!unit_sideways[3])
{
unit_sideways[1] = dist;
unit_sideways[0] = unit_sideways[2] = 0;
unit_sideways[3] = FRACUNIT;
res = VectorMatrixMultiply(unit_sideways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
res = VectorMatrixMultiply(unit_sideways, *RotateZMatrix(center->angle));
M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
}
if (pos_sideways[3] > mobj->movefactor)
{
do
{
pos_sideways[0] -= unit_sideways[0];
pos_sideways[1] -= unit_sideways[1];
pos_sideways[2] -= unit_sideways[2];
}
while ((--pos_sideways[3]) != mobj->movefactor);
}
else
{
do
{
pos_sideways[0] += unit_sideways[0];
pos_sideways[1] += unit_sideways[1];
pos_sideways[2] += unit_sideways[2];
}
while ((++pos_sideways[3]) != mobj->movefactor);
}
}
hnext = mobj->hnext; // just in case the mobj is removed
if (pos_lengthways[3] > mobj->movecount)
{
do
{
pos_lengthways[0] -= unit_lengthways[0];
pos_lengthways[1] -= unit_lengthways[1];
pos_lengthways[2] -= unit_lengthways[2];
}
while ((--pos_lengthways[3]) != mobj->movecount);
}
else if (pos_lengthways[3] < mobj->movecount)
{
do
{
pos_lengthways[0] += unit_lengthways[0];
pos_lengthways[1] += unit_lengthways[1];
pos_lengthways[2] += unit_lengthways[2];
}
while ((++pos_lengthways[3]) != 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[3])
{
mobj->x += pos_lengthways[0];
mobj->y += pos_lengthways[1];
zstore = pos_lengthways[2] + pos_sideways[2];
}
else
zstore = pos_sideways[2];
mobj->x += pos_sideways[0];
mobj->y += pos_sideways[1];
// 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->drawflags ^= MFD_DONTDRAW;
#endif
P_SetThingPosition(mobj);
#if 0 // toaster's height-clipping dealie!
if (!pos_lengthways[3] || 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[0], zstore);
mobj->y -= FixedMul(unit_lengthways[1], 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->drawflags |= MFD_SHADOW;
}
else
{
if (thing->info->painstate)
P_SetMobjState(thing,thing->info->spawnstate);
else
thing->drawflags &= ~(MFD_TRANSMASK|MFD_BRIGHTMASK);
}
}
thing->flags |= MF_NOCLIPHEIGHT;
thing->eflags = (thing->eflags & ~MFE_VERTICALFLIP)|(thing->target->eflags & MFE_VERTICALFLIP);
P_SetScale(thing, thing->target->scale);
thing->destscale = thing->scale;
P_UnsetThingPosition(thing);
thing->x = thing->target->x;
thing->y = thing->target->y;
if (thing->eflags & MFE_VERTICALFLIP)
thing->z = thing->target->z + (thing->target->height - thing->height - FixedMul(2*FRACUNIT, thing->target->scale));
else
thing->z = thing->target->z + FixedMul(2*FRACUNIT, thing->target->scale);
P_SetThingPosition(thing);
P_CheckPosition(thing, thing->x, thing->y);
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++)
{
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;
}
// Kartitem stuff.
boolean P_IsKartItem(INT32 type)
{
if (type == MT_EGGMANITEM || type == MT_EGGMANITEM_SHIELD ||
type == MT_BANANA || type == MT_BANANA_SHIELD ||
type == MT_ORBINAUT || type == MT_ORBINAUT_SHIELD ||
type == MT_JAWZ || type == MT_JAWZ_DUD || type == MT_JAWZ_SHIELD ||
type == MT_SSMINE || type == MT_SSMINE_SHIELD ||
type == MT_SINK || type == MT_SINK_SHIELD ||
type == MT_SPB || type == MT_BALLHOG || type == MT_BUBBLESHIELDTRAP ||
type == MT_LANDMINE)
return true;
else
return false;
}
// Called when a kart item "thinks"
void P_AddKartItem(mobj_t *thing)
{
I_Assert(thing != NULL);
if (kitemcap == NULL)
P_SetTarget(&kitemcap, thing);
else {
mobj_t *mo;
for (mo = kitemcap; mo && mo->itnext; mo = mo->itnext)
;
I_Assert(mo != NULL);
I_Assert(mo->itnext == NULL);
P_SetTarget(&mo->itnext, thing);
}
P_SetTarget(&thing->itnext, NULL);
}
// Called only when a kart item is removed
// Keeps the hnext list from corrupting.
static void P_RemoveKartItem(mobj_t *thing)
{
mobj_t *mo;
for (mo = kitemcap; mo; mo = mo->itnext)
if (mo->itnext == thing)
{
P_SetTarget(&mo->itnext, thing->itnext);
P_SetTarget(&thing->itnext, NULL);
return;
}
}
// Doesn't actually do anything since items have their own thinkers,
// but this is necessary for the sole purpose of updating kitemcap
void P_RunKartItems(void)
{
mobj_t *mobj, *next;
for (mobj = kitemcap; mobj; mobj = next)
{
next = mobj->itnext;
P_SetTarget(&mobj->itnext, NULL);
}
P_SetTarget(&kitemcap, NULL);
}
void P_RunOverlays(void)
{
// run overlays
mobj_t *mo, *next = NULL;
fixed_t destx,desty,zoffs;
for (mo = overlaycap; mo; mo = next)
{
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;
}
if (!r_splitscreen /*&& rendermode != render_soft*/)
{
angle_t viewingangle;
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, camera[0].x, camera[0].y);
if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1)
viewingangle += ANGLE_180;
destx = mo->target->x + P_ReturnThrustX(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale));
desty = mo->target->y + P_ReturnThrustY(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale));
}
else
{
destx = mo->target->x;
desty = mo->target->y;
}
mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP);
mo->scale = mo->destscale = mo->target->scale;
mo->angle = mo->target->angle + mo->movedir;
if ((mo->flags & MF_DONTENCOREMAP) != (mo->target->flags & MF_DONTENCOREMAP))
mo->flags ^= MF_DONTENCOREMAP;
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 = destx;
mo->y = desty;
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;
else
mo->z = mo->target->z + zoffs;
if (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;
for (mo = overlaycap; mo; mo = mo->hnext)
{
if (mo->hnext != thing)
continue;
P_SetTarget(&mo->hnext, thing->hnext);
P_SetTarget(&thing->hnext, NULL);
return;
}
}
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);
else if (mobj->scale < mobj->destscale)
P_SetScale(mobj, mobj->scale + mobj->scalespeed);
else if (mobj->scale > mobj->destscale)
P_SetScale(mobj, mobj->scale - mobj->scalespeed);
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:
if (mobj->player)
{
if (mobj->scale <= 1)
{
mobj->drawflags |= MFD_DONTDRAW;
}
}
else
{
if (!mobj->player && mobj->scale == 0)
{
P_RemoveMobj(mobj);
return;
}
}
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->drawflags & MFD_DONTDRAW) ? 0 : 4)); // oh my god this is nasty.
ref->flags |= MF_NOTHINK|MF_NOCLIPTHING;
ref->drawflags |= MFD_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->drawflags &= ~MFD_DONTDRAW;
ref->eflags = 0; // le sign
}
mobj->flags2 &= ~MF2_BEYONDTHEGRAVE;
}
}
// Okay, time to MOVE
P_MaceRotate(mobj, mobj->movedir, oldmovedir);
}
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);
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 = 20*FRACUNIT;
strength -= ((20*FRACUNIT)/16)*mobj->movedir;
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);
strength = 20*FRACUNIT;
strength -= ((20*FRACUNIT)/16)*mobj->movedir;
// 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;
bottomheight = lines[line].frontsector->floorheight;
topheight = lines[line].frontsector->ceilingheight - 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;
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);
P_SetScale(spawn, mobj->scale);
spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
spawn->destscale = spawn->scale/100;
spawn->scalespeed = spawn->scale/mobj->health;
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;
}
}
return true;
}
static void P_MobjSceneryThink(mobj_t *mobj)
{
if (LUAh_MobjThinker(mobj))
return;
if (P_MobjWasRemoved(mobj))
return;
if ((mobj->flags2 & MF2_SHIELD) && !P_AddShield(mobj))
return;
switch (mobj->type)
{
case MT_BOSSJUNK:
mobj->drawflags ^= MFD_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
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->z <= mobj->floorz || mobj->z <= mobj->watertop)
&& 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)
|| (P_CheckDeathPitCollide(mobj))
|| --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->drawflags &= ~MFD_DONTDRAW;
mobj->x = mobj->target->x;
mobj->y = mobj->target->y;
mobj->eflags |= (mobj->target->eflags & MFE_VERTICALFLIP);
mobj->destscale = mobj->target->destscale;
P_SetScale(mobj, mobj->target->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;
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);
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->z <= P_FloorzAtPos(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_ORBINAUT_SHIELD: // Kart orbit/trail items
case MT_JAWZ_SHIELD:
case MT_BANANA_SHIELD:
case MT_SSMINE_SHIELD:
case MT_EGGMANITEM_SHIELD:
case MT_SINK_SHIELD:
if ((mobj->health > 0
&& (!mobj->target || !mobj->target->player || mobj->target->health <= 0 || mobj->target->player->spectator))
|| (mobj->health <= 0 && P_IsObjectOnGround(mobj))
|| P_CheckDeathPitCollide(mobj)) // When in death state
{
P_RemoveMobj(mobj);
return;
}
break;
case MT_SMOLDERING:
if (leveltime % 2 == 0)
{
fixed_t x = P_RandomRange(-35, 35)*mobj->scale;
fixed_t y = P_RandomRange(-35, 35)*mobj->scale;
fixed_t z = P_RandomRange(0, 70)*mobj->scale;
mobj_t *smoke = P_SpawnMobj(mobj->x + x, mobj->y + y, mobj->z + z, MT_SMOKE);
P_SetMobjState(smoke, S_OPAQUESMOKE1);
K_MatchGenericExtraFlags(smoke, mobj);
smoke->scale = mobj->scale * 2;
smoke->destscale = mobj->scale * 6;
smoke->momz = P_RandomRange(4, 9)*FRACUNIT*P_MobjFlip(smoke);
}
break;
case MT_BOOMPARTICLE:
{
fixed_t x = P_RandomRange(-16, 16)*mobj->scale;
fixed_t y = P_RandomRange(-16, 16)*mobj->scale;
fixed_t z = P_RandomRange(0, 32)*mobj->scale*P_MobjFlip(mobj);
if (leveltime % 2 == 0)
{
mobj_t *smoke = P_SpawnMobj(mobj->x + x, mobj->y + y, mobj->z + z, MT_BOSSEXPLODE);
K_MatchGenericExtraFlags(smoke, mobj);
P_SetMobjState(smoke, S_QUICKBOOM1);
smoke->scale = mobj->scale/2;
smoke->destscale = mobj->scale;
smoke->color = mobj->color;
}
else
{
mobj_t *smoke = P_SpawnMobj(mobj->x + x, mobj->y + y, mobj->z + z, MT_SMOKE);
P_SetMobjState(smoke, S_OPAQUESMOKE1);
K_MatchGenericExtraFlags(smoke, mobj);
smoke->scale = mobj->scale;
smoke->destscale = mobj->scale*2;
}
if (mobj->tics <= TICRATE)
{
mobj->destscale = FixedDiv(mobj->scale, 100*FRACUNIT);
}
}
break;
case MT_BATTLEBUMPER:
if (mobj->health <= 0)
{
mobj->fuse--;
if (!S_SoundPlaying(mobj, sfx_cdfm71))
{
S_StartSound(mobj, sfx_cdfm71);
}
if (mobj->fuse <= 0)
{
statenum_t curState = (mobj->state - states);
if (curState == S_BATTLEBUMPER1)
{
P_SetMobjState(mobj, S_BATTLEBUMPER_EXCRYSTALA1);
if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
{
P_SetMobjState(mobj->tracer, S_BATTLEBUMPER_EXSHELLA1);
}
mobj->shadowscale *= 2;
mobj->fuse = 12;
}
else if (curState >= S_BATTLEBUMPER_EXCRYSTALA1 && curState <= S_BATTLEBUMPER_EXCRYSTALA4)
{
P_SetMobjState(mobj, S_BATTLEBUMPER_EXCRYSTALB1);
if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
{
P_SetMobjState(mobj->tracer, S_BATTLEBUMPER_EXSHELLB1);
}
mobj->shadowscale *= 2;
mobj->fuse = 24;
break;
}
else if (curState >= S_BATTLEBUMPER_EXCRYSTALB1 && curState <= S_BATTLEBUMPER_EXCRYSTALB4)
{
P_SetMobjState(mobj, S_BATTLEBUMPER_EXCRYSTALC1);
if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
{
P_SetMobjState(mobj->tracer, S_BATTLEBUMPER_EXSHELLC1);
}
mobj->shadowscale *= 2;
mobj->fuse = 32;
break;
}
else
{
const INT16 spacing = 64;
UINT8 i;
for (i = 0; i < 10; i++)
{
mobj_t *debris = P_SpawnMobjFromMobj(
mobj,
P_RandomRange(-spacing, spacing) * FRACUNIT,
P_RandomRange(-spacing, spacing) * FRACUNIT,
P_RandomRange(-spacing, spacing) * FRACUNIT,
MT_BATTLEBUMPER_DEBRIS
);
P_SetScale(debris, (debris->destscale *= 2));
debris->color = mobj->color;
debris->momz = -debris->scale * P_MobjFlip(debris);
}
for (i = 0; i < 2; i++)
{
mobj_t *blast = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BATTLEBUMPER_BLAST);
blast->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy) + ANGLE_45;
blast->destscale *= 4;
if (i & 1)
{
blast->angle += ANGLE_90;
S_StartSound(blast, sfx_cdfm64);
}
}
for (i = 0; i < 10; i++)
{
mobj_t *puff = P_SpawnMobjFromMobj(
mobj,
P_RandomRange(-spacing, spacing) * FRACUNIT,
P_RandomRange(-spacing, spacing) * FRACUNIT,
P_RandomRange(-spacing, spacing) * FRACUNIT,
MT_SPINDASHDUST
);
P_SetScale(puff, (puff->destscale *= 5));
puff->momz = puff->scale * P_MobjFlip(puff);
P_Thrust(puff, R_PointToAngle2(mobj->x, mobj->y, puff->x, puff->y), puff->scale);
}
P_RemoveMobj(mobj);
return;
}
}
break;
}
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player
&& mobj->target->health > 0 && !mobj->target->player->spectator)
{
fixed_t rad = 32*mobj->target->scale;
fixed_t offz;
angle_t ang, diff;
if (!((mobj->target->player-players) & 1))
ang = -FixedAngle(mobj->info->speed);
else
ang = FixedAngle(mobj->info->speed);
if (mobj->target->player->bumpers <= 1)
diff = 0;
else
diff = FixedAngle(360*FRACUNIT/mobj->target->player->bumpers);
ang = (ang*leveltime) + (diff * (mobj->threshold-1));
// If the player is on the ceiling, then flip your items as well.
if (mobj->target->eflags & MFE_VERTICALFLIP)
{
mobj->eflags |= MFE_VERTICALFLIP;
offz = mobj->target->height / 2;
}
else
{
mobj->eflags &= ~MFE_VERTICALFLIP;
offz = mobj->target->height / 5;
}
mobj->drawflags = (mobj->target->drawflags & MFD_DONTDRAW);
if (mobj->target->eflags & MFE_VERTICALFLIP)
{
offz += 4*FRACUNIT;
}
else
{
offz -= 4*FRACUNIT;
}
if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer) && mobj->tracer->player
&& mobj->tracer->health > 0 && !mobj->tracer->player->spectator) // STOLEN
{
mobj->color = mobj->tracer->color;
}
else
{
mobj->color = mobj->target->color;
}
if (mobj->target->player->bumpers < 2)
P_SetMobjState(mobj, S_BATTLEBUMPER3);
else if (mobj->target->player->bumpers < 3)
P_SetMobjState(mobj, S_BATTLEBUMPER2);
else
P_SetMobjState(mobj, S_BATTLEBUMPER1);
// Shrink your items if the player shrunk too.
P_SetScale(mobj, mobj->target->scale);
P_UnsetThingPosition(mobj);
{
const angle_t fa = ang >> ANGLETOFINESHIFT;
mobj->x = mobj->target->x + FixedMul(FINECOSINE(fa), rad);
mobj->y = mobj->target->y + FixedMul(FINESINE(fa), rad);
mobj->z = mobj->target->z + offz;
P_SetThingPosition(mobj);
}
if (mobj->target->player->bumpers <= mobj->threshold)
{
// Do bumper destruction
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
break;
}
}
else
{
// Sliently remove
P_RemoveMobj(mobj);
return;
}
break;
case MT_BATTLEBUMPER_DEBRIS:
if (mobj->state == states + S_BATTLEBUMPER_EXDEBRIS2)
{
mobj->drawflags ^= MFD_DONTDRAW;
}
break;
case MT_PLAYERARROW:
if (mobj->target && mobj->target->health
&& mobj->target->player && !mobj->target->player->spectator
&& mobj->target->health && mobj->target->player->playerstate != PST_DEAD
/*&& players[displayplayers[0]].mo && !players[displayplayers[0]].spectator*/)
{
fixed_t scale = 3*mobj->target->scale;
mobj->color = mobj->target->color;
K_MatchGenericExtraFlags(mobj, mobj->target);
if ((gametype == GT_RACE || mobj->target->player->bumpers <= 0)
#if 1 // Set to 0 to test without needing to host
|| (P_IsDisplayPlayer(mobj->target->player))
#endif
)
mobj->drawflags |= MFD_DONTDRAW;
P_UnsetThingPosition(mobj);
mobj->x = mobj->target->x;
mobj->y = mobj->target->y;
mobj->angle = R_PointToAngle(mobj->x, mobj->y) + ANGLE_90; // literally only happened because i wanted to ^L^R the SPR_ITEM's
if (!r_splitscreen && players[displayplayers[0]].mo)
{
scale = mobj->target->scale + FixedMul(FixedDiv(abs(P_AproxDistance(players[displayplayers[0]].mo->x-mobj->target->x,
players[displayplayers[0]].mo->y-mobj->target->y)), RING_DIST), mobj->target->scale);
if (scale > 16*mobj->target->scale)
scale = 16*mobj->target->scale;
}
mobj->destscale = scale;
if (!(mobj->target->eflags & MFE_VERTICALFLIP))
{
mobj->z = mobj->target->z + mobj->target->height + (16*mobj->target->scale);
mobj->eflags &= ~MFE_VERTICALFLIP;
}
else
{
mobj->z = mobj->target->z - mobj->target->height - (16*mobj->target->scale);
mobj->eflags |= MFE_VERTICALFLIP;
}
P_SetThingPosition(mobj);
if (!mobj->tracer)
{
mobj_t *overlay = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
P_SetTarget(&mobj->tracer, overlay);
P_SetTarget(&mobj->tracer->target, mobj);
P_SetMobjState(mobj->tracer, S_PLAYERARROW_ITEM);
P_SetScale(mobj->tracer, (mobj->tracer->destscale = mobj->scale));
}
// Do this in an easy way
if (mobj->target->player->kartstuff[k_itemroulette])
{
mobj->tracer->color = mobj->target->player->skincolor;
mobj->tracer->colorized = true;
}
else
{
mobj->tracer->color = SKINCOLOR_NONE;
mobj->tracer->colorized = false;
}
if (!(mobj->drawflags & MFD_DONTDRAW))
{
const INT32 numberdisplaymin = ((mobj->target->player->kartstuff[k_itemtype] == KITEM_ORBINAUT) ? 5 : 2);
// Set it to use the correct states for its condition
if (mobj->target->player->kartstuff[k_itemroulette])
{
P_SetMobjState(mobj, S_PLAYERARROW_BOX);
mobj->tracer->sprite = SPR_ITEM;
mobj->tracer->frame = FF_FULLBRIGHT|(((mobj->target->player->kartstuff[k_itemroulette] % (13*3)) / 3) + 1);
mobj->tracer->drawflags &= ~MFD_DONTDRAW;
}
else if (mobj->target->player->kartstuff[k_stolentimer] > 0)
{
P_SetMobjState(mobj, S_PLAYERARROW_BOX);
mobj->tracer->sprite = SPR_ITEM;
mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO;
if (leveltime & 2)
mobj->tracer->drawflags &= ~MFD_DONTDRAW;
else
mobj->tracer->drawflags |= MFD_DONTDRAW;
}
else if ((mobj->target->player->kartstuff[k_stealingtimer] > 0) && (leveltime & 2))
{
P_SetMobjState(mobj, S_PLAYERARROW_BOX);
mobj->tracer->sprite = SPR_ITEM;
mobj->tracer->frame = FF_FULLBRIGHT|KITEM_HYUDORO;
mobj->tracer->drawflags &= ~MFD_DONTDRAW;
}
else if (mobj->target->player->kartstuff[k_eggmanexplode] > 1)
{
P_SetMobjState(mobj, S_PLAYERARROW_BOX);
mobj->tracer->sprite = SPR_ITEM;
mobj->tracer->frame = FF_FULLBRIGHT|KITEM_EGGMAN;
if (leveltime & 1)
mobj->tracer->drawflags &= ~MFD_DONTDRAW;
else
mobj->tracer->drawflags |= MFD_DONTDRAW;
}
else if (mobj->target->player->kartstuff[k_rocketsneakertimer] > 1)
{
//itembar = mobj->target->player->kartstuff[k_rocketsneakertimer]; -- not today satan
P_SetMobjState(mobj, S_PLAYERARROW_BOX);
mobj->tracer->sprite = SPR_ITEM;
mobj->tracer->frame = FF_FULLBRIGHT|KITEM_ROCKETSNEAKER;
if (leveltime & 1)
mobj->tracer->drawflags &= ~MFD_DONTDRAW;
else
mobj->tracer->drawflags |= MFD_DONTDRAW;
}
else if (mobj->target->player->kartstuff[k_itemtype] && mobj->target->player->kartstuff[k_itemamount] > 0)
{
P_SetMobjState(mobj, S_PLAYERARROW_BOX);
switch (mobj->target->player->kartstuff[k_itemtype])
{
case KITEM_ORBINAUT:
mobj->tracer->sprite = SPR_ITMO;
mobj->tracer->frame = FF_FULLBRIGHT|(min(mobj->target->player->kartstuff[k_itemamount]-1, 3));
break;
case KITEM_INVINCIBILITY:
mobj->tracer->sprite = SPR_ITMI;
mobj->tracer->frame = FF_FULLBRIGHT|((leveltime % (7*3)) / 3);
break;
case KITEM_SAD:
mobj->tracer->sprite = SPR_ITEM;
mobj->tracer->frame = FF_FULLBRIGHT;
break;
default:
mobj->tracer->sprite = SPR_ITEM;
mobj->tracer->frame = FF_FULLBRIGHT|(mobj->target->player->kartstuff[k_itemtype]);
break;
}
if (mobj->target->player->kartstuff[k_itemheld])
{
if (leveltime & 1)
mobj->tracer->drawflags &= ~MFD_DONTDRAW;
else
mobj->tracer->drawflags |= MFD_DONTDRAW;
}
else
mobj->tracer->drawflags &= ~MFD_DONTDRAW;
}
else
{
P_SetMobjState(mobj, S_PLAYERARROW);
P_SetMobjState(mobj->tracer, S_PLAYERARROW_ITEM);
}
mobj->tracer->destscale = scale;
if (mobj->target->player->kartstuff[k_itemamount] >= numberdisplaymin
&& mobj->target->player->kartstuff[k_itemamount] <= 10) // Meh, too difficult to support greater than this; convert this to a decent HUD object and then maybe :V
{
mobj_t *number = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
mobj_t *numx = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
P_SetTarget(&number->target, mobj);
P_SetMobjState(number, S_PLAYERARROW_NUMBER);
P_SetScale(number, mobj->scale);
number->destscale = scale;
number->frame = FF_FULLBRIGHT|(mobj->target->player->kartstuff[k_itemamount]);
P_SetTarget(&numx->target, mobj);
P_SetMobjState(numx, S_PLAYERARROW_X);
P_SetScale(numx, mobj->scale);
numx->destscale = scale;
}
if (K_IsPlayerWanted(mobj->target->player) && mobj->movecount != 1)
{
mobj_t *wanted = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PLAYERWANTED);
P_SetTarget(&wanted->target, mobj->target);
P_SetTarget(&wanted->tracer, mobj);
P_SetScale(wanted, mobj->scale);
wanted->destscale = scale;
mobj->movecount = 1;
}
else if (!K_IsPlayerWanted(mobj->target->player))
mobj->movecount = 0;
}
else
mobj->tracer->drawflags |= MFD_DONTDRAW;
}
else if (mobj->health > 0)
{
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
return;
}
break;
case MT_PLAYERWANTED:
if (mobj->target && mobj->target->health && mobj->tracer
&& mobj->target->player && !mobj->target->player->spectator
&& mobj->target->health && mobj->target->player->playerstate != PST_DEAD
&& players[g_localplayers[0]].mo && !players[g_localplayers[0]].spectator)
{
fixed_t scale = 3*mobj->target->scale;
if (!K_IsPlayerWanted(mobj->target->player))
{
mobj->tracer->movecount = 0;
P_RemoveMobj(mobj);
return;
}
if (mobj->tracer->drawflags & MFD_DONTDRAW)
mobj->drawflags |= MFD_DONTDRAW;
else
mobj->drawflags &= ~MFD_DONTDRAW;
P_UnsetThingPosition(mobj);
mobj->x = mobj->target->x;
mobj->y = mobj->target->y;
if (!r_splitscreen && players[displayplayers[0]].mo)
{
scale = mobj->target->scale + FixedMul(FixedDiv(abs(P_AproxDistance(players[displayplayers[0]].mo->x-mobj->target->x,
players[displayplayers[0]].mo->y-mobj->target->y)), RING_DIST), mobj->target->scale);
if (scale > 16*mobj->target->scale)
scale = 16*mobj->target->scale;
}
mobj->destscale = scale;
if (!(mobj->target->eflags & MFE_VERTICALFLIP))
{
mobj->z = mobj->target->z + mobj->target->height + (16*mobj->target->scale) + (64*scale);
mobj->eflags &= ~MFE_VERTICALFLIP;
}
else
{
mobj->z = mobj->target->z - mobj->target->height - (16*mobj->target->scale) - (64*scale);
mobj->eflags |= MFE_VERTICALFLIP;
}
P_SetThingPosition(mobj);
}
else if (mobj->health > 0)
{
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
return;
}
break;
case MT_PETSMOKER:
if (!(leveltime % 10))
{
mobj_t *smok = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PETSMOKE);
if (mobj->spawnpoint && mobj->spawnpoint->options & MTF_OBJECTSPECIAL)
P_SetMobjStateNF(smok, smok->info->painstate); // same function, diff sprite
}
break;
case MT_BATTLECAPSULE_PIECE:
if (mobj->extravalue2)
mobj->frame |= FF_VERTICALFLIP;
else
mobj->frame &= ~FF_VERTICALFLIP;
if (mobj->flags2 & MF2_OBJECTFLIP)
mobj->eflags |= MFE_VERTICALFLIP;
if (mobj->tics > 0)
mobj->drawflags ^= MFD_DONTDRAW;
break;
case MT_SPINDASHWIND:
mobj->drawflags ^= MFD_DONTDRAW;
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 (!LUAh_MobjFuse(mobj))
P_RemoveMobj(mobj);
return;
}
}
break;
}
P_SceneryThinker(mobj);
}
static boolean P_MobjPushableThink(mobj_t *mobj)
{
P_MobjCheckWater(mobj);
P_PushableThinker(mobj);
return true;
}
static boolean P_MobjBossThink(mobj_t *mobj)
{
if (LUAh_BossThinker(mobj))
{
if (P_MobjWasRemoved(mobj))
return false;
}
else if (P_MobjWasRemoved(mobj))
return false;
else
switch (mobj->type)
{
// No SRB2Kart bosses... yet :)
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));
}
return true;
}
static boolean P_MobjDeadThink(mobj_t *mobj)
{
switch (mobj->type)
{
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->drawflags |= MFD_DONTDRAW;
}
else // safe to remove, nobody's going to complain!
{
P_RemoveMobj(mobj);
return false;
}
}
else // Apply gravity to fall downwards.
{
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);
S_StartSound(explosion, sfx_s3kb4);
}
P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
}
break;
case MT_ORBINAUT:
case MT_BANANA:
case MT_EGGMANITEM:
case MT_LANDMINE:
case MT_SPB:
if (P_IsObjectOnGround(mobj))
{
P_RemoveMobj(mobj);
return false;
}
/* FALLTHRU */
case MT_ORBINAUT_SHIELD:
case MT_BANANA_SHIELD:
case MT_EGGMANITEM_SHIELD:
mobj->drawflags ^= MFD_DONTDRAW;
break;
case MT_JAWZ:
case MT_JAWZ_DUD:
if (P_IsObjectOnGround(mobj))
P_SetMobjState(mobj, mobj->info->xdeathstate);
/* FALLTHRU */
case MT_JAWZ_SHIELD:
mobj->drawflags ^= MFD_DONTDRAW;
break;
case MT_SSMINE:
case MT_SPBEXPLOSION:
if (mobj->extravalue2 != -100)
{
P_SetMobjState(mobj, mobj->info->deathstate);
mobj->extravalue2 = -100;
}
else
{
P_RemoveMobj(mobj);
return false;
}
break;
case MT_MINEEXPLOSIONSOUND:
P_RemoveMobj(mobj);
return false;
case MT_CDUFO:
if (mobj->fuse > TICRATE)
mobj->drawflags ^= MFD_DONTDRAW; // only by good fortune does this end with it having MFD_DONTDRAW... don't touch!
break;
case MT_SMK_PIPE:
if (mobj->flags2 & MF2_AMBUSH)
P_SetMobjStateNF(mobj, mobj->info->seestate);
else
P_SetMobjStateNF(mobj, mobj->info->spawnstate);
/* FALLTHRU */
case MT_SMK_MOLE:
mobj->drawflags ^= MFD_DONTDRAW;
if (P_IsObjectOnGround(mobj))
{
P_RemoveMobj(mobj);
return false;
}
break;
case MT_SMK_THWOMP:
if (mobj->flags2 & MF2_AMBUSH)
{
mobj->colorized = true;
mobj->color = K_RainbowColor(leveltime);
mobj->frame |= FF_FULLBRIGHT;
}
else
{
mobj->colorized = false;
mobj->color = SKINCOLOR_NONE;
mobj->frame &= (~FF_FULLBRIGHT);
}
mobj->drawflags ^= MFD_DONTDRAW;
if (P_IsObjectOnGround(mobj))
{
P_RemoveMobj(mobj);
return false;
}
break;
case MT_BATTLECAPSULE:
if (!(mobj->fuse & 1))
{
const SINT8 amt = 96;
mobj_t *dust;
UINT8 i;
for (i = 0; i < 2; i++)
{
fixed_t xoffset = P_RandomRange(-amt, amt) * mobj->scale;
fixed_t yoffset = P_RandomRange(-amt, amt) * mobj->scale;
fixed_t zoffset = P_RandomRange(-(amt >> 1), (amt >> 1)) * mobj->scale;
dust = P_SpawnMobj(mobj->x + xoffset, mobj->y + yoffset,
mobj->z + (mobj->height >> 1) + zoffset, MT_EXPLODE);
}
if (dust && !P_MobjWasRemoved(dust)) // Only do for 1 explosion
S_StartSound(dust, sfx_s3k3d);
}
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->angleturn;
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 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))
{
mobj->fuse = TICRATE;
}
P_MobjCheckWater(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_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_PLAYER:
if (mobj->player)
P_PlayerMobjThinker(mobj);
return false;
case MT_RING:
if (P_MobjWasRemoved(mobj))
return false;
// No need to check water. Who cares?
P_RingThinker(mobj);
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
else
A_AttractChase(mobj);
return false;
// Flung items
case MT_FLINGRING:
if (P_MobjWasRemoved(mobj))
return false;
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
else
A_AttractChase(mobj);
break;
case MT_EMBLEM:
if (mobj->flags2 & MF2_NIGHTSPULL)
P_NightsItemChase(mobj);
break;
case MT_FLOATINGITEM:
{
if (mobj->flags & MF_NOCLIPTHING)
{
if (P_CheckDeathPitCollide(mobj))
{
P_RemoveMobj(mobj);
return false;
}
else if (P_IsObjectOnGround(mobj))
{
mobj->momx = 1;
mobj->momy = 0;
mobj->flags &= ~MF_NOCLIPTHING;
mobj->flags |= MF_NOGRAVITY;
}
}
else
{
mobj->angle += 2*ANG2;
if (mobj->flags2 & MF2_NIGHTSPULL)
{
if (!mobj->tracer || !mobj->tracer->health
|| mobj->scale <= mapobjectscale>>4)
{
P_RemoveMobj(mobj);
return false;
}
P_Attract(mobj, mobj->tracer, true);
}
else
{
fixed_t adj = FixedMul(FRACUNIT - FINECOSINE((mobj->movedir>>ANGLETOFINESHIFT) & FINEMASK), (mapobjectscale<<3));
mobj->movedir += 2*ANG2;
if (mobj->eflags & MFE_VERTICALFLIP)
mobj->z = mobj->ceilingz - mobj->height - adj;
else
mobj->z = mobj->floorz + adj;
}
}
switch (mobj->threshold)
{
case KITEM_ORBINAUT:
mobj->sprite = SPR_ITMO;
mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(min(mobj->movecount-1, 3));
break;
case KITEM_INVINCIBILITY:
mobj->sprite = SPR_ITMI;
mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|((leveltime % (7*3)) / 3);
break;
case KITEM_SAD:
mobj->sprite = SPR_ITEM;
mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE;
break;
case KITEM_SPB:
case KITEM_SHRINK:
indirectitemcooldown = 20*TICRATE;
/* FALLTHRU */
default:
mobj->sprite = SPR_ITEM;
mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(mobj->threshold);
break;
}
break;
}
case MT_ORBINAUT:
{
boolean grounded = P_IsObjectOnGround(mobj);
if (mobj->flags2 & MF2_AMBUSH)
{
if (grounded && (mobj->flags & MF_NOCLIPTHING))
{
mobj->momx = 1;
mobj->momy = 0;
mobj->frame = 3;
S_StartSound(mobj, mobj->info->activesound);
mobj->flags &= ~MF_NOCLIPTHING;
}
else if (mobj->movecount)
mobj->movecount--;
else if (mobj->frame < 3)
{
mobj->movecount = 2;
mobj->frame++;
}
}
else
{
fixed_t finalspeed = mobj->movefactor;
const fixed_t currentspeed = R_PointToDist2(0, 0, mobj->momx, mobj->momy);
fixed_t thrustamount = 0;
fixed_t frictionsafety = (mobj->friction == 0) ? 1 : mobj->friction;
mobj_t *ghost = P_SpawnGhostMobj(mobj);
ghost->colorized = true; // already has color!
if (!grounded)
{
// No friction in the air
frictionsafety = FRACUNIT;
}
mobj->angle = K_MomentumAngle(mobj);
if (mobj->health <= 5)
{
INT32 i;
for (i = 5; i >= mobj->health; i--)
finalspeed = FixedMul(finalspeed, FRACUNIT-FRACUNIT/4);
}
if (currentspeed >= finalspeed)
{
// Thrust as if you were at top speed, slow down naturally
thrustamount = FixedDiv(finalspeed, frictionsafety) - finalspeed;
}
else
{
const fixed_t beatfriction = FixedDiv(currentspeed, frictionsafety) - currentspeed;
// Thrust to immediately get to top speed
thrustamount = beatfriction + FixedDiv(finalspeed - currentspeed, frictionsafety);
}
P_Thrust(mobj, mobj->angle, thrustamount);
if (P_MobjTouchingSectorSpecial(mobj, 3, 1, true))
K_DoPogoSpring(mobj, 0, 1);
if (mobj->threshold > 0)
mobj->threshold--;
if (leveltime % 6 == 0)
S_StartSound(mobj, mobj->info->activesound);
}
break;
}
case MT_JAWZ:
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
ghost->color = mobj->target->player->skincolor;
ghost->colorized = true;
}
if (mobj->threshold > 0)
mobj->threshold--;
if (leveltime % TICRATE == 0)
S_StartSound(mobj, mobj->info->activesound);
// Movement handling has ALL been moved to A_JawzChase
K_DriftDustHandling(mobj);
if (P_MobjTouchingSectorSpecial(mobj, 3, 1, true))
K_DoPogoSpring(mobj, 0, 1);
if (!(gametyperules & GTR_CIRCUIT))
mobj->friction = max(0, 3 * mobj->friction / 4);
break;
}
case MT_JAWZ_DUD:
{
boolean grounded = P_IsObjectOnGround(mobj);
if (mobj->flags2 & MF2_AMBUSH)
{
if (grounded && (mobj->flags & MF_NOCLIPTHING))
{
mobj->momx = 1;
mobj->momy = 0;
S_StartSound(mobj, mobj->info->deathsound);
mobj->flags &= ~MF_NOCLIPTHING;
}
}
else
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
const fixed_t currentspeed = R_PointToDist2(0, 0, mobj->momx, mobj->momy);
fixed_t frictionsafety = (mobj->friction == 0) ? 1 : mobj->friction;
fixed_t thrustamount = 0;
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
ghost->color = mobj->target->player->skincolor;
ghost->colorized = true;
}
if (!grounded)
{
// No friction in the air
frictionsafety = FRACUNIT;
}
if (currentspeed >= mobj->movefactor)
{
// Thrust as if you were at top speed, slow down naturally
thrustamount = FixedDiv(mobj->movefactor, frictionsafety) - mobj->movefactor;
}
else
{
const fixed_t beatfriction = FixedDiv(currentspeed, frictionsafety) - currentspeed;
// Thrust to immediately get to top speed
thrustamount = beatfriction + FixedDiv(mobj->movefactor - currentspeed, frictionsafety);
}
mobj->angle = K_MomentumAngle(mobj);
P_Thrust(mobj, mobj->angle, thrustamount);
if (P_MobjTouchingSectorSpecial(mobj, 3, 1, true))
K_DoPogoSpring(mobj, 0, 1);
if (mobj->threshold > 0)
mobj->threshold--;
if (leveltime % TICRATE == 0)
S_StartSound(mobj, mobj->info->activesound);
}
break;
}
case MT_EGGMANITEM:
{
player_t *player = K_GetItemBoxPlayer(mobj);
UINT8 color = SKINCOLOR_BLACK;
if (player != NULL)
{
color = player->skincolor;
}
mobj->color = color;
mobj->colorized = false;
}
/* FALLTHRU */
case MT_BANANA:
mobj->friction = ORIG_FRICTION/4;
if (mobj->momx || mobj->momy)
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
ghost->color = mobj->target->player->skincolor;
ghost->colorized = true;
}
}
if (P_IsObjectOnGround(mobj) && mobj->health > 1)
{
S_StartSound(mobj, mobj->info->activesound);
mobj->momx = mobj->momy = 0;
mobj->health = 1;
}
if (mobj->threshold > 0)
mobj->threshold--;
break;
case MT_SPB:
indirectitemcooldown = 20*TICRATE;
/* FALLTHRU */
case MT_BALLHOG:
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
ghost->fuse = 3;
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
ghost->color = mobj->target->player->skincolor;
ghost->colorized = true;
}
if (mobj->threshold > 0)
mobj->threshold--;
}
break;
case MT_SINK:
if (mobj->momx || mobj->momy)
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
ghost->color = mobj->target->player->skincolor;
ghost->colorized = true;
}
}
if (P_IsObjectOnGround(mobj))
{
S_StartSound(mobj, mobj->info->deathsound);
P_SetMobjState(mobj, S_NULL);
}
if (mobj->threshold > 0)
mobj->threshold--;
break;
case MT_SSMINE:
if (mobj->target && mobj->target->player)
mobj->color = mobj->target->player->skincolor;
else
mobj->color = SKINCOLOR_KETCHUP;
if (mobj->momx || mobj->momy)
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
ghost->colorized = true; // already has color!
}
if (P_IsObjectOnGround(mobj) && (mobj->momz * P_MobjFlip(mobj)) <= 0
&& (mobj->state == &states[S_SSMINE_AIR1] || mobj->state == &states[S_SSMINE_AIR2]))
{
if (mobj->extravalue1 > 0)
mobj->extravalue1--;
else
{
mobj->momx = mobj->momy = 0;
S_StartSound(mobj, mobj->info->activesound);
P_SetMobjState(mobj, S_SSMINE_DEPLOY1);
}
}
if ((mobj->state >= &states[S_SSMINE1] && mobj->state <= &states[S_SSMINE4])
|| (mobj->state >= &states[S_SSMINE_DEPLOY8] && mobj->state <= &states[S_SSMINE_DEPLOY13]))
A_GrenadeRing(mobj);
if (mobj->threshold > 0)
mobj->threshold--;
break;
case MT_LANDMINE:
mobj->friction = ORIG_FRICTION/4;
if (mobj->momx || mobj->momy || mobj->momz)
{
mobj_t *ghost = P_SpawnGhostMobj(mobj);
ghost->colorized = true; // already has color!
}
if (P_IsObjectOnGround(mobj) && mobj->health > 1)
{
S_StartSound(mobj, mobj->info->activesound);
mobj->momx = mobj->momy = 0;
mobj->health = 1;
}
if (mobj->threshold > 0)
mobj->threshold--;
break;
case MT_SPBEXPLOSION:
mobj->health--;
break;
case MT_MINEEXPLOSION:
if ((mobj->z < mobj->floorz - mobj->height) || (mobj->z > mobj->ceilingz + mobj->height))
{
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
break;
}
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 false; // freed itself
}
P_UnsetThingPosition(mobj);
mobj->x += mobj->momx;
mobj->y += mobj->momy;
mobj->z += mobj->momz;
P_SetThingPosition(mobj);
return false;
case MT_MINEEXPLOSIONSOUND:
if (mobj->health == 100)
S_StartSound(mobj, sfx_s3k4e);
mobj->health--;
break;
case MT_EMERALD:
{
if (battleovertime.enabled >= 10*TICRATE)
{
fixed_t distance = R_PointToDist2(mobj->x, mobj->y, battleovertime.x, battleovertime.y);
if (distance > battleovertime.radius)
{
// Delete emeralds to let them reappear
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
}
}
if (leveltime % 3 == 0)
{
mobj_t *sparkle = P_SpawnMobjFromMobj(
mobj,
P_RandomRange(-48, 48) * FRACUNIT,
P_RandomRange(-48, 48) * FRACUNIT,
P_RandomRange(0, 64) * FRACUNIT,
MT_EMERALDSPARK
);
sparkle->color = mobj->color;
sparkle->momz += 8 * mobj->scale * P_MobjFlip(mobj);
}
if (mobj->threshold > 0)
mobj->threshold--;
}
break;
case MT_DRIFTEXPLODE:
if (!mobj->target || !mobj->target->health)
{
P_RemoveMobj(mobj);
return false;
}
//mobj->angle = mobj->target->angle;
{
angle_t angle = K_MomentumAngle(mobj->target);
fixed_t nudge;
mobj->angle = angle;
if (( mobj->fuse & 1 ))
{
nudge = 4*mobj->target->radius;
}
else
{
nudge = 2*mobj->target->radius;
/* rotate the papersprite frames to see the flat angle */
mobj->angle += ANGLE_90;
}
P_TeleportMove(mobj,
mobj->target->x + P_ReturnThrustX(mobj, angle + ANGLE_180, nudge),
mobj->target->y + P_ReturnThrustY(mobj, angle + ANGLE_180, nudge),
mobj->target->z);
}
P_SetScale(mobj, mobj->target->scale);
mobj->roll = mobj->target->roll;
mobj->pitch = mobj->target->pitch;
if (mobj->fuse <= 16)
{
mobj->color = SKINCOLOR_KETCHUP;
/* don't draw papersprite frames after blue boost */
mobj->drawflags ^= MFD_DONTDRAW;
}
else if (mobj->fuse <= 32)
mobj->color = SKINCOLOR_SAPPHIRE;
else if (mobj->fuse > 32)
mobj->color = K_RainbowColor(
(SKINCOLOR_PURPLE - SKINCOLOR_PINK) // Smoothly transition into the other state
+ ((mobj->fuse - 32) * 2) // Make the color flashing slow down while it runs out
);
switch (mobj->extravalue1)
{
case 3:/* rainbow boost */
/* every 20 tics, bang! */
if (( 120 - mobj->fuse ) % 10 == 0)
{
K_SpawnDriftBoostClip(mobj->target->player);
S_StartSound(mobj->target, sfx_s3k77);
}
break;
case 2:/* blue boost */
if (mobj->fuse == 16)/* to red*/
K_SpawnDriftBoostClip(mobj->target->player);
break;
}
{
player_t *p = NULL;
if (mobj->target->target && mobj->target->target->player)
p = mobj->target->target->player;
else if (mobj->target->player)
p = mobj->target->player;
if (p)
{
if (p->kartstuff[k_driftboost] > mobj->movecount)
{
; // reset animation
}
mobj->movecount = p->kartstuff[k_driftboost];
}
}
break;
case MT_BOOSTFLAME:
if (!mobj->target || !mobj->target->health)
{
P_RemoveMobj(mobj);
return false;
}
mobj->angle = mobj->target->angle;
P_TeleportMove(mobj, mobj->target->x + P_ReturnThrustX(mobj, mobj->angle+ANGLE_180, mobj->target->radius),
mobj->target->y + P_ReturnThrustY(mobj, mobj->angle+ANGLE_180, mobj->target->radius), mobj->target->z);
P_SetScale(mobj, mobj->target->scale);
mobj->roll = mobj->target->roll;
mobj->pitch = mobj->target->pitch;
{
player_t *p = NULL;
if (mobj->target->target && mobj->target->target->player)
p = mobj->target->target->player;
else if (mobj->target->player)
p = mobj->target->player;
if (p)
{
if (p->kartstuff[k_sneakertimer] > mobj->movecount)
P_SetMobjState(mobj, S_BOOSTFLAME);
mobj->movecount = p->kartstuff[k_sneakertimer];
}
}
if (mobj->state == &states[S_BOOSTSMOKESPAWNER])
{
mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z+(8<<FRACBITS), MT_BOOSTSMOKE);
P_SetScale(smoke, mobj->target->scale/2);
smoke->destscale = 3*mobj->target->scale/2;
smoke->scalespeed = mobj->target->scale/12;
smoke->momx = mobj->target->momx/2;
smoke->momy = mobj->target->momy/2;
smoke->momz = mobj->target->momz/2;
P_Thrust(smoke, mobj->angle+FixedAngle(P_RandomRange(135, 225)<<FRACBITS), P_RandomRange(0, 8) * mobj->target->scale);
}
break;
case MT_SPARKLETRAIL:
if (!mobj->target)
{
P_RemoveMobj(mobj);
return false;
}
mobj->color = mobj->target->color;
mobj->colorized = mobj->target->colorized;
break;
case MT_INVULNFLASH:
if (!mobj->target || !mobj->target->health || (mobj->target->player && !mobj->target->player->kartstuff[k_invincibilitytimer]))
{
P_RemoveMobj(mobj);
return false;
}
P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
break;
case MT_BRAKEDRIFT:
if ((!mobj->target || !mobj->target->health || !mobj->target->player || !P_IsObjectOnGround(mobj->target))
|| !mobj->target->player->kartstuff[k_drift] || !mobj->target->player->kartstuff[k_brakedrift]
|| !((mobj->target->player->cmd.buttons & BT_BRAKE)
|| !(mobj->target->player->cmd.buttons & BT_ACCELERATE))) // Letting go of accel functions about the same as brake-drifting
{
P_RemoveMobj(mobj);
return false;
}
else
{
UINT8 driftcolor = K_DriftSparkColor(mobj->target->player, mobj->target->player->kartstuff[k_driftcharge]);
fixed_t newx, newy;
angle_t travelangle;
travelangle = mobj->target->angle - ((ANGLE_45/5)*mobj->target->player->kartstuff[k_drift]);
newx = mobj->target->x + P_ReturnThrustX(mobj->target, travelangle+ANGLE_180, 24*mobj->target->scale);
newy = mobj->target->y + P_ReturnThrustY(mobj->target, travelangle+ANGLE_180, 24*mobj->target->scale);
P_TeleportMove(mobj, newx, newy, mobj->target->z);
mobj->angle = travelangle - ((ANGLE_90/5)*mobj->target->player->kartstuff[k_drift]);
P_SetScale(mobj, (mobj->destscale = mobj->target->scale));
if (driftcolor != SKINCOLOR_NONE)
mobj->color = driftcolor;
else
mobj->color = SKINCOLOR_SILVER;
if (!S_SoundPlaying(mobj, sfx_cdfm17))
S_StartSound(mobj, sfx_cdfm17);
K_MatchGenericExtraFlags(mobj, mobj->target);
if (leveltime & 1)
mobj->drawflags |= MFD_DONTDRAW;
}
break;
case MT_PLAYERRETICULE:
if (!mobj->target || !mobj->target->health)
{
P_RemoveMobj(mobj);
return false;
}
P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
break;
case MT_INSTASHIELDB:
mobj->drawflags ^= MFD_DONTDRAW;
K_MatchGenericExtraFlags(mobj, mobj->target);
/* FALLTHRU */
case MT_INSTASHIELDA:
if (!mobj->target || !mobj->target->health || (mobj->target->player && !mobj->target->player->kartstuff[k_instashield]))
{
P_RemoveMobj(mobj);
return false;
}
P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
K_MatchGenericExtraFlags(mobj, mobj->target);
break;
case MT_BATTLEPOINT:
if (!mobj->target || P_MobjWasRemoved(mobj->target))
{
P_RemoveMobj(mobj);
return false;
}
if (mobj->movefactor < 48*mobj->target->scale)
{
mobj->movefactor += (48*mobj->target->scale)/6;
if (mobj->movefactor > mobj->target->height)
mobj->movefactor = mobj->target->height;
}
else if (mobj->movefactor > 48*mobj->target->scale)
{
mobj->movefactor -= (48*mobj->target->scale)/6;
if (mobj->movefactor < mobj->target->height)
mobj->movefactor = mobj->target->height;
}
K_MatchGenericExtraFlags(mobj, mobj->target);
P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (mobj->target->height/2) + mobj->movefactor);
break;
case MT_RINGSPARKS:
if (!mobj->target || P_MobjWasRemoved(mobj->target))
{
P_RemoveMobj(mobj);
return false;
}
mobj->z = mobj->target->z;
K_MatchGenericExtraFlags(mobj, mobj->target);
P_TeleportMove(mobj, mobj->target->x + FINECOSINE(mobj->angle >> ANGLETOFINESHIFT),
mobj->target->y + FINESINE(mobj->angle >> ANGLETOFINESHIFT),
mobj->z + mobj->target->height * P_MobjFlip(mobj));
break;
case MT_FLAMESHIELDPAPER:
if (!mobj->target || P_MobjWasRemoved(mobj->target))
{
P_RemoveMobj(mobj);
return false;
}
mobj->z = mobj->target->z;
K_MatchGenericExtraFlags(mobj, mobj->target);
{
INT32 perpendicular = ((mobj->extravalue1 & 1) ? -ANGLE_90 : ANGLE_90);
fixed_t newx = mobj->target->x + P_ReturnThrustX(NULL, mobj->target->angle + perpendicular, 8*mobj->target->scale);
fixed_t newy = mobj->target->y + P_ReturnThrustY(NULL, mobj->target->angle + perpendicular, 8*mobj->target->scale);
P_TeleportMove(mobj, newx, newy, mobj->target->z);
if (mobj->extravalue1 & 1)
mobj->angle = mobj->target->angle - ANGLE_45;
else
mobj->angle = mobj->target->angle + ANGLE_45;
}
break;
case MT_TIREGREASE:
if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->player
|| !mobj->target->player->kartstuff[k_tiregrease])
{
P_RemoveMobj(mobj);
return false;
}
K_MatchGenericExtraFlags(mobj, mobj->target);
{
const angle_t off = FixedAngle(40*FRACUNIT);
angle_t ang = K_MomentumAngle(mobj->target);
fixed_t z;
UINT8 trans = (mobj->target->player->kartstuff[k_tiregrease] * (NUMTRANSMAPS+1)) / greasetics;
if (trans > NUMTRANSMAPS)
trans = NUMTRANSMAPS;
trans = NUMTRANSMAPS - trans;
z = mobj->target->z;
if (mobj->eflags & MFE_VERTICALFLIP)
z += mobj->target->height;
if (mobj->extravalue1)
ang = (signed)(ang - off);
else
ang = (signed)(ang + off);
P_TeleportMove(mobj,
mobj->target->x - FixedMul(mobj->target->radius, FINECOSINE(ang >> ANGLETOFINESHIFT)),
mobj->target->y - FixedMul(mobj->target->radius, FINESINE(ang >> ANGLETOFINESHIFT)),
z);
mobj->angle = ang;
if (leveltime & 1)
mobj->drawflags |= MFD_DONTDRAW;
if (trans >= NUMTRANSMAPS)
mobj->drawflags |= MFD_DONTDRAW;
else if (trans == 0)
mobj->drawflags = (mobj->drawflags & ~MFD_TRANSMASK);
else
mobj->drawflags = (mobj->drawflags & ~MFD_TRANSMASK)|(trans << MFD_TRANSSHIFT);
}
break;
case MT_THUNDERSHIELD:
{
fixed_t destx, desty;
if (!mobj->target || !mobj->target->health || !mobj->target->player
|| mobj->target->player->kartstuff[k_curshield] != KSHIELD_THUNDER)
{
P_RemoveMobj(mobj);
return false;
}
P_SetScale(mobj, (mobj->destscale = (5*mobj->target->scale)>>2));
if (!r_splitscreen /*&& rendermode != render_soft*/)
{
angle_t viewingangle;
statenum_t curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, camera[0].x, camera[0].y);
if (curstate > S_THUNDERSHIELD15 && curstate <= S_THUNDERSHIELD24)
viewingangle += ANGLE_180;
destx = mobj->target->x + P_ReturnThrustX(mobj->target, viewingangle, mobj->scale>>4);
desty = mobj->target->y + P_ReturnThrustY(mobj->target, viewingangle, mobj->scale>>4);
}
else
{
destx = mobj->target->x;
desty = mobj->target->y;
}
P_TeleportMove(mobj, destx, desty, mobj->target->z);
break;
}
case MT_BUBBLESHIELD:
{
fixed_t destx, desty;
fixed_t scale;
statenum_t curstate;
if (!mobj->target || !mobj->target->health || !mobj->target->player
|| mobj->target->player->kartstuff[k_curshield] != KSHIELD_BUBBLE)
{
P_RemoveMobj(mobj);
return false;
}
scale = (5*mobj->target->scale)>>2;
curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));
if (mobj->target->player->kartstuff[k_bubbleblowup])
{
INT32 blow = mobj->target->player->kartstuff[k_bubbleblowup];
if (blow > bubbletime)
blow = bubbletime;
if (curstate != S_BUBBLESHIELDBLOWUP)
P_SetMobjState(mobj, S_BUBBLESHIELDBLOWUP);
mobj->angle += ANGLE_22h;
mobj->drawflags &= ~MFD_SHADOW;
scale += (blow * (3*scale)) / bubbletime;
mobj->frame = (states[S_BUBBLESHIELDBLOWUP].frame + mobj->extravalue1);
if ((mobj->target->player->kartstuff[k_bubbleblowup] > bubbletime) && (leveltime & 1))
mobj->frame = (states[S_BUBBLESHIELDBLOWUP].frame + 5);
if (mobj->extravalue1 < 4 && mobj->extravalue2 < blow && !mobj->cvmem && (leveltime & 1)) // Growing
{
mobj->extravalue1++;
if (mobj->extravalue1 >= 4)
mobj->cvmem = 1; // shrink back down
}
else if ((mobj->extravalue1 > -4 && mobj->extravalue2 > blow)
|| (mobj->cvmem && mobj->extravalue1 > 0)) // Shrinking
mobj->extravalue1--;
if (P_IsObjectOnGround(mobj->target))
{
UINT8 i;
for (i = 0; i < 2; i++)
{
angle_t a = mobj->angle + ((i & 1) ? ANGLE_180 : 0);
fixed_t ws = (mobj->target->scale>>1);
mobj_t *wave;
ws += (blow * ws) / bubbletime;
wave = P_SpawnMobj(
(mobj->target->x - mobj->target->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (21*ws)),
(mobj->target->y - mobj->target->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (21*ws)),
(mobj->target->z - mobj->target->momz), MT_THOK);
wave->flags &= ~(MF_NOCLIPHEIGHT|MF_NOGRAVITY);
P_SetScale(wave, (wave->destscale = ws));
P_SetMobjState(wave, S_BUBBLESHIELDWAVE1);
wave->momx = mobj->target->momx;
wave->momy = mobj->target->momy;
wave->momz = mobj->target->momz;
}
}
}
else
{
mobj->cvmem = 0;
mobj->angle = mobj->target->angle;
if (curstate == S_BUBBLESHIELDBLOWUP)
{
if (mobj->extravalue1 != 0)
{
mobj->frame = (states[S_BUBBLESHIELDBLOWUP].frame + mobj->extravalue1);
if (mobj->extravalue1 < 0 && (leveltime & 1))
mobj->extravalue1++;
else if (mobj->extravalue1 > 0)
mobj->extravalue1--;
}
else
{
P_SetMobjState(mobj, S_BUBBLESHIELD1);
mobj->extravalue1 = 0;
}
}
else
{
if (mobj->target->player->kartstuff[k_bubblecool] && ((curstate-S_BUBBLESHIELD1) & 1))
mobj->drawflags |= MFD_SHADOW;
else
mobj->drawflags &= ~MFD_SHADOW;
}
}
mobj->extravalue2 = mobj->target->player->kartstuff[k_bubbleblowup];
P_SetScale(mobj, (mobj->destscale = scale));
if (!splitscreen /*&& rendermode != render_soft*/)
{
angle_t viewingangle;
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, camera[0].x, camera[0].y);
destx = mobj->target->x + P_ReturnThrustX(mobj->target, viewingangle, mobj->scale>>4);
desty = mobj->target->y + P_ReturnThrustY(mobj->target, viewingangle, mobj->scale>>4);
}
else
{
destx = mobj->target->x;
desty = mobj->target->y;
}
P_TeleportMove(mobj, destx, desty, mobj->target->z);
break;
}
case MT_FLAMESHIELD:
{
fixed_t destx, desty;
statenum_t curstate;
statenum_t underlayst = S_NULL;
INT32 flamemax = 0;
if (!mobj->target || !mobj->target->health || !mobj->target->player
|| mobj->target->player->kartstuff[k_curshield] != KSHIELD_FLAME)
{
P_RemoveMobj(mobj);
return false;
}
flamemax = mobj->target->player->kartstuff[k_flamelength] * flameseg;
P_SetScale(mobj, (mobj->destscale = (5*mobj->target->scale)>>2));
curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states)));
if (mobj->target->player->kartstuff[k_flamedash])
{
if (!(curstate >= S_FLAMESHIELDDASH1 && curstate <= S_FLAMESHIELDDASH12))
P_SetMobjState(mobj, S_FLAMESHIELDDASH1);
if (curstate == S_FLAMESHIELDDASH2)
underlayst = S_FLAMESHIELDDASH2_UNDERLAY;
else if (curstate == S_FLAMESHIELDDASH5)
underlayst = S_FLAMESHIELDDASH5_UNDERLAY;
else if (curstate == S_FLAMESHIELDDASH8)
underlayst = S_FLAMESHIELDDASH8_UNDERLAY;
else if (curstate == S_FLAMESHIELDDASH11)
underlayst = S_FLAMESHIELDDASH11_UNDERLAY;
if (leveltime & 1)
{
UINT8 i;
UINT8 nl = 2;
if (mobj->target->player->kartstuff[k_flamedash] > mobj->extravalue1)
nl = 3;
for (i = 0; i < nl; i++)
{
mobj_t *fast = P_SpawnMobj(mobj->x + (P_RandomRange(-36,36) * mobj->scale),
mobj->y + (P_RandomRange(-36,36) * mobj->scale),
mobj->z + (mobj->height/2) + (P_RandomRange(-20,20) * mobj->scale),
MT_FASTLINE);
fast->angle = mobj->angle;
fast->momx = 3*mobj->target->momx/4;
fast->momy = 3*mobj->target->momy/4;
fast->momz = 3*mobj->target->momz/4;
K_MatchGenericExtraFlags(fast, mobj);
P_SetMobjState(fast, S_FLAMESHIELDLINE1 + i);
}
}
}
else
{
if (curstate >= S_FLAMESHIELDDASH1 && curstate <= S_FLAMESHIELDDASH12)
P_SetMobjState(mobj, S_FLAMESHIELD1);
}
mobj->extravalue1 = mobj->target->player->kartstuff[k_flamedash];
if (mobj->target->player->kartstuff[k_flamemeter] > flamemax)
{
mobj_t *flash = P_SpawnMobj(mobj->x + mobj->target->momx, mobj->y + mobj->target->momy, mobj->z + mobj->target->momz, MT_THOK);
P_SetMobjState(flash, S_FLAMESHIELDFLASH);
if (leveltime & 1)
{
flash->frame |= 2 + ((leveltime / 2) % 4);
}
else
{
flash->frame |= ((leveltime / 2) % 2);
}
}
if (!splitscreen /*&& rendermode != render_soft*/)
{
angle_t viewingangle;
if (players[displayplayers[0]].awayviewtics)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].awayviewmobj->x, players[displayplayers[0]].awayviewmobj->y);
else if (!camera[0].chase && players[displayplayers[0]].mo)
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, players[displayplayers[0]].mo->x, players[displayplayers[0]].mo->y);
else
viewingangle = R_PointToAngle2(mobj->target->x, mobj->target->y, camera[0].x, camera[0].y);
if (curstate >= S_FLAMESHIELD1 && curstate < S_FLAMESHIELDDASH1 && ((curstate-S_FLAMESHIELD1) & 1))
viewingangle += ANGLE_180;
destx = mobj->target->x + P_ReturnThrustX(mobj->target, viewingangle, mobj->scale>>4);
desty = mobj->target->y + P_ReturnThrustY(mobj->target, viewingangle, mobj->scale>>4);
}
else
{
destx = mobj->target->x;
desty = mobj->target->y;
}
P_TeleportMove(mobj, destx, desty, mobj->target->z);
mobj->angle = K_MomentumAngle(mobj->target);
if (underlayst != S_NULL)
{
mobj_t *underlay = P_SpawnMobj(mobj->target->x, mobj->target->y, mobj->target->z, MT_FLAMESHIELDUNDERLAY);
underlay->angle = mobj->angle;
P_SetMobjState(underlay, underlayst);
}
break;
}
case MT_ROCKETSNEAKER:
if (!mobj->target || !mobj->target->health)
{
P_RemoveMobj(mobj);
return false;
}
if (mobj->target->player && !mobj->target->player->kartstuff[k_rocketsneakertimer])
{
mobj->flags &= ~MF_NOGRAVITY;
mobj->angle += ANGLE_45;
if (!mobj->extravalue2)
{
K_DropRocketSneaker(mobj->target->player);
}
else if (P_IsObjectOnGround(mobj))
{
P_RemoveMobj(mobj);
return false;
}
}
break;
case MT_KARMAHITBOX:
{
statenum_t state = (mobj->state-states);
if (!mobj->target || !mobj->target->health || !mobj->target->player || mobj->target->player->spectator
|| (gametype == GT_RACE || mobj->target->player->bumpers))
{
P_RemoveMobj(mobj);
return false;
}
P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z);
mobj->angle = mobj->target->angle;
mobj->scalespeed = mobj->target->scalespeed;
mobj->destscale = mobj->target->destscale;
P_SetScale(mobj, mobj->target->scale);
mobj->color = mobj->target->color;
mobj->colorized = true;
// Give items an item-sized hitbox
if (mobj->target->player->kartstuff[k_comebackmode] == 1)
mobj->radius = 48*mobj->target->scale;
else
mobj->radius = 24*mobj->target->scale;
mobj->height = 2*mobj->radius;
if (mobj->target->player->karmadelay > 0)
{
if (state < S_PLAYERBOMB1 || state > S_PLAYERBOMB20)
P_SetMobjState(mobj, S_PLAYERBOMB1);
if (mobj->target->player->karmadelay < TICRATE && (leveltime & 1))
mobj->drawflags &= ~MFD_DONTDRAW;
else
mobj->drawflags |= MFD_DONTDRAW;
}
else
{
if (!mobj->target->player->kartstuff[k_comebackmode]
&& (state < S_PLAYERBOMB1 || state > S_PLAYERBOMB20))
P_SetMobjState(mobj, S_PLAYERBOMB1);
else if (mobj->target->player->kartstuff[k_comebackmode] == 1
&& (state < S_PLAYERITEM1 || state > S_PLAYERITEM12))
P_SetMobjState(mobj, S_PLAYERITEM1);
else if (mobj->target->player->kartstuff[k_comebackmode] == 2
&& (state < S_PLAYERFAKE1 || state > S_PLAYERFAKE12))
P_SetMobjState(mobj, S_PLAYERFAKE1);
if (mobj->target->player->powers[pw_flashing] && (leveltime & 1))
mobj->drawflags |= MFD_DONTDRAW;
else
mobj->drawflags &= ~MFD_DONTDRAW;
}
// Update mobj antigravity status:
mobj->eflags = (mobj->eflags & ~MFE_VERTICALFLIP)|(mobj->target->eflags & MFE_VERTICALFLIP);
mobj->flags2 = (mobj->flags2 & ~MF2_OBJECTFLIP)|(mobj->target->flags2 & MF2_OBJECTFLIP);
// Now for the wheels
{
const fixed_t rad = FixedMul(mobjinfo[MT_PLAYER].radius, mobj->target->scale);
mobj_t *cur = mobj->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
fixed_t offx = rad;
fixed_t offy = rad;
if (cur->lastlook == 1 || cur->lastlook == 3)
offx *= -1;
if (cur->lastlook == 2 || cur->lastlook == 3)
offy *= -1;
P_TeleportMove(cur, mobj->x + offx, mobj->y + offy, mobj->z);
cur->scalespeed = mobj->target->scalespeed;
cur->destscale = mobj->target->destscale;
P_SetScale(cur, mobj->target->scale);
cur->color = mobj->target->color;
cur->colorized = true;
K_FlipFromObject(cur, mobj->target);
if (mobj->drawflags & MFD_DONTDRAW)
cur->drawflags |= MFD_DONTDRAW;
else
cur->drawflags &= ~MFD_DONTDRAW;
cur = cur->hnext;
}
}
}
break;
case MT_SIGN: // Kart's unique sign behavior
if (mobj->movecount != 0)
{
mobj_t *cur = mobj->hnext;
SINT8 newskin = -1;
UINT8 newcolor = SKINCOLOR_NONE;
angle_t endangle = FixedAngle(mobj->extravalue1 << FRACBITS);
if (mobj->movecount == 1)
{
if (mobj->z + mobj->momz <= mobj->movefactor)
{
if (mobj->info->attacksound)
S_StartSound(mobj, mobj->info->attacksound);
mobj->z = mobj->movefactor;
mobj->momz = 0;
mobj->movecount = 2;
newskin = ((skin_t*)mobj->target->skin) - skins;
newcolor = mobj->target->player->skincolor;
}
else
{
fixed_t g = (6*mobj->scale);
UINT16 ticstilimpact = abs(mobj->z - mobj->movefactor) / g;
P_SpawnMobj(
mobj->x + FixedMul(48*mobj->scale, FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)),
mobj->y + FixedMul(48*mobj->scale, FINESINE(mobj->angle >> ANGLETOFINESHIFT)),
mobj->z + ((24 + ((leveltime % 4) * 8)) * mobj->scale),
MT_SIGNSPARKLE
);
if (ticstilimpact == (3*TICRATE/2))
{
if (mobj->info->seesound)
S_StartSound(mobj, mobj->info->seesound);
}
mobj->angle += ANGLE_45;
mobj->momz = -g;
if (mobj->angle == endangle + ANGLE_180)
{
if (ticstilimpact <= 8)
{
newskin = ((skin_t*)mobj->target->skin) - skins;
newcolor = mobj->target->player->skincolor;
}
else
{
UINT8 plist[MAXPLAYERS];
UINT8 plistlen = 0;
UINT8 i;
memset(plist, 0, sizeof(plist));
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !players[i].spectator)
{
plist[plistlen] = i;
plistlen++;
}
}
if (plistlen <= 1)
{
// Default to the winner
newskin = ((skin_t*)mobj->target->skin) - skins;
newcolor = mobj->target->player->skincolor;
}
else
{
// Pick another player in the server!
player_t *p = &players[plist[P_RandomKey(plistlen)]];
newskin = ((skin_t*)p->mo->skin) - skins;
newcolor = p->skincolor;
}
}
}
}
}
else if (mobj->movecount == 2)
{
if (mobj->angle != endangle)
mobj->angle += ANGLE_11hh;
}
while (cur && !P_MobjWasRemoved(cur))
{
fixed_t amt = cur->extravalue1 * mobj->scale;
angle_t dir = mobj->angle + (cur->extravalue2 * ANGLE_90);
fixed_t z = mobj->z + (23*mobj->scale);
if (cur->state == &states[S_SIGN_FACE])
{
if (newcolor != SKINCOLOR_NONE)
{
cur->color = skincolors[newcolor].invcolor;
cur->frame = cur->state->frame + skincolors[newcolor].invshade;
}
}
else if (cur->state == &states[S_KART_SIGN])
{
z += (5*mobj->scale);
amt += 1;
if (newskin != -1)
{
cur->skin = &skins[newskin];
cur->color = newcolor;
}
}
else if (cur->state == &states[S_SIGN_ERROR])
{
z += (5*mobj->scale);
amt += 1;
}
P_TeleportMove(
cur,
mobj->x + FixedMul(amt, FINECOSINE(dir >> ANGLETOFINESHIFT)),
mobj->y + FixedMul(amt, FINESINE(dir >> ANGLETOFINESHIFT)),
z
);
cur->angle = dir + ANGLE_90;
cur = cur->hnext;
}
}
break;
case MT_CDUFO:
if (!mobj->spawnpoint || mobj->fuse)
break;
if (mobj->movecount)
{
mobj->movecount--;
break;
}
else if (P_AproxDistance(mobj->x - (mobj->spawnpoint->x<<FRACBITS), mobj->y - (mobj->spawnpoint->y<<FRACBITS)) < (420<<FRACBITS))
break;
mobj->movecount = 3;
{
angle_t facing = P_RandomRange(0, 90);
if (facing >= 45)
facing = InvAngle((facing - 45)*ANG1);
else
facing *= ANG1;
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->spawnpoint->x<<FRACBITS, mobj->spawnpoint->y<<FRACBITS) + facing;
}
break;
case MT_FZEROBOOM: // F-Zero explosion
if (!mobj->extravalue1)
{
fixed_t mx = P_ReturnThrustX(NULL, mobj->angle, 32*mobj->scale);
fixed_t my = P_ReturnThrustY(NULL, mobj->angle, 32*mobj->scale);
mobj_t *explosion = P_SpawnMobj(mobj->x + (2*mx), mobj->y + (2*my), mobj->z+(mobj->height/2), MT_THOK);
P_SetMobjState(explosion, S_FZEROBOOM1);
explosion->scale = mobj->scale*2;
explosion->momx = mx;
explosion->momy = my;
S_StartSound(mobj, mobj->info->seesound);
mobj->extravalue1 = 1;
}
if (!S_SoundPlaying(mobj, mobj->info->attacksound))
S_StartSound(mobj, mobj->info->attacksound);
if (mobj->extravalue2 <= 8) // Short delay
mobj->extravalue2++; // flametimer
else // fire + smoke pillar
{
UINT8 i;
mobj_t *fire = P_SpawnMobj(mobj->x + (P_RandomRange(-32, 32)*mobj->scale), mobj->y + (P_RandomRange(-32, 32)*mobj->scale), mobj->z, MT_THOK);
fire->sprite = SPR_FPRT;
fire->frame = FF_FULLBRIGHT|FF_TRANS30;
fire->scale = mobj->scale*4;
fire->momz = P_RandomRange(2, 3)*mobj->scale;
fire->scalespeed = mobj->scale/12;
fire->destscale = 1;
fire->tics = TICRATE;
for (i = 0; i < 2; i++)
{
mobj_t *smoke = P_SpawnMobj(mobj->x + (P_RandomRange(-16, 16)*mobj->scale), mobj->y + (P_RandomRange(-16, 16)*mobj->scale), mobj->z, MT_SMOKE);
P_SetMobjState(smoke, S_FZSLOWSMOKE1);
smoke->scale = mobj->scale;
smoke->momz = P_RandomRange(3, 10)*mobj->scale;
smoke->destscale = mobj->scale*4;
smoke->scalespeed = mobj->scale/24;
}
}
break;
case MT_EZZPROPELLER:
if (mobj->hnext)
{
mobj_t *cur = mobj->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
cur->angle += FixedAngle(mobj->info->speed);
P_TeleportMove(cur, mobj->x + FINECOSINE((cur->angle*8)>>ANGLETOFINESHIFT),
mobj->y + FINESINE((cur->angle*8)>>ANGLETOFINESHIFT), mobj->z);
//P_SpawnGhostMobj(cur)->tics = 2;
cur = cur->hnext;
}
}
if (!S_SoundPlaying(mobj, mobj->info->seesound))
S_StartSound(mobj, mobj->info->seesound);
break;
case MT_FROGGER:
{
statenum_t frogstate = (mobj->state-states);
// FROG ATTACK VALUES:
// threshold: distance
// movecount: time
// lastlook: direction
// extravalue1: x step
// extravalue2: y step
// cusval: z step
if (frogstate == S_FROGGER)
{
mobj->threshold = mobj->movecount = mobj->lastlook = 0; // clear tongue attack
mobj->extravalue1 = mobj->extravalue2 = mobj->cusval = 0;
if (mobj->hnext) // Clean hnext list
{
mobj_t *cur = mobj->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
mobj_t *next = cur->hnext;
P_RemoveMobj(cur);
cur = next;
}
}
if (mobj->reactiontime)
mobj->reactiontime--;
else
{
if (mobj->flags2 & MF2_AMBUSH)
{
mobj->momz = P_RandomRange(12, 16)<<FRACBITS;
S_StartSound(mobj, sfx_s3kb1);
P_SetMobjState(mobj, S_FROGGER_JUMP);
}
else
{
mobj_t *tongue = P_SpawnMobj(mobj->x, mobj->y, mobj->z + (mobj->height/2), MT_FROGTONGUE);
P_SetTarget(&mobj->tracer, tongue);
P_SetMobjState(mobj, S_FROGGER_ATTACK);
}
}
}
else if (frogstate == S_FROGGER_ATTACK)
{
if (!mobj->tracer || P_MobjWasRemoved(mobj->tracer))
{
mobj->reactiontime = mobj->info->reactiontime;
P_SetMobjState(mobj, S_FROGGER);
break;
}
if (mobj->threshold == 0)
{
fixed_t targetz = mobj->tracer->z; //mobj->z + (mobj->height/2)
mobj->threshold = 256;
mobj->movecount = 1;
mobj->lastlook = 1;
mobj->tracer->angle = mobj->angle;
mobj->extravalue1 = FixedMul(FixedMul((mobj->threshold/16)<<FRACBITS,
FINECOSINE(((angle_t)targetz)>>ANGLETOFINESHIFT)),
FINECOSINE(mobj->angle>>ANGLETOFINESHIFT)) >> FRACBITS;
mobj->extravalue2 = FixedMul(FixedMul((mobj->threshold/16)<<FRACBITS,
FINECOSINE(((angle_t)targetz)>>ANGLETOFINESHIFT)),
FINESINE(mobj->angle>>ANGLETOFINESHIFT)) >> FRACBITS;
mobj->cusval = FixedMul((mobj->threshold/16)<<FRACBITS,
FINESINE(((angle_t)targetz)>>ANGLETOFINESHIFT)) >> FRACBITS;
S_StartSound(mobj, sfx_s3k8c); // Play that tongue-y sound.
}
mobj->movecount += mobj->lastlook;
if (!(P_TryMove(mobj->tracer, mobj->x + ((mobj->extravalue1<<FRACBITS) * mobj->movecount), mobj->y + ((mobj->extravalue2<<FRACBITS) * mobj->movecount), true))
|| (mobj->movecount >= 16) // maximum travel time
|| (mobj->tracer->z <= mobj->tracer->floorz) // Through the floor
|| ((mobj->tracer->z + mobj->tracer->height) >= mobj->tracer->ceilingz)) // Through the ceiling
{
mobj->lastlook = -1; // Reverse direction.
}
if (mobj->movecount == 0) // It's back to its source, time to reset.
{
mobj->threshold = mobj->lastlook = 0;
P_RemoveMobj(mobj->tracer);
if (mobj->hnext) // Clean hnext list
{
mobj_t *cur = mobj->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
mobj_t *next = cur->hnext;
P_RemoveMobj(cur);
cur = next;
}
}
mobj->reactiontime = mobj->info->reactiontime;
P_SetMobjState(mobj, S_FROGGER);
}
else
{
const UINT8 numjoints = 11;
UINT8 joint = numjoints;
mobj_t *cur = mobj->hnext, *prev = mobj;
mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
for (; joint > 0; joint--)
{
fixed_t wx = mobj->tracer->x + (joint * (mobj->x - mobj->tracer->x) / (numjoints+1));
fixed_t wy = mobj->tracer->y + (joint * (mobj->y - mobj->tracer->y) / (numjoints+1));
fixed_t wz = mobj->tracer->z + (joint * ((mobj->z + (mobj->height/2)) - mobj->tracer->z) / (numjoints+1));
if (cur && !P_MobjWasRemoved(cur))
P_TeleportMove(cur, wx, wy, wz);
else
cur = P_SpawnMobj(wx, wy, wz, MT_FROGTONGUE_JOINT);
P_SetTarget(&cur->target, mobj);
P_SetTarget(&prev->hnext, cur);
P_SetTarget(&cur->hprev, prev);
prev = cur;
cur = cur->hnext;
}
}
}
else if (frogstate == S_FROGGER_JUMP)
{
if (P_IsObjectOnGround(mobj))
{
mobj->reactiontime = mobj->info->reactiontime;
P_SetMobjState(mobj, S_FROGGER);
}
}
}
break;
case MT_ROBRA:
case MT_BLUEROBRA:
if (mobj->health)
{
boolean blue = (mobj->type == MT_BLUEROBRA);
if (blue)
{
if (mobj->spawnpoint)
mobj->extravalue2 = mobj->spawnpoint->angle;
else
mobj->extravalue2 = 128;
}
else
{
if (!mobj->extravalue2)
mobj->extravalue2 = P_RandomRange(64, 192);
}
if (mobj->reactiontime)
mobj->reactiontime--;
else
{
if (!mobj->extravalue1)
{
mobj_t *head = P_SpawnMobj(mobj->x, mobj->y, mobj->z, (blue ? MT_BLUEROBRA_HEAD : MT_ROBRA_HEAD));
P_SetTarget(&mobj->tracer, head);
mobj->destscale = mapobjectscale;
P_SetTarget(&mobj->tracer->target, mobj->target);
P_SetTarget(&mobj->tracer->tracer, mobj);
mobj->tracer->extravalue2 = mobj->extravalue2;
if (!blue)
mobj->tracer->angle = mobj->angle;
mobj->extravalue1 = 1;
}
}
if ((mobj->extravalue1) && !(mobj->tracer && !P_MobjWasRemoved(mobj->tracer)))
{
mobj->reactiontime = 20*mobj->info->reactiontime;
P_SetTarget(&mobj->target, NULL);
mobj->extravalue1 = 0;
}
if ((mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) && !(leveltime % 10))
{
mobj_t *dust = P_SpawnMobj(mobj->x + (P_RandomRange(-4, 4)<<FRACBITS),
mobj->y + (P_RandomRange(-4, 4)<<FRACBITS),
mobj->z + (P_RandomRange(0, 2)<<FRACBITS), MT_BBZDUST);
P_SetScale(dust, mobj->scale/2);
P_InstaThrust(dust, FixedAngle(P_RandomRange(0,359)<<FRACBITS), abs(mobj->tracer->momz)/2);
if (abs(mobj->tracer->momz) >= 2<<FRACBITS)
S_StartSound(mobj, sfx_s3k7e);
}
}
break;
case MT_ROBRA_HEAD:
case MT_BLUEROBRA_HEAD:
if (mobj->health)
{
boolean blue = (mobj->type == MT_BLUEROBRA_HEAD);
UINT8 locnumsegs = abs(mobj->z - mobj->floorz) / (32 * mobj->scale);
UINT8 i;
mobj_t *cur = mobj->hnext, *prev = mobj;
if (blue)
mobj->angle = (angle_t)mobj->extravalue1;
mobj->extravalue1 += (FixedAngle(2*mobj->momz) * (blue ? -1 : 1));
for (i = 0; i < locnumsegs*2; i++) // *2 to check for any extra segs still present
{
fixed_t segz = mobj->z - ((i+1) * (32 * mobj->scale));
if (cur && !P_MobjWasRemoved(cur))
{
if (i >= locnumsegs) // Remove extras
{
mobj_t *next = cur->hnext;
P_RemoveMobj(cur);
cur = next;
continue;
}
else // Move into place
P_TeleportMove(cur, mobj->x, mobj->y, segz);
}
else
{
if (i >= locnumsegs) // We're done with this list
continue; //break;
else // Need another here!
cur = P_SpawnMobj(mobj->x, mobj->y, segz, (blue ? MT_BLUEROBRA_JOINT : MT_ROBRA_JOINT));
}
P_SetTarget(&cur->target, mobj);
P_SetScale(cur, (7*mobj->scale)/8);
cur->angle = mobj->extravalue1;
mobj->extravalue1 += (FixedAngle(2*mobj->momz) * (blue ? -1 : 1));
P_SetTarget(&prev->hnext, cur);
P_SetTarget(&cur->hprev, prev);
prev = cur;
cur = cur->hnext;
}
{
//fixed_t ceilingheight = mobj->ceilingz - (72<<FRACBITS);
fixed_t floorheight = mobj->floorz + (72<<FRACBITS);
fixed_t maxheight = mobj->floorz + (mobj->extravalue2<<FRACBITS);
fixed_t targetheight = maxheight;
if (mobj->z < targetheight)
{
mobj->momz += mobj->info->speed;
if ((mobj->z < floorheight) && (mobj->momz < 0))
mobj->momz /= 2;
}
else
{
mobj->momz -= mobj->info->speed;
if ((mobj->z > (targetheight + (64<<FRACBITS))) && (mobj->momz > 0))
mobj->momz /= 2;
}
}
}
break;
case MT_ROBRA_JOINT:
case MT_BLUEROBRA_JOINT:
if (!mobj->target || P_MobjWasRemoved(mobj->target))
{
P_RemoveMobj(mobj);
return false;
}
break;
case MT_SMK_PIPE:
if (mobj->flags2 & MF2_AMBUSH)
P_SetMobjStateNF(mobj, mobj->info->seestate);
else
P_SetMobjStateNF(mobj, mobj->info->spawnstate);
break;
case MT_SMK_MOLESPAWNER:
if (!mobj->target || P_MobjWasRemoved(mobj->target))
{
mobj_t *newmole = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SMK_MOLE);
P_SetTarget(&mobj->target, newmole);
return false;
}
break;
case MT_SMK_MOLE:
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
player_t *player = mobj->target->player;
mobj->extravalue1 = 1;
player->kartstuff[k_offroad] += 2<<FRACBITS;
P_TeleportMove(mobj,
player->mo->x + P_ReturnThrustX(NULL, player->mo->angle, player->mo->radius)
+ P_ReturnThrustX(NULL, player->mo->angle+ANGLE_90, (mobj->threshold)<<FRACBITS),
player->mo->y + P_ReturnThrustY(NULL, player->mo->angle, player->mo->radius)
+ P_ReturnThrustY(NULL, player->mo->angle+ANGLE_90, (mobj->threshold)<<FRACBITS),
player->mo->z + (player->mo->height/2 * P_MobjFlip(player->mo))
+ (P_RandomRange(-abs(mobj->threshold), abs(mobj->threshold))<<FRACBITS));
mobj->threshold /= 2;
mobj->momz = 0;
if (mobj->movecount > 8*TICRATE)
{
P_KillMobj(mobj, mobj->target, mobj->target, DMG_NORMAL);
break;
}
if (abs(player->cmd.turning) > 100)
{
INT32 lastsign = 0;
if (mobj->lastlook > 0)
lastsign = 1;
else if (mobj->lastlook < 0)
lastsign = -1;
if ((player->cmd.turning > 0 && lastsign < 0)
|| (player->cmd.turning < 0 && lastsign > 0))
{
mobj->movecount += (TICRATE/2);
mobj->threshold = 16*lastsign;
S_StartSound(mobj, sfx_s1ab);
}
mobj->lastlook = player->cmd.turning;
}
mobj->movecount++;
}
else if (mobj->extravalue1) // lost your player somehow, DIE
{
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
break;
}
else
{
if (P_IsObjectOnGround(mobj))
{
if (mobj->reactiontime)
mobj->reactiontime--;
else
{
mobj->momz = (mobj->info->speed * P_MobjFlip(mobj));
mobj->reactiontime = mobj->info->reactiontime;
}
}
}
break;
case MT_SMK_THWOMP:
if (mobj->flags2 & MF2_AMBUSH)
{
mobj->colorized = true;
mobj->color = K_RainbowColor(leveltime);
mobj->frame |= FF_FULLBRIGHT;
}
else
{
mobj->colorized = false;
mobj->color = SKINCOLOR_NONE;
mobj->frame &= (~FF_FULLBRIGHT);
}
if (!thwompsactive)
break;
if (mobj->reactiontime)
mobj->reactiontime--;
else
{
if (mobj->extravalue1)
{
P_SpawnGhostMobj(mobj)->tics = 3;
if (mobj->z == mobj->floorz)
{
UINT8 i;
mobj->extravalue1 = 0;
mobj->reactiontime = mobj->info->reactiontime;
S_StartSound(mobj, sfx_s1bd);
for (i = 0; i < 8; i++)
{
mobj_t *dust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_DRIFTDUST);
P_InstaThrust(dust, FixedAngle(((360*FRACUNIT)/8) * i), mobj->info->speed/8);
dust->momz = P_MobjFlip(mobj) * (P_RandomRange(1,4)<<FRACBITS);
dust->scale = mobj->scale/2;
dust->destscale = mobj->scale*3;
}
}
else
mobj->momz = (-mobj->info->speed) * P_MobjFlip(mobj);
}
else
{
if (mobj->z > mobj->movefactor)
mobj->z = mobj->movefactor;
if (mobj->z == mobj->movefactor)
{
mobj->extravalue1 = 1;
//S_StartSound(mobj, sfx_s1bb);
}
else
mobj->momz = (mobj->info->speed/16) * P_MobjFlip(mobj);
}
}
break;
case MT_BUBBLESHIELDTRAP:
if (leveltime % 180 == 0)
S_StartSound(mobj, sfx_s3kbfl);
if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer) && mobj->tracer->player)
{
player_t *player = mobj->tracer->player;
fixed_t destx, desty, curfz, destfz;
boolean blockmove = false;
mobj->flags = MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP;
mobj->extravalue1 = 1;
mobj->cvmem /= 2;
mobj->momz = 0;
mobj->destscale = ((5*mobj->tracer->scale)>>2) + (mobj->tracer->scale>>3);
mobj->tracer->momz = (8*mobj->tracer->scale) * P_MobjFlip(mobj->tracer);
mobj->tracer->momx = (31*mobj->tracer->momx)/32;
mobj->tracer->momy = (31*mobj->tracer->momy)/32;
destx = mobj->x + mobj->tracer->momx;
desty = mobj->y + mobj->tracer->momy;
if (mobj->tracer->eflags & MFE_VERTICALFLIP)
{
curfz = P_GetCeilingZ(mobj->tracer, mobj->tracer->subsector->sector, mobj->tracer->x, mobj->tracer->y, NULL);
destfz = P_GetCeilingZ(mobj->tracer, R_PointInSubsector(destx, desty)->sector, destx, desty, NULL);
blockmove = (curfz - destfz >= 24*mobj->scale);
}
else
{
curfz = P_GetFloorZ(mobj->tracer, mobj->tracer->subsector->sector, mobj->tracer->x, mobj->tracer->y, NULL);
destfz = P_GetFloorZ(mobj->tracer, R_PointInSubsector(destx, desty)->sector, destx, desty, NULL);
blockmove = (destfz - curfz >= 24*mobj->scale);
}
if (blockmove)
{
mobj->tracer->momx = mobj->tracer->momy = 0;
}
P_TeleportMove(mobj,
mobj->tracer->x + P_ReturnThrustX(NULL, mobj->tracer->angle+ANGLE_90, (mobj->cvmem)<<FRACBITS),
mobj->tracer->y + P_ReturnThrustY(NULL, mobj->tracer->angle+ANGLE_90, (mobj->cvmem)<<FRACBITS),
mobj->tracer->z - (4*mobj->tracer->scale) + (P_RandomRange(-abs(mobj->cvmem), abs(mobj->cvmem))<<FRACBITS));
if (mobj->movecount > 4*TICRATE)
{
S_StartSound(mobj->tracer, sfx_s3k77);
mobj->tracer->flags &= ~MF_NOGRAVITY;
P_KillMobj(mobj, mobj->tracer, mobj->tracer, DMG_NORMAL);
break;
}
if (abs(player->cmd.turning) > 100)
{
INT32 lastsign = 0;
if (mobj->lastlook > 0)
lastsign = 1;
else if (mobj->lastlook < 0)
lastsign = -1;
if ((player->cmd.turning > 0 && lastsign < 0)
|| (player->cmd.turning < 0 && lastsign > 0))
{
mobj->movecount += (TICRATE/2);
mobj->cvmem = 8*lastsign;
S_StartSound(mobj, sfx_s3k7a);
}
mobj->lastlook = player->cmd.turning;
}
mobj->movecount++;
}
else if (mobj->extravalue1) // lost your player somehow, DIE
{
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
break;
}
else
{
mobj->destscale = (5*mapobjectscale)>>2;
if (mobj->threshold > 0)
mobj->threshold--;
if (abs(mobj->momx) < 8*mobj->destscale && abs(mobj->momy) < 8*mobj->destscale)
{
// Stop, give light gravity
mobj->momx = mobj->momy = 0;
mobj->momz = -(mobj->scale * P_MobjFlip(mobj));
}
else
{
UINT8 i;
mobj_t *ghost = P_SpawnGhostMobj(mobj);
if (mobj->target && !P_MobjWasRemoved(mobj->target) && mobj->target->player)
{
ghost->color = mobj->target->player->skincolor;
ghost->colorized = true;
}
mobj->momx = (23*mobj->momx)/24;
mobj->momy = (23*mobj->momy)/24;
mobj->angle = K_MomentumAngle(mobj);
if ((mobj->z - mobj->floorz) < (24*mobj->scale) && (leveltime % 3 != 0))
{
// Cool wave effects!
for (i = 0; i < 2; i++)
{
angle_t aoff;
SINT8 sign = 1;
mobj_t *wave;
if (i & 1)
sign = -1;
else
sign = 1;
aoff = (mobj->angle + ANGLE_180) + (ANGLE_45 * sign);
wave = P_SpawnMobj(mobj->x + FixedMul(mobj->radius, FINECOSINE(aoff>>ANGLETOFINESHIFT)),
mobj->y + FixedMul(mobj->radius, FINESINE(aoff>>ANGLETOFINESHIFT)),
mobj->z, MT_THOK);
wave->flags &= ~(MF_NOCLIPHEIGHT|MF_NOGRAVITY);
P_SetScale(wave, (wave->destscale = mobj->scale/2));
P_SetMobjState(wave, S_BUBBLESHIELDWAVE1);
if (leveltime & 1)
wave->tics++;
P_SetTarget(&wave->target, mobj);
wave->angle = mobj->angle - (ANGLE_90 * sign); // point completely perpendicular from the bubble
K_FlipFromObject(wave, mobj);
P_Thrust(wave, wave->angle, 4*mobj->scale);
}
}
}
}
break;
case MT_KARMAFIREWORK:
if (mobj->flags & MF_NOGRAVITY)
break;
if (mobj->momz == 0)
{
P_RemoveMobj(mobj);
return false;
}
else
{
mobj_t *trail = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_THOK);
P_SetMobjState(trail, S_KARMAFIREWORKTRAIL);
P_SetScale(trail, mobj->scale);
trail->destscale = 1;
trail->scalespeed = mobj->scale/12;
trail->color = mobj->color;
}
break;
case MT_BATTLECAPSULE:
{
SINT8 realflip = P_MobjFlip(mobj);
SINT8 flip = realflip; // Flying capsules needs flipped sprites, but not flipped gravity
fixed_t bottom;
mobj_t *cur;
if (mobj->extravalue1)
{
const INT32 speed = 6*TICRATE; // longer is slower
const fixed_t pi = 22*FRACUNIT/7; // Inaccurate, but is close enough for our usage
fixed_t sine = FINESINE((((2*pi*speed) * leveltime) >> ANGLETOFINESHIFT) & FINEMASK) * flip;
// Flying capsules are flipped upside-down, like S3K
flip = -flip;
// ALL CAPSULE MOVEMENT NEEDS TO HAPPEN AFTER THIS & ADD TO MOMENTUM FOR BOBBING TO BE ACCURATE
mobj->momz = sine/2;
}
// Moving capsules
if (mobj->target && !P_MobjWasRemoved(mobj->target))
{
fixed_t speed = mobj->movefactor;
UINT8 sequence = mobj->lastlook;
UINT8 num = mobj->movecount;
boolean backandforth = (mobj->flags2 & MF2_AMBUSH);
SINT8 direction = mobj->cvmem;
mobj_t *next = NULL;
thinker_t *th;
fixed_t dist, momx, momy, momz;
dist = P_AproxDistance(mobj->target->x - mobj->x, mobj->target->y - mobj->y);
if (mobj->extravalue1)
dist = P_AproxDistance(dist, mobj->target->z - mobj->z);
if (dist < 1)
dist = 1;
if (speed <= dist)
{
momx = FixedMul(FixedDiv(mobj->target->x - mobj->x, dist), speed);
momy = FixedMul(FixedDiv(mobj->target->y - mobj->y, dist), speed);
if (mobj->extravalue1)
momz = mobj->momz + FixedMul(FixedDiv(mobj->target->z - mobj->z, dist), speed);
mobj->momx = momx;
mobj->momy = momy;
if (mobj->extravalue1)
mobj->momz = momz;
}
else
{
mobj_t *mo2;
speed -= dist;
P_UnsetThingPosition(mobj);
mobj->x = mobj->target->x;
mobj->y = mobj->target->y;
mobj->z = mobj->target->z;
P_SetThingPosition(mobj);
mobj->floorz = mobj->subsector->sector->floorheight;
mobj->ceilingz = mobj->subsector->sector->ceilingheight;
// Onto the next waypoint!
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_TUBEWAYPOINT)
continue;
if (mo2->threshold == sequence)
{
if (mo2->health == num + direction)
{
next = mo2;
break;
}
}
}
// Are we at the end of the waypoint chain?
// If so, search again for the first/previous waypoint (depending on settings)
if (next == NULL)
{
if (backandforth)
{
mobj->cvmem = -mobj->cvmem;
direction = mobj->cvmem;
}
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_TUBEWAYPOINT)
continue;
if (mo2->threshold == sequence)
{
if (backandforth)
{
if (mo2->health == num + direction)
{
next = mo2;
break;
}
}
else
{
if (direction < 0)
{
if (next == NULL || mo2->health > next->health)
next = mo2;
}
else
{
if (next == NULL || mo2->health < next->health)
next = mo2;
}
}
}
}
}
if (next && !P_MobjWasRemoved(next))
{
P_SetTarget(&mobj->target, next);
mobj->movecount = next->health;
dist = P_AproxDistance(mobj->target->x - mobj->x, mobj->target->y - mobj->y);
if (mobj->extravalue1)
dist = P_AproxDistance(dist, mobj->target->z - mobj->z);
if (dist < 1)
dist = 1;
momx = FixedMul(FixedDiv(mobj->target->x - mobj->x, dist), speed);
momy = FixedMul(FixedDiv(mobj->target->y - mobj->y, dist), speed);
if (mobj->extravalue1)
momz = mobj->momz + FixedMul(FixedDiv(mobj->target->z - mobj->z, dist), speed);
mobj->momx = momx;
mobj->momy = momy;
if (mobj->extravalue1)
mobj->momz = momz;
}
else
{
CONS_Alert(CONS_WARNING, "Moving capsule could not find next waypoint! (seq: %d)\n", sequence);
P_SetTarget(&mobj->target, NULL);
}
}
}
if (flip == -1)
bottom = mobj->z + mobj->height;
else
bottom = mobj->z;
cur = mobj->hnext;
// Move each piece to the proper position
while (cur && !P_MobjWasRemoved(cur))
{
fixed_t newx = mobj->x;
fixed_t newy = mobj->y;
fixed_t newz = bottom;
statenum_t state = (statenum_t)(cur->state-states);
cur->scale = mobj->scale;
cur->destscale = mobj->destscale;
cur->scalespeed = mobj->scalespeed;
cur->extravalue2 = mobj->extravalue1;
cur->flags2 = (cur->flags2 & ~MF2_OBJECTFLIP)|(mobj->flags2 & MF2_OBJECTFLIP);
if (state == S_BATTLECAPSULE_TOP)
newz += (80 * mobj->scale * flip);
else if (state == S_BATTLECAPSULE_BUTTON)
newz += (108 * mobj->scale * flip);
else if (state == S_BATTLECAPSULE_SUPPORT
|| state == S_BATTLECAPSULE_SUPPORTFLY
|| state == S_KARMAWHEEL)
{
fixed_t offx = mobj->radius;
fixed_t offy = mobj->radius;
if (cur->extravalue1 & 1)
offx = -offx;
if (cur->extravalue1 > 1)
offy = -offy;
newx += offx;
newy += offy;
}
else if (state == S_BATTLECAPSULE_SIDE1
|| state == S_BATTLECAPSULE_SIDE2)
{
fixed_t offset = 48 * mobj->scale;
angle_t angle = (ANGLE_45 * cur->extravalue1);
newx += FixedMul(offset, FINECOSINE(angle >> ANGLETOFINESHIFT));
newy += FixedMul(offset, FINESINE(angle >> ANGLETOFINESHIFT));
newz += (12 * mobj->scale * flip);
cur->angle = angle + ANGLE_90;
}
P_TeleportMove(cur, newx, newy, newz);
cur = cur->hnext;
}
}
break;
case MT_RANDOMITEM:
if (gametype == GT_BATTLE && mobj->threshold == 70)
{
mobj->color = K_RainbowColor(leveltime);
mobj->colorized = true;
if (battleovertime.enabled)
{
angle_t ang = FixedAngle((leveltime % 360) << FRACBITS);
fixed_t z = battleovertime.z;
fixed_t dist;
mobj_t *ghost;
/*if (z < mobj->subsector->sector->floorheight)
z = mobj->subsector->sector->floorheight;*/
if (mobj->extravalue1 < 512)
mobj->extravalue1++;
dist = mobj->extravalue1 * mapobjectscale;
P_TeleportMove(mobj, battleovertime.x + P_ReturnThrustX(NULL, ang, dist),
battleovertime.y + P_ReturnThrustY(NULL, ang, dist), z);
ghost = P_SpawnGhostMobj(mobj);
ghost->fuse = 4;
ghost->frame |= FF_FULLBRIGHT;
}
}
else
{
player_t *player = K_GetItemBoxPlayer(mobj);
UINT8 color = SKINCOLOR_BLACK;
if (player != NULL)
{
color = player->skincolor;
}
mobj->color = color;
mobj->colorized = false;
}
break;
default:
// check mobj against possible water content, before movement code
P_MobjCheckWater(mobj);
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;
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.
// If a box's speed is nonzero, it's allowed to respawn as a WRM/SRM.
if (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)
newmobj->flags2 = mobj->flags2;
P_RemoveMobj(mobj); // make sure they disappear
}
static boolean P_FuseThink(mobj_t *mobj)
{
if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG)
mobj->drawflags ^= MFD_DONTDRAW;
if (mobj->fuse <= TICRATE && ((mobj->type == MT_RANDOMITEM && mobj->threshold == 69) || mobj->type == MT_EGGMANITEM || mobj->type == MT_FALLINGROCK))
mobj->drawflags ^= MFD_DONTDRAW;
mobj->fuse--;
if (mobj->fuse)
return true;
if (LUAh_MobjFuse(mobj) || 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_SPIKE:
P_SetMobjState(mobj, mobj->state->nextstate);
mobj->fuse = mobj->info->speed;
if (mobj->spawnpoint)
mobj->fuse += mobj->spawnpoint->angle;
break;
case MT_WALLSPIKE:
P_SetMobjState(mobj, mobj->state->nextstate);
mobj->fuse = mobj->info->speed;
if (mobj->spawnpoint)
mobj->fuse += (mobj->spawnpoint->angle / 360);
break;
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_RANDOMITEM:
if ((gametyperules & GTR_BUMPERS) && (mobj->threshold != 70))
{
if (mobj->threshold != 69)
break;
}
else
{
mobj_t *newmobj;
// Respawn from mapthing if you have one!
if (mobj->spawnpoint)
{
P_SpawnMapThing(mobj->spawnpoint);
newmobj = mobj->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing
}
else
newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
P_SpawnMobj(newmobj->x, newmobj->y, newmobj->z, MT_EXPLODE); // poof into existance
// Transfer flags2 (strongbox, objectflip)
newmobj->flags2 = mobj->flags2;
if (mobj->threshold == 70)
newmobj->threshold = 70;
}
P_RemoveMobj(mobj); // make sure they disappear
return false;
case MT_SMK_ICEBLOCK:
{
mobj_t *cur = mobj->hnext, *next;
UINT8 i;
for (i = 0; i < 5; i++)
{
mobj_t *debris = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SMK_ICEBLOCK_DEBRIS);
debris->angle = FixedAngle(P_RandomRange(0,360)<<FRACBITS);
P_InstaThrust(debris, debris->angle, P_RandomRange(3,18)*(FRACUNIT/4));
debris->momz = P_RandomRange(4,8)<<FRACBITS;
if (!i) // kinda hacky :V
S_StartSound(debris, sfx_s3k82);
}
while (cur && !P_MobjWasRemoved(cur))
{
next = cur->hnext;
P_RemoveMobj(cur);
cur = next;
}
P_RemoveMobj(mobj);
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));
// 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->flags & MF_NOTHINK)
return;
if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->extrainfo)))
return;
// Don't run any thinker code while in hitlag
if (mobj->hitlag > 0)
{
mobj->hitlag--;
return;
}
mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL);
tmfloorthing = tmhitthing = NULL;
// Sector special (2,8) allows ANY mobj to trigger a linedef exec
if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
{
sector_t *sec2;
sec2 = P_ThingOnSpecial3DFloor(mobj);
if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
P_LinedefExecute(sec2->tag, mobj, sec2);
}
if (mobj->scale != mobj->destscale)
P_MobjScaleThink(mobj); // Slowly scale up/down to reach your destscale.
if (mobj->type == MT_GHOST && 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 (LUAh_MobjThinker(mobj) || P_MobjWasRemoved(mobj))
return;
}
else if (!mobj->player->spectator)
{
// You cannot short-circuit the player thinker like you can other thinkers.
LUAh_MobjThinker(mobj);
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;
// Destroy items sector special
if (mobj->type == MT_BANANA || mobj->type == MT_EGGMANITEM
|| mobj->type == MT_ORBINAUT || mobj->type == MT_BALLHOG
|| mobj->type == MT_JAWZ || mobj->type == MT_JAWZ_DUD
|| mobj->type == MT_SSMINE || mobj->type == MT_BUBBLESHIELDTRAP
|| mobj->type == MT_LANDMINE)
{
if (mobj->health > 0 && P_MobjTouchingSectorSpecial(mobj, 4, 7, true))
{
if (mobj->type == MT_SSMINE
|| mobj->type == MT_BUBBLESHIELDTRAP
|| mobj->type == MT_BALLHOG)
{
S_StartSound(mobj, mobj->info->deathsound);
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
}
else
{
// This Item Damage
if (mobj->eflags & MFE_VERTICALFLIP)
mobj->z -= mobj->height;
else
mobj->z += mobj->height;
S_StartSound(mobj, mobj->info->deathsound);
P_KillMobj(mobj, NULL, NULL, DMG_NORMAL);
P_SetObjectMomZ(mobj, 8*FRACUNIT, false);
P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy) + ANGLE_90, 16*FRACUNIT);
}
return;
}
}
if (mobj->flags2 & MF2_FIRING)
P_FiringThink(mobj);
if (mobj->flags & MF_AMBIENT)
{
if (!(leveltime % mobj->health) && mobj->info->seesound)
S_StartSound(mobj, mobj->info->seesound);
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
|| mobj->type == MT_EMERALD
|| mobj->type == MT_BIGTUMBLEWEED
|| mobj->type == MT_LITTLETUMBLEWEED
|| mobj->type == MT_CANNONBALLDECOR
|| mobj->type == MT_FALLINGROCK
|| mobj->type == MT_ORBINAUT
|| mobj->type == MT_JAWZ || mobj->type == MT_JAWZ_DUD) {
P_TryMove(mobj, mobj->x, mobj->y, true); // Sets mo->standingslope correctly
if (P_MobjWasRemoved(mobj)) // anything that calls checkposition can be lethal
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;
}
}
if (P_MobjWasRemoved(mobj))
return; // obligatory paranoia check
if (P_IsKartItem(mobj->type)) // mobj is a kart item we want on the list:
P_AddKartItem(mobj); // add to kitem list
// 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
{
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)
{
sector_t *sec;
I_Assert(mobj != NULL);
I_Assert(!P_MobjWasRemoved(mobj));
sec = mobj->subsector->sector;
if (GETSECSPECIAL(sec->special, 2) == 1 && mobj->z == sec->floorheight)
P_LinedefExecute(sec->tag, mobj, sec);
// else if (GETSECSPECIAL(sec->special, 2) == 8)
{
sector_t *sec2;
sec2 = P_ThingOnSpecial3DFloor(mobj);
if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
P_LinedefExecute(sec2->tag, mobj, sec2);
}
// 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 (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);
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;
}
}
// Sonic Advance 2 flashing afterimages
if (mobj->type == MT_GHOST && mobj->fuse > 0
&& mobj->extravalue1 > 0 && mobj->extravalue2 >= 2)
{
if (mobj->extravalue2 == 2) // I don't know why the normal logic doesn't work for this.
mobj->drawflags ^= MFD_DONTDRAW;
else
{
if (mobj->fuse == mobj->extravalue2)
mobj->drawflags &= ~MFD_DONTDRAW;
else
mobj->drawflags |= MFD_DONTDRAW;
}
}
// 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);
if (mobj->type != MT_RANDOMAUDIENCE)
return;
{
if (!mobj->colorized) // a fan of someone?
return;
if (mobj->threshold >= 0) // not already happy or sad?
{
if (!playeringame[mobj->threshold] || players[mobj->threshold].spectator) // focused on a valid player?
return;
if (!(players[mobj->threshold].exiting) && !(players[mobj->threshold].pflags & PF_GAMETYPEOVER)) // not finished yet?
return;
if (K_IsPlayerLosing(&players[mobj->threshold]))
mobj->threshold = -2;
else
{
mobj->threshold = -1;
S_StartSound(mobj, sfx_chaooo);
}
}
if (mobj->threshold == -1)
mobj->angle += ANGLE_22h;
if (((statenum_t)(mobj->state-states) != S_AUDIENCE_CHAO_CHEER2) || (mobj->tics != states[S_AUDIENCE_CHAO_CHEER2].tics)) // not at the start of your cheer jump?
return;
mobj->momz = 0;
P_SetMobjState(mobj, ((mobj->threshold == -1) ? S_AUDIENCE_CHAO_WIN2 : S_AUDIENCE_CHAO_LOSE));
}
}
//
// GAME SPAWN FUNCTIONS
//
static void P_DefaultMobjShadowScale(mobj_t *thing)
{
thing->shadowscale = 0;
thing->whiteshadow = (thing->frame & FF_FULLBRIGHT);
switch (thing->type)
{
case MT_PLAYER:
case MT_KART_LEFTOVER:
case MT_SMALLMACE:
case MT_BIGMACE:
case MT_PUMA:
case MT_BIGPUMA:
case MT_FALLINGROCK:
case MT_SMK_MOLE:
case MT_SMK_THWOMP:
case MT_BATTLEBUMPER:
case MT_BANANA:
case MT_ORBINAUT:
case MT_ORBINAUT_SHIELD:
case MT_JAWZ:
case MT_JAWZ_DUD:
case MT_JAWZ_SHIELD:
case MT_SSMINE:
case MT_SSMINE_SHIELD:
case MT_LANDMINE:
case MT_BALLHOG:
case MT_SINK:
case MT_ROCKETSNEAKER:
case MT_SPB:
thing->shadowscale = 3*FRACUNIT/2;
break;
case MT_BANANA_SHIELD:
thing->shadowscale = 12*FRACUNIT/5;
break;
case MT_RANDOMITEM:
thing->shadowscale = FRACUNIT/2;
thing->whiteshadow = false;
break;
case MT_EGGMANITEM:
thing->shadowscale = FRACUNIT;
thing->whiteshadow = false;
break;
case MT_EGGMANITEM_SHIELD:
thing->shadowscale = 3*FRACUNIT/2;
thing->whiteshadow = false;
break;
case MT_THUNDERSHIELD:
case MT_BUBBLESHIELD:
case MT_BUBBLESHIELDTRAP:
case MT_FLAMESHIELD:
thing->shadowscale = FRACUNIT;
break;
case MT_RING:
case MT_FLOATINGITEM:
case MT_BLUESPHERE:
case MT_EMERALD:
thing->shadowscale = FRACUNIT/2;
break;
case MT_DRIFTCLIP:
thing->shadowscale = FRACUNIT/3;
break;
default:
if (thing->flags & (MF_ENEMY|MF_BOSS))
thing->shadowscale = FRACUNIT;
break;
}
}
//
// 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 = 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->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;
if (mapobjectscale != FRACUNIT) //&& !(mobj->type == MT_BLACKEGGMAN)
{
mobj->destscale = mapobjectscale;
mobj->scalespeed = mapobjectscale/12;
}
// set subsector and/or block links
P_SetThingPosition(mobj);
I_Assert(mobj->subsector != NULL);
// Make sure scale matches destscale immediately when spawned
P_SetScale(mobj, mobj->destscale);
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;
mobj->colorized = false;
mobj->hitlag = 0;
// Set shadowscale here, before spawn hook so that Lua can change it
P_DefaultMobjShadowScale(mobj);
// DANGER! This can cause P_SpawnMobj to return NULL!
// Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks!
if (LUAh_MobjSpawn(mobj))
{
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, (mobj->destscale = 3*mobj->scale));
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);
spawn->destscale = mobj->scale;
P_SetScale(spawn, mobj->scale);
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);
spawn->destscale = mobj->scale;
P_SetScale(spawn, mobj->scale);
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);
ball->destscale = mobj->scale;
P_SetScale(ball, mobj->scale);
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);
ball->destscale = mobj->scale;
P_SetScale(ball, mobj->scale);
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);
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);
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));;
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_AQUAMARINE);
break;
case MT_BALLOON:
case MT_KART_LEFTOVER:
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);
P_SetTarget(&fire->target, mobj);
P_SetTarget(&mobj->hnext, fire);
fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE2);
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:
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);
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:
P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID));
mobj->tracer->angle = mobj->angle + ANGLE_90;
break;
case MT_TORCHFLOWER:
{
mobj_t *fire = P_SpawnMobjFromMobj(mobj, 0, 0, 46*FRACUNIT, MT_FLAME);
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;
// SRB2Kart
case MT_KARMAHITBOX:
{
const fixed_t rad = FixedMul(mobjinfo[MT_PLAYER].radius, mobj->scale);
mobj_t *cur, *prev = mobj;
INT32 i;
for (i = 0; i < 4; i++)
{
fixed_t offx = rad;
fixed_t offy = rad;
if (i == 1 || i == 3)
offx *= -1;
if (i == 2 || i == 3)
offy *= -1;
cur = P_SpawnMobj(mobj->x + offx, mobj->y + offy, mobj->z, MT_KARMAWHEEL);
cur->destscale = mobj->scale;
P_SetScale(cur, mobj->scale);
cur->lastlook = i;
P_SetTarget(&cur->hprev, prev);
P_SetTarget(&prev->hnext, cur);
prev = cur;
}
}
break;
case MT_BIGRING:
P_SetScale(mobj, (mobj->destscale = 3*FRACUNIT));
break;
case MT_RANDOMAUDIENCE:
{
fixed_t randu = P_RandomFixed();
P_SetScale(mobj, (mobj->destscale <<= 1));
if (randu < (FRACUNIT/9)) // a fan of someone?
{
UINT8 i, pcount = 0;
UINT8 pnum[MAXPLAYERS];
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
pnum[pcount] = i;
pcount++;
}
if (pcount)
{
mobj->threshold = pnum[P_RandomKey(pcount)];
mobj->color = players[mobj->threshold].skincolor;
mobj->colorized = true;
break;
}
}
if (randu > (FRACUNIT/2))
{
mobj->color = P_RandomKey(numskincolors-1)+1;
break;
}
mobj->color = SKINCOLOR_CYAN;
break;
}
case MT_MARBLETORCH:
P_SpawnMobj(mobj->x, mobj->y, mobj->z + (29*mobj->scale), MT_MARBLELIGHT);
break;
case MT_RUSTYLAMP_ORANGE:
P_SpawnMobj(mobj->x, mobj->y, mobj->z + (69*mobj->scale), MT_MARBLELIGHT);
break;
case MT_DAYTONAPINETREE:
{
angle_t diff = FixedAngle((360/mobj->info->mass)*FRACUNIT);
UINT8 i;
for (i = 0; i < mobj->info->mass; i++)
{
angle_t ang = i * diff;
mobj_t *side = P_SpawnMobj(mobj->x + FINECOSINE((ang>>ANGLETOFINESHIFT) & FINEMASK),
mobj->y + FINESINE((ang>>ANGLETOFINESHIFT) & FINEMASK), mobj->z, MT_DAYTONAPINETREE_SIDE);
side->angle = ang;
side->target = mobj;
side->threshold = i;
}
break;
}
case MT_EZZPROPELLER:
{
mobj_t *cur, *prev = mobj;
UINT8 i;
for (i = 0; i < mobj->info->mass; i++)
{
mobj->angle = FixedAngle((i * (360/mobj->info->mass))<<FRACBITS);
cur = P_SpawnMobj(mobj->x + FINECOSINE(((mobj->angle*8)>>ANGLETOFINESHIFT) & FINEMASK),
mobj->y + FINESINE(((mobj->angle*8)>>ANGLETOFINESHIFT) & FINEMASK), mobj->z, MT_EZZPROPELLER_BLADE);
cur->angle = mobj->angle;
P_SetTarget(&cur->hprev, prev);
P_SetTarget(&prev->hnext, cur);
prev = cur;
}
break;
}
case MT_ROBRA:
case MT_BLUEROBRA:
P_SetScale(mobj, (mobj->destscale = 1));
break;
case MT_ROBRA_HEAD:
{
mobj_t *shell;
shell = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
P_SetTarget(&shell->target, mobj);
P_SetMobjState(shell, S_ROBRASHELL_INSIDE);
shell = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
P_SetTarget(&shell->target, mobj);
P_SetMobjState(shell, S_ROBRASHELL_OUTSIDE);
}
break;
case MT_EERIEFOGGEN:
{
UINT16 i;
for (i = 0; i < mobj->info->mass; i++)
{
fixed_t newx = mobj->x + (P_RandomRange(-mobj->info->mass, mobj->info->mass)<<FRACBITS);
fixed_t newy = mobj->y + (P_RandomRange(-mobj->info->mass, mobj->info->mass)<<FRACBITS);
if (P_FloorzAtPos(newx, newy, mobj->z, 8<<FRACBITS) == mobj->z)
P_SpawnMobj(newx, newy, mobj->z, MT_EERIEFOG);
}
}
break;
case MT_SMK_MOLE:
mobj->reactiontime = P_RandomRange(0, 3*mobj->info->reactiontime/2); // Random delay on start of level
break;
case MT_SMK_THWOMP:
mobj->reactiontime = P_RandomRange(0, 3*mobj->info->reactiontime); // Random delay on start of level
if (mobj->z == mobj->floorz)
mobj->z += (256<<FRACBITS);
mobj->movefactor = mobj->z + (256<<FRACBITS);
break;
case MT_SMK_ICEBLOCK:
{
mobj_t *cur, *prev = mobj;
UINT8 i;
for (i = 0; i < 4; i++)
{
cur = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SMK_ICEBLOCK_SIDE);
P_SetTarget(&cur->target, mobj);
cur->threshold = i;
P_TeleportMove(cur, cur->x + ((cur->radius>>FRACBITS) * FINECOSINE((FixedAngle((90*cur->threshold)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK)),
cur->y + ((cur->radius>>FRACBITS) * FINESINE((FixedAngle((90*cur->threshold)<<FRACBITS)>>ANGLETOFINESHIFT) & FINEMASK)), cur->z);
cur->angle = ANGLE_90*(cur->threshold+1);
P_SetTarget(&cur->hprev, prev);
P_SetTarget(&prev->hnext, cur);
prev = cur;
}
}
break;
case MT_BOSS3WAYPOINT:
// Remove before release
CONS_Alert(CONS_WARNING, "Boss waypoints are deprecated. Did you forget to remove the old checkpoints, too?\n");
break;
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->flags & MF_NOTHINK))
P_AddThinker(THINK_MOBJ, &mobj->thinker);
if (mobj->skin) // correct inadequecies above.
{
mobj->sprite2 = P_GetSkinSprite2(mobj->skin, (mobj->frame & FF_FRAMEMASK), 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);
return mobj;
}
static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
{
const mobjinfo_t *info = &mobjinfo[type];
state_t *st;
precipmobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
fixed_t starting_floorz;
mobj->type = type;
mobj->info = info;
mobj->x = x;
mobj->y = y;
mobj->flags = info->flags;
// 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_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 = info->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 (GETSECSPECIAL(mobj->subsector->sector->special, 1) == 7
|| GETSECSPECIAL(mobj->subsector->sector->special, 1) == 6
|| mobj->subsector->sector->floorpic == skyflatnum)
mobj->precipflags |= PCF_PIT;
return mobj;
}
//
// 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.
LUAh_MobjRemoved(mobj);
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_BLUESPHERE)
&& !(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_KARMAHITBOX) // Remove linked list objects for certain types
{
mobj_t *cur = mobj->hnext;
while (cur && !P_MobjWasRemoved(cur))
{
mobj_t *prev = cur; // Kind of a dumb var, but we need to set cur before we remove the mobj
cur = cur->hnext;
P_RemoveMobj(prev);
}
}
if (mobj->type == MT_OVERLAY)
P_RemoveOverlay(mobj);
if (mobj->type == MT_SPB)
spbplace = -1;
if (P_IsKartItem(mobj->type))
P_RemoveKartItem(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;
// stop any playing sound
S_StopSound(mobj);
// killough 11/98:
//
// Remove any references to other mobjs.
P_SetTarget(&mobj->target, NULL);
P_SetTarget(&mobj->tracer, NULL);
// repair hnext chain
{
mobj_t *cachenext = mobj->hnext;
if (mobj->hnext && !P_MobjWasRemoved(mobj->hnext))
{
P_SetTarget(&mobj->hnext->hprev, mobj->hprev);
P_SetTarget(&mobj->hnext, NULL);
}
if (mobj->hprev && !P_MobjWasRemoved(mobj->hprev))
{
P_SetTarget(&mobj->hprev->hnext, cachenext);
P_SetTarget(&mobj->hprev, NULL);
}
}
// 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
// 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)
{
Z_Free(mobj); // No refrrences? Can be removed immediately! :D
return;
}
prevreferences = mobj->thinker.references;
P_AddThinker(THINK_MOBJ, (thinker_t *)mobj);
mobj->thinker.references = prevreferences;
}
P_RemoveThinker((thinker_t *)mobj);
}
// 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
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);
// free block
P_RemoveThinker((thinker_t *)mobj);
}
static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}};
consvar_t cv_itemrespawntime = CVAR_INIT ("respawnitemtime", "2", CV_NETVAR|CV_CHEAT, respawnitemtime_cons_t, NULL);
consvar_t cv_itemrespawn = CVAR_INIT ("respawnitem", "On", CV_NETVAR, CV_OnOff, NULL);
void P_SpawnPrecipitation(void)
{
INT32 i, j, k;
mobjtype_t type = precipprops[curWeather].type;
UINT8 randomstates = (UINT8)mobjinfo[type].damage;
fixed_t basex, basey, x, y, z, height;
subsector_t *precipsector = NULL;
precipmobj_t *rainmo = NULL;
if (dedicated || !cv_drawdist_precip.value || curWeather == PRECIP_NONE) // SRB2Kart
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;
{
UINT16 numparticles = 0;
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;
// Not in a sector with visible sky?
if (precipsector->sector->ceilingpic != skyflatnum)
continue;
height = precipsector->sector->ceilingheight - precipsector->sector->floorheight;
// Exists, but is too small for reasonable precipitation.
if (height < 64<<FRACBITS)
continue;
// Hack around a quirk of this entire system, where taller sectors look like they get less precipitation.
numparticles = 1 + (height / (MAPBLOCKUNITS<<4<<FRACBITS));
// Don't set z properly yet...
z = precipsector->sector->ceilingheight;
for (j = 0; j < numparticles; j++)
{
rainmo = P_SpawnPrecipMobj(x, y, z, type);
if (randomstates > 0)
{
UINT8 mrand = M_RandomByte();
UINT8 threshold = UINT8_MAX / (randomstates + 1);
statenum_t st = mobjinfo[type].spawnstate;
for (k = 0; k < randomstates; k++)
{
if (mrand < (threshold * (k+1)))
{
P_SetPrecipMobjState(rainmo, st+k+1);
break;
}
}
}
// 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;
INT32 rainsfx = mobjinfo[precipprops[curWeather].type].seesound;
INT32 rainfreq = mobjinfo[precipprops[curWeather].type].mass;
boolean sounds_thunder = (precipprops[curWeather].effects & PRECIPFX_THUNDER);
boolean effects_lightning = (precipprops[curWeather].effects & PRECIPFX_LIGHTNING);
boolean lightningStrike = false;
// No thunder except every other tic.
if (!(leveltime & 1))
{
if ((precipprops[globalweather].effects & PRECIPFX_THUNDER)
|| (precipprops[globalweather].effects & PRECIPFX_LIGHTNING))
{
// 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!
thunderchance = (P_RandomKey(8192));
}
else if (sounds_thunder || effects_lightning)
{
// But on the other hand, if the global weather is ANYTHING ELSE,
// don't sync lightning strikes.
// While we'll only use the variable if we care about it, it's
// nice to save on RNG calls when we don't need it.
thunderchance = (M_RandomKey(8192));
}
}
if (thunderchance < 70)
lightningStrike = true;
// 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[g_localplayers[0]] || !players[g_localplayers[0]].mo)
return;
if (sound_disabled)
return; // Sound off? D'aw, no fun.
if (players[g_localplayers[0]].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[g_localplayers[0]].mo->y - 1024*FRACUNIT;
yh = players[g_localplayers[0]].mo->y + 1024*FRACUNIT;
xl = players[g_localplayers[0]].mo->x - 1024*FRACUNIT;
xh = players[g_localplayers[0]].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[g_localplayers[0]].mo->x, players[g_localplayers[0]].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 (rainsfx != sfx_None && (!leveltime || leveltime % rainfreq == 1))
S_StartSoundAtVolume(players[g_localplayers[0]].mo, rainsfx, volume);
if (!sounds_thunder)
return;
if (effects_lightning && lightningStrike && volume)
{
// Large, close thunder sounds to go with our lightning.
S_StartSoundAtVolume(players[g_localplayers[0]].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[g_localplayers[0]].mo, sfx_athun1 + M_RandomKey(2), volume);
}
}
void P_RespawnBattleBoxes(void)
{
thinker_t *th;
if (!(gametyperules & GTR_BUMPERS))
return;
for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
{
mobj_t *box;
mobj_t *newmobj;
if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
continue;
box = (mobj_t *)th;
if (box->type != MT_RANDOMITEM || box->threshold != 68 || box->fuse) // only popped items
continue;
// Respawn from mapthing if you have one!
if (box->spawnpoint)
{
P_SpawnMapThing(box->spawnpoint);
newmobj = box->spawnpoint->mobj; // this is set to the new mobj in P_SpawnMapThing
P_SpawnMobj(box->spawnpoint->mobj->x, box->spawnpoint->mobj->y, box->spawnpoint->mobj->z, MT_EXPLODE); // poof into existance
}
else
{
newmobj = P_SpawnMobj(box->x, box->y, box->z, box->type);
P_SpawnMobj(newmobj->x, newmobj->y, newmobj->z, MT_EXPLODE); // poof into existance
}
// Transfer flags2 (strongbox, objectflip)
newmobj->flags2 = box->flags2;
P_RemoveMobj(box); // make sure they disappear
numgotboxes--; // you've restored a box, remove it from the count
if (numgotboxes < 0)
numgotboxes = 0;
}
}
/** 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)
{
UINT8 p, pcount = 0;
INT32 time = 30*TICRATE; // Respawn things in empty dedicated servers
mapthing_t *mthing = NULL;
//if (!(gametyperules & GTR_CIRCUIT) && numgotboxes >= (4*nummapboxes/5)) // Battle Mode respawns all boxes in a different way
//P_RespawnBattleBoxes();
// wait time depends on player count
for (p = 0; p < MAXPLAYERS; p++)
{
if (playeringame[p] && !players[p].spectator)
pcount++;
}
if (gametyperules & GTR_SPHERES)
{
if (pcount > 2)
time -= (5*TICRATE) * (pcount-2);
if (time < 5*TICRATE)
time = 5*TICRATE;
}
else
{
if (pcount == 1) // No respawn when alone
return;
else if (pcount > 1)
{
time = (120 - ((pcount-2) * 10)) * TICRATE;
// If the map is longer or shorter than 3 laps, then adjust ring respawn to account for this.
// 5 lap courses would have more retreaded ground, while 2 lap courses would have less.
if ((mapheaderinfo[gamemap-1]->numlaps != 3)
&& !(mapheaderinfo[gamemap-1]->levelflags & LF_SECTIONRACE))
time = (time * 3) / max(1, mapheaderinfo[gamemap-1]->numlaps);
if (time < 10*TICRATE)
{
// Ensure it doesn't go into absurdly low values
time = 10*TICRATE;
}
}
}
// nothing left to respawn?
if (iquehead == iquetail)
return;
// the first item in the queue is the first to respawn
if (leveltime - itemrespawntime[iquetail] < (tic_t)time)
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)
{
UINT8 i, pcount = 0;
player_t *p = &players[playernum];
mobj_t *mobj;
if (p->playerstate == PST_REBORN)
G_PlayerReborn(playernum, false);
for (i = 0; i < MAXPLAYERS; i++)
{
if (i == playernum)
continue;
if (!playeringame[i] || players[i].spectator)
continue;
if (players[i].jointime <= 1) // Prevent splitscreen hosters/joiners from only adding 1 player at a time in empty servers
continue;
pcount++;
}
// spawn as spectator determination
if (multiplayer && demo.playback)
{
; // Don't mess with spectator values since the demo setup handles them already.
}
else if (p->bot)
{
/*
if (bonusgame || specialstage)
{
// Bots should avoid
p->spectator = true;
}
*/
{
// No point in a spectating bot!
p->spectator = false;
}
}
else if (netgame && p->jointime <= 1 && pcount)
{
p->spectator = true;
#if 0
if (pcount == 1 || leveltime < starttime)
p->pflags |= PF_WANTSTOJOIN;
p->jointime = 2;
#endif
}
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;
if (playernum&1) p->skincolor = skincolor_redteam;
else p->skincolor = skincolor_blueteam;
// 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;
// Fix team colors.
// This code isn't being done right somewhere else. Oh well.
if (p->ctfteam == 1)
p->skincolor = skincolor_redteam;
else if (p->ctfteam == 2)
p->skincolor = skincolor_blueteam;
}
p->powers[pw_flashing] = K_GetKartFlashing(p); // Babysitting deterrent
mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER);
(mobj->player = p)->mo = mobj;
mobj->angle = 0;
// set color translations for player sprites
mobj->color = p->skincolor;
// 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;
//awayview stuff
p->awayviewmobj = NULL;
p->awayviewtics = 0;
P_SetTarget(&p->follower, NULL); // cleanse follower from existence
// set the scale to the mobj's destscale so settings get correctly set. if we don't, they sometimes don't.
if (cv_kartdebugshrink.value && !modeattacking && !p->bot)
mobj->destscale = 6*mobj->destscale/8;
P_SetScale(mobj, mobj->destscale);
P_FlashPal(p, 0, 0); // Resets
if (gametyperules & GTR_BUMPERS)
{
mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height + 16*FRACUNIT, MT_PLAYERARROW);
P_SetTarget(&overheadarrow->target, mobj);
overheadarrow->drawflags |= MFD_DONTDRAW;
P_SetScale(overheadarrow, mobj->destscale);
if (p->spectator)
{
// HEY! No being cheap...
p->bumpers = 0;
}
else if ((p->bumpers > 0) || (leveltime < starttime) || (pcount <= 1))
{
if ((leveltime < starttime) || (pcount <= 1)) // Start of the map?
{
// Reset those bumpers!
p->bumpers = K_StartingBumperCount();
}
if (p->bumpers)
{
angle_t diff = FixedAngle(360*FRACUNIT/p->bumpers);
angle_t newangle = mobj->angle;
fixed_t newx = mobj->x + P_ReturnThrustX(mobj, newangle + ANGLE_180, 64*FRACUNIT);
fixed_t newy = mobj->y + P_ReturnThrustY(mobj, newangle + ANGLE_180, 64*FRACUNIT);
mobj_t *mo;
for (i = 0; i < p->bumpers; i++)
{
mo = P_SpawnMobj(newx, newy, mobj->z, MT_BATTLEBUMPER);
mo->threshold = i;
P_SetTarget(&mo->target, mobj);
mo->angle = (diff * (i-1));
mo->color = mobj->color;
if (mobj->drawflags & MFD_DONTDRAW)
mo->drawflags |= MFD_DONTDRAW;
else
mo->drawflags &= ~MFD_DONTDRAW;
}
}
}
else if (p->bumpers <= 0)
{
mobj_t *karmahitbox = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_KARMAHITBOX); // Player hitbox is too small!!
P_SetTarget(&karmahitbox->target, mobj);
karmahitbox->destscale = mobj->scale;
P_SetScale(karmahitbox, mobj->scale);
}
}
}
void P_AfterPlayerSpawn(INT32 playernum)
{
player_t *p = &players[playernum];
mobj_t *mobj = p->mo;
UINT8 i;
P_SetPlayerAngle(p, mobj->angle);
p->viewheight = P_GetPlayerViewHeight(p);
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;
for (i = 0; i <= r_splitscreen; i++)
{
if (camera[i].chase)
{
if (displayplayers[i] == playernum)
P_ResetCamera(p, &camera[i]);
}
}
if (CheckForReverseGravity)
P_CheckGravity(mobj, false);
}
// 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;
if (p->respawn.state != RESPAWNST_NONE)
offset += K_RespawnOffset(p, (mthing->options & MTF_OBJECTFLIP));
// Flagging a player's ambush will make them start on the ceiling
// Objectflip inverts
if (!!(mthing->options & MTF_AMBUSH) ^ !!(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->options & MTF_AMBUSH)
P_SetPlayerMobjState(mobj, S_KART_SPINOUT);
}
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);
K_DoIngameRespawn(p);
P_UnsetThingPosition(mobj);
mobj->x = p->respawn.pointx;
mobj->y = p->respawn.pointy;
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->respawn.pointz;
if (z <= floor)
{
mobj->eflags |= MFE_ONGROUND;
z = floor;
}
mobj->floorz = floor;
mobj->ceilingz = ceiling;
mobj->z = z;
// Correct angle
if (p->respawn.wp != NULL)
{
size_t nwp = K_NextRespawnWaypointIndex(p->respawn.wp);
waypoint_t *wp;
if (nwp != SIZE_MAX)
{
wp = p->respawn.wp->nextwaypoints[nwp];
mobj->angle = p->drawangle = R_PointToAngle2(
mobj->x, mobj->y,
wp->mobj->x, wp->mobj->y
);
}
}
P_AfterPlayerSpawn(playernum);
}
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 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 P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(scale, offset + mobjinfo[mobjtype].height);
else
return 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));
switch (mobjtype)
{
// Objects with a non-zero default height.
// (None yet)
// Horizontal springs, may float additional units with MTF_AMBUSH.
case MT_YELLOWHORIZ:
case MT_REDHORIZ:
case MT_BLUEHORIZ:
offset += mthing->options & MTF_AMBUSH ? 16*mapobjectscale : 0;
break;
// Ring-like items, may float additional units with MTF_AMBUSH.
case MT_SPIKEBALL:
case MT_EMBLEM:
case MT_RING:
case MT_BLUESPHERE:
offset += mthing->options & MTF_AMBUSH ? 24*mapobjectscale : 0;
break;
// This object does not have an offset
default:
break;
}
if (!(dz + offset)) // 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);
}
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 == 777 || mthing->type == 778) // Slope anchors
|| (mthing->type >= 600 && mthing->type <= 609) // Special placement patterns
|| mthing->type == 1705 || mthing->type == 1713) // Hoops
{
return true; // These are handled elsewhere.
}
return false;
}
static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
{
(void)mthing;
switch (i)
{
case MT_EMBLEM:
if (netgame || multiplayer)
return false; // Single player only
if (modifiedgame && !savemoddata)
return false; // No cheating!!
break;
default:
break;
}
if (metalrecording) // Metal Sonic can't use these things.
{
if (mobjinfo[i].flags & (MF_ENEMY|MF_BOSS))
return false;
}
return true;
}
static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
{
(void)mthing;
if ((gametyperules & GTR_SPHERES) && (i == MT_RING))
return MT_BLUESPHERE;
if ((gametyperules & GTR_PAPERITEMS) && (i == MT_RANDOMITEM))
return MT_PAPERITEMSPOT;
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->tag == mthing->angle)
break;
emblem = M_GetLevelEmblems(-1);
}
if (!emblem)
{
CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, mthing->angle);
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;
if (emblemlocations[j].collected)
{
P_UnsetThingPosition(mobj);
mobj->flags |= MF_NOCLIP;
mobj->flags &= ~MF_SPECIAL;
mobj->flags |= MF_NOBLOCKMAP;
mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
P_SetThingPosition(mobj);
}
else
{
mobj->frame &= ~FF_TRANSMASK;
if (emblemlocations[j].type == ET_GLOBAL)
{
mobj->reactiontime = emblemlocations[j].var;
}
}
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;
INT32 line;
const size_t mthingi = (size_t)(mthing - mapthings);
// Find the corresponding linedef special, using angle as tag
// P_FindSpecialLineFromTag works here now =D
line = P_FindSpecialLineFromTag(9, mthing->angle, -1);
if (line == -1)
{
CONS_Debug(DBG_GAMELOGIC, "Mace chain (mapthing #%s) needs to be tagged to a #9 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
return false;
}
/*
mapthing -
MTF_AMBUSH :
MT_SPRINGBALLPOINT - upgrade from yellow to red spring
anything else - bigger mace/chain theory
MTF_OBJECTSPECIAL - force silent
MTF_GRAVFLIP - flips objects, doesn't affect chain arrangements
Parameter value : number of "spokes"
linedef -
ML_NOCLIMB :
MT_CHAINPOINT/MT_CHAINMACEPOINT with ML_EFFECT1 applied - Direction not controllable
anything else - no functionality
ML_EFFECT1 : Swings instead of spins
ML_EFFECT2 : Linktype is replaced with macetype for all spokes not ending in chains (inverted for MT_FIREBARPOINT)
ML_EFFECT3 : Spawn a bonus linktype at the hinge point
ML_EFFECT4 : Don't clip inside the ground
ML_EFFECT5 : Don't stop thinking when too far away
*/
mlength = abs(lines[line].dx >> FRACBITS);
mspeed = abs(lines[line].dy >> (FRACBITS - 4));
mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
if ((mminlength = -sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) < 0)
mminlength = 0;
else if (mminlength > mlength - 1)
mminlength = mlength - 1;
mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
mnumspokes = mthing->extrainfo + 1;
mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes) >> ANGLETOFINESHIFT;
if (lines[line].backsector)
{
mpinch = (lines[line].backsector->floorheight >> FRACBITS) % 360;
mroll = (lines[line].backsector->ceilingheight >> FRACBITS) % 360;
mnumnospokes = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS);
if ((mwidth = sides[lines[line].sidenum[1]].rowoffset >> FRACBITS) < 0)
mwidth = 0;
}
else
mpinch = mroll = mnumnospokes = mwidth = 0;
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->options & MTF_AMBUSH)
? MT_REDSPRINGBALL
: MT_YELLOWSPRINGBALL);
chainlink = MT_SMALLMACECHAIN;
break;
case MT_FIREBARPOINT:
macetype = ((mthing->options & MTF_AMBUSH)
? MT_BIGFIREBAR
: MT_SMALLFIREBAR);
chainlink = MT_NULL;
break;
case MT_CUSTOMMACEPOINT:
macetype = (mobjtype_t)sides[lines[line].sidenum[0]].toptexture;
if (lines[line].backsector)
chainlink = (mobjtype_t)sides[lines[line].sidenum[1]].toptexture;
else
chainlink = MT_NULL;
break;
case MT_CHAINPOINT:
if (mthing->options & MTF_AMBUSH)
{
macetype = MT_BIGGRABCHAIN;
chainlink = MT_BIGMACECHAIN;
}
else
{
macetype = MT_SMALLGRABCHAIN;
chainlink = MT_SMALLMACECHAIN;
}
mchainlike = true;
break;
default:
if (mthing->options & MTF_AMBUSH)
{
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 (lines[line].flags & ML_NOCLIMB)
mobj->flags |= MF_SLIDEME;
// Swinging
if (lines[line].flags & ML_EFFECT1)
{
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 (lines[line].flags & ML_EFFECT5)
mobj->flags2 |= MF2_BOSSNOTRAP;
// Make the links the same type as the end - repeated below
if ((mobj->type != MT_CHAINPOINT) && (((lines[line].flags & ML_EFFECT2) == ML_EFFECT2) != (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 = ((lines[line].flags & ML_EFFECT4) ? 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) {\
spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
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;\
}
mdosound = (mspeed && !(mthing->options & MTF_OBJECTSPECIAL));
mdocenter = (macetype && (lines[line].flags & ML_EFFECT3));
// The actual spawning of spokes
while (mnumspokes-- > 0)
{
// Offsets
if (lines[line].flags & ML_EFFECT1) // 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->options & MTF_AMBUSH) ? 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 (lines[line].flags & ML_EFFECT2)
{
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 (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 (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 (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;
INT32 type, numdivisions, anglespeed, ticcount;
angle_t angledivision;
INT32 line;
const size_t mthingi = (size_t)(mthing - mapthings);
// Find the corresponding linedef special, using angle as tag
line = P_FindSpecialLineFromTag(15, mthing->angle, -1);
if (line == -1)
{
CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%s) needs to be tagged to a #15 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
return false;
}
if (sides[lines[line].sidenum[0]].toptexture)
type = sides[lines[line].sidenum[0]].toptexture; // Set as object type in p_setup.c...
else
type = (INT32)MT_PARTICLE;
if (!lines[line].backsector
|| (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
ticcount = 3;
numdivisions = mthing->z;
if (numdivisions)
{
radius = R_PointToDist2(lines[line].v1->x, lines[line].v1->y, lines[line].v2->x, lines[line].v2->y);
anglespeed = (sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) % 360;
angledivision = 360/numdivisions;
}
else
{
numdivisions = 1; // Simple trick to make A_ParticleSpawn simpler.
radius = 0;
anglespeed = 0;
angledivision = 0;
}
speed = abs(sides[lines[line].sidenum[0]].textureoffset);
if (mthing->options & MTF_OBJECTFLIP)
speed *= -1;
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 seperation is %d\n",
sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
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->cvmem = line;
mobj->watertop = mobj->waterbottom = 0;
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);
seg->angle = angle - ANGLE_90;
P_SetMobjState(seg, facestate);
seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
seg->angle = angle + ANGLE_90;
P_SetMobjState(seg, facestate);
seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
seg->angle = angle;
P_SetMobjState(seg, leftstate);
seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
seg->angle = angle;
P_SetMobjState(seg, rightstate);
seg = P_SpawnMobjFromMobj(mobj, 13*(x1 + x2), 13*(y1 + y2), 0, MT_BOOSTERROLLER);
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
seg = P_SpawnMobjFromMobj(mobj, 13*(x1 - x2), 13*(y1 - y2), 0, MT_BOOSTERROLLER);
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
seg = P_SpawnMobjFromMobj(mobj, -13*(x1 + x2), -13*(y1 + y2), 0, MT_BOOSTERROLLER);
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
seg = P_SpawnMobjFromMobj(mobj, -13*(x1 - x2), -13*(y1 - y2), 0, MT_BOOSTERROLLER);
seg->angle = angle;
P_SetMobjState(seg, rollerstate);
return true;
}
static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
{
boolean override = LUAh_MapThingSpawn(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:
if (mthing->tag < 0 || mthing->tag > 15)
{
CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", mthing->tag, sizeu1((size_t)(mthing - mapthings)));
break;
}
if (mthing->options & MTF_OBJECTSPECIAL)
skyboxcenterpnts[mthing->tag] = mobj;
else
skyboxviewpnts[mthing->tag] = mobj;
break;
case MT_EGGSTATUE:
if (mthing->options & MTF_EXTRA)
{
mobj->color = SKINCOLOR_GOLD;
mobj->colorized = true;
}
break;
case MT_FAN:
if (mthing->options & MTF_OBJECTSPECIAL)
{
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->angle)
mobj->health = mthing->angle;
else
mobj->health = FixedMul(mobj->subsector->sector->ceilingheight - mobj->subsector->sector->floorheight, 3*(FRACUNIT/4)) >> FRACBITS;
break;
case MT_BALLOON:
if (mthing->angle > 0)
mobj->color = ((mthing->angle - 1) % (numskincolors - 1)) + 1;
break;
#define makesoftwarecorona(mo, h) \
corona = P_SpawnMobjFromMobj(mo, 0, 0, h<<FRACBITS, MT_PARTICLE);\
corona->sprite = SPR_FLAM;\
corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12);\
corona->tics = -1
case MT_FLAME:
if (mthing->options & MTF_EXTRA)
{
mobj_t *corona;
makesoftwarecorona(mobj, 20);
P_SetScale(corona, (corona->destscale = mobj->scale*3));
P_SetTarget(&mobj->tracer, corona);
}
break;
case MT_FLAMEHOLDER:
if (!(mthing->options & MTF_OBJECTSPECIAL)) // Spawn the fire
{
mobj_t *flame = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_FLAME);
P_SetTarget(&flame->target, mobj);
flame->flags2 |= MF2_BOSSNOTRAP;
if (mthing->options & MTF_EXTRA)
{
mobj_t *corona;
makesoftwarecorona(flame, 20);
P_SetScale(corona, (corona->destscale = flame->scale*3));
P_SetTarget(&flame->tracer, corona);
}
}
break;
case MT_CANDLE:
case MT_CANDLEPRICKET:
if (mthing->options & MTF_EXTRA)
{
mobj_t *corona;
makesoftwarecorona(mobj, ((mobj->type == MT_CANDLE) ? 42 : 176));
}
break;
#undef makesoftwarecorona
case MT_JACKO1:
case MT_JACKO2:
case MT_JACKO3:
if (!(mthing->options & MTF_EXTRA)) // take the torch out of the crafting recipe
{
mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
P_SetTarget(&overlay->target, mobj);
P_SetMobjState(overlay, mobj->info->raisestate);
}
break;
case MT_WATERDRIP:
mobj->tics = 3*TICRATE + mthing->angle;
break;
case MT_FLAMEJET:
case MT_VERTICALFLAMEJET:
mobj->threshold = (mthing->angle >> 10) & 7;
mobj->movecount = (mthing->angle >> 13);
mobj->threshold *= (TICRATE/2);
mobj->movecount *= (TICRATE/2);
mobj->movedir = mthing->extrainfo;
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_ROCKSPAWNER:
mobj->threshold = mthing->angle;
mobj->movecount = mthing->extrainfo;
break;
case MT_TUBEWAYPOINT:
{
UINT8 sequence = mthing->angle >> 8;
UINT8 id = mthing->angle & 255;
mobj->health = id;
mobj->threshold = sequence;
P_AddTubeWaypoint(sequence, id, mobj);
break;
}
case MT_DSZSTALAGMITE:
case MT_DSZ2STALAGMITE:
case MT_KELP:
if (mthing->options & MTF_OBJECTSPECIAL) { // make mobj twice as big as normal
P_SetScale(mobj, 2*mobj->scale); // not 2*FRACUNIT in case of something like the old ERZ3 mode
mobj->destscale = mobj->scale;
}
break;
case MT_THZTREE:
{ // Spawn the branches
angle_t mobjangle = FixedAngle((mthing->angle % 113) << FRACBITS);
P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_22h;
P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_157h;
P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
}
break;
case MT_CEZPOLE1:
case MT_CEZPOLE2:
{ // Spawn the banner
angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS);
P_SpawnMobjFromMobj(mobj,
P_ReturnThrustX(mobj, mobjangle, 4 << FRACBITS),
P_ReturnThrustY(mobj, mobjangle, 4 << FRACBITS),
0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->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);\
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->angle > 0)
mobj->tics += mthing->angle;
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);
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 uppermost bit is set
if (mthing->angle & 16384)
mobj->flags2 |= MF2_AMBUSH;
if (mthing->angle > 0)
mobj->radius = (mthing->angle & 16383) << FRACBITS;
// FALLTHRU
case MT_AXISTRANSFER:
case MT_AXISTRANSFERLINE:
// Mare it belongs to
mobj->threshold = min(mthing->extrainfo, 7);
// # in the mare
mobj->health = mthing->options;
mobj->flags2 |= MF2_AXIS;
break;
case MT_STARPOST:
{
thinker_t* th;
mobj_t* mo2;
boolean foundanother = false;
if (mthing->extrainfo)
// Allow thing Parameter to define star post num too!
// For starposts above param 15 (the 16th), add 360 to the angle like before and start parameter from 1 (NOT 0)!
// So the 16th starpost is angle=0 param=15, the 17th would be angle=360 param=1.
// This seems more intuitive for mappers to use until UDMF is ready, since most SP maps won't have over 16 consecutive star posts.
mobj->health = mthing->extrainfo + (mthing->angle/360)*15 + 1;
else
// Old behavior if Parameter is 0; add 360 to the angle for each consecutive star post.
mobj->health = (mthing->angle/360) + 1;
// See if other starposts exist in this level that have the same value.
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)
{
foundanother = true;
break;
}
}
if (!foundanother)
numstarposts++;
break;
}
case MT_SPIKE:
// Pop up spikes!
if (mthing->options & MTF_OBJECTSPECIAL)
{
mobj->flags &= ~MF_SCENERY;
mobj->fuse = (16 - mthing->extrainfo)*(mthing->angle + mobj->info->speed)/16;
if (mthing->options & MTF_EXTRA)
P_SetMobjState(mobj, mobj->info->meleestate);
}
// Use per-thing collision for spikes if the deaf flag isn't checked.
if (!(mthing->options & MTF_AMBUSH) && !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->options & MTF_OBJECTSPECIAL)
{
mobj->flags &= ~MF_SCENERY;
mobj->fuse = (16 - mthing->extrainfo)*((mthing->angle/360) + mobj->info->speed)/16;
if (mthing->options & MTF_EXTRA)
P_SetMobjState(mobj, mobj->info->meleestate);
}
// Use per-thing collision for spikes if the deaf flag isn't checked.
if (!(mthing->options & MTF_AMBUSH) && !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);
base->angle = mobjangle + ANGLE_90;
base->destscale = mobj->destscale;
P_SetScale(base, mobj->scale);
P_SetTarget(&base->target, mobj);
P_SetTarget(&mobj->tracer, base);
}
break;
case MT_BIGTUMBLEWEED:
case MT_LITTLETUMBLEWEED:
if (mthing->options & MTF_AMBUSH)
{
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;
}
break;
case MT_REDFLAG:
redflag = mobj;
rflagpoint = mobj->spawnpoint;
break;
case MT_BLUEFLAG:
blueflag = mobj;
bflagpoint = mobj->spawnpoint;
break;
// SRB2Kart
case MT_WAYPOINT:
{
const fixed_t mobjscale =
mapheaderinfo[gamemap-1]->default_waypoint_radius;
if (mthing->args[1] > 0)
mobj->radius = (mthing->args[1]) * FRACUNIT;
else if (mobjscale > 0)
mobj->radius = mobjscale;
else
mobj->radius = DEFAULT_WAYPOINT_RADIUS * mapobjectscale;
// Use threshold to store the next waypoint ID
// movecount is being used for the current waypoint ID
// reactiontime lets us know if we can respawn at it
// lastlook is used for indicating the waypoint is a shortcut
// extravalue1 is used for indicating the waypoint is disabled
// extravalue2 is used for indicating the waypoint is the finishline
mobj->threshold = mthing->args[0];
mobj->movecount = mthing->tag;
if (mthing->options & MTF_EXTRA)
{
mobj->extravalue1 = 0; // The waypoint is disabled if extra is on
}
else
{
mobj->extravalue1 = 1;
}
if (mthing->options & MTF_OBJECTSPECIAL)
{
mobj->lastlook = 1; // the waypoint is a shortcut if objectspecial is on
}
else
{
mobj->lastlook = 0;
}
if (mthing->options & MTF_AMBUSH)
{
mobj->reactiontime = 0; // Can't respawn at if Ambush is on
}
else
{
mobj->reactiontime = 1;
}
if (mthing->args[2] == 1)
{
mobj->extravalue2 = 1; // args[1] of 1 means the waypoint is at the finish line
}
else
{
mobj->extravalue2 = 0;
}
// Sryder 2018-12-7: Grabbed this from the old MT_BOSS3WAYPOINT section so they'll be in the waypointcap instead
P_SetTarget(&mobj->tracer, waypointcap);
P_SetTarget(&waypointcap, mobj);
break;
}
case MT_BOTHINT:
{
// Change size
if (mthing->args[0] > 0)
{
mobj->radius = mthing->args[0] * FRACUNIT;
}
else
{
mobj->radius = 32 * mapobjectscale;
}
// Steer away instead of towards
if (mthing->options & MTF_AMBUSH)
{
mobj->extravalue1 = 0;
}
else
{
mobj->extravalue1 = 1;
}
// Steering amount
if (mthing->args[1] == 0)
{
mobj->extravalue2 = 2;
}
else
{
mobj->extravalue2 = mthing->args[1];
}
break;
}
case MT_AAZTREE_HELPER:
{
fixed_t top = mobj->z;
UINT8 i;
UINT8 locnumsegs = (mthing->extrainfo)+2;
UINT8 numleaves = max(3, (abs(mthing->angle+1) % 6) + 3);
mobj_t *coconut;
// Spawn tree segments
for (i = 0; i < locnumsegs; i++)
{
P_SpawnMobj(mobj->x, mobj->y, top, MT_AAZTREE_SEG);
top += FixedMul(mobjinfo[MT_AAZTREE_SEG].height, mobj->scale);
}
// Big coconut topper
coconut = P_SpawnMobj(mobj->x, mobj->y, top - (8<<FRACBITS), MT_AAZTREE_COCONUT);
P_SetScale(coconut, (coconut->destscale = (2*mobj->scale)));
// Spawn all of the papersprite leaves
for (i = 0; i < numleaves; i++)
{
mobj_t *leaf;
mobj->angle = FixedAngle((i * (360/numleaves))<<FRACBITS);
leaf = P_SpawnMobj(mobj->x + FINECOSINE((mobj->angle>>ANGLETOFINESHIFT) & FINEMASK),
mobj->y + FINESINE((mobj->angle>>ANGLETOFINESHIFT) & FINEMASK), top, MT_AAZTREE_LEAF);
leaf->angle = mobj->angle;
// Small coconut for each leaf
P_SpawnMobj(mobj->x + (32 * FINECOSINE((mobj->angle>>ANGLETOFINESHIFT) & FINEMASK)),
mobj->y + (32 * FINESINE((mobj->angle>>ANGLETOFINESHIFT) & FINEMASK)), top - (24<<FRACBITS), MT_AAZTREE_COCONUT);
}
P_RemoveMobj(mobj); // Don't need this helper obj anymore
return false;
}
case MT_BATTLECAPSULE:
{
sector_t *sec = R_PointInSubsector(mobj->x, mobj->y)->sector;
mobj_t *cur, *prev = mobj;
fixed_t floorheights[MAXFFLOORS+1];
UINT8 numfloors = 0;
boolean fly = true;
UINT8 i;
// This floor height stuff is stupid but I couldn't get it to work any other way for whatever reason
if (mthing->options & MTF_OBJECTFLIP)
{
floorheights[numfloors] = P_GetSectorCeilingZAt(sec, mobj->x, mobj->y) - mobj->height;
}
else
{
floorheights[numfloors] = P_GetSectorFloorZAt(sec, mobj->x, mobj->y);
}
numfloors++;
if (sec->ffloors)
{
ffloor_t *rover;
for (rover = sec->ffloors; rover; rover = rover->next)
{
if ((rover->flags & FF_EXISTS) && (rover->flags & FF_BLOCKOTHERS))
{
if (mthing->options & MTF_OBJECTFLIP)
{
floorheights[numfloors] = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y) - mobj->height;
}
else
{
floorheights[numfloors] = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y);
}
numfloors++;
}
}
}
for (i = 0; i < numfloors; i++)
{
if (mobj->z == floorheights[i])
{
fly = false;
break;
}
}
// Flying capsules
if (fly)
{
mobj->flags |= MF_NOGRAVITY;
mobj->extravalue1 = 1; // Set extravalue1 for later reference
}
// Moving capsules!
if (mthing->args[0] && mthing->args[1])
{
K_SetupMovingCapsule(mthing, mobj);
}
// NOW FOR ALL OF THE PIECES!!
// Init hnext list
// Spherical top
cur = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_BATTLECAPSULE_PIECE);
P_SetMobjState(cur, S_BATTLECAPSULE_TOP);
P_SetTarget(&cur->target, mobj);
P_SetTarget(&cur->hprev, prev);
P_SetTarget(&prev->hnext, cur);
prev = cur;
// Tippity-top decorational button
cur = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_BATTLECAPSULE_PIECE);
P_SetMobjState(cur, S_BATTLECAPSULE_BUTTON);
P_SetTarget(&cur->target, mobj);
P_SetTarget(&cur->hprev, prev);
P_SetTarget(&prev->hnext, cur);
prev = cur;
// Supports on the bottom
for (i = 0; i < 4; i++)
{
cur = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_BATTLECAPSULE_PIECE);
cur->extravalue1 = i;
if (mobj->extravalue1) // Flying capsule, moving or not
P_SetMobjState(cur, S_BATTLECAPSULE_SUPPORTFLY);
else if (mobj->target && !P_MobjWasRemoved(mobj->target)) // Grounded, moving capsule
P_SetMobjState(cur, S_KARMAWHEEL);
else
P_SetMobjState(cur, S_BATTLECAPSULE_SUPPORT); // Grounded, stationary capsule
P_SetTarget(&cur->target, mobj);
P_SetTarget(&cur->hprev, prev);
P_SetTarget(&prev->hnext, cur);
prev = cur;
}
// Side paneling
for (i = 0; i < 8; i++)
{
cur = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_BATTLECAPSULE_PIECE);
cur->extravalue1 = i;
if (i & 1)
P_SetMobjState(cur, S_BATTLECAPSULE_SIDE2);
else
P_SetMobjState(cur, S_BATTLECAPSULE_SIDE1);
P_SetTarget(&cur->target, mobj);
P_SetTarget(&cur->hprev, prev);
P_SetTarget(&prev->hnext, cur);
prev = cur;
}
// Increment no. of capsules on the map counter
maptargets++;
}
default:
break;
}
if (mobj->flags & MF_BOSS)
{
if (mthing->options & MTF_OBJECTSPECIAL) // No egg trap for this boss
mobj->flags2 |= MF2_BOSSNOTRAP;
}
return true;
}
static void P_SetAmbush(mobj_t *mobj)
{
if (mobj->flags & MF_SPRING)
{
// gravity toggle
mobj->flags ^= MF_NOGRAVITY;
}
if (mobj->flags & MF_NIGHTSITEM)
{
// Spawn already displayed
mobj->flags |= MF_SPECIAL;
mobj->flags &= ~MF_NIGHTSITEM;
}
if (mobj->flags & MF_PUSHABLE)
mobj->flags &= ~MF_PUSHABLE;
if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
{
// flag for strong/weak random boxes
// any monitor with nonzero speed is allowed to respawn like this
mobj->flags2 |= MF2_AMBUSH;
}
else if (mobj->type != MT_AXIS &&
mobj->type != MT_AXISTRANSFER &&
mobj->type != MT_AXISTRANSFERLINE &&
mobj->type != MT_NIGHTSBUMPER &&
mobj->type != MT_STARPOST)
mobj->flags2 |= MF2_AMBUSH;
}
static void P_SetObjectSpecial(mobj_t *mobj)
{
if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
{
// flag for strong/weak random boxes
// any monitor with nonzero speed is allowed to respawn like this
mobj->flags2 |= MF2_STRONGBOX;
}
// Requires you to be in bonus time to activate
if (mobj->flags & MF_NIGHTSITEM)
mobj->flags2 |= MF2_STRONGBOX;
// Pushables bounce and slide coolly with object special flag set
if (mobj->flags & MF_PUSHABLE)
{
mobj->flags2 |= MF2_SLIDEPUSH;
mobj->flags &= ~MF_SLIDEME;
}
}
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);
mobj->spawnpoint = mthing;
P_SetScale(mobj, mthing->scale);
mobj->destscale = mthing->scale;
if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
return mobj;
if (doangle)
mobj->angle = FixedAngle(mthing->angle << FRACBITS);
if ((mobj->flags & MF_SPRING)
&& mobj->info->damage != 0
&& mobj->info->mass == 0)
{
// Offset sprite of horizontal springs
angle_t a = mobj->angle + ANGLE_180;
mobj->sprxoff = FixedMul(mobj->radius, FINECOSINE(a >> ANGLETOFINESHIFT));
mobj->spryoff = FixedMul(mobj->radius, FINESINE(a >> ANGLETOFINESHIFT));
}
mobj->pitch = FixedAngle(mthing->pitch << FRACBITS);
mobj->roll = FixedAngle(mthing->roll << FRACBITS);
mthing->mobj = mobj;
// ignore MTF_ flags and return early
if (i == MT_NIGHTSBUMPER)
return mobj;
if ((mthing->options & MTF_AMBUSH)
&& (mthing->options & MTF_OBJECTSPECIAL)
&& (mobj->flags & MF_PUSHABLE))
mobj->flags2 |= MF2_CLASSICPUSH;
else
{
if (mthing->options & MTF_AMBUSH)
P_SetAmbush(mobj);
if (mthing->options & MTF_OBJECTSPECIAL)
P_SetObjectSpecial(mobj);
}
// Generic reverse gravity for individual objects flag.
if (mthing->options & MTF_OBJECTFLIP)
{
mobj->eflags |= MFE_VERTICALFLIP;
mobj->flags2 |= MF2_OBJECTFLIP;
}
// Extra functionality
if (mthing->options & MTF_EXTRA)
{
if (mobj->flags & MF_MONITOR && (mthing->angle & 16384))
{
// Store line exec tag to run upon popping
mobj->lastlook = (mthing->angle & 16383);
}
}
// Final set of not being able to draw nightsitems.
if (mobj->flags & MF_NIGHTSITEM)
mobj->drawflags |= MFD_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);
}
static void P_SpawnHoopInternal(mapthing_t *mthing, INT32 hoopsize, fixed_t sizefactor)
{
mobj_t *mobj = NULL;
mobj_t *nextmobj = NULL;
mobj_t *hoopcenter;
TMatrix *pitchmatrix, *yawmatrix;
fixed_t radius = hoopsize*sizefactor;
INT32 i;
angle_t fa;
TVector 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);
hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER);
hoopcenter->spawnpoint = mthing;
hoopcenter->z -= hoopcenter->height/2;
P_UnsetThingPosition(hoopcenter);
hoopcenter->x = x;
hoopcenter->y = y;
P_SetThingPosition(hoopcenter);
// Scale 0-255 to 0-359 =(
hoopcenter->movedir = ((mthing->angle & 255)*360)/256; // Pitch
pitchmatrix = RotateXMatrix(FixedAngle(hoopcenter->movedir << FRACBITS));
hoopcenter->movecount = (((UINT16)mthing->angle >> 8)*360)/256; // Yaw
yawmatrix = RotateZMatrix(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[0] = FixedMul(FINECOSINE(fa), radius);
v[1] = 0;
v[2] = FixedMul(FINESINE(fa), radius);
v[3] = FRACUNIT;
res = VectorMatrixMultiply(v, *pitchmatrix);
M_Memcpy(&v, res, sizeof(v));
res = VectorMatrixMultiply(v, *yawmatrix);
M_Memcpy(&v, res, sizeof(v));
mobj = P_SpawnMobj(x + v[0], y + v[1], z + v[2], MT_HOOP);
mobj->z -= mobj->height/2;
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[0] = FixedMul(FINECOSINE(fa), radius);
v[1] = 0;
v[2] = FixedMul(FINESINE(fa), radius);
v[3] = FRACUNIT;
res = VectorMatrixMultiply(v, *pitchmatrix);
M_Memcpy(&v, res, sizeof(v));
res = VectorMatrixMultiply(v, *yawmatrix);
M_Memcpy(&v, res, sizeof(v));
mobj = P_SpawnMobj(x + v[0], y + v[1], z + v[2], MT_HOOPCOLLIDE);
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_SpawnHoop(mapthing_t *mthing)
{
if (metalrecording)
return;
if (mthing->type == 1705) // Generic hoop
P_SpawnHoopInternal(mthing, 24, 4*FRACUNIT);
else // Customizable hoop
// For each flag add 16 fracunits to the size
// Default (0 flags) is 32 fracunits
P_SpawnHoopInternal(mthing, 8 + (4*(mthing->options & 0xF)), 4*FRACUNIT);
}
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;
(void)bonustime;
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);
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;
}
}
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 };
return 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;
TVector v, *res;
(void)bonustime;
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);
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[0] = FixedMul(FINECOSINE(fa), size);
v[1] = 0;
v[2] = FixedMul(FINESINE(fa), size);
v[3] = FRACUNIT;
res = VectorMatrixMultiply(v, *RotateZMatrix(angle));
M_Memcpy(&v, res, sizeof(v));
mobj = P_SpawnMobjFromMapThing(&dummything, x + v[0], y + v[1], z + v[2], itemtype);
if (!mobj)
continue;
mobj->z -= mobj->height/2;
mobj->spawnpoint = NULL;
}
}
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)
{
INT32 numitems = (mthing->type & 1) ? 16 : 8;
fixed_t size = (mthing->type & 1) ? 192*FRACUNIT : 96*FRACUNIT;
mobjtype_t itemtypes[1] = { MT_RING };
P_SpawnItemCircle(mthing, itemtypes, 1, numitems, size, 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))
{
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 (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
th->destscale = source->scale;
P_SetScale(th, source->scale);
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 (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
th->destscale = source->scale;
P_SetScale(th, source->scale);
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 (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
th->destscale = source->scale;
P_SetScale(th, source->scale);
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 (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
th->destscale = source->scale;
P_SetScale(th, source->scale);
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 (source->eflags & MFE_VERTICALFLIP)
th->flags2 |= MF2_OBJECTFLIP;
th->destscale = source->scale;
P_SetScale(th, source->scale);
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;
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);
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->destscale = mobj->destscale;
P_SetScale(newmobj, mobj->scale);
return newmobj;
}