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/d_netcmd.c b/src/d_netcmd.c index e2ce5c178..bb1efe6f2 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" @@ -442,6 +443,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); @@ -2074,6 +2080,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; @@ -6133,6 +6144,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); @@ -6440,7 +6465,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/d_player.h b/src/d_player.h index 2f6d3ca74..843f31b65 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 @@ -451,6 +460,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 { @@ -768,6 +782,7 @@ struct player_t mobj_t *sliptideZipIndicator; mobj_t *whip; mobj_t *hand; + mobj_t *flickyAttacker; UINT8 instaShieldCooldown; UINT8 guardCooldown; @@ -792,6 +807,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/deh_tables.c b/src/deh_tables.c index 8f99be9bf..6aa24dfad 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4582,6 +4582,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", @@ -5708,6 +5710,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[] = { @@ -6867,6 +6872,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}, diff --git a/src/info.c b/src/info.c index 0a081d08f..3d1ec5bb8 100644 --- a/src/info.c +++ b/src/info.c @@ -649,6 +649,7 @@ char sprnames[NUMSPRITES + 1][5] = "ITMO", "ITMI", "ITMN", + "PWRB", "WANT", "PBOM", // player bomb @@ -816,6 +817,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", }; @@ -5247,6 +5250,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] = @@ -29806,6 +29811,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 fb725f23f..8d3e85031 100644 --- a/src/info.h +++ b/src/info.h @@ -1200,6 +1200,7 @@ typedef enum sprite SPR_ITMO, SPR_ITMI, SPR_ITMN, + SPR_PWRB, SPR_WANT, SPR_PBOM, // player bomb @@ -1367,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, @@ -5678,6 +5681,8 @@ typedef enum state S_GACHABOM_WAITING, S_GACHABOM_RETURNING, + S_SUPER_FLICKY, + S_FIRSTFREESLOT, S_LASTFREESLOT = S_FIRSTFREESLOT + NUMSTATEFREESLOTS - 1, NUMSTATES @@ -6823,6 +6828,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 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 ff193d26e..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); @@ -898,6 +901,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 diff --git a/src/k_kart.c b/src/k_kart.c index cf36a300e..2adcfa4ea 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -6533,13 +6533,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; @@ -6588,9 +6601,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) { @@ -6600,20 +6610,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); @@ -8288,6 +8308,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); @@ -12009,8 +12035,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 af7d142d2..9c9d876d8 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -150,7 +150,8 @@ 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); void K_DropRocketSneaker(player_t *player); diff --git a/src/k_objects.h b/src/k_objects.h index 52e29275a..cd645a1e7 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" @@ -144,6 +146,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/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/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}, 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; 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 7da79bf31..e2c5ed6fc 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") : "" @@ -332,14 +333,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); @@ -648,6 +656,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; @@ -2322,8 +2334,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 803559ef9..6592fab7b 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; @@ -7197,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: @@ -9533,6 +9530,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; diff --git a/src/p_saveg.c b/src/p_saveg.c index 3f075fc2d..d759da6af 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); @@ -757,6 +771,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); @@ -5264,6 +5284,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/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, 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);