From 9ff174dd0604b29c9139bc075dda75359a03d3c3 Mon Sep 17 00:00:00 2001 From: James R Date: Wed, 22 Nov 2023 18:57:18 -0800 Subject: [PATCH] Hardcode Frozen Production Frost Throwers (ceiling and horizontal versions) --- src/k_kart.c | 5 + src/k_objects.h | 10 + src/m_random.h | 2 + src/objects/CMakeLists.txt | 1 + src/objects/frost-thrower.cpp | 443 ++++++++++++++++++++++++++++++++++ src/p_inter.c | 6 + src/p_mobj.c | 28 +++ 7 files changed, 495 insertions(+) create mode 100644 src/objects/frost-thrower.cpp diff --git a/src/k_kart.c b/src/k_kart.c index 61701a918..a3e8fc452 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12664,6 +12664,11 @@ void K_MoveKartPlayer(player_t *player, boolean onground) else player->turbine--; // acts as a cooldown } + + if (player->icecube.frozen) + { + Obj_IceCubeInput(player); + } } void K_CheckSpectateStatus(boolean considermapreset) diff --git a/src/k_objects.h b/src/k_objects.h index a116b96ea..eb5bd2b79 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -303,6 +303,16 @@ void Obj_GPZSeasawSpawn(mobj_t *mo); void Obj_GPZSeasawThink(mobj_t *mo); void Obj_GPZSeasawCollide(mobj_t *mo, mobj_t *mo2); +/* Frozen Production Frost Thrower */ +void Obj_FreezeThrusterInit(mobj_t *mobj); +void Obj_FreezeThrusterThink(mobj_t *mobj); +void Obj_IceDustCollide(mobj_t *t1, mobj_t *t2); +boolean Obj_IceCubeThink(mobj_t *mobj); +void Obj_IceCubeInput(player_t *player); +void Obj_IceCubeBurst(player_t *player); +void Obj_SidewaysFreezeThrusterInit(mobj_t *mobj); +void Obj_SidewaysFreezeThrusterThink(mobj_t *mobj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/m_random.h b/src/m_random.h index 77a3f33ce..b2e68d798 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -83,6 +83,8 @@ typedef enum PR_FUZZ, // Stability testing + PR_FROSTTHROWERS, + PRNUMSYNCED, PR_INTERPHUDRANDOM = PRNUMSYNCED, // Interpolation-accomodating HUD randomisation diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 96182b5ea..0dae6ae06 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -42,6 +42,7 @@ target_sources(SRB2SDL2 PRIVATE ball-switch.cpp charge.c mega-barrier.cpp + frost-thrower.cpp ) add_subdirectory(versus) diff --git a/src/objects/frost-thrower.cpp b/src/objects/frost-thrower.cpp new file mode 100644 index 000000000..b5ef3e290 --- /dev/null +++ b/src/objects/frost-thrower.cpp @@ -0,0 +1,443 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 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. +//----------------------------------------------------------------------------- + +// Original Lua script by Lat +// Hardcoded by jartha + +#include +#include + +#include "../math/fixed.hpp" +#include "../math/vec.hpp" +#include "../mobj.hpp" + +#include "../d_player.h" +#include "../doomdef.h" +#include "../doomstat.h" +#include "../k_bot.h" // FIXME +#include "../k_kart.h" +#include "../k_objects.h" +#include "../m_random.h" +#include "../sounds.h" +#include "../tables.h" + +using srb2::Mobj; +using srb2::math::Fixed; +using srb2::math::Vec2; + +namespace +{ + +Vec2 angle_vector(angle_t x) +{ + return Vec2 {FCOS(x), FSIN(x)}; +} + +struct IceCube : Mobj +{ + static constexpr int kRadius = 33; + + void extravalue1() = delete; + angle_t angle_offset() const { return mobj_t::extravalue1; } + void angle_offset(angle_t n) { mobj_t::extravalue1 = n; } + + static void spawn_cube(Mobj* source) + { + angle_t offset = 0; + for (int i = 0; i < 4; ++i) + { + spawn_side(source, offset); + offset += ANGLE_90; + } + } + + bool valid() const { return Mobj::valid() && Mobj::valid(owner()) && player() && player()->icecube.frozen; } + player_t* player() const { return owner()->player; } + + bool think() + { + if (!valid()) + { + remove(); + return false; + } + + angle_t facing = player()->drawangle + angle_offset(); + + scale(owner()->scale()); + move_origin({owner()->pos2d() + (vector(facing) * scale()), owner()->z}); + frame = state()->frame + player()->icecube.wiggle; + angle = facing + ANGLE_90; + + return true; + } + +private: + static Vec2 vector(angle_t x) { return angle_vector(x) * kRadius; } + + static IceCube* spawn_side(Mobj* source, angle_t offset) + { + angle_t facing = source->angle + offset; + IceCube* cube = source->spawn_from({vector(facing) * source->scale(), 0}, MT_GGZICECUBE); + cube->angle_offset(offset); + cube->angle = facing + ANGLE_90; + cube->owner(source); + return cube; + } +}; + +struct Frost : Mobj +{ + void extravalue1() = delete; + bool skip_invincible_checks() const { return mobj_t::extravalue1; } + void skip_invincible_checks(bool n) { mobj_t::extravalue1 = n; } + + static Frost* spawn(Mobj* source, const Mobj::PosArg& p) + { + Frost* frost = source->spawn_from(p, MT_GGZICEDUST); + + auto rng = [] { return P_RandomRange(PR_FROSTTHROWERS, -2, 2) * (4 * mapobjectscale); }; + + frost->momx = rng(); + frost->momy = rng(); + frost->scale_between(mapobjectscale / 2, mapobjectscale * 2, mapobjectscale / 6); + + return frost; + } + + void collide(Mobj* mo) const + { + player_t* p = mo->player; + icecubevars_t& vars = p->icecube; + + if (vars.hitat < leveltime && leveltime - vars.hitat < TICRATE) + { + // avoid spamming instashield + return; + } + + vars.hitat = leveltime; + + if (!skip_invincible_checks()) + { + if (p->flashing || p->growshrinktimer > 0 || p->invincibilitytimer || p->hyudorotimer) + { + K_DoInstashield(p); + return; + } + } + + if (vars.frozen) + { + return; + } + + vars.frozen = true; + vars.wiggle = 0; + vars.frozenat = leveltime; + vars.shaketimer = 0; + + IceCube::spawn_cube(mo); + } +}; + +struct FreezeThruster : Mobj +{ + static constexpr tic_t kFlashRate = TICRATE*3/2; + + void extravalue1() = delete; + bool frosting() const { return mobj_t::extravalue1; } + void frosting(bool n) { mobj_t::extravalue1 = n; } + + void extravalue2() = delete; + tic_t default_timer() const { return mobj_t::extravalue2; } + void default_timer(tic_t n) { mobj_t::extravalue2 = n; } + + void threshold() = delete; + tic_t timer() const { return mobj_t::threshold; } + void timer(tic_t n) { mobj_t::threshold = n; } + + void thing_args() = delete; + bool double_speed() const { return mobj_t::thing_args[0]; } + bool skip_invincible_checks() const { return mobj_t::thing_args[1]; } + bool default_frosting() const { return !mobj_t::thing_args[2]; } + + void init() + { + // FIXME: this should use a thing arg, not angle! + if (spawnpoint) + { + // Object Angle = Tics Between Activations + default_timer(spawnpoint->angle); + } + + frosting(default_timer() ? default_frosting() : true); + timer(default_timer()); + } + + void think() + { + if (timer()) + { + timer(timer() - 1); + + if (timer() < 1) + { + frosting(!frosting()); + timer(default_timer()); + } + } + + // flash before turning on + if (!frosting() && timer() < kFlashRate && (leveltime % 4) < 2) + { + colorized = true; + color = SKINCOLOR_RED; + } + else + { + colorized = false; + } + + spew(); + } + +private: + void spew() + { + if (!frosting()) + { + return; + } + + // Object Height = Frostthrower Speed + fixed_t mz = is_flipped() ? floorz + 1 : ceilingz; + fixed_t frostspeed = std::abs(z - mz) / 10; + + if (double_speed()) + { + frostspeed *= 2; + } + + if (leveltime % 3 == 0) + { + Frost* frost = Frost::spawn(this, {}); + + if (is_flipped()) + { + frost->momz = -frostspeed; + } + else + { + frost->momz = frostspeed; + frost->flags2 |= MF2_OBJECTFLIP; + } + + frost->skip_invincible_checks(skip_invincible_checks()); + } + + voice_loop(sfx_s3k7f); + } +}; + +struct SidewaysFreezeThruster : Mobj +{ + static constexpr int kBaseRadius = 12; + + bool skip_invincible_checks() const { return mobj_t::thing_args[0]; } + + void init() + { + scale(scale()*3/2); + + const Vec2 base_vector = vector(angle) * kBaseRadius; + + angle_t an = angle + ANGLE_90; + + z += flip(24 * mapobjectscale); + + auto spawn_piece = [&](const Mobj::PosArg& p, angle_t angle, int frame) + { + Mobj* part = spawn_from(p, MT_THRUSTERPART); + part->angle = angle; + part->frame += frame; + return part; + }; + + spawn_piece({}, an, 0); // base + spawn_piece({vector(angle) * 24, 0}, an, 1); // cannon + + Vec2 h_vector = angle_vector(an); + + // spawn the pipes: + for (int i = 0; i < 4; ++i) + { + angle_t v_an = ANGLE_45 + (ANGLE_90 * i); + Vec2 v_vector = vector(v_an) * 32; + spawn_piece({h_vector * v_vector.y, v_vector.x}, angle, 2); + } + + // spawn the icons: + Vec2 v = vector(an) * 32; + spawn_piece({base_vector + v, 0}, angle, 3); + spawn_piece({base_vector - v, 0}, angle, 3); + } + + void think() + { + if (leveltime % 2 == 0) + { + spew(); + spew(); + } + } + +private: + Vec2 vector(angle_t angle) const { return angle_vector(angle) * scale(); } + + void spew() + { + Frost* frost = Frost::spawn(this, {vector(angle) * kBaseRadius, 0}); + auto rng = [](int x, int y) { return P_RandomRange(PR_FROSTTHROWERS, x, y); }; + frost->angle = angle + (rng(-8, 8) * ANG1); + frost->instathrust(frost->angle, mapobjectscale * 64); + frost->momz = rng(-5, 5) * mapobjectscale; + frost->scale_between(mapobjectscale * 3 / 4, mapobjectscale * 2, mapobjectscale / 6); + frost->skip_invincible_checks(skip_invincible_checks()); + } +}; + +struct Shatter : Mobj +{ + static Shatter* spawn(Mobj* source) + { + auto rng = [source](int x, int y) { return P_RandomRange(PR_DECORATION, x, y) * source->scale(); }; + Shatter* part = source->spawn_from({rng(-64, 64), rng(-64, 64), rng(0, 64)}, MT_GGZICESHATTER); + part->state(P_RandomRange(PR_DECORATION, 0, 1) ? S_GGZPARTICLE11 : S_GGZPARTICLE21); + part->fuse = TICRATE * 4; + return part; + } +}; + +}; // namespace + +void Obj_FreezeThrusterInit(mobj_t* mo) +{ + static_cast(mo)->init(); +} + +void Obj_FreezeThrusterThink(mobj_t* mo) +{ + static_cast(mo)->think(); +} + +void Obj_IceDustCollide(mobj_t* t1, mobj_t* t2) +{ + static_cast(t1)->collide(static_cast(t2)); +} + +boolean Obj_IceCubeThink(mobj_t* mo) +{ + return static_cast(mo)->think(); +} + +void Obj_IceCubeInput(player_t* player) +{ + // FIXME: this should be in the bot ticcmd code + auto bot_hack = [player] + { + if (!K_PlayerUsesBotMovement(player)) + { + return false; + } + + if (leveltime % 7) + { + return false; + } + + if (player->sneakertimer) + { + return false; + } + + if (!P_IsObjectOnGround(player->mo)) + { + return false; + } + + return true; + }; + + // Must be mashing some buttons + auto press = [player](buttoncode_t bt) { return (player->cmd.buttons & ~player->oldcmd.buttons) & bt; }; + if (!(press(BT_ACCELERATE) || press(BT_ATTACK) || press(BT_DRIFT) || bot_hack())) + { + return; + } + + if (leveltime - std::min(player->icecube.frozenat, leveltime) < TICRATE*2/3) + { + return; + } + + Mobj* mo = static_cast(player->mo); + + player->icecube.wiggle++; + player->icecube.shaketimer = 10; + + mo->voice(sfx_s3k94); + + auto rng = [mo](int x, int y) { return P_RandomRange(PR_DECORATION, x, y) * mo->scale(); }; + + for (int i = 0; i < 6; ++i) + { + Shatter* part = Shatter::spawn(mo); + part->scale_between(mo->scale() * 3, 1); + part->momx = mo->momx + rng(-14, 14); + part->momy = mo->momy + rng(-14, 14); + part->momz = mo->momz + rng(0, 14); + } + + if (player->icecube.wiggle > 4) + { + Obj_IceCubeBurst(player); + player->flashing = (TICRATE * 3) - 1; + } +} + +void Obj_IceCubeBurst(player_t* player) +{ + Mobj* mo = static_cast(player->mo); + + auto rng = [mo](int x, int y) { return P_RandomRange(PR_DECORATION, x, y) * mo->scale(); }; + + for (int i = 0; i < 20; ++i) + { + Shatter* part = Shatter::spawn(mo); + part->scale_between(mo->scale() * 5, 1); + part->momx = (mo->momx * 2) + rng(-64, 64); + part->momy = (mo->momy * 2) + rng(-64, 64); + part->momz = (mo->momz * 2) + rng(0, 20); + } + + player->icecube.frozen = false; + + mo->voice(sfx_glgz1); +} + +void Obj_SidewaysFreezeThrusterInit(mobj_t* mo) +{ + static_cast(mo)->init(); +} + +void Obj_SidewaysFreezeThrusterThink(mobj_t* mo) +{ + static_cast(mo)->think(); +} diff --git a/src/p_inter.c b/src/p_inter.c index eb7faa554..0a44e56bd 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -968,6 +968,12 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) break; } + case MT_GGZICEDUST: + { + Obj_IceDustCollide(special, toucher); + return; + } + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; diff --git a/src/p_mobj.c b/src/p_mobj.c index 4b156a7c6..f82c02c3e 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -6817,6 +6817,14 @@ static void P_MobjSceneryThink(mobj_t *mobj) } break; } + case MT_GGZICECUBE: + { + if (!Obj_IceCubeThink(mobj)) + { + return; + } + break; + } case MT_VWREF: case MT_VWREB: { @@ -10204,6 +10212,16 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; } + case MT_GGZFREEZETHRUSTER: + { + Obj_FreezeThrusterThink(mobj); + break; + } + case MT_SIDEWAYSFREEZETHRUSTER: + { + Obj_SidewaysFreezeThrusterThink(mobj); + break; + } default: // check mobj against possible water content, before movement code @@ -14421,6 +14439,16 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) Obj_LinkCheckpoint(mobj); break; } + case MT_GGZFREEZETHRUSTER: + { + Obj_FreezeThrusterInit(mobj); + break; + } + case MT_SIDEWAYSFREEZETHRUSTER: + { + Obj_SidewaysFreezeThrusterInit(mobj); + break; + } default: break; }