// 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 #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<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. } }