From fd0167a3b00ce94b3884aa871211ad95669e48bd Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:27:58 -0700 Subject: [PATCH 01/11] Add Super Flicky states Adds 3DFR sprite (previously freeslotted by followers.pk3) --- src/deh_tables.c | 5 +++++ src/info.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ src/info.h | 7 ++++++ 3 files changed, 70 insertions(+) diff --git a/src/deh_tables.c b/src/deh_tables.c index ddbae5cbf..9c46ebc93 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4571,6 +4571,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_GACHABOM_EXPLOSION_4", "S_GACHABOM_WAITING", "S_GACHABOM_RETURNING", + + "S_SUPER_FLICKY", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -5697,6 +5699,9 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_LOOPENDPOINT", "MT_LOOPCENTERPOINT", + + "MT_SUPER_FLICKY", + "MT_SUPER_FLICKY_CONTROLLER", }; const char *const MOBJFLAG_LIST[] = { diff --git a/src/info.c b/src/info.c index 7d1c0023c..61da58f39 100644 --- a/src/info.c +++ b/src/info.c @@ -815,6 +815,8 @@ char sprnames[NUMSPRITES + 1][5] = "GBOM", "GCHX", + "3DFR", + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later "VIEW", }; @@ -5235,6 +5237,8 @@ state_t states[NUMSTATES] = {SPR_GCHX, 6|FF_PAPERSPRITE|FF_ANIMATE|FF_REVERSEANIM, 14, {NULL}, 6, 2, S_GACHABOM_WAITING}, // S_GACHABOM_EXPLOSION_4 {SPR_GBOM, FF_INVERT, 8, {A_SetScale}, FRACUNIT, 0, S_GACHABOM_RETURNING}, // S_GACHABOM_WAITING {SPR_GBOM, FF_INVERT, -1, {A_SetScale}, FRACUNIT/2, 1, S_NULL}, // S_GACHABOM_RETURNING + + {SPR_3DFR, 1|FF_ANIMATE, -1, {NULL}, 2, 5, S_NULL}, // S_SUPER_FLICKY }; mobjinfo_t mobjinfo[NUMMOBJTYPES] = @@ -29794,6 +29798,60 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_DONTENCOREMAP, // flags S_NULL // raisestate }, + + { // MT_SUPER_FLICKY + -1, // doomednum + S_SUPER_FLICKY, // 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 + 32*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_SPECIAL|MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT, // flags + S_NULL // raisestate + }, + + { // MT_SUPER_FLICKY_CONTROLLER + -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 + 16*FRACUNIT, // radius + 32*FRACUNIT, // height + 0, // display offset + 0, // mass + 0, // damage + sfx_None, // activesound + MF_NOSECTOR|MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT|MF_SCENERY, // flags + S_NULL // raisestate + }, }; skincolor_t skincolors[MAXSKINCOLORS] = { diff --git a/src/info.h b/src/info.h index a203dbc3d..ea4df4be3 100644 --- a/src/info.h +++ b/src/info.h @@ -1368,6 +1368,8 @@ typedef enum sprite SPR_GBOM, SPR_GCHX, + SPR_3DFR, + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later SPR_VIEW, @@ -5667,6 +5669,8 @@ typedef enum state S_GACHABOM_WAITING, S_GACHABOM_RETURNING, + S_SUPER_FLICKY, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -6812,6 +6816,9 @@ typedef enum mobj_type MT_LOOPENDPOINT, MT_LOOPCENTERPOINT, + MT_SUPER_FLICKY, + MT_SUPER_FLICKY_CONTROLLER, + MT_FIRSTFREESLOT, MT_LASTFREESLOT = MT_FIRSTFREESLOT + NUMMOBJFREESLOTS - 1, NUMMOBJTYPES From b35d0ae25ebfdab7f24251b0f5d0a122d2eaa2d9 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 26 Jun 2023 23:25:29 -0700 Subject: [PATCH 02/11] Add Super Flicky sounds fbost1, fbird, fhurt1, fhurt2 were previously freeslotted by chars.pk3. They are used by the Flicky skin. --- src/sounds.c | 7 +++++++ src/sounds.h | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/sounds.c b/src/sounds.c index 9e2702a10..3accd6b79 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1189,6 +1189,13 @@ sfxinfo_t S_sfx[NUMSFX] = {"iwhp", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Instawhip attack {"gbrk", false, 255, 8, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Guard break! + // Super Flicky Power-Up + {"supflk", false, 255, 32, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Woodpecking - SF_NOINTERRUPT + {"fbost1", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Slowing down + {"fbird", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Speeding up + {"fhurt1", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Whipped + {"fhurt2", false, 255, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, // Hunting + // SRB2Kart - Engine sounds // Engine class A {"krta00", false, 48, 65, -1, NULL, 0, -1, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index a3a72c6a7..033728e37 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1258,6 +1258,12 @@ typedef enum sfx_iwhp, sfx_gbrk, + sfx_supflk, + sfx_fbost1, + sfx_fbird, + sfx_fhurt1, + sfx_fhurt2, + // Next up: UNIQUE ENGINE SOUNDS! Hoooooo boy... // Engine class A - Low Speed, Low Weight sfx_krta00, From d7373871324697f8dad994a51b510ca7b560c1a6 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:06:13 -0700 Subject: [PATCH 03/11] Add flickyAttacker and powerupvars_t to player_t, add to netsave --- src/d_player.h | 7 +++++++ src/k_kart.c | 6 ++++++ src/p_saveg.c | 34 ++++++++++++++++++++++++++++++++++ src/typedef.h | 1 + 4 files changed, 48 insertions(+) diff --git a/src/d_player.h b/src/d_player.h index 96e990f79..a360f41b7 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -439,6 +439,11 @@ typedef struct { boolean flip; } sonicloopvars_t; +// player_t struct for power-ups +struct powerupvars_t { + mobj_t *flickyController; +}; + // player_t struct for all alternative viewpoint variables struct altview_t { @@ -756,6 +761,7 @@ struct player_t mobj_t *sliptideZipIndicator; mobj_t *whip; mobj_t *hand; + mobj_t *flickyAttacker; UINT8 instaShieldCooldown; UINT8 guardCooldown; @@ -777,6 +783,7 @@ struct player_t sonicloopvars_t loop; roundconditions_t roundconditions; + powerupvars_t powerup; }; // WARNING FOR ANYONE ABOUT TO ADD SOMETHING TO THE PLAYER STRUCT, G_PlayerReborn WANTS YOU TO SUFFER diff --git a/src/k_kart.c b/src/k_kart.c index 1a6ba0d06..64f54942e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -8287,6 +8287,12 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->hand && P_MobjWasRemoved(player->hand)) P_SetTarget(&player->hand, NULL); + if (player->flickyAttacker && P_MobjWasRemoved(player->flickyAttacker)) + P_SetTarget(&player->flickyAttacker, NULL); + + if (player->powerup.flickyController && P_MobjWasRemoved(player->powerup.flickyController)) + P_SetTarget(&player->powerup.flickyController, NULL); + if (player->spectator == false) { K_KartEbrakeVisuals(player); diff --git a/src/p_saveg.c b/src/p_saveg.c index f80ffff7b..16efa2798 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -79,6 +79,8 @@ typedef enum RINGSHOOTER = 0x0100, WHIP = 0x0200, HAND = 0x0400, + FLICKYATTACKER = 0x0800, + FLICKYCONTROLLER = 0x1000, } player_saveflags; static inline void P_ArchivePlayer(savebuffer_t *save) @@ -319,6 +321,12 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (players[i].ringShooter) flags |= RINGSHOOTER; + if (players[i].flickyAttacker) + flags |= FLICKYATTACKER; + + if (players[i].powerup.flickyController) + flags |= FLICKYCONTROLLER; + WRITEUINT16(save->p, flags); if (flags & SKYBOXVIEW) @@ -351,6 +359,12 @@ static void P_NetArchivePlayers(savebuffer_t *save) if (flags & RINGSHOOTER) WRITEUINT32(save->p, players[i].ringShooter->mobjnum); + if (flags & FLICKYATTACKER) + WRITEUINT32(save->p, players[i].flickyAttacker->mobjnum); + + if (flags & FLICKYCONTROLLER) + WRITEUINT32(save->p, players[i].powerup.flickyController->mobjnum); + WRITEUINT32(save->p, (UINT32)players[i].followitem); WRITEUINT32(save->p, players[i].charflags); @@ -754,6 +768,12 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) if (flags & RINGSHOOTER) players[i].ringShooter = (mobj_t *)(size_t)READUINT32(save->p); + if (flags & FLICKYATTACKER) + players[i].flickyAttacker = (mobj_t *)(size_t)READUINT32(save->p); + + if (flags & FLICKYCONTROLLER) + players[i].powerup.flickyController = (mobj_t *)(size_t)READUINT32(save->p); + players[i].followitem = (mobjtype_t)READUINT32(save->p); //SetPlayerSkinByNum(i, players[i].skin); @@ -5258,6 +5278,20 @@ static void P_RelinkPointers(void) if (!P_SetTarget(&players[i].ringShooter, P_FindNewPosition(temp))) CONS_Debug(DBG_GAMELOGIC, "ringShooter not found on player %d\n", i); } + if (players[i].flickyAttacker) + { + temp = (UINT32)(size_t)players[i].flickyAttacker; + players[i].flickyAttacker = NULL; + if (!P_SetTarget(&players[i].flickyAttacker, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "flickyAttacker not found on player %d\n", i); + } + if (players[i].powerup.flickyController) + { + temp = (UINT32)(size_t)players[i].powerup.flickyController; + players[i].powerup.flickyController = NULL; + if (!P_SetTarget(&players[i].powerup.flickyController, P_FindNewPosition(temp))) + CONS_Debug(DBG_GAMELOGIC, "powerup.flickyController not found on player %d\n", i); + } } } diff --git a/src/typedef.h b/src/typedef.h index 275a30da4..013bf7f18 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -47,6 +47,7 @@ TYPEDEF (botvars_t); TYPEDEF (roundconditions_t); TYPEDEF (skybox_t); TYPEDEF (itemroulette_t); +TYPEDEF (powerupvars_t); TYPEDEF (altview_t); TYPEDEF (player_t); From ed262f780b6fa864df3df8b4119ebc6cf612a665 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:31:30 -0700 Subject: [PATCH 04/11] Add majority of Super Flicky object functionality Thinkers and most collision. - 4 Super Flickys deploy from above the owner player, in a radius. - Radius shrinks as Flickys descend. - Flickys orbit their owner until coming within range of another player. - The entire group of Flickys attack another player at once, with some delay between each. - Flickys accelerate toward their target, constantly building speed. - When a Flicky is both within a short radius of its target and the angle of momentum is narrowed toward the target, the Flicky will sharply accelerate to impale the target. - When a Flicky is both outside of a long radius of its target and the angle of momentum is facing away from the target, the Flicky's momentum will be drastically cut in order to make it easier for the Flicky to turn around. - After one of the Flickys in the group hits its target, all but one of the group is free to hunt a different target. - A new target is chosen from a radius around the current target. - Flickys can only target players who are not respawning and who have not already been attacked by another Flicky. - Super Flickys can be blocked by a Guard. The Super Flicky shall have all its momentum reflected (strong knockback). - Super Flickys can be insta-whipped. This shall have the same effect as a Guard, with the additional effect of knocking the 'Super' out of the Super Flicky. - Non-Super Flickys are knocked back with gravity. After bouncing off the ground once, it regains flight and will continue to chase its target. However, it cannot damage the target. After 5 seconds, the Flicky regains Super state. - The Flicky power-up is on a timer. After the timer expires, Flickys lose Super state and ascend back into the air (reverse of their initial descent). - If the Super Flicky is not orbiting its target when it ascends, it retains all horizontal momentum during the ascent, 'flying off into the distance'. --- src/k_objects.h | 17 + src/objects/CMakeLists.txt | 1 + src/objects/super-flicky.cpp | 769 +++++++++++++++++++++++++++++++++++ src/p_inter.c | 12 + src/p_mobj.c | 22 + 5 files changed, 821 insertions(+) create mode 100644 src/objects/super-flicky.cpp diff --git a/src/k_objects.h b/src/k_objects.h index a20798dac..477c109ac 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -2,6 +2,8 @@ #ifndef k_objects_H #define k_objects_H +#include "doomdef.h" +#include "m_fixed.h" #include "tables.h" // angle_t #include "taglist.h" @@ -143,6 +145,21 @@ void Obj_SpawnGachaBomRebound(mobj_t *source, mobj_t *target); /* Servant Hand */ void Obj_ServantHandHandling(player_t *player); +/* Super Flicky Controller */ +void Obj_SpawnSuperFlickySwarm(player_t *owner, tic_t time); +void Obj_SuperFlickyControllerThink(mobj_t *controller); +void Obj_EndSuperFlickySwarm(mobj_t *controller); +void Obj_ExtendSuperFlickySwarm(mobj_t *controller, tic_t time); +tic_t Obj_SuperFlickySwarmTime(const mobj_t *controller); + +/* Super Flicky */ +void Obj_SuperFlickyThink(mobj_t *flicky); +void Obj_WhipSuperFlicky(mobj_t *flicky); +void Obj_BlockSuperFlicky(mobj_t *flicky); +void Obj_SuperFlickyPlayerCollide(mobj_t *flicky, mobj_t *player); +void Obj_SuperFlickyLanding(mobj_t *flicky); +boolean Obj_IsSuperFlickyWhippable(const mobj_t *flicky); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 6ec83ac0c..1bbf55729 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -21,4 +21,5 @@ target_sources(SRB2SDL2 PRIVATE block.c gachabom-rebound.cpp servant-hand.c + super-flicky.cpp ) diff --git a/src/objects/super-flicky.cpp b/src/objects/super-flicky.cpp new file mode 100644 index 000000000..c28c3250d --- /dev/null +++ b/src/objects/super-flicky.cpp @@ -0,0 +1,769 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by James R. +// 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. +//----------------------------------------------------------------------------- +/// \brief Super Flicky power-up, hunts other players + +#include "../d_player.h" +#include "../doomdef.h" +#include "../g_game.h" +#include "../k_battle.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../k_respawn.h" +#include "../m_fixed.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../r_main.h" +#include "../s_sound.h" +#include "../tables.h" + +#define flicky_controller(o) ((o)->target) +#define flicky_chasing(o) ((o)->tracer) +#define flicky_next(o) ((o)->hnext) +#define flicky_next_target(o) ((o)->hprev) +#define flicky_phase(o) ((o)->threshold) +#define flicky_delay(o) ((o)->movecount) +#define flicky_mode(o) ((o)->extravalue1) +#define flicky_fly(o) ((o)->extravalue2) + +#define controller_source(o) ((o)->target) +#define controller_chasing(o) ((o)->tracer) +#define controller_flicky(o) ((o)->hnext) +#define controller_mode(o) ((o)->movecount) +#define controller_zofs(o) ((o)->sprzoff) +#define controller_expiry(o) ((o)->fuse) + +namespace +{ + +constexpr tic_t kOrbitSpeed = 2*TICRATE; +constexpr int kOrbitSpacing = ANGLE_90; + +// Multiples of player radius +constexpr fixed_t kOrbitRadiusInitial = 32*FRACUNIT; +constexpr fixed_t kOrbitRadius = 2*FRACUNIT; + +constexpr int kDescendHeight = 256; +constexpr int kDescendSmoothing = 16; + +constexpr int kSearchRadius = 1920; +constexpr int kFlightRadius = 1280; +constexpr int kPeckingRadius = 256; + +constexpr int kFlightSpeed = 2; +constexpr int kPeckingSpeed = 8; + +constexpr fixed_t kRebound = 8*FRACUNIT/9; + +constexpr tic_t kDelay = 8; +constexpr tic_t kStunTime = 5*TICRATE; +constexpr tic_t kBlockTime = 1*TICRATE; + +constexpr int kRiseTime = 1*TICRATE; +constexpr int kRiseSpeed = 4; + +// TODO: skincolor must be updated to 2.2 palette +constexpr skincolornum_t kSuperStart = SKINCOLOR_SUPERGOLD1; +constexpr skincolornum_t kSuperEnd = SKINCOLOR_SUPERGOLD5; + +// copied from objects/hyudoro.c +void +sine_bob +( mobj_t * hyu, + angle_t a, + fixed_t sineofs) +{ + const fixed_t kBobHeight = 4 * mapobjectscale; + + // slightly modified from objects/hyudoro.c + hyu->sprzoff = FixedMul(kBobHeight, + sineofs + FINESINE(a >> ANGLETOFINESHIFT)); +} + +void +bob_in_place +( mobj_t * hyu, + INT32 phase, + INT32 bob_speed) +{ + sine_bob(hyu, + ((leveltime + phase) & (bob_speed - 1)) * + (ANGLE_MAX / bob_speed), -(3*FRACUNIT/4)); +} + +struct Flicky; + +struct Controller : mobj_t +{ + enum class Mode : int + { + kDescend, + kOrbit, + kEnRoute, + kAttached, + kReturning, + }; + + mobj_t* source() const { return controller_source(this); } + void source(mobj_t* n) { P_SetTarget(&controller_source(this), n); } + + mobj_t* chasing() const { return controller_chasing(this); } + void chasing(mobj_t* n) { P_SetTarget(&controller_chasing(this), n); } + + Flicky* flicky() const; + void flicky(Flicky* n); + + Mode mode() const { return static_cast(controller_mode(this)); } + void mode(Mode n) { controller_mode(this) = static_cast(n); } + + fixed_t zofs() const { return controller_zofs(this); } + void zofs(fixed_t n) { controller_zofs(this) = n; } + + tic_t expiry() const { return controller_expiry(this); } + void expiry(tic_t n) { controller_expiry(this) = n; } + + static Controller* spawn(player_t* player, tic_t time) + { + Controller* x = static_cast(P_SpawnMobjFromMobjUnscaled( + player->mo, + 0, + 0, + kDescendHeight * mapobjectscale, + MT_SUPER_FLICKY_CONTROLLER + )); + + x->source(player->mo); + x->mode(Mode::kDescend); + x->zofs(0); + x->expiry(leveltime + time); + + P_SetTarget(&player->powerup.flickyController, x); + + S_StartSound(x, sfx_s3k46); + + return x; + } + + bool valid() { return !P_MobjWasRemoved(source()); } + tic_t time_remaining() const { return expiry() - leveltime; } + tic_t powerup_remaining() const { return ending() ? 0u : time_remaining() - kRiseTime; } + bool ending() const { return time_remaining() <= kRiseTime; } + + void descend() + { + fixed_t head = P_GetMobjHead(source()); + fixed_t tz = head; + + if (mode() == Mode::kDescend) + { + tz = z - ((z - head) / kDescendSmoothing); + + if ((tz - head) < mapobjectscale) + { + mode(Mode::kOrbit); + tz = head; + } + } + + z = tz + zofs(); + + if (ending()) + { + zofs(zofs() + (kRiseSpeed * mapobjectscale * P_MobjFlip(this))); + } + } + + void expand() + { + fixed_t n = FixedMul(kOrbitRadiusInitial, ((z - P_GetMobjHead(source())) / kDescendHeight)); + + radius = FixedMul(FixedMul(kOrbitRadius, source()->radius), FRACUNIT + n); + } + + void end() + { + // +1 in case flicky already thunk this tic + expiry(leveltime + kRiseTime + 1); + } + + void search(); +}; + +struct Flicky : mobj_t +{ + enum class Mode : int + { + kReserved, + kHunting, + kStunned, + kWeak, + }; + + enum class Fly : int + { + kNormal, + kZoom, + kSlow, + }; + + Controller* controller() const { return static_cast(flicky_controller(this)); } + void controller(Controller* n) { P_SetTarget(&flicky_controller(this), n); } + + mobj_t* chasing() const { return flicky_chasing(this); } + void chasing(mobj_t* n) { P_SetTarget(&flicky_chasing(this), n); } + + Flicky* next() const { return static_cast(flicky_next(this)); } + void next(Flicky* n) { P_SetTarget(&flicky_next(this), n); } + + mobj_t* next_target() const { return flicky_next_target(this); } + void next_target(mobj_t* n) { P_SetTarget(&flicky_next_target(this), n); } + + int phase() const { return flicky_phase(this); } + void phase(int n) { flicky_phase(this) = n; } + + int delay() const { return flicky_delay(this); } + void delay(int n) { flicky_delay(this) = n; } + + Mode mode() const { return static_cast(flicky_mode(this)); } + void mode(Mode n) { flicky_mode(this) = static_cast(n); } + + Fly fly() const { return static_cast(flicky_fly(this)); } + void fly(Fly n) { flicky_fly(this) = static_cast(n); } + + mobj_t* source() const { return controller()->source(); } + + static void spawn(Controller* controller, int phase) + { + Flicky* x = static_cast(P_SpawnMobjFromMobj(controller, 0, 0, 0, MT_SUPER_FLICKY)); + + x->controller(controller); + x->phase(phase); + x->delay(0); + x->mode(Mode::kReserved); + x->light_up(true); + + x->next(controller->flicky()); + controller->flicky(x); + } + + static skincolornum_t super_color() + { + return static_cast(kSuperStart + ((leveltime / 4) % ((kSuperEnd - kSuperStart) + 1))); + } + + bool valid() const { return !P_MobjWasRemoved(controller()) && controller()->valid(); } + + bool stunned() const { return mode() == Mode::kStunned || mode() == Mode::kWeak; } + + void light_up(bool n) + { + if (n) + { + renderflags |= RF_FULLBRIGHT; + color = super_color(); + } + else + { + renderflags &= ~(RF_FULLBRIGHT); + color = source()->player ? source()->player->skincolor : source()->color; + } + } + + void animate() + { + P_InstaScale(this, source()->scale * (chasing() ? 2 : 1)); + + if (color >= kSuperStart && color <= kSuperEnd) + { + color = super_color(); + } + + bob_in_place(this, phase() * 8, 32); + } + + void refocus() + { + if (controller()->ending()) + { + if (controller()->time_remaining() == kRiseTime) + { + light_up(false); + rise(); + } + + return; + } + + if (delay() > 0) + { + delay(delay() - 1); + } + else + { + if (chasing() != next_target()) + { + chasing(next_target()); + mode(Mode::kHunting); + + S_StartSound(this, sfx_fhurt2); + } + + if (stunned()) + { + light_up(true); + flags = info->flags; + mode(Mode::kHunting); + + S_StartSound(this, sfx_s3k9f); + } + } + } + + angle_t orbit_angle() const { return controller()->angle + (phase() * kOrbitSpacing); } + + vector3_t orbit_position() const + { + return { + source()->x + FixedMul(FCOS(orbit_angle()), controller()->radius), + source()->y + FixedMul(FSIN(orbit_angle()), controller()->radius), + controller()->z + }; + } + + void orbit() + { + vector3_t pos = orbit_position(); + + P_MoveOrigin(this, pos.x, pos.y, pos.z); + + momx = 0; + momy = 0; + momz = 0; + + angle = orbit_angle() + ANGLE_90; // face direction of orbit + } + + + // copied from objects/spb.c + void spawn_speed_lines(angle_t direction) + { + if (mode() != Mode::kHunting) + { + return; + } + + mobj_t *fast = P_SpawnMobjFromMobjUnscaled( + this, + P_RandomRange(PR_DECORATION, -24, 24) * mapobjectscale, + P_RandomRange(PR_DECORATION, -24, 24) * mapobjectscale, + (height / 2) + (P_RandomRange(PR_DECORATION, -24, 24) * mapobjectscale), + MT_FASTLINE + ); + + P_SetTarget(&fast->target, this); + fast->angle = direction; + + fast->color = source()->color; + fast->colorized = true; + fast->renderflags |= RF_ADD; + + K_MatchGenericExtraFlags(fast, this); + } + + void chase() + { + if (controller()->ending()) + { + return; + } + + vector3_t pos = chasing() ? vector3_t{chasing()->x, chasing()->y, P_GetMobjFeet(chasing())} : orbit_position(); + angle_t th = R_PointToAngle2(x, y, pos.x, pos.y); + + momz = (pos.z - z) / kDescendSmoothing; + angle = K_MomentumAngleReal(this); + + angle_t d = AngleDelta(th, angle); + fixed_t dist = FixedHypot(x - pos.x, y - pos.y); + + const Fly oldFly = fly(); + + if (d < ANGLE_11hh && dist < kPeckingRadius * mapobjectscale) + { + // Drastically speed up when about to intersect + P_Thrust(this, th, kPeckingSpeed * mapobjectscale); + fly(Fly::kZoom); + } + else + { + P_Thrust(this, th, kFlightSpeed * mapobjectscale); + fly(Fly::kNormal); + } + + if (d > ANGLE_45 && dist > kFlightRadius * mapobjectscale) + { + // Cut momentum when too far outside of intended trajectory + momx = FixedMul(momx, kRebound); + momy = FixedMul(momy, kRebound); + + spawn_speed_lines(th); + + fly(Fly::kSlow); + } + else + { + spawn_speed_lines(angle); + } + + // Returning to owner + if (!chasing()) + { + if (AngleDelta(th, R_PointToAngle2(x + momx, y + momy, pos.x, pos.y)) > ANG1) + { + mode(Mode::kReserved); + } + else + { + P_InstaThrust(this, th, FixedHypot(momx, momy)); + } + } + + if (fly() != oldFly) + { + switch (fly()) + { + case Fly::kNormal: + break; + + case Fly::kZoom: + S_StartSound(this, sfx_fbird); + break; + + case Fly::kSlow: + S_StartSound(this, sfx_fbost1); + break; + } + } + } + + void rise() + { + P_SetObjectMomZ(this, kRiseSpeed * FRACUNIT, false); + } + + void damage(mobj_t* mobj) + { + if (!mobj->player) + { + return; + } + + if (mobj != chasing()) + { + return; + } + + if (mode() != Mode::kHunting) + { + return; + } + + if (P_DamageMobj(mobj, this, source(), 1, DMG_NORMAL)) + { + P_InstaThrust(mobj, K_MomentumAngleReal(this), FixedHypot(momx, momy)); + K_StumblePlayer(mobj->player); + + mobj->player->spinouttimer = 1; // need invulnerability for one tic + + P_SetTarget(&mobj->player->flickyAttacker, this); + + controller()->mode(Controller::Mode::kAttached); + } + + S_StartSound(this, sfx_supflk); + } + + void reflect() + { + momx = -(momx); + momy = -(momy); + } + + void nerf() + { + light_up(false); + + flags &= ~(MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT); + } + + void whip() + { + reflect(); + P_SetObjectMomZ(this, 8*FRACUNIT, false); + + nerf(); + + mode(Mode::kStunned); + delay(kStunTime); + + S_StartSound(this, sfx_fhurt1); + } + + void block() + { + reflect(); + + mode(Mode::kStunned); + delay(kBlockTime); + } + + void land() + { + flags |= MF_NOGRAVITY; + + mode(Mode::kWeak); + } +}; + +Flicky* Controller::flicky() const +{ + return static_cast(controller_flicky(this)); +} + +void Controller::flicky(Flicky* n) +{ + P_SetTarget(&controller_flicky(this), n); +} + +void Controller::search() +{ + if (ending()) + { + return; + } + + fixed_t nearestDistance = INT32_MAX; + mobj_t* nearestMobj = nullptr; + + mobj_t* origin = chasing() ? chasing() : source(); + + for (int i = 0; i < MAXPLAYERS; ++i) + { + player_t* player = &players[i]; + mobj_t* mobj = player->mo; + + if (!playeringame[i] || P_MobjWasRemoved(mobj)) + { + continue; + } + + // Do not retarget existing target or owner. + if (mobj == chasing() || mobj == source()) + { + continue; + } + + // Target is already being hunted. + if (player->flickyAttacker) + { + continue; + } + + if (player->respawn.state != RESPAWNST_NONE) + { + continue; + } + + fixed_t dist = FixedHypot(origin->x - mobj->x, origin->y - mobj->y); + + if (dist < kSearchRadius * mapobjectscale && dist < nearestDistance) + { + nearestDistance = dist; + nearestMobj = mobj; + } + } + + if (nearestMobj) + { + if (chasing() && flicky()) + { + // Detach flicky from swarm. This one keeps its previous target. + flicky(flicky()->next()); + } + + chasing(nearestMobj); + mode(Mode::kEnRoute); + + // Update entire swarm + for (Flicky* x = flicky(); x; x = x->next()) + { + x->next_target(chasing()); + x->delay(x->phase() * kDelay); + } + } +} + +}; // namespace + +void Obj_SpawnSuperFlickySwarm(player_t* owner, tic_t time) +{ + Controller* controller = Controller::spawn(owner, time); + + Flicky::spawn(controller, 0); + Flicky::spawn(controller, 1); + Flicky::spawn(controller, 2); + Flicky::spawn(controller, 3); +} + +void Obj_SuperFlickyThink(mobj_t* mobj) +{ + Flicky* x = static_cast(mobj); + + if (!x->valid()) + { + P_RemoveMobj(x); + return; + } + + x->animate(); + x->refocus(); + + switch (x->mode()) + { + case Flicky::Mode::kReserved: + x->orbit(); + break; + + case Flicky::Mode::kHunting: + x->chase(); + break; + + case Flicky::Mode::kStunned: + break; + + case Flicky::Mode::kWeak: + x->chase(); + break; + } +} + +void Obj_SuperFlickyControllerThink(mobj_t* mobj) +{ + Controller* x = static_cast(mobj); + + if (!x->valid()) + { + P_RemoveMobj(x); + return; + } + + if (x->time_remaining() <= 1) + { + P_RemoveMobj(x); + return; + } + + x->angle += ANGLE_MAX / kOrbitSpeed; + + switch (x->mode()) + { + case Controller::Mode::kDescend: + x->descend(); + x->expand(); + break; + + case Controller::Mode::kOrbit: + x->descend(); + x->expand(); + x->search(); + break; + + case Controller::Mode::kEnRoute: + break; + + case Controller::Mode::kAttached: + x->search(); + break; + + case Controller::Mode::kReturning: + x->descend(); + break; + } +} + +void Obj_WhipSuperFlicky(mobj_t* t1) +{ + Flicky* x = static_cast(t1); + + if (x->valid()) + { + x->whip(); + } +} + +void Obj_BlockSuperFlicky(mobj_t* t1) +{ + Flicky* x = static_cast(t1); + + if (x->valid()) + { + x->block(); + } +} + +void Obj_SuperFlickyPlayerCollide(mobj_t* t1, mobj_t* t2) +{ + Flicky* x = static_cast(t1); + + if (x->valid()) + { + x->damage(t2); + } +} + +void Obj_SuperFlickyLanding(mobj_t* mobj) +{ + Flicky* x = static_cast(mobj); + + if (x->valid()) + { + x->land(); + } +} + +void Obj_EndSuperFlickySwarm(mobj_t* mobj) +{ + Controller* x = static_cast(mobj); + + if (x->valid()) + { + x->end(); + } +} + +void Obj_ExtendSuperFlickySwarm(mobj_t* mobj, tic_t time) +{ + Controller* x = static_cast(mobj); + + x->expiry(x->expiry() + time); +} + +tic_t Obj_SuperFlickySwarmTime(const mobj_t* mobj) +{ + const Controller* x = static_cast(mobj); + + return x ? x->powerup_remaining() : 0u; +} + +boolean Obj_IsSuperFlickyWhippable(const mobj_t* mobj) +{ + const Flicky* x = static_cast(mobj); + + return !x->stunned(); +} diff --git a/src/p_inter.c b/src/p_inter.c index c771b3427..6a6aabde9 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -584,6 +584,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) Obj_PlayerUsedRingShooter(special, player); return; + case MT_SUPER_FLICKY: + Obj_SuperFlickyPlayerCollide(special, toucher); + return; + default: // SOC or script pickup P_SetTarget(&special->target, toucher); break; @@ -2258,8 +2262,16 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (clash) { player->spheres = max(player->spheres - 10, 0); + if (inflictor) + { K_DoPowerClash(target, inflictor); + + if (inflictor->type == MT_SUPER_FLICKY) + { + Obj_BlockSuperFlicky(inflictor); + } + } else if (source) K_DoPowerClash(target, source); } diff --git a/src/p_mobj.c b/src/p_mobj.c index 054efcbb1..7f596338d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -2464,6 +2464,12 @@ boolean P_ZMovement(mobj_t *mo) return false; } } + else if (mo->type == MT_SUPER_FLICKY) + { + mom.z = -mom.z; + + Obj_SuperFlickyLanding(mo); + } else if (mo->type == MT_DRIFTCLIP) { mom.z = -mom.z/2; @@ -6684,6 +6690,14 @@ static void P_MobjSceneryThink(mobj_t *mobj) case MT_INSTAWHIP_RECHARGE: Obj_InstaWhipRechargeThink(mobj); + if (P_MobjWasRemoved(mobj)) + { + return; + } + break; + case MT_SUPER_FLICKY_CONTROLLER: + Obj_SuperFlickyControllerThink(mobj); + if (P_MobjWasRemoved(mobj)) { return; @@ -9533,6 +9547,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_GACHABOM_REBOUND: Obj_GachaBomReboundThink(mobj); + if (P_MobjWasRemoved(mobj)) + { + return false; + } + break; + case MT_SUPER_FLICKY: + Obj_SuperFlickyThink(mobj); + if (P_MobjWasRemoved(mobj)) { return false; From b9820e3264baceca485a0bbc1448ea95f202cf47 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:46:30 -0700 Subject: [PATCH 05/11] Whip Super Flicky --- src/k_collide.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/k_collide.cpp b/src/k_collide.cpp index ff193d26e..41f029bbe 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -898,6 +898,19 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) } return false; } + else if (victim->type == MT_SUPER_FLICKY) + { + if (Obj_IsSuperFlickyWhippable(victim)) + { + K_AddHitLag(victim, victimHitlag, true); + K_AddHitLag(attacker, attackerHitlag, false); + shield->hitlag = attacker->hitlag; + + Obj_WhipSuperFlicky(victim); + return true; + } + return false; + } else { if (victim->type == MT_ORBINAUT || victim->type == MT_JAWZ || victim->type == MT_GACHABOM From bef55b818b09ab555f0fd533bf4a966edd5c6714 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:38:00 -0700 Subject: [PATCH 06/11] Add power-up constants --- src/d_player.h | 11 ++++++++++- src/deh_tables.c | 6 ++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/d_player.h b/src/d_player.h index a360f41b7..a8da43220 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -179,7 +179,16 @@ typedef enum KRITEM_DUALJAWZ, KRITEM_TRIPLEGACHABOM, - NUMKARTRESULTS + NUMKARTRESULTS, + + // Power-ups exist in the same enum as items so it's easy + // for paper items to be reused for them. + FIRSTPOWERUP, + POWERUP_SMONITOR = FIRSTPOWERUP, + POWERUP_BARRIER, + POWERUP_BUMPER, + POWERUP_BADGE, + POWERUP_SUPERFLICKY, } kartitems_t; typedef enum diff --git a/src/deh_tables.c b/src/deh_tables.c index 9c46ebc93..31cf88e30 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6861,6 +6861,12 @@ struct int_const_s const INT_CONST[] = { {"KRITEM_DUALJAWZ",KRITEM_DUALJAWZ}, {"KRITEM_TRIPLEGACHABOM",KRITEM_TRIPLEGACHABOM}, {"NUMKARTRESULTS",NUMKARTRESULTS}, + {"FIRSTPOWERUP",FIRSTPOWERUP}, + {"POWERUP_SMONITOR",POWERUP_SMONITOR}, + {"POWERUP_BARRIER",POWERUP_BARRIER}, + {"POWERUP_BUMPER",POWERUP_BUMPER}, + {"POWERUP_BADGE",POWERUP_BADGE}, + {"POWERUP_SUPERFLICKY",POWERUP_SUPERFLICKY}, // kartshields_t {"KSHIELD_NONE",KSHIELD_NONE}, From 58cccedd642f5e7fb13e778a1e14d5d8f89d22f0 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:23:34 -0700 Subject: [PATCH 07/11] MT_FLOATINGITEM: use K_UpdateMobjItemOverlay --- src/p_mobj.c | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 7f596338d..5f1012708 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7211,29 +7211,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } - switch (mobj->threshold) + if (mobj->threshold == KITEM_SPB || mobj->threshold == KITEM_SHRINK) { - case KITEM_ORBINAUT: - mobj->sprite = SPR_ITMO; - mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetOrbinautItemFrame(mobj->movecount); - break; - case KITEM_INVINCIBILITY: - mobj->sprite = SPR_ITMI; - mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|K_GetInvincibilityItemFrame(); - break; - case KITEM_SAD: - mobj->sprite = SPR_ITEM; - mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; - break; - case KITEM_SPB: - case KITEM_SHRINK: - K_SetItemCooldown(mobj->threshold, 20*TICRATE); - /* FALLTHRU */ - default: - mobj->sprite = SPR_ITEM; - mobj->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(mobj->threshold); - break; + K_SetItemCooldown(mobj->threshold, 20*TICRATE); } + + K_UpdateMobjItemOverlay(mobj, mobj->threshold, mobj->movecount); break; } case MT_ITEMCAPSULE: From a0b0891009481ccce917083d9e5c15e98bb852d4 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:08:47 -0700 Subject: [PATCH 08/11] Add K_DropPaperItem, split from K_DropItems but with custom item type and amount --- src/k_kart.c | 26 ++++++++++++++++++-------- src/k_kart.h | 1 + 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 64f54942e..122b80964 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6599,20 +6599,30 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 return drop; } +void K_DropPaperItem(player_t *player, UINT8 itemtype, UINT16 itemamount) +{ + if (!player->mo || P_MobjWasRemoved(player->mo)) + { + return; + } + + mobj_t *drop = K_CreatePaperItem( + player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, + player->mo->angle + ANGLE_90, P_MobjFlip(player->mo), + itemtype, itemamount + ); + + K_FlipFromObject(drop, player->mo); +} + // For getting EXTRA hit! void K_DropItems(player_t *player) { K_DropHnextList(player); - if (player->mo && !P_MobjWasRemoved(player->mo) && player->itemamount > 0) + if (player->itemamount > 0) { - mobj_t *drop = K_CreatePaperItem( - player->mo->x, player->mo->y, player->mo->z + player->mo->height/2, - player->mo->angle + ANGLE_90, P_MobjFlip(player->mo), - player->itemtype, player->itemamount - ); - - K_FlipFromObject(drop, player->mo); + K_DropPaperItem(player, player->itemtype, player->itemamount); } K_StripItems(player); diff --git a/src/k_kart.h b/src/k_kart.h index af7d142d2..3235d539a 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -151,6 +151,7 @@ void K_KartUpdatePosition(player_t *player); void K_UpdateAllPlayerPositions(void); SINT8 K_GetTotallyRandomResult(UINT8 useodds); mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); +void K_DropPaperItem(player_t *player, UINT8 itemtype, UINT16 itemamount); void K_PopPlayerShield(player_t *player); void K_DropItems(player_t *player); void K_DropRocketSneaker(player_t *player); From 919c6698636b122f8cbf3d6c1e80eeb1d00d0716 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:43:55 -0700 Subject: [PATCH 09/11] Drop power-ups when whipped - Dropped power-ups become paper items (overloaded to store power-ups instead of items). - The dropped power-up stores its remaining duration. - The power-up can be picked up during any state. - If you already have the same kind of power-up, the duration is simply extended. --- src/CMakeLists.txt | 1 + src/info.c | 1 + src/info.h | 1 + src/k_battle.h | 1 + src/k_collide.cpp | 3 +++ src/k_kart.c | 37 +++++++++++++++++++++++++--------- src/k_kart.h | 2 +- src/k_powerup.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++ src/k_powerup.h | 19 ++++++++++++++++++ src/p_inter.c | 22 +++++++++++++------- 10 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 src/k_powerup.cpp create mode 100644 src/k_powerup.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa5697997..24b59ae08 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,6 +146,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_serverstats.c k_zvote.c k_mapuser.c + k_powerup.cpp ) if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) diff --git a/src/info.c b/src/info.c index 61da58f39..c0e95fa73 100644 --- a/src/info.c +++ b/src/info.c @@ -648,6 +648,7 @@ char sprnames[NUMSPRITES + 1][5] = "ITMO", "ITMI", "ITMN", + "PWRB", "WANT", "PBOM", // player bomb diff --git a/src/info.h b/src/info.h index ea4df4be3..50486d2b3 100644 --- a/src/info.h +++ b/src/info.h @@ -1201,6 +1201,7 @@ typedef enum sprite SPR_ITMO, SPR_ITMI, SPR_ITMN, + SPR_PWRB, SPR_WANT, SPR_PBOM, // player bomb diff --git a/src/k_battle.h b/src/k_battle.h index 4e1dda2c8..bd15f8b55 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -10,6 +10,7 @@ extern "C" { #define BATTLE_SPAWN_INTERVAL (4*TICRATE) #define BATTLE_DESPAWN_TIME (15*TICRATE) +#define BATTLE_POWERUP_TIME (20*TICRATE) extern struct battleovertime { diff --git a/src/k_collide.cpp b/src/k_collide.cpp index 41f029bbe..cc3dd492d 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -16,6 +16,7 @@ #include "k_objects.h" #include "k_roulette.h" #include "k_podium.h" +#include "k_powerup.h" angle_t K_GetCollideAngle(mobj_t *t1, mobj_t *t2) { @@ -888,6 +889,8 @@ boolean K_InstaWhipCollide(mobj_t *shield, mobj_t *victim) P_PlayerRingBurst(victimPlayer, 5); P_DamageMobj(victim, shield, attacker, 1, DMG_STUMBLE); // There's a special exception in P_DamageMobj for type==MT_INSTAWHIP + K_DropPowerUps(victimPlayer); + angle_t thrangle = ANGLE_180 + R_PointToAngle2(victim->x, victim->y, shield->x, shield->y); P_Thrust(victim, thrangle, FRACUNIT*10); diff --git a/src/k_kart.c b/src/k_kart.c index 122b80964..40527d844 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6532,13 +6532,26 @@ SINT8 K_GetTotallyRandomResult(UINT8 useodds) return i; } -mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount) +mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount) { mobj_t *drop = P_SpawnMobj(x, y, z, MT_FLOATINGITEM); - mobj_t *backdrop = P_SpawnMobjFromMobj(drop, 0, 0, 0, MT_OVERLAY); - P_SetTarget(&backdrop->target, drop); - P_SetMobjState(backdrop, S_ITEMBACKDROP); + // FIXME: due to linkdraw sucking major ass, I was unable + // to make a backdrop render behind dropped power-ups + // (which use a smaller sprite than normal items). So + // dropped power-ups have the backdrop baked into the + // sprite for now. + if (type < FIRSTPOWERUP) + { + mobj_t *backdrop = P_SpawnMobjFromMobj(drop, 0, 0, 0, MT_OVERLAY); + + P_SetTarget(&backdrop->target, drop); + P_SetMobjState(backdrop, S_ITEMBACKDROP); + + backdrop->dispoffset = 1; + P_SetTarget(&backdrop->tracer, drop); + backdrop->flags2 |= MF2_LINKDRAW; + } P_SetScale(drop, drop->scale>>4); drop->destscale = (3*drop->destscale)/2; @@ -6587,9 +6600,6 @@ mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 } drop->flags |= MF_NOCLIPTHING; - backdrop->dispoffset = 1; - P_SetTarget(&backdrop->tracer, drop); - backdrop->flags2 |= MF2_LINKDRAW; if (gametyperules & GTR_CLOSERPLAYERS) { @@ -12004,8 +12014,17 @@ void K_UpdateMobjItemOverlay(mobj_t *part, SINT8 itemType, UINT8 itemCount) part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE; break; default: - part->sprite = SPR_ITEM; - part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); + if (itemType >= FIRSTPOWERUP) + { + part->sprite = SPR_PWRB; + // Not a papersprite. See K_CreatePaperItem for why. + part->frame = FF_FULLBRIGHT|(itemType - FIRSTPOWERUP); + } + else + { + part->sprite = SPR_ITEM; + part->frame = FF_FULLBRIGHT|FF_PAPERSPRITE|(itemType); + } break; } } diff --git a/src/k_kart.h b/src/k_kart.h index 3235d539a..9c9d876d8 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -150,7 +150,7 @@ void K_SpawnDriftElectricSparks(player_t *player, int color, boolean shockwave); void K_KartUpdatePosition(player_t *player); void K_UpdateAllPlayerPositions(void); SINT8 K_GetTotallyRandomResult(UINT8 useodds); -mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT8 amount); +mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount); void K_DropPaperItem(player_t *player, UINT8 itemtype, UINT16 itemamount); void K_PopPlayerShield(player_t *player); void K_DropItems(player_t *player); diff --git a/src/k_powerup.cpp b/src/k_powerup.cpp new file mode 100644 index 000000000..f646ec4f7 --- /dev/null +++ b/src/k_powerup.cpp @@ -0,0 +1,50 @@ +/// \brief Battle mode power-up code + +#include "k_kart.h" +#include "k_objects.h" +#include "k_powerup.h" + +tic_t K_PowerUpRemaining(const player_t* player, kartitems_t powerup) +{ + switch (powerup) + { + case POWERUP_SUPERFLICKY: + return Obj_SuperFlickySwarmTime(player->powerup.flickyController); + + default: + return 0u; + } +} + +void K_GivePowerUp(player_t* player, kartitems_t powerup, tic_t time) +{ + switch (powerup) + { + case POWERUP_SUPERFLICKY: + if (K_PowerUpRemaining(player, POWERUP_SUPERFLICKY)) + { + Obj_ExtendSuperFlickySwarm(player->powerup.flickyController, time); + } + else + { + Obj_SpawnSuperFlickySwarm(player, time); + } + break; + + default: + break; + } +} + +void K_DropPowerUps(player_t* player) +{ + if (K_PowerUpRemaining(player, POWERUP_SUPERFLICKY)) + { + mobj_t* swarm = player->powerup.flickyController; + + // Be sure to measure the remaining time before ending the power-up + K_DropPaperItem(player, POWERUP_SUPERFLICKY, Obj_SuperFlickySwarmTime(swarm)); + + Obj_EndSuperFlickySwarm(swarm); + } +} diff --git a/src/k_powerup.h b/src/k_powerup.h new file mode 100644 index 000000000..736ce68fe --- /dev/null +++ b/src/k_powerup.h @@ -0,0 +1,19 @@ +#ifndef __K_POWERUP__ +#define __K_POWERUP__ + +#include "doomtype.h" +#include "d_player.h" + +#ifdef __cplusplus +extern "C" { +#endif + +tic_t K_PowerUpRemaining(const player_t *player, kartitems_t powerup); +void K_GivePowerUp(player_t *player, kartitems_t powerup, tic_t timer); +void K_DropPowerUps(player_t *player); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __K_POWERUP__ diff --git a/src/p_inter.c b/src/p_inter.c index 6a6aabde9..0a659f255 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -41,6 +41,7 @@ #include "k_roulette.h" #include "k_boss.h" #include "acs/interface.h" +#include "k_powerup.h" // CTF player names #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" @@ -276,14 +277,21 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) P_InstaThrust(player->mo, player->mo->angle, 20<itemamount && player->itemtype != special->threshold)) - return; - - player->itemtype = special->threshold; - if ((UINT16)(player->itemamount) + special->movecount > 255) - player->itemamount = 255; + if (special->threshold >= FIRSTPOWERUP) + { + K_GivePowerUp(player, special->threshold, special->movecount); + } else - player->itemamount += special->movecount; + { + if (!P_CanPickupItem(player, 3) || (player->itemamount && player->itemtype != special->threshold)) + return; + + player->itemtype = special->threshold; + if ((UINT16)(player->itemamount) + special->movecount > 255) + player->itemamount = 255; + else + player->itemamount += special->movecount; + } S_StartSound(special, special->info->deathsound); From 996f01f4182b83407b3088585fa9f3f95eb6833f Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:47:29 -0700 Subject: [PATCH 10/11] Add K_PowerUpRemaining, K_GivePowerUp, K_DropPowerUps to Lua --- src/lua_baselib.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 47d0e5994..ef76bfb89 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -36,6 +36,7 @@ #include "p_spec.h" // P_StartQuake #include "i_system.h" // I_GetPreciseTime, I_GetPrecisePrecision #include "hu_stuff.h" // for the cecho +#include "k_powerup.h" #include "lua_script.h" #include "lua_libs.h" @@ -3883,6 +3884,40 @@ static int lib_kAddHitLag(lua_State *L) } +static int lib_kPowerUpRemaining(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + kartitems_t powerup = luaL_checkinteger(L, 2); + //HUDSAFE + if (!player) + return LUA_ErrInvalid(L, "player_t"); + lua_pushinteger(L, K_PowerUpRemaining(player, powerup)); + return 1; +} + +static int lib_kGivePowerUp(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + kartitems_t powerup = luaL_checkinteger(L, 2); + tic_t time = (tic_t)luaL_checkinteger(L, 3); + NOHUD + if (!player) + return LUA_ErrInvalid(L, "player_t"); + K_GivePowerUp(player, powerup, time); + return 0; +} + +static int lib_kDropPowerUps(lua_State *L) +{ + player_t *player = *((player_t **)luaL_checkudata(L, 1, META_PLAYER)); + NOHUD + if (!player) + return LUA_ErrInvalid(L, "player_t"); + K_DropPowerUps(player); + return 0; +} + + static int lib_kInitBossHealthBar(lua_State *L) { const char *enemyname = luaL_checkstring(L, 1); @@ -4211,6 +4246,11 @@ static luaL_Reg lib[] = { {"K_GetCollideAngle",lib_kGetCollideAngle}, {"K_AddHitLag",lib_kAddHitLag}, + // k_powerup + {"K_PowerUpRemaining",lib_kPowerUpRemaining}, + {"K_GivePowerUp",lib_kGivePowerUp}, + {"K_DropPowerUps",lib_kDropPowerUps}, + // k_boss {"K_InitBossHealthBar", lib_kInitBossHealthBar}, {"K_UpdateBossHealthBar", lib_kUpdateBossHealthBar}, From 7130435667ad6ca5e681763fbb8e5a36c5dbe6f6 Mon Sep 17 00:00:00 2001 From: James R Date: Tue, 27 Jun 2023 00:39:10 -0700 Subject: [PATCH 11/11] Let give command give power-ups, amount = power-up duration in tics --- src/d_netcmd.c | 38 +++++++++++++++++++++++++++++++++++++- src/m_cheat.h | 1 + 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f391e83c1..77d69003c 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -67,6 +67,7 @@ #include "k_vote.h" #include "k_zvote.h" #include "k_bot.h" +#include "k_powerup.h" #ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES #include "m_avrecorder.h" @@ -440,6 +441,11 @@ static CV_PossibleValue_t kartdebugitem_cons_t[] = #define FOREACH( name, n ) { n, #name } KART_ITEM_ITERATOR, #undef FOREACH + {POWERUP_SMONITOR, "SMonitor"}, + {POWERUP_BARRIER, "Barrier"}, + {POWERUP_BUMPER, "Bumper"}, + {POWERUP_BADGE, "Badge"}, + {POWERUP_SUPERFLICKY, "SuperFlicky"}, {0} }; consvar_t cv_kartdebugitem = CVAR_INIT ("debugitem", "None", CV_NETVAR|CV_CHEAT, kartdebugitem_cons_t, NULL); @@ -2072,6 +2078,11 @@ void D_Cheat(INT32 playernum, INT32 cheat, ...) COPY(WRITEUINT8, unsigned int); break; + case CHEAT_GIVEPOWERUP: + COPY(WRITEUINT8, unsigned int); + COPY(WRITEUINT16, unsigned int); + break; + case CHEAT_SCORE: COPY(WRITEUINT32, UINT32); break; @@ -6112,6 +6123,20 @@ static void Got_Cheat(UINT8 **cp, INT32 playernum) break; } + case CHEAT_GIVEPOWERUP: { + UINT8 powerup = READUINT8(*cp); + UINT16 time = READUINT16(*cp); + + // FIXME: we should have actual KITEM_ name array + const char *powerupname = cv_kartdebugitem.PossibleValue[ + 1 + NUMKARTITEMS + (powerup - FIRSTPOWERUP)].strvalue; + + K_GivePowerUp(player, powerup, time); + + CV_CheaterWarning(playernum, va("give powerup %s %d tics", powerupname, time)); + break; + } + case CHEAT_SCORE: { UINT32 score = READUINT32(*cp); @@ -6419,7 +6444,18 @@ static void Command_KartGiveItem_f(void) } } - if (item < NUMKARTITEMS) + if (item >= FIRSTPOWERUP) + { + INT32 amt; + + if (ac > 2) + amt = atoi(COM_Argv(2)); + else + amt = BATTLE_POWERUP_TIME; + + D_Cheat(consoleplayer, CHEAT_GIVEPOWERUP, item, amt); + } + else if (item < NUMKARTITEMS) { INT32 amt; diff --git a/src/m_cheat.h b/src/m_cheat.h index d8b21d477..bf7fc79be 100644 --- a/src/m_cheat.h +++ b/src/m_cheat.h @@ -39,6 +39,7 @@ typedef enum { CHEAT_SCORE, CHEAT_ANGLE, CHEAT_RESPAWNAT, + CHEAT_GIVEPOWERUP, NUMBER_OF_CHEATS } cheat_t;