mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
1448 lines
33 KiB
C
1448 lines
33 KiB
C
// 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);
|
|
}
|
|
}
|