Skin and playerstate code in demos has been partly rewritten to support more than was previously possible.

Notable new features:
- Guaranteed native compatibility with SF_IRONMAN even with differing # of skins
- Bots (todo: can still desync midway through round)

Implementation details:
- Demo code (skins):
    - Instead of writing a skin name string, and the player's kartspeed, kartweight, and charflags for each player in the initial player-interpreting loop...
    - Write a skinlist of EVERY skin's name string, kartspeed, kartweight, and flags next to the file list, to be read into `demo.skinlist`.
        - If the skin name isn't loaded, find the skin with (in order)
            - SF_IRONMAN if your skin had SF_IRONMAN, since that's more important to signal
            - the closest stats otherwise (as per previous implementation)
        - Just as tolerant to stats AND the number of base skins changing between versions (the bonuschars aegis situation)
        - Not tolerant to restat, but we can add a DXD or EZT later if we want to natively support that kind of mod
    - In the initial loop and DXD_SKIN, just write an index that can be used for `demo.skinlist`, and store it in `demo.currentskinid[p]`
    - The player's skin is now encoded as EZT_IRONMAN for ghosts (and just in case RNG sync fails for unrelated reasons)
- In the SF_IRONMAN code when demo.playback is true
    - everywhere where `skins[player->skin]` is referenced instead uses an index into `demo.skinlist`
    - SetRandomFakePlayerSkin uses the `demo.skinlist` to build a table to ensure exact random call parity
        - Also means it no longer double rejection-samples.
    - `player->fakeskin` and `lastfakeskin` are always == their original recording values, a skin id which can be used into `demo.skinlist`
- Demo code (playstate, initial player setup loop):
    - Add bot flag (`DXD_PST_ISBOT`, `DEMO_BOT`)
    - Add in-between-level botvars (difficulty, diffincrease, rival)
    - Don't rely on `PF_WANTSTOJOIN` to activate

Additional bugfixes:
- Followerskin set to -1 in CL_ClearPlayer so a bad follower isn't recorded on player join without name and color change arriving immediately
- Accomodate new joiners in demo code even if they're not on DXD_PST_SPECTATING for one reason or another
- Demo extra file list saving is now its own function for code cleanliness
- Actually only modify players relevant to the demo at the end of G_DoPlayDemo, not all 16 by supplying and overwriting garbage values (POSSIBLE MEMORY CORRUPTION FIX, mobj_t pointer was previously dereferenced)
This commit is contained in:
toaster 2022-11-22 14:28:48 +00:00
parent e65d17cd4d
commit b8f59fd227
8 changed files with 771 additions and 499 deletions

View file

@ -2550,6 +2550,8 @@ void CL_ClearPlayer(INT32 playernum)
memset(&players[playernum], 0, sizeof (player_t));
players[playernum].followerskin = -1; // don't have a ghost follower
RemoveAdminPlayer(playernum); // don't stay admin after you're gone
}

View file

@ -310,6 +310,8 @@ typedef struct botvars_s
UINT8 diffincrease; // In GP: bot difficulty will increase this much next round
boolean rival; // If true, they're the GP rival
// All entries above persist between rounds and must be recorded in demos
fixed_t rubberband; // Bot rubberband value
UINT16 controller; // Special bot controller linedef ID

File diff suppressed because it is too large Load diff

View file

