// DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2024 by Sally "TehRealSalt" Cochenour // Copyright (C) 2024 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 ufo.c /// \brief Special Stage UFO + Emerald handler #include "../command.h" #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 "../m_cond.h" #include "../r_main.h" #include "../s_sound.h" #include "../g_game.h" #include "../z_zone.h" #include "../k_waypoint.h" #include "../k_specialstage.h" #include "../r_skins.h" #include "../k_hitlag.h" #include "../acs/interface.h" #include "../hu_stuff.h" #include "../k_grandprix.h" #define UFO_BASE_SPEED (42 * FRACUNIT) // UFO's slowest speed. #define UFO_SPEEDUP (FRACUNIT >> 1) // Acceleration #define UFO_SLOWDOWN (FRACUNIT >> 1) // Deceleration #define UFO_SPACING (768 * FRACUNIT) // How far the UFO wants to stay in front #define UFO_DEADZONE (2048 * FRACUNIT) // Deadzone where it won't update it's speed as much. #define UFO_SPEEDFACTOR (FRACUNIT * 3 / 4) // Factor of player's best speed, to make it more fair. #define UFO_DAMAGED_SPEED (UFO_BASE_SPEED >> 1) // Speed to add when UFO takes damage. #define UFO_START_SPEED (UFO_BASE_SPEED << 1) // Speed when the map starts. #define UFO_PITY_DIST (10000) // Let's aim for an exciting finish! Try to stick closer to the player once they're past this threshold. #define UFO_PITY_BRAKES (600 * FRACUNIT) // Subtract this amount from UFO_SFACING, starting at UFO_PITY_DIST and ending at the finish line. #define UFO_NUMARMS (3) #define UFO_ARMDELTA (ANGLE_MAX / UFO_NUMARMS) #define UFO_NUM_GLASSFRAMES (10) #define ufo_emeraldnum(o) ((o)->cvmem) #define ufo_waypoint(o) ((o)->extravalue1) #define ufo_distancetofinish(o) ((o)->extravalue2) #define ufo_speed(o) ((o)->watertop) #define ufo_collectdelay(o) ((o)->threshold) #define ufo_pieces(o) ((o)->hnext) #define ufo_piece_type(o) ((o)->extravalue1) #define ufo_piece_glass_flickerframe(o) ((o)->cusval) #define ufo_piece_owner(o) ((o)->target) #define ufo_piece_next(o) ((o)->hnext) #define ufo_piece_prev(o) ((o)->hprev) #define ufo_intangible(o) ((o)->cusval) #define ufo_emerald(o) ((o)->tracer) enum { UFO_PIECE_TYPE_POD, UFO_PIECE_TYPE_GLASS, UFO_PIECE_TYPE_GLASS_UNDER, UFO_PIECE_TYPE_ARM, UFO_PIECE_TYPE_STEM, }; static sfxenum_t hums[16] = {sfx_claw01, sfx_claw02, sfx_claw03, sfx_claw04, sfx_claw05, sfx_claw06, sfx_claw07, sfx_claw08, sfx_claw09, sfx_claw10, sfx_claw11, sfx_claw12, sfx_claw13, sfx_claw14, sfx_claw15, sfx_claw16}; static int maxhum = sizeof(hums) / sizeof(hums[0]) - 1; static void SpawnUFOSpeedLines(mobj_t *ufo) { mobj_t *fast = P_SpawnMobjFromMobj(ufo, P_RandomRange(PR_DECORATION, -120, 120) * FRACUNIT, P_RandomRange(PR_DECORATION, -120, 120) * FRACUNIT, (ufo->info->height / 2) + (P_RandomRange(PR_DECORATION, -24, 24) * FRACUNIT), MT_FASTLINE ); fast->scale *= 3; P_SetTarget(&fast->target, ufo); fast->angle = K_MomentumAngle(ufo); fast->color = SKINCOLOR_WHITE; fast->colorized = true; K_MatchGenericExtraFlags(fast, ufo); } static void SpawnEmeraldSpeedLines(mobj_t *mo) { mobj_t *fast = P_SpawnMobjFromMobj(mo, P_RandomRange(PR_DECORATION, -48, 48) * FRACUNIT, P_RandomRange(PR_DECORATION, -48, 48) * FRACUNIT, P_RandomRange(PR_DECORATION, 0, 64) * FRACUNIT, MT_FASTLINE); P_SetMobjState(fast, S_KARTINVLINES1); P_SetTarget(&fast->target, mo); fast->angle = K_MomentumAngle(mo); fast->momx = 3*mo->momx/4; fast->momy = 3*mo->momy/4; fast->momz = 3*P_GetMobjZMovement(mo)/4; K_MatchGenericExtraFlags(fast, mo); P_SetTarget(&fast->owner, mo); fast->renderflags |= RF_REDUCEVFX; fast->color = mo->color; fast->colorized = true; } static void UFOMoveTo(mobj_t *ufo, fixed_t destx, fixed_t desty, fixed_t destz) { ufo->momx = destx - ufo->x; ufo->momy = desty - ufo->y; ufo->momz = destz - ufo->z; } static fixed_t GenericDistance( fixed_t curx, fixed_t cury, fixed_t curz, fixed_t destx, fixed_t desty, fixed_t destz) { return P_AproxDistance(P_AproxDistance(destx - curx, desty - cury), destz - curz); } static boolean UFOEmeraldChase(mobj_t *ufo) { return (ufo->health <= 1); } static boolean UFOPieceValid(mobj_t *piece) { return (piece != NULL && P_MobjWasRemoved(piece) == false && piece->health > 0); } static void UFOUpdateDistanceToFinish(mobj_t *ufo) { waypoint_t *finishLine = K_GetFinishLineWaypoint(); waypoint_t *nextWaypoint = K_GetWaypointFromIndex((size_t)ufo_waypoint(ufo)); if (nextWaypoint != NULL && finishLine != NULL) { const boolean useshortcuts = false; const boolean huntbackwards = false; boolean pathfindsuccess = false; path_t pathtofinish = {0}; pathfindsuccess = K_PathfindToWaypoint(nextWaypoint, finishLine, &pathtofinish, useshortcuts, huntbackwards); // Update the UFO's distance to the finish line if a path was found. if (pathfindsuccess == true) { // Add euclidean distance to the next waypoint to the distancetofinish UINT32 adddist; fixed_t disttowaypoint = P_AproxDistance( (ufo->x >> FRACBITS) - (nextWaypoint->mobj->x >> FRACBITS), (ufo->y >> FRACBITS) - (nextWaypoint->mobj->y >> FRACBITS)); disttowaypoint = P_AproxDistance(disttowaypoint, (ufo->z >> FRACBITS) - (nextWaypoint->mobj->z >> FRACBITS)); adddist = (UINT32)disttowaypoint; ufo_distancetofinish(ufo) = pathtofinish.totaldist + adddist; Z_Free(pathtofinish.array); } } } static void UFOUpdateSpeed(mobj_t *ufo) { const fixed_t mapspeedscale = FixedMul(mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); const fixed_t baseSpeed = FixedMul(UFO_BASE_SPEED, K_GetKartGameSpeedScalar(gamespeed)); const UINT32 deadzone = FixedMul(UFO_DEADZONE, mapspeedscale) >> FRACBITS; UINT32 spacing = FixedMul(UFO_SPACING, mapspeedscale) >> FRACBITS; UINT32 distanceNerf = FixedMul(UFO_PITY_BRAKES, mapspeedscale) >> FRACBITS; // Best values of all of the players. UINT32 bestDist = UINT32_MAX; fixed_t bestSpeed = 0; // Desired values for the UFO itself. UINT32 wantedDist = UINT32_MAX; fixed_t wantedSpeed = ufo_speed(ufo); fixed_t speedDelta = 0; UINT8 i; for (i = 0; i < MAXPLAYERS; i++) { player_t *player = NULL; if (playeringame[i] == false) { continue; } player = &players[i]; if (player->spectator == true) { continue; } if (player->mo == NULL || P_MobjWasRemoved(player->mo) == true) { continue; } if (player->distancetofinish < bestDist) { bestDist = player->distancetofinish; // Doesn't matter if a splitscreen player behind is moving faster behind the one most caught up. bestSpeed = R_PointToDist2(0, 0, player->rmomx, player->rmomy); bestSpeed = min(bestSpeed, K_GetKartSpeed(player, false, false)); // Don't become unfair with Sneakers. bestSpeed = FixedDiv(bestSpeed, mapobjectscale); // Unscale from mapobjectscale to FRACUNIT bestSpeed = FixedMul(bestSpeed, UFO_SPEEDFACTOR); // Make it a bit more lenient } } if (bestDist == UINT32_MAX) { // Invalid, lets go back to base speed. wantedSpeed = baseSpeed; } else { INT32 distDelta = 0; if (bestDist < UFO_PITY_DIST && UFOEmeraldChase(ufo)) { INT32 brakeDelta = UFO_PITY_DIST - bestDist; INT32 distPerNerf = UFO_PITY_DIST / distanceNerf; // Doing this in the sensible way integer overflows. Sorry. spacing = spacing - (brakeDelta / distPerNerf); } if (bestDist > spacing) { wantedDist = bestDist - spacing; } else { wantedDist = 0; } distDelta = ufo_distancetofinish(ufo) - wantedDist; if (distDelta > 0) { // Too far behind! Start speeding up! wantedSpeed = max(bestSpeed, baseSpeed << 2); } else { if (abs(distDelta) <= deadzone) { // We're in a good spot, try to match the player. wantedSpeed = max(bestSpeed >> 1, baseSpeed); } else { // Too far ahead! Start slowing down! wantedSpeed = baseSpeed; } } } // Slowly accelerate or decelerate to // get to our desired speed. speedDelta = wantedSpeed - ufo_speed(ufo); if (speedDelta > 0) { if (abs(speedDelta) <= UFO_SPEEDUP) { ufo_speed(ufo) = wantedSpeed; } else { ufo_speed(ufo) += UFO_SPEEDUP; } // these number are primarily vibes based and not empirically derived if (UFOEmeraldChase(ufo)) { if (ufo_speed(ufo) > 50*FRACUNIT) SpawnEmeraldSpeedLines(ufo); } else if (ufo_speed(ufo) > 70*FRACUNIT && !S_SoundPlaying(ufo, sfx_clawzm)) { S_StartSound(ufo, sfx_clawzm); } } else if (speedDelta < 0) { if (abs(speedDelta) <= UFO_SLOWDOWN) { ufo_speed(ufo) = wantedSpeed; } else { ufo_speed(ufo) -= UFO_SLOWDOWN; } } } static void UFOUpdateAngle(mobj_t *ufo) { angle_t dest = K_MomentumAngle(ufo); INT32 delta = AngleDeltaSigned(ufo->angle, dest); ufo->angle += delta >> 2; } waypoint_t *K_GetSpecialUFOWaypoint(mobj_t *ufo) { if ((ufo == NULL) && (specialstageinfo.valid == true)) { ufo = specialstageinfo.ufo; } if (ufo != NULL && P_MobjWasRemoved(ufo) == false && ufo->type == MT_SPECIAL_UFO) { if (ufo_waypoint(ufo) >= 0) { return K_GetWaypointFromIndex((size_t)ufo_waypoint(ufo)); } } return NULL; } static void UFOMoveToDistance(mobj_t *ufo, UINT32 distancetofinish) { waypoint_t *finishline = K_GetFinishLineWaypoint(); const boolean useshortcuts = false; const boolean huntbackwards = true; path_t pathtofinish = {0}; if (finishline == NULL) { return; } boolean pathfindsuccess = K_PathfindThruCircuit( finishline, distancetofinish, &pathtofinish, useshortcuts, huntbackwards ); if (pathfindsuccess == false) { return; } pathfindnode_t *node = &pathtofinish.array[pathtofinish.numnodes - 1]; if (node->camefrom != NULL) { UINT32 a_to_b = (node->gscore - node->camefrom->gscore); UINT32 overshot = (node->gscore - distancetofinish); fixed_t f = FixedDiv(overshot, max(1, a_to_b)); mobj_t *a = ((waypoint_t*)node->camefrom->nodedata)->mobj; mobj_t *b = ((waypoint_t*)node->nodedata)->mobj; UFOMoveTo( ufo, b->x - FixedMul(f, b->x - a->x), b->y - FixedMul(f, b->y - a->y), b->z - FixedMul(f, b->z - a->z) ); } Z_Free(pathtofinish.array); } static void UFOMove(mobj_t *ufo) { extern consvar_t cv_ufo_follow; if (cv_ufo_follow.value) { if (playeringame[cv_ufo_follow.value - 1]) { UFOMoveToDistance(ufo, players[cv_ufo_follow.value - 1].distancetofinish); } return; } waypoint_t *curWaypoint = NULL; waypoint_t *destWaypoint = NULL; fixed_t distLeft = INT32_MAX; fixed_t newX = ufo->x; fixed_t newY = ufo->y; fixed_t newZ = ufo->z; const fixed_t floatHeight = 24 * ufo->scale; const boolean useshortcuts = false; const boolean huntbackwards = false; boolean pathfindsuccess = false; path_t pathtofinish = {0}; size_t pathIndex = 0; boolean reachedEnd = false; curWaypoint = K_GetSpecialUFOWaypoint(ufo); destWaypoint = K_GetFinishLineWaypoint(); if (curWaypoint == NULL || destWaypoint == NULL) { // Waypoints aren't valid. // Just go straight up. // :japanese_ogre: : "Abrupt and funny is the funniest way to end the special stage anyways" ufo->momx = 0; ufo->momy = 0; ufo->momz = ufo_speed(ufo); ufo_distancetofinish(ufo) = 0; return; } distLeft = FixedMul(ufo_speed(ufo), mapobjectscale); while (distLeft > 0) { fixed_t wpX = curWaypoint->mobj->x; fixed_t wpY = curWaypoint->mobj->y; fixed_t wpZ = curWaypoint->mobj->z + floatHeight; fixed_t distToNext = GenericDistance( newX, newY, newZ, wpX, wpY, wpZ ); if (distToNext > distLeft) { // Only made it partially there. newX += FixedMul(FixedDiv(wpX - newX, distToNext), distLeft); newY += FixedMul(FixedDiv(wpY - newY, distToNext), distLeft); newZ += FixedMul(FixedDiv(wpZ - newZ, distToNext), distLeft); distLeft = 0; } else { // Close enough to the next waypoint, // move there and remove the distance. newX = wpX; newY = wpY; newZ = wpZ; distLeft -= distToNext; if (curWaypoint == destWaypoint) { // Reached the end. reachedEnd = true; break; } // Create waypoint path to our destination. // Crazy over-engineered, just to catch when // waypoints are insanely close to each other :P if (pathfindsuccess == false) { pathfindsuccess = K_PathfindToWaypoint( curWaypoint, destWaypoint, &pathtofinish, useshortcuts, huntbackwards ); if (pathfindsuccess == false) { // Path isn't valid. // Just keep going. break; } } pathIndex++; if (pathIndex >= pathtofinish.numnodes) { // Successfully reached the end of the path. reachedEnd = true; break; } // Now moving to the next waypoint. curWaypoint = (waypoint_t *)pathtofinish.array[pathIndex].nodedata; ufo_waypoint(ufo) = (INT32)K_GetWaypointHeapIndex(curWaypoint); } } UFOMoveTo(ufo, newX, newY, newZ); if (reachedEnd == true) { // Invalidate UFO/emerald ufo_waypoint(ufo) = -1; ufo->flags &= ~(MF_SPECIAL|MF_PICKUPFROMBELOW); // Disable player P_DoAllPlayersExit(PF_NOCONTEST, false); HU_DoTitlecardCEcho(NULL, "TOO LATE...", false); } if (pathfindsuccess == true) { Z_Free(pathtofinish.array); } } static void UFOEmeraldVFX(mobj_t *emerald) { const INT32 bobS = 32; const angle_t bobA = (leveltime & (bobS - 1)) * (ANGLE_MAX / bobS); const fixed_t bobH = 16 * emerald->scale; emerald->sprzoff = FixedMul(bobH, FINESINE(bobA >> ANGLETOFINESHIFT)); Obj_SpawnEmeraldSparks(emerald); } static boolean UFOHumPlaying(mobj_t *ufo) { INT32 i; for (i = 0; i <= maxhum; i++) { if (S_SoundPlaying(ufo, hums[i])) return true; } return false; } static void UFOUpdateSound(mobj_t *ufo) { INT32 maxhealth = max(mobjinfo[MT_SPECIAL_UFO].spawnhealth, 1); INT32 healthlevel = maxhum * ufo->health / maxhealth; if (!UFOEmeraldChase(ufo) && !UFOHumPlaying(ufo)) { healthlevel = min(max(healthlevel, 1), maxhum); S_StartSound(ufo, hums[maxhum - healthlevel]); } } static void UFODebugSetHealth(mobj_t *ufo, UINT8 health) { if (ufo->health == health + 1 || UFOEmeraldChase(ufo) == true) { return; } extern consvar_t cv_ufo_follow; UINT8 pnum = max(1, cv_ufo_follow.value) - 1; mobj_t *source = players[pnum].mo; if (playeringame[pnum] == false || P_MobjWasRemoved(source) == true) { return; } ufo->health = health + 2; Obj_SpecialUFODamage(ufo, ufo, source, DMG_NORMAL); // does 1 damage, updates pieces } void Obj_SpecialUFOThinker(mobj_t *ufo) { { extern consvar_t cv_ufo_health; if (cv_ufo_health.value != -1) { UFODebugSetHealth(ufo, cv_ufo_health.value); } } UFOMove(ufo); UFOUpdateAngle(ufo); UFOUpdateDistanceToFinish(ufo); UFOUpdateSpeed(ufo); UFOUpdateSound(ufo); if (ufo_intangible(ufo)) ufo_intangible(ufo)--; if (UFOEmeraldChase(ufo) == true) { ufo_collectdelay(ufo)--; } else { ufo_collectdelay(ufo) = TICRATE; } } // The following is adapted from monitor.c for UFO Catcher damage // I couldn't just exose the relevant things via k_object.h // because they're *just* too specific to Monitors... ~toast 070423 #define shard_can_roll(o) ((o)->extravalue1) static inline boolean can_shard_state_roll (statenum_t state) { switch (state) { case S_MONITOR_BIG_SHARD: case S_MONITOR_SMALL_SHARD: return true; default: return false; } } static void spawn_shard ( mobj_t * part, statenum_t state) { mobj_t *ufo = ufo_piece_owner(part); // These divisions and multiplications are done on the // offsets to give bigger increments of randomness. const fixed_t h = FixedDiv( ufo->height, ufo->scale); const UINT16 rad = (ufo->radius / ufo->scale) / 4; const UINT16 tall = (h / FRACUNIT); mobj_t *p = P_SpawnMobjFromMobj(ufo, P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT, P_RandomRange(PR_ITEM_DEBRIS, -(rad), rad) * 8 * FRACUNIT, P_RandomKey(PR_ITEM_DEBRIS, tall + 1) * 4 * FRACUNIT, MT_MONITOR_SHARD); P_SetScale(p, (p->destscale = p->destscale * 3)); angle_t th = R_PointToAngle2(ufo->x, ufo->y, p->x, p->y); th -= P_RandomKey(PR_ITEM_DEBRIS, ANGLE_45) - ANGLE_22h; p->hitlag = 0; P_Thrust(p, th, 6 * p->scale); p->momz = P_RandomRange(PR_ITEM_DEBRIS, 3, 10) * p->scale; P_SetMobjState(p, state); shard_can_roll(p) = can_shard_state_roll(state); if (shard_can_roll(p)) { p->rollangle = P_Random(PR_ITEM_DEBRIS); } if (P_RandomChance(PR_ITEM_DEBRIS, FRACUNIT/2)) { p->renderflags |= RF_DONTDRAW; } } static void set_flickerframe (mobj_t *ufo, mobj_t *piece) { INT32 healthcalc = (UFO_NUM_GLASSFRAMES - 1); if (ufo && !P_MobjWasRemoved(ufo)) { INT32 maxhealth = mobjinfo[MT_SPECIAL_UFO].spawnhealth; healthcalc = (maxhealth - ufo->health); if (healthcalc > 0) { maxhealth /= UFO_NUM_GLASSFRAMES; if (maxhealth <= 0) maxhealth = 1; healthcalc /= maxhealth; if (healthcalc >= UFO_NUM_GLASSFRAMES) healthcalc = (UFO_NUM_GLASSFRAMES - 1); if (healthcalc < 0) healthcalc = 0; } else { healthcalc = 0; } } healthcalc |= FF_FULLBRIGHT; ufo_piece_glass_flickerframe(piece) = healthcalc; piece->frame = healthcalc; } static void spawn_debris (mobj_t *part) { mobj_t *ufo = ufo_piece_owner(part); INT32 i; for (i = ufo->health; i <= mobjinfo[ufo->type].spawnhealth; i += 5) { spawn_shard(part, S_MONITOR_BIG_SHARD); spawn_shard(part, S_MONITOR_SMALL_SHARD); spawn_shard(part, S_MONITOR_TWINKLE); } } static void UFOCopyHitlagToPieces(mobj_t *ufo) { mobj_t *piece = NULL; piece = ufo_pieces(ufo); while (UFOPieceValid(piece) == true) { piece->hitlag = ufo->hitlag; piece->eflags = (piece->eflags & ~MFE_DAMAGEHITLAG) | (ufo->eflags & MFE_DAMAGEHITLAG); if (ufo_piece_type(piece) == UFO_PIECE_TYPE_GLASS) { set_flickerframe (ufo, piece); spawn_debris (piece); } piece = ufo_piece_next(piece); } } static void UFOKillPiece(mobj_t *piece) { angle_t dir = ANGLE_MAX; fixed_t thrust = 0; if (UFOPieceValid(piece) == false) { return; } piece->health = 0; piece->tics = TICRATE; piece->flags &= ~MF_NOGRAVITY; switch (ufo_piece_type(piece)) { case UFO_PIECE_TYPE_GLASS: { set_flickerframe(NULL, piece); piece->tics = 1; return; } case UFO_PIECE_TYPE_GLASS_UNDER: case UFO_PIECE_TYPE_STEM: { piece->tics = 1; return; } case UFO_PIECE_TYPE_ARM: { dir = piece->angle; thrust = 12 * piece->scale; break; } default: { dir = FixedAngle(P_RandomRange(PR_DECORATION, 0, 359) << FRACBITS); thrust = 4 * piece->scale; break; } } P_Thrust(piece, dir, -thrust); P_SetObjectMomZ(piece, 12*FRACUNIT, true); } static void UFOKillPieces(mobj_t *ufo) { mobj_t *piece = NULL; piece = ufo_pieces(ufo); while (UFOPieceValid(piece) == true) { UFOKillPiece(piece); piece = ufo_piece_next(piece); } } static UINT8 GetUFODamage(mobj_t *inflictor, UINT8 damageType) { UINT8 ret = 0; targetdamaging_t targetdamaging = UFOD_GENERIC; if (inflictor != NULL && P_MobjWasRemoved(inflictor) == false) { switch (inflictor->type) { // Shields deal chip damage. case MT_JAWZ_SHIELD: { targetdamaging = UFOD_JAWZ; ret = 10; break; } case MT_ORBINAUT_SHIELD: { targetdamaging = UFOD_ORBINAUT; ret = 10; break; } case MT_INSTAWHIP: { targetdamaging = UFOD_WHIP; ret = 10; inflictor->extravalue2 = 1; // Disable whip collision break; } case MT_JAWZ: { // Thrown Jawz deal a bit extra. targetdamaging = UFOD_JAWZ; ret = 15; break; } case MT_ORBINAUT: { // Thrown orbinauts deal double damage. targetdamaging = UFOD_ORBINAUT; ret = 20; break; } case MT_GACHABOM: { // Thrown gachabom need to be tracked, but have no special damage value as of yet. targetdamaging = UFOD_GACHABOM; break; } case MT_SPB: case MT_SPBEXPLOSION: { if (inflictor->type != MT_SPBEXPLOSION || inflictor->threshold == KITEM_SPB) { targetdamaging |= UFOD_SPB; } // SPB deals triple damage. ret = 30; break; } case MT_BANANA: { targetdamaging = UFOD_BANANA; // Banana snipes deal triple damage, // laid down bananas deal regular damage. if (inflictor->health > 1) { ret = 30; break; } ret = 10; break; } case MT_GARDENTOP: { // Garden Top is not classified as a "field // item" because the player can ride it. So // an explicit case is necessary. ret = 10; break; } case MT_PLAYER: { // Players deal damage relative to how many sneakers they used. targetdamaging = UFOD_BOOST; ret = 15 * max(1, inflictor->player->numsneakers + inflictor->player->numpanelsneakers); break; } case MT_SPECIAL_UFO: { // UFODebugSetHealth ret = 1; break; } default: { // General hazards cannot damage the UFO if (P_IsKartFieldItem(inflictor->type) == false) { return 0; } break; } } } P_TrackRoundConditionTargetDamage(targetdamaging); if (ret != 0) return ret; // Guess from damage type. switch (damageType & DMG_TYPEMASK) { case DMG_NORMAL: case DMG_STING: default: { return 10; } case DMG_WIPEOUT: { return 20; } case DMG_EXPLODE: case DMG_TUMBLE: { return 30; } case DMG_VOLTAGE: { return 15; } } } boolean Obj_SpecialUFODamage(mobj_t *ufo, mobj_t *inflictor, mobj_t *source, UINT8 damageType) { const fixed_t addSpeed = FixedMul(UFO_DAMAGED_SPEED, K_GetKartGameSpeedScalar(gamespeed)); UINT8 damage = 1; if (UFOEmeraldChase(ufo) == true) { // Damaged fully already, no need for any more. return false; } damage = GetUFODamage(inflictor, damageType); if (damage <= 0) { return false; } if (source->player) { UINT32 skinflags = (demo.playback) ? demo.skinlist[demo.currentskinid[(source->player-players)]].flags : skins[source->player->skin].flags; if (skinflags & SF_IRONMAN) SetRandomFakePlayerSkin(source->player, true, false); } ufo_intangible(ufo) = 60; // Speed up on damage! ufo_speed(ufo) += addSpeed; ufo->health = max(1, ufo->health - damage); if (grandprixinfo.gp) { grandprixinfo.specialDamage += damage; } K_SetHitLagForObjects(ufo, inflictor, source, (damage / 3) + 2, true); UFOCopyHitlagToPieces(ufo); if (ufo->health == 1) { // Destroy the UFO parts, and make the emerald collectible! UFOKillPieces(ufo); gamedata->deferredconditioncheck = true; // Check Challenges! ufo->flags = (ufo->flags & ~MF_SHOOTABLE) | (MF_SPECIAL|MF_PICKUPFROMBELOW); ufo->shadowscale = FRACUNIT/3; ACS_RunCatcherScript(source); S_StopSound(ufo); S_StartSound(ufo, sfx_gbrk); S_StartSound(ufo, sfx_clawk2); P_StartQuake(30, 96 * ufo->scale, 0, NULL); ufo_speed(ufo) += addSpeed; // Even more speed! return true; } S_StartSound(ufo, sfx_clawht); S_StopSoundByID(ufo, sfx_clawzm); for (int i = 0; i <= maxhum; i++) { S_StopSoundByID(ufo, hums[i]); } UFOUpdateSound(ufo); P_StartQuake(10, 64 * ufo->scale, 0, NULL); return true; } void Obj_PlayerUFOCollide(mobj_t *ufo, mobj_t *other) { if (other->player == NULL) { return; } if (other->z > ufo->z + ufo->height) { return; // overhead } if (other->z + other->height < ufo->z) { return; // underneath } if (ufo_intangible(ufo)) { return; // We were just hit! } if (other->player->tripwirePass >= TRIPWIRE_BOOST && !P_PlayerInPain(other->player) && (other->player->flashing == 0)) { // Bump and deal damage. Obj_SpecialUFODamage(ufo, other, other, DMG_STEAL); other->player->sneakertimer = 0; other->player->numsneakers = 0; other->player->panelsneakertimer = 0; other->player->numpanelsneakers = 0; // Copied from Obj_OrbinautThrown const ffloor_t *rover = P_IsObjectFlipped(other) ? other->ceilingrover : other->floorrover; if (rover && (rover->fofflags & FOF_SWIMMABLE)) { // Player is waterskiing so use different math to // reduce their speed some but keep them skiing // at high speeds. fixed_t linear = K_GetKartSpeed(other->player, false, false) / 2; if ((other->player->speed - linear) < other->player->speed / 4) { other->momx /= 4; other->momy /= 4; } else { angle_t mom = R_PointToAngle2(0, 0, other->momx, other->momy); P_Thrust(other, mom, -linear); } } else { K_KartBouncing(other, ufo); } } else { const angle_t moveAngle = K_MomentumAngle(ufo); const angle_t clipAngle = R_PointToAngle2(ufo->x, ufo->y, other->x, other->y); if (AngleDelta(moveAngle, clipAngle) < ANG60) { // in front K_StumblePlayer(other->player); } K_KartBouncing(other, ufo); } } boolean Obj_UFOEmeraldCollect(mobj_t *ufo, mobj_t *toucher) { mobj_t *emerald = ufo_emerald(ufo); if (toucher->player != NULL) { if (toucher->player->exiting || mapreset || (toucher->player->pflags & PF_ELIMINATED)) { return false; } } if (ufo_collectdelay(ufo) > 0) { return false; } if (toucher->hitlag > 0) { return false; } ACS_RunEmeraldScript(toucher); if (!P_MobjWasRemoved(emerald)) { const int kScaleTics = 16; // Emerald will now orbit the player Obj_BeginEmeraldOrbit(emerald, toucher, 100 * mapobjectscale, 64, 0); // Scale down because the emerald is huge // Super Emerald needs to be scaled down further emerald->destscale = emerald->scale / (ufo_emeraldnum(ufo) > 7 ? 3 : 2); emerald->scalespeed = abs(emerald->destscale - emerald->scale) / kScaleTics; } return true; } void Obj_UFOPieceThink(mobj_t *piece) { mobj_t *ufo = ufo_piece_owner(piece); if (ufo == NULL || P_MobjWasRemoved(ufo) == true) { P_KillMobj(piece, NULL, NULL, DMG_NORMAL); return; } piece->scalespeed = ufo->scalespeed; switch (ufo_piece_type(piece)) { case UFO_PIECE_TYPE_POD: { piece->destscale = 3 * ufo->destscale / 2; UFOMoveTo(piece, ufo->x, ufo->y, ufo->z + (132 * piece->scale)); if (S_SoundPlaying(ufo, sfx_clawzm) && ufo_speed(ufo) > 70*FRACUNIT) SpawnUFOSpeedLines(piece); break; } case UFO_PIECE_TYPE_GLASS: { // Flicker glass cracks for visibility if (piece->frame == FF_SEMIBRIGHT) { piece->frame = ufo_piece_glass_flickerframe(piece); } else { piece->frame = FF_SEMIBRIGHT; } piece->destscale = 5 * ufo->destscale / 3; UFOMoveTo(piece, ufo->x, ufo->y, ufo->z); break; } case UFO_PIECE_TYPE_GLASS_UNDER: { piece->destscale = 5 * ufo->destscale / 3; UFOMoveTo(piece, ufo->x, ufo->y, ufo->z); break; } case UFO_PIECE_TYPE_ARM: { fixed_t dis = (88 * piece->scale); fixed_t x = ufo->x - FixedMul(dis, FINECOSINE(piece->angle >> ANGLETOFINESHIFT)); fixed_t y = ufo->y - FixedMul(dis, FINESINE(piece->angle >> ANGLETOFINESHIFT)); piece->destscale = 3 * ufo->destscale / 2; UFOMoveTo(piece, x, y, ufo->z + (24 * piece->scale)); piece->angle -= FixedMul(ANG2, FixedDiv(ufo_speed(ufo), UFO_BASE_SPEED)); break; } case UFO_PIECE_TYPE_STEM: { fixed_t stemZ = ufo->z + (294 * piece->scale); fixed_t sc = FixedDiv(FixedDiv(ufo->ceilingz - stemZ, piece->scale), 15 * FRACUNIT); piece->destscale = 3 * ufo->destscale / 2; UFOMoveTo(piece, ufo->x, ufo->y, stemZ); if (sc > 0) { piece->spriteyscale = sc; } break; } default: { P_RemoveMobj(piece); return; } } } void Obj_UFOPieceDead(mobj_t *piece) { piece->renderflags ^= RF_DONTDRAW; } void Obj_UFOPieceRemoved(mobj_t *piece) { // Repair piece list. mobj_t *ufo = ufo_piece_owner(piece); mobj_t *next = ufo_piece_next(piece); mobj_t *prev = ufo_piece_prev(piece); if (prev != NULL && P_MobjWasRemoved(prev) == false) { P_SetTarget( &ufo_piece_next(prev), (next != NULL && P_MobjWasRemoved(next) == false) ? next : NULL ); } if (next != NULL && P_MobjWasRemoved(next) == false) { P_SetTarget( &ufo_piece_prev(next), (prev != NULL && P_MobjWasRemoved(prev) == false) ? prev : NULL ); } if (ufo != NULL && P_MobjWasRemoved(ufo) == false) { if (piece == ufo_pieces(ufo)) { P_SetTarget( &ufo_pieces(ufo), (next != NULL && P_MobjWasRemoved(next) == false) ? next : NULL ); } } P_SetTarget(&ufo_piece_next(piece), NULL); P_SetTarget(&ufo_piece_prev(piece), NULL); } static mobj_t *InitSpecialUFO(waypoint_t *start) { mobj_t *ufo = NULL; mobj_t *overlay = NULL; mobj_t *piece = NULL; mobj_t *prevPiece = NULL; size_t i; if (start == NULL) { // Simply create at the origin with default values. ufo = P_SpawnMobj(0, 0, 0, MT_SPECIAL_UFO); ufo_waypoint(ufo) = -1; // Invalidate ufo_distancetofinish(ufo) = INT32_MAX; } else { // Create with a proper waypoint track! ufo = P_SpawnMobj(start->mobj->x, start->mobj->y, start->mobj->z, MT_SPECIAL_UFO); ufo_waypoint(ufo) = (INT32)K_GetWaypointHeapIndex(start); UFOUpdateDistanceToFinish(ufo); specialstageinfo.maxDist = ufo_distancetofinish(ufo); } // Set specialDamage as early as possible, for glass ball's sake if (grandprixinfo.gp && grandprixinfo.specialDamage) { ufo->health -= min(4*(UINT32)mobjinfo[MT_SPECIAL_UFO].spawnhealth/10, grandprixinfo.specialDamage/6); } ufo_speed(ufo) = FixedMul(UFO_START_SPEED, K_GetKartGameSpeedScalar(gamespeed)); // Adjustable Special Stage emerald color/shape { mobj_t *emerald = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_EMERALD); emerald->flags |= MF_NOGRAVITY | MF_NOCLIP | MF_NOCLIPTHING | MF_NOCLIPHEIGHT; overlay = P_SpawnMobjFromMobj(emerald, 0, 0, 0, MT_OVERLAY); emerald->color = SKINCOLOR_CHAOSEMERALD1; i = ufo_emeraldnum(ufo) = P_GetNextEmerald(); if (i > 0) { emerald->color += (i - 1) % 7; if (i > 7) { // Super Emeralds P_SetMobjState(emerald, S_SUPEREMERALD1); P_SetMobjState(overlay, S_SUPEREMERALD_UNDER); } else { // Chaos Emerald P_SetMobjState(emerald, S_CHAOSEMERALD1); P_SetMobjState(overlay, S_CHAOSEMERALD_UNDER); } } else { // Prize -- todo, currently using fake Emerald P_SetMobjState(emerald, S_CHAOSEMERALD1); P_SetMobjState(overlay, S_CHAOSEMERALD_UNDER); emerald->color = SKINCOLOR_GOLD; } P_SetTarget(&emerald->target, ufo); P_SetTarget(&ufo_emerald(ufo), emerald); ufo->color = emerald->color; // for minimap overlay->color = emerald->color; P_SetTarget(&overlay->target, emerald); // UFO needs this so Jawz reticle lines up! ufo->sprzoff = 32 * mapobjectscale; emerald->sprzoff = ufo->sprzoff; } // Create UFO pieces. // First: UFO center. piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); P_SetTarget(&ufo_piece_owner(piece), ufo); P_SetMobjState(piece, S_SPECIAL_UFO_POD); ufo_piece_type(piece) = UFO_PIECE_TYPE_POD; overlay = P_SpawnMobjFromMobj(piece, 0, 0, 0, MT_OVERLAY); P_SetTarget(&overlay->target, piece); P_SetMobjState(overlay, S_SPECIAL_UFO_OVERLAY); P_SetTarget(&ufo_pieces(ufo), piece); prevPiece = piece; // Next, the glass ball. { piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); P_SetTarget(&ufo_piece_owner(piece), ufo); P_SetMobjState(piece, S_SPECIAL_UFO_GLASS); ufo_piece_type(piece) = UFO_PIECE_TYPE_GLASS; set_flickerframe(ufo, piece); /*overlay = P_SpawnMobjFromMobj(piece, 0, 0, 0, MT_OVERLAY); P_SetTarget(&overlay->target, piece); P_SetMobjState(overlay, S_SPECIAL_UFO_GLASS_UNDER); overlay->dispoffset = -20;*/ P_SetTarget(&ufo_piece_next(prevPiece), piece); P_SetTarget(&ufo_piece_prev(piece), prevPiece); prevPiece = piece; } // This SHOULD have been an MT_OVERLAY... but it simply doesn't // draw-order stack with the Emerald correctly any other way. { piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); P_SetTarget(&ufo_piece_owner(piece), ufo); P_SetMobjState(piece, S_SPECIAL_UFO_GLASS_UNDER); ufo_piece_type(piece) = UFO_PIECE_TYPE_GLASS_UNDER; piece->dispoffset = -2; P_SetTarget(&ufo_piece_next(prevPiece), piece); P_SetTarget(&ufo_piece_prev(piece), prevPiece); prevPiece = piece; } // Add the catcher arms. for (i = 0; i < UFO_NUMARMS; i++) { piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); P_SetTarget(&ufo_piece_owner(piece), ufo); P_SetMobjState(piece, S_SPECIAL_UFO_ARM); ufo_piece_type(piece) = UFO_PIECE_TYPE_ARM; piece->angle = UFO_ARMDELTA * i; P_SetTarget(&ufo_piece_next(prevPiece), piece); P_SetTarget(&ufo_piece_prev(piece), prevPiece); prevPiece = piece; } // Add the stem. piece = P_SpawnMobjFromMobj(ufo, 0, 0, 0, MT_SPECIAL_UFO_PIECE); P_SetTarget(&ufo_piece_owner(piece), ufo); P_SetMobjState(piece, S_SPECIAL_UFO_STEM); ufo_piece_type(piece) = UFO_PIECE_TYPE_STEM; P_SetTarget(&ufo_piece_next(prevPiece), piece); P_SetTarget(&ufo_piece_prev(piece), prevPiece); prevPiece = piece; return ufo; } mobj_t *Obj_CreateSpecialUFO(void) { return InitSpecialUFO(K_GetStartingWaypoint()); } UINT32 K_GetSpecialUFODistance(void) { if (specialstageinfo.valid == true) { if (specialstageinfo.ufo != NULL && P_MobjWasRemoved(specialstageinfo.ufo) == false) { return (UINT32)ufo_distancetofinish(specialstageinfo.ufo); } } return UINT32_MAX; } void Obj_UFOEmeraldThink(mobj_t *emerald) { mobj_t *ufo = emerald->target; P_MoveOrigin(emerald, ufo->x, ufo->y, ufo->z); if (UFOEmeraldChase(ufo) == true) { // Spawn emerald sparkles UFOEmeraldVFX(emerald); } }