// BLANKART //----------------------------------------------------------------------------- // 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 "p_mobj.h" #include "d_netcmd.h" #include "d_think.h" #include "dehacked.h" #include "deh_tables.h" #include "doomdef.h" #include "doomstat.h" #include "doomtype.h" #include "g_game.h" #include "g_input.h" #include "st_stuff.h" #include "hu_stuff.h" #include "p_local.h" #include "p_setup.h" #include "r_fps.h" #include "r_main.h" #include "r_skins.h" #include "r_sky.h" #include "r_splats.h" #include "s_sound.h" #include "z_zone.h" #include "m_random.h" #include "m_cheat.h" #include "m_misc.h" #include "info.h" #include "i_video.h" #include "lua_hook.h" #include "p_slopes.h" #include "f_finale.h" #include "m_cond.h" #include "m_aatree.h" // SRB2kart #include "k_kart.h" #include "k_battle.h" #include "k_boss.h" #include "k_color.h" #include "k_bot.h" #include "k_terrain.h" #include "k_collide.h" #include "k_objects.h" #include "k_items.h" #include "k_waypoint.h" // BlanKart #include "blan/b_soc.h" static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}}; consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL); actioncache_t actioncachehead; static mobj_t *overlaycap = NULL; mobj_t *kitemcap = NULL; // Used for Kart offensive items (the ones that can get removed by sizedown) mobj_t *misccap = NULL; // Used for misc object tracking mobj_t *waypointcap = NULL; mobj_t *boss3cap = 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(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; } static inline INT32 randomframe (mobj_t *mobj, INT32 n) { // Only mobj thinkers should use synced RNG if (mobj->thinker.function == (actionf_p1)P_MobjThinker) return P_RandomKey(n); else return M_RandomKey(n); } // // P_SetupStateAnimation // static 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) { mobj->anim_duration -= (leveltime % st->var2); // Duration synced to timer mobj->frame += (leveltime / st->var2) % (animlength + 1); // Frame synced to timer (duration taken into account) if (!thinkersCompleted) // objects spawned BEFORE (or during) thinkers will think during this tic... mobj->anim_duration++; // ...so increase the duration of their current frame by 1 to sync with objects spawned AFTER thinkers } else if (st->frame & FF_RANDOMANIM) { mobj->frame += randomframe(mobj, animlength + 1); // Random starting frame mobj->anim_duration -= randomframe(mobj, 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) { const UINT8 start = mobj->state->frame & FF_FRAMEMASK; UINT8 frame = mobj->frame & FF_FRAMEMASK; // 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_REVERSEANIM ? (start - (--frame)) : ((++frame) - start)) > mobj->state->var1) frame = start; mobj->frame = frame | (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 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: case S_KART_STILL_GLANCE_L: case S_KART_STILL_GLANCE_R: case S_KART_STILL_LOOK_L: case S_KART_STILL_LOOK_R: player->panim = PA_STILL; break; case S_KART_SLOW: case S_KART_SLOW_L: case S_KART_SLOW_R: case S_KART_SLOW_GLANCE_L: case S_KART_SLOW_GLANCE_R: case S_KART_SLOW_LOOK_L: case S_KART_SLOW_LOOK_R: player->panim = PA_SLOW; break; case S_KART_FAST: case S_KART_FAST_L: case S_KART_FAST_R: case S_KART_FAST_GLANCE_L: case S_KART_FAST_GLANCE_R: case S_KART_FAST_LOOK_L: case S_KART_FAST_LOOK_R: player->panim = PA_FAST; 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_DRIFT; break; case S_KART_SPINOUT: case S_KART_DEAD: player->panim = PA_HURT; 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, 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) { var1 = st->var1; var2 = st->var2; astate = st; st->action(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) { var1 = st->var1; var2 = st->var2; astate = st; st->action(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_FreePrecipMobj(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->fofflags & FOF_EXISTS)) return false; if ((((rover->fofflags & FOF_BLOCKPLAYER) && mobj->player) || ((rover->fofflags & FOF_BLOCKOTHERS) && !mobj->player))) return false; topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y); bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y); if (mobj->z > topheight) return false; if (mobj->z + mobj->height < bottomheight) return false; return true; } // P_GetFloorZ (and its ceiling counterpart) // Gets the floor height (or ceiling height) of the mobj's contact point in sector, assuming object's center if moved to [x, y] // If line is supplied, it's a divider line on the sector. Set it to NULL if you're not checking for collision with a line // Supply boundsec ONLY when checking for specials! It should be the "in-level" sector, and sector the control sector (if separate). // If set, then this function will iterate through boundsec's linedefs to find the highest contact point on the slope. Non-special-checking // usage will handle that later. static fixed_t HighestOnLine(fixed_t radius, fixed_t x, fixed_t y, const line_t *line, const 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(const mobj_t *mobj, const sector_t *sector, const sector_t *boundsec, fixed_t x, fixed_t y, const 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(const mobj_t *mobj, const sector_t *sector, const sector_t *boundsec, fixed_t x, fixed_t y, const 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; } INT32 P_FloorPicAtPos(fixed_t x, fixed_t y, fixed_t z, fixed_t height) { sector_t *sector = R_PointInSubsector(x, y)->sector; INT32 floorpic = sector->floorpic; if (sector->ffloors) { ffloor_t *best = NULL; fixed_t thingtop = z + height; for (ffloor_t *rover = sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; fixed_t topheight = P_GetFFloorTopZAt(rover, x, y); fixed_t bottomheight = P_GetFFloorBottomZAt(rover, x, y); fixed_t delta1 = z - (bottomheight + ((topheight - bottomheight)/2)); fixed_t delta2 = thingtop - (bottomheight + ((topheight - bottomheight)/2)); if (topheight > P_GetFFloorTopZAt(best, x, y) && abs(delta1) < abs(delta2)) { best = rover; } } if (best) floorpic = *best->toppic; } return floorpic; } static void P_PlayerFlip(mobj_t *mo) { if (!mo->player) return; if (mo->player->pflags & PF_FLIPCAM) { UINT8 i; mo->player->aiming = InvAngle(mo->player->aiming); for (i = 0; i <= r_splitscreen; i++) { if (mo->player-players == displayplayers[i]) { localaiming[i] = mo->player->aiming; if (camera[i].chase) { camera[i].aiming = InvAngle(camera[i].aiming); camera[i].z = mo->z - camera[i].z + mo->z; if (mo->eflags & MFE_VERTICALFLIP) camera[i].z += FixedMul(20*FRACUNIT, mo->scale); } } } } G_GhostAddFlip((INT32) (mo->player - players)); // Flip aiming to match! } fixed_t P_GetMobjGravity(mobj_t *mo) { fixed_t gravityadd = 0; fixed_t gravitymul = FRACUNIT; sector_t *gravsector = NULL; // Custom gravity boolean goopgravity = false; boolean wasflip; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); gravitymul = mo->scale; wasflip = (mo->eflags & MFE_VERTICALFLIP) != 0; mo->eflags &= ~MFE_VERTICALFLIP; if (mo->subsector->sector->ffloors) // Check for 3D floor gravity too. { ffloor_t *rover; for (rover = mo->subsector->sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS) || !P_InsideANonSolidFFloor(mo, rover)) // P_InsideANonSolidFFloor checks for FOF_EXISTS itself, but let's not always call this function continue; if ((rover->fofflags & (FOF_SWIMMABLE|FOF_GOOWATER)) == (FOF_SWIMMABLE|FOF_GOOWATER)) goopgravity = true; if (P_GetSectorGravityFactor(rover->master->frontsector) == FRACUNIT) continue; gravsector = rover->master->frontsector; break; } } if (!gravsector) // If there is no 3D floor gravity, check sector's gravity gravsector = mo->subsector->sector; gravityadd = -FixedMul(FixedMul(gravity, mo->gravity), P_GetSectorGravityFactor(gravsector)); if ((gravsector->flags & MSF_GRAVITYFLIP) && gravityadd > 0) { if (gravsector->specialflags & SSF_GRAVITYOVERRIDE) mo->flags2 &= ~MF2_OBJECTFLIP; mo->eflags |= MFE_VERTICALFLIP; } // Less gravity underwater. if (mo->eflags & MFE_UNDERWATER && !goopgravity) gravityadd = gravityadd/3; if (mo->player) { // MF2_OBJECTFLIP is relative -- flips sector reverse gravity back to normal if (mo->flags2 & MF2_OBJECTFLIP) { 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->pogospring && !mo->player->dashRainbowPogo) { gravityadd = (5*gravityadd)/2; } if (mo->player->carry == CR_DASHRING && Obj_DashRingPlayerHasNoGravity(mo->player)) { gravityadd = 0; } if (K_IsAltShrunk(mo->player)) { gravitymul = mapobjectscale; } } else { // Objects with permanent reverse gravity (MF2_OBJECTFLIP) if (mo->flags2 & MF2_OBJECTFLIP) { mo->eflags |= MFE_VERTICALFLIP; if (mo->z + mo->height >= mo->ceilingz) gravityadd = 0; else if (gravityadd < 0) // Don't sink, only rise up gravityadd *= -1; } else //Otherwise, sort through the other exceptions. { switch (mo->type) { case MT_FLINGRING: case MT_FLINGCOIN: case MT_FLINGNIGHTSCHIP: case MT_FLINGEMERALD: 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_EGGMINE: case MT_SINK: if (mo->extravalue2 > 0) { gravityadd *= mo->extravalue2; } gravityadd = (5*gravityadd)/2; break; case MT_ORBINAUT: case MT_JAWZ: case MT_JAWZ_DUD: gravityadd = (5*gravityadd)/2; break; case MT_SIGN: gravityadd /= 8; break; case MT_KARMAFIREWORK: gravityadd /= 3; break; default: break; } } } // Goop has slower, reversed gravity if (goopgravity) gravityadd = -gravityadd/5; gravityadd = FixedMul(gravityadd, gravitymul); 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->slopepitch = R_PointToAngle2(0, 0, FixedSqrt(FixedMul(tempy, tempy) + FixedMul(tempz, tempz)), tempx); mo->sloperoll = R_PointToAngle2(0, 0, tempz, tempy); } else { mo->slopepitch = mo->sloperoll = 0; } mo->pitch = mo->roll = 0; } // // P_SetPitchRoll // void P_SetPitchRoll(mobj_t *mo, angle_t pitch, angle_t yaw) { pitch = InvAngle(pitch); yaw >>= ANGLETOFINESHIFT; mo->roll = FixedMul(pitch, FINESINE (yaw)); mo->pitch = FixedMul(pitch, FINECOSINE (yaw)); } #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; //mo->movefactor = ((0x10092 - mo->friction)*(0x70))/0x158; } 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 (abs(player->rmomx) < FixedMul(STOPSPEED, mo->scale) && abs(player->rmomy) < FixedMul(STOPSPEED, mo->scale) && (!player->cmd.forwardmove && !player->cmd.sidemove) && !(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_SLOW) { P_SetPlayerMobjState(mo, S_KART_STILL); } mo->momx = player->cmomx; mo->momy = player->cmomy; } 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); } 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->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_BUSTUP)) continue; if (!(rover->bustflags & FB_PUSHABLES)) continue; if (rover->master->frontsector->crumblestate != CRUMBLE_NONE) continue; topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL); bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL); // Height checks if (rover->bustflags & FB_ONLYBOTTOM) { if (mo->z + mo->momz + mo->height < bottomheight) continue; if (mo->z + mo->height > bottomheight) continue; } else { switch (rover->busttype) { case BT_TOUCH: if (mo->z + mo->momz > topheight) continue; if (mo->z + mo->momz + mo->height < bottomheight) continue; break; case BT_SPINBUST: if (mo->z + mo->momz > topheight) continue; if (mo->z + mo->height < bottomheight) continue; break; default: if (mo->z >= topheight) continue; if (mo->z + mo->height < bottomheight) continue; break; } } EV_CrumbleChain(NULL, rover); // node->m_sector // Run a linedef executor?? if (rover->bustflags & FB_EXECUTOR) P_LinedefExecute(rover->busttag, mo, node->m_sector); goto bustupdone; } } bustupdone: P_UnsetThingPosition(mo); mo->x = oldx; mo->y = oldy; P_SetThingPosition(mo); } // // P_CheckSkyHit // static boolean P_CheckSkyHit(mobj_t *mo) { if (g_tm.ceilingline && g_tm.ceilingline->backsector && g_tm.ceilingline->backsector->ceilingpic == skyflatnum && g_tm.ceilingline->frontsector && g_tm.ceilingline->frontsector->ceilingpic == skyflatnum && (mo->z >= g_tm.ceilingline->frontsector->ceilingheight || mo->z >= g_tm.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; TryMoveResult_t result = {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; mo->terrain = 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, &result) && !(P_MobjWasRemoved(mo) || mo->eflags & MFE_SPRUNG)) { // blocked move moved = false; if (LUA_HookMobjMoveBlocked(mo, result.mo, result.line)) { if (P_MobjWasRemoved(mo)) return; } else if (P_MobjWasRemoved(mo)) return; P_PushSpecialLine(result.line, mo); //{ 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 (g_tm.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(g_tm.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(g_tm.blockingline, P_PointOnLineSide(mo->x,mo->y,g_tm.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 walltransfered = false; boolean walltransferok = B_UseWallTransfer(mo); //if (player || mo->flags & MF_SLIDEME|MF_PUSHABLE) if (walltransferok) { // try to slide along it pslope_t *transferslope = NULL; fixed_t transfermomz = 0; // Wall transfer part 1. 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. P_SlideMove(mo, &result); xmove = ymove = 0; walltransfered = true; 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; mo->terrain = NULL; if (mo->player) { S_StartSound(mo, sfx_kc5e); mo->player->walltransfered = true; } } } } if (walltransfered == false) { if (player) { P_BounceMove(mo,&result); if (P_MobjWasRemoved(mo)) return; xmove = ymove = 0; } else if (mo->flags & MF_SLIDEME) { P_SlideMove(mo, &result); if (P_MobjWasRemoved(mo)) return; xmove = ymove = 0; } else if (mo->flags & MF_BOUNCE) { P_BounceMove(mo, &result); if (P_MobjWasRemoved(mo)) return; xmove = ymove = 0; S_StartSound(mo, mo->info->activesound); //{ SRB2kart - Orbinaut, Ballhog 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; } else if (mo->type == MT_EGGMINE) { K_SpawnEggMineBumpEffect(mo); } switch (mo->type) { case MT_ORBINAUT: // Orbinaut speed decreasing if (mo->health > 1) { S_StartSound(mo, mo->info->attacksound); mo->health--; // This prevents an item thrown at a wall from // phasing through you on its return. mo->threshold = 0; } /*FALLTHRU*/ case MT_JAWZ: 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); } break; case MT_BUBBLESHIELDTRAP: S_StartSound(mo, sfx_s3k44); // Bubble bounce break; default: break; } } else mo->momx = mo->momy = 0; } } // } else moved = true; if (P_MobjWasRemoved(mo)) // MF_SPECIAL touched a player! O_o;; return; if (moved == true) { // TERRAIN footstep effects. K_HandleFootstepParticles(mo); } if (moved && oldslope && !(mo->flags & MF_NOCLIPHEIGHT)) { // Check to see if we ran off if (oldslope != mo->standingslope) { // First, compare different slopes angle_t oldangle, newangle; angle_t moveangle = R_PointToAngle2(0, 0, mo->momx, mo->momy); oldangle = FixedMul((signed)oldslope->zangle, FINECOSINE((moveangle - oldslope->xydirection) >> ANGLETOFINESHIFT)); if (mo->standingslope) newangle = FixedMul((signed)mo->standingslope->zangle, FINECOSINE((moveangle - mo->standingslope->xydirection) >> ANGLETOFINESHIFT)); else newangle = 0; // Now compare the Zs of the different quantizations if (oldangle-newangle > ANG30 && oldangle-newangle < ANGLE_180) { // Allow for a bit of sticking - this value can be adjusted later mo->standingslope = oldslope; P_SetPitchRollFromSlope(mo, mo->standingslope); P_SlopeLaunch(mo); //CONS_Printf("launched off of slope - "); } /* CONS_Printf("old angle %f - new angle %f = %f\n", FIXED_TO_FLOAT(AngleFixed(oldangle)), FIXED_TO_FLOAT(AngleFixed(newangle)), FIXED_TO_FLOAT(AngleFixed(oldangle-newangle)) ); */ } else if (predictedz-mo->z > abs(slopemom.z/2) && P_CanApplySlopePhysics(mo, mo->standingslope) == true) { // 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; mo->terrain = 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) 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 (mo->player && (mo->player->spinouttimer && !mo->player->wipeoutslow) && mo->player->speed <= K_GetKartSpeed(mo->player, false, true)/2) return; //} if (((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz)) && !(player && player->carry == CR_SLIDING)) return; // no friction when airborne P_XYFriction(mo, oldx, oldy); } void P_RingXYMovement(mobj_t *mo) { TryMoveResult_t result = {0}; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy, &result)) P_BounceMove(mo, &result); } void P_SceneryXYMovement(mobj_t *mo) { fixed_t oldx, oldy; // reducing bobbing/momentum on ice when up against walls TryMoveResult_t result = {0}; 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, &result)) (mo->flags & MF_BOUNCE ? P_BounceMove : P_SlideMove)(mo, &result); if (P_MobjWasRemoved(mo)) return; if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz)) return; // no friction when airborne if (mo->flags & MF_NOCLIPHEIGHT) return; // no frictions for objects that can pass through floors P_SceneryXYFriction(mo, oldx, oldy); } // // P_AdjustMobjFloorZ_FFloors // // Utility function for P_ZMovement and related // Adjusts mo->floorz/mo->ceiling accordingly for FFloors // // "motype" determines what behaviour to use exactly // This is to keep things consistent in case these various object types NEED to be different // // motype options: // 0 - normal // 1 - forces false check for water (rings) // 2 - forces false check for water + different quicksand behaviour (scenery) // void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype) { ffloor_t *rover; fixed_t delta1, delta2, thingtop; fixed_t topheight, bottomheight; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); thingtop = mo->z + mo->height; for (rover = sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; topheight = P_GetFOFTopZ(mo, sector, rover, mo->x, mo->y, NULL); bottomheight = P_GetFOFBottomZ(mo, sector, rover, mo->x, mo->y, NULL); if (P_CheckSolidFFloorSurface(mo, rover)) // only the player should stand on lava or run on water ; else if (motype != 0 && rover->fofflags & FOF_SWIMMABLE) // "scenery" only continue; else if (rover->fofflags & FOF_QUICKSAND) // quicksand ; else if (!( // if it's not either of the following... (rover->fofflags & (FOF_BLOCKPLAYER|FOF_MARIO) && mo->player) // ...solid to players? (mario blocks are always solid from beneath to players) || (rover->fofflags & FOF_BLOCKOTHERS && !mo->player) // ...solid to others? )) // ...don't take it into account. continue; if (rover->fofflags & FOF_QUICKSAND) { switch (motype) { case 2: // scenery does things differently for some reason if (mo->z < topheight && bottomheight < thingtop) { 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->fofflags & FOF_SOLID) // Non-FOF_SOLID Mario blocks are only solid from bottom && !(rover->fofflags & FOF_REVERSEPLATFORM) && ((P_MobjFlip(mo)*mo->momz >= 0) || (!(rover->fofflags & FOF_PLATFORM)))) // In reverse gravity, only clip for FOFs that are intangible from their bottom (the "top" you're falling through) if you're coming from above ("below" in your frame of reference) { mo->floorz = topheight; } if (bottomheight < mo->ceilingz && abs(delta1) >= abs(delta2) && !(rover->fofflags & FOF_PLATFORM) && ((P_MobjFlip(mo)*mo->momz >= 0) || ((rover->fofflags & FOF_SOLID) && !(rover->fofflags & FOF_REVERSEPLATFORM)))) // In normal gravity, only clip for FOFs that are intangible from the top if you're coming from below { mo->ceilingz = bottomheight; } } } // // P_AdjustMobjFloorZ_PolyObjs // // Utility function for P_ZMovement and related // Adjusts mo->floorz/mo->ceiling accordingly for PolyObjs // static void P_AdjustMobjFloorZ_PolyObjs(mobj_t *mo, subsector_t *subsec) { polyobj_t *po = subsec->polyList; sector_t *polysec; fixed_t delta1, delta2, thingtop; fixed_t polytop, polybottom; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); thingtop = mo->z + mo->height; while(po) { if (!P_MobjInsidePolyobj(po, mo) || !(po->flags & POF_SOLID)) { po = (polyobj_t *)(po->link.next); continue; } // We're inside it! Yess... polysec = po->lines[0]->backsector; if (po->flags & POF_CLIPPLANES) { polytop = polysec->ceilingheight; polybottom = polysec->floorheight; } else { polytop = INT32_MAX; polybottom = INT32_MIN; } delta1 = mo->z - (polybottom + ((polytop - polybottom)/2)); delta2 = thingtop - (polybottom + ((polytop - polybottom)/2)); if (polytop > mo->floorz && abs(delta1) < abs(delta2)) mo->floorz = polytop; if (polybottom < mo->ceilingz && abs(delta1) >= abs(delta2)) mo->ceilingz = polybottom; po = (polyobj_t *)(po->link.next); } } void P_RingZMovement(mobj_t *mo) { I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); // Intercept the stupid 'fall through 3dfloors' bug if (mo->subsector->sector->ffloors) P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 1); if (mo->subsector->polyList) P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector); // adjust height if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo)) { mo->momz += mo->pmomz; mo->pmomz = 0; mo->eflags &= ~MFE_APPLYPMOMZ; } mo->z += mo->momz; // clip movement if (mo->z <= mo->floorz && !(mo->flags & MF_NOCLIPHEIGHT)) { mo->z = mo->floorz; mo->momz = 0; } else if (mo->z + mo->height > mo->ceilingz && !(mo->flags & MF_NOCLIPHEIGHT)) { mo->z = mo->ceilingz - mo->height; mo->momz = 0; } } boolean P_CheckDeathPitCollide(mobj_t *mo) { I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); if (mo->player && mo->player->pflags & PF_GODMODE) { return false; } if (mo->subsector->sector->damagetype == SD_DEATHPIT) { const boolean flipped = (mo->eflags & MFE_VERTICALFLIP); const sectorflags_t flags = mo->subsector->sector->flags; if (((flags & MSF_TRIGGERSPECIAL_HEADBUMP) || !flipped) && (flags & MSF_FLIPSPECIAL_FLOOR)) return (mo->z <= P_GetSpecialBottomZ(mo, mo->subsector->sector, mo->subsector->sector)); if (((flags & MSF_TRIGGERSPECIAL_HEADBUMP) || flipped) && (flags & MSF_FLIPSPECIAL_CEILING)) return (mo->z + mo->height >= P_GetSpecialTopZ(mo, mo->subsector->sector, mo->subsector->sector)); } return false; } boolean P_CheckSolidLava(ffloor_t *rover) { if ((rover->fofflags & FOF_SWIMMABLE) && (rover->master->frontsector->damagetype == SD_LAVA)) { 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; boolean wasonground; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); wasonground = P_IsObjectOnGround(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: // 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_RING: // Ignore still rings case MT_BLUEBALL: // 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_FLINGRING: // Remove flinged rings from death pits. if (P_CheckDeathPitCollide(mo)) { P_RemoveMobj(mo); return false; } 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_BOUNCE) return true; break; case MT_SPIKE: case MT_WALLSPIKE: // Dead spike particles disappear upon ground contact if (!mo->health && (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz)) { P_RemoveMobj(mo); return false; } break; default: // SRB2kart stuff that should die in pits // Shouldn't stop moving along the Z if there's no speed though! if (P_CanDeleteKartItem(mo->type)) { // Remove stuff from death pits. if (P_CheckDeathPitCollide(mo)) { P_RemoveMobj(mo); return false; } } break; } if (!mo->player && P_CheckDeathPitCollide(mo)) { if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS) { // Kill enemies and bosses. 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, NULL); // Sets mo->standingslope correctly if (P_MobjWasRemoved(mo)) // mobjs can be removed by P_CheckPosition -- Monster Iestyn 31/07/21 return false; K_UpdateMobjTerrain(mo, ((mo->eflags & MFE_VERTICALFLIP) ? g_tm.ceilingpic : g_tm.floorpic)); if (((mo->eflags & MFE_VERTICALFLIP) ? g_tm.ceilingslope : g_tm.floorslope) && (mo->type != MT_STEAM)) { mo->standingslope = (mo->eflags & MFE_VERTICALFLIP) ? g_tm.ceilingslope : g_tm.floorslope; 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_PLAYER) // only DEAD players { mom.z = -4*mom.z/5; mo->flags |= MF_NOCLIPHEIGHT; // fall through floor next time } 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; K_SpawnSplashForMobj(mo, abs(mom.z)); if (mo->flags2 & MF2_SKULLFLY) // the skull slammed into something 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) { // 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 = (g_tm.floorthing ? g_tm.floorthing->momz : 0); } else if (g_tm.floorthing) mom.z = g_tm.floorthing->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 { mo->terrain = NULL; 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; } } P_CheckSectorTransitionalEffects(mo, mo->subsector->sector, wasonground); return true; } // Check for "Mario" blocks to hit and bounce them static void P_CheckMarioBlocks(mobj_t *mo) { msecnode_t *node; if (netgame && mo->player->spectator) return; for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next) { ffloor_t *rover; if (!node->m_sector->ffloors) continue; for (rover = node->m_sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_MARIO)) continue; if (mo->eflags & MFE_VERTICALFLIP) continue; // if you were flipped, your head isn't actually hitting your ceilingz is it? if (*rover->bottomheight != mo->ceilingz) continue; if (rover->fofflags & FOF_GOOWATER) // Brick block! EV_CrumbleChain(node->m_sector, rover); else // Question block! EV_MarioBlock(rover, node->m_sector, mo); } } } // Check if we're on a polyobject that triggers a linedef executor. static boolean P_PlayerPolyObjectZMovement(mobj_t *mo) { msecnode_t *node; boolean stopmovecut = false; if (!numPolyObjects) return 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; boolean wasonground; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); if (!mo->player) return; wasonground = P_IsObjectOnGround(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); // 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->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; } K_UpdateMobjTerrain(mo, (mo->eflags & MFE_VERTICALFLIP ? g_tm.ceilingpic : g_tm.floorpic)); // Get up if you fell. if (mo->player->panim == PA_HURT && !K_IsPlayerDamaged(mo->player) && mo->player->squishedtimer == 0) { P_SetPlayerMobjState(mo, S_KART_STILL); } if (!mo->standingslope && (mo->eflags & MFE_VERTICALFLIP ? g_tm.ceilingslope : g_tm.floorslope)) { // Handle landing on slope during Z movement P_HandleSlopeLanding(mo, (mo->eflags & MFE_VERTICALFLIP ? g_tm.ceilingslope : g_tm.floorslope)); } 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 = (g_tm.floorthing ? g_tm.floorthing->momz : 0); } } else if (g_tm.floorthing) { mo->momz = g_tm.floorthing->momz; } } else { mo->terrain = NULL; 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); mo->momz = 0; } } P_CheckSectorTransitionalEffects(mo, mo->subsector->sector, wasonground); } 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_BOUNCE) && (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz)) { // set standingslope P_TryMove(mo, mo->x, mo->y, true, NULL); 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 (mo->type == MT_KART_TIRE) { mo->momz = -mo->momz; mo->flags |= MF_NOCLIPHEIGHT; } else if (g_tm.floorthing) mo->momz = g_tm.floorthing->momz; else if (!g_tm.floorthing) mo->momz = 0; } } else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here! { /// \todo may not be needed (done in P_MobjThinker normally) mo->eflags &= ~MFE_JUSTHITFLOOR; P_CheckGravity(mo, true); } if (((mo->z + mo->height > mo->ceilingz && !(mo->eflags & MFE_VERTICALFLIP)) || (mo->z < mo->floorz && mo->eflags & MFE_VERTICALFLIP)) && !(mo->flags & MF_NOCLIPHEIGHT)) { if (mo->eflags & MFE_VERTICALFLIP) mo->z = mo->floorz; else mo->z = mo->ceilingz - mo->height; if (P_MobjFlip(mo)*mo->momz > 0) // hit the ceiling mo->momz = 0; } return true; } // // P_CanRunOnWater // // Returns true if mobj can water run on a 3D floor // boolean P_CanRunOnWater(mobj_t *mobj, ffloor_t *rover) { const boolean flip = (mobj->eflags & MFE_VERTICALFLIP); fixed_t surfaceheight = INT32_MAX; fixed_t playerbottom = INT32_MAX; fixed_t surfDiff = INT32_MAX; fixed_t maxStep = INT32_MAX; boolean doifit = false; pslope_t *waterSlope = NULL; angle_t ourZAng = 0; angle_t waterZAng = 0; if (rover == NULL) { // No rover. return false; } if (!(rover->fofflags & FOF_SWIMMABLE)) { // It's not even a water FOF. return false; } if (mobj->player && mobj->player->carry != CR_NONE) // Special carry state. { // No good player state. return false; } if (P_IsObjectOnGround(mobj) == false && !K_ItemMobjAllowedtoWaterRun(mobj)) { // Don't allow jumping onto water to start a water run. // (Unless you are some specfic item mobjs!) // (Already water running still counts as being on the ground.) return false; } if (K_WaterRun(mobj) == false) { // Basic conditions for enabling water run. return false; } if (mobj->standingslope != NULL) { ourZAng = mobj->standingslope->zangle; } waterSlope = (flip ? *rover->b_slope : *rover->t_slope); if (waterSlope != NULL) { waterZAng = waterSlope->zangle; } if (ourZAng != waterZAng) { // The surface slopes are different. return false; } surfaceheight = flip ? P_GetFFloorBottomZAt(rover, mobj->x, mobj->y) : P_GetFFloorTopZAt(rover, mobj->x, mobj->y); playerbottom = flip ? (mobj->z + mobj->height) : mobj->z; doifit = flip ? (surfaceheight - mobj->floorz >= mobj->height) : (mobj->ceilingz - surfaceheight >= mobj->height); if (!doifit) { // Player can't fit in this space. return false; } maxStep = P_GetThingStepUp(mobj, mobj->x, mobj->y); surfDiff = flip ? (surfaceheight - playerbottom) : (playerbottom - surfaceheight); if (surfDiff <= maxStep && surfDiff >= 0) { // We start water run IF we can step-down! return true; } return false; } // Of course this shit is different. static boolean P_HandleSolidLavaCompat(mobj_t *mobj, ffloor_t *rover) { if (mapnamespace == MNS_SRB2KART) { I_Assert(mobj != NULL); I_Assert(!P_MobjWasRemoved(mobj)); fixed_t topheight = P_GetFFloorTopZAt(rover, mobj->x, mobj->y); if (rover->fofflags & FOF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3 && !(rover->master->flags & ML_BLOCKMONSTERS) && ((rover->master->flags & ML_MIDPEG) || mobj->z-mobj->momz > topheight - FixedMul(16*FRACUNIT, mobj->scale))) return true; } return P_CheckSolidLava(rover); } boolean P_CheckSolidFFloorSurface(mobj_t *mobj, ffloor_t *rover) { if (!mobj->player) { // They can run on water now! return P_CanRunOnWater(mobj, rover); } return P_HandleSolidLavaCompat(mobj, rover) || P_CanRunOnWater(mobj, rover); } // // 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; fixed_t halfheight = height / 2; boolean wasgroundpounding = false; fixed_t top2 = P_GetSectorCeilingZAt(sector, mobj->x, mobj->y); fixed_t bot2 = P_GetSectorFloorZAt(sector, mobj->x, mobj->y); if (mobj->type == MT_WAYPOINT) { CONS_Printf("P_MobjCheckWater waypoint call (oh boy...)\n"); } // 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; topheight = P_GetSpecialTopZ(mobj, sectors + rover->secnum, sector); bottomheight = P_GetSpecialBottomZ(mobj, sectors + rover->secnum, sector); if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_SWIMMABLE) || (((rover->fofflags & FOF_BLOCKPLAYER) && mobj->player) || ((rover->fofflags & FOF_BLOCKOTHERS) && !mobj->player))) { if (topheight < top2 && topheight > thingtop) top2 = topheight; if (bottomheight > bot2 && bottomheight < mobj->z) bot2 = bottomheight; continue; } if (mobj->eflags & MFE_VERTICALFLIP) { if (topheight < (thingtop - halfheight) || bottomheight > (thingtop + halfheight)) continue; } else { if (topheight < (mobj->z - halfheight) || bottomheight > (mobj->z + halfheight)) continue; } // Set the watertop and waterbottom mobj->watertop = topheight; mobj->waterbottom = bottomheight; // Just touching the water? if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - height < bottomheight) || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + height > topheight)) mobj->eflags |= MFE_TOUCHWATER; // Actually in the water? if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - (height>>1) > bottomheight) || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + (height>>1) < topheight)) mobj->eflags |= MFE_UNDERWATER; if (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER)) { if (rover->master->frontsector->damagetype == SD_LAVA) mobj->eflags |= MFE_TOUCHLAVA; if (rover->fofflags & FOF_GOOWATER && !(mobj->flags & MF_NOGRAVITY)) mobj->eflags |= MFE_GOOWATER; } } if (mobj->terrain != NULL && K_AffectingTerrainActive()) { if (mobj->terrain->flags & TRF_LIQUID) { // This floor is water. mobj->eflags |= MFE_TOUCHWATER; if (mobj->eflags & MFE_VERTICALFLIP) { mobj->watertop = thingtop + height; mobj->waterbottom = thingtop; } else { mobj->watertop = mobj->z; mobj->waterbottom = mobj->z - height; } } } if (mobj->watertop > top2) mobj->watertop = top2; if (mobj->waterbottom < bot2) mobj->waterbottom = bot2; // Spectators and dead players don't get to do any of the things after this. if (p && (p->spectator || p->playerstate != PST_LIVE)) { return; } if (mobj->flags & MF_APPLYTERRAIN) { K_SpawnWaterTrail(mobj); } // The rest of this code only executes on a water state change. if (!!(mobj->eflags & MFE_UNDERWATER) == wasinwater) return; if (wasinwater && p != NULL) { if (K_GetShieldFromPlayer(p) != KSHIELD_BUBBLE && mobj->player->waterskip == 0 && p->breathTimer > 15*TICRATE) { // Play the gasp sound S_StartSound(mobj, sfx_s3k38); } p->breathTimer = 0; } if ((p) // Players || (mobj->flags & MF_PUSHABLE) // Pushables || ((mobj->info->flags & MF_PUSHABLE) && mobj->fuse) // Previously pushable, might be moving still ) { fixed_t waterZ = INT32_MAX; fixed_t solidZ = INT32_MAX; fixed_t diff = INT32_MAX; fixed_t thingZ = INT32_MAX; boolean splashValid = false; if (mobj->eflags & MFE_VERTICALFLIP) { waterZ = mobj->waterbottom; solidZ = mobj->ceilingz; } else { waterZ = mobj->watertop; solidZ = mobj->floorz; } diff = waterZ - solidZ; if (mobj->eflags & MFE_VERTICALFLIP) { diff = -diff; } // 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); } } } if (waterwasnotset) return; // 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 (diff <= (height >> 1)) { return; } if (!wasgroundpounding && (mobj->eflags & MFE_GOOWATER || wasingoo)) // Decide what happens to your momentum when you enter/leave goopy water. { if (P_MobjFlip(mobj)*mobj->momz < 0) // You are entering the goo? mobj->momz = FixedMul(mobj->momz, FixedDiv(2*FRACUNIT, 5*FRACUNIT)); // kill momentum significantly, to make the goo feel thick. } else if (wasinwater && P_MobjFlip(mobj) * mobj->momz > 0) { // Give the mobj a little out-of-water boost. mobj->momz = FixedMul(mobj->momz, FixedDiv(780*FRACUNIT, 457*FRACUNIT)); } if (mobj->eflags & MFE_VERTICALFLIP) { thingZ = thingtop - (height >> 1); splashValid = (thingZ - mobj->momz <= waterZ); } else { thingZ = mobj->z + (height >> 1); splashValid = (thingZ - mobj->momz >= waterZ); } if (P_MobjFlip(mobj) * mobj->momz <= 0) { if (splashValid == true) { // 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, waterZ - FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype); splish->flags2 |= MF2_OBJECTFLIP; splish->eflags |= MFE_VERTICALFLIP; } else { splish = P_SpawnMobj(mobj->x, mobj->y, waterZ, splishtype); } splish->destscale = mobj->scale; P_SetScale(splish, mobj->scale); // skipping stone! if (p && p->waterskip < 2 && ((p->speed/3 > abs(mobj->momz)) // Going more forward than horizontal, so you can skip across the water. || (p->speed > K_GetKartSpeed(p,false,true)/3 && p->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 min = 6<momx = mobj->momx/2; mobj->momy = mobj->momy/2; mobj->momz = -mobj->momz/2; if (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->momz < FixedMul(min, mobj->scale)) mobj->momz = FixedMul(min, mobj->scale); else if (mobj->eflags & MFE_VERTICALFLIP && mobj->momz > FixedMul(-min, mobj->scale)) mobj->momz = FixedMul(-min, mobj->scale); p->waterskip++; } } } else if (P_MobjFlip(mobj) * mobj->momz > 0) { if (splashValid == true && !(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, waterZ - FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype); splish->flags2 |= MF2_OBJECTFLIP; splish->eflags |= MFE_VERTICALFLIP; } else { splish = P_SpawnMobj(mobj->x, mobj->y, waterZ, splishtype); } splish->destscale = mobj->scale; P_SetScale(splish, mobj->scale); } } } } static void P_SceneryCheckWater(mobj_t *mobj) { sector_t *sector; if (mobj->type == MT_WAYPOINT) { CONS_Printf("P_SceneryCheckWater waypoint call (oh boy...)\n"); } // Default if no water exists. mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT; // see if we are in water, and set some flags for later sector = mobj->subsector->sector; if (sector->ffloors) { ffloor_t *rover; fixed_t topheight, bottomheight; mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER); for (rover = sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_SWIMMABLE) || rover->fofflags & FOF_BLOCKOTHERS) continue; topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y); bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y); if (topheight <= mobj->z || bottomheight > (mobj->z + (mobj->height>>1))) continue; if (mobj->z + mobj->height > topheight) mobj->eflags |= MFE_TOUCHWATER; else mobj->eflags &= ~MFE_TOUCHWATER; // Set the watertop and waterbottom mobj->watertop = topheight; mobj->waterbottom = bottomheight; if (mobj->z + (mobj->height>>1) < topheight) mobj->eflags |= MFE_UNDERWATER; else mobj->eflags &= ~MFE_UNDERWATER; } } else mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER); } static boolean P_CameraCheckHeat(camera_t *thiscam) { sector_t *sector; fixed_t halfheight = thiscam->z + (thiscam->height >> 1); // see if we are in water sector = thiscam->subsector->sector; if (sector->flags & MSF_HEATWAVE) return true; if (sector->ffloors) { ffloor_t *rover; for (rover = sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; if (halfheight >= P_GetFFloorTopZAt(rover, thiscam->x, thiscam->y)) continue; if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y)) continue; if (rover->master->frontsector->flags & MSF_HEATWAVE) return true; } } return false; } static boolean P_CameraCheckWater(camera_t *thiscam) { sector_t *sector; fixed_t halfheight = thiscam->z + (thiscam->height >> 1); // see if we are in water sector = thiscam->subsector->sector; if (sector->ffloors) { ffloor_t *rover; for (rover = sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS) || !(rover->fofflags & FOF_SWIMMABLE) || rover->fofflags & FOF_BLOCKOTHERS) continue; if (halfheight >= P_GetFFloorTopZAt(rover, thiscam->x, thiscam->y)) continue; if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y)) continue; return true; } } return false; } // 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) { const boolean flipcam = ((player->pflags & PF_FLIPCAM) && (player->mo->eflags & MFE_VERTICALFLIP)); UINT8 postimgtype = 0; // This can happen when joining if (thiscam->subsector == NULL || thiscam->subsector->sector == NULL) return; if (encoremode) postimgtype |= POSTIMG_MIRROR; if (flipcam) postimgtype |= POSTIMG_FLIP; #ifdef HWRENDER if (rendermode == render_opengl && splitscreen) { thiscam->postimgflags = postimgtype; return; } #endif 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)) postimgtype |= POSTIMG_WATER; else if (P_CameraCheckHeat(&dummycam)) postimgtype |= POSTIMG_HEAT; } else { // Are we in water? if (P_CameraCheckWater(thiscam)) postimgtype |= POSTIMG_WATER; else if (P_CameraCheckHeat(thiscam)) postimgtype |= POSTIMG_HEAT; } thiscam->postimgflags = postimgtype; } // P_CameraThinker // // Process the mobj-ish required functions of the camera boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled) { // This can happen when joining if (thiscam->subsector == NULL || thiscam->subsector->sector == NULL) return true; P_CalcChasePostImg(player, thiscam); if (thiscam->momx || thiscam->momy) { if (!P_TryCameraMove(thiscam->x + thiscam->momx, thiscam->y + thiscam->momy, thiscam)) // Thanks for the greatly improved camera, Lach -- Sev { mobj_t dummy; dummy.thinker.function = (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_NOCONTEST) { player->karthud[khud_timeovercam] = (2*TICRATE)+1; } if (!resetcalled && !(player->mo->flags & MF_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); 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_PointInSubsectorFast(thiscam->x, thiscam->y); thiscam->floorz = g_tm.floorz; thiscam->ceilingz = g_tm.ceilingz; 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->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_CRUMBLE)) continue; if (mobj->eflags & MFE_VERTICALFLIP) { if (P_GetSpecialBottomZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z + mobj->height) continue; } else { if (P_GetSpecialTopZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z) continue; } EV_StartCrumble(rover->master->frontsector, rover, (rover->fofflags & FOF_FLOATBOB), mobj->player, rover->alpha, !(rover->fofflags & FOF_NORETURN)); } } } static boolean P_MobjTouchesSectorWithWater(mobj_t *mobj) { msecnode_t *node; for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next) { ffloor_t *rover; if (!node->m_sector->ffloors) continue; for (rover = node->m_sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_SWIMMABLE)) continue; return true; } } return false; } // Check for floating water platforms and bounce them static void P_CheckFloatbobPlatforms(mobj_t *mobj) { msecnode_t *node; // Can't land on anything if you're not moving downwards if (P_MobjFlip(mobj)*mobj->momz >= 0) return; if (!P_MobjTouchesSectorWithWater(mobj)) return; for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next) { ffloor_t *rover; if (!node->m_sector->ffloors) continue; for (rover = node->m_sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_FLOATBOB)) continue; if (mobj->eflags & MFE_VERTICALFLIP) { if (abs(*rover->bottomheight - (mobj->z + mobj->height)) > abs(mobj->momz)) continue; } else { if (abs(*rover->topheight - mobj->z) > abs(mobj->momz)) continue; } // Initiate a 'bouncy' elevator function which slowly diminishes. EV_BounceSector(rover->master->frontsector, -mobj->momz, rover->master); } } } static void P_PlayerMobjThinker(mobj_t *mobj) { I_Assert(mobj != NULL); I_Assert(mobj->player != NULL); I_Assert(!P_MobjWasRemoved(mobj)); P_MobjCheckWater(mobj); P_ButteredSlope(mobj); // momentum movement mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN; // Zoom tube if ((mobj->player->carry == CR_ZOOMTUBE && mobj->tracer && !P_MobjWasRemoved(mobj->tracer)) || mobj->player->loop.radius != 0) { 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, NULL); mobj->floorz = g_tm.floorz; mobj->ceilingz = g_tm.ceilingz; mobj->standingslope = NULL; mobj->terrain = NULL; goto animonly; } // Needed for gravity boots P_CheckGravity(mobj, false); if (mobj->momx || mobj->momy) { P_XYMovement(mobj); if (P_MobjWasRemoved(mobj)) return; } else P_TryMove(mobj, mobj->x, mobj->y, true, NULL); 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, NULL); // Need this to pick up objects! if (P_MobjWasRemoved(mobj)) return; } else { mobj->eflags &= ~MFE_JUSTHITFLOOR; } K_UpdateTerrainOverlay(mobj); animonly: P_CyclePlayerMobjState(mobj); } void P_CalculatePrecipFloor(precipmobj_t *mobj) { // recalculate floorz each time const sector_t *mobjsecsubsec; boolean setWater = false; if (mobj && mobj->subsector && mobj->subsector->sector) mobjsecsubsec = mobj->subsector->sector; else return; mobj->precipflags &= ~PCF_INVISIBLE; mobj->floorz = P_GetSectorFloorZAt(mobjsecsubsec, mobj->x, mobj->y); mobj->ceilingz = P_GetSectorCeilingZAt(mobjsecsubsec, mobj->x, mobj->y); if (mobjsecsubsec->ffloors) { ffloor_t *rover; fixed_t height; for (rover = mobjsecsubsec->ffloors; rover; rover = rover->next) { // If it exists, it'll get rained on. if (!(rover->fofflags & FOF_EXISTS)) continue; if (precipprops[curWeather].effects & PRECIPFX_WATERPARTICLES) { if (!(rover->fofflags & FOF_SWIMMABLE)) continue; if (setWater == false) { mobj->ceilingz = P_GetFFloorTopZAt(rover, mobj->x, mobj->y); mobj->floorz = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y); setWater = true; } else { height = P_GetFFloorTopZAt(rover, mobj->x, mobj->y); if (height > mobj->ceilingz) mobj->ceilingz = height; height = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y); if (height < mobj->floorz) mobj->floorz = height; } } else { if (!(rover->fofflags & FOF_BLOCKOTHERS) && !(rover->fofflags & FOF_SWIMMABLE)) continue; height = P_GetFFloorTopZAt(rover, mobj->x, mobj->y); if (height > mobj->floorz) mobj->floorz = height; } } } if ((precipprops[curWeather].effects & PRECIPFX_WATERPARTICLES) && setWater == false) { mobj->precipflags |= PCF_INVISIBLE; } } 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) P_CalculatePrecipFloor(psecnode->m_thing); } // // P_NullPrecipThinker // // Just the identification of a precip thinker. The thinker // should never actually be called! void P_NullPrecipThinker(precipmobj_t *mobj) { (void)mobj; I_Assert("P_NullPrecipThinker should not be called" == 0); } boolean P_PrecipThinker(precipmobj_t *mobj) { boolean flip = (mobj->precipflags & PCF_FLIP); if (mobj->lastThink == leveltime) return true; // already thinked this tick mobj->lastThink = leveltime; R_ResetPrecipitationMobjInterpolationState(mobj); P_CycleStateAnimation((mobj_t *)mobj); if (mobj->state == &states[S_RAINRETURN]) { // Reset to ceiling! if (!P_SetPrecipMobjState(mobj, mobj->info->spawnstate)) return false; mobj->z = (flip) ? (mobj->floorz) : (mobj->ceilingz); mobj->momz = FixedMul(-mobj->info->speed, mapobjectscale); mobj->precipflags &= ~PCF_SPLASH; R_ResetPrecipitationMobjInterpolationState(mobj); } 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); if (!P_SetPrecipMobjState(mobj, S_RAINRETURN)) return false; mobj->frame = frame; return true; } else { if (!P_SetPrecipMobjState(mobj, mobj->state->nextstate)) return true; } } } if (mobj->precipflags & PCF_SPLASH) return true; mobj->z += mobj->momz; // adjust height if ((flip) ? (mobj->z >= mobj->ceilingz) : (mobj->z <= mobj->floorz)) { if ((mobj->info->deathstate == S_NULL) || (mobj->precipflags & PCF_PIT)) // no splashes on sky or bottomless pits { mobj->z = (flip) ? (mobj->floorz) : (mobj->ceilingz); R_ResetPrecipitationMobjInterpolationState(mobj); } else { if (!P_SetPrecipMobjState(mobj, mobj->info->deathstate)) return false; mobj->z = (flip) ? (mobj->ceilingz) : (mobj->floorz); mobj->precipflags |= PCF_SPLASH; R_ResetPrecipitationMobjInterpolationState(mobj); } } return true; } 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, NULL); // 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->renderflags |= RF_DONTDRAW; else mobj->renderflags &= ~RF_DONTDRAW; } if (!mobj->fuse) { if (!LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse))) { mobj->renderflags &= ~RF_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 ? 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); } static void P_ItemCapsulePartThinker(mobj_t *mobj) { if (mobj->fuse > 0) // dead { mobj->fuse--; if (mobj->fuse == 0) { P_RemoveMobj(mobj); return; } mobj->renderflags ^= RF_DONTDRAW; } else // alive { mobj_t *target = mobj->target; fixed_t targetScale, z; if (P_MobjWasRemoved(target)) { P_RemoveMobj(mobj); return; } // match the capsule's scale if (mobj->extravalue1) targetScale = FixedMul(mobj->extravalue1, target->scale); else targetScale = target->scale; if (mobj->scale != targetScale) P_SetScale(mobj, mobj->destscale = targetScale); // find z position if (mobj->flags2 & MF2_CLASSICPUSH) // centered items should not be flipped mobj->renderflags = (mobj->renderflags & ~RF_DONTDRAW) | (target->renderflags & RF_DONTDRAW); else K_GenericExtraFlagsNoZAdjust(mobj, target); if (mobj->eflags & MFE_VERTICALFLIP) z = target->z + target->height - mobj->height - FixedMul(mobj->scale, mobj->movefactor); else z = target->z + FixedMul(mobj->scale, mobj->movefactor); // rotate & move to capsule mobj->angle += mobj->movedir; if (mobj->flags2 & MF2_CLASSICPUSH) // centered P_MoveOrigin(mobj, target->x, target->y, z); else P_MoveOrigin(mobj, target->x + P_ReturnThrustX(mobj, mobj->angle + ANGLE_90, mobj->radius), target->y + P_ReturnThrustY(mobj, mobj->angle + ANGLE_90, mobj->radius), z); } } static void P_RefreshItemCapsuleParts(mobj_t *mobj) { UINT8 numNumbers = 0; INT32 count = 0; INT32 itemType = mobj->threshold; mobj_t *part; UINT32 newRenderFlags = 0; if (itemType < 1 || itemType >= numkartitems) itemType = MAXKARTITEMS; part = mobj; while (!P_MobjWasRemoved(part->hnext)) { part = part->hnext; part->renderflags = (part->renderflags & ~RF_BRIGHTMASK) | newRenderFlags; } // update inside item frame part = mobj->tracer; if (P_MobjWasRemoved(part)) return; part->threshold = mobj->threshold; part->movecount = mobj->movecount; K_UpdateMobjItemOverlay(part, itemType, mobj->movecount); // update number frame if (mobj->movecount >= K_GetItemNumberDisplayMin(itemType, false)) count = mobj->movecount; while (count > 0) { if (part->tracer == NULL || P_MobjWasRemoved(part->tracer)) { P_SetTarget(&part->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY)); P_SetTarget(&part->tracer->target, part); P_SetMobjState(part->tracer, S_INVISIBLE); part->tracer->spriteyoffset = 10*FRACUNIT; part->tracer->spritexoffset = 13*numNumbers*FRACUNIT; part->tracer->threshold = OV_DONTSCREENOFFSET; } part = part->tracer; part->sprite = SPR_ITMN; part->frame = FF_FULLBRIGHT|(count % 10); count /= 10; numNumbers++; } // delete any extra overlays (I guess in case the number changes?) if (part->tracer) { P_RemoveMobj(part->tracer); P_SetTarget(&part->tracer, NULL); } } #define ROTATIONSPEED (2*ANG2) static void P_SpawnItemCapsuleParts(mobj_t *mobj) { mobj_t *part; // inside item part = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_ITEMCAPSULE_PART); P_SetTarget(&part->target, mobj); P_SetMobjState(part, S_ITEMICON); part->movedir = ROTATIONSPEED; // rotation speed part->extravalue1 = 175*FRACUNIT/100; // relative scale part->flags2 |= MF2_CLASSICPUSH; // classicpush = centered horizontally part->flags2 &= ~MF2_OBJECTFLIP; // centered item should not be flipped part->eflags &= ~MFE_VERTICALFLIP; P_SetTarget(&mobj->tracer, part); // pointer to this item, so we can modify its sprite/frame P_RefreshItemCapsuleParts(mobj); } #undef ROTATIONSPEED // // 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; INT32 i, pcount = 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; // No players? Don't bother. for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; pcount++; } if (!pcount) return false; 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->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].mo || players[c].bot) continue; if (players[c].mo->health <= 0) continue; // dead if ((gametyperules & GTR_BUMPERS) && players[c].bumper <= 0) continue; // other dead playersinthegame[stop] = &players[c]; stop++; } } if (!stop) return false; P_SetTarget(&actor->target, playersinthegame[P_RandomKey(stop)]->mo); return true; } // AI for a generic boss. static void P_GenericBossThinker(mobj_t *mobj) { if (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1) mobj->flags2 &= ~MF2_FRET; if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)) { if (mobj->health <= 0) return; // look for a new target if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound) S_StartSound(mobj, mobj->info->seesound); return; } // Don't call A_ functions here, let the SOC do the AI! if (mobj->state == &states[mobj->info->meleestate] || (mobj->state == &states[mobj->info->missilestate] && mobj->health > mobj->info->damage)) { mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); } } // AI for the first boss. static void P_Boss1Thinker(mobj_t *mobj) { if (mobj->flags2 & MF2_FRET && (statenum_t)(mobj->state-states) == mobj->info->spawnstate) { mobj->flags2 &= ~(MF2_FRET|MF2_SKULLFLY); mobj->momx = mobj->momy = mobj->momz = 0; } if (!mobj->tracer) { var1 = 0; A_BossJetFume(mobj); } if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)) { if (mobj->target && mobj->target->health && mobj->target->type == MT_EGGMOBILE_TARGET) // Oh, we're just firing our laser. return; // It's okay, then. if (mobj->health <= 0) { if (P_BossTargetPlayer(mobj, false) && mobj->info->mass) // Bid farewell! S_StartSound(mobj, mobj->info->mass); return; } // look for a new target if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound) S_StartSound(mobj, mobj->info->seesound); return; } if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0 && mobj->flags & MF_FLOAT && !(mobj->flags2 & MF2_SKULLFLY)) mobj->momz = FixedMul(mobj->momz,7*FRACUNIT/8); if (mobj->state == &states[mobj->info->meleestate] || (mobj->state == &states[mobj->info->missilestate] && mobj->health > mobj->info->damage)) { mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); } } // AI for the second boss. // No, it does NOT convert "Boss" to a "Thinker". =P static void P_Boss2Thinker(mobj_t *mobj) { if (mobj->movecount) mobj->movecount--; if (!mobj->movecount) mobj->flags2 &= ~MF2_FRET; if (!mobj->tracer) { var1 = 0; A_BossJetFume(mobj); } if (mobj->health <= mobj->info->damage && (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))) { if (mobj->health <= 0) { // look for a new target if (P_BossTargetPlayer(mobj, false) && mobj->info->mass) // Bid farewell! S_StartSound(mobj, mobj->info->mass); return; } // look for a new target if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound) S_StartSound(mobj, mobj->info->seesound); return; } if (mobj->state == &states[mobj->info->spawnstate] && mobj->health > mobj->info->damage) A_Boss2Chase(mobj); else if (mobj->health > 0 && mobj->state != &states[mobj->info->painstate] && mobj->state != &states[mobjinfo[mobj->info->missilestate].raisestate]) { mobj->flags &= ~MF_NOGRAVITY; A_Boss2Pogo(mobj); P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); } } // AI for the third boss. // // Notes for reminders: // movedir = move 2x fast? // movecount = fire missiles? // reactiontime = shock the water? // threshold = next waypoint # // extravalue1 = previous time shock sound was used // static void P_Boss3Thinker(mobj_t *mobj) { if (mobj->state == &states[mobj->info->spawnstate]) mobj->flags2 &= ~MF2_FRET; if (mobj->flags2 & MF2_FRET) mobj->movedir = 1; if (!mobj->tracer) { var1 = 1; A_BossJetFume(mobj); } if (mobj->health <= 0) { mobj->movecount = 0; mobj->reactiontime = 0; if (mobj->state < &states[mobj->info->xdeathstate]) return; if (mobj->threshold == -1) { mobj->momz = mobj->info->speed; return; } } if (mobj->reactiontime) // Shock mode { UINT32 i; if (mobj->state != &states[mobj->info->spawnstate]) P_SetMobjState(mobj, mobj->info->spawnstate); mobj->reactiontime--; if (!mobj->reactiontime) { ffloor_t *rover; // Shock the water for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!players[i].mo) continue; if (players[i].mo->health <= 0) continue; if (players[i].mo->eflags & MFE_UNDERWATER) P_DamageMobj(players[i].mo, mobj, mobj, 1, DMG_VOLTAGE); } // Make the water flash for (i = 0; i < numsectors; i++) { if (!sectors[i].ffloors) continue; for (rover = sectors[i].ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_SWIMMABLE)) continue; P_SpawnLightningFlash(rover->master->frontsector); break; } } if ((UINT32)mobj->extravalue1 + TICRATE*2 < leveltime) { mobj->extravalue1 = (INT32)leveltime; S_StartSound(0, sfx_buzz1); } // If in the center, check to make sure // none of the players are in the water for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!players[i].mo || players[i].bot) continue; if (players[i].mo->health <= 0) continue; if (players[i].mo->eflags & MFE_UNDERWATER) { // Stay put mobj->reactiontime = 2*TICRATE; return; } } } if (!mobj->reactiontime && mobj->health <= mobj->info->damage) { // Spawn pinch dummies from the center when we're leaving it. thinker_t *th; mobj_t *mo2; mobj_t *dummy; SINT8 way = mobj->threshold - 1; // 0 through 4. SINT8 way2; i = 0; // reset i to 0 so we can check how many clones we've removed // scan the thinkers to make sure all the old pinch dummies are gone before making new ones // this can happen if the boss was hurt earlier than expected for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function == (actionf_p1)P_RemoveThinkerDelayed) continue; mo2 = (mobj_t *)th; if (mo2->type == (mobjtype_t)mobj->info->mass && mo2->tracer == mobj) { P_RemoveMobj(mo2); i++; } if (i == 2) // we've already removed 2 of these, let's stop now break; } way = (way + P_RandomRange(1,3)) % 5; // dummy 1 at one of the first three options after eggmobile dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass); dummy->angle = mobj->angle; dummy->threshold = way + 1; P_SetTarget(&dummy->tracer, mobj); do way2 = (way + P_RandomRange(1,3)) % 5; // dummy 2 has to be careful, while (way2 == mobj->threshold - 1); // to make sure it doesn't try to go the Eggman Way if dummy 1 rolled high. dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass); dummy->angle = mobj->angle; dummy->threshold = way2 + 1; P_SetTarget(&dummy->tracer, mobj); CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", mobj->threshold, way + 1, dummy->threshold); P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); } } else if (mobj->movecount) // Firing mode { UINT32 i; // look for a new target P_BossTargetPlayer(mobj, false); if (!mobj->target || !mobj->target->player) return; // Are there any players underwater? If so, shock them! for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!players[i].mo || players[i].bot) continue; if (players[i].mo->health <= 0) continue; if (players[i].mo->eflags & MFE_UNDERWATER) { mobj->movecount = 0; P_SetMobjState(mobj, mobj->info->spawnstate); return; } } // Always face your target. A_FaceTarget(mobj); // Check if the attack animation is running. If not, play it. if (mobj->state < &states[mobj->info->missilestate] || mobj->state > &states[mobj->info->raisestate]) { if (mobj->health <= mobj->info->damage) // pinch phase mobj->movecount--; // limited number of shots before diving again if (mobj->movecount) P_SetMobjState(mobj, mobj->info->missilestate); } } else if (mobj->threshold >= 0) // Traveling mode { thinker_t *th; mobj_t *mo2; fixed_t dist, dist2; fixed_t speed; P_SetTarget(&mobj->target, NULL); if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0 && !(mobj->flags2 & MF2_FRET)) P_SetMobjState(mobj, mobj->info->spawnstate); // scan the thinkers // to find a point that matches // the number for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function == (actionf_p1)P_RemoveThinkerDelayed) continue; mo2 = (mobj_t *)th; if (!mo2) continue; if (mo2->type != MT_BOSS3WAYPOINT) continue; if (!mo2->spawnpoint) continue; if (mo2->spawnpoint->angle != mobj->threshold) continue; P_SetTarget(&mobj->target, mo2); break; } if (!mobj->target) // Should NEVER happen { CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d\n", mobj->threshold); return; } dist = P_AproxDistance(P_AproxDistance(mobj->target->x - mobj->x, mobj->target->y - mobj->y), mobj->target->z - mobj->z); if (dist < 1) dist = 1; if ((mobj->movedir) || (mobj->health <= mobj->info->damage)) speed = mobj->info->speed * 2; else speed = mobj->info->speed; mobj->momx = FixedMul(FixedDiv(mobj->target->x - mobj->x, dist), speed); mobj->momy = FixedMul(FixedDiv(mobj->target->y - mobj->y, dist), speed); mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, dist), speed); if (mobj->momx != 0 || mobj->momy != 0) mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy); dist2 = P_AproxDistance(P_AproxDistance(mobj->target->x - (mobj->x + mobj->momx), mobj->target->y - (mobj->y + mobj->momy)), mobj->target->z - (mobj->z + mobj->momz)); if (dist2 < 1) dist2 = 1; if ((dist >> FRACBITS) <= (dist2 >> FRACBITS)) { // If further away, set XYZ of mobj to waypoint location P_UnsetThingPosition(mobj); mobj->x = mobj->target->x; mobj->y = mobj->target->y; mobj->z = mobj->target->z; mobj->momx = mobj->momy = mobj->momz = 0; P_SetThingPosition(mobj); if (mobj->threshold == 0) { mobj->reactiontime = 1; // Bzzt! Shock the water! mobj->movedir = 0; if (mobj->health <= 0) { mobj->flags |= MF_NOGRAVITY|MF_NOCLIP; mobj->flags |= MF_NOCLIPHEIGHT; mobj->threshold = -1; return; } } // Set to next waypoint in sequence if (mobj->target->spawnpoint) { // From the center point, choose one of the five paths if (mobj->target->spawnpoint->angle == 0) mobj->threshold = P_RandomRange(1,5); else mobj->threshold = mobj->target->spawnpoint->extrainfo; // If the deaf flag is set, go into firing mode if (mobj->target->spawnpoint->options & MTF_AMBUSH) mobj->movecount = mobj->health+1; } else // This should never happen, as well CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 waypoint has no spawnpoint associated with it.\n"); } } } // Move Boss4's sectors by delta. static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta) { INT32 snum; sector_t *sector; boolean gotcage = false; if (!mobj->spawnpoint) return false; TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum) { sector = §ors[snum]; sector->floorheight += delta; sector->ceilingheight += delta; P_CheckSector(sector, true); gotcage = true; } return gotcage; } // Move Boss4's arms to angle static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz) { INT32 s; mobj_t *base = mobj, *seg; fixed_t dist, bz = mobj->watertop+(16<tracer)) { for (seg = base, dist = 172*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 124*FRACUNIT, --s) P_MoveOrigin(seg, mobj->x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), bz + FixedMul(fz, FixedDiv(s<watertop+(16<tracer)) { for (seg = base, dist = 112*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 132*FRACUNIT, --s) { seg->z = bz + FixedMul(fz, FixedDiv(s<x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), true, NULL); } angle += ANGLE_MAX/3; } } // Destroy cage FOFs. static void P_Boss4DestroyCage(mobj_t *mobj) { INT32 snum; size_t a; sector_t *sector, *rsec; ffloor_t *rover; if (!mobj->spawnpoint) return; TAG_ITER_SECTORS(mobj->spawnpoint->args[4], snum) { sector = §ors[snum]; // Destroy the FOFs. for (a = 0; a < sector->numattached; a++) { rsec = §ors[sector->attached[a]]; for (rover = rsec->ffloors; rover; rover = rover->next) if (rover->fofflags & FOF_EXISTS && rover->secnum == (size_t)snum) { if (rover->fofflags & FOF_RENDERALL) // checking for FF_RENDERANY. EV_CrumbleChain(rsec, rover); // This FOF is visible to some extent? Crumble it. else // Completely invisible FOF { // no longer exists (can't collide with again) rover->fofflags &= ~FOF_EXISTS; sector->moved = true; rsec->moved = true; } } } } } // Destroy Boss4's arms static void P_Boss4PopSpikeballs(mobj_t *mobj) { mobj_t *base = mobj->tracer, *seg, *next; P_SetTarget(&mobj->tracer, NULL); while(base) { next = base->tracer; P_SetTarget(&base->tracer, NULL); for (seg = base; seg; seg = seg->hnext) { if (seg->health) P_KillMobj(seg, NULL, NULL, DMG_INSTAKILL); } base = next; } } // // AI for the fourth boss. // static void P_Boss4Thinker(mobj_t *mobj) { if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate) { if (mobj->health > mobj->info->damage || mobj->movedir == 4) mobj->flags2 &= ~MF2_FRET; mobj->reactiontime = 0; // Drop the cage immediately. } // Oh no, we dead? D: if (!mobj->health) { if (mobj->tracer) // need to clean up! { P_Boss4DestroyCage(mobj); // Just in case pinch phase was skipped. P_Boss4PopSpikeballs(mobj); } return; } // movedir == battle stage: // 0: initialization // 1: phase 1 forward // 2: phase 1 reverse // 3: pinch rise // 4: pinch phase switch(mobj->movedir) { // WELCOME to your DOOM! case 0: { // For this stage only: // movecount == cage height // threshold == cage momz if (mobj->movecount == 0) // Initialize stage! { fixed_t z; INT32 i, arm; mobj_t *seg, *base = mobj; // First frame init, spawn all the things. mobj->watertop = mobj->z; z = mobj->z + mobj->height/2 - mobjinfo[MT_EGGMOBILE4_MACE].height/2; for (arm = 0; arm <3 ; arm++) { seg = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE); P_SetTarget(&base->tracer, seg); base = seg; P_SetTarget(&seg->target, mobj); for (i = 0; i < 9; i++) { P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE)); P_SetTarget(&seg->hnext->hprev, seg); seg = seg->hnext; } } // Move the cage up to the sky. mobj->movecount = 800*FRACUNIT; if (!P_Boss4MoveCage(mobj, mobj->movecount)) { mobj->movecount = 0; mobj->threshold = 3*TICRATE; mobj->extravalue1 = 1; mobj->movedir++; // We don't have a cage, just continue. } else P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount); } else // Cage slams down over Eggman's head! { fixed_t oldz = mobj->movecount; mobj->threshold -= 5*FRACUNIT; mobj->movecount += mobj->threshold; if (mobj->movecount < 0) mobj->movecount = 0; P_Boss4MoveCage(mobj, mobj->movecount - oldz); P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount); if (mobj->movecount == 0) { mobj->threshold = 3*TICRATE; mobj->extravalue1 = 1; P_LinedefExecute(LE_BOSS4DROP, mobj, NULL); mobj->movedir++; // Initialization complete, next phase! } } return; } // Normal operation case 1: case 2: break; // Pinch phase init! case 3: { fixed_t z; if (mobj->z < mobj->watertop+(512<momz = 8*FRACUNIT; else { mobj->momz = 0; mobj->movedir++; } mobj->movecount += 400<<(FRACBITS>>1); mobj->movecount %= 360*FRACUNIT; z = mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2; if (z < 0) // We haven't risen high enough to pull the spikeballs along yet P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0); // So don't pull the spikeballs along yet. else P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), z); return; } // Pinch phase! case 4: { if (mobj->z < (mobj->watertop + ((512+128*(mobj->info->damage-mobj->health))<momz = 8*FRACUNIT; else mobj->momz = 0; mobj->movecount += (800+800*(mobj->info->damage-mobj->health))<<(FRACBITS>>1); mobj->movecount %= 360*FRACUNIT; P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2); if (!mobj->target || !mobj->target->health) P_SupermanLook4Players(mobj); A_FaceTarget(mobj); return; } default: // ????? return; } // Haahahahahaaa, and let the FUN.. BEGIN! // movedir == arms direction // movecount == arms angle // threshold == countdown to next attack // reactiontime == cage raise, speed burst // movefactor == cage z // friction == turns until helm lift // Raise the cage! if (mobj->reactiontime == 1) { fixed_t oldz = mobj->movefactor; mobj->movefactor += 8*FRACUNIT; if (mobj->movefactor > 128*FRACUNIT) mobj->movefactor = 128*FRACUNIT; P_Boss4MoveCage(mobj, mobj->movefactor - oldz); } // Drop the cage! else if (mobj->movefactor) { fixed_t oldz = mobj->movefactor; mobj->movefactor -= 4*FRACUNIT; if (mobj->movefactor < 0) mobj->movefactor = 0; P_Boss4MoveCage(mobj, mobj->movefactor - oldz); if (!mobj->movefactor) { if (mobj->health <= mobj->info->damage) { // Proceed to pinch phase! P_Boss4DestroyCage(mobj); mobj->movedir = 3; P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); return; } P_LinedefExecute(LE_BOSS4DROP, mobj, NULL); } } { fixed_t movespeed = 170<<(FRACBITS>>1); if (mobj->reactiontime == 2) movespeed *= 3; if (mobj->movedir == 2) mobj->movecount -= movespeed; else mobj->movecount += movespeed; } mobj->movecount %= 360*FRACUNIT; P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->movefactor); // Check for attacks, always tick the timer even while animating!! if (!(mobj->flags2 & MF2_FRET) // but pause for pain so we don't interrupt pinch phase, eep! && mobj->threshold-- == 0) { // 5 -> 2.5 second timer mobj->threshold = 5*TICRATE-(TICRATE/2)*(mobj->info->spawnhealth-mobj->health); if (mobj->threshold < 1) mobj->threshold = 1; if (mobj->extravalue1-- == 0) { P_SetMobjState(mobj, mobj->info->raisestate); mobj->extravalue1 = 3; } else { if (mobj->reactiontime == 1) // Cage is raised? mobj->reactiontime = 0; // Drop it! switch(P_RandomKey(10)) { // Telegraph Right (Speed Up!!) case 1: case 3: case 4: case 5: case 6: P_SetMobjState(mobj, mobj->info->missilestate); break; // Telegraph Left (Reverse Direction) default: P_SetMobjState(mobj, mobj->info->meleestate); break; } } } // Leave if animating. if ((statenum_t)(mobj->state-states) != mobj->info->spawnstate) return; // Map allows us to get killed despite cage being down? if (mobj->health <= mobj->info->damage) { // Proceed to pinch phase! P_Boss4DestroyCage(mobj); // spawn jet's flame now you're flying upwards // tracer is already used, so if this ever gets reached again we've got problems var1 = 3; A_BossJetFume(mobj); mobj->movedir = 3; P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); return; } mobj->reactiontime = 0; // Drop the cage if it hasn't been dropped already. if (!mobj->target || !mobj->target->health) P_SupermanLook4Players(mobj); A_FaceTarget(mobj); } // // AI for Black Eggman // Note: You CANNOT have more than ONE Black Eggman // in a level! Just don't try it! // static void P_Boss7Thinker(mobj_t *mobj) { if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)) { // look for a new target if (P_BossTargetPlayer(mobj, false)) return; // got a new target P_SetMobjStateNF(mobj, mobj->info->spawnstate); return; } if (mobj->health >= mobj->info->spawnhealth && (leveltime & 14) == 0) { mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height, MT_SMOKE); smoke->destscale = mobj->destscale; P_SetScale(smoke, smoke->destscale); smoke->momz = FixedMul(FRACUNIT, smoke->scale); } if (mobj->state == &states[S_BLACKEGG_STND] && mobj->tics == mobj->state->tics) { mobj->reactiontime += P_RandomByte(); if (mobj->health <= mobj->info->damage) mobj->reactiontime /= 4; } else if (mobj->state == &states[S_BLACKEGG_DIE4] && mobj->tics == mobj->state->tics) A_BossDeath(mobj); else if (mobj->state >= &states[S_BLACKEGG_WALK1] && mobj->state <= &states[S_BLACKEGG_WALK6]) A_Boss7Chase(mobj); else if (mobj->state == &states[S_BLACKEGG_PAIN1] && mobj->tics == mobj->state->tics) { if (mobj->health > 0) mobj->health--; S_StartSound(0, (mobj->health) ? sfx_behurt : sfx_bedie2); mobj->reactiontime /= 3; if (mobj->health <= 0) { INT32 i; P_KillMobj(mobj, NULL, NULL, DMG_INSTAKILL); // It was a team effort for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i]) continue; P_AddPlayerScore(&players[i], 1000); } } } else if (mobj->state == &states[S_BLACKEGG_PAIN35] && mobj->tics == 1) { if (mobj->health == mobj->info->damage) { // Begin platform destruction mobj->flags2 |= MF2_FRET; P_SetMobjState(mobj, mobj->info->raisestate); P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); } } else if (mobj->state == &states[S_BLACKEGG_HITFACE4] && mobj->tics == mobj->state->tics) { // This is where Black Eggman hits his face. // If a player is on top of him, the player gets hurt. // But, if the player has managed to escape, // Black Eggman gets hurt! INT32 i; mobj->state->nextstate = mobj->info->painstate; // Reset S_StartSound(0, sfx_bedeen); for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!players[i].mo) continue; if (players[i].mo->health <= 0) continue; if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > (mobj->radius + players[i].mo->radius)) continue; if (players[i].mo->z > mobj->z + mobj->height - FRACUNIT && players[i].mo->z < mobj->z + mobj->height + 128*FRACUNIT) // You can't be in the vicinity, either... { // Punch him! P_DamageMobj(players[i].mo, mobj, mobj, 1, DMG_NORMAL); mobj->state->nextstate = mobj->info->spawnstate; // Laugh S_StartSound(0, sfx_bewar1 + P_RandomKey(4)); } } } else if (mobj->state == &states[S_BLACKEGG_GOOP]) { // Lob cannon balls if (mobj->movecount-- <= 0 || !mobj->target) { P_SetMobjState(mobj, mobj->info->spawnstate); return; } if ((leveltime & 15) == 0) { var1 = MT_CANNONBALL; var2 = 2*TICRATE + (80<<16); A_LobShot(mobj); S_StartSound(0, sfx_begoop); } } else if (mobj->state == &states[S_BLACKEGG_SHOOT2]) { // Chaingun goop mobj_t *missile; if (mobj->movecount-- <= 0 || !mobj->target) { P_SetMobjState(mobj, mobj->info->spawnstate); return; } A_FaceTarget(mobj); missile = P_SpawnXYZMissile(mobj, mobj->target, MT_BLACKEGGMAN_GOOPFIRE, mobj->x + P_ReturnThrustX(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)), mobj->y + P_ReturnThrustY(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)), mobj->z + FixedDiv(mobj->height, 3*FRACUNIT/2)); S_StopSound(missile); if (leveltime & 1) S_StartSound(0, sfx_beshot); } else if (mobj->state == &states[S_BLACKEGG_JUMP1] && mobj->tics == 1) { mobj_t *hitspot = NULL, *mo2; angle_t an; fixed_t dist, closestdist; fixed_t vertical, horizontal; fixed_t airtime = 5*TICRATE; INT32 waypointNum = 0; thinker_t *th; INT32 i; boolean foundgoop = false; INT32 closestNum; // Looks for players in goop. If you find one, try to jump on him. for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!players[i].mo) continue; if (players[i].mo->health <= 0) continue; if (players[i].powers[pw_ingoop]) { closestNum = -1; closestdist = 16384*FRACUNIT; // Just in case... // Find waypoint he is closest to for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function == (actionf_p1)P_RemoveThinkerDelayed) continue; mo2 = (mobj_t *)th; if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint) { dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y); if (closestNum == -1 || dist < closestdist) { closestNum = (mo2->spawnpoint->options & 7); closestdist = dist; foundgoop = true; } } } waypointNum = closestNum; break; } } if (!foundgoop) { if (mobj->z > 1056*FRACUNIT) waypointNum = 0; else waypointNum = 1 + P_RandomKey(4); } // Don't jump to the center when health is low. // Force the player to beat you with missiles. if (mobj->health <= mobj->info->damage && waypointNum == 0) waypointNum = 1 + P_RandomKey(4); if (mobj->tracer && mobj->tracer->type == MT_BOSS3WAYPOINT && mobj->tracer->spawnpoint && (mobj->tracer->spawnpoint->options & 7) == waypointNum) { if (P_RandomChance(FRACUNIT/2)) waypointNum++; else waypointNum--; waypointNum %= 5; if (waypointNum < 0) waypointNum = 0; } if (waypointNum == 0 && mobj->health <= mobj->info->damage) waypointNum = 1 + (P_RandomFixed() & 1); // scan the thinkers to find // the waypoint to use for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function == (actionf_p1)P_RemoveThinkerDelayed) continue; mo2 = (mobj_t *)th; if (mo2->type == MT_BOSS3WAYPOINT && mo2->spawnpoint && (mo2->spawnpoint->options & 7) == waypointNum) { hitspot = mo2; break; } } if (hitspot == NULL) { CONS_Debug(DBG_GAMELOGIC, "BlackEggman unable to find waypoint #%d!\n", waypointNum); P_SetMobjState(mobj, mobj->info->spawnstate); return; } P_SetTarget(&mobj->tracer, hitspot); mobj->angle = R_PointToAngle2(mobj->x, mobj->y, hitspot->x, hitspot->y); an = mobj->angle; an >>= ANGLETOFINESHIFT; dist = P_AproxDistance(hitspot->x - mobj->x, hitspot->y - mobj->y); horizontal = dist / airtime; vertical = (gravity*airtime)/2; mobj->momx = FixedMul(horizontal, FINECOSINE(an)); mobj->momy = FixedMul(horizontal, FINESINE(an)); mobj->momz = vertical; // mobj->momz = 10*FRACUNIT; } else if (mobj->state == &states[S_BLACKEGG_JUMP2] && mobj->z <= mobj->floorz) { // BANG! onto the ground INT32 i,j; fixed_t ns; fixed_t x,y,z; mobj_t *mo2; S_StartSound(0, sfx_befall); z = mobj->floorz; for (j = 0; j < 2; j++) { for (i = 0; i < 32; i++) { const angle_t fa = (i*FINEANGLES/16) & FINEMASK; ns = 64 * FRACUNIT; x = mobj->x + FixedMul(FINESINE(fa),ns); y = mobj->y + FixedMul(FINECOSINE(fa),ns); mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE); ns = 16 * FRACUNIT; mo2->momx = FixedMul(FINESINE(fa),ns); mo2->momy = FixedMul(FINECOSINE(fa),ns); } z -= 32*FRACUNIT; } // Hurt player?? for (i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator) continue; if (!players[i].mo) continue; if (players[i].mo->health <= 0) continue; if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > mobj->radius*4) continue; if (players[i].mo->z > mobj->z + 128*FRACUNIT) continue; if (players[i].mo->z < mobj->z - 64*FRACUNIT) continue; P_DamageMobj(players[i].mo, mobj, mobj, 1, DMG_NORMAL); // Laugh S_StartSound(0, sfx_bewar1 + P_RandomKey(4)); } P_SetMobjState(mobj, mobj->info->spawnstate); } else if (mobj->state == &states[mobj->info->deathstate] && mobj->tics == mobj->state->tics) S_StartSound(0, sfx_bedie1 + (P_RandomFixed() & 1)); } // Metal Sonic battle boss // You CAN put multiple Metal Sonics in a single map // because I am a totally competent programmer who can do shit right. static void P_Boss9Thinker(mobj_t *mobj) { TryMoveResult_t result = {0}; if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate) mobj->flags2 &= ~MF2_FRET; if (!mobj->tracer) { thinker_t *th; mobj_t *mo2; mobj_t *last=NULL; // Initialize the boss, spawn jet fumes, etc. mobj->threshold = 0; mobj->reactiontime = 0; mobj->watertop = mobj->floorz + 32*FRACUNIT; var1 = 2; A_BossJetFume(mobj); // Run through the thinkers ONCE and find all of the MT_BOSS9GATHERPOINT in the map. // Build a hoop linked list of 'em! for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function == (actionf_p1)P_RemoveThinkerDelayed) continue; mo2 = (mobj_t *)th; if (mo2->type == MT_BOSS9GATHERPOINT) { if (last) P_SetTarget(&last->hnext, mo2); else P_SetTarget(&mobj->hnext, mo2); P_SetTarget(&mo2->hprev, last); last = mo2; } } } if (mobj->health <= 0) return; if ((!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))) { P_BossTargetPlayer(mobj, false); if (mobj->target && !P_IsObjectOnGround(mobj->target)) P_SetTarget(&mobj->target, NULL); // Wait for them to hit the ground first if (!mobj->target) // Still no target, aww. { // Reset the boss. P_SetMobjState(mobj, mobj->info->spawnstate); mobj->fuse = 0; mobj->momx = FixedDiv(mobj->momx, FRACUNIT + (FRACUNIT>>2)); mobj->momy = FixedDiv(mobj->momy, FRACUNIT + (FRACUNIT>>2)); mobj->momz = FixedDiv(mobj->momz, FRACUNIT + (FRACUNIT>>2)); return; } else if (!mobj->fuse) mobj->fuse = 10*TICRATE; } // AI goes here. { boolean danger = true; angle_t angle; if (mobj->threshold) mobj->momz = (mobj->watertop-mobj->z)/16; // Float to your desired position FASTER else mobj->momz = (mobj->watertop-mobj->z)/40; // Float to your desired position if (mobj->movecount == 2) { mobj_t *spawner; fixed_t dist = 0; angle = ANGLE_135*leveltime; // Alter your energy bubble's size/position if (mobj->health > 3) { mobj->tracer->destscale = FRACUNIT + (4*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2); P_SetScale(mobj->tracer, mobj->tracer->destscale); P_SetOrigin(mobj->tracer, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->tracer->height/2); mobj->tracer->momx = mobj->momx; mobj->tracer->momy = mobj->momy; mobj->tracer->momz = mobj->momz; } // Face your target P_BossTargetPlayer(mobj, true); angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle angle = (angle-mobj->angle); // relative angle if (angle < ANGLE_180) mobj->angle += angle/8; else mobj->angle -= InvAngle(angle)/8; // Spawn energy particles for (spawner = mobj->hnext; spawner; spawner = spawner->hnext) { dist = P_AproxDistance(spawner->x - mobj->x, spawner->y - mobj->y); if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1) break; } if (spawner && dist) { mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER); missile->momz = FixedDiv(missile->momz, 7*FRACUNIT/4); missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy)); if (missile->fuse > mobj->fuse) P_RemoveMobj(missile); } } // Pre-threshold reactiontime stuff for attack phases if (mobj->reactiontime && mobj->movecount == 3) { if (mobj->movedir == 0 || mobj->movedir == 2) // Pausing between bounces in the pinball phase { if (mobj->target->player->powers[pw_tailsfly]) // Trying to escape, eh? mobj->watertop = mobj->target->z + mobj->target->momz*6; // Readjust your aim. >:3 else mobj->watertop = mobj->target->floorz + 16*FRACUNIT; if (!(mobj->threshold%4)) mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4); } // Pausing between energy ball shots mobj->reactiontime--; return; } // threshold is used for attacks/maneuvers. if (mobj->threshold) { fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<info->spawnhealth<movecount == 3 && mobj->movedir == 1) { if (!(mobj->threshold&1)) { mobj_t *missile; if (mobj->info->seesound) S_StartSound(mobj, mobj->info->seesound); P_SetMobjState(mobj, mobj->info->missilestate); if (mobj->extravalue1 == 3) mobj->reactiontime = TICRATE/16; else mobj->reactiontime = TICRATE/8; A_FaceTarget(mobj); missile = P_SpawnMissile(mobj, mobj->target, mobj->info->speed); if (mobj->extravalue1 == 2 || mobj->extravalue1 == 3) { missile->destscale = FRACUNIT>>1; P_SetScale(missile, missile->destscale); } missile->fuse = 3*TICRATE; missile->z -= missile->height/2; if (mobj->extravalue1 == 2) { int i; mobj_t *spread; missile->flags |= MF_MISSILE; for (i = 0; i < 5; i++) { if (i == 2) continue; spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type); spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2); P_InstaThrust(spread,spread->angle,spread->info->speed); spread->momz = missile->momz; spread->destscale = FRACUNIT>>1; P_SetScale(spread, spread->destscale); spread->fuse = 3*TICRATE; } missile->flags &= ~MF_MISSILE; } } else { P_SetMobjState(mobj, mobj->state->nextstate); if (mobj->extravalue1 == 3) mobj->reactiontime = TICRATE/8; else mobj->reactiontime = TICRATE/4; } mobj->threshold--; return; } P_SpawnGhostMobj(mobj); // Pinball attack! if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2)) { if ((statenum_t)(mobj->state-states) != mobj->info->seestate) P_SetMobjState(mobj, mobj->info->seestate); if (mobj->movedir == 0) // mobj health == 1 P_InstaThrust(mobj, mobj->angle, 38*FRACUNIT); else if (mobj->health == 3) P_InstaThrust(mobj, mobj->angle, 22*FRACUNIT); else // mobj health == 2 P_InstaThrust(mobj, mobj->angle, 30*FRACUNIT); if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true, &result)) // Hit a wall? Find a direction to bounce { mobj->threshold--; if (mobj->threshold) { P_SetMobjState(mobj, mobj->state->nextstate); if (mobj->info->mass) S_StartSound(mobj, mobj->info->mass); if (!(mobj->threshold%4)) // We've decided to lock onto the player this bounce. { mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4); mobj->reactiontime = TICRATE; // targetting time } else // No homing, just use P_BounceMove { P_BounceMove(mobj, &result); mobj->angle = R_PointToAngle2(0,0,mobj->momx,mobj->momy); mobj->reactiontime = TICRATE/4; // just a pause before you bounce away } mobj->momx = mobj->momy = 0; } } return; } // Vector form dodge! mobj->angle += mobj->movedir; P_InstaThrust(mobj, mobj->angle, -speed); while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true, &result) && tries++ < 16) { mobj->angle += mobj->movedir; P_InstaThrust(mobj, mobj->angle, -speed); } mobj->momx = mobj->momy = 0; mobj->threshold--; if (!mobj->threshold) { // Go into stun after dodge. // from 3*TICRATE down to 1.25*TICRATE //mobj->reactiontime = 5*TICRATE/4 + (FixedMul((7*TICRATE/4)<health-1)<info->spawnhealth-1)<>FRACBITS); // from 3*TICRATE down to 2*TICRATE mobj->reactiontime = 2*TICRATE + (FixedMul((1*TICRATE)<health-1)<info->spawnhealth-1)<>FRACBITS); mobj->flags |= MF_SPECIAL|MF_SHOOTABLE; P_SetMobjState(mobj, mobj->state->nextstate); } return; } angle = ANGLE_135*leveltime; mobj->momz += FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),2*FRACUNIT); // Use that "angle" to bob gently in the air // This is below threshold because we don't want to bob while zipping around // Ohh you're in for it now.. if (mobj->flags2 & MF2_FRET && mobj->health <= mobj->info->damage) mobj->fuse = 0; // reactiontime is used for delays. if (mobj->reactiontime) { // Stunned after vector form if (mobj->movedir > ANGLE_180) mobj->angle -= FixedAngle(FixedMul(AngleFixed(InvAngle(mobj->movedir)),FixedDiv(mobj->reactiontime<angle += FixedAngle(FixedMul(AngleFixed(mobj->movedir),FixedDiv(mobj->reactiontime<reactiontime--; if (!mobj->reactiontime) // Out of stun. P_SetMobjState(mobj, mobj->state->nextstate); return; } // Not stunned? Can hit. // Here because stun won't always get the chance to complete due to pinch phase activating, being hit, etc. mobj->flags &= ~(MF_SPECIAL|MF_SHOOTABLE); if (mobj->health <= mobj->info->damage && mobj->fuse && !(mobj->fuse%TICRATE)) { var1 = 1; var2 = 0; A_BossScream(mobj); } // Don't move if we're still in pain! if (mobj->flags2 & MF2_FRET) return; if (mobj->state == &states[mobj->info->raisestate]) // Charging energy { if (mobj->momx != 0 || mobj->momy != 0) // Apply the air breaks { if (abs(mobj->momx)+abs(mobj->momy) < FRACUNIT) mobj->momx = mobj->momy = 0; else P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -6*FRACUNIT/8); } return; } if (mobj->fuse == 0) { // It's time to attack! What are we gonna do?! switch(mobj->movecount) { case 0: default: // Fly up and prepare for an attack! // We have to charge up first, so let's go up into the air P_SetMobjState(mobj, mobj->info->raisestate); if (mobj->floorz >= mobj->target->floorz) mobj->watertop = mobj->floorz + 256*FRACUNIT; else mobj->watertop = mobj->target->floorz + 256*FRACUNIT; break; case 1: { // Okay, we're up? Good, time to gather energy... if (mobj->health > mobj->info->damage) { // No more bubble if we're broken (pinch phase) mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT); P_SetTarget(&mobj->tracer, shield); P_SetTarget(&shield->target, mobj); } else P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); mobj->fuse = 4*TICRATE; mobj->flags |= MF_PAIN; if (mobj->info->attacksound) S_StartSound(mobj, mobj->info->attacksound); A_FaceTarget(mobj); break; } case 2: // We're all charged and ready now! Unleash the fury!! if (mobj->health > mobj->info->damage) { mobj_t *removemobj = mobj->tracer; P_SetTarget(&mobj->tracer, mobj->hnext); P_RemoveMobj(removemobj); } if (mobj->health <= mobj->info->damage) { // Attack 1: Pinball dash! if (mobj->health == 1) mobj->movedir = 0; else mobj->movedir = 2; if (mobj->info->seesound) S_StartSound(mobj, mobj->info->seesound); P_SetMobjState(mobj, mobj->info->seestate); if (mobj->movedir == 2) mobj->threshold = 16; // bounce 16 times else mobj->threshold = 32; // bounce 32 times mobj->watertop = mobj->target->floorz + 16*FRACUNIT; P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); } else { // Attack 2: Energy shot! mobj->movedir = 1; if (mobj->health >= 8) mobj->extravalue1 = 0; else if (mobj->health >= 5) mobj->extravalue1 = 2; else if (mobj->health >= 4) mobj->extravalue1 = 1; else mobj->extravalue1 = 3; switch(mobj->extravalue1) { case 0: // shoot once case 2: // spread-shot default: mobj->threshold = 2; break; case 1: // shoot 3 times mobj->threshold = 3*2; break; case 3: // shoot like a goddamn machinegun mobj->threshold = 8*2; break; } } break; case 3: // Return to idle. mobj->watertop = mobj->target->floorz + 32*FRACUNIT; P_SetMobjState(mobj, mobj->info->spawnstate); mobj->flags &= ~MF_PAIN; mobj->fuse = 10*TICRATE; break; } mobj->movecount++; mobj->movecount %= 4; return; } // Idle AI if (mobj->state == &states[mobj->info->spawnstate]) { fixed_t dist; // Target the closest player P_BossTargetPlayer(mobj, true); // Face your target angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle angle = (angle-mobj->angle); // relative angle if (angle < ANGLE_180) mobj->angle += angle/8; else mobj->angle -= InvAngle(angle)/8; //A_FaceTarget(mobj); // Check if we're being attacked if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius) danger = false; if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius) danger = false; if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius) danger = false; if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius) danger = false; if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z) danger = false; if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height) danger = false; if (danger) { // An incoming attack is detected! What should we do?! // Go into vector form! mobj->movedir = ANGLE_11hh - FixedAngle(FixedMul(AngleFixed(ANGLE_11hh), FixedDiv((mobj->info->spawnhealth - mobj->health)<info->spawnhealth-1)<movedir = InvAngle(mobj->movedir); mobj->threshold = 6 + (FixedMul(24<info->spawnhealth - mobj->health)<info->spawnhealth-1)<>FRACBITS); if (mobj->info->activesound) S_StartSound(mobj, mobj->info->activesound); if (mobj->info->painchance) P_SetMobjState(mobj, mobj->info->painchance); return; } // Move normally: Approach the player using normal thrust and simulated friction. dist = P_AproxDistance(mobj->x-mobj->target->x, mobj->y-mobj->target->y); P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -3*FRACUNIT/8); if (dist < 64*FRACUNIT) P_Thrust(mobj, mobj->angle, -4*FRACUNIT); else if (dist > 180*FRACUNIT) P_Thrust(mobj, mobj->angle, FRACUNIT); mobj->momz += P_AproxDistance(mobj->momx, mobj->momy)/12; // Move up higher the faster you're going. } } } // // 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 == (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_Alert(CONS_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))); memcpy(&v, res, sizeof (v)); res = VectorMatrixMultiply(v, *RotateZMatrix(FixedAngle(mobj->target->movecount*FRACUNIT))); 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_Alert(CONS_WARNING, "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)); memcpy(&v, res, sizeof (v)); res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle)); 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)); memcpy(&v, res, sizeof (v)); res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle)); 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_SetScale2(mobj_t *mobj, fixed_t newscale, boolean compat) { player_t *player; fixed_t oldscale; if (!mobj) return; oldscale = mobj->scale; //keep for adjusting stuff below if (LUA_HookMobjScaleChange(mobj, newscale, oldscale) || P_MobjWasRemoved(mobj)) return; mobj->scale = newscale; if (compat) { mobj->radius = FixedMul(mobj->info->radius, newscale); mobj->height = FixedMul(mobj->info->height, newscale); } else { 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_MoveOrigin(source, tx, ty, tz); } else { vangle = R_PointToAngle2(source->z, 0, tz, xydist); source->momx = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINECOSINE(source->angle >> ANGLETOFINESHIFT), source->movefactor)); source->momy = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINESINE(source->angle >> ANGLETOFINESHIFT), source->movefactor)); source->momz = FixedMul(FINECOSINE(vangle >> ANGLETOFINESHIFT), source->movefactor); } } else { if (nightsgrab) speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(8*FRACUNIT, source->scale); else speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(source->info->speed, source->scale); source->momx = FixedMul(FixedDiv(tx - source->x, dist), speedmul); source->momy = FixedMul(FixedDiv(ty - source->y, dist), speedmul); source->momz = FixedMul(FixedDiv(tz - source->z, dist), speedmul); } // Instead of just unsetting NOCLIP like an idiot, let's check the distance to our target. ndist = P_AproxDistance(P_AproxDistance(tx - (source->x+source->momx), ty - (source->y+source->momy)), tz - (source->z+source->momz)); if (ndist > dist) // gone past our target { // place us on top of them then. source->momx = source->momy = source->momz = 0; P_UnsetThingPosition(source); source->x = tx; source->y = ty; source->z = tz; P_SetThingPosition(source); } } // // 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; // completed a half-spin dosound = ((prevfa > (FINEMASK/2)) != (fa > (FINEMASK/2))); 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)); memcpy(&unit_lengthways, res, sizeof(unit_lengthways)); res = VectorMatrixMultiply(unit_lengthways, *RotateZMatrix(center->angle)); 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)); memcpy(&unit_sideways, res, sizeof(unit_sideways)); res = VectorMatrixMultiply(unit_sideways, *RotateZMatrix(center->angle)); 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->renderflags ^= RF_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; } } // Kartitem stuff. // This item is never attached to a player -- it can DIE // unconditionally in death sectors. boolean P_IsKartFieldItem(INT32 type) { switch (type) { case MT_BANANA: case MT_EGGMANITEM: case MT_ORBINAUT: case MT_JAWZ: case MT_JAWZ_DUD: case MT_SSMINE: case MT_LANDMINE: case MT_EGGMINE: case MT_BALLHOG: case MT_BUBBLESHIELDTRAP: case MT_SINK: return true; default: return false; } } boolean P_IsKartItem(INT32 type) { switch (type) { case MT_EGGMANITEM_SHIELD: case MT_BANANA_SHIELD: case MT_ORBINAUT_SHIELD: case MT_JAWZ_SHIELD: case MT_SSMINE_SHIELD: case MT_SINK_SHIELD: case MT_EGGMINE_SHIELD: return true; // Primarily for minimap data, handle with care case MT_SPB: return true; // FALLTHRU default: return P_IsKartFieldItem(type); } } boolean K_IsMissileOrKartItem(mobj_t *mo) { if (mo->flags & MF_MISSILE) { // It's already a missile! return true; } if (mo->type == MT_SPB) { // Not considered a field item, so manually include. return true; } return P_IsKartFieldItem(mo->type); } // This item can die in death sectors. There may be some // special conditions for items that don't switch types... // TODO: just make a general function for things that should // die like this? boolean P_CanDeleteKartItem(INT32 type) { return P_IsKartFieldItem(type); } // 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, **p; for (mo = *(p = &kitemcap); mo; mo = *(p = &mo->itnext)) { if (mo != thing) continue; P_SetTarget(p, 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); } static boolean P_IsMiscCapObject(INT32 type) { switch (type) { case MT_RING: case MT_FLINGRING: if (cv_kartdebugrings.value) return true; break; case MT_RANDOMITEM: if (itembreaker) return true; break; default: break; } return false; } // Called when a misc mobj "thinks" void P_AddMiscCap(mobj_t *thing) { I_Assert(thing != NULL); if (misccap == NULL) P_SetTarget(&misccap, thing); else { mobj_t *mo; for (mo = misccap; 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_RemoveMiscCap(mobj_t *thing) { mobj_t *mo, **p; for (mo = *(p = &misccap); mo; mo = *(p = &mo->itnext)) { if (mo != thing) continue; P_SetTarget(p, thing->itnext); P_SetTarget(&thing->itnext, NULL); return; } } // Doesn't actually do anything since objects can have their own thinkers, // but this is necessary for the sole purpose of updating misccap void P_RunMiscCap(void) { mobj_t *mobj, *next; for (mobj = misccap; mobj; mobj = next) { next = mobj->itnext; P_SetTarget(&mobj->itnext, NULL); } P_SetTarget(&misccap, 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; } destx = mo->target->x; desty = mo->target->y; mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP); mo->scale = mo->destscale = FixedMul(mo->target->scale, mo->movefactor); if (!(mo->threshold & OV_DONTCOPYANGLE)) { mo->angle = (mo->target->player ? mo->target->player->drawangle : mo->target->angle) + mo->movedir; } if (!(mo->threshold & OV_DONTSCREENOFFSET)) { mo->spritexoffset = mo->target->spritexoffset; mo->spriteyoffset = mo->target->spriteyoffset; } if (!(mo->threshold & OV_DONT3DOFFSET)) { mo->sprxoff = mo->target->sprxoff; mo->spryoff = mo->target->spryoff; mo->sprzoff = mo->target->sprzoff; } if (!(mo->threshold & OV_DONTXYSCALE)) { mo->spritexscale = mo->target->spritexscale; mo->spriteyscale = mo->target->spriteyscale; } if (!(mo->threshold & OV_DONTROLL)) { mo->rollangle = mo->target->rollangle; mo->pitch = mo->target->pitch; mo->roll = mo->target->roll; } if (!(mo->threshold & OV_DONTBAKEOFFSET)) { // offsets mo->bakexoff = mo->target->bakexoff; mo->bakeyoff = mo->target->bakeyoff; mo->bakezoff = mo->target->bakezoff; // pivots mo->bakexpiv = mo->target->bakexpiv; mo->bakeypiv = mo->target->bakeypiv; mo->bakezpiv = mo->target->bakezpiv; } if ((mo->flags & MF_DONTENCOREMAP) != (mo->target->flags & MF_DONTENCOREMAP)) mo->flags ^= MF_DONTENCOREMAP; mo->dispoffset = mo->target->dispoffset + mo->info->dispoffset; if (!(mo->state->frame & FF_ANIMATE)) { zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale); if (mo->state->var1) { mo->dispoffset--; } else { mo->dispoffset++; } } else { // if you're using FF_ANIMATE on an overlay, // then you're on your own. 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, NULL); } 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, **p; for (mo = *(p = &overlaycap); mo; mo = *(p = &mo->hnext)) { if (mo != thing) continue; P_SetTarget(p, thing->hnext); P_SetTarget(&thing->hnext, NULL); return; } } static UINT32 P_GetTranslucencyForInvincibility(mobj_t *mo) { if (!mo->player) { // Get out. return 0; } return max(0, min(9, 10 - FixedMul(10, K_InvincibilityGradient(mo->player->invincibilitytimer))))<target != NULL); I_Assert(thing->target->player != NULL); if (!thing->target->player->invincibilitytimer) { P_SetTarget(&thing->target, NULL); I_Assert(thing->target == NULL); return; } thing->destscale = thing->target->scale; if (thing->target->eflags & MFE_VERTICALFLIP) { thing->eflags |= MFE_VERTICALFLIP; thing->z += thing->target->height - thing->height; } thing->color = K_AltInvincibilityColor(leveltime / 2); thing->colorized = true; thing->dispoffset = min(2, thing->target->dispoffset + 1); thing->angle = (thing->target->player ? thing->target->player->drawangle : thing->target->angle); // Rotation is handled in r_patchrotation thing->sprite = thing->target->sprite; thing->sprite2 = thing->target->sprite2; thing->frame = thing->target->frame | FF_FULLBRIGHT | FF_ADD; thing->tics = -1; thing->renderflags = (thing->target->renderflags & ~RF_TRANSMASK)|P_GetTranslucencyForInvincibility(thing->target); thing->skin = thing->target->skin; thing->standingslope = thing->target->standingslope; thing->sprxoff = thing->target->sprxoff; thing->spryoff = thing->target->spryoff; thing->sprzoff = thing->target->sprzoff; thing->spritexscale = thing->target->spritexscale; thing->spriteyscale = thing->target->spriteyscale; thing->spritexoffset = thing->target->spritexoffset; thing->spriteyoffset = thing->target->spriteyoffset; if (thing->target->flags2 & MF2_OBJECTFLIP) thing->flags2 |= MF2_OBJECTFLIP; if (!(thing->target->flags & MF_DONTENCOREMAP)) thing->flags &= ~MF_DONTENCOREMAP; } 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->renderflags |= RF_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->renderflags & RF_DONTDRAW) ? 0 : 4)); // oh my god this is nasty. ref->flags |= MF_NOTHINK|MF_NOCLIPTHING; ref->renderflags |= RF_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->renderflags &= ~RF_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->renderflags |= RF_TRANS40; 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<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); flame->renderflags |= RF_TRANS40; 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 (!udmf) return false; 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 (LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker))) return; if (P_MobjWasRemoved(mobj)) return; if (P_IsMiscCapObject(mobj->type)) // mobj is object we want on the list: P_AddMiscCap(mobj); // add to misccap list switch (mobj->type) { case MT_SHADOW: if (mobj->tracer) { P_MoveOrigin(mobj, mobj->tracer->x, mobj->tracer->y, mobj->tracer->z); } else { P_RemoveMobj(mobj); return; } break; case MT_MACEPOINT: case MT_CHAINMACEPOINT: case MT_CHAINPOINT: case MT_CUSTOMMACEPOINT: case MT_HIDDEN_SLING: P_MaceSceneryThink(mobj); break; case MT_SMALLMACE: case MT_BIGMACE: P_SpawnGhostMobj(mobj)->tics = 8; 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); if ((mobj->extravalue2) && (mobj->target->player)) { // Could be overlaying an invincible player. P_PlayerInvincibilityOverlay(mobj); } } 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->renderflags &= ~RF_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; mobj->old_z = mobj->z; break; case MT_LOCKONINF: if (!(mobj->flags2 & MF2_STRONGBOX)) { mobj->threshold = mobj->z; mobj->flags2 |= MF2_STRONGBOX; } if (!(mobj->eflags & MFE_VERTICALFLIP)) mobj->z = mobj->threshold + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale); else mobj->z = mobj->threshold - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale); mobj->old_z = mobj->z; break; case MT_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: 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_ORBINAUT_SHIELD: // Kart orbit/trail items case MT_JAWZ_SHIELD: case MT_BANANA_SHIELD: case MT_SSMINE_SHIELD: case MT_EGGMANITEM_SHIELD: case MT_EGGMINE_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_BUBBLESHIELD_DEBRIS: mobj->rollangle += ANG1 * 4; if (P_IsObjectOnGround(mobj) && mobj->health > 0) { // "Despawn" and play the glass landing sound. if (mobj->extravalue1 == 0) S_StartSoundAtVolume(mobj, mobj->info->deathsound, 192); P_KillMobj(mobj, NULL, NULL, DMG_NORMAL); mobj->fuse = TICRATE; } goto dofuse; 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->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->bumper <= 1) diff = 0; else diff = FixedAngle(360*FRACUNIT/mobj->target->player->bumper); 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->renderflags = (mobj->target->renderflags & RF_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->bumper < 2) P_SetMobjState(mobj, S_BATTLEBUMPER3); else if (mobj->target->player->bumper < 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); } // Was this so hard? if (mobj->target->player->bumper <= mobj->threshold) { P_RemoveMobj(mobj); return; } } else if ((mobj->health > 0 && (!mobj->target || !mobj->target->player || !mobj->target->player->mo || 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; // see also K_drawKartItem in k_hud.c 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) { fixed_t scale = 3*mobj->target->scale; mobj->color = mobj->target->color; K_MatchGenericExtraFlags(mobj, mobj->target); if ((!(gametyperules & GTR_ITEMARROWS) || (gametyperules & GTR_BUMPERS && mobj->target->player->bumper <= 0)) #if 1 // Set to 0 to test without needing to host || (P_IsDisplayPlayer(mobj->target->player)) #endif ) mobj->renderflags |= RF_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->itemroulette) { mobj->tracer->color = mobj->target->player->skincolor; mobj->tracer->colorized = true; } else { mobj->tracer->color = SKINCOLOR_NONE; mobj->tracer->colorized = false; } if (!(mobj->renderflags & RF_DONTDRAW)) { statenum_t statenum = S_PLAYERARROW_BOX; kartitemtype_e itemtype = mobj->target->player->itemtype; UINT8 itemamount = 0; boolean hide = false; // Set it to use the correct states for its condition if (mobj->target->player->itemroulette) { itemtype = K_GetRollingRouletteItem(mobj->target->player); } else if (mobj->target->player->stealingtimer < 0) { itemtype = KITEM_HYUDORO; hide = leveltime & 2; } else if ((mobj->target->player->stealingtimer > 0) && (leveltime & 2)) { itemtype = KITEM_HYUDORO; } else if (mobj->target->player->eggmanexplode > 1) { itemtype = KITEM_EGGMAN; hide = leveltime & 1; } else if (mobj->target->player->rocketsneakertimer > 1) { //itembar = mobj->target->player->rocketsneakertimer; -- not today satan itemtype = KITEM_ROCKETSNEAKER; hide = leveltime & 1; } else if (mobj->target->player->flametimer > 1) { //itembar = mobj->target->player->flametimer; -- not today satan itemtype = KITEM_FLAMESHIELD; hide = leveltime & 1; } else if (mobj->target->player->growshrinktimer > 0) { itemtype = KITEM_GROW; hide = leveltime & 1; } else if (mobj->target->player->itemtype && mobj->target->player->itemamount > 0) { itemtype = mobj->target->player->itemtype; itemamount = mobj->target->player->itemamount; hide = mobj->target->player->itemflags & IF_ITEMOUT && leveltime & 1; } else { statenum = S_PLAYERARROW; } P_SetMobjState(mobj, statenum); if (statenum == S_PLAYERARROW_BOX) { mobj->tracer->sprite = SPR_ITEM; K_UpdateMobjItemOverlay(mobj->tracer, itemtype, itemamount); mobj->tracer->threshold = itemtype; // sorry, i have to mobj->tracer->frame &= ~FF_PAPERSPRITE; if (hide) mobj->tracer->renderflags |= RF_DONTDRAW; else mobj->tracer->renderflags &= ~RF_DONTDRAW; } else { P_SetMobjState(mobj->tracer, S_PLAYERARROW_ITEM); mobj->tracer->renderflags &= ~RF_DONTDRAW; } mobj->tracer->destscale = scale; if (mobj->target->player->itemamount >= K_GetItemNumberDisplayMin(mobj->target->player->itemtype, false) && mobj->target->player->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->itemamount); P_SetTarget(&numx->target, mobj); P_SetMobjState(numx, S_PLAYERARROW_X); P_SetScale(numx, mobj->scale); numx->destscale = scale; // ugh... overlays.... number->old_z = number->z += FixedMul(number->state->var2 * FRACUNIT, mobj->scale); numx->old_z = numx->z += FixedMul(number->state->var2 * FRACUNIT, mobj->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->renderflags |= RF_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->renderflags & RF_DONTDRAW) mobj->renderflags |= RF_DONTDRAW; else mobj->renderflags &= ~RF_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->args[0]) P_SetMobjStateNF(smok, smok->info->painstate); // same function, diff sprite } break; case MT_ITEMCAPSULE_PART: P_ItemCapsulePartThinker(mobj); if (P_MobjWasRemoved(mobj)) return; break; case MT_SCRIPT_THING: { if (mobj->spawnpoint->args[2] != 0) { // turned off break; } UINT8 i; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] == false) { continue; } player_t *player = &players[i]; if (P_MobjWasRemoved(player->mo) == true) { continue; } fixed_t dist = R_PointToDist2( mobj->x, mobj->y, player->mo->x, player->mo->y ); if (dist < mobj->spawnpoint->args[0] * FRACUNIT) { P_ActivateThingSpecial(mobj, player->mo); if (mobj->spawnpoint->args[1] == 0) { P_RemoveMobj(mobj); return; } break; } } break; } /* FALLTHRU */ default: dofuse: if (mobj->fuse) { // Scenery object fuse! Very basic! mobj->fuse--; if (!mobj->fuse) { if (!LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse))) P_RemoveMobj(mobj); return; } } break; } P_SceneryThinker(mobj); } static boolean P_MobjPushableThink(mobj_t *mobj) { P_MobjCheckWater(mobj); P_PushableThinker(mobj); // Extinguish fire objects in water. (Yes, it's extraordinarily rare to have a pushable flame object, but Brak uses such a case.) if (mapnamespace != MNS_RINGRACERS && mobj->flags & MF_FIRE && (mapnamespace == MNS_SRB2KART ? mobj->type != MT_PUMA && mobj->type != MT_FIREBALL : !(mobj->eflags & MFE_TOUCHLAVA)) && (mobj->eflags & (MFE_UNDERWATER | MFE_TOUCHWATER))) { P_KillMobj(mobj, NULL, NULL, 0); return false; } return true; } static boolean P_MobjBossThink(mobj_t *mobj) { if (LUA_HookMobj(mobj, MOBJ_HOOK(BossThinker))) { if (P_MobjWasRemoved(mobj)) return false; } else if (P_MobjWasRemoved(mobj)) return false; else switch (mobj->type) { case MT_EGGMOBILE: if (mobj->health < mobj->info->damage+1 && leveltime & 1 && mobj->health > 0) P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SMOKE); if (mobj->flags2 & MF2_SKULLFLY) P_SpawnGhostMobj(mobj); P_Boss1Thinker(mobj); break; case MT_EGGMOBILE2: P_Boss2Thinker(mobj); break; case MT_EGGMOBILE3: P_Boss3Thinker(mobj); break; case MT_EGGMOBILE4: P_Boss4Thinker(mobj); break; case MT_BLACKEGGMAN: P_Boss7Thinker(mobj); break; case MT_METALSONIC_BATTLE: P_Boss9Thinker(mobj); break; // 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_BLUEBALL: if ((mobj->tics>>2)+1 > 0 && (mobj->tics>>2)+1 <= tr_trans60) // tr_trans50 through tr_trans90, shifting once every second frame mobj->frame = (NUMTRANSMAPS-((mobj->tics>>2)+1))<frame = tr_trans60<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->renderflags |= RF_DONTDRAW; } else // safe to remove, nobody's going to complain! { P_RemoveMobj(mobj); return false; } } // Apply gravity to fall downwards. else if (mobj->player == NULL || skins[mobj->player->skin].flags & SF_OLDDEATH) { P_SetObjectMomZ(mobj, -3*FRACUNIT/2, true); if (mobj->player != NULL) mobj->player->drawangle -= ANGLE_22h; } else { P_SetObjectMomZ(mobj, -7*FRACUNIT/8, true); } break; case MT_KART_LEFTOVER: mobj->angle += ANG15; P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true); break; case MT_METALSONIC_RACE: { if (!(mobj->fuse % 8)) { fixed_t r = mobj->radius >> FRACBITS; fixed_t rand_x; fixed_t rand_y; fixed_t rand_z; rand_z = mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS); rand_y = mobj->y + (P_RandomRange(r, -r) << FRACBITS); rand_x = mobj->x + (P_RandomRange(r, -r) << FRACBITS); mobj_t *explosion = P_SpawnMobj(rand_x, rand_y, rand_z, 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_EGGMINE: case MT_SPB: if (P_IsObjectOnGround(mobj)) { P_RemoveMobj(mobj); return false; } /* FALLTHRU */ case MT_ORBINAUT_SHIELD: case MT_EGGMINE_SHIELD: case MT_BANANA_SHIELD: case MT_EGGMANITEM_SHIELD: mobj->renderflags ^= RF_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->renderflags ^= RF_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->renderflags ^= RF_DONTDRAW; // only by good fortune does this end with it having RF_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->renderflags ^= RF_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->renderflags ^= RF_DONTDRAW; if (P_IsObjectOnGround(mobj)) { P_RemoveMobj(mobj); return false; } 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; // mobj->lastlook - Don't disable behavior after first failure // mobj->extravalue1 - Angle tolerance // 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 { 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_ActivateThingSpecial(mobj->tracer, mobj); } } 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->fuse && !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_AQUABUZZ: case MT_BIGAIRMINE: { if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0 && P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius * 16) { // Home in on the target. P_HomingAttack(mobj, mobj->tracer); if (mobj->z < mobj->floorz) mobj->z = mobj->floorz; if (leveltime % mobj->info->painchance == 0) S_StartSound(mobj, mobj->info->activesound); } else { // Try to find a player P_LookForPlayers(mobj, true, true, mobj->radius * 16); mobj->momx >>= 1; mobj->momy >>= 1; mobj->momz >>= 1; } } break; case MT_BIGMINE: { if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0 && P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius * 16) { P_MobjCheckWater(mobj); // Home in on the target. P_HomingAttack(mobj, mobj->tracer); // Don't let it go out of water if (mobj->z + mobj->height > mobj->watertop) mobj->z = mobj->watertop - mobj->height; if (mobj->z < mobj->floorz) mobj->z = mobj->floorz; if (leveltime % mobj->info->painchance == 0) S_StartSound(mobj, mobj->info->activesound); } else { // Try to find a player P_LookForPlayers(mobj, true, true, mobj->radius * 16); mobj->momx >>= 1; mobj->momy >>= 1; mobj->momz >>= 1; } } 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_NIGHTSCORE: mobj->color = (UINT16)(leveltime % SKINCOLOR_WHITE); break; case MT_JETFUME1: { fixed_t jetx, jety; if (!mobj->target // if you have no target || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now { // then remove yourself as well! P_RemoveMobj(mobj); return false; } jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale)); jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale)); if (mobj->fuse == 56) // First one { P_UnsetThingPosition(mobj); mobj->x = jetx; mobj->y = jety; if (mobj->target->eflags & MFE_VERTICALFLIP) mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale); else mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale); mobj->floorz = mobj->z; mobj->ceilingz = mobj->z+mobj->height; P_SetThingPosition(mobj); } else if (mobj->fuse == 57) { P_UnsetThingPosition(mobj); mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle-ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); if (mobj->target->eflags & MFE_VERTICALFLIP) mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale); else mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale); mobj->floorz = mobj->z; mobj->ceilingz = mobj->z+mobj->height; P_SetThingPosition(mobj); } else if (mobj->fuse == 58) { P_UnsetThingPosition(mobj); mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle+ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale)); if (mobj->target->eflags & MFE_VERTICALFLIP) mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale); else mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale); mobj->floorz = mobj->z; mobj->ceilingz = mobj->z+mobj->height; P_SetThingPosition(mobj); } else if (mobj->fuse == 59) { jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius); jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius); P_UnsetThingPosition(mobj); mobj->x = jetx; mobj->y = jety; if (mobj->target->eflags & MFE_VERTICALFLIP) mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2; else mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2; mobj->floorz = mobj->z; mobj->ceilingz = mobj->z+mobj->height; P_SetThingPosition(mobj); } mobj->fuse++; } break; case MT_PROPELLER: { fixed_t jetx, jety; if (!mobj->target // if you have no target || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now { // then remove yourself as well! P_RemoveMobj(mobj); return false; } jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale)); jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-60*FRACUNIT, mobj->target->scale)); P_UnsetThingPosition(mobj); mobj->x = jetx; mobj->y = jety; mobj->z = mobj->target->z + FixedMul(17*FRACUNIT, mobj->target->scale); mobj->angle = mobj->target->angle - ANGLE_180; mobj->floorz = mobj->z; mobj->ceilingz = mobj->z+mobj->height; P_SetThingPosition(mobj); } break; case MT_JETFLAME: { if (!mobj->target // if you have no target || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now { // then remove yourself as well! P_RemoveMobj(mobj); return false; } P_UnsetThingPosition(mobj); mobj->x = mobj->target->x; mobj->y = mobj->target->y; mobj->z = mobj->target->z - FixedMul(50*FRACUNIT, mobj->target->scale); mobj->floorz = mobj->z; mobj->ceilingz = mobj->z+mobj->height; P_SetThingPosition(mobj); } 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 (cv_kartdebugrings.value) P_AddMiscCap(mobj); A_AttractChase(mobj); return false; // Flung items case MT_FLINGRING: if (P_MobjWasRemoved(mobj)) return false; A_AttractChase(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; } } K_UpdateMobjItemOverlay(mobj, mobj->threshold, mobj->movecount); break; } case MT_ITEMCAPSULE: // scale the capsule if (mobj->scale < mobj->extravalue1) { fixed_t oldHeight = mobj->height; if ((mobj->extravalue1 - mobj->scale) < mobj->scalespeed) P_SetScale(mobj, mobj->destscale = mobj->extravalue1); else P_SetScale(mobj, mobj->destscale = mobj->scale + mobj->scalespeed); if (mobj->eflags & MFE_VERTICALFLIP) mobj->z -= (mobj->height - oldHeight); } // spawn parts if not done yet // (this SHOULD be done when the capsule is spawned, but gravflip isn't set up at that point) if (!(mobj->flags2 & MF2_JUSTATTACKED)) { mobj->flags2 |= MF2_JUSTATTACKED; P_SpawnItemCapsuleParts(mobj); } // update & animate capsule if (!P_MobjWasRemoved(mobj->tracer)) { mobj_t *part = mobj->tracer; if (mobj->threshold != part->threshold || mobj->movecount != part->movecount) // change the capsule properties if the item type or amount is updated P_RefreshItemCapsuleParts(mobj); K_UpdateMobjItemOverlay(part, mobj->threshold, mobj->movecount); if (mobj->threshold == KITEM_INVINCIBILITY) mobj->color = K_RainbowColor(leveltime); } break; case MT_ORBINAUT: { boolean grounded = P_IsObjectOnGround(mobj); sector_t *sec2; 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++; } mobj->flags2 &= ~MF2_WATERRUN; } 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_AproxDistance(mobj->momx, mobj->momy) <= 14*mobj->scale) { // Not moving fast enough to water run. mobj->flags2 &= ~MF2_WATERRUN; } sec2 = P_ThingOnSpecial3DFloor(mobj); if ((K_AffectingTerrainActive() && mobj->terrain && mobj->terrain->pogoSpring > 0) || (sec2 && (sec2->specialflags & (SSF_YELLOWPOGOSPRING|SSF_REDPOGOSPRING))) || (P_IsObjectOnRealGround(mobj, mobj->subsector->sector) && (mobj->subsector->sector->specialflags & (SSF_YELLOWPOGOSPRING|SSF_REDPOGOSPRING)))) K_DoPogoSpring(mobj, 0, 1); if (mobj->threshold > 0) mobj->threshold--; if (leveltime % 6 == 0) S_StartSound(mobj, mobj->info->activesound); } P_MobjCheckWater(mobj); break; } case MT_JAWZ: { sector_t *sec2; fixed_t topspeed = mobj->movefactor; fixed_t distbarrier = 512*mapobjectscale; fixed_t distaway; P_SpawnGhostMobj(mobj); if (mobj->threshold > 0) mobj->threshold--; if (leveltime % TICRATE == 0) S_StartSound(mobj, mobj->info->activesound); if (gamespeed == KARTSPEED_EASY) distbarrier = FixedMul(distbarrier, FRACUNIT-FRACUNIT/4); else if (gamespeed == KARTSPEED_HARD) distbarrier = FixedMul(distbarrier, FRACUNIT+FRACUNIT/4); else if (gamespeed == KARTSPEED_EXPERT) distbarrier = FixedMul(distbarrier, FRACUNIT+FRACUNIT/2); if ((gametyperules & GTR_CIRCUIT) && mobj->tracer) { distaway = P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y); if (distaway < distbarrier) { if (mobj->tracer->player) { fixed_t speeddifference = abs(topspeed - min(mobj->tracer->player->speed, K_GetKartSpeed(mobj->tracer->player, false,false))); topspeed = topspeed - FixedMul(speeddifference, FRACUNIT-FixedDiv(distaway, distbarrier)); } } } if (gametype == GT_BATTLE) { mobj->friction -= 1228; if (mobj->friction > FRACUNIT) mobj->friction = FRACUNIT; if (mobj->friction < 0) mobj->friction = 0; } mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy); P_InstaThrust(mobj, mobj->angle, topspeed); if (mobj->tracer) mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y); else mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy); K_DriftDustHandling(mobj); if (P_AproxDistance(mobj->momx, mobj->momy) <= 14*mobj->scale) { // Not moving fast enough to water run. mobj->flags2 &= ~MF2_WATERRUN; } sec2 = P_ThingOnSpecial3DFloor(mobj); if ((K_AffectingTerrainActive() && mobj->terrain && mobj->terrain->pogoSpring > 0) || (sec2 && (sec2->specialflags & (SSF_YELLOWPOGOSPRING|SSF_REDPOGOSPRING))) || (P_IsObjectOnRealGround(mobj, mobj->subsector->sector) && (mobj->subsector->sector->specialflags & (SSF_YELLOWPOGOSPRING|SSF_REDPOGOSPRING)))) K_DoPogoSpring(mobj, 0, 1); P_MobjCheckWater(mobj); 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; } mobj->flags2 &= ~MF2_WATERRUN; } else { P_MobjCheckWater(mobj); P_SpawnGhostMobj(mobj); mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy); P_InstaThrust(mobj, mobj->angle, mobj->movefactor); if (grounded) { sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj); if ((K_AffectingTerrainActive() && mobj->terrain && mobj->terrain->pogoSpring > 0) || (sec2 && (sec2->specialflags & (SSF_YELLOWPOGOSPRING|SSF_REDPOGOSPRING))) || (P_IsObjectOnRealGround(mobj, mobj->subsector->sector) && (mobj->subsector->sector->specialflags & (SSF_YELLOWPOGOSPRING|SSF_REDPOGOSPRING)))) K_DoPogoSpring(mobj, 0, 1); } if (P_AproxDistance(mobj->momx, mobj->momy) <= 14*mobj->scale) { // Not moving fast enough to water run. mobj->flags2 &= ~MF2_WATERRUN; } if (mobj->threshold > 0) mobj->threshold--; if (leveltime % TICRATE == 0) S_StartSound(mobj, mobj->info->activesound); } P_MobjCheckWater(mobj); break; } case MT_EGGMANITEM: /* FALLTHRU */ case MT_BANANA: mobj->friction = ORIG_FRICTION/4; P_MobjCheckWater(mobj); 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; } P_MobjCheckWater(mobj); if (mobj->threshold > 0) mobj->threshold--; break; case MT_SPB: 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--; P_MobjCheckWater(mobj); } 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_SSMineSearch(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_EGGMINE: mobj->friction = 95*ORIG_FRICTION/100; if (mobj->momx || mobj->momy) { mobj_t *ghost = P_SpawnGhostMobj(mobj); if (mobj->target && mobj->target->player) { ghost->color = mobj->target->player->skincolor; ghost->colorized = true; } } if (P_IsObjectOnGround(mobj)) { if (mobj->health > 1 && mobj->threshold == 0) { S_StartSound(mobj, mobj->info->activesound); mobj->health = 1; mobj->whiteshadow = false; } if (!S_SoundPlaying(mobj, sfx_s3kd2l)) { S_StartSound(mobj, sfx_s3kd2l); } mobj->movefactor -= 1; if (mobj->movefactor <= 0) { P_KillMobj(mobj, mobj, NULL, DMG_INSTAKILL); } } else if (S_SoundPlaying(mobj, sfx_s3kd2l)) { S_StopSoundByID(mobj, sfx_s3kd2l); } if (mobj->threshold > 0) { mobj->threshold -= 1; } if (mobj->extravalue1 > 0) { mobj->extravalue1 -= 1; } break; case MT_SPBEXPLOSION: mobj->health--; break; case MT_MINEEXPLOSION: if ((mobj->z < mobj->floorz - mobj->height) || (mobj->z > mobj->ceilingz + mobj->height) || (cv_kartexplosion_limitlifetime.value && mobj->mobjlifetime > cv_kartexplosion_limitlifetime_cap.value)) { 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_BOOSTFLAME: if (!mobj->target || !mobj->target->health) { P_RemoveMobj(mobj); return false; } { 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; mobj->angle = mobj->extravalue1 && p ? p->drawangle : mobj->target->angle; P_MoveOrigin(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); K_FlipFromObject(mobj, mobj->target); P_SetScale(mobj, mobj->target->scale); mobj->roll = mobj->target->roll; mobj->pitch = mobj->target->pitch; mobj->sloperoll = mobj->target->sloperoll; mobj->slopepitch = mobj->target->slopepitch; if (p) { if (mobj->extravalue1) { if (p->driftboost > mobj->movecount) { P_SetMobjState(mobj, S_DRIFTBOOSTFLAME); if (mobj->extravalue1 == 1) mobj->tics /= 2; // Less time for blue drift else if (mobj->extravalue1 == 3) mobj->tics = 3*mobj->tics/2; // A little longer for purple drift else if (mobj->extravalue1 == 4) mobj->tics *= 2; // More time for rainbow drift } mobj->movecount = p->driftboost; if (mobj->extravalue1 == 4) P_SetScale(mobj, max(10, mobj->tics - 3*TICRATE/2)*mobj->scale/10); // larger karts need larger flames if (mobj->target->skin && !wadfiles[((skin_t *)mobj->target->skin)->wadnum]->compatmode) { P_SetScale(mobj, 9*mobj->scale/8); mobj->spriteyoffset = -6*FRACUNIT; } if (p->driftboost == 0) { P_RemoveMobj(mobj); return false; } } else { if (stackingactive && cv_kartstacking_colorflame.value) { switch(p->numsneakers) { case 0: case 1: mobj->colorized = false; break; case 2: mobj->colorized = true; mobj->color = SKINCOLOR_BLUEBERRY; break; case 3: mobj->colorized = true; mobj->color = SKINCOLOR_PURPLE; break; case 4: mobj->colorized = true; mobj->color = SKINCOLOR_MOONSLAM; break; case 5: mobj->colorized = true; mobj->color = SKINCOLOR_WHITE; break; default: mobj->colorized = true; mobj->color = SKINCOLOR_WHITE; break; } } if (p->sneakertimer > mobj->movecount) P_SetMobjState(mobj, S_BOOSTFLAME); mobj->movecount = p->sneakertimer; } } } if (mobj->extravalue1) { if (leveltime % 2 == 1) { mobj->frame |= FF_TRANS60; } else { mobj->frame &= ~FF_TRANS60; } if (mobj->extravalue1 == 4) { mobj->color = K_RainbowColor(leveltime); } } if (mobj->state == &states[S_BOOSTSMOKESPAWNER]) { mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z+(8<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; fixed_t rand_angle; fixed_t rand_move; rand_move = P_RandomRange(0, 8) * mobj->target->scale; rand_angle = mobj->angle+FixedAngle(P_RandomRange(135, 225)<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->invincibilitytimer)) { P_RemoveMobj(mobj); return false; } P_MoveOrigin(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->drift || !(mobj->target->player->pflags & PF_BRAKEDRIFT) || !((mobj->target->player->cmd.buttons & BT_BRAKE) || (K_GetKartButtons(mobj->target->player) & 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->driftcharge); fixed_t newx, newy; angle_t travelangle; travelangle = mobj->target->angle - ((ANGLE_45/5)*mobj->target->player->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_MoveOrigin(mobj, newx, newy, mobj->target->z); mobj->angle = travelangle - ((ANGLE_90/5)*mobj->target->player->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->renderflags |= RF_DONTDRAW; } break; case MT_PLAYERRETICULE: if (!mobj->target || !mobj->target->health) { P_RemoveMobj(mobj); return false; } P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z); mobj->old_x = mobj->target->old_x; mobj->old_y = mobj->target->old_y; mobj->old_z = mobj->target->old_z; break; case MT_INSTASHIELDB: mobj->renderflags ^= RF_DONTDRAW; /* FALLTHRU */ case MT_INSTASHIELDA: if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->health || (mobj->target->player && !mobj->target->player->instashield)) { P_RemoveMobj(mobj); return false; } P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z); mobj->old_x = mobj->target->old_x; mobj->old_y = mobj->target->old_y; mobj->old_z = mobj->target->old_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_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + (mobj->target->height/2) + mobj->movefactor); break; case MT_GAINAX: if (!mobj->target || P_MobjWasRemoved(mobj->target) // sanity || !mobj->target->player // ditto || !mobj->target->player->glanceDir // still glancing? || mobj->target->player->aizdriftturn // only other circumstance where can glance || ((K_GetKartButtons(mobj->target->player) & BT_LOOKBACK) != BT_LOOKBACK)) // it's a lookback indicator... { P_RemoveMobj(mobj); return false; } mobj->angle = mobj->target->player->drawangle; mobj->z = mobj->target->z; K_MatchGenericExtraFlags(mobj, mobj->target); mobj->renderflags = (mobj->renderflags & ~RF_DONTDRAW)|K_GetPlayerDontDrawFlag(mobj->target->player); P_MoveOrigin(mobj, mobj->target->x + FixedMul(34 * mapobjectscale, FINECOSINE((mobj->angle + mobj->movedir) >> ANGLETOFINESHIFT)), mobj->target->y + FixedMul(34 * mapobjectscale, FINESINE((mobj->angle + mobj->movedir) >> ANGLETOFINESHIFT)), mobj->z + (32 * mapobjectscale * P_MobjFlip(mobj))); { statenum_t gainaxstate = mobj->state-states; if (gainaxstate == S_GAINAX_TINY) { if (abs(mobj->target->player->glanceDir) > 1) { if (mobj->target->player->itemamount && mobj->target->player->itemtype) gainaxstate = S_GAINAX_HUGE; else gainaxstate = S_GAINAX_MID1; P_SetMobjState(mobj, gainaxstate); } } else if (abs(mobj->target->player->glanceDir) <= 1) { if (mobj->flags2 & MF2_AMBUSH) mobj->flags2 &= ~MF2_AMBUSH; else P_SetMobjState(mobj, S_GAINAX_TINY); } } break; case MT_BOOSTSTACK: { if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->health || (mobj->target->player && !mobj->target->player->numboosts)) { P_RemoveMobj(mobj); return false; } mobj->roll = mobj->target->roll; mobj->pitch = mobj->target->pitch; mobj->sloperoll = mobj->target->sloperoll; mobj->slopepitch = mobj->target->slopepitch; // Thx Indev! (taken and modified from BoostStack) P_MoveOrigin(mobj, mobj->target->x + FixedMul(cos(mobj->target->angle), FixedMul(30*FRACUNIT, mapobjectscale)), mobj->target->y + FixedMul(sin(mobj->target->angle), FixedMul(30*FRACUNIT, mapobjectscale)), mobj->target->z); K_FlipFromObject(mobj, mobj->target); // Leveltime based animation mobj->frame = states[S_BOOSTSTACK].frame + (leveltime / 3) % 5; break; } case MT_THUNDERSHIELD: { if (!mobj->target || !mobj->target->health || !mobj->target->player || K_GetShieldFromPlayer(mobj->target->player) != KSHIELD_THUNDER) { P_RemoveMobj(mobj); return false; } P_SetScale(mobj, (mobj->destscale = (5*mobj->target->scale)>>2)); // Don't sloperoll Shields mobj->pitch = 0; mobj->roll = 0; mobj->slopepitch = 0; mobj->sloperoll = 0; P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z); break; } case MT_BUBBLESHIELD: { const player_t *player = P_MobjWasRemoved(mobj->target) ? NULL : mobj->target->player; if (player == NULL || player->mo->health == 0 || K_GetShieldFromPlayer(player) != KSHIELD_BUBBLE) { P_RemoveMobj(mobj); return false; } fixed_t scale = 5*player->mo->scale/4; if (cv_kartbubble_defense_canidle.value && player->bubbleblowup == 0 && player->bubblecool > 0) mobj->renderflags |= RF_GHOSTLY; else if ((!cv_kartbubble_defense_canidle.value) && !K_IsBubbleDefending(player)) mobj->renderflags |= (RF_TRANS40 | RF_FULLBRIGHT); else mobj->renderflags &= ~RF_GHOSTLYMASK; if (player->bubblecool) { INT32 blow = player->bubbleblowup + min(player->bubblecool, bubbletime)*10; scale += (blow * scale) / (bubbletime * 10); if (mobj->state - states != S_BUBBLESHIELDBLOWUP) P_SetMobjState(mobj, S_BUBBLESHIELDBLOWUP); mobj->angle += ANGLE_22h; fixed_t stretch = 0; if (player->bubblecool >= bubbletime) // overcharge stretch = ((player->bubblecool - bubbletime) % 4) * FRACUNIT/4; else if (player->bubbleblowup > 0 && player->bubblecool <= 4) // inflate 1 stretch = player->bubblecool*FRACUNIT/5; else if (player->bubbleblowup >= 5 && player->bubblecool < 8) // inflate 2 stretch = (8 - player->bubblecool)*FRACUNIT/5; else if (player->bubblehealth == 0 && player->bubbleblowup == 0) // decaying stretch = (player->bubblecool - bubbletime)*FRACUNIT/bubbletime; mobj->spritexscale = FRACUNIT + FTAN(FixedAngle(stretch*30) + ANGLE_90); mobj->spriteyscale = FRACUNIT - stretch/5; mobj->colorized = true; if (player->bubbleblowup > 0 && leveltime & 1) mobj->color = SKINCOLOR_WHITE; else mobj->color = SKINCOLOR_BLUE; if (P_IsObjectOnGround(player->mo) && player->bubbleblowup > 0) { UINT8 i; for (i = 0; i < 2; i++) { angle_t a = mobj->angle + ((i & 1) ? ANGLE_180 : 0); mobj_t *wave; wave = P_SpawnMobj( (player->mo->x - player->mo->momx) + P_ReturnThrustX(NULL, a, mobj->radius - (5*scale)), (player->mo->y - player->mo->momy) + P_ReturnThrustY(NULL, a, mobj->radius - (5*scale)), (player->mo->z - player->mo->momz), MT_THOK); wave->colorized = true; wave->color = SKINCOLOR_BLUE; wave->flags &= ~(MF_NOCLIPHEIGHT|MF_NOGRAVITY); P_SetScale(wave, (wave->destscale = scale/3)); P_SetMobjState(wave, S_SPLISH1); wave->momx = player->mo->momx; wave->momy = player->mo->momy; wave->momz = player->mo->momz; } } } else { mobj->angle = player->mo->angle; // mobj->renderflags &= ~RF_GHOSTLYMASK; mobj->colorized = false; mobj->color = SKINCOLOR_BLUE; if (mobj->state - states == S_BUBBLESHIELDBLOWUP) P_SetMobjState(mobj, S_BUBBLESHIELD1); } P_SetScale(mobj, (mobj->destscale = scale)); if (mobj->tracer && (!P_MobjWasRemoved(mobj->tracer))) { if (mobj->tracer->type == MT_OVERLAY) { fixed_t health_scalar; P_SetScale(mobj->tracer, mobj->destscale); mobj->tracer->threshold |= OV_DONTXYSCALE; mobj->tracer->spritexscale = mobj->spritexscale; mobj->tracer->spriteyscale = mobj->spriteyscale; health_scalar = player->bubblehealth * FRACUNIT / MAXBUBBLEHEALTH; health_scalar = 5 - CLAMP(health_scalar / (FRACUNIT / 5), 1, 5); statenum_t damagestate = health_scalar == 0 ? S_INVISIBLE : S_BUBLSHLD_DMG_1 - 1 + health_scalar; // Depending on the level of damage done to the shield, show some cracks. if (mobj->tracer->state - states != damagestate) P_SetMobjState(mobj->tracer, damagestate); } else { // Very silently detach ourselves from our tracer and try a // damage-control fix. P_SetTarget(&mobj->tracer, NULL); mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z, MT_OVERLAY); spawn->destscale = mobj->scale; P_SetScale(spawn, mobj->scale); P_SetTarget(&mobj->tracer, spawn); P_SetTarget(&spawn->target, mobj); } } // Don't sloperoll Bubble Shields since it messes with the shatter VFX mobj->pitch = 0; mobj->roll = 0; mobj->slopepitch = 0; mobj->sloperoll = 0; mobj->flags &= ~(MF_NOCLIP|MF_NOCLIPTHING); P_MoveOrigin(mobj, player->mo->x, player->mo->y, player->mo->z); mobj->flags |= MF_NOCLIP|MF_NOCLIPTHING; break; } case MT_FLAMESHIELD: { statenum_t curstate; if (!mobj->target || !mobj->target->health || !mobj->target->player || K_GetShieldFromPlayer(mobj->target->player) != KSHIELD_FLAME) { P_RemoveMobj(mobj); return false; } 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->flamestore) { if (curstate != S_FLAMESHIELDDASH) P_SetMobjState(mobj, S_FLAMESHIELDDASH); mobj->renderflags ^= RF_DONTDRAW; } else { if (curstate == S_FLAMESHIELDDASH) P_SetMobjState(mobj, S_FLAMESHIELD1); mobj->renderflags &= ~RF_DONTDRAW; } // Don't sloperoll Shields mobj->pitch = 0; mobj->roll = 0; mobj->slopepitch = 0; mobj->sloperoll = 0; P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z); mobj->angle = K_MomentumAngle(mobj->target); break; } case MT_ROCKETSNEAKER: if (!mobj->target || !mobj->target->health) { P_RemoveMobj(mobj); return false; } if (mobj->target->player && !mobj->target->player->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 || mobj->target->player->bumper || !(gametyperules & GTR_KARMA)) { P_RemoveMobj(mobj); return false; } P_MoveOrigin(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->karmamode == 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 < mobj->info->spawnstate || state > mobj->info->spawnstate+19) P_SetMobjState(mobj, mobj->info->spawnstate); if (mobj->target->player->karmadelay < TICRATE && (leveltime & 1)) mobj->renderflags &= ~RF_DONTDRAW; else mobj->renderflags |= RF_DONTDRAW; } else { if (!mobj->target->player->karmamode && (state < mobj->info->spawnstate || state > mobj->info->spawnstate+19)) P_SetMobjState(mobj, mobj->info->spawnstate); else if (mobj->target->player->karmamode == 1 && state != mobj->info->seestate) P_SetMobjState(mobj, mobj->info->seestate); else if (mobj->target->player->karmamode == 2 && state != mobj->info->painstate) P_SetMobjState(mobj, mobj->info->painstate); if (mobj->target->player->flashing && (leveltime & 1)) mobj->renderflags |= RF_DONTDRAW; else mobj->renderflags &= ~RF_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_MoveOrigin(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->renderflags & RF_DONTDRAW) cur->renderflags |= RF_DONTDRAW; else cur->renderflags &= ~RF_DONTDRAW; cur = cur->hnext; } } } break; case MT_SIGN: // Kart's unique sign behavior if (mobj->movecount) { if (mobj->z <= mobj->movefactor) { P_SetMobjState(mobj, S_SIGN_END); if (mobj->info->attacksound) S_StartSound(mobj, mobj->info->attacksound); mobj->flags |= MF_NOGRAVITY; // ? mobj->flags &= ~MF_NOCLIPHEIGHT; mobj->z = mobj->movefactor; mobj->movecount = 0; } else { fixed_t rand_x; fixed_t rand_y; fixed_t rand_z; rand_z = mobj->z + (24*mobj->scale) + (P_RandomRange(-8,8)*mobj->scale); rand_y = mobj->y + (P_RandomRange(-48,48)*mobj->scale); rand_x = mobj->x + (P_RandomRange(-48,48)*mobj->scale); P_SpawnMobj(rand_x, rand_y, rand_z, MT_SIGNSPARKLE); mobj->flags &= ~MF_NOGRAVITY; if (abs(mobj->z - mobj->movefactor) <= (512*mobj->scale) && !mobj->cvmem) { if (mobj->info->seesound) S_StartSound(mobj, mobj->info->seesound); mobj->cvmem = 1; } } } 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<y - (mobj->spawnpoint->y<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<spawnpoint->y<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 (mobj->extravalue1 != 2 && !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; fixed_t rand_x; fixed_t rand_y; rand_y = mobj->y + (P_RandomRange(-32, 32)*mobj->scale); rand_x = mobj->x + (P_RandomRange(-32, 32)*mobj->scale); mobj_t *fire = P_SpawnMobj(rand_x, rand_y, 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++) { rand_y = mobj->y + (P_RandomRange(-16, 16)*mobj->scale); rand_x = mobj->x + (P_RandomRange(-16, 16)*mobj->scale); mobj_t *smoke = P_SpawnMobj(rand_x, rand_y, 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_MoveOrigin(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)<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)<>ANGLETOFINESHIFT)), FINECOSINE(mobj->angle>>ANGLETOFINESHIFT)) >> FRACBITS; mobj->extravalue2 = FixedMul(FixedMul((mobj->threshold/16)<>ANGLETOFINESHIFT)), FINESINE(mobj->angle>>ANGLETOFINESHIFT)) >> FRACBITS; mobj->cusval = FixedMul((mobj->threshold/16)<>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<movecount), mobj->y + ((mobj->extravalue2<movecount), true, NULL)) || (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_MoveOrigin(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)) { fixed_t rand_x; fixed_t rand_y; fixed_t rand_z; rand_z = mobj->z + (P_RandomRange(0, 2)<y + (P_RandomRange(-4, 4)<x + (P_RandomRange(-4, 4)<scale/2); P_InstaThrust(dust, FixedAngle(P_RandomRange(0,359)<tracer->momz)/2); if (abs(mobj->tracer->momz) >= 2<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_MoveOrigin(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<floorz + (72<floorz + (mobj->extravalue2<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<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->offroad += 2<mo->x + P_ReturnThrustX(NULL, player->mo->angle, player->mo->radius) + P_ReturnThrustX(NULL, player->mo->angle+ANGLE_90, (mobj->threshold)<mo->y + P_ReturnThrustY(NULL, player->mo->angle, player->mo->radius) + P_ReturnThrustY(NULL, player->mo->angle+ANGLE_90, (mobj->threshold)<mo->z + (player->mo->height/2 * P_MobjFlip(player->mo)) + (P_RandomRange(-abs(mobj->threshold), abs(mobj->threshold))<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)<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 = (5*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_MoveOrigin(mobj, mobj->tracer->x + P_ReturnThrustX(NULL, mobj->tracer->angle+ANGLE_90, (mobj->cvmem)<tracer->y + P_ReturnThrustY(NULL, mobj->tracer->angle+ANGLE_90, (mobj->cvmem)<tracer->z - (4*mobj->tracer->scale) + (P_RandomRange(-abs(mobj->cvmem), abs(mobj->cvmem))<movecount > 5*TICRATE) { S_StartSound(mobj->tracer, sfx_s3k77); mobj->tracer->flags &= ~MF_NOGRAVITY; P_KillMobj(mobj, mobj->tracer, mobj->tracer, DMG_NORMAL); player->respawn = 1; break; } // Uses cmd.turning over steering intentionally. 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/4); 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->colorized = true; wave->color = SKINCOLOR_PERIWINKLE; wave->flags &= ~(MF_NOCLIPHEIGHT|MF_NOGRAVITY); P_SetScale(wave, (wave->destscale = mobj->scale/2)); P_SetMobjState(wave, S_SPLISH1); 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_TUMBLEGEM: case MT_TUMBLECOIN: mobj->friction = 95*FRACUNIT/100; if ((((statenum_t)(mobj->state-states)) == mobj->info->seestate) && abs(mobj->momx) < mobj->scale && abs(mobj->momy) < mobj->scale && P_IsObjectOnGround(mobj)) { mobj->momx = mobj->momy = 0; P_SetMobjState(mobj, mobj->info->spawnstate); } 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_RAINBOWDASHRING: Obj_RainbowDashRingThink(mobj); break; default: // check mobj against possible water content, before movement code P_MobjCheckWater(mobj); // Extinguish fire objects in water if (mapnamespace != MNS_RINGRACERS && mobj->flags & MF_FIRE && (mapnamespace == MNS_SRB2KART ? mobj->type != MT_PUMA && mobj->type != MT_FIREBALL : !(mobj->eflags & MFE_TOUCHLAVA)) && (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER))) { P_KillMobj(mobj, NULL, NULL, 0); return false; } break; } return true; } static void P_FiringThink(mobj_t *mobj) { if (!mobj->target) return; if (mobj->health <= 0) return; if (mobj->state->action == (actionf_p1)A_Boss1Laser) { if (mobj->state->tics > 1) { var1 = mobj->state->var1; var2 = mobj->state->var2 & 65535; mobj->state->action(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_SNEAKERTV, 0, 10); // Super Sneakers SETMONITORCHANCES(MT_INV, 2, 0); // Invincibility SETMONITORCHANCES(MT_WHITETV, 3, 8); // Whirlwind Shield SETMONITORCHANCES(MT_GREENTV, 3, 8); // Elemental Shield SETMONITORCHANCES(MT_YELLOWTV, 2, 0); // Attraction Shield SETMONITORCHANCES(MT_BLUETV, 3, 3); // Force Shield SETMONITORCHANCES(MT_BLACKTV, 2, 0); // Armageddon Shield SETMONITORCHANCES(MT_MIXUPBOX, 0, 1); // Teleporters SETMONITORCHANCES(MT_RECYCLETV, 0, 1); // Recycler SETMONITORCHANCES(MT_PRUP, 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->fuse <= TICRATE && ((mobj->type == MT_RANDOMITEM && mobj->threshold == 69) || mobj->type == MT_EGGMANITEM || mobj->type == MT_FALLINGROCK)) mobj->renderflags ^= RF_DONTDRAW; mobj->fuse--; if (mobj->fuse) return true; if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjFuse)) || P_MobjWasRemoved(mobj)) ; else if (mobj->info->flags & MF_MONITOR) { P_MonitorFuseThink(mobj); return false; } else switch (mobj->type) { // gargoyle and snowman handled in P_PushableThinker, not here case MT_SPIKE: case MT_WALLSPIKE: P_SetMobjState(mobj, mobj->state->nextstate); mobj->fuse = mobj->args[0]; break; case MT_RANDOMITEM: if (mobj->flags2 & MF2_DONTRESPAWN) { ; } else if (gametyperules & GTR_BATTLEBOXES) { 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); // Transfer flags2 (strongbox, objectflip, bossnotrap) newmobj->flags2 = mobj->flags2; } P_RemoveMobj(mobj); // make sure they disappear return false; case MT_ITEMCAPSULE: if (mobj->spawnpoint) P_SpawnMapThing(mobj->spawnpoint); else { mobj_t *newMobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type); newMobj->threshold = mobj->threshold; newMobj->movecount = mobj->movecount; } P_RemoveMobj(mobj); 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)<angle, P_RandomRange(3,18)*(FRACUNIT/4)); debris->momz = P_RandomRange(4,8)<hnext; P_RemoveMobj(cur); cur = next; } P_RemoveMobj(mobj); return false; } case MT_NIGHTSCORE: P_RemoveMobj(mobj); return false; case MT_PLAYER: break; // don't remove case MT_SNEAKERPANELSPAWNER: { Obj_SneakerPanelSpawnerFuse(mobj); break; } 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->itnext && P_MobjWasRemoved(mobj->itnext)) P_SetTarget(&mobj->itnext, NULL); // Tick lifetime of mobj mobj->mobjlifetime++; if (mobj->flags & MF_NOTHINK) return; if ((mobj->flags & MF_BOSS) && (bossdisabled & (1 << mobj->args[0]))) return; mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG|MFE_JUSTBOUNCEDWALL|MFE_SLOPELAUNCHED); // sal: what the hell? is there any reason this isn't done, like, literally ANYWHERE else? P_SetTarget(&g_tm.floorthing, NULL); P_SetTarget(&g_tm.hitthing, NULL); if (udmf) { // Check for continuous sector special actions P_CheckMobjTouchingSectorActions(mobj, true, true); } else { // Sector flag MSF_TRIGGERLINE_MOBJ allows ANY mobj to trigger a linedef exec P_CheckMobjTrigger(mobj, false); } I_Assert(!P_MobjWasRemoved(mobj)); if (mobj->scale != mobj->destscale) { P_MobjScaleThink(mobj); // Slowly scale up/down to reach your destscale. if (P_MobjWasRemoved(mobj)) return; } if (mobj->type == MT_GHOST && mobj->fuse > 0) // Not guaranteed to be MF_SCENERY or not MF_SCENERY! { if (mobj->extravalue1 > 0) // Sonic Advance 2 mode { if (mobj->extravalue2 >= 2) { UINT32 dontdraw = RF_DONTDRAW; if (mobj->tracer) dontdraw &= ~(mobj->tracer->renderflags); if (mobj->extravalue2 == 2) // I don't know why the normal logic doesn't work for this. mobj->renderflags ^= dontdraw; else { if (mobj->fuse == mobj->extravalue2) mobj->renderflags &= ~(dontdraw); else mobj->renderflags |= dontdraw; } } } else if (mobj->extravalue3 <= 0) // Legacy mode bypasses this visibility system. { UINT32 dur = (mobj->flags2 & MF2_BOSSNOTRAP) ? (2*mobj->fuse)/3 : mobj->fuse/2; if (((mobj->renderflags & RF_TRANSMASK) >> RF_TRANSSHIFT) < ((NUMTRANSMAPS-1) - dur)) // fade out when nearing the end of fuse... mobj->renderflags = (mobj->renderflags & ~RF_TRANSMASK) | (((NUMTRANSMAPS-1) - dur) << RF_TRANSSHIFT); } } // Special thinker for scenery objects if (mobj->flags & MF_SCENERY) { P_MobjSceneryThink(mobj); return; } // Check for a Lua thinker first if (!mobj->player) { if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker)) || P_MobjWasRemoved(mobj)) return; } else if (!mobj->player->spectator) { // You cannot short-circuit the player thinker like you can other thinkers. LUA_HookMobj(mobj, MOBJ_HOOK(MobjThinker)); if (P_MobjWasRemoved(mobj)) return; } // if it's pushable, or if it would be pushable other than temporary disablement, use the // separate thinker if (mobj->flags & MF_PUSHABLE || (mobj->info->flags & MF_PUSHABLE && mobj->fuse)) { if (!P_MobjPushableThink(mobj)) return; } else if (mobj->flags & MF_BOSS) { if (!P_MobjBossThink(mobj)) return; } else if (mobj->health <= 0) // Dead things think differently than the living. { if (!P_MobjDeadThink(mobj)) return; } else { if (!P_MobjRegularThink(mobj)) return; } if (P_MobjWasRemoved(mobj)) return; // Destroy items sector special if (P_CanDeleteKartItem(mobj->type)) { if (mobj->health > 0 && P_MobjTouchingSectorSpecialFlag(mobj, SSF_DELETEITEMS)) { 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) return; if (mobj->threshold) S_StartSound(mobj, mobj->threshold); return; } // Check fuse if (mobj->fuse && !P_FuseThink(mobj)) return; I_Assert(mobj != NULL); I_Assert(!P_MobjWasRemoved(mobj)); if (mobj->momx || mobj->momy || (mobj->flags2 & MF2_SKULLFLY)) { P_XYMovement(mobj); if (P_MobjWasRemoved(mobj)) return; } // always do the gravity bit now, that's simpler // BUT CheckPosition only if wasn't done before. if (!(mobj->eflags & MFE_ONGROUND) || mobj->momz || ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz) || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz) || P_IsObjectInGoop(mobj)) { if (!P_ZMovement(mobj)) return; // mobj was removed P_CheckPosition(mobj, mobj->x, mobj->y, NULL); // 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_FLINGNIGHTSCHIP || mobj->type == MT_FLINGEMERALD || mobj->type == MT_BIGTUMBLEWEED || mobj->type == MT_LITTLETUMBLEWEED || mobj->type == MT_CANNONBALLDECOR || mobj->type == MT_FALLINGROCK) { P_TryMove(mobj, mobj->x, mobj->y, true, NULL); // 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); } K_UpdateTerrainOverlay(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 if (P_IsMiscCapObject(mobj->type)) // mobj is object we want on the list: P_AddMiscCap(mobj); // add to misccap 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, NULL); } return P_MobjWasRemoved(mobj) || (x == mobj->x && y == mobj->y && z == mobj->z); } // Unquick, unoptimized function for pushables void P_PushableThinker(mobj_t *mobj) { I_Assert(mobj != NULL); I_Assert(!P_MobjWasRemoved(mobj)); P_CheckMobjTrigger(mobj, true); // it has to be pushable RIGHT NOW for this part to happen if (mobj->flags & MF_PUSHABLE && !(mobj->momx || mobj->momy)) P_TryMove(mobj, mobj->x, mobj->y, true, NULL); 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; } } // 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, NULL); // Need this to pick up objects! if (P_MobjWasRemoved(mobj)) return; mobj->floorz = g_tm.floorz; mobj->ceilingz = g_tm.ceilingz; mobj->floorrover = g_tm.floorrover; mobj->ceilingrover = g_tm.ceilingrover; } 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_NOCONTEST)) // 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_BRIGHTMASK) == FF_FULLBRIGHT); thing->shadowcolor = 15; // Those have shadow by default 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_EGGMINE: case MT_EGGMINE_SHIELD: case MT_BALLHOG: case MT_SINK: case MT_ROCKETSNEAKER: case MT_SPB: case MT_ADVENTURESPIKEA: case MT_ADVENTURESPIKEB: case MT_ADVENTURESPIKEC: case MT_CDUFO: thing->shadowscale = 4*FRACUNIT/3; 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_FLOATINGITEM: case MT_ITEMCAPSULE: thing->shadowscale = FRACUNIT/2; break; case MT_THUNDERSHIELD: case MT_BUBBLESHIELD: case MT_FLAMESHIELD: case MT_BLUEFRUIT: case MT_ORANGEFRUIT: case MT_REDFRUIT: case MT_PINKFRUIT: case MT_RANDOMAUDIENCE: case MT_BLUECRAWLA: case MT_REDCRAWLA: case MT_GFZFISH: // Greenflower Fish case MT_GOLDBUZZ: case MT_REDBUZZ: case MT_AQUABUZZ: // AquaBuzz for ATZ case MT_JETTBOMBER: // Jetty-Syn Bomber case MT_JETTGUNNER: // Jetty-Syn Gunner case MT_CRAWLACOMMANDER: // Crawla Commander case MT_DETON: // Deton case MT_SKIM: // Skim mine dropper case MT_TURRET: case MT_POPUPTURRET: case MT_SHARP: // Sharp case MT_JETJAW: // Jet Jaw case MT_SNAILER: // Snailer case MT_VULTURE: // Vulture case MT_POINTY: // Pointy case MT_POINTYBALL: // Pointy Ball case MT_ROBOHOOD: // Robo-Hood case MT_FACESTABBER: // CastleBot FaceStabber case MT_EGGGUARD: // Egg Guard case MT_EGGSHIELD: // Egg Shield for Egg Guard case MT_GSNAPPER: // Green Snapper case MT_MINUS: // Minus case MT_SPRINGSHELL: // Spring Shell case MT_YELLOWSHELL: // Spring Shell (yellow) case MT_UNIDUS: // Unidus case MT_UNIBALL: // Unidus Ball case MT_GOOMBA: case MT_BLUEGOOMBA: case MT_KOOPA: case MT_MONOKUMA: case MT_ARIDTOAD: case MT_SRB1_CRAWLA: case MT_SRB1_BAT: case MT_SRB1_ROBOFISH: case MT_SRB1_VOLCANOGUY: case MT_SRB1_HOPPY: case MT_SRB1_HOPPYWATER: case MT_SRB1_HOPPYSKYLAB: case MT_SRB1_MMZFLYING: case MT_SRB1_UFO: case MT_SRB1_GRAYBOT: case MT_SRB1_ROBOTOPOLIS: case MT_SRB1_RBZBUZZ: case MT_SRB1_RBZSPIKES: case MT_SRB1_METALSONIC: case MT_SRB1_GOLDBOT: case MT_SRB1_GENREX: case MT_BOWLINGPIN: case MT_EXPLODINGBARREL: case MT_FROGGER: case MT_ROBRA: case MT_CHOMPER: case MT_BUZZBOMBER: case MT_GARGOYLE: case MT_GBA_BOO: case MT_FLYINGGARG: thing->shadowscale = FRACUNIT; break; case MT_RING: case MT_FLINGRING: case MT_COIN: case MT_FLINGCOIN: case MT_REDTEAMRING: case MT_BLUETEAMRING: case MT_REDFLAG: case MT_BLUEFLAG: case MT_BOUNCERING: case MT_AUTOMATICRING: case MT_INFINITYRING: case MT_RAILRING: case MT_EXPLOSIONRING: case MT_SCATTERRING: case MT_GRENADERING: case MT_BOUNCEPICKUP: case MT_RAILPICKUP: case MT_AUTOPICKUP: case MT_EXPLODEPICKUP: case MT_SCATTERPICKUP: case MT_GRENADEPICKUP: case MT_REDRING: case MT_THROWNBOUNCE: case MT_THROWNINFINITY: case MT_THROWNAUTOMATIC: case MT_THROWNSCATTER: case MT_THROWNEXPLOSION: case MT_THROWNGRENADE: case MT_EMBLEM: case MT_DOOD_BOX: case MT_DOOD_BALLOON: thing->shadowscale = FRACUNIT/3; break; case MT_SNEAKERPANEL: thing->shadowscale = 0; break; default: if (thing->flags & (MF_ENEMY|MF_BOSS)) thing->shadowscale = FRACUNIT; break; } } mobj_t *P_AllocateMobj(void) { mobj_t* mobj = (mobj_t*)Z_LevelPoolCalloc(sizeof(mobj_t)); mobj->thinker.alloctype = TAT_LEVELPOOL; mobj->thinker.size = sizeof(mobj_t); return mobj; } // // 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; if (type == MT_NULL) { #if 0 #ifdef PARANOIA I_Error("Tried to spawn MT_NULL\n"); #endif return NULL; #endif // Hack: Some code assumes that P_SpawnMobj can never return NULL // So replace MT_NULL with MT_RAY in the meantime // Remove when dealt properly CONS_Debug(DBG_GAMELOGIC, "Tried to spawn MT_NULL, using MT_RAY\n"); type = MT_RAY; } mobj = P_AllocateMobj(); // this is officially a mobj, declared as soon as possible. mobj->thinker.function = (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; mobj->gravity = 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; } // Sprite rendering mobj->spritexscale = mobj->spriteyscale = mobj->scale; mobj->spritexoffset = mobj->spriteyoffset = 0; mobj->dispoffset = info->dispoffset; mobj->floorspriteslope = NULL; // 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; // Set shadowscale here, before spawn hook so that Lua can change it P_DefaultMobjShadowScale(mobj); if (!(mobj->flags & MF_NOTHINK)) P_AddThinker(THINK_MOBJ, &mobj->thinker); // DANGER! This can cause P_SpawnMobj to return NULL! // Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks! if (LUA_HookMobj(mobj, MOBJ_HOOK(MobjSpawn))) { 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<info->damage<threshold = ball->radius + mobj->radius + FixedMul(ball->info->painchance, ball->scale); var1 = ball->state->var1, var2 = ball->state->var2; ball->state->action(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_BIGMINE: mobj->extravalue1 = FixedHypot(mobj->x, mobj->y)>>FRACBITS; 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: { static const UINT8 BALLOONCOLORS[] = { // Carnival Night balloon colors SKINCOLOR_KETCHUP, SKINCOLOR_SAPPHIRE, SKINCOLOR_TANGERINE, SKINCOLOR_JET }; mobj->color = BALLOONCOLORS[P_RandomKey(sizeof(BALLOONCOLORS))]; } break; case MT_KART_LEFTOVER: mobj->color = SKINCOLOR_RED; break; case MT_REDRING: // Make MT_REDRING red by default mobj->color = skincolor_redring; break; case MT_SMALLBUBBLE: // Bubbles eventually dissipate, in case they get caught somewhere. case MT_MEDIUMBUBBLE: case MT_EXTRALARGEBUBBLE: mobj->fuse += 30 * TICRATE; break; case MT_NIGHTSDRONE: nummaprings = -1; // no perfect bonus, rings are free break; case MT_EGGCAPSULE: mobj->reactiontime = 0; mobj->extravalue1 = mobj->cvmem =\ mobj->cusval = mobj->movecount =\ mobj->lastlook = mobj->extravalue2 = -1; break; case MT_REDTEAMRING: mobj->color = skincolor_redteam; break; case MT_BLUETEAMRING: mobj->color = skincolor_blueteam; break; case MT_RING: case MT_COIN: case MT_BLUEBALL: if (nummaprings >= 0) nummaprings++; break; // SRB2Kart case MT_EGGMINE: case MT_EGGMINE_SHIELD: { mobj_t *overlay = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY); P_SetMobjState(overlay, S_EGGMINE_OVERLAY); overlay->destscale = mobj->scale; P_SetScale(overlay, mobj->scale); P_SetTarget(&overlay->target, mobj); P_SetTarget(&overlay->tracer, mobj); overlay->flags2 |= MF2_LINKDRAW; overlay->dispoffset = -1; mobj->movefactor = TICRATE; mobj->threshold = 6; break; } case MT_BUBBLESHIELD: { // Spawn in the damage overlay mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z, MT_OVERLAY); spawn->destscale = mobj->scale; P_SetScale(spawn, mobj->scale); P_SetTarget(&mobj->tracer, spawn); P_SetTarget(&spawn->target, mobj); break; } case MT_BUBBLESHIELD_DEBRIS: { statenum_t shardset[] = {S_BUBBLEDEBRIS_1, S_BUBBLEDEBRIS_2, S_BUBBLEDEBRIS_3, S_BUBBLEDEBRIS_4, S_BUBBLEDEBRIS_5}; P_SetMobjState(mobj, shardset[P_RandomRange(0, 4)]); break; } case MT_ITEMCAPSULE: { // set default item & count #if 0 // set to 1 to test capsules with random items, e.g. with objectplace if (P_RandomChance(FRACUNIT/3)) mobj->threshold = KITEM_SUPERRING; else if (P_RandomChance(FRACUNIT/3)) mobj->threshold = KITEM_SPB; else if (P_RandomChance(FRACUNIT/3)) mobj->threshold = KITEM_ORBINAUT; else mobj->threshold = P_RandomRange(1, numkartitems - 1); mobj->movecount = P_RandomChance(FRACUNIT/3) ? 1 : P_RandomKey(32) + 1; #else mobj->threshold = KITEM_SUPERRING; // default item is super ring mobj->movecount = 1; #endif // set starting scale mobj->extravalue1 = mobj->scale; // this acts as the capsule's destscale; we're avoiding P_MobjScaleThink because we want aerial capsules not to scale from their center mobj->scalespeed >>= 1; P_SetScale(mobj, mobj->destscale = mapobjectscale >> 4); break; } 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_PINETREE: { 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_PINETREE_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))<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)<y + (P_RandomRange(-mobj->info->mass, mobj->info->mass)<z, 8<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<movefactor = mobj->z + (256<x, mobj->y, mobj->z, MT_SMK_ICEBLOCK_SIDE); P_SetTarget(&cur->target, mobj); cur->threshold = i; P_MoveOrigin(cur, cur->x + ((cur->radius>>FRACBITS) * FINECOSINE((FixedAngle((90*cur->threshold)<>ANGLETOFINESHIFT) & FINEMASK)), cur->y + ((cur->radius>>FRACBITS) * FINESINE((FixedAngle((90*cur->threshold)<>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_TUMBLEGEM: mobj->color = P_RandomKey(numskincolors - 1) + 1; break; case MT_DASHRING: Obj_RegularDashRingSpawn(mobj); break; case MT_RAINBOWDASHRING: Obj_RainbowDashRingSpawn(mobj); break; case MT_SNEAKERPANEL: Obj_SneakerPanelSpawn(mobj); break; case MT_SNEAKERPANELSPAWNER: Obj_SneakerPanelSpawnerSpawn(mobj); 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->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 && (mobj->flags & MF_RUNSPAWNFUNC)) { if (levelloading == true) { // 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(mobj); // DANGER! This can cause P_SpawnMobj to return NULL! // Avoid using MF_RUNSPAWNFUNC on mobjs whose spawn state expects target or tracer to already be set! if (P_MobjWasRemoved(mobj)) return NULL; } } if (CheckForReverseGravity && !(mobj->flags & MF_NOBLOCKMAP)) P_CheckGravity(mobj, false); R_AddMobjInterpolator(mobj); return mobj; } static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) { const mobjinfo_t *info = &mobjinfo[type]; state_t *st; fixed_t start_z = INT32_MIN; precipmobj_t *mobj = Z_LevelPoolCalloc(sizeof(precipmobj_t)); mobj->thinker.alloctype = TAT_LEVELPOOL; mobj->thinker.size = sizeof(precipmobj_t); 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 = 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 = FixedMul(-info->speed, mapobjectscale); if (info->speed < 0) { mobj->precipflags |= PCF_FLIP; } start_z = mobj->floorz; mobj->thinker.function = (actionf_p1)P_NullPrecipThinker; P_AddThinker(THINK_PRECIP, &mobj->thinker); P_CalculatePrecipFloor(mobj); if (mobj->floorz != start_z) { ; //mobj->precipflags |= PCF_FOF; } else { INT32 dmg = mobj->subsector->sector->damagetype; boolean sFlag = (mobj->precipflags & PCF_FLIP) ? (mobj->subsector->sector->flags & MSF_FLIPSPECIAL_CEILING) : (mobj->subsector->sector->flags & MSF_FLIPSPECIAL_FLOOR); boolean pitFloor = ((dmg == SD_DEATHPIT) && sFlag); boolean skyFloor = (mobj->precipflags & PCF_FLIP) ? (mobj->subsector->sector->ceilingpic == skyflatnum) : (mobj->subsector->sector->floorpic == skyflatnum); if (pitFloor || skyFloor) { mobj->precipflags |= PCF_PIT; } } R_ResetPrecipitationMobjInterpolationState(mobj); return mobj; } void *P_CreateFloorSpriteSlope(mobj_t *mobj) { if (mobj->floorspriteslope) Z_Free(mobj->floorspriteslope); mobj->floorspriteslope = Z_Calloc(sizeof(pslope_t), PU_LEVEL, NULL); mobj->floorspriteslope->normal.z = FRACUNIT; return (void *)mobj->floorspriteslope; } void P_RemoveFloorSpriteSlope(mobj_t *mobj) { if (mobj->floorspriteslope) Z_Free(mobj->floorspriteslope); mobj->floorspriteslope = NULL; } // // P_RemoveMobj // mapthing_t *itemrespawnque[ITEMQUESIZE]; tic_t itemrespawntime[ITEMQUESIZE]; size_t iquehead, iquetail; #ifdef PARANOIA //#define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed #endif void P_RemoveMobj(mobj_t *mobj) { I_Assert(mobj != NULL); if (P_MobjWasRemoved(mobj)) return; // something already removing this mobj. mobj->thinker.function = (actionf_p1)P_RemoveThinkerDelayed; // shh. no recursing. LUA_HookMobj(mobj, MOBJ_HOOK(MobjRemoved)); mobj->thinker.function = (actionf_p1)P_MobjThinker; // needed for P_UnsetThingPosition, etc. to work. // Rings only, please! if (mobj->spawnpoint && (mobj->type == MT_RING || mobj->type == MT_BLUEBALL) && !(mobj->flags2 & MF2_DONTRESPAWN)) { //CONS_Printf("added to queue at tic %d\n", leveltime); 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 (mobj->type == MT_BUBBLESHIELD && mobj->tracer && !mobj->tracer->player) { // Remove the tracer (overlay) if (!P_MobjWasRemoved(mobj->tracer)) P_RemoveMobj(mobj->tracer); P_SetTarget(&mobj->tracer, NULL); } if (P_IsKartItem(mobj->type)) P_RemoveKartItem(mobj); if (P_IsMiscCapObject(mobj->type)) P_RemoveMiscCap(mobj); if (mobj->player && mobj->player->followmobj) { P_RemoveMobj(mobj->player->followmobj); P_SetTarget(&mobj->player->followmobj, NULL); } mobj->health = 0; // Just because // unlink from sector and block lists P_UnsetThingPosition(mobj); if (sector_list) { P_DelSeclist(sector_list); sector_list = NULL; } mobj->flags |= MF_NOSECTOR|MF_NOBLOCKMAP; mobj->subsector = NULL; mobj->state = NULL; mobj->player = NULL; P_RemoveFloorSpriteSlope(mobj); // stop any playing sound S_StopSound(mobj); // killough 11/98: // // Remove any references to other mobjs. P_SetTarget(&mobj->target, 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); } } P_SetTarget(&mobj->itnext, NULL); P_RemoveThingTID(mobj); P_DeleteMobjStringArgs(mobj); R_RemoveMobjInterpolator(mobj); // free block if (!mobj->thinker.next) { // Uh-oh, the mobj doesn't think, P_RemoveThinker would never go through! INT32 prevreferences; prevreferences = mobj->thinker.references; P_AddThinker(THINK_MOBJ, (thinker_t *)mobj); mobj->thinker.references = prevreferences; } P_RemoveThinker((thinker_t *)mobj); #ifdef PARANOIA // Saved to avoid being scrambled like below... mobj->thinker.debug_mobjtype = mobj->type; #endif // DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error. #ifdef SCRAMBLE_REMOVED // Invalidate mobj_t data to cause crashes if accessed! memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t)); #endif } void P_FreePrecipMobj(precipmobj_t *mobj) { // unlink from sector and block lists P_UnsetPrecipThingPosition(mobj); if (precipsector_list) { P_DelPrecipSeclist(precipsector_list); precipsector_list = NULL; } // free block // Precipmobjs don't actually think using their thinker, // so the free cannot be delayed. P_UnlinkThinker((thinker_t*)mobj); } // Clearing out stuff for savegames void P_RemoveSavegameMobj(mobj_t *mobj) { if (((thinker_t *)mobj)->function == (actionf_p1)P_NullPrecipThinker) { // unlink from sector and block lists P_UnsetPrecipThingPosition((precipmobj_t *)mobj); if (precipsector_list) { P_DelPrecipSeclist(precipsector_list); precipsector_list = NULL; } } else { // unlink from tid chains P_RemoveThingTID(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; } P_DeleteMobjStringArgs(mobj); } // stop any playing sound S_StopSound(mobj); R_RemoveMobjInterpolator(mobj); // free block // Here we use the same code as R_RemoveThinkerDelayed, but without reference counting (we're removing everything so it shouldn't matter) and without touching currentthinker since we aren't in P_RunThinkers P_UnlinkThinker((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); static void P_SpawnPrecipitationAt(fixed_t basex, fixed_t basey) { INT32 j, k; const mobjtype_t type = precipprops[curWeather].type; const UINT8 randomstates = (UINT8)mobjinfo[type].damage; const boolean flip = (mobjinfo[type].speed < 0); fixed_t i, x, y, z, height; UINT16 numparticles = 0; boolean condition = false; subsector_t *precipsector = NULL; precipmobj_t *rainmo = NULL; // If mobjscale < FRACUNIT, each blockmap cell covers // more area so spawn more precipitation in that area. for (i = 0; i < FRACUNIT; i += mapobjectscale) { 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 (precipprops[curWeather].effects & PRECIPFX_WATERPARTICLES) { condition = false; if (precipsector->sector->ffloors) { ffloor_t *rover; for (rover = precipsector->sector->ffloors; rover; rover = rover->next) { if (!(rover->fofflags & FOF_EXISTS)) continue; if (!(rover->fofflags & FOF_SWIMMABLE)) continue; condition = true; break; } } } else { condition = (precipsector->sector->ceilingpic == skyflatnum); } if (precipsector->sector->flags & MSF_INVERTPRECIP) { condition = !condition; } if (!condition) { continue; } height = precipsector->sector->ceilingheight - precipsector->sector->floorheight; height = FixedDiv(height, mapobjectscale); // Exists, but is too small for reasonable precipitation. if (height < 64<sector->floorheight) : (precipsector->sector->ceilingheight); for (j = 0; j < numparticles; j++) { INT32 floorz; INT32 ceilingz; 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; } } } floorz = rainmo->floorz >> FRACBITS; ceilingz = rainmo->ceilingz >> FRACBITS; if (floorz < ceilingz) { // Randomly assign a height, now that floorz is set. rainmo->z = M_RandomRange(floorz, ceilingz) << FRACBITS; } else { // ...except if the floor is above the ceiling. rainmo->z = ceilingz << FRACBITS; } } } } void P_SpawnPrecipitation(void) { INT32 i; const mobjtype_t type = precipprops[curWeather].type; fixed_t basex, basey; if (dedicated || !cv_drawdist_precip.value || type == MT_NULL) 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; P_SpawnPrecipitationAt(basex, basey); } } // // 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; boolean sounds_rain = (rainsfx != sfx_None && (!leveltime || leveltime % rainfreq == 1)); // 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 (!sounds_rain && !sounds_thunder) return; // no need to calculate volume at ALL if (players[g_localplayers[0]].mo->subsector->sector->ceilingpic == skyflatnum) volume = 255; // Sky above? We get it full blast. else { INT64 x, y, yl, yh, xl, xh; fixed_t closedist, newdist; // Essentially check in a 1024 unit radius of the player for an outdoor area. #define RADIUSSTEP (64*FRACUNIT) #define SEARCHRADIUS (16*RADIUSSTEP) yl = yh = players[g_localplayers[0]].mo->y; yl -= SEARCHRADIUS; while (yl < INT32_MIN) yl += RADIUSSTEP; yh += SEARCHRADIUS; while (yh > INT32_MAX) yh -= RADIUSSTEP; xl = xh = players[g_localplayers[0]].mo->x; xl -= SEARCHRADIUS; while (xl < INT32_MIN) xl += RADIUSSTEP; xh += SEARCHRADIUS; while (xh > INT32_MAX) xh -= RADIUSSTEP; closedist = SEARCHRADIUS*2; #undef SEARCHRADIUS for (y = yl; y <= yh; y += RADIUSSTEP) for (x = xl; x <= xh; x += RADIUSSTEP) { if (R_PointInSubsectorFast((fixed_t)x, (fixed_t)y)->sector->ceilingpic != skyflatnum) // Found the outdoors! continue; newdist = S_CalculateSoundDistance(players[g_localplayers[0]].mo->x, players[g_localplayers[0]].mo->y, 0, (fixed_t)x, (fixed_t)y, 0); if (newdist < closedist) closedist = newdist; } #undef RADIUSSTEP volume = 255 - (closedist>>(FRACBITS+2)); } if (volume < 0) volume = 0; else if (volume > 255) volume = 255; if (sounds_rain) 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); } } /** 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) { INT32 i; for (i = NUMMOBJTYPES-1; i >= 0; 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; // wait time depends on player count for (p = 0; p < MAXPLAYERS; p++) { if (playeringame[p] && !players[p].spectator) pcount++; } #if 0 // set to 1 to enable quick respawns for testing if (true) time = 5*TICRATE; else #endif { if (pcount == 1) // No respawn when alone { return; } else if (pcount > 1) { time = (120 * TICRATE) / (pcount - 1); // 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; } } } // only respawn items when cv_itemrespawn is on if (!cv_itemrespawn.value) return; // 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); //CONS_Printf("respawn happened on tic %d, irt %d, t %d\n", leveltime, itemrespawntime[iquetail], time); // 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; // MAXPLAYERS if exiting 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].exiting) { pcount = MAXPLAYERS; break; } 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 || boss) { // 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 p->spectatorreentry = 0; //(cv_spectatorreentry.value * TICRATE); } 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; p->spectatorreentry = 0; //(cv_spectatorreentry.value * TICRATE); 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; p->spectatorreentry = 0; //(cv_spectatorreentry.value * TICRATE); } // 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; } if (leveltime > introtime) p->flashing = K_GetKartFlashing(p); // Babysitting deterrent mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER); mobj->player = p; P_SetTarget(&p->mo, mobj); mobj->angle = mobj->old_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). R_ApplySkin(mobj, &skins[p->skin]); P_SetupStateAnimation(mobj, mobj->state); if (p->voice_id) { // During respawns, do a quick check on our voice ID to make sure // our skin can use it. // This should prevent bots from always having Sonic's voice. mobj->voice = &skins[p->skin].voices[p->voice_id]; } mobj->health = 1; p->playerstate = PST_LIVE; if (!p->exiting || !p->realtime) { p->realtime = leveltime; } p->followitem = skins[p->skin].followitem; if (K_PlayerShrinkCheat(p) == true) { mobj->destscale = FixedMul(mobj->destscale, SHRINK_SCALE); } // set the scale to the mobj's destscale so settings get correctly set. if we don't, they sometimes don't. P_SetScale(mobj, mobj->destscale); P_FlashPal(p, 0, 0); // Resets p->grieftime = 0; p->spinoutrot = 0; if (gametyperules & GTR_ITEMARROWS) { mobj_t *overheadarrow = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height + 16*FRACUNIT, MT_PLAYERARROW); P_SetTarget(&overheadarrow->target, mobj); overheadarrow->renderflags |= RF_DONTDRAW; P_SetScale(overheadarrow, mobj->destscale); } if (gametyperules & GTR_BUMPERS) { if (p->spectator) { // HEY! No being cheap... p->bumper = 0; } else if ((p->bumper > 0) || (leveltime < starttime)) { if ((leveltime < starttime)) // Start of the map? { p->bumper = K_StartingBumperCount(); } K_SpawnPlayerBattleBumpers(p); } else if (p->bumper <= 0 && (gametyperules & GTR_KARMA)) { 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; // fix the verticalflip flag to prevent camera jank (the player mobj initially spawns at the origin) P_CheckGravity(players[playernum].mo, false); // Update interpolation mobj->old_x = mobj->x; mobj->old_y = mobj->y; mobj->old_z = mobj->z; mobj->old_angle = mobj->angle; 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; if (p->spectator == false) { 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); // set the player's starting waypoint if (!K_UsingLegacyCheckpoints()) p->currentwaypoint = K_GetSpawnWaypointForMobj(p->mo); } // 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*FRACUNIT); } //spawn at the origin as a desperation move if there is no mapthing // set Z height sector = R_PointInSubsector(x, y)->sector; floor = sector->f_slope ? P_GetSlopeZAt(sector->f_slope, x, y) : sector->floorheight; ceiling = sector->c_slope ? P_GetSlopeZAt(sector->c_slope, x, y) : sector->ceilingheight; ceilingspawn = ceiling - mobjinfo[MT_PLAYER].height; if (mthing) { fixed_t offset = udmf ? mthing->z << FRACBITS : (mthing->options >> ZSHIFT) << FRACBITS; if (p->respawn) offset += 128*mapobjectscale; // Setting the spawnpoint's args[0] will make the player start on the ceiling // Objectflip inverts if (!!(mthing->args[0]) ^ !!(mthing->options & MTF_OBJECTFLIP)) z = ceilingspawn - offset; else z = floor + offset; if (mthing->options & MTF_OBJECTFLIP) // flip the player! { mobj->eflags |= MFE_VERTICALFLIP; mobj->flags2 |= MF2_OBJECTFLIP; } if (mthing->args[0]) P_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->z == mobj->floorz) mobj->eflags |= MFE_ONGROUND; mobj->angle = angle; P_AfterPlayerSpawn(playernum); } void P_MovePlayerToStarpost(INT32 playernum) { fixed_t z; sector_t *sector; fixed_t floor, ceiling; player_t *p = &players[playernum]; mobj_t *mobj = p->mo; I_Assert(mobj != NULL); P_UnsetThingPosition(mobj); mobj->x = p->starpostx; mobj->y = p->starposty; P_SetThingPosition(mobj); sector = R_PointInSubsector(mobj->x, mobj->y)->sector; floor = sector->f_slope ? P_GetSlopeZAt(sector->f_slope, mobj->x, mobj->y) : sector->floorheight; ceiling = sector->c_slope ? P_GetSlopeZAt(sector->c_slope, mobj->x, mobj->y) : sector->ceilingheight; z = CLAMP(p->starpostz, floor, ceiling); // no more copyslope floorspawning jank if (mobj->player->starpostflip) z -= 128*mapobjectscale + mobj->height; else z += 128*mapobjectscale; z = CLAMP(z, floor, ceiling - mobjinfo[MT_PLAYER].height); //mobj->player->starpostflip = 0; // why??? mobj->floorz = floor; mobj->ceilingz = ceiling; mobj->z = z; if (mobj->z == mobj->floorz) mobj->eflags |= MFE_ONGROUND; mobj->angle = p->starpostangle; //p->kartstuff[k_waypoint] = p->kartstuff[k_starpostwp]; // SRB2kart P_AfterPlayerSpawn(playernum); //if (!(netgame || multiplayer)) // leveltime = p->starposttime; } 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. // Horizontal springs, float additional units unless args[0] is set. case MT_YELLOWHORIZ: case MT_REDHORIZ: case MT_BLUEHORIZ: case MT_GRAYHORIZ: offset += mthing->args[0] ? 0 : 16*FRACUNIT; break; // Ring-like items, float additional units unless args[0] is set. case MT_SPIKEBALL: case MT_EMBLEM: case MT_RING: case MT_BLUEBALL: case MT_EMMY: offset += mthing->args[0] ? 0 : 24*FRACUNIT; break; // This object does not have an offset default: break; } if (!(dz + offset) && mthing->layer == 0) // 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, mthing->layer, flip, mthing->scale); } static void SpreadFinishLine(line_t *line, line_t *prev, INT32 args) { size_t i; for (i = 0; i < numlines; i++) { line_t *line2 = &lines[i]; if (line2 != line && line2 != prev && (line2->slopetype == ST_HORIZONTAL || line2->slopetype == ST_VERTICAL) && line2->slopetype == line->slopetype && (line->v1 == line2->v1 || line->v2 == line2->v2 || line->v1 == line2->v2 || line->v2 == line2->v1)) { boolean flipped = line->v1 == line2->v1 || line->v2 == line2->v2 ? TMCFF_FLIP : 0; line2->special = 2001; // Finish Line line2->activation = SPAC_CROSS|SPAC_REPEATSPECIAL; line2->args[0] = args ^ flipped; SpreadFinishLine(line2, line, args ^ flipped); } } } 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 == FLOOR_SLOPE_THING || mthing->type == CEILING_SLOPE_THING) // Slope anchors || (mthing->type >= 600 && mthing->type <= 611) // Special placement patterns || mthing->type == 1713) // Hoops { return true; // These are handled elsewhere. } else if (K_UsingPatchedMap() && mthing->type >= 6502 && mthing->type <= 6503) // mappatch things! { fixed_t x = mthing->x << FRACBITS, y = mthing->y << FRACBITS; sector_t *sec = R_PointInSubsector(x, y)->sector; line_t *line = P_FindNearestLine(x, y, sec, -1); if (line != NULL) switch (mthing->type) { case 6502: // Spawn Finish Line usedspawnfinishline = true; line->special = 2001; line->activation = SPAC_CROSS|SPAC_REPEATSPECIAL; line->args[0] = mthing->args[0]; if (mthing->args[1] == 0) // don't spread? SpreadFinishLine(line, NULL, mthing->args[0]); break; case 6503: break; } return true; } 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; } case MT_RING: { if ((K_RingsActive() == false) ) return false; break; } case MT_EMERALD1: case MT_EMERALD2: case MT_EMERALD3: case MT_EMERALD4: case MT_EMERALD5: case MT_EMERALD6: case MT_EMERALD7: { if (gametype == GT_RACE || gametype == GT_BATTLE) // they don't spawn in kart so I guess i'll do this? return false; if (metalrecording) return false; // Metal Sonic isn't for collecting emeralds. if (emeralds & mobjinfo[i].speed) // You already have this emerald! return false; break; } case MT_ITEMCAPSULE: { boolean isRingCapsule = mthing->args[0] < 1 || mthing->args[0] == KITEM_SUPERRING || mthing->args[0] >= numkartitems; // don't spawn ring capsules in with rings off. if (isRingCapsule && (K_RingsActive() == false)) return false; // in record attack, only spawn ring capsules // (behavior can be inverted with the Extra flag, i.e. item capsule spawns and ring capsule does not) if (modeattacking && (!(mthing->args[2] & TMICF_INVERTTIMEATTACK) == !isRingCapsule)) return false; } break; case MT_COIN: case MT_BLUEBALL: { if (gametype == GT_RACE || gametype == GT_BATTLE) // they don't spawn in kart so I guess i'll do this? return false; break; } case MT_NIGHTSWING: { if (gametype == GT_RACE || gametype == GT_BATTLE) // they don't spawn in kart so I guess i'll do this? return false; break; } default: break; } // No bosses outside of a combat situation. // (just in case we want boss arenas to do double duty as battle maps) // TODO: Look at this latter. Kart maps use stuff like brak eggman. //if (!bossinfo.boss && (mobjinfo[i].flags & MF_BOSS)) //return false; 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 ((i == MT_RANDOMITEM) && (gametyperules & (GTR_PAPERITEMS|GTR_CIRCUIT)) == (GTR_PAPERITEMS|GTR_CIRCUIT) && !bossinfo.boss) 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; INT16 tagnum = mthing->tid; while (emblem) { if (emblem->type == ET_GLOBAL && emblem->tag == tagnum) break; emblem = M_GetLevelEmblems(-1); } if (!emblem) { CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, tagnum); 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; const size_t mthingi = (size_t)(mthing - mapthings); mlength = abs(mthing->args[0]); mnumspokes = mthing->args[1] + 1; mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes) >> ANGLETOFINESHIFT; mwidth = max(0, mthing->args[2]); mspeed = abs(mthing->args[3] << (mapnamespace == MNS_SRB2KART ? 0 : 4)); mphase = mthing->args[4] % 360; mpinch = mthing->args[5] % 360; mnumnospokes = mthing->args[6]; mminlength = max(0, min(mlength - 1, mthing->args[7])); mpitch = mthing->pitch % 360; myaw = mthing->angle % 360; mroll = mthing->roll % 360; CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n" "Length is %d (minus %d)\n" "Speed is %d\n" "Phase is %d\n" "Yaw is %d\n" "Pitch is %d\n" "No. of spokes is %d (%d antispokes)\n" "Pinch is %d\n" "Roll is %d\n" "Width is %d\n", sizeu1(mthingi), mlength, mminlength, mspeed, mphase, myaw, mpitch, mnumspokes, mnumnospokes, mpinch, mroll, mwidth); if (mnumnospokes > 0 && (mnumnospokes < mnumspokes)) mnumnospokes = mnumspokes/mnumnospokes; else mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes) : 0); mobj->lastlook = mspeed; mobj->movecount = mobj->lastlook; mobj->angle = FixedAngle(myaw << FRACBITS); *doangle = false; mobj->threshold = (FixedAngle(mpitch << FRACBITS) >> ANGLETOFINESHIFT); mobj->movefactor = mpinch; mobj->movedir = 0; // Mobjtype selection switch (mobj->type) { case MT_CUSTOMMACEPOINT: macetype = mthing->stringargs[0] ? get_number(mthing->stringargs[0]) : MT_NULL; chainlink = mthing->stringargs[1] ? get_number(mthing->stringargs[1]) : MT_NULL; break; case MT_CHAINPOINT: if (mthing->args[8] & TMM_DOUBLESIZE) { macetype = MT_BIGGRABCHAIN; chainlink = MT_BIGMACECHAIN; } else { macetype = MT_SMALLGRABCHAIN; chainlink = MT_SMALLMACECHAIN; } mchainlike = true; break; default: if (mthing->args[8] & TMM_DOUBLESIZE) { macetype = MT_BIGMACE; chainlink = MT_BIGMACECHAIN; } else { macetype = MT_SMALLMACE; chainlink = MT_SMALLMACECHAIN; } break; } if (!macetype && !chainlink) return true; if (mobj->type == MT_CHAINPOINT) { if (!mlength) return true; } else mlength++; firsttype = macetype; // Adjustable direction if (mthing->args[8] & TMM_ALLOWYAWCONTROL) mobj->flags |= MF_SLIDEME; // Swinging if (mthing->args[8] & TMM_SWING) { mobj->flags2 |= MF2_STRONGBOX; mmin = ((mnumnospokes > 1) ? 1 : 0); } else mmin = mnumspokes; // If over distance away, don't move UNLESS this flag is applied if (mthing->args[8] & TMM_ALWAYSTHINK) mobj->flags2 |= MF2_BOSSNOTRAP; // Make the links the same type as the end - repeated below if ((mobj->type != MT_CHAINPOINT) && ((mthing->args[8] & TMM_MACELINKS) == TMM_MACELINKS)) // exclusive or { linktype = macetype; radiusfactor = 2; // Double the radius. } else radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1); if (!mchainlike) mchainlike = (firsttype == chainlink); widthfactor = (mchainlike ? 1 : 2); mflagsapply = (mthing->args[8] & TMM_CLIP) ? 0 : (MF_NOCLIP|MF_NOCLIPHEIGHT); mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0); meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0); msound = (mchainlike ? 0 : (mwidth & 1)); // Quick and easy preparatory variable setting mphase = (FixedAngle(mphase << FRACBITS) >> ANGLETOFINESHIFT); mroll = (FixedAngle(mroll << FRACBITS) >> ANGLETOFINESHIFT); #define makemace(mobjtype, dist, moreflags2) {\ 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->args[8] & TMM_SILENT)); mdocenter = (macetype && (mthing->args[8] & TMM_CENTERLINK)); // The actual spawning of spokes while (mnumspokes-- > 0) { // Offsets if (mthing->args[8] & TMM_SWING) // Swinging mroll = (mroll - mspokeangle) & FINEMASK; else // Spinning mphase = (mphase - mspokeangle) & FINEMASK; if (mnumnospokes && !(mnumspokes % mnumnospokes)) // Skipping a "missing" spoke { if (mobj->type != MT_CHAINMACEPOINT) continue; linktype = chainlink; firsttype = ((mthing->args[8] & TMM_DOUBLESIZE) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN); mmaxlength = 1 + (mlength - 1) * radiusfactor; radiusfactor = widthfactor = 1; } else { if (mobj->type == MT_CHAINMACEPOINT) { // Make the links the same type as the end - repeated above if (mthing->args[8] & TMM_MACELINKS) { linktype = macetype; radiusfactor = 2; } else radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1); firsttype = macetype; widthfactor = 2; } mmaxlength = mlength; } mwidthset = mwidth; mlengthset = mminlength; if (mdocenter) // Innermost link makemace(linktype, 0, 0); // Out from the center... if (linktype) { while ((++mlengthset) < mmaxlength) makemace(linktype, radiusfactor*mlengthset, 0); } else mlengthset = mmaxlength; // Outermost mace/link if (firsttype) makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH); if (!mwidth) { if (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, zdist; INT32 type, numdivisions, anglespeed, ticcount; angle_t angledivision; INT32 line; const size_t mthingi = (size_t)(mthing - mapthings); if (!udmf) return false; // Find the corresponding linedef special, using args[6] as tag line = mthing->args[6] ? Tag_FindLineSpecial(15, mthing->args[6]) : -1; type = mthing->stringargs[0] ? get_number(mthing->stringargs[0]) : MT_PARTICLE; ticcount = mthing->args[4]; if (ticcount < 1) ticcount = 3; numdivisions = mthing->args[0]; if (numdivisions) { radius = mthing->args[1] << FRACBITS; anglespeed = (mthing->args[3]) % 360; angledivision = 360/numdivisions; } else { numdivisions = 1; // Simple trick to make P_ParticleGenSceneryThink simpler. radius = 0; anglespeed = 0; angledivision = 0; } speed = abs(mthing->args[2]) << FRACBITS; if (mthing->options & MTF_OBJECTFLIP) speed *= -1; zdist = abs(mthing->args[5]) << FRACBITS; CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n" "Radius is %d\n" "Speed is %d\n" "Anglespeed is %d\n" "Numdivisions is %d\n" "Angledivision is %d\n" "Type is %d\n" "Tic separation is %d\n", sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount); if (line == -1) CONS_Debug(DBG_GAMELOGIC, "Spawn Z is %d\nZ dist is %d\n", mobj->z, zdist); else CONS_Debug(DBG_GAMELOGIC, "Heights are taken from control sector\n"); mobj->angle = 0; mobj->movefactor = speed; mobj->lastlook = numdivisions; mobj->movedir = angledivision*ANG1; mobj->movecount = anglespeed*ANG1; mobj->friction = radius; mobj->threshold = type; mobj->reactiontime = ticcount; mobj->extravalue1 = zdist; mobj->cvmem = line; mobj->watertop = mobj->waterbottom = 0; return true; } static mobj_t *P_MakeSoftwareCorona(mobj_t *mo, INT32 height) { mobj_t *corona = P_SpawnMobjFromMobj(mo, 0, 0, height<sprite = SPR_FLAM; corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12); corona->tics = -1; return corona; } void P_InitSkyboxPoint(mobj_t *mobj, mapthing_t *mthing) { mtag_t tag = mthing->tid; if (tag < 0 || tag > 15) { CONS_Debug(DBG_GAMELOGIC, "P_InitSkyboxPoint: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings))); return; } if (mthing->args[0]) P_SetTarget(&skyboxcenterpnts[tag], mobj); else P_SetTarget(&skyboxviewpnts[tag], mobj); } static boolean P_MapAlreadyHasStarPost(mobj_t *mobj) { thinker_t *th; mobj_t *mo2; for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next) { if (th->function == (actionf_p1)P_RemoveThinkerDelayed) continue; mo2 = (mobj_t *)th; if (mo2 == mobj) continue; if (mo2->type == MT_STARPOST && mo2->health == mobj->health) return true; } return false; } static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle) { boolean override = LUA_HookMapThingSpawn(mobj, mthing); if (P_MobjWasRemoved(mobj)) return false; if (override) return true; switch (mobj->type) { case MT_EMBLEM: { if (!P_SetupEmblem(mthing, mobj)) return false; break; } case MT_SKYBOX: { P_InitSkyboxPoint(mobj, mthing); break; } case MT_EGGSTATUE: if (mthing->args[1]) { mobj->color = SKINCOLOR_GOLD; mobj->colorized = true; } break; case MT_FAN: if (mthing->args[1] & TMF_INVISIBLE) { P_UnsetThingPosition(mobj); if (sector_list) { P_DelSeclist(sector_list); sector_list = NULL; } mobj->flags |= MF_NOSECTOR; // this flag basically turns it invisible P_SetThingPosition(mobj); } if (mthing->args[1] & TMF_NODISTANCECHECK) mobj->flags2 |= MF2_AMBUSH; if (mthing->args[0]) mobj->health = mthing->args[0]; else mobj->health = FixedMul(mobj->subsector->sector->ceilingheight - mobj->subsector->sector->floorheight, 3*(FRACUNIT/4)) >> FRACBITS; break; case MT_BALLOON: if (mthing->stringargs[0]) mobj->color = get_number(mthing->stringargs[0]); if (mthing->args[0]) mobj->flags2 |= MF2_AMBUSH; break; case MT_FLAME: if (mthing->args[0]) { mobj_t *corona = P_MakeSoftwareCorona(mobj, 20); P_SetScale(corona, (corona->destscale = mobj->scale*3)); P_SetTarget(&mobj->tracer, corona); } break; case MT_WATERDRIP: mobj->tics = 3*TICRATE + mthing->args[0]; break; case MT_FLAMEJET: case MT_VERTICALFLAMEJET: mobj->movecount = mthing->args[0]; mobj->threshold = mthing->args[1]; mobj->movedir = mthing->args[2]; if (mthing->args[3]) mobj->flags2 |= MF2_AMBUSH; break; case MT_MACEPOINT: case MT_CHAINMACEPOINT: case MT_CHAINPOINT: 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_NIGHTSBUMPER: // Pitch of the bumper is set in 30 degree increments. mobj->threshold = ((mthing->pitch/30) + 3) % 12; P_SetMobjState(mobj, mobj->info->spawnstate + mobj->threshold); break; case MT_TUBEWAYPOINT: { UINT8 sequence = mthing->args[0]; UINT8 id = mthing->args[1]; mobj->health = id; mobj->threshold = sequence; P_AddTubeWaypoint(sequence, id, mobj); break; } break; case MT_AXIS: // Inverted if args[3] is set if (mthing->args[3]) mobj->flags2 |= MF2_AMBUSH; mobj->radius = abs(mthing->args[2]) << FRACBITS; // FALLTHRU case MT_AXISTRANSFER: case MT_AXISTRANSFERLINE: // Mare it belongs to mobj->threshold = min(mthing->args[0], 7); // # in the mare mobj->health = mthing->args[1]; mobj->flags2 |= MF2_AXIS; break; case MT_STARPOST: mobj->health = mthing->args[0] + 1; if (!P_MapAlreadyHasStarPost(mobj)) numstarposts++; break; case MT_BOSS3WAYPOINT: mobj->health = mthing->angle; mobj->movecount = mthing->extrainfo; P_SetTarget(&mobj->tracer, boss3cap); P_SetTarget(&boss3cap, mobj); numbosswaypoints++; break; case MT_SPIKE: // Pop up spikes! if (mthing->args[0]) { mobj->flags &= ~MF_SCENERY; mobj->fuse = mthing->args[1]; } if (mthing->args[2] & TMSF_RETRACTED) P_SetMobjState(mobj, mobj->info->meleestate); // Use per-thing collision for spikes unless the intangible flag is checked. if (!(mthing->args[2] & TMSF_INTANGIBLE) && !metalrecording) { P_UnsetThingPosition(mobj); mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT); mobj->flags |= MF_SOLID; P_SetThingPosition(mobj); } break; case MT_WALLSPIKE: // Pop up spikes! if (mthing->args[0]) { mobj->flags &= ~MF_SCENERY; mobj->fuse = mthing->args[1]; } if (mthing->args[2] & TMSF_RETRACTED) P_SetMobjState(mobj, mobj->info->meleestate); // Use per-thing collision for spikes unless the intangible flag is checked. if (!(mthing->args[2] & TMSF_INTANGIBLE) && !metalrecording) { P_UnsetThingPosition(mobj); mobj->flags &= ~(MF_NOBLOCKMAP | MF_NOCLIPHEIGHT); mobj->flags |= MF_SOLID; P_SetThingPosition(mobj); } // spawn base { const angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); // the mobj's own angle hasn't been set quite yet so... const fixed_t baseradius = mobj->radius - mobj->scale; mobj_t* base = P_SpawnMobj( mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius), mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius), mobj->z, MT_WALLSPIKEBASE); 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->args[0]) { fixed_t offset = FixedMul(16*FRACUNIT, mobj->scale); mobj->momx += P_RandomChance(FRACUNIT/2) ? offset : -offset; mobj->momy += P_RandomChance(FRACUNIT/2) ? offset : -offset; mobj->momz += offset; mobj->flags2 |= MF2_AMBUSH; } break; // SRB2Kart case MT_WAYPOINT: { const fixed_t mobjscale = mapheaderinfo[gamemap-1]->default_waypoint_radius; mtag_t tag = mthing->tid; 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 // watertop is the waypoint's drift settings mobj->threshold = mthing->args[0]; mobj->movecount = tag; if (mthing->args[2] & TMWPF_DISABLED) { mobj->extravalue1 = 0; // The waypoint is disabled if extra is on } else { mobj->extravalue1 = 1; } if (mthing->args[2] & TMWPF_SHORTCUT) { mobj->lastlook = 1; // the waypoint is a shortcut if objectspecial is on } else { mobj->lastlook = 0; } if (mthing->args[2] & TMWPF_NORESPAWN) { mobj->reactiontime = 0; // Can't respawn at if Ambush is on } else { mobj->reactiontime = 1; } if (mthing->args[2] & TMWPF_FINISHLINE) { mobj->extravalue2 = 1; // args[2] of 1 means the waypoint is at the finish line mobj->reactiontime = 0; // Also don't respawn at finish lines line_t *finishline = P_FindNearestLine(mobj->x, mobj->y, mobj->subsector->sector, K_UsingPatchedMap() ? -1 : 2001); // case 2001: Finish Line if (finishline != NULL) { P_UnsetThingPosition(mobj); P_ClosestPointOnLine(mobj->x, mobj->y, finishline, (vertex_t *)&mobj->x); P_SetThingPosition(mobj); if (K_UsingPatchedMap() && !usedspawnfinishline) { CONS_Alert(CONS_WARNING, "Using waypoints to generate finish lines is deprecated, please add mapthing type 6502 to your patch. Read the wiki article for details\n"); finishline->special = 2001; // Finish Line finishline->activation = SPAC_CROSS|SPAC_REPEATSPECIAL; finishline->args[0] = mthing->args[4]; if (!(mthing->args[4] & TMCFF_NOSPREAD)) SpreadFinishLine(finishline, NULL, mthing->args[4]); } } } else { mobj->extravalue2 = 0; } mobj->watertop = mthing->args[3]; // 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_WAYPOINT_ANCHOR: { // Get the drift parameters from the anchor's spawnpoint. // Drift left mobj->extravalue1 = ((mthing->options & MTF_OBJECTSPECIAL) >> 1); if (mthing->options & MTF_AMBUSH) { // Drift right mobj->extravalue1 = ((mthing->options & MTF_AMBUSH) >> 1); } // Powerslide offset mobj->extravalue1 = max(0, min(4, mobj->extravalue1 - (mthing->options & 1))); if (mthing->extrainfo) { // If the parameter is set at all, this anchor ends the drift. mobj->extravalue1 = 5; } 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->args[2]) { mobj->extravalue1 = 0; } else { mobj->extravalue1 = 1; } // Steering amount if (mthing->args[1] == 0) { mobj->extravalue2 = 4; } else { mobj->extravalue2 = mthing->args[1]; } break; } case MT_RANDOMITEM: { mobj->flags2 |= MF2_BOSSNOTRAP; // mark as here on map start break; } case MT_ITEMCAPSULE: { // we have to adjust for reverse gravity early so that the below grounded checks work if (mthing->options & MTF_OBJECTFLIP) { mobj->eflags |= MFE_VERTICALFLIP; mobj->flags2 |= MF2_OBJECTFLIP; mobj->z += FixedMul(mobj->extravalue1, mobj->info->height) - mobj->height; } // determine whether this capsule is grounded or aerial if (mobj->subsector->sector->ffloors) P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 0); if (mobj->subsector->polyList) P_AdjustMobjFloorZ_PolyObjs(mobj, mobj->subsector); if (!P_IsObjectOnGround(mobj)) mobj->flags |= MF_NOGRAVITY; // Item type static kartitemtype_e rrtypes[] = { KITEM_SNEAKER, KITEM_ROCKETSNEAKER, KITEM_INVINCIBILITY, KITEM_BANANA, KITEM_EGGMAN, KITEM_ORBINAUT, KITEM_JAWZ, KITEM_MINE, MAXKARTITEMS, // LANDMINE KITEM_BALLHOG, KITEM_SPB, KITEM_GROW, KITEM_SHRINK, KITEM_THUNDERSHIELD, KITEM_BUBBLESHIELD, KITEM_FLAMESHIELD, KITEM_HYUDORO, KITEM_POGOSPRING, KITEM_SUPERRING, KITEM_KITCHENSINK, MAXKARTITEMS, // DROPTARGET MAXKARTITEMS, // GARDENTOP MAXKARTITEMS, // GACHABOM MAXKARTITEMS, // STONESHOE MAXKARTITEMS, // TOXOMISTER }; if (mthing->stringargs[0] != NULL) mobj->threshold = DEH_FindKartItem(mthing->stringargs[0]); else if (mapnamespace == MNS_RINGRACERS && mthing->args[0] > 0 && (size_t)mthing->args[0] < sizeof(rrtypes)/sizeof(*rrtypes)) mobj->threshold = rrtypes[mthing->args[0] - 1]; // Parameter = extra items (x5 for rings) mobj->movecount += mthing->args[1]; // Ambush = double size (grounded) / half size (aerial) if (!(mthing->args[2] & TMICF_INVERTSIZE) == !P_IsObjectOnGround(mobj)) { mobj->extravalue1 <<= 1; mobj->scalespeed <<= 1; } const fixed_t blimit = FixedDiv(MAPBLOCKSIZE, mobj->info->radius); if (mobj->extravalue1 > blimit) { // don't make them larger than the blockmap can handle mobj->extravalue1 = blimit; } break; } case MT_AAZTREE_HELPER: { fixed_t top = mobj->z; UINT8 i; UINT8 locnumsegs = abs(mthing->args[0])+2; UINT8 numleaves = max(3, (abs(mthing->args[1]+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<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))<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<options & MTF_OBJECTSPECIAL) // Mario Block version mobj->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); else { fixed_t zheight = mobj->z; mobj_t *tokenobj; if (mthing->options & MTF_OBJECTFLIP) zheight += mobj->height-FixedMul(mobjinfo[MT_TOKEN].height, mobj->scale); // align with emmy properly! tokenobj = P_SpawnMobj(mobj->x, mobj->y, zheight, MT_TOKEN); P_SetTarget(&mobj->tracer, tokenobj); tokenobj->destscale = mobj->scale; P_SetScale(tokenobj, mobj->scale); if (mthing->options & MTF_OBJECTFLIP) // flip token to match emmy { tokenobj->eflags |= MFE_VERTICALFLIP; tokenobj->flags2 |= MF2_OBJECTFLIP; } } // We advanced tokenbits earlier due to the return check. // Subtract 1 here for the correct value. mobj->health = 1 << (tokenbits - 1); break; } case MT_REDSPRING: case MT_BLUESPRING: case MT_GRAYSPRING: case MT_YELLOWSPRING: case MT_INVISSPRING: { if (mthing->args[0] & TMSPR_NOGRAVITY) mobj->gravity = 0; break; } case MT_YELLOWDIAG: case MT_REDDIAG: case MT_BLUEDIAG: case MT_GRAYDIAG: { mobj->angle = FixedAngle(mthing->angle << FRACBITS); if (mthing->args[0] & TMSPR_NOGRAVITY) mobj->gravity = 0; if (mthing->args[0] & TMSPR_ROTATEEXTRA) mobj->angle += ANGLE_22h; *doangle = false; break; } case MT_DASHRING: case MT_RAINBOWDASHRING: { Obj_DashRingSetup(mobj, mthing); break; } case MT_SNEAKERPANEL: { Obj_SneakerPanelSetup(mobj, mthing); break; } case MT_SNEAKERPANELSPAWNER: { Obj_SneakerPanelSpawnerSetup(mobj, mthing); break; } default: break; } if (P_MobjWasRemoved(mobj)) return false; if (mobj->flags & MF_BOSS) { if (mthing->args[1]) // No egg trap for this boss mobj->flags2 |= MF2_BOSSNOTRAP; } if (mobj->flags & MF_PUSHABLE) { switch (mthing->args[0]) { case TMP_NORMAL: default: break; case TMP_SLIDE: mobj->flags2 |= MF2_SLIDEPUSH; mobj->flags |= MF_BOUNCE; break; case TMP_IMMOVABLE: mobj->flags &= ~MF_PUSHABLE; break; case TMP_CLASSIC: mobj->flags2 |= MF2_CLASSICPUSH; break; } } if (mobj->flags & MF_SPRING && mobj->info->painchance == 3) { if (mthing->args[0]) mobj->flags2 |= MF2_AMBUSH; } if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0) { switch (mthing->args[1]) { case TMMR_SAME: default: break; case TMMR_WEAK: mobj->flags2 |= MF2_AMBUSH; break; case TMMR_STRONG: mobj->flags2 |= MF2_STRONGBOX; } } // Custom ambient sounds if ((mobj->flags & MF_AMBIENT) && mobj->type != MT_AMBIENT) { mobj->threshold = mobj->info->seesound; mobj->health = mobj->info->spawnhealth; } return true; } // TODO: 2.3: Delete (Pre-UDMF backwards compatibility stuff) static void P_SetAmbush(mapthing_t *mthing, mobj_t *mobj) { if (mobj->type == MT_NIGHTSBUMPER || mobj->type == MT_AXIS || mobj->type == MT_AXISTRANSFER || mobj->type == MT_AXISTRANSFERLINE || mobj->type == MT_NIGHTSBUMPER || mobj->type == MT_STARPOST) return; if ((mthing->options & MTF_OBJECTSPECIAL) && (mobj->flags & MF_PUSHABLE)) return; mobj->flags2 |= MF2_AMBUSH; } static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i) { mobj_t *mobj = NULL; boolean doangle = true; size_t arg = SIZE_MAX; mobj = P_SpawnMobj(x, y, z, i); mobj->spawnpoint = mthing; if (doangle) { mobj->angle = FixedAngle(mthing->angle << FRACBITS); } mobj->pitch = FixedAngle(mthing->pitch << FRACBITS); mobj->roll = FixedAngle(mthing->roll << FRACBITS); P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale)); mobj->destscale = FixedMul(mobj->destscale, mthing->scale); mobj->spritexscale = mthing->spritexscale; mobj->spriteyscale = mthing->spriteyscale; mobj->tid = mthing->tid; P_AddThingTID(mobj); mobj->special = mthing->special; for (arg = 0; arg < NUM_MAPTHING_ARGS; arg++) { mobj->args[arg] = mthing->args[arg]; } for (arg = 0; arg < NUM_MAPTHING_STRINGARGS; arg++) { size_t len = 0; if (mthing->stringargs[arg]) { len = strlen(mthing->stringargs[arg]); } if (len == 0) { Z_Free(mobj->stringargs[arg]); mobj->stringargs[arg] = NULL; continue; } mobj->stringargs[arg] = Z_Realloc(mobj->stringargs[arg], len + 1, PU_LEVEL, NULL); memcpy(mobj->stringargs[arg], mthing->stringargs[arg], len + 1); } for (arg = 0; arg < NUM_SCRIPT_ARGS; arg++) { mobj->script_args[arg] = mthing->args[arg]; } for (arg = 0; arg < NUM_SCRIPT_STRINGARGS; arg++) { size_t len = 0; if (mthing->script_stringargs[arg]) { len = strlen(mthing->script_stringargs[arg]); } if (len == 0) { Z_Free(mobj->script_stringargs[arg]); mobj->script_stringargs[arg] = NULL; continue; } mobj->script_stringargs[arg] = Z_Realloc(mobj->script_stringargs[arg], len + 1, PU_LEVEL, NULL); memcpy(mobj->script_stringargs[arg], mthing->script_stringargs[arg], len + 1); } if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle)) { if (P_MobjWasRemoved(mobj)) return NULL; return mobj; } mthing->mobj = mobj; if ((mapnamespace == MNS_SRB2KART) && (mthing->options & MTF_AMBUSH)) P_SetAmbush(mthing, mobj); // Generic reverse gravity for individual objects flag. if (mthing->options & MTF_OBJECTFLIP) { mobj->eflags |= MFE_VERTICALFLIP; mobj->flags2 |= MF2_OBJECTFLIP; } return mobj; } // // P_SpawnMapThing // The fields of the mapthing should // already be in host byte order. // mobj_t *P_SpawnMapThing(mapthing_t *mthing) { mobjtype_t i; mobj_t *mobj = NULL; fixed_t x, y, z; if (!mthing->type) return mobj; // Ignore type-0 things as NOPs if (mthing->type == 3328) // 3D Mode start Thing return mobj; if (!objectplacing && P_SpawnNonMobjMapThing(mthing)) return mobj; i = P_GetMobjtype(mthing->type); if (i == MT_UNKNOWN) CONS_Alert(CONS_WARNING, M_GetText("Unknown thing type %d placed at (%d, %d)\n"), mthing->type, mthing->x, mthing->y); // Skip all returning/substitution code in objectplace. if (!objectplacing) { if (!P_AllowMobjSpawn(mthing, i)) return mobj; i = P_GetMobjtypeSubstitute(mthing, i); if (i == MT_NULL) // Don't spawn mobj return mobj; } x = mthing->x << FRACBITS; y = mthing->y << FRACBITS; z = P_GetMapThingSpawnHeight(i, mthing, x, y); return P_SpawnMobjFromMapThing(mthing, x, y, z, i); } void P_SpawnHoop(mapthing_t *mthing) { if (metalrecording) return; mobj_t *mobj = NULL; mobj_t *nextmobj = NULL; mobj_t *hoopcenter; TMatrix *pitchmatrix, *yawmatrix; fixed_t radius = mthing->args[0] << FRACBITS; fixed_t sizefactor = 4*FRACUNIT; fixed_t hoopsize = radius/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, mthing->layer, 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); hoopcenter->movedir = mthing->pitch; pitchmatrix = RotateXMatrix(FixedAngle(hoopcenter->movedir << FRACBITS)); hoopcenter->movecount = mthing->angle; 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); memcpy(&v, res, sizeof(v)); res = VectorMatrixMultiply(v, *yawmatrix); 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); memcpy(&v, res, sizeof(v)); res = VectorMatrixMultiply(v, *yawmatrix); 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); } static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle) { 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; boolean isloopend = (mthing->type == mobjinfo[MT_LOOPENDPOINT].doomednum); mobj_t *loopanchor = NULL; boolean inclusive = isloopend; 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->layer, mthing->options & MTF_OBJECTFLIP, mthing->scale); if (isloopend) { const fixed_t length = (numitems - 1) * horizontalspacing / 2; mobj_t *loopcenter = Obj_FindLoopCenter(mthing->tid); // Spawn the anchor at the middle point of the line loopanchor = P_SpawnMobjFromMapThing(&dummything, x + FixedMul(length, FINECOSINE(fineangle)), y + FixedMul(length, FINESINE(fineangle)), z, MT_LOOPCENTERPOINT); if (P_MobjWasRemoved(loopanchor)) { // No recovery. return; } loopanchor->spawnpoint = NULL; if (!P_MobjWasRemoved(loopanchor)) Obj_LinkLoopAnchor(loopanchor, loopcenter, mthing->args[0]); } for (r = 0; r < numitems; r++) { mobjtype_t itemtype = itemtypes[r % numitemtypes]; if (itemtype == MT_NULL) continue; dummything.type = mobjinfo[itemtype].doomednum; if (inclusive) mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); x += FixedMul(horizontalspacing, FINECOSINE(fineangle)); y += FixedMul(horizontalspacing, FINESINE(fineangle)); z += (mthing->options & MTF_OBJECTFLIP) ? -verticalspacing : verticalspacing; if (!inclusive) mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype); if (P_MobjWasRemoved(mobj)) continue; mobj->spawnpoint = NULL; if (!isloopend) continue; Obj_InitLoopEndpoint(mobj, loopanchor); } } static void P_SpawnSingularItemRow(mapthing_t *mthing, mobjtype_t itemtype, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle) { mobjtype_t itemtypes[1] = { itemtype }; P_SpawnItemRow(mthing, itemtypes, 1, numitems, horizontalspacing, verticalspacing, fixedangle); } static void P_SpawnItemCircle(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t size) { 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; 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, mthing->layer, 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)); memcpy(&v, res, sizeof(v)); mobj = P_SpawnMobjFromMapThing(&dummything, x + v[0], y + v[1], z + v[2], itemtype); if (P_MobjWasRemoved(mobj)) continue; mobj->spawnpoint = NULL; mobj->z -= mobj->height/2; } } static void P_ParseItemTypes(char *itemstring, mobjtype_t *itemtypes, UINT8 *numitemtypes) { char *tok; *numitemtypes = 0; if (itemstring) { char *stringcopy = Z_Malloc(strlen(itemstring) + 1, PU_LEVEL, NULL); memcpy(stringcopy, itemstring, strlen(itemstring)); stringcopy[strlen(itemstring)] = '\0'; tok = strtok(stringcopy, " "); while (tok && *numitemtypes < 128) { itemtypes[*numitemtypes] = get_number(tok); tok = strtok(NULL, " "); (*numitemtypes)++; } Z_Free(stringcopy); } else { //If no types are supplied, default to ring itemtypes[0] = MT_RING; *numitemtypes = 1; } } void P_SpawnItemPattern(mapthing_t *mthing) { switch (mthing->type) { // Special placement patterns case 600: // 5 vertical rings (yellow spring) P_SpawnSingularItemRow(mthing, MT_RING, 5, 0, 64*FRACUNIT, 0); return; case 601: // 5 vertical rings (red spring) P_SpawnSingularItemRow(mthing, MT_RING, 5, 0, 128*FRACUNIT, 0); return; case 602: // 5 diagonal rings (yellow spring) P_SpawnSingularItemRow(mthing, MT_RING, 5, 64*FRACUNIT, 64*FRACUNIT, mthing->angle); return; case 603: // 10 diagonal rings (red spring) P_SpawnSingularItemRow(mthing, MT_RING, 10, 64*FRACUNIT, 64*FRACUNIT, mthing->angle); 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); return; } case 610: // Generic item row { mobjtype_t itemtypes[128]; //If you want to have a row with more than 128 different object types, you're crazy. UINT8 numitemtypes; if (!udmf) return; P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes); P_SpawnItemRow(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS, mthing->args[2] << FRACBITS, mthing->angle); return; } case 611: // Generic item circle { mobjtype_t itemtypes[128]; //If you want to have a circle with more than 128 different object types, you're crazy. UINT8 numitemtypes; if (!udmf) return; CONS_Printf("Itemstring: %s\n", mthing->stringargs[0]); P_ParseItemTypes(mthing->stringargs[0], itemtypes, &numitemtypes); P_SpawnItemCircle(mthing, itemtypes, numitemtypes, mthing->args[0], mthing->args[1] << FRACBITS); return; } default: return; } } void P_SpawnItemLine(mapthing_t *mt1, mapthing_t *mt2) { const mobjtype_t type = P_GetMobjtype(mt1->type); const fixed_t diameter = 2 * FixedMul(mobjinfo[type].radius, mapobjectscale); const fixed_t dx = (mt2->x - mt1->x) * FRACUNIT; const fixed_t dy = (mt2->y - mt1->y) * FRACUNIT; const fixed_t dist = FixedHypot(dx, dy); const angle_t angle = R_PointToAngle2(0, 0, dx, dy); P_SpawnSingularItemRow(mt1, type, (dist / diameter) + 1, diameter, 0, AngleFixed(angle) / FRACUNIT); } // // 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, NULL)) { 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 (source->player && 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 (source->player && 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_ScaleFromMap // Scales a number relative to the mapobjectscale. // fixed_t P_ScaleFromMap(fixed_t n, fixed_t scale) { return FixedMul(n, FixedDiv(scale, mapobjectscale)); } // // P_SpawnMobjFromMobjUnscaled // Spawns an object with offsets relative to the position of another object. // Scale, gravity flip, etc. is taken into account automatically. // mobj_t *P_SpawnMobjFromMobjUnscaled(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zofs, mobjtype_t type) { mobj_t *newmobj; newmobj = P_SpawnMobj(mobj->x + xofs, mobj->y + yofs, mobj->z + zofs, type); if (!newmobj) return NULL; newmobj->destscale = P_ScaleFromMap(mobj->destscale, newmobj->destscale); P_SetScale(newmobj, P_ScaleFromMap(mobj->scale, newmobj->scale)); if (mobj->eflags & MFE_VERTICALFLIP) { newmobj->eflags |= MFE_VERTICALFLIP; newmobj->flags2 |= MF2_OBJECTFLIP; newmobj->z = mobj->z + mobj->height - zofs - newmobj->height; newmobj->old_z = mobj->old_z + mobj->height - zofs - newmobj->height; newmobj->old_z2 = mobj->old_z2 + mobj->height - zofs - newmobj->height; } else { newmobj->old_z = mobj->old_z + zofs; newmobj->old_z2 = mobj->old_z2 + zofs; } newmobj->old_x2 = mobj->old_x2 + xofs; newmobj->old_y2 = mobj->old_y2 + yofs; newmobj->old_x = mobj->old_x + xofs; newmobj->old_y = mobj->old_y + yofs; // This angle hack is needed for Lua scripts that set the angle after // spawning, to avoid erroneous interpolation. if (mobj->player) { newmobj->old_angle2 = mobj->player->old_drawangle2; newmobj->old_angle = mobj->player->old_drawangle; } else { newmobj->old_angle2 = mobj->old_angle2; newmobj->old_angle = mobj->old_angle; } newmobj->old_scale2 = mobj->old_scale2; newmobj->old_scale = mobj->old_scale; newmobj->old_spritexscale = mobj->old_spritexscale; newmobj->old_spriteyscale = mobj->old_spriteyscale; newmobj->old_spritexoffset = mobj->old_spritexoffset; newmobj->old_spriteyoffset = mobj->old_spriteyoffset; return newmobj; } // // 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; newmobj->destscale = P_ScaleFromMap(mobj->destscale, newmobj->destscale); P_SetScale(newmobj, P_ScaleFromMap(mobj->scale, newmobj->scale)); if (mobj->eflags & MFE_VERTICALFLIP) { newmobj->eflags |= MFE_VERTICALFLIP; newmobj->flags2 |= MF2_OBJECTFLIP; newmobj->z = mobj->z + mobj->height - zofs - newmobj->height; newmobj->old_z = mobj->old_z + mobj->height - zofs - newmobj->height; newmobj->old_z2 = mobj->old_z2 + mobj->height - zofs - newmobj->height; } else { newmobj->old_z = mobj->old_z + zofs; newmobj->old_z2 = mobj->old_z2 + zofs; } newmobj->old_x2 = mobj->old_x2 + xofs; newmobj->old_y2 = mobj->old_y2 + yofs; newmobj->old_x = mobj->old_x + xofs; newmobj->old_y = mobj->old_y + yofs; // This angle hack is needed for Lua scripts that set the angle after // spawning, to avoid erroneous interpolation. if (mobj->player) { newmobj->old_angle2 = mobj->player->old_drawangle2; newmobj->old_angle = mobj->player->old_drawangle; } else { newmobj->old_angle2 = mobj->old_angle2; newmobj->old_angle = mobj->old_angle; } newmobj->old_scale2 = mobj->old_scale2; newmobj->old_scale = mobj->old_scale; newmobj->old_spritexscale = mobj->old_spritexscale; newmobj->old_spriteyscale = mobj->old_spriteyscale; newmobj->old_spritexoffset = mobj->old_spritexoffset; newmobj->old_spriteyoffset = mobj->old_spriteyoffset; return newmobj; } // // P_GetMobjHead & P_GetMobjFeet // Returns the top and bottom of an object, follows appearance, not physics, // in reverse gravity. // fixed_t P_GetMobjHead(const mobj_t *mobj) { return P_IsObjectFlipped(mobj) ? mobj->z : mobj->z + mobj->height; } fixed_t P_GetMobjFeet(const mobj_t *mobj) { /* | | | | /--\------/ | | | ----------------- */ return P_IsObjectFlipped(mobj) ? mobj->z + mobj->height : mobj->z; } // // P_GetMobjGround // Returns the object's floor, or ceiling in reverse gravity. // fixed_t P_GetMobjGround(const mobj_t *mobj) { return P_IsObjectFlipped(mobj) ? mobj->ceilingz : mobj->floorz; } // // P_GetMobjZMovement // Returns the Z momentum of the object, accounting for slopes if the object is grounded // fixed_t P_GetMobjZMovement(mobj_t *mo) { pslope_t *slope = mo->standingslope; angle_t angDiff; fixed_t speed; if (!P_IsObjectOnGround(mo)) return mo->momz; if (!slope) return 0; angDiff = R_PointToAngle2(0, 0, mo->momx, mo->momy) - slope->xydirection; speed = FixedHypot(mo->momx, mo->momy); return P_ReturnThrustY(mo, slope->zangle, P_ReturnThrustX(mo, angDiff, speed)); } kartvoice_t *P_GetMobjVoice(const mobj_t *mo) { if (P_MobjWasRemoved(mo)) { // Nonexistent or NULL object. return NULL; } if (mo->voice) { return mo->voice; } #ifdef PARANOIA // Scream at the idiot that let this happen :^) CONS_Printf( "PARANOIA/P_GetMobjVoice: %p MT_%s passed to function with NULL voice\n", (void*)mo, P_MobjTypeName(mo) ); #endif return NULL; } // // Thing IDs / tags // // TODO: Replace this system with taglist_t instead. // The issue is that those require a static numbered ID // to determine which struct it belongs to, which mobjs // don't really have. // #define TID_HASH_CHAINS (131) static mobj_t *TID_Hash[TID_HASH_CHAINS]; // // P_InitTIDHash // Initializes mobj tag hash array // void P_InitTIDHash(void) { memset(TID_Hash, 0, TID_HASH_CHAINS * sizeof(mobj_t *)); } // // P_AddThingTID // Adds a mobj to the hash array // void P_AddThingTID(mobj_t *mo) { I_Assert(!P_MobjWasRemoved(mo)); if (mo->tid <= 0) { // 0 is no TID, and negative // values are reserved. mo->tid = 0; mo->tid_next = NULL; mo->tid_prev = NULL; return; } // Insert at the head of this chain INT32 key = mo->tid % TID_HASH_CHAINS; mo->tid_next = TID_Hash[key]; mo->tid_prev = &TID_Hash[key]; TID_Hash[key] = mo; // Connect to any existing things in chain if (mo->tid_next != NULL) { mo->tid_next->tid_prev = &(mo->tid_next); } } // // P_RemoveThingTID // Removes a mobj from the hash array // void P_RemoveThingTID(mobj_t *mo) { if (mo->tid != 0 && mo->tid_prev != NULL) { // Fix the gap this would leave. *(mo->tid_prev) = mo->tid_next; if (mo->tid_next != NULL) { mo->tid_next->tid_prev = mo->tid_prev; } mo->tid_prev = NULL; mo->tid_next = NULL; } // Remove TID. mo->tid = 0; } // // P_SetThingTID // Changes a mobj's TID // void P_SetThingTID(mobj_t *mo, mtag_t tid) { P_RemoveThingTID(mo); if (P_MobjWasRemoved(mo)) { // Do not assign if it is going to be removed. return; } mo->tid = tid; P_AddThingTID(mo); } // // P_FindMobjFromTID // Mobj tag search function. // mobj_t *P_FindMobjFromTID(mtag_t tid, mobj_t *i, mobj_t *activator) { if (tid <= 0) { if (tid == 0) { // 0 grabs the activator, if applicable, // for some ACS functions. if (i != NULL) { // Don't do more than once. return NULL; } return activator; } else if (tid >= -MAXPLAYERS) { // -1 to -MAXPLAYERS returns an arbritrary player's object. INT32 playerID = -tid - 1; player_t *player = &players[ playerID ]; if (playeringame[playerID] == true && player->spectator == false) { return player->mo; } return NULL; } // Invalid input, return NULL. return NULL; } i = (i != NULL) ? i->tid_next : TID_Hash[tid % TID_HASH_CHAINS]; while (i != NULL && i->tid != tid) { i = i->tid_next; } return i; } void P_DeleteMobjStringArgs(mobj_t *mobj) { size_t i = SIZE_MAX; for (i = 0; i < NUM_MAPTHING_STRINGARGS; i++) { Z_Free(mobj->stringargs[i]); mobj->stringargs[i] = NULL; } for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) { Z_Free(mobj->script_stringargs[i]); mobj->script_stringargs[i] = NULL; } }