First pass on Special Stage UFO

- UFO spawns at first waypoint for TOL_SPECIAL maps
- UFO moves up the waypoints, the speed varying based on your position relative to it and your own speed.
- You are able to tether off of the UFO.
- Bugfix: PathfindThruCircuit no longer fails when reaching a one-ended waypoint.

Damage is my next project but I wanted to get this committed for now.
This commit is contained in:
Sally Coolatta 2022-11-21 23:51:26 -05:00
parent df35a72c58
commit 8ca3d41696
11 changed files with 630 additions and 110 deletions

View file

@ -5617,6 +5617,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t
"MT_PAPERITEMSPOT",
"MT_BEAMPOINT",
"MT_SPECIAL_UFO",
};
const char *const MOBJFLAG_LIST[] = {

View file

@ -28990,6 +28990,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
MF_NOBLOCKMAP|MF_NOSECTOR|MF_NOCLIPTHING|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
{ // MT_SPECIAL_UFO
-1, // doomednum
S_CHAOSEMERALD1, // spawnstate
1000, // spawnhealth
S_NULL, // seestate
sfx_None, // seesound
8, // reactiontime
sfx_None, // attacksound
S_NULL, // painstate
0, // painchance
sfx_None, // painsound
S_NULL, // meleestate
S_NULL, // missilestate
S_NULL, // deathstate
S_NULL, // xdeathstate
sfx_None, // deathsound
0, // speed
72*FRACUNIT, // radius
72*FRACUNIT, // height
0, // display offset
16, // mass
0, // damage
sfx_None, // activesound
MF_SOLID|MF_SHOOTABLE|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags
S_NULL // raisestate
},
};
skincolor_t skincolors[MAXSKINCOLORS] = {

View file

@ -6667,6 +6667,8 @@ typedef enum mobj_type
MT_BEAMPOINT,
MT_SPECIAL_UFO,
MT_FIRSTFREESLOT,
MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1,
NUMMOBJTYPES

View file

@ -183,6 +183,9 @@ UINT32 K_GetPlayerDontDrawFlag(player_t *player)
{
UINT32 flag = 0;
if (player == NULL)
return flag;
if (player == &players[displayplayers[0]])
flag = RF_DONTDRAWP1;
else if (r_splitscreen >= 1 && player == &players[displayplayers[1]])
@ -1979,7 +1982,7 @@ static void K_UpdateOffroad(player_t *player)
player->offroad = 0;
}
static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent)
static void K_DrawDraftCombiring(player_t *player, mobj_t *victim, fixed_t curdist, fixed_t maxdist, boolean transparent)
{
#define CHAOTIXBANDLEN 15
#define CHAOTIXBANDCOLORS 9
@ -2010,9 +2013,9 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur
c = FixedMul(CHAOTIXBANDCOLORS<<FRACBITS, FixedDiv(curdist-minimumdist, maxdist-minimumdist)) >> FRACBITS;
}
stepx = (victim->mo->x - player->mo->x) / CHAOTIXBANDLEN;
stepy = (victim->mo->y - player->mo->y) / CHAOTIXBANDLEN;
stepz = ((victim->mo->z + (victim->mo->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN;
stepx = (victim->x - player->mo->x) / CHAOTIXBANDLEN;
stepy = (victim->y - player->mo->y) / CHAOTIXBANDLEN;
stepz = ((victim->z + (victim->height / 2)) - (player->mo->z + (player->mo->height / 2))) / CHAOTIXBANDLEN;
curx = player->mo->x + stepx;
cury = player->mo->y + stepy;
@ -2046,7 +2049,7 @@ static void K_DrawDraftCombiring(player_t *player, player_t *victim, fixed_t cur
if (transparent)
band->renderflags |= RF_GHOSTLY;
band->renderflags |= RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim));
band->renderflags |= RF_DONTDRAW & ~(K_GetPlayerDontDrawFlag(player) | K_GetPlayerDontDrawFlag(victim->player));
}
curx += stepx;
@ -2071,6 +2074,129 @@ static boolean K_HasInfiniteTether(player_t *player)
return false;
}
static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed_t draftdistance, UINT8 leniency)
{
//#define EASYDRAFTTEST
fixed_t dist, olddraft;
fixed_t theirSpeed = 0;
#ifndef EASYDRAFTTEST
angle_t yourangle, theirangle, diff;
#endif
#ifndef EASYDRAFTTEST
// Don't draft on yourself :V
if (dest->player && dest->player == player)
{
return false;
}
#endif
if (dest->player != NULL)
{
// No tethering off of the guy who got the starting bonus :P
if (dest->player->startboost > 0)
{
return false;
}
theirSpeed = dest->player->speed;
}
else
{
theirSpeed = R_PointToDist2(0, 0, dest->momx, dest->momy);
}
// They're not enough speed to draft off of them.
if (theirSpeed < 20 * dest->scale)
{
return false;
}
#ifndef EASYDRAFTTEST
yourangle = K_MomentumAngle(player->mo);
theirangle = K_MomentumAngle(dest);
// Not in front of this player.
diff = AngleDelta(R_PointToAngle2(player->mo->x, player->mo->y, dest->x, dest->y), yourangle);
if (diff > ANG10)
{
return false;
}
// Not moving in the same direction.
diff = AngleDelta(yourangle, theirangle);
if (diff > ANGLE_90)
{
return false;
}
#endif
dist = P_AproxDistance(P_AproxDistance(dest->x - player->mo->x, dest->y - player->mo->y), dest->z - player->mo->z);
#ifndef EASYDRAFTTEST
// TOO close to draft.
if (dist < minDist)
{
return false;
}
// Not close enough to draft.
if (dist > draftdistance && draftdistance > 0)
{
return false;
}
#endif
olddraft = player->draftpower;
player->draftleeway = leniency;
if (dest->player != NULL)
{
player->lastdraft = dest->player - players;
}
else
{
player->lastdraft = MAXPLAYERS;
}
// Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed.
// How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic)
if (player->draftpower < FRACUNIT)
{
fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));;
player->draftpower += add;
if (player->bot && player->botvars.rival)
{
// Double speed for the rival!
player->draftpower += add;
}
if (gametype == GT_BATTLE)
{
// TODO: gametyperules
// Double speed in Battle
player->draftpower += add;
}
}
if (player->draftpower > FRACUNIT)
{
player->draftpower = FRACUNIT;
}
// Play draft finish noise
if (olddraft < FRACUNIT && player->draftpower >= FRACUNIT)
{
S_StartSound(player->mo, sfx_cdfm62);
}
// Spawn in the visual!
K_DrawDraftCombiring(player, dest, dist, draftdistance, false);
return true;
}
/** \brief Updates the player's drafting values once per frame
\param player player object passed from K_KartPlayerThink
@ -2079,6 +2205,9 @@ static boolean K_HasInfiniteTether(player_t *player)
*/
static void K_UpdateDraft(player_t *player)
{
const boolean addUfo = ((specialStage.active == true)
&& (specialStage.ufo != NULL && P_MobjWasRemoved(specialStage.ufo) == false));
fixed_t topspd = K_GetKartSpeed(player, false, false);
fixed_t draftdistance;
fixed_t minDist;
@ -2116,104 +2245,43 @@ static void K_UpdateDraft(player_t *player)
}
// Not enough speed to draft.
if (player->speed >= 20*player->mo->scale)
if (player->speed >= 20 * player->mo->scale)
{
//#define EASYDRAFTTEST
if (addUfo == true)
{
// Tether off of the UFO!
if (K_TryDraft(player, specialStage.ufo, minDist, draftdistance, leniency) == true)
{
return; // Finished doing our draft.
}
}
// Let's hunt for players to draft off of!
for (i = 0; i < MAXPLAYERS; i++)
{
fixed_t dist, olddraft;
#ifndef EASYDRAFTTEST
angle_t yourangle, theirangle, diff;
#endif
player_t *otherPlayer = NULL;
if (!playeringame[i] || players[i].spectator || !players[i].mo)
continue;
#ifndef EASYDRAFTTEST
// Don't draft on yourself :V
if (&players[i] == player)
continue;
#endif
// Not enough speed to draft off of.
if (players[i].speed < 20*players[i].mo->scale)
continue;
// No tethering off of the guy who got the starting bonus :P
if (players[i].startboost > 0)
continue;
#ifndef EASYDRAFTTEST
yourangle = K_MomentumAngle(player->mo);
theirangle = K_MomentumAngle(players[i].mo);
diff = R_PointToAngle2(player->mo->x, player->mo->y, players[i].mo->x, players[i].mo->y) - yourangle;
if (diff > ANGLE_180)
diff = InvAngle(diff);
// Not in front of this player.
if (diff > ANG10)
continue;
diff = yourangle - theirangle;
if (diff > ANGLE_180)
diff = InvAngle(diff);
// Not moving in the same direction.
if (diff > ANGLE_90)
continue;
#endif
dist = P_AproxDistance(P_AproxDistance(players[i].mo->x - player->mo->x, players[i].mo->y - player->mo->y), players[i].mo->z - player->mo->z);
#ifndef EASYDRAFTTEST
// TOO close to draft.
if (dist < minDist)
continue;
// Not close enough to draft.
if (dist > draftdistance && draftdistance > 0)
continue;
#endif
olddraft = player->draftpower;
player->draftleeway = leniency;
player->lastdraft = i;
// Draft power is used later in K_GetKartBoostPower, ranging from 0 for normal speed and FRACUNIT for max draft speed.
// How much this increments every tic biases toward acceleration! (min speed gets 1.5% per tic, max speed gets 0.5% per tic)
if (player->draftpower < FRACUNIT)
if (playeringame[i] == false)
{
fixed_t add = (FRACUNIT/200) + ((9 - player->kartspeed) * ((3*FRACUNIT)/1600));;
player->draftpower += add;
if (player->bot && player->botvars.rival)
{
// Double speed for the rival!
player->draftpower += add;
}
if (gametype == GT_BATTLE)
{
// TODO: gametyperules
// Double speed in Battle
player->draftpower += add;
}
continue;
}
if (player->draftpower > FRACUNIT)
player->draftpower = FRACUNIT;
otherPlayer = &players[i];
// Play draft finish noise
if (olddraft < FRACUNIT && player->draftpower >= FRACUNIT)
S_StartSound(player->mo, sfx_cdfm62);
if (otherPlayer->spectator == true)
{
continue;
}
// Spawn in the visual!
K_DrawDraftCombiring(player, &players[i], dist, draftdistance, false);
if (otherPlayer->mo == NULL || P_MobjWasRemoved(otherPlayer->mo) == true)
{
continue;
}
return; // Finished doing our draft.
if (K_TryDraft(player, otherPlayer->mo, minDist, draftdistance, leniency) == true)
{
return; // Finished doing our draft.
}
}
}
@ -2234,7 +2302,13 @@ static void K_UpdateDraft(player_t *player)
{
player_t *victim = &players[player->lastdraft];
fixed_t dist = P_AproxDistance(P_AproxDistance(victim->mo->x - player->mo->x, victim->mo->y - player->mo->y), victim->mo->z - player->mo->z);
K_DrawDraftCombiring(player, victim, dist, draftdistance, true);
K_DrawDraftCombiring(player, victim->mo, dist, draftdistance, true);
}
else if (addUfo == true)
{
// kind of a hack to not have to mess with how lastdraft works
fixed_t dist = P_AproxDistance(P_AproxDistance(specialStage.ufo->x - player->mo->x, specialStage.ufo->y - player->mo->y), specialStage.ufo->z - player->mo->z);
K_DrawDraftCombiring(player, specialStage.ufo, dist, draftdistance, true);
}
}
else // Remove draft speed boost.

