From b610353699decaad09d24583656e33ec6e1c4608 Mon Sep 17 00:00:00 2001
From: Sally Coolatta <tehrealsalt@gmail.com>
Date: Mon, 29 Apr 2024 10:22:24 -0400
Subject: [PATCH] Add follower ring animation

Add "RingState" and "RingTime" to followers. RingState is the state to use while they're giving you an auto-ring. The animation plays for RingTime tics per-ring, so it can extend to however long they need to use rings.
---
 src/deh_soc.c    | 13 +++++++++++++
 src/k_follower.c | 17 ++++++++++++++++-
 src/k_follower.h |  3 +++
 src/k_kart.c     | 10 +++++++++-
 4 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/src/deh_soc.c b/src/deh_soc.c
index b7ccb9187c..d0889e9117 100644
--- a/src/deh_soc.c
+++ b/src/deh_soc.c
@@ -4152,6 +4152,7 @@ void readfollower(MYFILE *f)
 	followers[numfollowers].bobspeed = TICRATE*2;
 	followers[numfollowers].bobamp = 4*FRACUNIT;
 	followers[numfollowers].hitconfirmtime = TICRATE;
+	followers[numfollowers].ringtime = 6;
 	followers[numfollowers].defaultcolor = FOLLOWERCOLOR_MATCH;
 	followers[numfollowers].category = UINT8_MAX;
 	followers[numfollowers].hornsound = sfx_horn00;
@@ -4329,6 +4330,16 @@ void readfollower(MYFILE *f)
 			{
 				followers[numfollowers].hitconfirmtime = (tic_t)get_number(word2);
 			}
+			else if (fastcmp(word, "RINGSTATE"))
+			{
+				if (word2)
+					strupr(word2);
+				followers[numfollowers].ringstate = get_number(word2);
+			}
+			else if (fastcmp(word, "RINGTIME"))
+			{
+				followers[numfollowers].ringtime = (tic_t)get_number(word2);
+			}
 			else
 			{
 				deh_warning("Follower %d: unknown word '%s'", numfollowers, word);
@@ -4386,6 +4397,7 @@ if ((signed)followers[numfollowers].field < threshold) \
 	FALLBACK(bobamp, "BOBAMP", 0, 0);
 	FALLBACK(bobspeed, "BOBSPEED", 0, 0);
 	FALLBACK(hitconfirmtime, "HITCONFIRMTIME", 1, 1);
+	FALLBACK(ringtime, "RINGTIME", 1, 1);
 	FALLBACK(scale, "SCALE", 1, 1);				// No null/negative scale
 	FALLBACK(bubblescale, "BUBBLESCALE", 0, 0);	// No negative scale
 
@@ -4408,6 +4420,7 @@ if (!followers[numfollowers].field) \
 	NOSTATE(losestate, "LOSESTATE");
 	NOSTATE(winstate, "WINSTATE");
 	NOSTATE(hitconfirmstate, "HITCONFIRMSTATE");
+	NOSTATE(ringstate, "RINGSTATE");
 #undef NOSTATE
 
 	if (!followers[numfollowers].hornsound)
diff --git a/src/k_follower.c b/src/k_follower.c
index 1617c8d456..715c44956e 100644
--- a/src/k_follower.c
+++ b/src/k_follower.c
@@ -537,6 +537,15 @@ void K_HandleFollower(player_t *player)
 			destAngle += ANGLE_180;
 		}
 
+		// Using auto-ring, face towards the player while throwing your rings.
+		if (player->follower->cvmem)
+		{
+			destAngle = R_PointToAngle2(
+				player->follower->x, player->follower->y,
+				player->mo->x, player->mo->y
+			);
+		}
+
 		// Sal: Smoothly rotate angle to the destination value.
 		angleDiff = AngleDeltaSigned(destAngle, player->follower->angle);
 
@@ -628,8 +637,9 @@ void K_HandleFollower(player_t *player)
 		// hurt or dead
 		if (P_PlayerInPain(player) == true || player->mo->state == &states[S_KART_SPINOUT] || player->mo->health <= 0)
 		{
-			// cancel hit confirm.
+			// cancel hit confirm / rings
 			player->follower->movecount = 0;
+			player->follower->cvmem = 0;
 
 			// spin out
 			player->follower->angle = player->drawangle;
@@ -661,6 +671,11 @@ void K_HandleFollower(player_t *player)
 			K_UpdateFollowerState(player->follower, fl->hitconfirmstate, FOLLOWERSTATE_HITCONFIRM);
 			player->follower->movecount--;
 		}
+		else if (player->follower->cvmem)
+		{
+			K_UpdateFollowerState(player->follower, fl->ringstate, FOLLOWERSTATE_RING);
+			player->follower->cvmem--;
+		}
 		else if (player->speed > 10*player->mo->scale) // animation for moving fast enough
 		{
 			K_UpdateFollowerState(player->follower, fl->followstate, FOLLOWERSTATE_FOLLOW);
diff --git a/src/k_follower.h b/src/k_follower.h
index dc3c6cb8b8..579532d4ab 100644
--- a/src/k_follower.h
+++ b/src/k_follower.h
@@ -49,6 +49,7 @@ typedef enum
 	FOLLOWERSTATE_WIN,
 	FOLLOWERSTATE_LOSE,
 	FOLLOWERSTATE_HITCONFIRM, // Uses movecount as a timer for how long to play this state.
+	FOLLOWERSTATE_RING, // Uses cvmem as a timer for how long to play this state.
 	FOLLOWERSTATE__MAX
 } followerstate_t;
 
@@ -93,6 +94,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
+	statenum_t ringstate;		// state for giving an auto-ring
+	tic_t ringtime;				// time to keep the above playing for
 
 	sfxenum_t hornsound;		// Press (B) to announce you are pressing (B)
 };
diff --git a/src/k_kart.c b/src/k_kart.c
index 54119254bc..b57ad67077 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -12650,12 +12650,20 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 					ring->shadowscale = 0;
 					P_SetTarget(&ring->target, player->mo); // user
 
-					if (player->follower && !P_MobjWasRemoved(player->follower))
+					if (player->follower != NULL
+						&& P_MobjWasRemoved(player->follower) == false
+						&& player->followerskin >= 0
+						&& player->followerskin < numfollowers)
 					{
 						// TODO: only do when using an auto-ring
+						const follower_t *fl = &followers[player->followerskin];
+
 						ring->cusval = player->follower->x - player->mo->x;
 						ring->cvmem = player->follower->y - player->mo->y;
 						ring->movefactor = P_GetMobjHead(player->follower) - P_GetMobjHead(player->mo);
+
+						// cvmem is used to play the ring animation for followers
+						player->follower->cvmem = fl->ringtime;
 					}
 					else
 					{
-- 
GitLab