@ -28,6 +28,13 @@ extern consvar_t cv_recordmultiplayerdemos, cv_netdemosyncquality;
extern tic_t demostarttime;
typedef struct democharlist_s {
UINT8 mapping; // No, this isn't about levels. It maps to loaded character ID.
UINT8 kartspeed;
UINT8 kartweight;
UINT32 flags;
} democharlist_t;
// Publicly-accessible demo vars
struct demovars_s {
char titlename[65];
@ -54,6 +61,9 @@ struct demovars_s {
boolean freecam;
UINT8 numskins;
democharlist_t *skinlist;
UINT8 currentskinid[MAXPLAYERS];
};
extern struct demovars_s demo;
@ -102,20 +112,18 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname);
typedef enum
{
GHC_NORMAL = 0,
GHC_SUPER,
GHC_FIREFLOWER,
GHC_INVINCIBLE,
GHC_RETURNSKIN // not actually a colour
GHC_SUPER
} ghostcolor_t;
extern UINT8 demo_extradata[MAXPLAYERS];
extern UINT8 demo_writerng;
#define DXD_RESPAWN 0x01 // "respawn" command in console
#define DXD_SKIN 0x02 // skin changed
#define DXD_NAME 0x04 // name changed
#define DXD_COLOR 0x08 // color changed
#define DXD_PLAYSTATE 0x10 // state changed between playing, spectating, or not in-game
#define DXD_PLAYSTATE 0x01 // state changed between playing, spectating, or not in-game
#define DXD_RESPAWN 0x02 // "respawn" command in console
#define DXD_SKIN 0x04 // skin changed
#define DXD_NAME 0x08 // name changed
#define DXD_COLOR 0x10 // color changed
#define DXD_FOLLOWER 0x20 // follower was changed
#define DXD_WEAPONPREF 0x40 // netsynced playsim settings were changed
@ -123,6 +131,8 @@ extern UINT8 demo_writerng;
#define DXD_PST_SPECTATING 0x02
#define DXD_PST_LEFT 0x03
#define DXD_PST_ISBOT 0x80 // extra flag
// Record/playback tics
void G_ReadDemoExtraData(void);
void G_WriteDemoExtraData(void);
@ -155,6 +165,8 @@ typedef struct demoghost {
UINT8 *buffer, *p, color;
UINT8 fadein;
UINT16 version;
UINT8 numskins;
democharlist_t *skinlist;
mobj_t oldmo, *mo;
struct demoghost *next;
} demoghost;

View file

@ -1748,6 +1748,7 @@ static boolean K_drawKartPositionFaces(void)
INT32 xoff, yoff, flipflag = 0;
UINT8 workingskin;
UINT8 *colormap;
UINT32 skinflags;
ranklines = 0;
memset(completed, 0, sizeof (completed));
@ -1832,10 +1833,13 @@ static boolean K_drawKartPositionFaces(void)
bumperx = FACE_X+19;
emeraldx = FACE_X+16;
skinflags = (demo.playback)
? demo.skinlist[demo.currentskinid[rankplayer[i]]].flags
: skins[players[rankplayer[i]].skin].flags;
// Flip SF_IRONMAN portraits, but only if they're transformed
if (skins[players[rankplayer[i]].skin].flags & SF_IRONMAN
&& !P_MobjWasRemoved(players[rankplayer[i]].mo)
&& !(((skin_t*)players[rankplayer[i]].mo->skin)->flags & SF_IRONMAN) )
if (skinflags & SF_IRONMAN
&& !(players[rankplayer[i]].charflags & SF_IRONMAN) )
{
flipflag = V_FLIP|V_VFLIP; // blonic flip
xoff = yoff = 16;

View file

@ -1927,9 +1927,16 @@ static void K_HandleLapIncrement(player_t *player)
{
P_DoPlayerExit(player);
P_SetupSignExit(player);
} else if (skins[player->skin].flags & SF_IRONMAN)
}
else
{
SetRandomFakePlayerSkin(player, true);
UINT32 skinflags = (demo.playback)
? demo.skinlist[demo.currentskinid[(player-players)]].flags
: skins[player->skin].flags;
if (skinflags & SF_IRONMAN)
{
SetRandomFakePlayerSkin(player, true);
}
}

View file

