RingRacers/src/objects/sealed-star.c

667 lines
17 KiB
C

// DR. ROBOTNIK'S RING RACERS
//-------------------------------
// 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.
//-----------------------------------------------------------------------------
/// \brief Sealed Star objects
#include <string.h>
#include "../info.h"
#include "../doomdef.h"
#include "../g_game.h"
#include "../p_local.h"
#include "../m_fixed.h"
#include "../m_random.h"
#include "../k_kart.h"
#include "../k_objects.h"
#include "../r_main.h"
#include "../s_sound.h"
static const INT32 kMinTranslucency = 0;
static const INT32 kMaxTranslucency = 10;
void Obj_SSCandleMobjThink(mobj_t* mo)
{
mo->extravalue1 = (mo->extravalue1 + 3) % 360;
mo->z += FSIN(mo->extravalue1 * ANG1) / 4;
if (mo->tracer)
{
mo->tracer->z = mo->z + (256*FRACUNIT);
}
}
void Obj_SSHologramRotatorMapThingSpawn(mobj_t* mo, mapthing_t* mt)
{
INT32 numStates = 0;
statenum_t mystates[32] = {0};
char stringarg[256];
char *token;
int i;
INT32 speed = 8;
INT32 radius = 256;
INT32 amplitude = mt->thing_args[2];
INT32 frequency = mt->thing_args[3];
angle_t angDiff;
mobj_t* part;
fixed_t circumference;
// This is probably dangerously unsafe but what code written in C isn't
// Christ, parsing strings in C is heinous
// strtok is a nightmare so let's try to avoid that
if (mt->thing_stringargs[0] == NULL)
{
CONS_Alert(CONS_WARNING, "MT_HOLOGRAM_ROTATOR %d: stringarg 0 is NULL\n", mt->tid);
return;
}
memset(stringarg, 0, sizeof(stringarg));
strlcpy(stringarg, mt->thing_stringargs[0], sizeof(stringarg));
for (i = 0; i < (int)(sizeof(stringarg)); i++)
{
if (stringarg[i] == ' ' || stringarg[i] == ',')
{
stringarg[i] = 0;
}
}
stringarg[sizeof(stringarg) - 1] = 0;
for (token = stringarg; token < stringarg + sizeof(stringarg) && numStates < 32; )
{
char *next = token + strlen(token) + 1;
if (stricmp(token, "bird") == 0)
{
mystates[numStates] = S_HOLOGRAM_BIRD;
numStates += 1;
}
else if (stricmp(token, "fish") == 0)
{
mystates[numStates] = S_HOLOGRAM_FISH;
numStates += 1;
}
else if (stricmp(token, "crab") == 0)
{
mystates[numStates] = S_HOLOGRAM_CRAB;
numStates += 1;
}
else if (stricmp(token, "squid") == 0)
{
mystates[numStates] = S_HOLOGRAM_SQUID;
numStates += 1;
}
token = next;
}
if (numStates == 0)
{
CONS_Alert(CONS_WARNING, "MT_HOLOGRAM_ROTATOR %d: stringarg 0 consists exclusively of unrecognised creatures\n", mt->tid);
return;
}
if (mt->thing_args[0])
{
speed = mt->thing_args[0];
}
if (mt->thing_args[1])
{
radius = mt->thing_args[1];
}
// adjust for quarter-scale default
radius *= 4;
amplitude *= 2;
angDiff = FixedAngle(360 * FRACUNIT/numStates);
part = mo;
circumference = 2 * M_PI_FIXED * radius;
mo->cusval = abs(speed * mo->scale);
mo->movedir = FixedAngle(FixedDiv(360 * speed * FRACUNIT, circumference));
mo->movefactor = radius * mo->scale;
if (amplitude > 0 && frequency > 0)
{
mo->extravalue1 = amplitude * mo->scale;
mo->extravalue2 = frequency * ANG1 / 2;
}
else
{
mo->extravalue1 = 0;
mo->extravalue2 = 0;
}
for (i = 0; i < numStates; i++)
{
statenum_t thisstate = mystates[i];
P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_SS_HOLOGRAM));
P_SetTarget(&part->hnext->hprev, part);
part = part->hnext;
P_SetTarget(&part->target, mo);
P_SetMobjState(part, thisstate);
part->angle = angDiff * i;
if (speed < 0)
{
part->renderflags ^= RF_HORIZONTALFLIP;
}
}
}
void Obj_SSHologramRotatorMobjThink(mobj_t* mo)
{
mobj_t* part = mo;
while (part->hnext)
{
fixed_t oldZ, z;
part = part->hnext;
oldZ = part->z;
if (mo->extravalue1)
{
z = mo->z + mo->extravalue1 + FixedMul(mo->extravalue1, FSIN(mo->extravalue2 * (part->extravalue2 + leveltime)));
}
else
{
z = oldZ;
}
part->angle += mo->movedir;
P_MoveOrigin(
part,
mo->x + FixedMul(mo->movefactor, FCOS(part->angle - ANGLE_90)),
mo->y + FixedMul(mo->movefactor, FSIN(part->angle - ANGLE_90)),
z
);
part->rollangle = R_PointToAngle2(0, 0, mo->cusval, z - oldZ);
}
}
void Obj_SSHologramMobjSpawn(mobj_t* mo)
{
mo->fuse = mo->reactiontime;
mo->threshold = -1 + P_RandomKey(PR_UNDEFINED, 2) * 2;
mo->extravalue2 = P_RandomFixed(PR_UNDEFINED);
}
void Obj_SSHologramMapThingSpawn(mobj_t* mo, mapthing_t* mt)
{
char* stringarg0 = mt->thing_stringargs[0];
if (stringarg0 == NULL)
{
P_SetMobjState(mo, S_HOLOGRAM_CRAB);
}
else if (stricmp(stringarg0, "bird"))
{
P_SetMobjState(mo, S_HOLOGRAM_BIRD);
}
else if (stricmp(stringarg0, "crab") == 0)
{
P_SetMobjState(mo, S_HOLOGRAM_CRAB);
}
else if (stricmp(stringarg0, "fish") == 0)
{
P_SetMobjState(mo, S_HOLOGRAM_FISH);
}
else if (stricmp(stringarg0, "squid") == 0)
{
P_SetMobjState(mo, S_HOLOGRAM_SQUID);
}
if (mt->thing_args[0])
{
mo->renderflags ^= RF_HORIZONTALFLIP;
}
}
void Obj_SSHologramMobjFuse(mobj_t* mo)
{
INT32 trans = (mo->frame & FF_TRANSMASK) >> FF_TRANSSHIFT;
if (trans >= kMaxTranslucency)
{
mo->threshold = -1;
}
else if (trans <= kMinTranslucency)
{
mo->threshold = 1;
}
trans += mo->threshold;
mo->frame = (mo->frame & ~FF_TRANSMASK) | (trans << FF_TRANSSHIFT);
if (trans >= kMaxTranslucency)
{
mo->fuse = 24;
}
else
{
mo->fuse = mo->reactiontime;
}
}
static struct {
INT32 t;
INT32 x;
INT32 y;
INT32 z;
INT32 w;
INT32 xx;
INT32 yy;
INT32 zz;
INT32 ww;
} srng_state = {0, 5197528, 3154710, 9406548, 1028369, 0, 0, 0, 0};
static void set_srng_seed(INT32 seed)
{
srng_state.t = seed;
srng_state.xx = srng_state.x;
srng_state.yy = srng_state.y;
srng_state.zz = srng_state.z;
srng_state.ww = srng_state.w;
}
static INT32 srng_random(void)
{
srng_state.xx = srng_state.yy;
srng_state.yy = srng_state.zz;
srng_state.zz = srng_state.ww;
srng_state.ww = srng_state.ww ^ (srng_state.ww >> 19) ^ srng_state.t ^ (srng_state.t >> 8);
srng_state.t = srng_state.xx ^ (srng_state.xx << 11);
return srng_state.ww;
}
static INT32 srng_RandomRange(INT32 a, INT32 b)
{
return a + abs(srng_random() % (b - a + 1));
}
static fixed_t srng_RandomFixed(void)
{
return (fixed_t)(srng_RandomRange(0, FRACUNIT));
}
static boolean srng_RandomChance(INT32 chance)
{
return srng_RandomFixed() < chance;
}
// spinning-related values
#define COIN_FRAMES (4) // number of frames the coin has
#define MIN_SPIN_TICS (3) // fastest speed with which a coin can animate
#define MAX_SPIN_TICS (10) // slowest speed with which a coin can animate
// spawning-related values
#define SPAWNS_PER_MAPTHINGS (10) // number of coins to spawn per spawner
#define COIN_SCALE (2*FRACUNIT) // upscales coins by this factor
#define SPAWN_RANGE (256*FRACUNIT*4) // cubed range over which coins should spawn
#define MIN_SPACING (64*FRACUNIT*4) // minimum spherical space between each coin
// IMPORTANT not to make the spacing too large relative to the spawn range, or spawn attempts could potentially escalate and impact load times or even cause a softlock
// eidolon hardcoder note: this should probably have been implemented
// with a statistical distribution instead of a linear search,
// but this was hardcoded to copy the exact behavior of the Lua
static boolean cloud_CheckDistribution(mobj_t **coins, INT32 coin_count, fixed_t x, fixed_t y, fixed_t z, fixed_t scale)
{
fixed_t spacing = FixedMul(MIN_SPACING, scale);
int i;
x = FixedMul(x, scale);
y = FixedMul(y, scale);
z = FixedMul(z, scale);
for (i = 0; i < coin_count; i++)
{
mobj_t *coin = coins[i];
if (FixedHypot(FixedHypot(coin->x - x, coin->y - y), coin->z - z) < spacing)
{
return false;
}
}
return true;
}
static void CoinCloudSpawn(mobj_t *mo, INT32 seed, mobjtype_t ty)
{
fixed_t x, y, z;
mobj_t *coins[SPAWNS_PER_MAPTHINGS];
mobj_t *coin;
int i;
set_srng_seed(seed);
for (i = 0; i < SPAWNS_PER_MAPTHINGS; i++)
{
// as mentioned... this is a really goofy way to implement a statistical distribution
do {
x = FixedMul(SPAWN_RANGE, srng_RandomFixed()) - (SPAWN_RANGE >> 1);
y = FixedMul(SPAWN_RANGE, srng_RandomFixed()) - (SPAWN_RANGE >> 1);
z = FixedMul(SPAWN_RANGE, srng_RandomFixed());
} while (!cloud_CheckDistribution(coins, i, x, y, z, mo->scale));
coin = P_SpawnMobjFromMobj(mo, x, y, z, ty);
coin->scale = coin->destscale = FixedMul(coin->scale, COIN_SCALE);
// initial angle & frame
coin->angle = FixedAngle(360 * srng_RandomFixed());
coin->frame = (coin->frame & ~FF_FRAMEMASK) | srng_RandomRange(0, COIN_FRAMES - 1);
// random animation speed
coin->extravalue1 = srng_RandomRange(MIN_SPIN_TICS, MAX_SPIN_TICS);
coin->extravalue2 = coin->extravalue1;
coins[i] = coin;
}
}
void Obj_SSCoinCloudMapThingSpawn(mobj_t* mo, mapthing_t* mt)
{
CoinCloudSpawn(mo, mt->thing_args[0], MT_SS_COIN);
}
void Obj_SSCoinMobjThink(mobj_t* mo)
{
mo->extravalue2 -= 1;
if (mo->extravalue2 <= 0)
{
mo->extravalue2 = mo->extravalue1;
mo->frame = (mo->frame & ~FF_FRAMEMASK) | ((mo->frame + 1) % COIN_FRAMES);
}
}
// spinning-related values
#define GOBLET_FRAMES (8) // number of frames the goblet has
#define MIN_VERTICAL_SPIN_TICS (3) // fastest speed with which a goblet can animate (frame)
#define MAX_VERTICAL_SPIN_TICS (10) // slowest speed with which a goblet can animate (frame)
#define MIN_HORIZONTAL_SPIN_SPEED (ANG1 * 1) // slowest speed with which a goblet can rotate (angle)
#define MAX_HORIZONTAL_SPIN_SPEED (ANG1 * 3) // fastest speed with which a goblet can rotate (angle)
// spawning-related values
#define GOBLET_SPAWNS_PER_MAPTHING (5) // number of goblets to spawn per spawner
#define GOBLET_SCALE (2*FRACUNIT) // upscales goblets by this factor
#define GOBLET_SPAWN_RANGE (256*FRACUNIT*4) // cubed range over which goblets should spawn
#define GOBLET_MIN_SPACING (64*FRACUNIT*4) // minimum spherical space between each goblet
// IMPORTANT not to make the spacing too large relative to the spawn range, or spawn attempts could potentially escalate and impact load times or even cause a softlock
#define GOBLET_SEED_OFFSET (1267491679) // I'm setting a garbage seed offset here so that seed 0 clouds are markedly different to their coin counterparts
static void GobletCloudSpawn(mobj_t *mo, INT32 seed, mobjtype_t ty)
{
fixed_t x, y, z;
mobj_t *coins[GOBLET_SPAWNS_PER_MAPTHING];
mobj_t *coin;
int i;
set_srng_seed(seed + GOBLET_SEED_OFFSET);
for (i = 0; i < GOBLET_SPAWNS_PER_MAPTHING; i++)
{
// as mentioned... this is a really goofy way to implement a statistical distribution
do {
x = FixedMul(GOBLET_SPAWN_RANGE, srng_RandomFixed()) - (GOBLET_SPAWN_RANGE >> 1);
y = FixedMul(GOBLET_SPAWN_RANGE, srng_RandomFixed()) - (GOBLET_SPAWN_RANGE >> 1);
z = FixedMul(GOBLET_SPAWN_RANGE, srng_RandomFixed());
} while (!cloud_CheckDistribution(coins, i, x, y, z, mo->scale));
coin = P_SpawnMobjFromMobj(mo, x, y, z, ty);
coin->scale = coin->destscale = FixedMul(coin->scale, GOBLET_SCALE);
// initial angle & frame
coin->angle = FixedAngle(360 * srng_RandomFixed());
coin->frame = (coin->frame & ~FF_FRAMEMASK) | srng_RandomRange(0, GOBLET_FRAMES - 1);
// random animation speed
coin->extravalue1 = srng_RandomRange(MIN_VERTICAL_SPIN_TICS, MAX_VERTICAL_SPIN_TICS);
coin->extravalue2 = coin->extravalue1;
// random angle speed
coin->movedir = MIN_HORIZONTAL_SPIN_SPEED + FixedMul(srng_RandomFixed(), MAX_HORIZONTAL_SPIN_SPEED - MIN_HORIZONTAL_SPIN_SPEED);
if (srng_RandomChance(FRACUNIT/2))
{
coin->movedir = -coin->movedir;
}
coins[i] = coin;
}
}
void Obj_SSGobletCloudMapThingSpawn(mobj_t* mo, mapthing_t* mt)
{
GobletCloudSpawn(mo, mt->thing_args[0], MT_SS_GOBLET);
}
void Obj_SSGobletMobjThink(mobj_t* mo)
{
mo->angle += mo->movedir;
mo->extravalue2 -= 1;
if (mo->extravalue2 <= 0)
{
mo->extravalue2 = mo->extravalue1;
mo->frame = (mo->frame & ~FF_FRAMEMASK) | ((mo->frame + 1) % GOBLET_FRAMES);
}
}
#define LAMP_SCALE (4 * FRACUNIT)
#define BULB_HORIZONTAL_OFFSET (124 * FRACUNIT)
#define BULB_VERTICAL_OFFSET (243 * FRACUNIT)
void Obj_SSLampMapThingSpawn(mobj_t* mo, mapthing_t* mt)
{
int i;
angle_t angle = FixedAngle(mt->angle * FRACUNIT);
fixed_t bulbX = FixedMul(BULB_HORIZONTAL_OFFSET, FCOS(angle));
fixed_t bulbY = FixedMul(BULB_HORIZONTAL_OFFSET, FSIN(angle));
mobj_t *core, *part;
mo->scale = mo->destscale = FixedMul(mo->scale, LAMP_SCALE);
// INVISIBLE (BUT RENDERERED) CORE
// we need this because linkdrawing to a papersprite results in all linked mobjs becoming invisible when the papersprite is viewed parallel to its angle
core = P_SpawnMobjFromMobj(mo, bulbX, bulbY, BULB_VERTICAL_OFFSET, MT_SS_LAMP_BULB);
P_SetTarget(&core->tracer, mo);
P_SetMobjState(core, S_SHADOW);
// parallel bulb
part = P_SpawnMobjFromMobj(core, 0, 0, 0, MT_SS_LAMP_BULB);
part->angle = angle;
part->flags2 |= MF2_LINKDRAW;
P_SetTarget(&part->tracer, core);
// perpendicular bulb (as 2 minecraft cross parts)
angle += ANGLE_90;
bulbX = FixedMul(core->info->radius, FCOS(angle));
bulbY = FixedMul(core->info->radius, FSIN(angle));
part = core;
for (i = 0; i < 1; i++)
{
P_SetTarget(&part->hnext, P_SpawnMobjFromMobj(core, bulbX, bulbY, 0, MT_SS_LAMP_BULB));
P_SetTarget(&part->hnext->hprev, part);
part = part->hnext;
part->angle = angle;
part->frame = (part->frame & ~FF_FRAMEMASK) | 2;
part->flags2 |= MF2_LINKDRAW;
P_SetTarget(&part->tracer, core);
part->dispoffset = 1;
angle += ANGLE_180;
bulbX = -bulbX;
bulbY = -bulbY;
}
// aura
part = P_SpawnMobjFromMobj(core, 0, 0, 0, MT_SS_LAMP_BULB);
P_SetMobjState(part, S_SS_LAMP_AURA);
part->dispoffset -= 1;
}
void Obj_SSWindowMapThingSpawn(mobj_t* mo, mapthing_t* mt)
{
angle_t angle;
fixed_t co, si, x, y, xofs, yofs;
mobj_t *shinel, *shinem, *shiner;
mo->scale = mo->destscale = FRACUNIT;
angle = FixedAngle(mt->angle * FRACUNIT);
co = FCOS(angle);
si = FSIN(angle);
x = 192 * co;
y = 192 * si;
xofs = 128 * si;
yofs = -128 * co;
shinel = P_SpawnMobjFromMobj(mo, x + xofs, y + yofs, 0, MT_SSWINDOW_SHINE);
shinem = P_SpawnMobjFromMobj(mo, x, y, 64*FRACUNIT, MT_SSWINDOW_SHINE);
shiner = P_SpawnMobjFromMobj(mo, x - xofs, y - yofs, 0, MT_SSWINDOW_SHINE);
shinel->angle = angle;
shinem->angle = angle;
shiner->angle = angle;
shinel->scale = mo->destscale = FRACUNIT;
shinem->scale = mo->destscale = FRACUNIT;
shiner->scale = mo->destscale = FRACUNIT;
}
void Obj_SLSTMaceMobjThink(mobj_t* mo)
{
if (leveltime % 2 == 0)
{
var1 = 9;
var2 = 0;
A_GhostMe(mo);
}
}
#define BUMPER_STRENGTH (56)
void Obj_SSBumperTouchSpecial(mobj_t* special, mobj_t* toucher)
{
angle_t hang;
angle_t vang;
fixed_t str;
int i;
hang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y);
vang = 0;
if (P_IsObjectOnGround(toucher) == false)
{
vang = R_PointToAngle2(
FixedHypot(special->x, special->y), special->z + (special->height >> 1),
FixedHypot(toucher->x, toucher->y), toucher->z + (toucher->height >> 1)
);
}
str = (BUMPER_STRENGTH * special->scale) >> 1;
toucher->momx = FixedMul(FixedMul(str, FCOS(hang)), abs(FCOS(vang)));
toucher->momy = FixedMul(FixedMul(str, FSIN(hang)), abs(FCOS(vang)));
toucher->momz = FixedMul(str, FSIN(vang));
if (toucher->player)
{
if (toucher->player->tiregrease == 0)
{
for (i = 0; i < 2; i++)
{
mobj_t *grease = P_SpawnMobjFromMobj(toucher, 0, 0, 0, MT_TIREGREASE);
P_SetTarget(&grease->target, toucher);
grease->angle = toucher->angle;
grease->extravalue1 = i;
}
}
if (toucher->player->tiregrease < 2*TICRATE) // greasetics
{
toucher->player->tiregrease = 2*TICRATE;
}
}
if (special->state != &states[special->info->seestate])
{
S_StartSound(special, special->info->deathsound);
P_SetMobjState(special, special->info->seestate);
}
}
void Obj_SSBumperMobjSpawn(mobj_t* mo)
{
mo->shadowscale = FRACUNIT;
mo->destscale <<= 1;
P_SetScale(mo, mo->destscale);
}
void Obj_SSChainMobjThink(mobj_t* mo)
{
mo->angle += ANGLE_22h;
if (mo->momx == 0 && P_IsObjectOnGround(mo))
{
P_RemoveMobj(mo);
}
}
void Obj_SSGachaTargetMobjSpawn(mobj_t* mo)
{
mo->scale *= 4;
mo->destscale = mo->scale;
}
void Obj_SSCabotronMobjSpawn(mobj_t* mo)
{
int i;
mobj_t *ball;
for (i = 0; i < 4; i++)
{
ball = P_SpawnMobj(mo->x, mo->y, mo->z, MT_CABOTRONSTAR);
ball->destscale = mo->scale;
P_SetScale(ball, mo->scale);
P_SetTarget(&ball->target, mo);
ball->movedir = FixedAngle(FixedMul(FixedDiv(i<<FRACBITS, 5<<FRACBITS), 360<<FRACBITS));
ball->threshold = ball->radius + mo->radius + FixedMul(11*FRACUNIT, ball->scale);
}
mo->scale *= 2;
mo->destscale = mo->scale;
}
void Obj_SSCabotronMobjThink(mobj_t* mo)
{
angle_t xdir = FSIN(mo->angle - ANGLE_90);
angle_t ydir = FCOS(mo->angle + ANGLE_90);
boolean didMove;
didMove = P_TryMove(mo, mo->x - (xdir * mo->info->speed), mo->y - (ydir * mo->info->speed), false, NULL);
if (!didMove)
{
// --print("Flipping sawblade")
mo->angle += ANGLE_180;
}
}
void Obj_SSCabotronStarMobjThink(mobj_t* mo)
{
if (mo->target)
{
// "but ang just call the action in the soc :))))"
var1 = 0;
var2 = 0;
A_UnidusBall(mo);
// how about no because for some reason it will call the func ONCE and never again in its pitiful aimless life
// and let's just hack FF_ANIMATE too shall we; since var1 is being used to make sure the fucking balls don't fly off the handle which means frame anims can't be handled by the soc
// this game is popsicle sticks and i am out of glue.
}
}