From 21849ef1cedb49d56ba52348ae2ae3ba0a534a4f Mon Sep 17 00:00:00 2001 From: toaster Date: Fri, 30 Jan 2026 03:09:11 -0500 Subject: [PATCH] Horncode A much more focused replacement for Hornmod, specc'd out by Tyron and Oni working together and implemented by the author of this commit because it's pretty funny. - Followers have `hornsound` in their SOC configuration. - The default sound for all followers without a provided one is sfx_horn00. - They'll play this sound if you use lookback with one following you, and there's nearby players to get the player looking all the way around. - Only the players who are successfully considered for lookback will hear it. - Has a v1-like visual with less randomisation, but still netsynced. - Also controlled by the cvar `taunthorns`, which, like `tauntvoices`, takes "Tasteful" (default), "Meme", and "Off". TODO: make the condition for horn a little delayed, so you have to hold lookback for a little bit. --- src/d_netcmd.c | 3 +- src/d_netcmd.h | 1 + src/d_player.h | 1 + src/deh_soc.c | 5 ++ src/info/mobjs.h | 3 ++ src/info/sounds.h | 3 ++ src/info/sprites.h | 3 ++ src/info/states.h | 2 + src/k_follower.c | 122 +++++++++++++++++++++++++++++++++++++++++++++ src/k_follower.h | 17 +++++++ src/k_kart.c | 24 ++++++++- src/p_saveg.c | 4 +- 12 files changed, 184 insertions(+), 4 deletions(-) diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f13b53579..3348acaaf 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -436,7 +436,8 @@ consvar_t cv_kartgametypepreference = CVAR_INIT ("kartgametypepreference", "None static CV_PossibleValue_t kartspeedometer_cons_t[] = {{0, "Off"}, {1, "Kilometers"}, {2, "Miles"}, {3, "Fracunits"}, {4, "Percentage"}, {0, NULL}}; consvar_t cv_kartspeedometer = CVAR_INIT ("kartdisplayspeed", "Percentage", CV_SAVE, kartspeedometer_cons_t, NULL); // use tics in display static CV_PossibleValue_t kartvoices_cons_t[] = {{0, "Never"}, {1, "Tasteful"}, {2, "Meme"}, {0, NULL}}; -consvar_t cv_kartvoices = CVAR_INIT ("kartvoices", "Tasteful", CV_SAVE, kartvoices_cons_t, NULL); +consvar_t cv_kartvoices = CVAR_INIT ("tauntvoices", "Tasteful", CV_SAVE, kartvoices_cons_t, NULL); +consvar_t cv_karthorns = CVAR_INIT ("taunthorns", "Tasteful", CV_SAVE, kartvoices_cons_t, NULL); static CV_PossibleValue_t kartbot_cons_t[] = { {0, "Off"}, diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 8b5b1cc66..ad422ee8d 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -88,6 +88,7 @@ extern consvar_t cv_kartvoterulechanges; extern consvar_t cv_kartgametypepreference; extern consvar_t cv_kartspeedometer; extern consvar_t cv_kartvoices; +extern consvar_t cv_karthorns; extern consvar_t cv_kartbot; extern consvar_t cv_kartbot_cap; extern consvar_t cv_kartbot_modifiermax; diff --git a/src/d_player.h b/src/d_player.h index 0e3d9193c..1842884a5 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -206,6 +206,7 @@ typedef enum khud_tauntvoices, // Used to specifically stop taunt voice spam khud_confirmvictim, // Player ID that you dealt damage to khud_confirmvictimdelay, // Delay before playing the sound + khud_taunthorns, // Used to specifically stop taunt horn spam // Battle khud_cardanimation, // Used to determine the position of some full-screen Battle Mode graphics diff --git a/src/deh_soc.c b/src/deh_soc.c index ae4994be6..275c92791 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -3763,6 +3763,7 @@ void readfollower(MYFILE *f) followers[numfollowers].bobamp = 4*FRACUNIT; followers[numfollowers].hitconfirmtime = TICRATE; followers[numfollowers].defaultcolor = FOLLOWERCOLOR_MATCH; + followers[numfollowers].hornsound = sfx_horn00; strcpy(followers[numfollowers].icon, "MISSING"); do @@ -3829,6 +3830,10 @@ void readfollower(MYFILE *f) followers[numfollowers].defaultcolor = color == MAXSKINCOLORS ? SKINCOLOR_GREEN : color; } } + else if (fastcmp(word, "HORNSOUND")) + { + followers[numfollowers].hornsound = get_number(word2); + } else if (fastcmp(word, "SCALE")) { followers[numfollowers].scale = (fixed_t)get_number(word2); diff --git a/src/info/mobjs.h b/src/info/mobjs.h index a45912f63..a7e4a6fcb 100644 --- a/src/info/mobjs.h +++ b/src/info/mobjs.h @@ -874,3 +874,6 @@ _(RAINBOWDASHRING) // Sneaker Panels _(SNEAKERPANEL) _(SNEAKERPANELSPAWNER) + +// Horncode +_(FOLLOWERHORN) diff --git a/src/info/sounds.h b/src/info/sounds.h index 601248ee4..2182f9703 100644 --- a/src/info/sounds.h +++ b/src/info/sounds.h @@ -1017,3 +1017,6 @@ _(sysmsg) // Dash Rings _(dashr) _(rainbr) + +// Horncode +_(horn00) diff --git a/src/info/sprites.h b/src/info/sprites.h index 74b3bfa27..5f8fb0b5e 100644 --- a/src/info/sprites.h +++ b/src/info/sprites.h @@ -656,5 +656,8 @@ _(AWBT) // Recovery Spin Skid _(RCSP) +// Horncode +_(FHRN) + // First person view sprites; this is a sprite so that it can be replaced by a specialized MD2 draw later _(VIEW) diff --git a/src/info/states.h b/src/info/states.h index 7440fdeff..f4c659848 100644 --- a/src/info/states.h +++ b/src/info/states.h @@ -3624,3 +3624,5 @@ _(ALTSHRINK_ARROWBULLET) // Recovery Spin Skid _(RECSPIN_SKID) +// Horncode +_(HORNCODE) diff --git a/src/k_follower.c b/src/k_follower.c index b5706319d..58ae90fd0 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -11,6 +11,8 @@ #include "r_skins.h" #include "p_local.h" #include "p_mobj.h" +#include "s_sound.h" +#include "m_cond.h" INT32 numfollowers = 0; follower_t followers[MAXSKINS]; @@ -610,5 +612,125 @@ void K_HandleFollower(player_t *player) { K_UpdateFollowerState(player->follower, fl.idlestate, FOLLOWERSTATE_IDLE); } + + // Horncode + if (P_MobjWasRemoved(player->follower->hprev) == false) + { + mobj_t *honk = player->follower->hprev; + + honk->flags2 &= ~MF2_AMBUSH; + + honk->color = player->skincolor; + + P_MoveOrigin( + honk, + player->follower->x, + player->follower->y, + player->follower->z + player->follower->height + ); + + K_FlipFromObject(honk, player->follower); + + honk->angle = R_PointToAngle2( + player->mo->x, + player->mo->y, + player->follower->x, + player->follower->y + ); + + honk->destscale = (2*player->mo->scale)/3; + + fixed_t offsetamount = 0; + if (honk->fuse > 1) + { + offsetamount = (honk->fuse-1)*honk->destscale/2; + if (leveltime & 1) + offsetamount = -offsetamount; + } + else if (S_SoundPlaying(honk, fl.hornsound)) + { + honk->fuse++; + } + + honk->sprxoff = P_ReturnThrustX(honk, honk->angle, offsetamount); + honk->spryoff = P_ReturnThrustY(honk, honk->angle, offsetamount); + honk->sprzoff = -honk->sprxoff; + } + } +} + +/*-------------------------------------------------- + void K_FollowerHornTaunt(player_t *taunter, player_t *victim) + + See header file for description. +--------------------------------------------------*/ +void K_FollowerHornTaunt(player_t *taunter, player_t *victim) +{ + if ( + (cv_karthorns.value == 0) + || taunter == NULL + || victim == NULL + || taunter->followerskin < 0 + || taunter->followerskin >= numfollowers + || (P_IsLocalPlayer(victim) == false && cv_karthorns.value != 2) + || P_MobjWasRemoved(taunter->mo) == true + || P_MobjWasRemoved(taunter->follower) == true + ) + return; + + const follower_t *fl = &followers[taunter->followerskin]; + + const boolean tasteful = (taunter->karthud[khud_taunthorns] == 0); + + if (tasteful || cv_karthorns.value == 2) + { + mobj_t *honk = taunter->follower->hprev; + const fixed_t desiredscale = (2*taunter->mo->scale)/3; + + if (P_MobjWasRemoved(honk) == true) + { + honk = P_SpawnMobj( + taunter->follower->x, + taunter->follower->y, + taunter->follower->z + taunter->follower->height, + MT_FOLLOWERHORN + ); + + // TODO (yama): Honk icons based on a follower's "emotion" + // (♪ for hitconfirm honks, 💢 for mid-damage honks) + + if (P_MobjWasRemoved(honk) == true) + return; // Permit lua override of horn production + + P_SetTarget(&taunter->follower->hprev, honk); + P_SetTarget(&honk->target, taunter->follower); + + K_FlipFromObject(honk, taunter->follower); + + honk->color = taunter->skincolor; + + honk->angle = honk->old_angle = R_PointToAngle2( + taunter->mo->x, + taunter->mo->y, + taunter->follower->x, + taunter->follower->y + ); + } + + // Only do for the first activation this tic. + if (!(honk->flags2 & MF2_AMBUSH)) + { + honk->destscale = desiredscale; + + P_SetScale(honk, (11*desiredscale)/10); + honk->fuse = TICRATE/2; + honk->renderflags |= RF_DONTDRAW; + + S_StartSound(taunter->follower, fl->hornsound); + + honk->flags2 |= MF2_AMBUSH; + } + + honk->renderflags &= ~K_GetPlayerDontDrawFlag(victim); } } diff --git a/src/k_follower.h b/src/k_follower.h index 41efae7c0..28dae9929 100644 --- a/src/k_follower.h +++ b/src/k_follower.h @@ -89,6 +89,8 @@ struct follower_t statenum_t losestate; // state when the player has lost statenum_t hitconfirmstate; // state for hit confirm tic_t hitconfirmtime; // time to keep the above playing for + + sfxenum_t hornsound; // Press (B) to announce you are pressing (B) }; extern INT32 numfollowers; @@ -179,6 +181,21 @@ UINT16 K_GetEffectiveFollowerColor(UINT16 followercolor, follower_t *follower, U void K_HandleFollower(player_t *player); +/*-------------------------------------------------- + void K_FollowerHornTaunt(player_t *taunter, player_t *victim) + + Plays horn and spawns object (MOSTLY non-netsynced) + + Input Arguments:- + taunter - Source player with a follower + victim - Player that hears and sees the honk + + Return:- + None +--------------------------------------------------*/ + +void K_FollowerHornTaunt(player_t *taunter, player_t *victim); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_kart.c b/src/k_kart.c index 007eec8f4..dcc1a9e94 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -246,6 +246,7 @@ void K_RegisterKartStuff(void) CV_RegisterVar(&cv_kartspeedometer); CV_RegisterVar(&cv_kartvoices); CV_RegisterVar(&cv_karthitemdialog); + CV_RegisterVar(&cv_karthorns); CV_RegisterVar(&cv_kartbot); CV_RegisterVar(&cv_kartbot_cap); CV_RegisterVar(&cv_kartbot_modifiermax); @@ -1541,7 +1542,7 @@ void K_SpawnBumpEffect(mobj_t *mo) S_StartSound(mo, sfx_s3k49); } -static SINT8 K_GlanceAtPlayers(player_t *glancePlayer) +static SINT8 K_GlanceAtPlayers(player_t *glancePlayer, boolean horn) { const fixed_t maxdistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); const angle_t blindSpotSize = ANG10; // ANG5 @@ -1623,9 +1624,24 @@ static SINT8 K_GlanceAtPlayers(player_t *glancePlayer) // That poses a limitation if there's an equal number of targets on both sides... // In that case, we'll pick the last chosen glance direction. lastValidGlance = dir; + + if (horn == true) + { + K_FollowerHornTaunt(glancePlayer, p); + } } } + if (horn == true && lastValidGlance != 0) + { + const boolean tasteful = (glancePlayer->karthud[khud_taunthorns] == 0); + + K_FollowerHornTaunt(glancePlayer, glancePlayer); + + if (tasteful && glancePlayer->karthud[khud_taunthorns] < 2*TICRATE) + glancePlayer->karthud[khud_taunthorns] = 2*TICRATE; + } + if (glanceDir > 0) { return 1; @@ -1783,7 +1799,8 @@ void K_KartMoveAnimation(player_t *player) { // Only try glancing if you're driving straight. // This avoids all-players loops when we don't need it. - destGlanceDir = K_GlanceAtPlayers(player); + const boolean horn = lookback && !(player->pflags & PF_GAINAX); + destGlanceDir = K_GlanceAtPlayers(player, horn); if (lookback == true) { @@ -6400,6 +6417,9 @@ void K_KartPlayerHUDUpdate(player_t *player) if (player->karthud[khud_tauntvoices]) player->karthud[khud_tauntvoices]--; + if (player->karthud[khud_taunthorns]) + player->karthud[khud_taunthorns]--; + if (gametyperules & GTR_RINGS) { if ((K_RingsActive() == true)) diff --git a/src/p_saveg.c b/src/p_saveg.c index d48571bfe..cd3bc4a5b 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -4499,7 +4499,9 @@ void P_SaveNetGame(savebuffer_t *save, boolean resending) mobj = (mobj_t *)th; if (UNLIKELY(mobj->type == MT_HOOP || mobj->type == MT_HOOPCOLLIDE || mobj->type == MT_HOOPCENTER // MT_SPARK: used for debug stuff - || mobj->type == MT_SPARK || mobj->flags2 & MF2_DONTSYNC)) + || mobj->type == MT_SPARK + // MT_FOLLOWERHORN: So it turns out hornmod is fundamentally incompatible with netsync + || mobj->type == MT_FOLLOWERHORN || mobj->flags2 & MF2_DONTSYNC)) continue; mobj->mobjnum = i++; }