From b5334e6b42ae6b4092dc0c68e58431d2c8e13972 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Mon, 23 May 2022 00:23:46 -0400 Subject: [PATCH] Move follower code to its own file --- src/Sourcefile | 1 + src/d_netcmd.c | 17 +- src/deh_soc.c | 57 +++--- src/doomdef.h | 2 +- src/g_demo.c | 5 +- src/k_follower.c | 462 +++++++++++++++++++++++++++++++++++++++++++++++ src/k_follower.h | 133 ++++++++++++++ src/k_kart.c | 3 +- src/m_menu.c | 3 +- src/p_user.c | 291 +---------------------------- src/r_data.h | 3 - src/r_draw.c | 1 - src/r_skins.c | 99 ---------- src/r_skins.h | 47 ----- 14 files changed, 649 insertions(+), 475 deletions(-) create mode 100644 src/k_follower.c create mode 100644 src/k_follower.h diff --git a/src/Sourcefile b/src/Sourcefile index 9c7960081..c1e29e119 100644 --- a/src/Sourcefile +++ b/src/Sourcefile @@ -118,3 +118,4 @@ k_hud.c k_terrain.c k_brightmap.c k_director.c +k_follower.c diff --git a/src/d_netcmd.c b/src/d_netcmd.c index c9a030ab8..8d66a27bc 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -58,6 +58,7 @@ #include "k_respawn.h" #include "k_grandprix.h" #include "k_boss.h" +#include "k_follower.h" #include "doomstat.h" #include "deh_tables.h" @@ -1452,7 +1453,7 @@ static void SendNameAndColor(UINT8 n) // Update follower for local games: if (cv_follower[n].value >= -1 && cv_follower[n].value != player->followerskin) - SetFollower(playernum, cv_follower[n].value); + K_SetFollowerByNum(playernum, cv_follower[n].value); player->followercolor = cv_followercolor[n].value; @@ -1632,11 +1633,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) SetPlayerSkinByNum(playernum, skin); // set follower colour: - // Don't bother doing garbage and kicking if we receive None, this is both silly and a waste of time, this will be handled properly in P_HandleFollower. + // Don't bother doing garbage and kicking if we receive None, + // this is both silly and a waste of time, + // this will be handled properly in K_HandleFollower. p->followercolor = followercolor; // set follower - SetFollower(playernum, follower); + K_SetFollowerByNum(playernum, follower); #ifdef HAVE_DISCORDRPC if (playernum == consoleplayer) @@ -5316,7 +5319,7 @@ static void Follower_OnChange(void) return; } - num = R_FollowerAvailable(str); + num = K_FollowerAvailable(str); if (num == -1) // that's an error. CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); @@ -5370,7 +5373,7 @@ static void Follower2_OnChange(void) return; } - num = R_FollowerAvailable(str); + num = K_FollowerAvailable(str); if (num == -1) // that's an error. CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); @@ -5421,7 +5424,7 @@ static void Follower3_OnChange(void) return; } - num = R_FollowerAvailable(str); + num = K_FollowerAvailable(str); if (num == -1) // that's an error. CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); @@ -5472,7 +5475,7 @@ static void Follower4_OnChange(void) return; } - num = R_FollowerAvailable(str); + num = K_FollowerAvailable(str); if (num == -1) // that's an error. CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); diff --git a/src/deh_soc.c b/src/deh_soc.c index 973daadf1..6fe6ca36b 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -49,6 +49,7 @@ // SRB2Kart #include "filesrch.h" // refreshdirmenu +#include "k_follower.h" // Loops through every constant and operation in word and performs its calculations, returning the final value. fixed_t get_number(const char *word) @@ -3819,15 +3820,15 @@ void readfollower(MYFILE *f) // Ready the default variables for followers. We will overwrite them as we go! We won't set the name or states RIGHT HERE as this is handled down instead. followers[numfollowers].scale = FRACUNIT; - followers[numfollowers].bubblescale = 0; // No bubble by default - followers[numfollowers].atangle = 230; - followers[numfollowers].dist = 32; // changed from 16 to 32 to better account for ogl models - followers[numfollowers].height = 16; - followers[numfollowers].zoffs = 32; - followers[numfollowers].horzlag = 2; - followers[numfollowers].vertlag = 6; + followers[numfollowers].bubblescale = 0; // No bubble by default + followers[numfollowers].atangle = FixedAngle(230 * FRACUNIT); + followers[numfollowers].dist = 32*FRACUNIT; // changed from 16 to 32 to better account for ogl models + followers[numfollowers].height = 16*FRACUNIT; + followers[numfollowers].zoffs = 32*FRACUNIT; + followers[numfollowers].horzlag = 2*FRACUNIT; + followers[numfollowers].vertlag = 6*FRACUNIT; followers[numfollowers].bobspeed = TICRATE*2; - followers[numfollowers].bobamp = 4; + followers[numfollowers].bobamp = 4*FRACUNIT; followers[numfollowers].hitconfirmtime = TICRATE; followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; @@ -3865,48 +3866,47 @@ void readfollower(MYFILE *f) } else if (fastcmp(word, "DEFAULTCOLOR")) { - followers[numfollowers].defaultcolor = (UINT16)get_number(word2); + followers[numfollowers].defaultcolor = get_number(word2); } - else if (fastcmp(word, "SCALE")) { - followers[numfollowers].scale = get_number(word2); + followers[numfollowers].scale = (fixed_t)get_number(word2); } else if (fastcmp(word, "BUBBLESCALE")) { - followers[numfollowers].bubblescale = get_number(word2); + followers[numfollowers].bubblescale = (fixed_t)get_number(word2); } else if (fastcmp(word, "ATANGLE")) { - followers[numfollowers].atangle = (INT32)atoi(word2); + followers[numfollowers].atangle = (angle_t)(get_number(word2) * ANG1); } else if (fastcmp(word, "HORZLAG")) { - followers[numfollowers].horzlag = (INT32)atoi(word2); + followers[numfollowers].horzlag = (fixed_t)get_number(word2); } else if (fastcmp(word, "VERTLAG")) { - followers[numfollowers].vertlag = (INT32)atoi(word2); + followers[numfollowers].vertlag = (fixed_t)get_number(word2); } else if (fastcmp(word, "BOBSPEED")) { - followers[numfollowers].bobspeed = (INT32)atoi(word2); + followers[numfollowers].bobspeed = (tic_t)get_number(word2); } else if (fastcmp(word, "BOBAMP")) { - followers[numfollowers].bobamp = (INT32)atoi(word2); + followers[numfollowers].bobamp = (fixed_t)get_number(word2); } else if (fastcmp(word, "ZOFFSET") || (fastcmp(word, "ZOFFS"))) { - followers[numfollowers].zoffs = (INT32)atoi(word2); + followers[numfollowers].zoffs = (fixed_t)get_number(word2); } else if (fastcmp(word, "DISTANCE") || (fastcmp(word, "DIST"))) { - followers[numfollowers].dist = (INT32)atoi(word2); + followers[numfollowers].dist = (fixed_t)get_number(word2); } else if (fastcmp(word, "HEIGHT")) { - followers[numfollowers].height = (INT32)atoi(word2); + followers[numfollowers].height = (fixed_t)get_number(word2); } else if (fastcmp(word, "IDLESTATE")) { @@ -3947,16 +3947,19 @@ void readfollower(MYFILE *f) } else if (fastcmp(word, "HITTIME") || (fastcmp(word, "HITCONFIRMTIME"))) { - followers[numfollowers].hitconfirmtime = (INT32)atoi(word2); + followers[numfollowers].hitconfirmtime = (tic_t)get_number(word2); } else + { deh_warning("Follower %d: unknown word '%s'", numfollowers, word); + } } } while (!myfeof(f)); // finish when the line is empty - if (!nameset) // well this is problematic. + if (!nameset) { - strcpy(followers[numfollowers].name, va("Follower%d", numfollowers)); // this is lazy, so what + // well this is problematic. + strcpy(followers[numfollowers].name, va("Follower%d", numfollowers)); // this is lazy, so what } // set skin name (this is just the follower's name in lowercases): @@ -3966,7 +3969,7 @@ void readfollower(MYFILE *f) // lower testname for skin checks... strlwr(testname); - res = R_FollowerAvailable(testname); + res = K_FollowerAvailable(testname); if (res > -1) // yikes, someone else has stolen our name already { INT32 startlen = strlen(testname); @@ -3990,7 +3993,7 @@ void readfollower(MYFILE *f) // fallbacks for variables // Print a warning if the variable is on a weird value and set it back to the minimum available if that's the case. #define FALLBACK(field, field2, threshold, set) \ -if (followers[numfollowers].field < threshold) \ +if ((signed)followers[numfollowers].field < threshold) \ { \ followers[numfollowers].field = set; \ deh_warning("Follower '%s': Value for '%s' is too low! Minimum should be %d. Value was overwritten to %d.", dname, field2, set, set); \ @@ -4008,7 +4011,7 @@ if (followers[numfollowers].field < threshold) \ FALLBACK(bubblescale, "BUBBLESCALE", 0, 0); // No negative scale // Special case for color I suppose - if (followers[numfollowers].defaultcolor > numskincolors-1) + if (followers[numfollowers].defaultcolor > (unsigned)(numskincolors-1)) { followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; deh_warning("Follower \'%s\': Value for 'color' should be between 1 and %d.\n", dname, numskincolors-1); @@ -4036,7 +4039,7 @@ if (!followers[numfollowers].field) \ #undef NOSTATE CONS_Printf("Added follower '%s'\n", dname); - numfollowers++; // add 1 follower + numfollowers++; // add 1 follower Z_Free(s); } diff --git a/src/doomdef.h b/src/doomdef.h index 0b7745be2..f36e780e6 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -206,7 +206,7 @@ extern char logfilename[1024]; #define MAXPLAYERNAME 21 #define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer -#define MAXSKINS 128 +#define MAXSKINS UINT8_MAX #define COLORRAMPSIZE 16 #define MAXCOLORNAME 32 diff --git a/src/g_demo.c b/src/g_demo.c index ddf471684..fcc19e224 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -51,6 +51,7 @@ #include "k_respawn.h" #include "k_bot.h" #include "k_color.h" +#include "k_follower.h" static CV_PossibleValue_t recordmultiplayerdemos_cons_t[] = {{0, "Disabled"}, {1, "Manual Save"}, {2, "Auto Save"}, {0, NULL}}; consvar_t cv_recordmultiplayerdemos = CVAR_INIT ("netdemo_record", "Manual Save", CV_SAVE, recordmultiplayerdemos_cons_t, NULL); @@ -300,7 +301,7 @@ void G_ReadDemoExtraData(void) // Set our follower M_Memcpy(name, demo_p, 16); demo_p += 16; - SetPlayerFollower(p, name); + K_SetFollowerByName(p, name); // Follower's color M_Memcpy(name, demo_p, 16); @@ -3057,7 +3058,7 @@ void G_DoPlayDemo(char *defdemoname) // Follower M_Memcpy(follower, demo_p, 16); demo_p += 16; - SetPlayerFollower(p, follower); + K_SetFollowerByName(p, follower); // Follower colour M_Memcpy(color, demo_p, 16); diff --git a/src/k_follower.c b/src/k_follower.c new file mode 100644 index 000000000..42b956e9a --- /dev/null +++ b/src/k_follower.c @@ -0,0 +1,462 @@ + +#include "k_follower.h" + +#include "k_kart.h" + +#include "doomtype.h" +#include "doomdef.h" +#include "g_game.h" +#include "g_demo.h" +#include "r_skins.h" +#include "p_local.h" +#include "p_mobj.h" + +INT32 numfollowers = 0; +follower_t followers[MAXSKINS]; + +CV_PossibleValue_t Followercolor_cons_t[MAXSKINCOLORS+3]; // +3 to account for "Match", "Opposite" & NULL + +/*-------------------------------------------------- + INT32 K_FollowerAvailable(const char *name) + + See header file for description. +--------------------------------------------------*/ +INT32 K_FollowerAvailable(const char *name) +{ + INT32 i; + + for (i = 0; i < numfollowers; i++) + { + if (stricmp(followers[i].skinname, name) == 0) + return i; + } + + return -1; +} + +/*-------------------------------------------------- + boolean K_SetFollowerByName(INT32 playernum, const char *skinname) + + See header file for description. +--------------------------------------------------*/ +boolean K_SetFollowerByName(INT32 playernum, const char *skinname) +{ + INT32 i; + player_t *player = &players[playernum]; + + if (stricmp("None", skinname) == 0) + { + K_SetFollowerByNum(playernum, -1); // reminder that -1 is nothing + return true; + } + + for (i = 0; i < numfollowers; i++) + { + // search in the skin list + if (stricmp(followers[i].skinname, skinname) == 0) + { + K_SetFollowerByNum(playernum, i); + return true; + } + } + + if (P_IsLocalPlayer(player)) + { + CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found.\n"), skinname); + } + else if (server || IsPlayerAdmin(consoleplayer)) + { + CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) follower '%s' not found\n"), playernum, player_names[playernum], skinname); + } + + K_SetFollowerByNum(playernum, -1); // reminder that -1 is nothing + return false; +} + +/*-------------------------------------------------- + void K_SetFollowerByNum(INT32 playernum, INT32 skinnum) + + See header file for description. +--------------------------------------------------*/ +void K_SetFollowerByNum(INT32 playernum, INT32 skinnum) +{ + player_t *player = &players[playernum]; + mobj_t *bub; + mobj_t *tmp; + + player->followerready = true; // we are ready to perform follower related actions in the player thinker, now. + + if (skinnum >= -1 && skinnum <= numfollowers) // Make sure it exists! + { + /* + We don't spawn the follower here since it'll be easier to handle all of it in the Player thinker itself. + However, we will despawn it right here if there's any to make it easy for the player thinker to replace it or delete it. + */ + + if (player->follower && skinnum != player->followerskin) // this is also called when we change colour so don't respawn the follower unless we changed skins + { + // Remove follower's possible hnext list (bubble) + bub = player->follower->hnext; + + while (bub && !P_MobjWasRemoved(bub)) + { + tmp = bub->hnext; + P_RemoveMobj(bub); + bub = tmp; + } + + P_RemoveMobj(player->follower); + P_SetTarget(&player->follower, NULL); + } + + player->followerskin = skinnum; + + // for replays: We have changed our follower mid-game; let the game know so it can do the same in the replay! + demo_extradata[playernum] |= DXD_FOLLOWER; + return; + } + + if (P_IsLocalPlayer(player)) + { + CONS_Alert(CONS_WARNING, M_GetText("Follower %d not found\n"), skinnum); + } + else if (server || IsPlayerAdmin(consoleplayer)) + { + CONS_Alert(CONS_WARNING, "Player %d (%s) follower %d not found\n", playernum, player_names[playernum], skinnum); + } + + K_SetFollowerByNum(playernum, -1); // Not found, then set -1 (nothing) as our follower. +} + + +/*-------------------------------------------------- + static void K_SetFollowerState(mobj_t *f, statenum_t state) + + Sets a follower object's state. + This is done as a separate function to prevent running follower actions. + + Input Arguments:- + f - The follower's mobj_t. + state - The state to set. + + Return:- + None +--------------------------------------------------*/ +static void K_SetFollowerState(mobj_t *f, statenum_t state) +{ + if (f == NULL || P_MobjWasRemoved(f) == true) + { + // safety net + return; + } + + // No, do NOT set the follower to S_NULL. Set it to S_INVISIBLE. + if (state == S_NULL) + { + state = S_INVISIBLE; + f->threshold = 1; // Threshold = 1 means stop doing anything related to setting states, so that we don't get out of S_INVISIBLE + } + + // extravalue2 stores the last "first state" we used. + // because states default to idlestates, if we use an animation that uses an "ongoing" state line, don't reset it! + // this prevents it from looking very dumb + if (state == (statenum_t)f->extravalue2) + { + return; + } + + // we will save the state into extravalue2. + f->extravalue2 = state; + + P_SetMobjStateNF(f, state); + if (f->state->tics > 0) + { + f->tics++; + } +} + +/*-------------------------------------------------- + void K_HandleFollower(player_t *player) + + See header file for description. +--------------------------------------------------*/ +void K_HandleFollower(player_t *player) +{ + follower_t fl; + angle_t an; + fixed_t zoffs; + fixed_t sx, sy, sz, deltaz; + UINT16 color; + + fixed_t bubble; // bubble scale (0 if no bubble) + mobj_t *bmobj; // temp bubble mobj + + if (player->followerready == false) + { + // we aren't ready to perform anything follower related yet. + return; + } + + // How about making sure our follower exists and is added before trying to spawn it n' all? + if (player->followerskin > numfollowers-1 || player->followerskin < -1) + { + //CONS_Printf("Follower skin invlaid. Setting to -1.\n"); + player->followerskin = -1; + return; + } + + // don't do anything if we can't have a follower to begin with. + // (It gets removed under those conditions) + if (player->spectator) + { + return; + } + + if (player->followerskin < 0) + { + return; + } + + // Before we do anything, let's be sure of where we're supposed to be + fl = followers[player->followerskin]; + + an = player->mo->angle + fl.atangle; + zoffs = fl.zoffs; + bubble = fl.bubblescale; // 0 if no bubble to spawn. + + // do you like angle maths? I certainly don't... + sx = player->mo->x + FixedMul(FixedMul(player->mo->scale, fl.dist), FINECOSINE((an) >> ANGLETOFINESHIFT)); + sy = player->mo->y + FixedMul(FixedMul(player->mo->scale, fl.dist), FINESINE((an) >> ANGLETOFINESHIFT)); + + // interp info helps with stretchy fix + deltaz = (player->mo->z - player->mo->old_z); + + // for the z coordinate, don't be a doof like Steel and forget that MFE_VERTICALFLIP exists :P + sz = player->mo->z + FixedMul(player->mo->scale, zoffs) * P_MobjFlip(player->mo); + if (player->mo->eflags & MFE_VERTICALFLIP) + { + sz += FixedMul(fl.height, player->mo->scale); + } + + // finally, add a cool floating effect to the z height. + // not stolen from k_kart I swear!! + { + const fixed_t pi = (22<> ANGLETOFINESHIFT) & FINEMASK)); + sz += FixedMul(player->mo->scale, sine) * P_MobjFlip(player->mo); + } + + // Set follower colour + switch (player->followercolor) + { + case FOLLOWERCOLOR_MATCH: // "Match" + color = player->skincolor; + break; + + case FOLLOWERCOLOR_OPPOSITE: // "Opposite" + color = skincolors[player->skincolor].invcolor; + break; + + default: + color = player->followercolor; + if (color == 0 || color > MAXSKINCOLORS+2) // Make sure this isn't garbage + { + color = player->skincolor; // "Match" as fallback. + } + break; + } + + if (player->follower == NULL) // follower doesn't exist / isn't valid + { + //CONS_Printf("Spawning follower...\n"); + + // so let's spawn one! + P_SetTarget(&player->follower, P_SpawnMobj(sx, sy, sz, MT_FOLLOWER)); + K_SetFollowerState(player->follower, fl.idlestate); + P_SetTarget(&player->follower->target, player->mo); // we need that to know when we need to disappear + P_InitAngle(player->follower, player->mo->angle); + + // This is safe to only spawn it here, the follower is removed then respawned when switched. + if (bubble) + { + bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_FRONT); + P_SetTarget(&player->follower->hnext, bmobj); + P_SetTarget(&bmobj->target, player->follower); // Used to know if we have to despawn at some point. + + bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_BACK); + P_SetTarget(&player->follower->hnext->hnext, bmobj); // this seems absolutely stupid, I know, but this will make updating the momentums/flags of these a bit easier. + P_SetTarget(&bmobj->target, player->follower); // Ditto + } + + player->follower->extravalue1 = 0; // extravalue1 is used to know what "state set" to use. + /* + 0 = idle + 1 = forwards + 2 = hurt + 3 = win + 4 = lose + 5 = hitconfirm (< this one uses ->movecount as timer to know when to end, and goes back to normal states afterwards, unless hurt) + */ + } + else // follower exists, woo! + { + // Safety net (2) + + if (P_MobjWasRemoved(player->follower)) + { + P_SetTarget(&player->follower, NULL); // Remove this and respawn one, don't crash the game if Lua decides to P_RemoveMobj this thing. + return; + } + + // first of all, handle states following the same model as above: + if (player->follower->tics == 1) + { + K_SetFollowerState(player->follower, player->follower->state->nextstate); + } + + // move the follower next to us (yes, this is really basic maths but it looks pretty damn clean in practice)! + // 02/09/2021: cast lag to int32 otherwise funny things happen since it was changed to uint32 in the struct + player->follower->momx = FixedDiv(sx - player->follower->x, fl.horzlag); + player->follower->momy = FixedDiv(sy - player->follower->y, fl.horzlag); + player->follower->z += FixedDiv(deltaz, fl.vertlag); + player->follower->momz = FixedDiv(sz - player->follower->z, fl.vertlag); + player->follower->angle = player->mo->angle; + + if (player->mo->colorized) + { + player->follower->color = player->mo->color; + } + else + { + player->follower->color = color; + } + + player->follower->colorized = player->mo->colorized; + + P_SetScale(player->follower, FixedMul(fl.scale, player->mo->scale)); + K_GenericExtraFlagsNoZAdjust(player->follower, player->mo); // Not K_MatchGenericExtraFlag because the Z adjust it has only works properly if master & mo have the same Z height. + + // Match how the player is being drawn + player->follower->renderflags = player->mo->renderflags; + + // Make the follower invisible if we no contest'd rather than removing it. No one will notice the diff seriously. + if (player->pflags & PF_NOCONTEST) + { + player->follower->renderflags |= RF_DONTDRAW; + } + + // if we're moving let's make the angle the direction we're moving towards. This is to avoid drifting / reverse looking awkward. + player->follower->angle = K_MomentumAngle(player->follower); + + // Finally, if the follower has bubbles, move them, set their scale, etc.... + // This is what I meant earlier by it being easier, now we can just use this weird lil loop to get the job done! + + bmobj = player->follower->hnext; // will be NULL if there's no bubble + + while (bmobj != NULL && P_MobjWasRemoved(bmobj) == false) + { + // match follower's momentums and (e)flags(2). + bmobj->momx = player->follower->momx; + bmobj->momy = player->follower->momy; + bmobj->z += FixedDiv(deltaz, fl.vertlag); + bmobj->momz = player->follower->momz; + + P_SetScale(bmobj, FixedMul(bubble, player->mo->scale)); + K_GenericExtraFlagsNoZAdjust(bmobj, player->follower); + bmobj->renderflags = player->mo->renderflags; + + if (player->follower->threshold) + { + // threshold means the follower was "despawned" with S_NULL (is actually just set to S_INVISIBLE) + P_SetMobjState(bmobj, S_INVISIBLE); // sooooo... let's do the same! + } + + // switch to other bubble layer or exit + bmobj = bmobj->hnext; + } + + if (player->follower->threshold) + { + // Threshold means the follower was "despanwed" with S_NULL. + return; + } + + // However with how the code is factored, this is just a special case of S_INVISBLE to avoid having to add other player variables. + + // handle follower animations. Could probably be better... + // hurt or dead + if (P_PlayerInPain(player) == true || player->mo->state == &states[S_KART_SPINOUT] || player->mo->health <= 0) + { + // cancel hit confirm. + player->follower->movecount = 0; + + // spin out + player->follower->angle = player->drawangle; + + if (player->follower->extravalue1 != 2) + { + player->follower->extravalue1 = 2; + K_SetFollowerState(player->follower, fl.hurtstate); + } + + if (player->mo->health <= 0) + { + // if dead, follow the player's z momentum exactly so they both look like they die at the same speed. + player->follower->momz = player->mo->momz; + } + } + else if (player->follower->movecount) + { + if (player->follower->extravalue1 != 5) + { + player->follower->extravalue1 = 5; + K_SetFollowerState(player->follower, fl.hitconfirmstate); + } + + player->follower->movecount--; + } + else if (player->speed > 10*player->mo->scale) // animation for moving fast enough + { + if (player->follower->extravalue1 != 1) + { + player->follower->extravalue1 = 1; + K_SetFollowerState(player->follower, fl.followstate); + } + } + else + { + // animations when nearly still. This includes winning and losing. + if (player->follower->extravalue1 != 0) + { + if (player->exiting) + { + // win/ loss animations + if (K_IsPlayerLosing(player)) + { + // L + if (player->follower->extravalue1 != 4) + { + player->follower->extravalue1 = 4; + K_SetFollowerState(player->follower, fl.losestate); + } + } + else + { + // W + if (player->follower->extravalue1 != 3) + { + player->follower->extravalue1 = 3; + K_SetFollowerState(player->follower, fl.winstate); + } + } + } + else + { + // normal standstill + player->follower->extravalue1 = 0; + K_SetFollowerState(player->follower, fl.idlestate); + } + } + } + } +} diff --git a/src/k_follower.h b/src/k_follower.h new file mode 100644 index 000000000..807d9ac57 --- /dev/null +++ b/src/k_follower.h @@ -0,0 +1,133 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2018-2022 by "Lat'" +// Copyright (C) 2018-2022 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. +//----------------------------------------------------------------------------- +/// \file k_follower.h +/// \brief Code relating to the follower system + +#ifndef __K_FOLLOWER__ +#define __K_FOLLOWER__ + +#include "doomdef.h" +#include "doomstat.h" +#include "r_skins.h" + +#define FOLLOWERCOLOR_MATCH UINT16_MAX +#define FOLLOWERCOLOR_OPPOSITE (UINT16_MAX-1) + +extern CV_PossibleValue_t Followercolor_cons_t[]; // follower colours table, not a duplicate because of the "Match" option. + +// +// We'll define these here because they're really just a mobj that'll follow some rules behind a player +// +typedef struct follower_s +{ + char skinname[SKINNAMESIZE+1]; // Skin Name. This is what to refer to when asking the commands anything. + char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. + + skincolornum_t defaultcolor; // default color for menus. + + fixed_t scale; // Scale relative to the player's. + fixed_t bubblescale; // Bubble scale relative to the player scale. If not set, no bubble will spawn (default) + + // some position shenanigans: + angle_t atangle; // angle the object will be at around the player. The object itself will always face the same direction as the player. + fixed_t dist; // distance relative to the player. (In a circle) + fixed_t height; // height of the follower, this is mostly important for Z flipping. + fixed_t zoffs; // Z offset relative to the player's height. Cannot be negative. + + // movement options + + fixed_t horzlag; // Lag for X/Y displacement. Default is 2. Must be > 0 because we divide by this number. + fixed_t vertlag; // Z displacement lag. Default is 6. Must be > 0 because we divide by this number. + fixed_t bobamp; // Bob amplitude. Default is 4. + tic_t bobspeed; // Arbitrary modifier for bobbing speed. Default is TICRATE*2 (70) + + // from there on out, everything is STATES to allow customization + // these are only set once when the action is performed and are then free to animate however they want. + + statenum_t idlestate; // state when the player is at a standstill + statenum_t followstate; // state when the player is moving + statenum_t hurtstate; // state when the player is being hurt + statenum_t winstate; // state when the player has won + 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 +} follower_t; + +extern INT32 numfollowers; +extern follower_t followers[MAXSKINS]; + +/*-------------------------------------------------- + INT32 K_FollowerAvailable(const char *name) + + Check if a follower with the specified name + exists or not. + + Input Arguments:- + name - The skin name of the follower to check for. + + Return:- + The follower numerical ID of the follower, + or -1 if it doesn't exist. +--------------------------------------------------*/ + +INT32 K_FollowerAvailable(const char *name); + + +/*-------------------------------------------------- + boolean K_SetFollowerByName(INT32 playernum, const char *skinname) + + Updates a player's follower type via a named value. + Calls "K_SetFollowerByNum" internally. + + Input Arguments:- + playernum - The player ID to update + skinname - The follower's skin name + + Return:- + true if it was a valid name for a follower, + otherwise false. +--------------------------------------------------*/ + +boolean K_SetFollowerByName(INT32 playernum, const char *skinname); + + +/*-------------------------------------------------- + void K_SetFollowerByNum(INT32 playernum, INT32 skinnum) + + Updates a player's follower type via a numerical ID. + + Input Arguments:- + playernum - The player ID to update. + skinnum - The follower's skin ID + + Return:- + None +--------------------------------------------------*/ + +void K_SetFollowerByNum(INT32 playernum, INT32 skinnum); + + +/*-------------------------------------------------- + void K_HandleFollower(player_t *player) + + Updates a player's follower pointer, and does + its positioning and animations. + + Input Arguments:- + player - The player who we want to update the follower of. + + Return:- + None +--------------------------------------------------*/ + +void K_HandleFollower(player_t *player); + + +#endif // __K_FOLLOWER__ diff --git a/src/k_kart.c b/src/k_kart.c index 81374d9c6..e5fe76af1 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -37,6 +37,7 @@ #include "k_terrain.h" #include "k_director.h" #include "k_collide.h" +#include "k_follower.h" // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // gamespeed is cc (0 for easy, 1 for normal, 2 for hard) @@ -2806,7 +2807,7 @@ void K_PlayHitEmSound(mobj_t *source, mobj_t *victim) if (source->player->follower) { follower_t fl = followers[source->player->followerskin]; - source->player->follower->movecount = fl.hitconfirmtime; // movecount is used to play the hitconfirm animation for followers. + source->player->follower->movecount = fl.hitconfirmtime; // movecount is used to play the hitconfirm animation for followers. } if (cv_kartvoices.value) diff --git a/src/m_menu.c b/src/m_menu.c index 8255e5f35..d9d229cf2 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -65,6 +65,7 @@ #include "d_player.h" // KITEM_ constants #include "k_color.h" #include "k_grandprix.h" +#include "k_follower.h" #include "r_fps.h" #include "i_joy.h" // for joystick menu controls @@ -9667,7 +9668,7 @@ static void M_DrawSetupMultiPlayerMenu(void) follower_t fl = followers[setupm_fakefollower]; // shortcut for our sanity // smooth floating, totally not stolen from rocket sneakers. const fixed_t pi = (22<>ANGLETOFINESHIFT) & FINEMASK); + fixed_t sine = FixedMul(fl.bobamp, FINESINE((((8 * pi * (fl.bobspeed)) * followertimer)>>ANGLETOFINESHIFT) & FINEMASK)); UINT8 *colormap = R_GetTranslationColormap(-1, setupm_fakecolor->color, 0); V_DrawFixedPatch((mx+65)*FRACUNIT, (my+131-fl.zoffs)*FRACUNIT+sine, fl.scale, flags, patch, colormap); diff --git a/src/p_user.c b/src/p_user.c index dbf716925..648077340 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -56,6 +56,7 @@ #include "k_boss.h" #include "k_terrain.h" // K_SpawnSplashForMobj #include "k_color.h" +#include "k_follower.h" #ifdef HW3SOUND #include "hardware/hw3sound.h" @@ -3961,289 +3962,6 @@ static void P_ParabolicMove(mobj_t *mo, fixed_t x, fixed_t y, fixed_t z, fixed_t #endif -/* set follower state with our weird hacks - the reason we do this is to avoid followers ever using actions (majormods, yikes!) - without having to touch p_mobj.c. - so we give it 1more tic and change the state when tic == 1 instead of 0 - cool beans? - cool beans. -*/ -static void P_SetFollowerState(mobj_t *f, INT32 state) -{ - - if (!f || P_MobjWasRemoved(f)) - return; // safety net - - // No, do NOT set the follower to S_NULL. Set it to S_INVISIBLE. - if (state == S_NULL) - { - state = S_INVISIBLE; - f->threshold = 1; // Threshold = 1 means stop doing anything related to setting states, so that we don't get out of S_INVISIBLE - } - - // extravalue2 stores the last "first state" we used. - // because states default to idlestates, if we use an animation that uses an "ongoing" state line, don't reset it! - // this prevents it from looking very dumb - if (state == f->extravalue2) - return; - - // we will save the state into extravalue2. - f->extravalue2 = state; - - P_SetMobjStateNF(f, state); - if (f->state->tics > 0) - f->tics++; -} - -// -//P_HandleFollower -// -//Handle the follower's spawning and moving along with the player. Do note that some of the stuff like the removal if a player doesn't exist anymore is handled in MT_FOLLOWER's thinker. -static void P_HandleFollower(player_t *player) -{ - follower_t fl; - angle_t an; - fixed_t zoffs; - fixed_t sx, sy, sz, deltaz; - UINT16 color; - - fixed_t bubble; // bubble scale (0 if no bubble) - mobj_t *bmobj; // temp bubble mobj - - - if (!player->followerready) - return; // we aren't ready to perform anything follower related yet. - - // How about making sure our follower exists and is added before trying to spawn it n' all? - if (player->followerskin > numfollowers-1 || player->followerskin < -1) - { - //CONS_Printf("Follower skin invlaid. Setting to -1.\n"); - player->followerskin = -1; - return; - } - - // don't do anything if we can't have a follower to begin with. (It gets removed under those conditions) - if (player->spectator) - return; - if (player->followerskin < 0) - return; - // Before we do anything, let's be sure of where we're supposed to be - fl = followers[player->followerskin]; - - an = player->mo->angle + (fl.atangle)*ANG1; // it's aproximative but it really doesn't matter in the grand scheme of things... - zoffs = (fl.zoffs)*FRACUNIT; - bubble = fl.bubblescale; // 0 if no bubble to spawn. - - // do you like angle maths? I certainly don't... - sx = player->mo->x + FixedMul((player->mo->scale*fl.dist), FINECOSINE((an)>>ANGLETOFINESHIFT)); - sy = player->mo->y + FixedMul((player->mo->scale*fl.dist), FINESINE((an)>>ANGLETOFINESHIFT)); - - // interp info helps with stretchy fix - deltaz = (player->mo->z - player->mo->old_z); - - // for the z coordinate, don't be a doof like Steel and forget that MFE_VERTICALFLIP exists :P - sz = player->mo->z + FixedMul(player->mo->scale, zoffs)*P_MobjFlip(player->mo); - if (player->mo->eflags & MFE_VERTICALFLIP) - sz += fl.height*player->mo->scale; - - // finally, add a cool floating effect to the z height. - // not stolen from k_kart I swear!! - { - const fixed_t pi = (22<>ANGLETOFINESHIFT) & FINEMASK); - sz += FixedMul(player->mo->scale, sine)*P_MobjFlip(player->mo); - } - - // Set follower colour - switch (player->followercolor) - { - case FOLLOWERCOLOR_MATCH: // "Match" - color = player->skincolor; - break; - case FOLLOWERCOLOR_OPPOSITE: // "Opposite" - color = skincolors[player->skincolor].invcolor; - break; - default: - - color = player->followercolor; - if (!color || color > MAXSKINCOLORS+2) // Make sure this isn't garbage - color = player->skincolor; // "Match" as fallback. - - break; - } - - - - if (!player->follower) // follower doesn't exist / isn't valid - { - //CONS_Printf("Spawning follower...\n"); - // so let's spawn one! - P_SetTarget(&player->follower, P_SpawnMobj(sx, sy, sz, MT_FOLLOWER)); - P_SetFollowerState(player->follower, fl.idlestate); - P_SetTarget(&player->follower->target, player->mo); // we need that to know when we need to disappear - P_InitAngle(player->follower, player->mo->angle); - - // This is safe to only spawn it here, the follower is removed then respawned when switched. - if (bubble) - { - bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_FRONT); - P_SetTarget(&player->follower->hnext, bmobj); - P_SetTarget(&bmobj->target, player->follower); // Used to know if we have to despawn at some point. - - bmobj = P_SpawnMobj(player->follower->x, player->follower->y, player->follower->z, MT_FOLLOWERBUBBLE_BACK); - P_SetTarget(&player->follower->hnext->hnext, bmobj); // this seems absolutely stupid, I know, but this will make updating the momentums/flags of these a bit easier. - P_SetTarget(&bmobj->target, player->follower); // Ditto - } - - player->follower->extravalue1 = 0; // extravalue1 is used to know what "state set" to use. - /* - 0 = idle - 1 = forwards - 2 = hurt - 3 = win - 4 = lose - 5 = hitconfirm (< this one uses ->movecount as timer to know when to end, and goes back to normal states afterwards, unless hurt) - */ - } - else // follower exists, woo! - { - - // Safety net (2) - - if (P_MobjWasRemoved(player->follower)) - { - P_SetTarget(&player->follower, NULL); // Remove this and respawn one, don't crash the game if Lua decides to P_RemoveMobj this thing. - return; - } - - // first of all, handle states following the same model as above: - if (player->follower->tics == 1) - P_SetFollowerState(player->follower, player->follower->state->nextstate); - - // move the follower next to us (yes, this is really basic maths but it looks pretty damn clean in practice)! - // 02/09/2021: cast lag to int32 otherwise funny things happen since it was changed to uint32 in the struct - player->follower->momx = (sx - player->follower->x)/ (INT32)fl.horzlag; - player->follower->momy = (sy - player->follower->y)/ (INT32)fl.horzlag; - player->follower->z += (deltaz/ (INT32)fl.vertlag); - player->follower->momz = (sz - player->follower->z)/ (INT32)fl.vertlag; - player->follower->angle = player->mo->angle; - - if (player->mo->colorized) - player->follower->color = player->mo->color; - else - player->follower->color = color; - - player->follower->colorized = player->mo->colorized; - - P_SetScale(player->follower, FixedMul(fl.scale, player->mo->scale)); - K_GenericExtraFlagsNoZAdjust(player->follower, player->mo); // Not K_MatchGenericExtraFlag because the Z adjust it has only works properly if master & mo have the same Z height. - - // Match how the player is being drawn - player->follower->renderflags = player->mo->renderflags; - - // Make the follower invisible if we no contest'd rather than removing it. No one will notice the diff seriously. - if (player->pflags & PF_NOCONTEST) - player->follower->renderflags |= RF_DONTDRAW; - - // if we're moving let's make the angle the direction we're moving towards. This is to avoid drifting / reverse looking awkward. - player->follower->angle = K_MomentumAngle(player->follower); - - // Finally, if the follower has bubbles, move them, set their scale, etc.... - // This is what I meant earlier by it being easier, now we can just use this weird lil loop to get the job done! - - bmobj = player->follower->hnext; // will be NULL if there's no bubble - - while (bmobj && !P_MobjWasRemoved(bmobj)) - { - // match follower's momentums and (e)flags(2). - bmobj->momx = player->follower->momx; - bmobj->momy = player->follower->momy; - bmobj->z += (deltaz/ (INT32)fl.vertlag); - bmobj->momz = player->follower->momz; - - P_SetScale(bmobj, FixedMul(bubble, player->mo->scale)); - K_GenericExtraFlagsNoZAdjust(bmobj, player->follower); - bmobj->renderflags = player->mo->renderflags; - - if (player->follower->threshold) // threshold means the follower was "despawned" with S_NULL (is actually just set to S_INVISIBLE) - P_SetMobjState(bmobj, S_INVISIBLE); // sooooo... let's do the same! - - bmobj = bmobj->hnext; // switch to other bubble layer or exit - } - - - if (player->follower->threshold) - return; // Threshold means the follower was "despanwed" with S_NULL. - - // However with how the code is factored, this is just a special case of S_INVISBLE to avoid having to add other player variables. - - - // handle follower animations. Could probably be better... - // hurt or dead - if (player->spinouttimer || player->mo->state == &states[S_KART_SPINOUT] || player->mo->health <= 0) - { - player->follower->movecount = 0; // cancel hit confirm. - player->follower->angle = player->drawangle; // spin out - if (player->follower->extravalue1 != 2) - { - player->follower->extravalue1 = 2; - P_SetFollowerState(player->follower, fl.hurtstate); - } - if (player->mo->health <= 0) // if dead, follow the player's z momentum exactly so they both look like they die at the same speed. - player->follower->momz = player->mo->momz; - } - else if (player->follower->movecount) - { - if (player->follower->extravalue1 != 5) - { - player->follower->extravalue1 = 5; - P_SetFollowerState(player->follower, fl.hitconfirmstate); - } - player->follower->movecount--; - } - else if (player->speed > 10*player->mo->scale) // animation for moving fast enough - { - - if (player->follower->extravalue1 != 1) - { - player->follower->extravalue1 = 1; - P_SetFollowerState(player->follower, fl.followstate); - } - } - else // animations when nearly still. This includes winning and losing. - { - if (player->follower->extravalue1 != 0) - { - - if (player->exiting) // win/ loss animations - { - if (K_IsPlayerLosing(player)) // L - { - if (player->follower->extravalue1 != 4) - { - player->follower->extravalue1 = 4; - P_SetFollowerState(player->follower, fl.losestate); - } - } - else // W - { - if (player->follower->extravalue1 != 3) - { - player->follower->extravalue1 = 3; - P_SetFollowerState(player->follower, fl.winstate); - } - } - } - else // normal standstill - { - player->follower->extravalue1 = 0; - P_SetFollowerState(player->follower, fl.idlestate); - } - } - } - } -} - /* gaysed script from me, based on Golden's sprite slope roll */ // holy SHIT @@ -4370,9 +4088,6 @@ void P_PlayerThink(player_t *player) player->awayviewtics = 0; // reset to zero } - // Run followes here. We need them to run even when we're dead to follow through what we're doing. - P_HandleFollower(player); - if (player->flashcount) player->flashcount--; @@ -4767,6 +4482,10 @@ void P_PlayerAfterThink(player_t *player) } #endif + // Run followers in AfterThink, after the players have moved, + // so a lag value of 1 is exactly attached to the player. + K_HandleFollower(player); + #ifdef SECTORSPECIALSAFTERTHINK if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo)) player->onconveyor = 0; diff --git a/src/r_data.h b/src/r_data.h index 1228f2420..3c8908a59 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -40,9 +40,6 @@ extern INT16 color8to16[256]; // remap color index to highcolor extern INT16 *hicolormaps; // remap high colors to high colors.. extern CV_PossibleValue_t Color_cons_t[]; -extern CV_PossibleValue_t Followercolor_cons_t[]; // follower colours table, not a duplicate because of the "Match" option. -#define FOLLOWERCOLOR_MATCH UINT16_MAX -#define FOLLOWERCOLOR_OPPOSITE (UINT16_MAX-1) // I/O, setting up the stuff. void R_InitTextureData(void); diff --git a/src/r_draw.c b/src/r_draw.c index d9f892423..7488ab5aa 100644 --- a/src/r_draw.c +++ b/src/r_draw.c @@ -194,7 +194,6 @@ static INT32 CacheIndexToSkin(INT32 ttc) } CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1]; -CV_PossibleValue_t Followercolor_cons_t[MAXSKINCOLORS+3]; // +3 to account for "Match", "Opposite" & NULL #define TRANSTAB_AMTMUL10 (255.0f / 10.0f) diff --git a/src/r_skins.c b/src/r_skins.c index ba7ed0811..d4b10125e 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -34,8 +34,6 @@ INT32 numskins = 0; skin_t skins[MAXSKINS]; -INT32 numfollowers = 0; - // FIXTHIS: don't work because it must be inistilised before the config load //#define SKINVALUES #ifdef SKINVALUES @@ -44,9 +42,6 @@ CV_PossibleValue_t skin_cons_t[MAXSKINS+1]; CV_PossibleValue_t Forceskin_cons_t[MAXSKINS+2]; -// SRB2Kart followers -follower_t followers[MAXSKINS]; - // // P_GetSkinSprite2 // For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing. @@ -860,97 +855,3 @@ next_token: } #undef SYMBOLCONVERT - -// SRB2Kart: Followers! -// TODO: put this stuff in its own file? - -// same thing as R_SkinAvailable, but for followers -INT32 R_FollowerAvailable(const char *name) -{ - INT32 i; - - for (i = 0; i < numfollowers; i++) - { - if (stricmp(followers[i].skinname,name)==0) - return i; - } - return -1; -} - -// same thing as SetPlayerSkin, but for followers -boolean SetPlayerFollower(INT32 playernum, const char *skinname) -{ - INT32 i; - player_t *player = &players[playernum]; - - if (stricmp("None", skinname) == 0) - { - SetFollower(playernum, -1); // reminder that -1 is nothing - return true; - } - for (i = 0; i < numfollowers; i++) - { - // search in the skin list - if (stricmp(followers[i].skinname, skinname) == 0) - { - SetFollower(playernum, i); - return true; - } - } - - if (P_IsLocalPlayer(player)) - CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found.\n"), skinname); - else if(server || IsPlayerAdmin(consoleplayer)) - CONS_Alert(CONS_WARNING, M_GetText("Player %d (%s) follower '%s' not found\n"), playernum, player_names[playernum], skinname); - - SetFollower(playernum, -1); // reminder that -1 is nothing - return false; -} - -// SetPlayerSkinByNum, for followers -void SetFollower(INT32 playernum, INT32 skinnum) -{ - player_t *player = &players[playernum]; - mobj_t *bub; - mobj_t *tmp; - - player->followerready = true; // we are ready to perform follower related actions in the player thinker, now. - - if (skinnum >= -1 && skinnum <= numfollowers) // Make sure it exists! - { - /* - We don't spawn the follower here since it'll be easier to handle all of it in the Player thinker itself. - However, we will despawn it right here if there's any to make it easy for the player thinker to replace it or delete it. - */ - - if (player->follower && skinnum != player->followerskin) // this is also called when we change colour so don't respawn the follower unless we changed skins - { - // Remove follower's possible hnext list (bubble) - bub = player->follower->hnext; - - while (bub && !P_MobjWasRemoved(bub)) - { - tmp = bub->hnext; - P_RemoveMobj(bub); - bub = tmp; - } - - P_RemoveMobj(player->follower); - P_SetTarget(&player->follower, NULL); - } - - player->followerskin = skinnum; - - // for replays: We have changed our follower mid-game; let the game know so it can do the same in the replay! - demo_extradata[playernum] |= DXD_FOLLOWER; - - return; - } - - if (P_IsLocalPlayer(player)) - CONS_Alert(CONS_WARNING, M_GetText("Follower %d not found\n"), skinnum); - else if(server || IsPlayerAdmin(consoleplayer)) - CONS_Alert(CONS_WARNING, "Player %d (%s) follower %d not found\n", playernum, player_names[playernum], skinnum); - - SetFollower(playernum, -1); // Not found, then set -1 (nothing) as our follower. -} diff --git a/src/r_skins.h b/src/r_skins.h index 2d29af188..cffd54da8 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -90,51 +90,4 @@ void R_AddSkins(UINT16 wadnum); UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player); -// SRB2Kart Followers - -// -// We'll define these here because they're really just a mobj that'll follow some rules behind a player -// -typedef struct follower_s -{ - char skinname[SKINNAMESIZE+1]; // Skin Name. This is what to refer to when asking the commands anything. - char name[SKINNAMESIZE+1]; // Name. This is used for the menus. We'll just follow the same rules as skins for this. - - UINT16 defaultcolor; // default color for menus. - - fixed_t scale; // Scale relative to the player's. - fixed_t bubblescale; // Bubble scale relative to the player scale. If not set, no bubble will spawn (default) - - // some position shenanigans: - INT32 atangle; // angle the object will be at around the player. The object itself will always face the same direction as the player. - INT32 dist; // distance relative to the player. (In a circle) - INT32 height; // height of the follower, this is mostly important for Z flipping. - INT32 zoffs; // Z offset relative to the player's height. Cannot be negative. - - // movement options - - UINT32 horzlag; // Lag for X/Y displacement. Default is 2. Must be > 0 because we divide by this number. - UINT32 vertlag; // not Vert from Neptunia lagging, this is for Z displacement lag Default is 6. Must be > 0 because we divide by this number. - INT32 bobamp; // Bob amplitude. Default is 4. - INT32 bobspeed; // Arbitrary modifier for bobbing speed, default is TICRATE*2 (70). - - // from there on out, everything is STATES to allow customization - // these are only set once when the action is performed and are then free to animate however they want. - - INT32 idlestate; // state when the player is at a standstill - INT32 followstate; // state when the player is moving - INT32 hurtstate; // state when the player is being hurt - INT32 winstate; // state when the player has won - INT32 losestate; // state when the player has lost - INT32 hitconfirmstate; // state for hit confirm - UINT32 hitconfirmtime; // time to keep the above playing for -} follower_t; - -extern INT32 numfollowers; -extern follower_t followers[MAXSKINS]; // again, use the same rules as skins, no reason not to. - -INT32 R_FollowerAvailable(const char *name); -boolean SetPlayerFollower(INT32 playernum,const char *skinname); -void SetFollower(INT32 playernum,INT32 skinnum); - #endif //__R_SKINS__