RingRacers/src/objects/audience.c
2025-02-13 15:32:26 -06:00

372 lines
8.5 KiB
C

// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2025 by Vivian "toastergrl" Grannell.
// 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 objects/audience.c
/// \brief Follower Audience
#include "../doomdef.h"
#include "../info.h"
#include "../g_game.h"
#include "../k_objects.h"
#include "../k_follower.h"
#include "../m_random.h"
#include "../p_local.h"
#include "../p_setup.h"
#include "../r_draw.h" // R_GetColorByName
#include "../r_main.h" // R_PointToAngle2, R_PointToDist2
#include "../z_zone.h" // Z_StrDup/Z_Free
// The following cannot be used due to conflicts with MT_EMBLEM.
//#define audience_emblem_reserved_1(o) ((o)->reactiontime)
#define audience_mainstate(o) ((o)->cvmem)
#define audience_bobamp(o) ((o)->movefactor)
#define audience_bobspeed(o) ((o)->cusval)
#define audience_animoffset(o) ((o)->threshold)
#define audience_focusplayer(o) ((o)->lastlook)
#define audience_focusdelay(o) ((o)->movecount)
void
Obj_AudienceInit
( mobj_t * mobj,
mapthing_t *mthing,
INT32 followerpick)
{
const boolean ourchoiceofvisuals = (followerpick < 0 || followerpick > numfollowers);
INT16 *reflist = NULL;
INT16 tempreflist[MAXHEADERFOLLOWERS];
UINT8 numref = 0;
audience_mainstate(mobj) = S_NULL;
// Pick follower
if (ourchoiceofvisuals == true)
{
if (mthing != NULL && mthing->thing_stringargs[0] != NULL)
{
// From mapthing
char *stringcopy = Z_StrDup(mthing->thing_stringargs[0]);
char *tok = strtok(stringcopy, " ,");
char *c; // for erasing underscores
numref = 0;
while (tok && numref < MAXHEADERFOLLOWERS)
{
// Match follower name conversion
for (c = tok; *c; c++)
{
if (*c != '_')
continue;
*c = ' ';
}
if ((tempreflist[numref] = K_FollowerAvailable(tok)) == -1)
{
CONS_Alert(CONS_WARNING, "Mapthing %s: Follower \"%s\" is invalid!\n", sizeu1(mthing-mapthings), tok);
}
else
numref++;
tok = strtok(NULL, " ,");
}
if (!numref)
{
// This is the one thing a user should definitely be told about.
CONS_Alert(CONS_WARNING, "Mapthing %s: Follower audience has no valid followers to pick from!\n", sizeu1(mthing-mapthings));
// DO NOT RETURN HERE
}
Z_Free(stringcopy);
reflist = tempreflist;
}
else
{
// From mapheader
if (!mapheaderinfo[gamemap-1])
{
// No mapheader, no shoes, no service.
return;
}
numref = mapheaderinfo[gamemap-1]->numFollowers;
reflist = mapheaderinfo[gamemap-1]->followers;
}
if (!numref || !reflist)
{
// Clean up after ourselves.
P_RemoveMobj(mobj);
return;
}
followerpick = reflist[P_RandomKey(PR_RANDOMAUDIENCE, numref)];
if (followerpick < 0 || followerpick >= numfollowers)
{
// Is this user error or user choice..?
P_RemoveMobj(mobj);
return;
}
}
// Handle storing follower properties on the object
{
mobj->destscale = FixedMul(3*mobj->destscale, followers[followerpick].scale);
P_SetScale(mobj, mobj->destscale);
if (mobj->flags2 & MF2_BOSSNOTRAP)
{
audience_bobamp(mobj) = 0;
}
else
{
fixed_t bobscale = mapobjectscale * 2;
// The following is derived from the default bobamp
if (mobj->type != MT_EMBLEM && !(mobj->flags & MF_NOGRAVITY) && followers[followerpick].bobamp < 4*FRACUNIT)
{
audience_bobamp(mobj) = 4*bobscale;
}
else
{
audience_bobamp(mobj) = FixedMul(bobscale, followers[followerpick].bobamp);
}
}
audience_bobspeed(mobj) = followers[followerpick].bobspeed;
audience_focusplayer(mobj) = MAXPLAYERS;
audience_mainstate(mobj) =
audience_bobamp(mobj) != 0
? followers[followerpick].followstate
: followers[followerpick].idlestate;
P_SetMobjState(mobj, audience_mainstate(mobj));
if (P_MobjWasRemoved(mobj))
return;
if (P_RandomChance(PR_RANDOMAUDIENCE, FRACUNIT/2))
{
audience_animoffset(mobj) = 5;
}
}
// Handle colors
if (ourchoiceofvisuals == true)
{
UINT16 colorpick = SKINCOLOR_NONE;
if (mthing != NULL && mthing->thing_stringargs[1] != NULL)
{
if (!stricmp("Random", mthing->thing_stringargs[1]))
{
colorpick = FOLLOWERCOLOR_MATCH;
}
else
{
char *stringcopy = Z_StrDup(mthing->thing_stringargs[1]);
char *tok = strtok(stringcopy, " ");
numref = 0;
while (tok && numref < MAXHEADERFOLLOWERS)
{
if ((tempreflist[numref++] = R_GetColorByName(tok)) == SKINCOLOR_NONE)
CONS_Alert(CONS_WARNING, "Mapthing %s: Follower color \"%s\" is invalid!\n", sizeu1(mthing-mapthings), tok);
tok = strtok(NULL, " ");
}
Z_Free(stringcopy);
if (numref)
{
colorpick = tempreflist[P_RandomKey(PR_RANDOMAUDIENCE, numref)];
}
}
}
if (colorpick == SKINCOLOR_NONE
|| (colorpick >= numskincolors
&& colorpick != FOLLOWERCOLOR_MATCH
&& colorpick != FOLLOWERCOLOR_OPPOSITE))
{
colorpick = followers[followerpick].defaultcolor;
}
if (colorpick >= numskincolors)
{
colorpick = P_RandomKey(PR_RANDOMAUDIENCE, numskincolors-1)+1;
}
mobj->color = colorpick;
}
}
void
Obj_AudienceThink
( mobj_t * mobj,
boolean focusonplayer,
boolean checkdeathpit)
{
boolean landed = false;
if (mobj->fuse && mobj->fuse < (TICRATE/2))
{
mobj->renderflags ^= RF_DONTDRAW;
return; // no jumping when you hit the floor, your gravity is weird
}
if (audience_mainstate(mobj) == S_NULL)
{
// Uninitialised, don't do anything funny.
return;
}
if (focusonplayer == true)
{
if (audience_focusplayer(mobj) < MAXPLAYERS && audience_focusplayer(mobj) >= 0)
{
if (playeringame[audience_focusplayer(mobj)] == false
|| players[audience_focusplayer(mobj)].spectator == true
|| P_MobjWasRemoved(players[audience_focusplayer(mobj)].mo))
{
// Reset the timer, search for a player again
audience_focusdelay(mobj) = 0;
}
}
if (audience_focusdelay(mobj) == 0)
{
fixed_t bestdist = INT32_MAX, dist;
UINT8 i;
audience_focusplayer(mobj) = MAXPLAYERS;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] == false
|| players[i].spectator == true
|| P_MobjWasRemoved(players[i].mo))
continue;
dist = R_PointToDist2(
mobj->x,
mobj->y,
players[i].mo->x,
players[i].mo->y
);
if (dist >= bestdist)
continue;
dist = R_PointToDist2(
mobj->z,
0,
players[i].mo->z,
dist
);
if (dist >= bestdist)
continue;
bestdist = dist;
audience_focusplayer(mobj) = i;
}
// Try to add some spacing out so the object isn't constantly looking for players
audience_focusdelay(mobj) = TICRATE + min((bestdist/FRACUNIT), TICRATE) + (bestdist % TICRATE);
}
else
{
audience_focusdelay(mobj)--;
}
if (audience_focusplayer(mobj) < MAXPLAYERS && audience_focusplayer(mobj) >= 0)
{
angle_t diff = R_PointToAngle2(
mobj->x,
mobj->y,
players[audience_focusplayer(mobj)].mo->x,
players[audience_focusplayer(mobj)].mo->y
) - mobj->angle;
boolean reverse = (diff >= ANGLE_180);
if (reverse)
diff = InvAngle(diff);
if (diff > (ANG1*5))
diff /= 5;
if (reverse)
diff = InvAngle(diff);
mobj->angle += diff;
}
}
if (mobj->flags & MF_NOGRAVITY)
{
// This horrible calculation was inherited from k_follower.c
mobj->sprzoff = FixedMul(audience_bobamp(mobj),
FINESINE(((
FixedMul(4 * M_TAU_FIXED, audience_bobspeed(mobj))
* (leveltime + audience_animoffset(mobj))
) >> ANGLETOFINESHIFT) & FINEMASK));
// Offset to not go through floor...
if (mobj->type == MT_EMBLEM)
{
; // ...unless it's important to keep a centered hitbox
}
else if (mobj->flags2 & MF2_OBJECTFLIP)
{
mobj->sprzoff -= audience_bobamp(mobj);
}
else
{
mobj->sprzoff += audience_bobamp(mobj);
}
}
else if (audience_animoffset(mobj) > 0)
{
// Skipped frames at spawn for offset in jumping
audience_animoffset(mobj)--;
}
else if (audience_bobamp(mobj) == 0)
{
// Just sit there
;
}
else if (mobj->flags2 & MF2_OBJECTFLIP)
{
landed = (mobj->z + mobj->height >= mobj->ceilingz);
}
else
{
landed = (mobj->z <= mobj->floorz);
}
if (landed == true)
{
if (checkdeathpit && P_CheckDeathPitCollide(mobj))
{
P_RemoveMobj(mobj);
return;
}
mobj->momx = mobj->momy = 0;
mobj->momz = P_MobjFlip(mobj)*audience_bobamp(mobj);
P_SetMobjState(mobj, audience_mainstate(mobj));
}
}