// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2022 by Sally "TehRealSalt" Cochenour // Copyright (C) 2022 by Kart Krew // // 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 spb.c /// \brief Self Propelled Bomb item code. #include "../doomdef.h" #include "../doomstat.h" #include "../info.h" #include "../k_kart.h" #include "../k_objects.h" #include "../m_random.h" #include "../p_local.h" #include "../r_main.h" #include "../s_sound.h" #include "../g_game.h" #include "../z_zone.h" #include "../k_waypoint.h" #include "../k_respawn.h" #define SPB_SEEKTEST #define SPB_SLIPTIDEDELTA (ANG1 * 3) #define SPB_STEERDELTA (ANGLE_90 - ANG10) #define SPB_DEFAULTSPEED (FixedMul(mapobjectscale, K_GetKartSpeedFromStat(9) * 2)) #define SPB_ACTIVEDIST (1024 * FRACUNIT) #define SPB_HOTPOTATO (2*TICRATE) #define SPB_MAXSWAPS (2) #define SPB_FLASHING (TICRATE) #define SPB_CHASETIMESCALE (60*TICRATE) #define SPB_CHASETIMEMUL (3*FRACUNIT) #define SPB_SEEKTURN (FRACUNIT/8) #define SPB_CHASETURN (FRACUNIT/4) #define SPB_MANTA_SPACING (2750 * FRACUNIT) #define SPB_MANTA_VSTART (150) #define SPB_MANTA_VRATE (60) #define SPB_MANTA_VMAX (100) enum { SPB_MODE_SEEK, SPB_MODE_CHASE, SPB_MODE_WAIT, }; #define spb_mode(o) ((o)->extravalue1) #define spb_modetimer(o) ((o)->extravalue2) #define spb_nothink(o) ((o)->threshold) #define spb_intangible(o) ((o)->cvmem) #define spb_lastplayer(o) ((o)->lastlook) #define spb_speed(o) ((o)->movefactor) #define spb_pitch(o) ((o)->movedir) #define spb_chasetime(o) ((o)->watertop) // running out of variables here... #define spb_swapcount(o) ((o)->health) #define spb_curwaypoint(o) ((o)->cusval) #define spb_manta_vscale(o) ((o)->movecount) #define spb_manta_totaldist(o) ((o)->reactiontime) #define spb_owner(o) ((o)->target) #define spb_chase(o) ((o)->tracer) static void SPBMantaRings(mobj_t *spb) { fixed_t vScale = INT32_MAX; fixed_t spacing = INT32_MAX; fixed_t finalDist = INT32_MAX; const fixed_t floatHeight = 24 * spb->scale; fixed_t floorDist = INT32_MAX; if (leveltime % SPB_MANTA_VRATE == 0) { spb_manta_vscale(spb) = max(spb_manta_vscale(spb) - 1, SPB_MANTA_VMAX); } spacing = FixedMul(SPB_MANTA_SPACING, spb->scale); spacing = FixedMul(spacing, K_GetKartGameSpeedScalar(gamespeed)); vScale = FixedDiv(spb_manta_vscale(spb) * FRACUNIT, 100 * FRACUNIT); finalDist = FixedMul(spacing, vScale); floorDist = abs(P_GetMobjFeet(spb) - P_GetMobjGround(spb)); spb_manta_totaldist(spb) += P_AproxDistance(spb->momx, spb->momy); if (spb_manta_totaldist(spb) > finalDist && floorDist <= floatHeight) { spb_manta_totaldist(spb) = 0; Obj_MantaRingCreate( spb, spb_owner(spb), #ifdef SPB_SEEKTEST NULL #else spb_chase(spb) #endif ); } } static void SpawnSPBDust(mobj_t *spb) { // The easiest way to spawn a V shaped cone of dust from the SPB is simply to spawn 2 particles, and to both move them to the sides in opposite direction. mobj_t *dust; fixed_t sx; fixed_t sy; fixed_t sz = spb->floorz; angle_t sa = spb->angle - ANG1*60; INT32 i; if (spb->eflags & MFE_VERTICALFLIP) { sz = spb->ceilingz; } if ((leveltime & 1) && abs(spb->z - sz) < FRACUNIT*64) // Only every other frame. Also don't spawn it if we're way above the ground. { // Determine spawning position next to the SPB: for (i = 0; i < 2; i++) { sx = 96 * FINECOSINE(sa >> ANGLETOFINESHIFT); sy = 96 * FINESINE(sa >> ANGLETOFINESHIFT); dust = P_SpawnMobjFromMobj(spb, sx, sy, 0, MT_SPBDUST); dust->z = sz; dust->momx = spb->momx/2; dust->momy = spb->momy/2; dust->momz = spb->momz/2; // Give some of the momentum to the dust P_SetScale(dust, spb->scale * 2); dust->color = SKINCOLOR_RED; dust->colorized = true; dust->angle = spb->angle - FixedAngle(FRACUNIT*90 - FRACUNIT*180*i); // The first one will spawn to the right of the spb, the second one to the left. P_Thrust(dust, dust->angle, 6*dust->scale); K_MatchGenericExtraFlags(dust, spb); sa += ANG1*120; // Add 120 degrees to get to mo->angle + ANG1*60 } } } // Spawns SPB slip tide. To be used when the SPB is turning. // Modified version of K_SpawnAIZDust. Maybe we could merge those to be cleaner? // dir should be either 1 or -1 to determine where to spawn the dust. static void SpawnSPBSliptide(mobj_t *spb, SINT8 dir) { fixed_t newx; fixed_t newy; mobj_t *spark; angle_t travelangle; fixed_t sz = spb->floorz; if (spb->eflags & MFE_VERTICALFLIP) { sz = spb->ceilingz; } travelangle = K_MomentumAngle(spb); if ((leveltime & 1) && abs(spb->z - sz) < FRACUNIT*64) { newx = P_ReturnThrustX(spb, travelangle - (dir*ANGLE_45), 24*FRACUNIT); newy = P_ReturnThrustY(spb, travelangle - (dir*ANGLE_45), 24*FRACUNIT); spark = P_SpawnMobjFromMobj(spb, newx, newy, 0, MT_SPBDUST); spark->z = sz; P_SetMobjState(spark, S_KARTAIZDRIFTSTRAT); P_SetTarget(&spark->target, spb); spark->colorized = true; spark->color = SKINCOLOR_RED; spark->angle = travelangle + (dir * ANGLE_90); P_SetScale(spark, (spark->destscale = spb->scale*3/2)); spark->momx = (6*spb->momx)/5; spark->momy = (6*spb->momy)/5; K_MatchGenericExtraFlags(spark, spb); } } // Used for seeking and when SPB is trailing its target from way too close! static void SpawnSPBSpeedLines(mobj_t *spb) { mobj_t *fast = P_SpawnMobjFromMobj(spb, P_RandomRange(-24, 24) * FRACUNIT, P_RandomRange(-24, 24) * FRACUNIT, (spb->info->height / 2) + (P_RandomRange(-24, 24) * FRACUNIT), MT_FASTLINE ); P_SetTarget(&fast->target, spb); fast->angle = K_MomentumAngle(spb); fast->color = SKINCOLOR_RED; fast->colorized = true; K_MatchGenericExtraFlags(fast, spb); } static fixed_t SPBDist(mobj_t *a, mobj_t *b) { return P_AproxDistance(P_AproxDistance( a->x - b->x, a->y - b->y), a->z - b->z ); } static void SPBTurn( fixed_t destSpeed, angle_t destAngle, fixed_t *editSpeed, angle_t *editAngle, fixed_t lerp, SINT8 *returnSliptide) { INT32 delta = AngleDeltaSigned(destAngle, *editAngle); fixed_t dampen = FRACUNIT; // Slow down when turning; it looks better and makes U-turns not unfair dampen = FixedDiv((180 * FRACUNIT) - AngleFixed(abs(delta)), 180 * FRACUNIT); *editSpeed = FixedMul(destSpeed, dampen); delta = FixedMul(delta, lerp); // Calculate sliptide effect during seeking. if (returnSliptide != NULL) { const boolean isSliptiding = (abs(delta) >= SPB_SLIPTIDEDELTA); SINT8 sliptide = 0; if (isSliptiding == true) { if (delta < 0) { sliptide = -1; } else { sliptide = 1; } } *returnSliptide = sliptide; } *editAngle += delta; } static void SetSPBSpeed(mobj_t *spb, fixed_t xySpeed, fixed_t zSpeed) { spb->momx = FixedMul(FixedMul( xySpeed, FINECOSINE(spb->angle >> ANGLETOFINESHIFT)), FINECOSINE(spb_pitch(spb) >> ANGLETOFINESHIFT) ); spb->momy = FixedMul(FixedMul( xySpeed, FINESINE(spb->angle >> ANGLETOFINESHIFT)), FINECOSINE(spb_pitch(spb) >> ANGLETOFINESHIFT) ); spb->momz = FixedMul( zSpeed, FINESINE(spb_pitch(spb) >> ANGLETOFINESHIFT) ); } static boolean SPBSeekSoundPlaying(mobj_t *spb) { return (S_SoundPlaying(spb, sfx_spbska) || S_SoundPlaying(spb, sfx_spbskb) || S_SoundPlaying(spb, sfx_spbskc)); } static void SPBSeek(mobj_t *spb, player_t *bestPlayer) { const fixed_t desiredSpeed = SPB_DEFAULTSPEED; waypoint_t *curWaypoint = NULL; waypoint_t *destWaypoint = NULL; fixed_t dist = INT32_MAX; fixed_t activeDist = INT32_MAX; fixed_t destX = spb->x; fixed_t destY = spb->y; fixed_t destZ = spb->z; angle_t destAngle = spb->angle; angle_t destPitch = 0U; fixed_t xySpeed = desiredSpeed; fixed_t zSpeed = desiredSpeed; SINT8 sliptide = 0; fixed_t steerDist = INT32_MAX; mobj_t *steerMobj = NULL; boolean circling = false; size_t i; spb_lastplayer(spb) = -1; // Just make sure this is reset if (bestPlayer == NULL || bestPlayer->mo == NULL || P_MobjWasRemoved(bestPlayer->mo) == true || bestPlayer->mo->health <= 0 || (bestPlayer->respawn.state != RESPAWNST_NONE)) { // No one there? Completely STOP. spb->momx = spb->momy = spb->momz = 0; if (bestPlayer == NULL) { spbplace = -1; } return; } // Found someone, now get close enough to initiate the slaughter... P_SetTarget(&spb_chase(spb), bestPlayer->mo); spbplace = bestPlayer->position; dist = SPBDist(spb, spb_chase(spb)); activeDist = FixedMul(SPB_ACTIVEDIST, spb_chase(spb)->scale); if (spb_swapcount(spb) > SPB_MAXSWAPS + 1) { // Too much hot potato. // Go past our target and explode instead. if (spb->fuse == 0) { spb->fuse = 2*TICRATE; } } #ifndef SPB_SEEKTEST // Easy debug switch else { if (dist <= activeDist) { S_StopSound(spb); S_StartSound(spb, spb->info->attacksound); spb_mode(spb) = SPB_MODE_CHASE; // TARGET ACQUIRED spb_swapcount(spb)++; spb_modetimer(spb) = SPB_HOTPOTATO; spb_intangible(spb) = SPB_FLASHING; spb_speed(spb) = desiredSpeed; return; } } #endif if (SPBSeekSoundPlaying(spb) == false) { if (dist <= activeDist * 3) { S_StartSound(spb, sfx_spbskc); } else if (dist <= activeDist * 6) { S_StartSound(spb, sfx_spbskb); } else { S_StartSound(spb, sfx_spbska); } } // Move along the waypoints until you get close enough if (spb_curwaypoint(spb) == -1) { // Determine first waypoint. curWaypoint = K_GetBestWaypointForMobj(spb); spb_curwaypoint(spb) = (INT32)K_GetWaypointHeapIndex(curWaypoint); } else { curWaypoint = K_GetWaypointFromIndex( (size_t)spb_curwaypoint(spb) ); } destWaypoint = bestPlayer->nextwaypoint; if (curWaypoint != NULL) { fixed_t waypointDist = INT32_MAX; fixed_t waypointRad = INT32_MAX; destX = curWaypoint->mobj->x; destY = curWaypoint->mobj->y; destZ = curWaypoint->mobj->z; waypointDist = R_PointToDist2(spb->x, spb->y, destX, destY) / mapobjectscale; waypointRad = max(curWaypoint->mobj->radius / mapobjectscale, DEFAULT_WAYPOINT_RADIUS); if (waypointDist <= waypointRad) { boolean pathfindsuccess = false; if (destWaypoint != NULL) { // Go to next waypoint. const boolean useshortcuts = K_GetWaypointIsShortcut(destWaypoint); // If the player is on a shortcut, use shortcuts. No escape. boolean huntbackwards = false; path_t pathtoplayer = {0}; pathfindsuccess = K_PathfindToWaypoint( curWaypoint, destWaypoint, &pathtoplayer, useshortcuts, huntbackwards ); if (pathfindsuccess == true) { #ifdef SPB_SEEKTEST if (pathtoplayer.numnodes > 1) { // Go to the next waypoint. curWaypoint = (waypoint_t *)pathtoplayer.array[1].nodedata; } else if (destWaypoint->numnextwaypoints > 0) { // Run ahead. curWaypoint = destWaypoint->nextwaypoints[0]; } else { // Sort of wait at the player's dest waypoint. circling = true; curWaypoint = destWaypoint; } #else path_t reversepath = {0}; boolean reversesuccess = false; huntbackwards = true; reversesuccess = K_PathfindToWaypoint( curWaypoint, destWaypoint, &reversepath, useshortcuts, huntbackwards ); if (reversesuccess == true && reversepath.totaldist < pathtoplayer.totaldist) { // It's faster to go backwards than to chase forward. // Keep curWaypoint the same, so the SPB waits around for them. circling = true; } else if (pathtoplayer.numnodes > 1) { // Go to the next waypoint. curWaypoint = (waypoint_t *)pathtoplayer.array[1].nodedata; } else if (spb->fuse > 0 && destWaypoint->numnextwaypoints > 0) { // Run ahead. curWaypoint = destWaypoint->nextwaypoints[0]; } else { // Sort of wait at the player's dest waypoint. circling = true; curWaypoint = destWaypoint; } if (reversesuccess == true) { Z_Free(reversepath.array); } #endif Z_Free(pathtoplayer.array); } } if (pathfindsuccess == true && curWaypoint != NULL) { // Update again spb_curwaypoint(spb) = (INT32)K_GetWaypointHeapIndex(curWaypoint); destX = curWaypoint->mobj->x; destY = curWaypoint->mobj->y; destZ = curWaypoint->mobj->z; } else { spb_curwaypoint(spb) = -1; destX = spb_chase(spb)->x; destY = spb_chase(spb)->y; destZ = spb_chase(spb)->z; } } } else { spb_curwaypoint(spb) = -1; destX = spb_chase(spb)->x; destY = spb_chase(spb)->y; destZ = spb_chase(spb)->z; } destAngle = R_PointToAngle2(spb->x, spb->y, destX, destY); destPitch = R_PointToAngle2(0, spb->z, P_AproxDistance(spb->x - destX, spb->y - destY), destZ); SPBTurn(desiredSpeed, destAngle, &xySpeed, &spb->angle, SPB_SEEKTURN, &sliptide); SPBTurn(desiredSpeed, destPitch, &zSpeed, &spb_pitch(spb), SPB_SEEKTURN, NULL); SetSPBSpeed(spb, xySpeed, zSpeed); // see if a player is near us, if they are, try to hit them by slightly thrusting towards them, otherwise, bleh! steerDist = 1536 * mapobjectscale; for (i = 0; i < MAXPLAYERS; i++) { fixed_t ourDist = INT32_MAX; INT32 ourDelta = INT32_MAX; if (playeringame[i] == false || players[i].spectator == true) { // Not in-game continue; } if (players[i].mo == NULL || P_MobjWasRemoved(players[i].mo) == true) { // Invalid mobj continue; } ourDelta = AngleDelta(spb->angle, R_PointToAngle2(spb->x, spb->y, players[i].mo->x, players[i].mo->y)); if (ourDelta > SPB_STEERDELTA) { // Check if the angle wouldn't make us LOSE speed. continue; } ourDist = R_PointToDist2(spb->x, spb->y, players[i].mo->x, players[i].mo->y); if (ourDist < steerDist) { steerDist = ourDist; steerMobj = players[i].mo; // it doesn't matter if we override this guy now. } } // different player from our main target, try and ram into em~! if (steerMobj != NULL && steerMobj != spb_chase(spb)) { P_Thrust(spb, R_PointToAngle2(spb->x, spb->y, steerMobj->x, steerMobj->y), spb_speed(spb) / 4); } if (sliptide != 0) { // 1 if turning left, -1 if turning right. // Angles work counterclockwise, remember! SpawnSPBSliptide(spb, sliptide); } else { // if we're mostly going straight, then spawn the V dust cone! SpawnSPBDust(spb); } // Always spawn speed lines while seeking SpawnSPBSpeedLines(spb); // Don't run this while we're circling around one waypoint intentionally. if (circling == false) { // Spawn a trail of rings behind the SPB! SPBMantaRings(spb); } } static void SPBChase(mobj_t *spb, player_t *bestPlayer) { fixed_t baseSpeed = 0; fixed_t maxSpeed = 0; fixed_t desiredSpeed = 0; fixed_t range = INT32_MAX; fixed_t cx = 0, cy = 0; fixed_t dist = INT32_MAX; angle_t destAngle = spb->angle; angle_t destPitch = 0U; fixed_t xySpeed = 0; fixed_t zSpeed = 0; mobj_t *chase = NULL; player_t *chasePlayer = NULL; spb_curwaypoint(spb) = -1; // Reset waypoint chase = spb_chase(spb); if (chase == NULL || P_MobjWasRemoved(chase) == true || chase->health <= 0) { P_SetTarget(&spb_chase(spb), NULL); spb_mode(spb) = SPB_MODE_WAIT; spb_modetimer(spb) = 55; // Slightly over the respawn timer length return; } if (chase->hitlag) { // If the player is frozen, the SPB should be too. spb->hitlag = max(spb->hitlag, chase->hitlag); return; } // Increment chase time spb_chasetime(spb)++; baseSpeed = SPB_DEFAULTSPEED; range = (160 * chase->scale); // Play the intimidating gurgle if (S_SoundPlaying(spb, spb->info->activesound) == false) { S_StartSound(spb, spb->info->activesound); } // Maybe we want SPB to target an object later? IDK lol chasePlayer = chase->player; if (chasePlayer != NULL) { UINT8 fracmax = 32; UINT8 spark = ((10 - chasePlayer->kartspeed) + chasePlayer->kartweight) / 2; fixed_t easiness = ((chasePlayer->kartspeed + (10 - spark)) << FRACBITS) / 2; spb_lastplayer(spb) = chasePlayer - players; // Save the player num for death scumming... spbplace = chasePlayer->position; chasePlayer->pflags |= PF_RINGLOCK; // set ring lock if (P_IsObjectOnGround(chase) == false) { // In the air you have no control; basically don't hit unless you make a near complete stop baseSpeed = (7 * chasePlayer->speed) / 8; } else { // 7/8ths max speed for Knuckles, 3/4ths max speed for min accel, exactly max speed for max accel baseSpeed = FixedMul( ((fracmax+1) << FRACBITS) - easiness, K_GetKartSpeed(chasePlayer, false, false) ) / fracmax; } if (chasePlayer->carry == CR_SLIDING) { baseSpeed = chasePlayer->speed/2; } // Be fairer on conveyors cx = chasePlayer->cmomx; cy = chasePlayer->cmomy; // Switch targets if you're no longer 1st for long enough if (bestPlayer != NULL && chasePlayer->position <= bestPlayer->position) { spb_modetimer(spb) = SPB_HOTPOTATO; } else { if (spb_modetimer(spb) > 0) { spb_modetimer(spb)--; } if (spb_modetimer(spb) <= 0) { spb_mode(spb) = SPB_MODE_SEEK; // back to SEEKING spb_intangible(spb) = SPB_FLASHING; } } } dist = P_AproxDistance(P_AproxDistance(spb->x - chase->x, spb->y - chase->y), spb->z - chase->z); desiredSpeed = FixedMul(baseSpeed, FRACUNIT + FixedDiv(dist - range, range)); if (desiredSpeed < baseSpeed) { desiredSpeed = baseSpeed; } maxSpeed = (baseSpeed * 3) / 2; if (desiredSpeed > maxSpeed) { desiredSpeed = maxSpeed; } if (desiredSpeed < 20 * chase->scale) { desiredSpeed = 20 * chase->scale; } if (chasePlayer != NULL && chasePlayer->carry == CR_SLIDING) { // Hack for current sections to make them fair. desiredSpeed = min(desiredSpeed, chasePlayer->speed / 2); } destAngle = R_PointToAngle2(spb->x, spb->y, chase->x, chase->y); destPitch = R_PointToAngle2(0, spb->z, P_AproxDistance(spb->x - chase->x, spb->y - chase->y), chase->z); // Modify stored speed if (desiredSpeed > spb_speed(spb)) { spb_speed(spb) += (desiredSpeed - spb_speed(spb)) / TICRATE; } else { spb_speed(spb) = desiredSpeed; } SPBTurn(spb_speed(spb), destAngle, &xySpeed, &spb->angle, SPB_CHASETURN, NULL); SPBTurn(spb_speed(spb), destPitch, &zSpeed, &spb_pitch(spb), SPB_CHASETURN, NULL); SetSPBSpeed(spb, xySpeed, zSpeed); spb->momx += cx; spb->momy += cy; // Spawn a trail of rings behind the SPB! SPBMantaRings(spb); // Red speed lines for when it's gaining on its target. A tell for when you're starting to lose too much speed! if (R_PointToDist2(0, 0, spb->momx, spb->momy) > (16 * R_PointToDist2(0, 0, chase->momx, chase->momy)) / 15 // Going faster than the target && xySpeed > 20 * mapobjectscale) // Don't display speedup lines at pitifully low speeds { SpawnSPBSpeedLines(spb); } } static void SPBWait(mobj_t *spb) { player_t *oldPlayer = NULL; spb->momx = spb->momy = spb->momz = 0; // Stoooop spb_curwaypoint(spb) = -1; // Reset waypoint if (spb_lastplayer(spb) != -1 && playeringame[spb_lastplayer(spb)] == true) { oldPlayer = &players[spb_lastplayer(spb)]; } if (oldPlayer != NULL && oldPlayer->spectator == false && oldPlayer->exiting > 0) { spbplace = oldPlayer->position; oldPlayer->pflags |= PF_RINGLOCK; } if (spb_modetimer(spb) > 0) { spb_modetimer(spb)--; } if (spb_modetimer(spb) <= 0) { if (oldPlayer != NULL) { if (oldPlayer->mo != NULL && P_MobjWasRemoved(oldPlayer->mo) == false) { P_SetTarget(&spb_chase(spb), oldPlayer->mo); spb_mode(spb) = SPB_MODE_CHASE; spb_modetimer(spb) = SPB_HOTPOTATO; spb_intangible(spb) = SPB_FLASHING; spb_speed(spb) = SPB_DEFAULTSPEED; } } else { spb_mode(spb) = SPB_MODE_SEEK; spb_modetimer(spb) = 0; spb_intangible(spb) = SPB_FLASHING; spbplace = -1; } } } void Obj_SPBThink(mobj_t *spb) { mobj_t *ghost = NULL; player_t *bestPlayer = NULL; UINT8 bestRank = UINT8_MAX; size_t i; if (spb->health <= 0) { return; } K_SetItemCooldown(KITEM_SPB, 20*TICRATE); ghost = P_SpawnGhostMobj(spb); ghost->fuse = 3; if (spb_owner(spb) != NULL && P_MobjWasRemoved(spb_owner(spb)) == false && spb_owner(spb)->player != NULL) { ghost->color = spb_owner(spb)->player->skincolor; ghost->colorized = true; } if (spb_nothink(spb) > 0) { // Init values, don't think yet. spb_lastplayer(spb) = -1; spb_curwaypoint(spb) = -1; spb_chasetime(spb) = 0; spbplace = -1; spb_manta_totaldist(spb) = 0; // 30000? spb_manta_vscale(spb) = SPB_MANTA_VSTART; P_InstaThrust(spb, spb->angle, SPB_DEFAULTSPEED); spb_nothink(spb)--; } else { // Find the player with the best rank for (i = 0; i < MAXPLAYERS; i++) { player_t *player = NULL; if (playeringame[i] == false) { // Not valid continue; } player = &players[i]; if (player->spectator == true || player->exiting > 0) { // Not playing continue; } /* if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) { // No mobj continue; } if (player->mo <= 0) { // Dead continue; } if (player->respawn.state != RESPAWNST_NONE) { // Respawning continue; } */ if (player->position < bestRank) { bestRank = player->position; bestPlayer = player; } } switch (spb_mode(spb)) { case SPB_MODE_SEEK: default: SPBSeek(spb, bestPlayer); break; case SPB_MODE_CHASE: SPBChase(spb, bestPlayer); break; case SPB_MODE_WAIT: SPBWait(spb); break; } } // Flash on/off when intangible. if (spb_intangible(spb) > 0) { spb_intangible(spb)--; if (spb_intangible(spb) & 1) { spb->renderflags |= RF_DONTDRAW; } else { spb->renderflags &= ~RF_DONTDRAW; } } // Flash white when about to explode! if (spb->fuse > 0) { if (spb->fuse & 1) { spb->color = SKINCOLOR_INVINCFLASH; spb->colorized = true; } else { spb->color = SKINCOLOR_NONE; spb->colorized = false; } } // Clamp within level boundaries. if (spb->z < spb->floorz) { spb->z = spb->floorz; } else if (spb->z > spb->ceilingz - spb->height) { spb->z = spb->ceilingz - spb->height; } } void Obj_SPBExplode(mobj_t *spb) { mobj_t *spbExplode = NULL; // Don't continue playing the gurgle or the siren S_StopSound(spb); spbExplode = P_SpawnMobjFromMobj(spb, 0, 0, 0, MT_SPBEXPLOSION); if (spb_owner(spb) != NULL && P_MobjWasRemoved(spb_owner(spb)) == false) { P_SetTarget(&spbExplode->target, spb_owner(spb)); } // Tell the explosion to use alternate knockback. spbExplode->movefactor = ((SPB_CHASETIMESCALE - spb_chasetime(spb)) * SPB_CHASETIMEMUL) / SPB_CHASETIMESCALE; P_RemoveMobj(spb); } void Obj_SPBTouch(mobj_t *spb, mobj_t *toucher) { player_t *player = toucher->player; if (spb_intangible(spb) > 0) { return; } if ((spb_owner(spb) == toucher || spb_owner(spb) == toucher->target) && (spb_nothink(spb) > 0)) { return; } if (spb->health <= 0 || toucher->health <= 0) { return; } if (player->spectator == true) { return; } if (player->bubbleblowup > 0) { // Stun the SPB, and remove the shield. K_DropHnextList(player, false); spb_mode(spb) = SPB_MODE_WAIT; spb_modetimer(spb) = 55; // Slightly over the respawn timer length return; } if (spb_chase(spb) != NULL && P_MobjWasRemoved(spb_chase(spb)) == false && toucher == spb_chase(spb)) { // Cause the explosion. Obj_SPBExplode(spb); return; } else { // Regular spinout, please. P_DamageMobj(toucher, spb, spb_owner(spb), 1, DMG_NORMAL); } }