From 8ca3d41696d3c3428bf5209be840585c0087f884 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 21 Nov 2022 23:51:26 -0500 Subject: [PATCH] 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. --- src/deh_tables.c | 2 + src/info.c | 27 +++ src/info.h | 2 + src/k_kart.c | 260 ++++++++++++++++++---------- src/k_objects.h | 4 + src/k_specialstage.c | 3 + src/k_specialstage.h | 2 +- src/k_waypoint.c | 59 +++++-- src/objects/Sourcefile | 1 + src/objects/ufo.c | 375 +++++++++++++++++++++++++++++++++++++++++ src/p_mobj.c | 5 + 11 files changed, 630 insertions(+), 110 deletions(-) create mode 100644 src/objects/ufo.c diff --git a/src/deh_tables.c b/src/deh_tables.c index aa9dc9310..2198466f5 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -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[] = { diff --git a/src/info.c b/src/info.c index 44a4d25ae..47d593aac 100644 --- a/src/info.c +++ b/src/info.c @@ -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] = { diff --git a/src/info.h b/src/info.h index 78fc6e2db..02f69263a 100644 --- a/src/info.h +++ b/src/info.h @@ -6667,6 +6667,8 @@ typedef enum mobj_type MT_BEAMPOINT, + MT_SPECIAL_UFO, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_kart.c b/src/k_kart.c index 40d1ff0a2..f781c64cb 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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; } - 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. diff --git a/src/k_objects.h b/src/k_objects.h index 96e0fa2b5..127435c91 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -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*/ diff --git a/src/k_specialstage.c b/src/k_specialstage.c index 3a2d751ac..3700d98fb 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -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++) { diff --git a/src/k_specialstage.h b/src/k_specialstage.h index 8e11d761d..c93136b99 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -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; /*-------------------------------------------------- diff --git a/src/k_waypoint.c b/src/k_waypoint.c index 28ff00d04..4577b4f55 100644 --- a/src/k_waypoint.c +++ b/src/k_waypoint.c @@ -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}; diff --git a/src/objects/Sourcefile b/src/objects/Sourcefile index b8cb63b1f..1db7b4a33 100644 --- a/src/objects/Sourcefile +++ b/src/objects/Sourcefile @@ -7,3 +7,4 @@ manta-ring.c orbinaut.c jawz.c duel-bomb.c +ufo.c diff --git a/src/objects/ufo.c b/src/objects/ufo.c new file mode 100644 index 000000000..8b5dfaf6a --- /dev/null +++ b/src/objects/ufo.c @@ -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); +} diff --git a/src/p_mobj.c b/src/p_mobj.c index d5924de51..d2ec29404 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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)