diff --git a/src/d_player.h b/src/d_player.h index 85d30355f..328fd321d 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -551,6 +551,16 @@ struct powerupvars_t { mobj_t *barrier; }; +// player_t struct for Frozen Production ice cube state +struct icecubevars_t { + tic_t hitat; // last tic player properly touched frost + + boolean frozen; // frozen in an ice cube + UINT8 wiggle; // number of times player wiggled so far + tic_t frozenat; // tic that player was frozen + UINT8 shaketimer; // while it counts down, ice cube shakes +}; + // player_t struct for all alternative viewpoint variables struct altview_t { @@ -965,6 +975,7 @@ struct player_t sonicloopvars_t loop; roundconditions_t roundconditions; powerupvars_t powerup; + icecubevars_t icecube; level_tally_t tally; }; diff --git a/src/deh_tables.c b/src/deh_tables.c index b29e08394..6adb1147b 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4795,6 +4795,39 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_GPZ_TREETHING_B", "S_GPZ_TREETHING_M", "S_GPZ_TREETHING_S", + + // MT_GGZFREEZETHRUSTER + "S_GGZFREEZETHRUSTER", + + // MT_GGZICEDUST + "S_GGZICEDUST1", + "S_GGZICEDUST2", + "S_GGZICEDUST3", + "S_GGZICEDUST4", + "S_GGZICEDUST5", + "S_GGZICEDUST6", + "S_GGZICEDUST7", + "S_GGZICEDUST8", + "S_GGZICEDUST9", + "S_GGZICEDUST10", + "S_GGZICEDUST11", + "S_GGZPARTICLE11", + "S_GGZPARTICLE12", + "S_GGZPARTICLE13", + "S_GGZPARTICLE14", + "S_GGZPARTICLE15", + "S_GGZPARTICLE16", + "S_GGZPARTICLE17", + "S_GGZPARTICLE18", + "S_GGZPARTICLE21", + "S_GGZPARTICLE22", + "S_GGZPARTICLE23", + "S_GGZPARTICLE24", + + "S_GGZICECUBE", + + // MT_THRUSTERPART + "S_THRUSTERPART", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -6010,6 +6043,13 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_GPZ_TREETHING_B", "MT_GPZ_TREETHING_M", "MT_GPZ_TREETHING_S", + + "MT_GGZFREEZETHRUSTER", + "MT_GGZICEDUST", + "MT_GGZICECUBE", + "MT_GGZICESHATTER", + "MT_SIDEWAYSFREEZETHRUSTER", + "MT_THRUSTERPART", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index 74d760f2f..432ef260e 100644 --- a/src/info.c +++ b/src/info.c @@ -977,6 +977,15 @@ char sprnames[NUMSPRITES + 1][5] = "GPTM", "GPTS", + "GGZ1", + "GGZ2", + "GGZ3", + "GGZ6", + "GGZ7", + "GGZ8", + "FBTN", + "SFTR", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5632,6 +5641,39 @@ state_t states[NUMSTATES] = {SPR_GPTB, 0, -1, {A_SetScale}, 4*FRACUNIT, 0, S_NULL}, // S_GPZ_TREETHING_B, {SPR_GPTM, 0, -1, {A_SetScale}, 4*FRACUNIT, 0, S_NULL}, // S_GPZ_TREETHING_M, {SPR_GPTS, 0, -1, {A_SetScale}, 4*FRACUNIT, 0, S_NULL}, // S_GPZ_TREETHING_S, + + // MT_GGZFREEZETHRUSTER + {SPR_GGZ6, 0, -1, {NULL}, 0, 0, S_GGZFREEZETHRUSTER}, // S_GGZFREEZETHRUSTER + + // MT_GGZICEDUST + {SPR_GGZ7, 0|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST2}, // S_GGZICEDUST1 + {SPR_GGZ7, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST3}, // S_GGZICEDUST2 + {SPR_GGZ7, 2|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST4}, // S_GGZICEDUST3 + {SPR_GGZ7, 3|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST5}, // S_GGZICEDUST4 + {SPR_GGZ7, 4|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST6}, // S_GGZICEDUST5 + {SPR_GGZ7, 5|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST7}, // S_GGZICEDUST6 + {SPR_GGZ7, 6|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST8}, // S_GGZICEDUST7 + {SPR_GGZ7, 7|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST9}, // S_GGZICEDUST8 + {SPR_GGZ7, 8|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST10}, // S_GGZICEDUST9 + {SPR_GGZ7, 9|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZICEDUST11}, // S_GGZICEDUST10 + {SPR_GGZ7, 10|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_NULL}, // S_GGZICEDUST11 + {SPR_GGZ1, 0|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE12}, // S_GGZPARTICLE11 + {SPR_GGZ1, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE13}, // S_GGZPARTICLE12 + {SPR_GGZ1, 2|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE14}, // S_GGZPARTICLE13 + {SPR_GGZ1, 3|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE15}, // S_GGZPARTICLE14 + {SPR_GGZ1, 4|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE16}, // S_GGZPARTICLE15 + {SPR_GGZ1, 5|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE17}, // S_GGZPARTICLE16 + {SPR_GGZ1, 6|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE18}, // S_GGZPARTICLE17 + {SPR_GGZ1, 7|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE11}, // S_GGZPARTICLE18 + {SPR_GGZ2, 0|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE22}, // S_GGZPARTICLE21 + {SPR_GGZ2, 1|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE23}, // S_GGZPARTICLE22 + {SPR_GGZ2, 2|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE24}, // S_GGZPARTICLE23 + {SPR_GGZ2, 3|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_GGZPARTICLE21}, // S_GGZPARTICLE24 + + {SPR_GGZ8, FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_NULL}, // S_GGZICECUBE + + // MT_THRUSTERPART + {SPR_SFTR, 0|FF_PAPERSPRITE, -1, {NULL}, 0, 0, S_THRUSTERPART}, // S_THRUSTERPART }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -31919,6 +31961,163 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOGRAVITY|MF_RUNSPAWNFUNC, // flags S_NULL // raisestate }, + + { // MT_GGZFREEZETHRUSTER + 691, // doomednum + S_GGZFREEZETHRUSTER, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 48*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPTHING|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_GGZICEDUST + -1, // doomednum + S_GGZICEDUST1, // spawnstate + 2, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 16*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOSQUISH|MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_GGZICECUBE + -1, // doomednum + S_GGZICECUBE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 16*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + { // MT_GGZICESHATTER + -1, // doomednum + S_INVISIBLE, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 32*FRACUNIT, // radius + 64*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOCLIPHEIGHT|MF_NOSQUISH, // flags + S_NULL // raisestate + }, + { // MT_SIDEWAYSFREEZETHRUSTER + 693, // doomednum + S_INVISIBLE, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY, // flags + S_NULL // raisestate + }, + { // MT_THRUSTERPART + -1, // doomednum + S_THRUSTERPART, // spawnstate + 1, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 16*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // dispoffset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_SCENERY|MF_NOCLIPTHING, // flags + S_NULL // raisestate + }, }; diff --git a/src/info.h b/src/info.h index 53a9e62fd..be4221a7d 100644 --- a/src/info.h +++ b/src/info.h @@ -1531,6 +1531,15 @@ typedef enum sprite SPR_GPTM, SPR_GPTS, + SPR_GGZ1, + SPR_GGZ2, + SPR_GGZ3, + SPR_GGZ6, + SPR_GGZ7, + SPR_GGZ8, + SPR_FBTN, + SPR_SFTR, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -6057,6 +6066,39 @@ typedef enum state S_GPZ_TREETHING_M, S_GPZ_TREETHING_S, + // MT_GGZFREEZETHRUSTER + S_GGZFREEZETHRUSTER, + + // MT_GGZICEDUST + S_GGZICEDUST1, + S_GGZICEDUST2, + S_GGZICEDUST3, + S_GGZICEDUST4, + S_GGZICEDUST5, + S_GGZICEDUST6, + S_GGZICEDUST7, + S_GGZICEDUST8, + S_GGZICEDUST9, + S_GGZICEDUST10, + S_GGZICEDUST11, + S_GGZPARTICLE11, + S_GGZPARTICLE12, + S_GGZPARTICLE13, + S_GGZPARTICLE14, + S_GGZPARTICLE15, + S_GGZPARTICLE16, + S_GGZPARTICLE17, + S_GGZPARTICLE18, + S_GGZPARTICLE21, + S_GGZPARTICLE22, + S_GGZPARTICLE23, + S_GGZPARTICLE24, + + S_GGZICECUBE, + + // MT_THRUSTERPART + S_THRUSTERPART, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -7292,6 +7334,13 @@ typedef enum mobj_type MT_GPZ_TREETHING_M, MT_GPZ_TREETHING_S, + MT_GGZFREEZETHRUSTER, + MT_GGZICEDUST, + MT_GGZICECUBE, + MT_GGZICESHATTER, + MT_SIDEWAYSFREEZETHRUSTER, + MT_THRUSTERPART, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES diff --git a/src/k_kart.c b/src/k_kart.c index c66f58f41..a3e8fc452 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1168,7 +1168,8 @@ static void K_UpdateOffroad(player_t *player) fixed_t offroadstrength = 0; // If tiregrease is active, don't - if (player->tiregrease == 0) + // If inside an ice cube, don't + if (player->tiregrease == 0 && player->icecube.frozen == false) { // TODO: Make this use actual special touch code. if (terrain != NULL && terrain->offroad > 0) @@ -3599,6 +3600,7 @@ SINT8 K_GetForwardMove(player_t *player) } if (player->spinouttimer != 0 + || player->icecube.frozen || K_PressingEBrake(player) == true || K_PlayerEBrake(player) == true) { @@ -8865,6 +8867,15 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) K_HandleDelayedHitByEm(player); player->pflags &= ~PF_POINTME; + + if (player->icecube.frozen && player->icecube.shaketimer) + { + player->mo->sprxoff += P_RandomRange(PR_DECORATION, -4, 4) * player->mo->scale; + player->mo->spryoff += P_RandomRange(PR_DECORATION, -4, 4) * player->mo->scale; + player->mo->sprzoff += P_RandomRange(PR_DECORATION, -4, 4) * player->mo->scale; + + player->icecube.shaketimer--; + } } void K_KartResetPlayerColor(player_t *player) @@ -8953,12 +8964,17 @@ void K_KartResetPlayerColor(player_t *player) fullbright = true; goto finalise; } - else + + if (player->icecube.frozen) { - player->mo->colorized = (player->dye != 0); - player->mo->color = player->dye ? player->dye : player->skincolor; + player->mo->colorized = true; + player->mo->color = SKINCOLOR_CYAN; + goto finalise; } + player->mo->colorized = (player->dye != 0); + player->mo->color = player->dye ? player->dye : player->skincolor; + finalise: if (player->curshield && player->curshield != KSHIELD_TOP) @@ -11233,6 +11249,11 @@ void K_AdjustPlayerFriction(player_t *player) player->mo->friction -= 9824; } + if (player->icecube.frozen) + { + player->mo->friction = FRACUNIT; + } + // Cap between intended values if (player->mo->friction > FRACUNIT) player->mo->friction = FRACUNIT; @@ -12643,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/mobj.hpp b/src/mobj.hpp new file mode 100644 index 000000000..2a330727e --- /dev/null +++ b/src/mobj.hpp @@ -0,0 +1,195 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James Robert Roman +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef mobj_hpp +#define mobj_hpp + +#include + +#include "math/fixed.hpp" +#include "math/vec.hpp" + +#include "info.h" +#include "p_local.h" +#include "p_mobj.h" +#include "s_sound.h" +#include "sounds.h" +#include "typedef.h" + +namespace srb2 +{ + +struct Mobj : mobj_t +{ + // TODO: Vec3 would be nice + struct PosArg + { + math::Fixed x, y, z; + + PosArg() : PosArg(0, 0, 0) {} + PosArg(fixed_t x_, fixed_t y_, fixed_t z_) : x(x_), y(y_), z(z_) {} + + template + PosArg(math::Vec2 p, fixed_t z) : PosArg(p.x, p.y, z) {} + + PosArg(const mobj_t* mobj) : PosArg(mobj->x, mobj->y, mobj->z) {} + }; + + // ManagedPtr(mobj_t::target); wrapper around a reference + // counted mobj pointer. Assigning the wrapper updates + // the original pointer and performs reference counting. + struct ManagedPtr + { + ManagedPtr(mobj_t*& ref) : ref_(ref) {} + ManagedPtr& operator=(mobj_t* ptr) + { + P_SetTarget(&ref_, ptr); + return *this; + } + operator mobj_t*() const { return ref_; } + + private: + mobj_t*& ref_; + }; + + + // + // Validity checks + // + + static bool valid(const Mobj* mobj) { return !P_MobjWasRemoved(mobj); } // safe for nullptr + bool valid() const { return Mobj::valid(this); } + + void remove() { P_RemoveMobj(this); } + + + // + // Spawning + // + + // Mobj_t::spawn; spawn Mobj at position. Mobj inherits map defaults (scale, gravity). + template + static T* spawn(const PosArg& p, mobjtype_t type) { return static_cast(P_SpawnMobj(p.x, p.y, p.z, type)); } + + // this->spawn_from; spawn Mobj relative to parent (this->pos + p). Mobj inherits parent scale, gravity. + template + T* spawn_from(const PosArg& p, mobjtype_t type) + { + return static_cast(P_SpawnMobjFromMobjUnscaled(this, p.x, p.y, p.z, type)); + } + + template + T* spawn_from(mobjtype_t type) { return spawn_from({}, type); } + + // TODO: ghosts have unique properties, add Ghost class + Mobj* spawn_ghost() { return static_cast(P_SpawnGhostMobj(this)); } + + + // + // Position + // + + PosArg center() const { return {x, y, z + (height / 2)}; } + PosArg pos() const { return {x, y, z}; } + math::Vec2 pos2d() const { return {x, y}; } + math::Fixed top() const { return z + height; } + + bool is_flipped() const { return eflags & MFE_VERTICALFLIP; } + math::Fixed flip(fixed_t x) const { return x * P_MobjFlip(this); } + + // Collision helper + bool z_overlaps(const Mobj* b) const { return z < b->top() && b->z < top(); } + + void move_origin(const PosArg& p) { P_MoveOrigin(this, p.x, p.y, p.z); } + void set_origin(const PosArg& p) { P_SetOrigin(this, p.x, p.y, p.z); } + void instathrust(angle_t angle, fixed_t speed) { P_InstaThrust(this, angle, speed); } + void thrust(angle_t angle, fixed_t speed) { P_Thrust(this, angle, speed); } + + + // + // Mobj pointers + // + +#define MOBJ_PTR_METHOD(member) \ + template \ + T* member() const { return static_cast(mobj_t::member); }\ + template \ + void member(T* n) { ManagedPtr(this->mobj_t::member) = n; } + + MOBJ_PTR_METHOD(hnext) + MOBJ_PTR_METHOD(hprev) + MOBJ_PTR_METHOD(itnext) + MOBJ_PTR_METHOD(target) + MOBJ_PTR_METHOD(tracer) + MOBJ_PTR_METHOD(punt_ref) + MOBJ_PTR_METHOD(owner) + +#undef MOBJ_PTR_METHOD + + + // + // State + // + + struct State : state_t + { + statenum_t num() const { return static_cast(static_cast(this) - states); } + }; + + void state(statenum_t state) { P_SetMobjState(this, state); } + const State* state() const { return static_cast(mobj_t::state); } + + + // + // Scale + // + + math::Fixed scale() const { return mobj_t::scale; } + + void scale(fixed_t n) + { + mobj_t::scale = n; + mobj_t::destscale = n; + } + + void scale_to(fixed_t stop, std::optional speed = {}) + { + mobj_t::destscale = stop; + + if (speed) + { + mobj_t::scalespeed = *speed; + } + } + + void scale_between(fixed_t start, fixed_t stop, std::optional speed = {}) + { + mobj_t::scale = start; + scale_to(stop, speed); + } + + + // + // Sound + // + + void voice(sfxenum_t sfx, int volume = 255) const { S_StartSoundAtVolume(this, sfx, volume); } + bool voice_playing(sfxenum_t sfx) const { return S_SoundPlaying(this, sfx); } + void voice_loop(sfxenum_t sfx, int volume = 255) const + { + if (!voice_playing(sfx)) + { + voice(sfx, volume); + } + } +}; + +}; // namespace srb2 + +#endif/*mobj_hpp*/ 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..bd200a64d --- /dev/null +++ b/src/objects/frost-thrower.cpp @@ -0,0 +1,451 @@ +// 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: + auto spawn_pipe = [&](int 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); + }; + + // This is unrolled because when it was a for loop, + // it ran infinitely, but only under MinGW. + // Tested: gcc.exe (Rev2, Built by MSYS2 project) 13.2.0 (32-bit version) + spawn_pipe(0); + spawn_pipe(1); + spawn_pipe(2); + spawn_pipe(3); + + // 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/objects/mega-barrier.cpp b/src/objects/mega-barrier.cpp index cfdd4f077..258b6717a 100644 --- a/src/objects/mega-barrier.cpp +++ b/src/objects/mega-barrier.cpp @@ -9,6 +9,8 @@ #include +#include "../mobj.hpp" + #include "../doomdef.h" #include "../d_player.h" #include "../g_game.h" @@ -23,45 +25,13 @@ #define barrier_player(o) ((o)->extravalue1) +using srb2::Mobj; + namespace { struct Barrier; -// TODO: header -struct Mobj : mobj_t -{ - struct PosArg - { - fixed_t x, y, z; - - PosArg(fixed_t x_, fixed_t y_, fixed_t z_) : x(x_), y(y_), z(z_) {} - PosArg(const mobj_t* mobj) : x(mobj->x), y(mobj->y), z(mobj->z) {} - }; - - static bool valid(const Mobj* mobj) { return !P_MobjWasRemoved(mobj); } - - PosArg center() const { return {x, y, z + (height / 2)}; } - - template - T* spawn_offset(mobjtype_t type) { return static_cast(P_SpawnMobjFromMobj(this, 0, 0, 0, type)); } - - void state(statenum_t state) { P_SetMobjState(this, state); } - statenum_t statenum() const { return static_cast(mobj_t::state - states); } - - fixed_t scale() const { return mobj_t::scale; } - - void scale(fixed_t n) - { - mobj_t::scale = n; - mobj_t::destscale = n; - } - - void move_origin(const PosArg& p) { P_MoveOrigin(this, p.x, p.y, p.z); } - - void remove() { P_RemoveMobj(this); } -}; - struct Player : player_t { struct Powerups : powerupvars_t @@ -87,7 +57,7 @@ struct Barrier : Mobj static Barrier* spawn(Player* player, statenum_t state, int idx) { - Barrier* child = player->mobj()->spawn_offset(MT_MEGABARRIER); + Barrier* child = player->mobj()->spawn_from(MT_MEGABARRIER); child->angle = player->mobj()->angle + (idx * kSpinGap); child->player(player); 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; } diff --git a/src/p_saveg.c b/src/p_saveg.c index ea80ea855..9d727cdcc 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -771,6 +771,13 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].tally.showGrade); WRITEUINT8(save->p, players[i].tally.done); } + + // icecubevars_t + WRITEUINT32(save->p, players[i].icecube.hitat); + WRITEUINT8(save->p, players[i].icecube.frozen); + WRITEUINT8(save->p, players[i].icecube.wiggle); + WRITEUINT32(save->p, players[i].icecube.frozenat); + WRITEUINT8(save->p, players[i].icecube.shaketimer); } } @@ -1329,6 +1336,13 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].tally.done = (boolean)READUINT8(save->p); } + // icecubevars_t + players[i].icecube.hitat = READUINT32(save->p); + players[i].icecube.frozen = READUINT8(save->p); + players[i].icecube.wiggle = READUINT8(save->p); + players[i].icecube.frozenat = READUINT32(save->p); + players[i].icecube.shaketimer = READUINT8(save->p); + //players[i].viewheight = P_GetPlayerViewHeight(players[i]); // scale cannot be factored in at this point } } diff --git a/src/p_user.c b/src/p_user.c index 2bf8b8193..3f9540ba3 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -461,7 +461,7 @@ UINT8 P_FindHighestLap(void) // boolean P_PlayerInPain(player_t *player) { - if (player->spinouttimer || (player->tumbleBounces > 0) || (player->pflags & PF_FAULT)) + if (player->spinouttimer || (player->tumbleBounces > 0) || (player->pflags & PF_FAULT) || player->icecube.frozen) return true; return false; @@ -2420,7 +2420,14 @@ void P_MovePlayer(player_t *player) } // Kart frames - if (player->tumbleBounces > 0) + if (player->icecube.frozen) + { + INT32 spd = FixedMul(player->mo->scale, FixedHypot(player->mo->momx, player->mo->momy)) / FRACUNIT; + P_SetPlayerMobjState(player->mo, S_KART_SPINOUT); + player->drawangle -= max(2, spd / 6) * ANG1; + P_ResetPitchRoll(player->mo); + } + else if (player->tumbleBounces > 0) { fixed_t playerSpeed = P_AproxDistance(player->mo->momx, player->mo->momy); // maybe momz too? diff --git a/src/s_sound.c b/src/s_sound.c index 33cf4c3be..90cc11fb8 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1090,7 +1090,7 @@ INT32 S_IdPlaying(sfxenum_t id) // Searches through the channels and checks for // origin x playing sound id y. -INT32 S_SoundPlaying(void *origin, sfxenum_t id) +INT32 S_SoundPlaying(const void *origin, sfxenum_t id) { INT32 cnum; if (!origin) diff --git a/src/s_sound.h b/src/s_sound.h index bb20f8994..3ad5b65cc 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -248,7 +248,7 @@ void S_SetMusicVolume(void); INT32 S_OriginPlaying(void *origin); INT32 S_IdPlaying(sfxenum_t id); -INT32 S_SoundPlaying(void *origin, sfxenum_t id); +INT32 S_SoundPlaying(const void *origin, sfxenum_t id); void S_StartSoundName(void *mo, const char *soundname); diff --git a/src/sounds.c b/src/sounds.c index dcfa01cc3..0db2361bf 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1247,6 +1247,8 @@ sfxinfo_t S_sfx[NUMSFX] = {"befan1", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Whisking"}, // Blend Eye whisk startup {"befan2", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Whisking"}, // Blend Eye whisk + {"glgz1", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, "Ice Cube shatters"}, + // Damage sounds {"dmga1", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, {"dmga2", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, "Damaged"}, diff --git a/src/sounds.h b/src/sounds.h index 0d686b3c2..f0337ea40 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1317,6 +1317,9 @@ typedef enum sfx_befan1, sfx_befan2, + // Ice Cube + sfx_glgz1, + // Damage sounds sfx_dmga1, sfx_dmga2, diff --git a/src/typedef.h b/src/typedef.h index b32d4b974..82b2bd7b4 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -52,6 +52,7 @@ TYPEDEF (roundconditions_t); TYPEDEF (skybox_t); TYPEDEF (itemroulette_t); TYPEDEF (powerupvars_t); +TYPEDEF (icecubevars_t); TYPEDEF (altview_t); TYPEDEF (player_t);