Move follower code to its own file

This commit is contained in:
Sally Coolatta 2022-05-23 00:23:46 -04:00
parent 14053a55cd
commit b5334e6b42
14 changed files with 649 additions and 475 deletions

View file

@ -118,3 +118,4 @@ k_hud.c
k_terrain.c k_terrain.c
k_brightmap.c k_brightmap.c
k_director.c k_director.c
k_follower.c

View file

@ -58,6 +58,7 @@
#include "k_respawn.h" #include "k_respawn.h"
#include "k_grandprix.h" #include "k_grandprix.h"
#include "k_boss.h" #include "k_boss.h"
#include "k_follower.h"
#include "doomstat.h" #include "doomstat.h"
#include "deh_tables.h" #include "deh_tables.h"
@ -1452,7 +1453,7 @@ static void SendNameAndColor(UINT8 n)
// Update follower for local games: // Update follower for local games:
if (cv_follower[n].value >= -1 && cv_follower[n].value != player->followerskin) 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; player->followercolor = cv_followercolor[n].value;
@ -1632,11 +1633,13 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum)
SetPlayerSkinByNum(playernum, skin); SetPlayerSkinByNum(playernum, skin);
// set follower colour: // 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; p->followercolor = followercolor;
// set follower // set follower
SetFollower(playernum, follower); K_SetFollowerByNum(playernum, follower);
#ifdef HAVE_DISCORDRPC #ifdef HAVE_DISCORDRPC
if (playernum == consoleplayer) if (playernum == consoleplayer)
@ -5316,7 +5319,7 @@ static void Follower_OnChange(void)
return; return;
} }
num = R_FollowerAvailable(str); num = K_FollowerAvailable(str);
if (num == -1) // that's an error. if (num == -1) // that's an error.
CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str);
@ -5370,7 +5373,7 @@ static void Follower2_OnChange(void)
return; return;
} }
num = R_FollowerAvailable(str); num = K_FollowerAvailable(str);
if (num == -1) // that's an error. if (num == -1) // that's an error.
CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str);
@ -5421,7 +5424,7 @@ static void Follower3_OnChange(void)
return; return;
} }
num = R_FollowerAvailable(str); num = K_FollowerAvailable(str);
if (num == -1) // that's an error. if (num == -1) // that's an error.
CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str);
@ -5472,7 +5475,7 @@ static void Follower4_OnChange(void)
return; return;
} }
num = R_FollowerAvailable(str); num = K_FollowerAvailable(str);
if (num == -1) // that's an error. if (num == -1) // that's an error.
CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str); CONS_Alert(CONS_WARNING, M_GetText("Follower '%s' not found\n"), str);

View file