@ -4171,25 +4171,31 @@ void P_PlayerThink(player_t *player)
}
// Random skin / "ironman"
if ((!P_MobjWasRemoved(player->mo)) && (skins[player->skin].flags & SF_IRONMAN)) // we are Heavy Magician with a mobj
{
if (((skin_t *)player->mo->skin)->flags & SF_IRONMAN) // no fakeskin yet
UINT32 skinflags = (demo.playback)
? demo.skinlist[demo.currentskinid[playeri]].flags
: skins[player->skin].flags;
if (skinflags & SF_IRONMAN) // we are Heavy Magician
{
if (leveltime >= starttime && !player->exiting)
if (player->charflags & SF_IRONMAN) // no fakeskin yet
{
if (player->fakeskin != MAXSKINS)
if (leveltime >= starttime && !player->exiting)
{
SetFakePlayerSkin(player, player->fakeskin);
}
else if (!(gametyperules & GTR_CIRCUIT))
{
SetRandomFakePlayerSkin(player, false);
if (player->fakeskin != MAXSKINS)
{
SetFakePlayerSkin(player, player->fakeskin);
}
else if (!(gametyperules & GTR_CIRCUIT))
{
SetRandomFakePlayerSkin(player, false);
}
}
}
}
else if (player->exiting) // wearing a fakeskin, but need to display signpost postrace etc
{
ClearFakePlayerSkin(player);
else if (player->exiting) // wearing a fakeskin, but need to display signpost postrace etc
{
ClearFakePlayerSkin(player);
}
}
}

View file

@ -337,23 +337,53 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum)
}
// Set mo skin but not player_t skin, for ironman
void SetFakePlayerSkin(player_t* player, INT32 skinnum)
void SetFakePlayerSkin(player_t* player, INT32 skinid)
{
player->mo->skin = &skins[skinnum];
player->fakeskin = skinnum;
player->lastfakeskin = skinnum;
player->kartspeed = skins[skinnum].kartspeed;
player->kartweight = skins[skinnum].kartweight;
player->charflags = skins[skinnum].flags;
player->fakeskin = skinid;
if (demo.playback)
{
player->kartspeed = demo.skinlist[skinid].kartspeed;
player->kartweight = demo.skinlist[skinid].kartweight;
player->charflags = demo.skinlist[skinid].flags;
skinid = demo.skinlist[skinid].mapping;
}
else
{
player->kartspeed = skins[skinid].kartspeed;
player->kartweight = skins[skinid].kartweight;
player->charflags = skins[skinid].flags;
}
player->mo->skin = &skins[skinid];
}
// Loudly rerandomize
void SetRandomFakePlayerSkin(player_t* player, boolean fast)
{
INT32 i;
do {
i = P_RandomKey(PR_RANDOMSKIN, numskins);
} while (skins[i].flags & SF_IRONMAN || i == player->lastfakeskin);
UINT8 usableskins = 0, maxskinpick;
UINT8 grabskins[MAXSKINS];
maxskinpick = (demo.playback ? demo.numskins : numskins);
for (i = 0; i < maxskinpick; i++)
{
if (i == player->lastfakeskin)
continue;
if (demo.playback)
{
if (demo.skinlist[i].flags & SF_IRONMAN)
continue;
}
else if (skins[i].flags & SF_IRONMAN)
continue;
/*if (K_SkinLocked(i))
continue;*/
grabskins[usableskins++] = i;
}
i = grabskins[P_RandomKey(PR_RANDOMSKIN, usableskins)];
SetFakePlayerSkin(player, i);
@ -407,16 +437,28 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast)
// Return to base skin from an SF_IRONMAN randomization
void ClearFakePlayerSkin(player_t* player)
{
if ((skins[player->skin].flags & SF_IRONMAN) && !P_MobjWasRemoved(player->mo))
UINT8 skinid;
UINT32 flags;
if (demo.playback)
{
player->mo->skin = &skins[player->skin];
player->fakeskin = MAXSKINS;
player->kartspeed = skins[player->skin].kartspeed;
player->kartweight = skins[player->skin].kartweight;
player->charflags = skins[player->skin].flags;
skinid = demo.currentskinid[(player-players)];
flags = demo.skinlist[skinid].flags;
}
else
{
skinid = player->skin;
flags = skins[player->skin].flags;
}
if ((flags & SF_IRONMAN) && !P_MobjWasRemoved(player->mo))
{
SetFakePlayerSkin(player, skinid);
S_StartSound(player->mo, sfx_s3k9f);
K_SpawnMagicianParticles(player->mo, 5);
}
player->fakeskin = MAXSKINS;
}
//