mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2026-01-14 10:42:30 +00:00
292 lines
7.3 KiB
C
292 lines
7.3 KiB
C
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2025 by Lachlan "Lach" Wright
|
|
// Copyright (C) 2025 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 ancient-gear.c
|
|
/// \brief Ancient Gear object code.
|
|
|
|
#include "../p_local.h"
|
|
#include "../k_objects.h"
|
|
#include "../s_sound.h"
|
|
#include "../k_endcam.h"
|
|
#include "../k_hud.h"
|
|
#include "../m_cond.h"
|
|
#include "../g_game.h"
|
|
|
|
#define DEATH_FREEZE_TIME (5*TICRATE)
|
|
#define DEATH_TIME (2*TICRATE)
|
|
#define DEATH_RISE_SPEED (3*FRACUNIT)
|
|
|
|
#define DELTA_YAW (-ANG2)
|
|
#define DELTA_ROLL (ANG2)
|
|
static const angle_t DELTA_YAW_ACCELERATION = (-ANG1 / 2);
|
|
static const angle_t DELTA_ROLL_ACCELERATION = (ANG1 / 3);
|
|
static const fixed_t DELTA_SPRITEXSCALE = ((FRACUNIT/4 - FRACUNIT) / DEATH_TIME);
|
|
static const fixed_t DELTA_SPRITEYSCALE = ((FRACUNIT*3 - FRACUNIT) / DEATH_TIME);
|
|
static const tic_t TRANS_SHIFT_RATE = (DEATH_TIME / 10);
|
|
|
|
#define MAX_GEARS 32
|
|
|
|
static UINT8 numGears = 0;
|
|
static UINT32 gearBank = 0;
|
|
static UINT8 gearBankIndex = 0;
|
|
static boolean allGearsCollected = false;
|
|
static player_t *collectingPlayer = NULL;
|
|
|
|
static void UpdateAncientGearPart(mobj_t *part)
|
|
{
|
|
mobj_t *gear = part->target;
|
|
fixed_t radius = FixedMul(FixedMul(part->movefactor, gear->scale), gear->spritexscale);
|
|
fixed_t xOffset = P_ReturnThrustX(NULL, part->angle - ANGLE_90, radius);
|
|
fixed_t yOffset = P_ReturnThrustY(NULL, part->angle - ANGLE_90, radius);
|
|
|
|
P_InstaScale(part, gear->scale);
|
|
P_MoveOrigin(part,
|
|
gear->x + xOffset,
|
|
gear->y + yOffset,
|
|
gear->z + gear->height / 2
|
|
);
|
|
part->spritexscale = gear->spritexscale;
|
|
part->spriteyscale = gear->spriteyscale;
|
|
}
|
|
|
|
void Obj_AncientGearSpawn(mobj_t *gear)
|
|
{
|
|
UINT8 i;
|
|
mobj_t *part = gear;
|
|
|
|
numGears++;
|
|
|
|
gear->extravalue1 = DELTA_YAW;
|
|
gear->extravalue2 = DELTA_ROLL;
|
|
|
|
for (i = 0; i < 6; i++)
|
|
{
|
|
P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(gear, 0, 0, 0, MT_ANCIENTGEAR_PART));
|
|
P_SetTarget(&part->hnext->hprev, part);
|
|
part = part->hnext;
|
|
P_SetTarget(&part->target, gear);
|
|
|
|
part->angle += (i & 1) * ANGLE_180;
|
|
|
|
if (i < 2) // middle parts
|
|
{
|
|
part->angle += ANGLE_90;
|
|
part->movefactor = 10 * FRACUNIT; // horizontal offset from hitbox
|
|
part->extravalue1 = 0; // direction to roll the sprite
|
|
part->frame = (part->frame & ~FF_FRAMEMASK) | 1;
|
|
P_SetTarget(&part->tracer, gear);
|
|
part->flags2 |= MF2_LINKDRAW;
|
|
}
|
|
else // side parts
|
|
{
|
|
part->movefactor = 7 * FRACUNIT; // horizontal offset from hitbox
|
|
part->extravalue1 = (i & 1) * 2 - 1; // direction to roll the sprite
|
|
if (i > 3) // fake brightmaps
|
|
{
|
|
part->frame = (part->frame & ~FF_FRAMEMASK) | 3 | FF_FULLBRIGHT;
|
|
part->dispoffset++;
|
|
}
|
|
else
|
|
{
|
|
part->frame = (part->frame & ~FF_FRAMEMASK) | 2;
|
|
}
|
|
}
|
|
|
|
UpdateAncientGearPart(part);
|
|
part->old_x = part->x;
|
|
part->old_y = part->y;
|
|
part->old_z = part->z;
|
|
part->old_angle = part->angle;
|
|
part->old_scale = part->scale;
|
|
}
|
|
}
|
|
|
|
void Obj_AncientGearPartThink(mobj_t *part)
|
|
{
|
|
mobj_t *gear = part->target;
|
|
if (P_MobjWasRemoved(gear))
|
|
{
|
|
P_RemoveMobj(part);
|
|
return;
|
|
}
|
|
part->angle += gear->extravalue1;
|
|
part->rollangle += gear->extravalue2 * part->extravalue1;
|
|
UpdateAncientGearPart(part);
|
|
}
|
|
|
|
void Obj_AncientGearRemoved(mobj_t *gear)
|
|
{
|
|
while (!P_MobjWasRemoved(gear->hnext))
|
|
{
|
|
P_RemoveMobj(gear->hnext);
|
|
}
|
|
}
|
|
|
|
void Obj_AncientGearTouch(mobj_t *gear, mobj_t *toucher)
|
|
{
|
|
P_KillMobj(gear, NULL, toucher, DMG_NORMAL);
|
|
}
|
|
|
|
void Obj_AncientGearDeath(mobj_t *gear, mobj_t *source)
|
|
{
|
|
if (--numGears == 0)
|
|
{
|
|
allGearsCollected = true;
|
|
M_UpdateUnlockablesAndExtraEmblems(true, true);
|
|
}
|
|
|
|
// if this gear has a bank account, mark it as collected so that it doesn't respawn upon retrying the map
|
|
if (gear->threshold != 0)
|
|
{
|
|
gearBank |= (1 << (gear->threshold - 1));
|
|
}
|
|
|
|
gear->fuse = DEATH_TIME;
|
|
gear->shadowscale = gear->spritexscale;
|
|
|
|
// give the gear some upwards momentum
|
|
gear->flags |= MF_NOCLIPHEIGHT;
|
|
P_SetObjectMomZ(gear, DEATH_RISE_SPEED, false);
|
|
|
|
// don't activate the round win camera if there is no camera target,
|
|
// or if the round win camera is already active for any actual reason
|
|
if (P_MobjWasRemoved(source) || source->player == NULL || g_endcam.active)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// track the collecting player to display a message for them
|
|
collectingPlayer = source->player;
|
|
P_SetTarget(&gear->target, source);
|
|
|
|
// play the collection jingle!
|
|
S_StartSound(NULL, gear->info->seesound);
|
|
|
|
// fade out the music for as long as the sound plays
|
|
g_musicfade.start = leveltime;
|
|
g_musicfade.end = g_musicfade.start + DEATH_FREEZE_TIME;
|
|
g_musicfade.fade = 12;
|
|
g_musicfade.ticked = false;
|
|
|
|
// start the round win camera
|
|
gear->flags2 |= MF2_BEYONDTHEGRAVE; // a gear with this flag will stop the round win camera upon next thinking
|
|
K_StartRoundWinCamera(
|
|
source,
|
|
source->player->angleturn,
|
|
FixedMul(cv_cam_dist[0].value, mapobjectscale),
|
|
6*TICRATE,
|
|
FRACUNIT/16,
|
|
DEATH_FREEZE_TIME
|
|
);
|
|
}
|
|
|
|
void Obj_AncientGearDeadThink(mobj_t *gear)
|
|
{
|
|
mobj_t *part = gear;
|
|
|
|
// if the round win camera was activated, tell it to stop focusing on the player,
|
|
// and show the player a message
|
|
if (gear->flags2 & MF2_BEYONDTHEGRAVE)
|
|
{
|
|
gear->flags2 &= ~MF2_BEYONDTHEGRAVE;
|
|
K_StopRoundWinCamera();
|
|
|
|
collectingPlayer = NULL;
|
|
K_AddMessage(
|
|
numGears > 0 ? va("%d Ancient Gear%s left!", numGears, numGears == 1 ? "" : "s")
|
|
: "All Ancient Gears collected!"
|
|
, true, false
|
|
);
|
|
}
|
|
|
|
// play another sound once immediately after the round win camera finishes
|
|
if (!(gear->flags2 & MF2_DONTRESPAWN))
|
|
{
|
|
gear->flags2 |= MF2_DONTRESPAWN;
|
|
S_StartSound(gear, gear->info->deathsound);
|
|
}
|
|
|
|
// increase the translucency level every so often
|
|
if (gear->fuse % TRANS_SHIFT_RATE == 0)
|
|
{
|
|
while (!P_MobjWasRemoved(part = part->hnext))
|
|
{
|
|
part->frame += FF_TRANS10;
|
|
}
|
|
}
|
|
|
|
// accelerate the spinning and rotating
|
|
gear->extravalue1 += DELTA_YAW_ACCELERATION;
|
|
gear->extravalue2 += DELTA_ROLL_ACCELERATION;
|
|
|
|
// stretch the gear
|
|
gear->spritexscale += DELTA_SPRITEXSCALE;
|
|
gear->spriteyscale += DELTA_SPRITEYSCALE;
|
|
gear->shadowscale = gear->spritexscale;
|
|
}
|
|
|
|
boolean Obj_AllowNextAncientGearSpawn(void)
|
|
{
|
|
// always allow gears spawned by objectplace
|
|
if (objectplacing)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// don't allow the map to spawn more gears than we can bank
|
|
if (gearBankIndex >= MAX_GEARS)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "Map exceeds maximum possible number of Ancient Gears (%d)!\n", MAX_GEARS);
|
|
return false;
|
|
}
|
|
|
|
// don't spawn a gear that was already collected prior to retrying
|
|
if (gearBank & (1 << gearBankIndex++))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Obj_AncientGearSetup(mobj_t *gear, mapthing_t *mt)
|
|
{
|
|
mobj_t *part = gear;
|
|
while ((part = part->hnext))
|
|
{
|
|
UpdateAncientGearPart(part); // update part scales to apply mapthing scales
|
|
}
|
|
|
|
if (!objectplacing)
|
|
{
|
|
gear->threshold = gearBankIndex; // allocate this gear a bank account slot
|
|
}
|
|
}
|
|
|
|
void Obj_AncientGearLevelInit(void)
|
|
{
|
|
if (!G_GetRetryFlag() || G_IsModeAttackRetrying())
|
|
{
|
|
gearBank = 0;
|
|
allGearsCollected = false;
|
|
}
|
|
gearBankIndex = 0;
|
|
numGears = 0;
|
|
collectingPlayer = NULL;
|
|
}
|
|
|
|
player_t *Obj_GetAncientGearCollectingPlayer(void)
|
|
{
|
|
return collectingPlayer;
|
|
}
|
|
|
|
boolean Obj_AllAncientGearsCollected(void)
|
|
{
|
|
return allGearsCollected;
|
|
}
|