View file

@ -54,4 +54,8 @@ void Obj_DuelBombReverse(mobj_t *bomb);
void Obj_DuelBombTouch(mobj_t *bomb, mobj_t *toucher);
void Obj_DuelBombInit(mobj_t *bomb);
/* Special Stage UFO */
void Obj_SpecialUFOThinker(mobj_t *bomb);
mobj_t *Obj_CreateSpecialUFO(void);
#endif/*k_objects_H*/

View file

@ -20,6 +20,7 @@
#include "st_stuff.h"
#include "z_zone.h"
#include "k_waypoint.h"
#include "k_objects.h"
struct specialStage specialStage;
@ -30,6 +31,7 @@ struct specialStage specialStage;
--------------------------------------------------*/
void K_ResetSpecialStage(void)
{
P_SetTarget(&specialStage.ufo, NULL);
memset(&specialStage, 0, sizeof(struct specialStage));
}
@ -43,6 +45,7 @@ void K_InitSpecialStage(void)
INT32 i;
specialStage.beamDist = UINT32_MAX; // TODO: make proper value
P_SetTarget(&specialStage.ufo, Obj_CreateSpecialUFO());
for (i = 0; i < MAXPLAYERS; i++)
{

View file

@ -22,7 +22,7 @@ extern struct specialStage
boolean encore; ///< Copy of encore, just to make sure you can't cheat it with cvars
UINT32 beamDist; ///< Where the exit beam is.
mobj_t *capsule; ///< The Chaos Emerald capsule.
mobj_t *ufo; ///< The Chaos Emerald capsule.
} specialStage;
/*--------------------------------------------------

View file

@ -1070,6 +1070,45 @@ static boolean K_WaypointPathfindReachedEnd(void *data, void *setupData)
return isEnd;
}
/*--------------------------------------------------
static boolean K_WaypointPathfindNextValid(void *data, void *setupData)
Returns if the current waypoint data has a next waypoint.
Input Arguments:-
data - Should point to a pathfindnode_t to compare
setupData - Should point to the pathfindsetup_t to compare
Return:-
True if the waypoint has a next waypoint, false otherwise.
--------------------------------------------------*/
static boolean K_WaypointPathfindNextValid(void *data, void *setupData)
{
boolean nextValid = false;
if (data == NULL || setupData == NULL)
{
CONS_Debug(DBG_GAMELOGIC, "K_WaypointPathfindNextValid received NULL data.\n");
}
else
{
pathfindnode_t *node = (pathfindnode_t *)data;
pathfindsetup_t *setup = (pathfindsetup_t *)setupData;
waypoint_t *wp = (waypoint_t *)node->nodedata;
if (setup->getconnectednodes == K_WaypointPathfindGetPrev)
{
nextValid = (wp->numprevwaypoints > 0U);
}
else
{
nextValid = (wp->numnextwaypoints > 0U);
}
}
return nextValid;
}
/*--------------------------------------------------
static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData)
@ -1094,8 +1133,9 @@ static boolean K_WaypointPathfindReachedGScore(void *data, void *setupData)
{
pathfindnode_t *node = (pathfindnode_t *)data;
pathfindsetup_t *setup = (pathfindsetup_t *)setupData;
boolean nextValid = K_WaypointPathfindNextValid(data, setupData);
scoreReached = (node->gscore >= setup->endgscore);
scoreReached = (node->gscore >= setup->endgscore) || (nextValid == false);
}
return scoreReached;
@ -1127,8 +1167,9 @@ static boolean K_WaypointPathfindReachedGScoreSpawnable(void *data, void *setupD
pathfindnode_t *node = (pathfindnode_t *)data;
pathfindsetup_t *setup = (pathfindsetup_t *)setupData;
waypoint_t *wp = (waypoint_t *)node->nodedata;
boolean nextValid = K_WaypointPathfindNextValid(data, setupData);
scoreReached = (node->gscore >= setup->endgscore);
scoreReached = (node->gscore >= setup->endgscore) || (nextValid == false);
spawnable = K_GetWaypointIsSpawnpoint(wp);
}
@ -1251,13 +1292,6 @@ boolean K_PathfindThruCircuit(
"K_PathfindThruCircuit: sourcewaypoint with ID %d has no next waypoint\n",
K_GetWaypointID(sourcewaypoint));
}
else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0))
|| ((huntbackwards == true) && (finishline->numnextwaypoints == 0)))
{
CONS_Debug(DBG_GAMELOGIC,
"K_PathfindThruCircuit: finishline with ID %d has no previous waypoint\n",
K_GetWaypointID(finishline));
}
else
{
pathfindsetup_t pathfindsetup = {0};
@ -1334,13 +1368,6 @@ boolean K_PathfindThruCircuitSpawnable(
"K_PathfindThruCircuitSpawnable: sourcewaypoint with ID %d has no next waypoint\n",
K_GetWaypointID(sourcewaypoint));
}
else if (((huntbackwards == false) && (finishline->numprevwaypoints == 0))
|| ((huntbackwards == true) && (finishline->numnextwaypoints == 0)))
{
CONS_Debug(DBG_GAMELOGIC,
"K_PathfindThruCircuitSpawnable: finishline with ID %d has no previous waypoint\n",
K_GetWaypointID(finishline));
}
else
{
pathfindsetup_t pathfindsetup = {0};

View file

@ -7,3 +7,4 @@ manta-ring.c
orbinaut.c
jawz.c
duel-bomb.c
ufo.c

375
src/objects/ufo.c Normal file
View file

@ -0,0 +1,375 @@
// 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 shrink.c
/// \brief Shrink laser 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"
#define UFO_BASE_SPEED (12 * FRACUNIT) // UFO's slowest speed.
#define UFO_SPEEDUP (FRACUNIT)
#define UFO_SLOWDOWN (FRACUNIT >> 2)
#define UFO_SPACING (1024 * FRACUNIT)
#define UFO_DEADZONE (512 * FRACUNIT)
#define UFO_SPEEDFACTOR (FRACUNIT * 9 / 10)
#define ufo_waypoint(o) ((o)->extravalue1)
#define ufo_distancetofinish(o) ((o)->extravalue2)
#define ufo_speed(o) ((o)->watertop)
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 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 baseSpeed = FixedMul(UFO_BASE_SPEED, K_GetKartGameSpeedScalar(gamespeed));
const UINT32 spacing = FixedMul(FixedMul(UFO_SPACING, mapobjectscale), K_GetKartGameSpeedScalar(gamespeed)) >> FRACBITS;
const UINT32 deadzone = FixedMul(FixedMul(UFO_DEADZONE, mapobjectscale), K_GetKartGameSpeedScalar(gamespeed)) >> 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 = 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 > spacing)
{
wantedDist = bestDist - spacing;
}
else
{
wantedDist = 0;
}
distDelta = ufo_distancetofinish(ufo) - wantedDist;
if (abs(distDelta) <= deadzone)
{
// We're in a good spot, try to match the player.
wantedSpeed = max(bestSpeed >> 1, baseSpeed);
}
else
{
if (distDelta > 0)
{
// Too far behind! Start speeding up!
wantedSpeed = max(bestSpeed << 1, baseSpeed << 2);
}
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;
}
}
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;
}
static void UFOMove(mobj_t *ufo)
{
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 boolean useshortcuts = false;
const boolean huntbackwards = false;
boolean pathfindsuccess = false;
path_t pathtofinish = {0};
size_t pathIndex = 0;
curWaypoint = K_GetWaypointFromIndex((size_t)ufo_waypoint(ufo));
destWaypoint = K_GetFinishLineWaypoint();
if (curWaypoint == NULL || destWaypoint == NULL)
{
// Waypoints aren't valid.
// Just stand still.
ufo->momx = 0;
ufo->momy = 0;
ufo->momz = 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;
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.
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 transition into the next state.
break;
}
}
pathIndex++;
if (pathIndex >= pathtofinish.numnodes)
{
// Successfully reached the end of the path.
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 (pathfindsuccess == true)
{
Z_Free(pathtofinish.array);
}
}
void Obj_SpecialUFOThinker(mobj_t *ufo)
{
UFOMove(ufo);
UFOUpdateAngle(ufo);
UFOUpdateDistanceToFinish(ufo);
UFOUpdateSpeed(ufo);
}
static mobj_t *InitSpecialUFO(waypoint_t *start)
{
mobj_t *ufo = NULL;
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);
}
ufo_speed(ufo) = UFO_BASE_SPEED;
return ufo;
}
mobj_t *Obj_CreateSpecialUFO(void)
{
waypoint_t *finishWaypoint = K_GetFinishLineWaypoint();
waypoint_t *startWaypoint = NULL;
if (finishWaypoint != NULL)
{
const boolean huntbackwards = true;
const boolean useshortcuts = false;
const UINT32 traveldist = UINT32_MAX; // Go as far back as possible.
boolean pathfindsuccess = false;
path_t pathtofinish = {0};
pathfindsuccess = K_PathfindThruCircuit(
finishWaypoint, traveldist,
&pathtofinish,
useshortcuts, huntbackwards
);
if (pathfindsuccess == true)
{
startWaypoint = (waypoint_t *)pathtofinish.array[ pathtofinish.numnodes - 1 ].nodedata;
Z_Free(pathtofinish.array);
}
}
return InitSpecialUFO(startWaypoint);
}

View file

@ -7176,6 +7176,11 @@ static boolean P_MobjRegularThink(mobj_t *mobj)
Obj_DuelBombThink(mobj);
break;
}
case MT_SPECIAL_UFO:
{
Obj_SpecialUFOThinker(mobj);
break;
}
case MT_EMERALD:
{
if (battleovertime.enabled >= 10*TICRATE)