@ -49,6 +49,7 @@
// SRB2Kart // SRB2Kart
#include "filesrch.h" // refreshdirmenu #include "filesrch.h" // refreshdirmenu
#include "k_follower.h"
// Loops through every constant and operation in word and performs its calculations, returning the final value. // Loops through every constant and operation in word and performs its calculations, returning the final value.
fixed_t get_number(const char *word) fixed_t get_number(const char *word)
@ -3820,14 +3821,14 @@ 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. // 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].scale = FRACUNIT;
followers[numfollowers].bubblescale = 0; // No bubble by default followers[numfollowers].bubblescale = 0; // No bubble by default
followers[numfollowers].atangle = 230; followers[numfollowers].atangle = FixedAngle(230 * FRACUNIT);
followers[numfollowers].dist = 32; // changed from 16 to 32 to better account for ogl models followers[numfollowers].dist = 32*FRACUNIT; // changed from 16 to 32 to better account for ogl models
followers[numfollowers].height = 16; followers[numfollowers].height = 16*FRACUNIT;
followers[numfollowers].zoffs = 32; followers[numfollowers].zoffs = 32*FRACUNIT;
followers[numfollowers].horzlag = 2; followers[numfollowers].horzlag = 2*FRACUNIT;
followers[numfollowers].vertlag = 6; followers[numfollowers].vertlag = 6*FRACUNIT;
followers[numfollowers].bobspeed = TICRATE*2; followers[numfollowers].bobspeed = TICRATE*2;
followers[numfollowers].bobamp = 4; followers[numfollowers].bobamp = 4*FRACUNIT;
followers[numfollowers].hitconfirmtime = TICRATE; followers[numfollowers].hitconfirmtime = TICRATE;
followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; followers[numfollowers].defaultcolor = SKINCOLOR_GREEN;
@ -3865,48 +3866,47 @@ void readfollower(MYFILE *f)
} }
else if (fastcmp(word, "DEFAULTCOLOR")) else if (fastcmp(word, "DEFAULTCOLOR"))
{ {
followers[numfollowers].defaultcolor = (UINT16)get_number(word2); followers[numfollowers].defaultcolor = get_number(word2);
} }
else if (fastcmp(word, "SCALE")) else if (fastcmp(word, "SCALE"))
{ {
followers[numfollowers].scale = get_number(word2); followers[numfollowers].scale = (fixed_t)get_number(word2);
} }
else if (fastcmp(word, "BUBBLESCALE")) else if (fastcmp(word, "BUBBLESCALE"))
{ {
followers[numfollowers].bubblescale = get_number(word2); followers[numfollowers].bubblescale = (fixed_t)get_number(word2);
} }
else if (fastcmp(word, "ATANGLE")) 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")) else if (fastcmp(word, "HORZLAG"))
{ {
followers[numfollowers].horzlag = (INT32)atoi(word2); followers[numfollowers].horzlag = (fixed_t)get_number(word2);
} }
else if (fastcmp(word, "VERTLAG")) else if (fastcmp(word, "VERTLAG"))
{ {
followers[numfollowers].vertlag = (INT32)atoi(word2); followers[numfollowers].vertlag = (fixed_t)get_number(word2);
} }
else if (fastcmp(word, "BOBSPEED")) else if (fastcmp(word, "BOBSPEED"))
{ {
followers[numfollowers].bobspeed = (INT32)atoi(word2); followers[numfollowers].bobspeed = (tic_t)get_number(word2);
} }
else if (fastcmp(word, "BOBAMP")) 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"))) 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"))) 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")) else if (fastcmp(word, "HEIGHT"))
{ {
followers[numfollowers].height = (INT32)atoi(word2); followers[numfollowers].height = (fixed_t)get_number(word2);
} }
else if (fastcmp(word, "IDLESTATE")) else if (fastcmp(word, "IDLESTATE"))
{ {
@ -3947,15 +3947,18 @@ void readfollower(MYFILE *f)
} }
else if (fastcmp(word, "HITTIME") || (fastcmp(word, "HITCONFIRMTIME"))) else if (fastcmp(word, "HITTIME") || (fastcmp(word, "HITCONFIRMTIME")))
{ {
followers[numfollowers].hitconfirmtime = (INT32)atoi(word2); followers[numfollowers].hitconfirmtime = (tic_t)get_number(word2);
} }
else else
{
deh_warning("Follower %d: unknown word '%s'", numfollowers, word); deh_warning("Follower %d: unknown word '%s'", numfollowers, word);
} }
}
} while (!myfeof(f)); // finish when the line is empty } while (!myfeof(f)); // finish when the line is empty
if (!nameset) // well this is problematic. if (!nameset)
{ {
// well this is problematic.
strcpy(followers[numfollowers].name, va("Follower%d", numfollowers)); // this is lazy, so what strcpy(followers[numfollowers].name, va("Follower%d", numfollowers)); // this is lazy, so what
} }
@ -3966,7 +3969,7 @@ void readfollower(MYFILE *f)
// lower testname for skin checks... // lower testname for skin checks...
strlwr(testname); strlwr(testname);
res = R_FollowerAvailable(testname); res = K_FollowerAvailable(testname);
if (res > -1) // yikes, someone else has stolen our name already if (res > -1) // yikes, someone else has stolen our name already
{ {
INT32 startlen = strlen(testname); INT32 startlen = strlen(testname);
@ -3990,7 +3993,7 @@ void readfollower(MYFILE *f)
// fallbacks for variables // 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. // 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) \ #define FALLBACK(field, field2, threshold, set) \
if (followers[numfollowers].field < threshold) \ if ((signed)followers[numfollowers].field < threshold) \
{ \ { \
followers[numfollowers].field = set; \ 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); \ 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 FALLBACK(bubblescale, "BUBBLESCALE", 0, 0); // No negative scale
// Special case for color I suppose // Special case for color I suppose
if (followers[numfollowers].defaultcolor > numskincolors-1) if (followers[numfollowers].defaultcolor > (unsigned)(numskincolors-1))
{ {
followers[numfollowers].defaultcolor = SKINCOLOR_GREEN; followers[numfollowers].defaultcolor = SKINCOLOR_GREEN;
deh_warning("Follower \'%s\': Value for 'color' should be between 1 and %d.\n", dname, numskincolors-1); deh_warning("Follower \'%s\': Value for 'color' should be between 1 and %d.\n", dname, numskincolors-1);

View file

@ -206,7 +206,7 @@ extern char logfilename[1024];
#define MAXPLAYERNAME 21 #define MAXPLAYERNAME 21
#define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer #define MAXSPLITSCREENPLAYERS 4 // Max number of players on a single computer
#define MAXSKINS 128 #define MAXSKINS UINT8_MAX
#define COLORRAMPSIZE 16 #define COLORRAMPSIZE 16
#define MAXCOLORNAME 32 #define MAXCOLORNAME 32

View file

@ -51,6 +51,7 @@
#include "k_respawn.h" #include "k_respawn.h"
#include "k_bot.h" #include "k_bot.h"
#include "k_color.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}}; 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); 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 // Set our follower
M_Memcpy(name, demo_p, 16); M_Memcpy(name, demo_p, 16);
demo_p += 16; demo_p += 16;
SetPlayerFollower(p, name); K_SetFollowerByName(p, name);
// Follower's color // Follower's color
M_Memcpy(name, demo_p, 16); M_Memcpy(name, demo_p, 16);
@ -3057,7 +3058,7 @@ void G_DoPlayDemo(char *defdemoname)
// Follower // Follower
M_Memcpy(follower, demo_p, 16); M_Memcpy(follower, demo_p, 16);
demo_p += 16; demo_p += 16;
SetPlayerFollower(p, follower); K_SetFollowerByName(p, follower);
// Follower colour // Follower colour
M_Memcpy(color, demo_p, 16); M_Memcpy(color, demo_p, 16);

462
src/k_follower.c Normal file
View file

@ -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<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
fixed_t sine = FixedMul(fl.bobamp, FINESINE((((8 * pi * fl.bobspeed) * leveltime) >> 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);
}
}
}
}
}

133
src/k_follower.h Normal file
View file

@ -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__

View file

@ -37,6 +37,7 @@
#include "k_terrain.h" #include "k_terrain.h"
#include "k_director.h" #include "k_director.h"
#include "k_collide.h" #include "k_collide.h"
#include "k_follower.h"
// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H: // SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H:
// gamespeed is cc (0 for easy, 1 for normal, 2 for hard) // gamespeed is cc (0 for easy, 1 for normal, 2 for hard)

View file

@ -65,6 +65,7 @@
#include "d_player.h" // KITEM_ constants #include "d_player.h" // KITEM_ constants
#include "k_color.h" #include "k_color.h"
#include "k_grandprix.h" #include "k_grandprix.h"
#include "k_follower.h"
#include "r_fps.h" #include "r_fps.h"
#include "i_joy.h" // for joystick menu controls #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 follower_t fl = followers[setupm_fakefollower]; // shortcut for our sanity
// smooth floating, totally not stolen from rocket sneakers. // smooth floating, totally not stolen from rocket sneakers.
const fixed_t pi = (22<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise const fixed_t pi = (22<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
fixed_t sine = fl.bobamp * FINESINE((((8*pi*(fl.bobspeed)) * followertimer)>>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); 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); V_DrawFixedPatch((mx+65)*FRACUNIT, (my+131-fl.zoffs)*FRACUNIT+sine, fl.scale, flags, patch, colormap);

View file

@ -56,6 +56,7 @@
#include "k_boss.h" #include "k_boss.h"
#include "k_terrain.h" // K_SpawnSplashForMobj #include "k_terrain.h" // K_SpawnSplashForMobj
#include "k_color.h" #include "k_color.h"
#include "k_follower.h"
#ifdef HW3SOUND #ifdef HW3SOUND
#include "hardware/hw3sound.h" #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 #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<<FRACBITS) / 7; // loose approximation, this doesn't need to be incredibly precise
fixed_t sine = fl.bobamp * FINESINE((((8*pi*(fl.bobspeed)) * leveltime)>>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 */ /* gaysed script from me, based on Golden's sprite slope roll */
// holy SHIT // holy SHIT
@ -4370,9 +4088,6 @@ void P_PlayerThink(player_t *player)
player->awayviewtics = 0; // reset to zero 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) if (player->flashcount)
player->flashcount--; player->flashcount--;
@ -4767,6 +4482,10 @@ void P_PlayerAfterThink(player_t *player)
} }
#endif #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 #ifdef SECTORSPECIALSAFTERTHINK
if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo)) if (player->onconveyor != 1 || !P_IsObjectOnGround(player->mo))
player->onconveyor = 0; player->onconveyor = 0;

View file

@ -40,9 +40,6 @@ extern INT16 color8to16[256]; // remap color index to highcolor
extern INT16 *hicolormaps; // remap high colors to high colors.. extern INT16 *hicolormaps; // remap high colors to high colors..
extern CV_PossibleValue_t Color_cons_t[]; 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. // I/O, setting up the stuff.
void R_InitTextureData(void); void R_InitTextureData(void);

View file

@ -194,7 +194,6 @@ static INT32 CacheIndexToSkin(INT32 ttc)
} }
CV_PossibleValue_t Color_cons_t[MAXSKINCOLORS+1]; 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) #define TRANSTAB_AMTMUL10 (255.0f / 10.0f)

View file

@ -34,8 +34,6 @@
INT32 numskins = 0; INT32 numskins = 0;
skin_t skins[MAXSKINS]; skin_t skins[MAXSKINS];
INT32 numfollowers = 0;
// FIXTHIS: don't work because it must be inistilised before the config load // FIXTHIS: don't work because it must be inistilised before the config load
//#define SKINVALUES //#define SKINVALUES
#ifdef SKINVALUES #ifdef SKINVALUES
@ -44,9 +42,6 @@ CV_PossibleValue_t skin_cons_t[MAXSKINS+1];
CV_PossibleValue_t Forceskin_cons_t[MAXSKINS+2]; CV_PossibleValue_t Forceskin_cons_t[MAXSKINS+2];
// SRB2Kart followers
follower_t followers[MAXSKINS];
// //
// P_GetSkinSprite2 // 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. // 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 #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.
}

View file

@ -90,51 +90,4 @@ void R_AddSkins(UINT16 wadnum);
UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player); 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__ #endif //__R_SKINS__