diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 070ab105d..6ce85db27 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2550,6 +2550,9 @@ void CL_ClearPlayer(INT32 playernum) memset(&players[playernum], 0, sizeof (player_t)); + players[playernum].followerskin = -1; // don't have a ghost follower + players[playernum].fakeskin = players[playernum].lastfakeskin = MAXSKINS; // don't avoid eggman + RemoveAdminPlayer(playernum); // don't stay admin after you're gone } diff --git a/src/d_player.h b/src/d_player.h index d3b6975ed..50076e3aa 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -35,8 +35,8 @@ // Extra abilities/settings for skins (combinable stuff) typedef enum { - SF_HIRES = 1, // Draw the sprite at different size? - SF_MACHINE = 1<<1, // Beep boop. Are you a robot? + SF_MACHINE = 1, // Beep boop. Are you a robot? + SF_IRONMAN = 1<<1, // Pick a new skin during POSITION. I main Random! // free up to and including 1<<31 } skinflags_t; @@ -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 @@ -385,6 +387,9 @@ typedef struct player_s INT32 skin; UINT32 availabilities; + UINT8 fakeskin; // ironman + UINT8 lastfakeskin; + UINT8 kartspeed; // Kart speed stat between 1 and 9 UINT8 kartweight; // Kart weight stat between 1 and 9 diff --git a/src/deh_tables.c b/src/deh_tables.c index aa9dc9310..a36b7af9d 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3282,6 +3282,10 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi //"S_ITEMCAPSULE_BOTTOM", //"S_ITEMCAPSULE_INSIDE", + "S_MAGICIANBOX", + "S_MAGICIANBOXTOP", + "S_MAGICIANBOXBOTTOM", + // Signpost sparkles "S_SIGNSPARK1", "S_SIGNSPARK2", @@ -5284,6 +5288,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_FLOATINGITEM", "MT_ITEMCAPSULE", "MT_ITEMCAPSULE_PART", + "MT_MAGICIANBOX", "MT_SIGNSPARKLE", @@ -6323,8 +6328,8 @@ struct int_const_s const INT_CONST[] = { {"CR_ZOOMTUBE",CR_ZOOMTUBE}, // Character flags (skinflags_t) - {"SF_HIRES",SF_HIRES}, {"SF_MACHINE",SF_MACHINE}, + {"SF_IRONMAN",SF_IRONMAN}, // Sound flags {"SF_TOTALLYSINGLE",SF_TOTALLYSINGLE}, @@ -6691,6 +6696,7 @@ struct int_const_s const INT_CONST[] = { {"V_OVERLAY",V_OVERLAY}, {"V_ALLOWLOWERCASE",V_ALLOWLOWERCASE}, {"V_FLIP",V_FLIP}, + {"V_VFLIP",V_VFLIP}, {"V_SNAPTOTOP",V_SNAPTOTOP}, {"V_SNAPTOBOTTOM",V_SNAPTOBOTTOM}, {"V_SNAPTOLEFT",V_SNAPTOLEFT}, diff --git a/src/g_demo.c b/src/g_demo.c index 7978d0ef6..8566a4c22 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -94,8 +94,13 @@ static struct { // EZT_SCALE fixed_t scale, lastscale; - // EZT_KART - INT32 kartitem, kartamount, kartbumpers; + // EZT_ITEMDATA + SINT8 itemtype; + UINT8 itemamount, bumpers; + + // EZT_STATDATA + UINT8 skinid, kartspeed, kartweight; + UINT32 charflags; UINT8 desyncframes; // Don't try to resync unless we've been off for two frames, to monkeypatch a few trouble spots @@ -133,6 +138,7 @@ demoghost *ghosts = NULL; #define DEMO_SPECTATOR 0x01 #define DEMO_KICKSTART 0x02 #define DEMO_SHRINKME 0x04 +#define DEMO_BOT 0x08 // For demos #define ZT_FWD 0x01 @@ -172,13 +178,14 @@ static ticcmd_t oldcmd[MAXPLAYERS]; #define GZT_EXTRA 0x40 #define GZT_FOLLOW 0x80 // Followmobj -// GZT_EXTRA flags -#define EZT_COLOR 0x001 // Changed color (Super transformation, Mario fireflowers/invulnerability, etc.) -#define EZT_FLIP 0x002 // Reversed gravity -#define EZT_SCALE 0x004 // Changed size -#define EZT_HIT 0x008 // Damaged a mobj -#define EZT_SPRITE 0x010 // Changed sprite set completely out of PLAY (NiGHTS, SOCs, whatever) -#define EZT_KART 0x020 // SRB2Kart: Changed current held item/quantity and bumpers for battle +// GZT_EXTRA flags (currently UINT8) +#define EZT_COLOR 0x01 // Changed color (Super transformation, Mario fireflowers/invulnerability, etc.) +#define EZT_FLIP 0x02 // Reversed gravity +#define EZT_SCALE 0x04 // Changed size +#define EZT_HIT 0x08 // Damaged a mobj +#define EZT_SPRITE 0x10 // Changed sprite set completely out of PLAY (NiGHTS, SOCs, whatever) +#define EZT_ITEMDATA 0x20 // Changed current held item/quantity and bumpers for battle +#define EZT_STATDATA 0x40 // Changed skin/stats // GZT_FOLLOW flags #define FZT_SPAWNED 0x01 // just been spawned @@ -206,14 +213,23 @@ void G_LoadMetal(UINT8 **buffer) } // Finds a skin with the closest stats if the expected skin doesn't exist. -static INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight) +static INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight, UINT32 flags) { INT32 i, closest_skin = 0; - UINT8 closest_stats = UINT8_MAX, stat_diff; + UINT8 closest_stats, stat_diff; + boolean doflagcheck = true; + UINT32 flagcheck = flags; + +flaglessretry: + closest_stats = UINT8_MAX; for (i = 0; i < numskins; i++) { stat_diff = abs(skins[i].kartspeed - kartspeed) + abs(skins[i].kartweight - kartweight); + if (doflagcheck && (skins[i].flags & flagcheck) != flagcheck) + { + continue; + } if (stat_diff < closest_stats) { closest_stats = stat_diff; @@ -221,17 +237,22 @@ static INT32 GetSkinNumClosestToStats(UINT8 kartspeed, UINT8 kartweight) } } + if (stat_diff && (doflagcheck || closest_stats == UINT8_MAX)) + { + // Just grab *any* SF_IRONMAN if we don't get it on the first pass. + if ((flagcheck & SF_IRONMAN) && (flagcheck != SF_IRONMAN)) + { + flagcheck = SF_IRONMAN; + } + + doflagcheck = false; + + goto flaglessretry; + } + return closest_skin; } -static void FindClosestSkinForStats(UINT32 p, UINT8 kartspeed, UINT8 kartweight) -{ - INT32 closest_skin = GetSkinNumClosestToStats(kartspeed, kartweight); - - //CONS_Printf("Using %s instead...\n", skins[closest_skin].name); - SetPlayerSkinByNum(p, closest_skin); -} - void G_ReadDemoExtraData(void) { INT32 p, extradata, i; @@ -255,6 +276,64 @@ void G_ReadDemoExtraData(void) { extradata = READUINT8(demo_p); + if (extradata & DXD_PLAYSTATE) + { + i = READUINT8(demo_p); + + if (!playeringame[p]) + { + CL_ClearPlayer(p); + playeringame[p] = true; + G_AddPlayer(p); + players[p].spectator = true; + } + + if ((players[p].bot = !!(i & DXD_PST_ISBOT))) + { + players[p].botvars.difficulty = READUINT8(demo_p); + players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic + players[p].botvars.rival = (boolean)READUINT8(demo_p); + + i &= ~DXD_PST_ISBOT; + } + + switch (i) { + case DXD_PST_PLAYING: + if (players[p].bot) + { + players[p].spectator = false; + } + else + { + players[p].pflags |= PF_WANTSTOJOIN; + } + //CONS_Printf("player %s is despectating on tic %d\n", player_names[p], leveltime); + break; + + case DXD_PST_SPECTATING: + players[p].pflags &= ~PF_WANTSTOJOIN; // double-fuck you + if (players[p].spectator != true) + { + //CONS_Printf("player %s is spectating on tic %d\n", player_names[p], leveltime); + players[p].spectator = true; + if (players[p].mo) + P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_INSTAKILL); + else + players[p].playerstate = PST_REBORN; + } + break; + + case DXD_PST_LEFT: + CL_RemovePlayer(p, 0); + break; + } + + G_ResetViews(); + + // maybe these are necessary? + K_CheckBumpers(); + P_CheckRacers(); + } if (extradata & DXD_RESPAWN) { if (players[p].mo) @@ -265,21 +344,19 @@ void G_ReadDemoExtraData(void) } if (extradata & DXD_SKIN) { - UINT8 kartspeed, kartweight; + UINT8 skinid; // Skin - M_Memcpy(name, demo_p, 16); - demo_p += 16; - SetPlayerSkin(p, name); - kartspeed = READUINT8(demo_p); - kartweight = READUINT8(demo_p); + skinid = READUINT8(demo_p); + if (skinid >= demo.numskins) + skinid = 0; + SetPlayerSkinByNum(p, demo.skinlist[skinid].mapping); + demo.currentskinid[p] = skinid; - if (stricmp(skins[players[p].skin].name, name) != 0) - FindClosestSkinForStats(p, kartspeed, kartweight); - - players[p].kartspeed = kartspeed; - players[p].kartweight = kartweight; + players[p].kartspeed = ghostext[p].kartspeed = demo.skinlist[skinid].kartspeed; + players[p].kartweight = ghostext[p].kartweight = demo.skinlist[skinid].kartweight; + players[p].charflags = ghostext[p].charflags = demo.skinlist[skinid].flags; } if (extradata & DXD_COLOR) { @@ -320,48 +397,6 @@ void G_ReadDemoExtraData(void) } } } - if (extradata & DXD_PLAYSTATE) - { - i = READUINT8(demo_p); - - switch (i) { - case DXD_PST_PLAYING: - players[p].pflags |= PF_WANTSTOJOIN; // fuck you - //CONS_Printf("player %s is despectating on tic %d\n", player_names[p], leveltime); - break; - - case DXD_PST_SPECTATING: - players[p].pflags &= ~PF_WANTSTOJOIN; // double-fuck you - if (!playeringame[p]) - { - CL_ClearPlayer(p); - playeringame[p] = true; - G_AddPlayer(p); - players[p].spectator = true; - //CONS_Printf("player %s is joining server on tic %d\n", player_names[p], leveltime); - } - else - { - //CONS_Printf("player %s is spectating on tic %d\n", player_names[p], leveltime); - players[p].spectator = true; - if (players[p].mo) - P_DamageMobj(players[p].mo, NULL, NULL, 1, DMG_INSTAKILL); - else - players[p].playerstate = PST_REBORN; - } - break; - - case DXD_PST_LEFT: - CL_RemovePlayer(p, 0); - break; - } - - G_ResetViews(); - - // maybe these are necessary? - K_CheckBumpers(); - P_CheckRacers(); - } if (extradata & DXD_WEAPONPREF) { WeaponPref_Parse(&demo_p, p); @@ -375,6 +410,7 @@ void G_ReadDemoExtraData(void) while (p != DW_END) { UINT32 rng; + boolean storesynced = demosynced; switch (p) { @@ -388,10 +424,11 @@ void G_ReadDemoExtraData(void) P_SetRandSeed(i, rng); if (demosynced) - CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced (RNG)!\n")); - demosynced = false; + CONS_Alert(CONS_WARNING, "Demo playback has desynced (RNG class %d)!\n", i); + storesynced = false; } } + demosynced = storesynced; } p = READUINT8(demo_p); @@ -417,18 +454,43 @@ void G_WriteDemoExtraData(void) WRITEUINT8(demo_p, i); WRITEUINT8(demo_p, demo_extradata[i]); + if (demo_extradata[i] & DXD_PLAYSTATE) + { + UINT8 pst = DXD_PST_PLAYING; + + demo_writerng = 1; + + if (!playeringame[i]) + { + pst = DXD_PST_LEFT; + } + else if ( + players[i].spectator && + !(players[i].pflags & PF_WANTSTOJOIN) // <= fuck you specifically + ) + { + pst = DXD_PST_SPECTATING; + } + + if (players[i].bot) + { + pst |= DXD_PST_ISBOT; + } + + WRITEUINT8(demo_p, pst); + + if (pst & DXD_PST_ISBOT) + { + WRITEUINT8(demo_p, players[i].botvars.difficulty); + WRITEUINT8(demo_p, players[i].botvars.diffincrease); // needed to avoid having to duplicate logic + WRITEUINT8(demo_p, (UINT8)players[i].botvars.rival); + } + } //if (demo_extradata[i] & DXD_RESPAWN) has no extra data if (demo_extradata[i] & DXD_SKIN) { // Skin - memset(name, 0, 16); - strncpy(name, skins[players[i].skin].name, 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; - - WRITEUINT8(demo_p, skins[players[i].skin].kartspeed); - WRITEUINT8(demo_p, skins[players[i].skin].kartweight); - + WRITEUINT8(demo_p, players[i].skin); } if (demo_extradata[i] & DXD_COLOR) { @@ -469,19 +531,6 @@ void G_WriteDemoExtraData(void) demo_p += 16; } - if (demo_extradata[i] & DXD_PLAYSTATE) - { - demo_writerng = 1; - if (!playeringame[i]) - WRITEUINT8(demo_p, DXD_PST_LEFT); - else if ( - players[i].spectator && - !(players[i].pflags & PF_WANTSTOJOIN) // <= fuck you specifically - ) - WRITEUINT8(demo_p, DXD_PST_SPECTATING); - else - WRITEUINT8(demo_p, DXD_PST_PLAYING); - } if (demo_extradata[i] & DXD_WEAPONPREF) { WeaponPref_Save(&demo_p, i); @@ -788,15 +837,29 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) } if (ghost->player && ( - ghostext[playernum].kartitem != ghost->player->itemtype || - ghostext[playernum].kartamount != ghost->player->itemamount || - ghostext[playernum].kartbumpers != ghost->player->bumpers + ghostext[playernum].itemtype != ghost->player->itemtype || + ghostext[playernum].itemamount != ghost->player->itemamount || + ghostext[playernum].bumpers != ghost->player->bumpers )) { - ghostext[playernum].flags |= EZT_KART; - ghostext[playernum].kartitem = ghost->player->itemtype; - ghostext[playernum].kartamount = ghost->player->itemamount; - ghostext[playernum].kartbumpers = ghost->player->bumpers; + ghostext[playernum].flags |= EZT_ITEMDATA; + ghostext[playernum].itemtype = ghost->player->itemtype; + ghostext[playernum].itemamount = ghost->player->itemamount; + ghostext[playernum].bumpers = ghost->player->bumpers; + } + + if (ghost->player && ( + ghostext[playernum].skinid != (UINT8)(((skin_t *)ghost->skin)-skins) || + ghostext[playernum].kartspeed != ghost->player->kartspeed || + ghostext[playernum].kartweight != ghost->player->kartweight || + ghostext[playernum].charflags != ghost->player->charflags + )) + { + ghostext[playernum].flags |= EZT_STATDATA; + ghostext[playernum].skinid = (UINT8)(((skin_t *)ghost->skin)-skins); + ghostext[playernum].kartspeed = ghost->player->kartspeed; + ghostext[playernum].kartweight = ghost->player->kartweight; + ghostext[playernum].charflags = ghost->player->charflags; } if (ghostext[playernum].flags) @@ -840,11 +903,18 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) } if (ghostext[playernum].flags & EZT_SPRITE) WRITEUINT16(demo_p,oldghost[playernum].sprite); - if (ghostext[playernum].flags & EZT_KART) + if (ghostext[playernum].flags & EZT_ITEMDATA) { - WRITEINT32(demo_p, ghostext[playernum].kartitem); - WRITEINT32(demo_p, ghostext[playernum].kartamount); - WRITEINT32(demo_p, ghostext[playernum].kartbumpers); + WRITESINT8(demo_p, ghostext[playernum].itemtype); + WRITEUINT8(demo_p, ghostext[playernum].itemamount); + WRITEUINT8(demo_p, ghostext[playernum].bumpers); + } + if (ghostext[playernum].flags & EZT_STATDATA) + { + WRITEUINT8(demo_p,ghostext[playernum].skinid); + WRITEUINT8(demo_p,ghostext[playernum].kartspeed); + WRITEUINT8(demo_p,ghostext[playernum].kartweight); + WRITEUINT32(demo_p, ghostext[playernum].charflags); } ghostext[playernum].flags = 0; @@ -1016,11 +1086,20 @@ void G_ConsGhostTic(INT32 playernum) } if (xziptic & EZT_SPRITE) demo_p += sizeof(UINT16); - if (xziptic & EZT_KART) + if (xziptic & EZT_ITEMDATA) { - ghostext[playernum].kartitem = READINT32(demo_p); - ghostext[playernum].kartamount = READINT32(demo_p); - ghostext[playernum].kartbumpers = READINT32(demo_p); + ghostext[playernum].itemtype = READSINT8(demo_p); + ghostext[playernum].itemamount = READUINT8(demo_p); + ghostext[playernum].bumpers = READUINT8(demo_p); + } + if (xziptic & EZT_STATDATA) + { + ghostext[playernum].skinid = READUINT8(demo_p); + if (ghostext[playernum].skinid >= demo.numskins) + ghostext[playernum].skinid = 0; + ghostext[playernum].kartspeed = READUINT8(demo_p); + ghostext[playernum].kartweight = READUINT8(demo_p); + ghostext[playernum].charflags = READUINT32(demo_p); } } @@ -1079,17 +1158,41 @@ void G_ConsGhostTic(INT32 playernum) else ghostext[playernum].desyncframes = 0; - if (players[playernum].itemtype != ghostext[playernum].kartitem - || players[playernum].itemamount != ghostext[playernum].kartamount - || players[playernum].bumpers != ghostext[playernum].kartbumpers) + if (players[playernum].itemtype != ghostext[playernum].itemtype + || players[playernum].itemamount != ghostext[playernum].itemamount + || players[playernum].bumpers != ghostext[playernum].bumpers) { if (demosynced) CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced (item/bumpers)!\n")); demosynced = false; - players[playernum].itemtype = ghostext[playernum].kartitem; - players[playernum].itemamount = ghostext[playernum].kartamount; - players[playernum].bumpers = ghostext[playernum].kartbumpers; + players[playernum].itemtype = ghostext[playernum].itemtype; + players[playernum].itemamount = ghostext[playernum].itemamount; + players[playernum].bumpers = ghostext[playernum].bumpers; + } + + if (players[playernum].kartspeed != ghostext[playernum].kartspeed + || players[playernum].kartweight != ghostext[playernum].kartweight + || players[playernum].charflags != ghostext[playernum].charflags || + demo.skinlist[ghostext[playernum].skinid].mapping != (UINT8)(((skin_t *)testmo->skin)-skins)) + { + if (demosynced) + CONS_Alert(CONS_WARNING, M_GetText("Demo playback has desynced (Character/stats)!\n")); + demosynced = false; + + testmo->skin = &skins[demo.skinlist[ghostext[playernum].skinid].mapping]; + players[playernum].kartspeed = ghostext[playernum].kartspeed; + players[playernum].kartweight = ghostext[playernum].kartweight; + players[playernum].charflags = ghostext[playernum].charflags; + + if (demo.skinlist[demo.currentskinid[playernum]].flags & SF_IRONMAN) + { + players[playernum].lastfakeskin = players[playernum].fakeskin; + players[playernum].fakeskin = + (ghostext[playernum].skinid == demo.currentskinid[playernum]) + ? MAXSKINS + : ghostext[playernum].skinid; + } } } @@ -1115,16 +1218,16 @@ void G_GhostTicker(void) if (ziptic == 0) // Only support player 0 info for now { ziptic = READUINT8(g->p); + if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING) + I_Error("Ghost is not a record attack ghost PLAYSTATE"); //@TODO lmao don't blow up like this if (ziptic & DXD_SKIN) - g->p += 18; // We _could_ read this info, but it shouldn't change anything in record attack... + g->p++; // We _could_ read this info, but it shouldn't change anything in record attack... if (ziptic & DXD_COLOR) g->p += 16; // Same tbh if (ziptic & DXD_NAME) g->p += 16; // yea if (ziptic & DXD_FOLLOWER) g->p += 32; // ok (32 because there's both the skin and the colour) - if (ziptic & DXD_PLAYSTATE && READUINT8(g->p) != DXD_PST_PLAYING) - I_Error("Ghost is not a record attack ghost PLAYSTATE"); //@TODO lmao don't blow up like this if (ziptic & DXD_WEAPONPREF) g->p++; // ditto } @@ -1201,13 +1304,6 @@ void G_GhostTicker(void) g->mo->z = g->oldmo.z; P_SetThingPosition(g->mo); g->mo->angle = g->oldmo.angle; - g->mo->frame = g->oldmo.frame | tr_trans30<fadein) - { - g->mo->frame += (((--g->fadein)/6)<fadein is bad, and it's only set once, so... - g->mo->renderflags &= ~RF_DONTDRAW; - } - g->mo->sprite2 = g->oldmo.sprite2; if (ziptic & GZT_EXTRA) { // But wait, there's more! @@ -1218,9 +1314,6 @@ void G_GhostTicker(void) switch(g->color) { default: - case GHC_RETURNSKIN: - g->mo->skin = g->oldmo.skin; - /* FALLTHRU */ case GHC_NORMAL: // Go back to skin color g->mo->color = g->oldmo.color; break; @@ -1228,9 +1321,6 @@ void G_GhostTicker(void) case GHC_SUPER: case GHC_INVINCIBLE: break; - case GHC_FIREFLOWER: // Fireflower - g->mo->color = SKINCOLOR_WHITE; - break; } } if (xziptic & EZT_FLIP) @@ -1270,8 +1360,25 @@ void G_GhostTicker(void) } if (xziptic & EZT_SPRITE) g->mo->sprite = READUINT16(g->p); - if (xziptic & EZT_KART) - g->p += 12; // kartitem, kartamount, kartbumpers + if (xziptic & EZT_ITEMDATA) + g->p += 3; // itemtype, itemamount, bumpers + if (xziptic & EZT_STATDATA) + { + UINT8 skinid = READUINT8(g->p); + if (skinid >= g->numskins) + skinid = 0; + g->mo->skin = &skins[g->skinlist[skinid].mapping]; + g->p += 6; // kartspeed, kartweight, charflags + } + } + + // todo better defaulting + g->mo->sprite2 = g->oldmo.sprite2; + g->mo->frame = g->oldmo.frame | tr_trans30<fadein) + { + g->mo->frame += (((--g->fadein)/6)<fadein is bad, and it's only set once, so... + g->mo->renderflags &= ~RF_DONTDRAW; } #define follow g->mo->tracer @@ -1381,6 +1488,7 @@ skippedghosttic: p->next = g->next; else ghosts = g->next; + Z_Free(g->skinlist); Z_Free(g); continue; } @@ -1954,291 +2062,24 @@ void G_RecordMetal(void) metalrecording = true; } -void G_BeginRecording(void) +static void G_SaveDemoExtraFiles(UINT8 **pp) { - UINT8 i, j, p; - char name[MAXCOLORNAME+1]; - player_t *player = &players[consoleplayer]; - char *filename; - UINT8 totalfiles; - UINT8 *m; + UINT8 totalfiles = 0, i; + UINT8 *m = (*pp);/* file count */ + (*pp)++; - if (demo_p) - return; - memset(name,0,sizeof(name)); - - demo_p = demobuffer; - demoflags = DF_GHOST|(multiplayer ? DF_MULTIPLAYER : (modeattacking<lumpname, MAXMAPLUMPNAME); - M_Memcpy(demo_p, mapmd5, 16); demo_p += 16; - - WRITEUINT8(demo_p, demoflags); - WRITEUINT8(demo_p, gametype & 0xFF); - WRITEUINT8(demo_p, numlaps); - - // file list - m = demo_p;/* file count */ - demo_p += 1; - - totalfiles = 0; for (i = mainwads; ++i < numwadfiles; ) if (wadfiles[i]->important) { nameonly(( filename = va("%s", wadfiles[i]->filename) )); - WRITESTRINGL(demo_p, filename, MAX_WADPATH); - WRITEMEM(demo_p, wadfiles[i]->md5sum, 16); + WRITESTRINGL((*pp), filename, MAX_WADPATH); + WRITEMEM((*pp), wadfiles[i]->md5sum, 16); totalfiles++; } WRITEUINT8(m, totalfiles); - - switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) - { - case ATTACKING_NONE: // 0 - break; - case ATTACKING_TIME: // 1 - demotime_p = demo_p; - WRITEUINT32(demo_p,UINT32_MAX); // time - WRITEUINT32(demo_p,UINT32_MAX); // lap - break; - case ATTACKING_CAPSULES: // 2 - demotime_p = demo_p; - WRITEUINT32(demo_p,UINT32_MAX); // time - break; - default: // 3 - break; - } - - for (i = 0; i < PRNUMCLASS; i++) - { - WRITEUINT32(demo_p, P_GetInitSeed(i)); - } - - // Reserved for extrainfo location from start of file - demoinfo_p = demo_p; - WRITEUINT32(demo_p, 0); - - // Save netvar data - CV_SaveDemoVars(&demo_p); - - // Now store some info for each in-game player - - // Lat' 12/05/19: Do note that for the first game you load, everything that gets saved here is total garbage; - // The name will always be Player , the skin sonic, the color None and the follower 0. This is only correct on subsequent games. - // In the case of said first game, the skin and the likes are updated with Got_NameAndColor, which are then saved in extradata for the demo with DXD_SKIN in r_things.c for instance. - - - for (p = 0; p < MAXPLAYERS; p++) { - if (playeringame[p]) { - player = &players[p]; - WRITEUINT8(demo_p, p); - - i = 0; - if (player->spectator) - i |= DEMO_SPECTATOR; - if (player->pflags & PF_KICKSTARTACCEL) - i |= DEMO_KICKSTART; - if (player->pflags & PF_SHRINKME) - i |= DEMO_SHRINKME; - WRITEUINT8(demo_p, i); - - // Name - memset(name, 0, 16); - strncpy(name, player_names[p], 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; - - // Skin - memset(name, 0, 16); - strncpy(name, skins[player->skin].name, 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; - - // Color - memset(name, 0, 16); - strncpy(name, skincolors[player->skincolor].name, 16); - M_Memcpy(demo_p,name,16); - demo_p += 16; - - // Save follower's skin name - // PS: We must check for 'follower' to determine if the followerskin is valid. It's going to be 0 if we don't have a follower, but 0 is also absolutely a valid follower! - // Doesn't really matter if the follower mobj is valid so long as it exists in a way or another. - - memset(name, 0, 16); - if (player->follower) - strncpy(name, followers[player->followerskin].name, 16); - else - strncpy(name, "None", 16); // Say we don't have one, then. - - M_Memcpy(demo_p,name,16); - demo_p += 16; - - // Save follower's colour - memset(name, 0, 16); - for (j = (numskincolors+2)-1; j > 0; j--) - { - if (Followercolor_cons_t[j].value == players[i].followercolor) - break; - } - strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" - M_Memcpy(demo_p, name, 16); - demo_p += 16; - - // Score, since Kart uses this to determine where you start on the map - WRITEUINT32(demo_p, player->score); - - // Power Levels - WRITEUINT16(demo_p, clientpowerlevels[p][gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE]); - - // Kart speed and weight - WRITEUINT8(demo_p, skins[player->skin].kartspeed); - WRITEUINT8(demo_p, skins[player->skin].kartweight); - - // And mobjtype_t is best with UINT32 too... - WRITEUINT32(demo_p, player->followitem); - } - } - - WRITEUINT8(demo_p, 0xFF); // Denote the end of the player listing - - // player lua vars, always saved even if empty - if (demoflags & DF_LUAVARS) - LUA_Archive(&demo_p); - - memset(&oldcmd,0,sizeof(oldcmd)); - memset(&oldghost,0,sizeof(oldghost)); - memset(&ghostext,0,sizeof(ghostext)); - - for (i = 0; i < MAXPLAYERS; i++) - { - ghostext[i].lastcolor = ghostext[i].color = GHC_NORMAL; - ghostext[i].lastscale = ghostext[i].scale = FRACUNIT; - - if (players[i].mo) - { - oldghost[i].x = players[i].mo->x; - oldghost[i].y = players[i].mo->y; - oldghost[i].z = players[i].mo->z; - oldghost[i].angle = players[i].mo->angle; - - // preticker started us gravity flipped - if (players[i].mo->eflags & MFE_VERTICALFLIP) - ghostext[i].flags |= EZT_FLIP; - } - } -} - -void G_BeginMetal(void) -{ - mobj_t *mo = players[consoleplayer].mo; - -#if 0 - if (demo_p) - return; -#endif - - demo_p = demobuffer; - - // Write header. - M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12; - WRITEUINT8(demo_p,VERSION); - WRITEUINT8(demo_p,SUBVERSION); - WRITEUINT16(demo_p,DEMOVERSION); - - // demo checksum - demo_p += 16; - - M_Memcpy(demo_p, "METL", 4); demo_p += 4; - - memset(&ghostext,0,sizeof(ghostext)); - ghostext[0].lastscale = ghostext[0].scale = FRACUNIT; - - // Set up our memory. - memset(&oldmetal,0,sizeof(oldmetal)); - oldmetal.x = mo->x; - oldmetal.y = mo->y; - oldmetal.z = mo->z; - oldmetal.angle = mo->angle>>24; -} - -void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT16 color, UINT32 val) -{ - char temp[16]; - - if (demoinfo_p && *(UINT32 *)demoinfo_p == 0) - { - WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker - *(UINT32 *)demoinfo_p = demo_p - demobuffer; - } - - WRITEUINT8(demo_p, DW_STANDING); - WRITEUINT8(demo_p, ranking); - - // Name - memset(temp, 0, 16); - strncpy(temp, name, 16); - M_Memcpy(demo_p,temp,16); - demo_p += 16; - - // Skin - memset(temp, 0, 16); - strncpy(temp, skins[skinnum].name, 16); - M_Memcpy(demo_p,temp,16); - demo_p += 16; - - // Color - memset(temp, 0, 16); - strncpy(temp, skincolors[color].name, 16); - M_Memcpy(demo_p,temp,16); - demo_p += 16; - - // Score/time/whatever - WRITEUINT32(demo_p, val); -} - -void G_SetDemoTime(UINT32 ptime, UINT32 plap) -{ - if (!demo.recording || !demotime_p) - return; - if (demoflags & DF_TIMEATTACK) - { - WRITEUINT32(demotime_p, ptime); - WRITEUINT32(demotime_p, plap); - demotime_p = NULL; - } - else if (demoflags & DF_BREAKTHECAPSULES) - { - WRITEUINT32(demotime_p, ptime); - (void)plap; - demotime_p = NULL; - } } static void G_LoadDemoExtraFiles(UINT8 **pp) @@ -2403,6 +2244,367 @@ static UINT8 G_CheckDemoExtraFiles(UINT8 **pp, boolean quick) return error; } +static void G_SaveDemoSkins(UINT8 **pp) +{ + char skin[16]; + UINT8 i; + + WRITEUINT8((*pp), numskins); + for (i = 0; i < numskins; i++) + { + // Skinname, for first attempt at identification. + memset(skin, 0, 16); + strncpy(skin, skins[i].name, 16); + WRITEMEM((*pp), skin, 16); + + // Backup information for second pass. + WRITEUINT8((*pp), skins[i].kartspeed); + WRITEUINT8((*pp), skins[i].kartweight); + WRITEUINT32((*pp), skins[i].flags); + } +} + +static democharlist_t *G_LoadDemoSkins(UINT8 **pp, UINT8 *worknumskins, boolean getclosest) +{ + char skin[17]; + UINT8 i; + democharlist_t *skinlist = NULL; + + (*worknumskins) = READUINT8((*pp)); + if (!(*worknumskins)) + return NULL; + + skinlist = Z_Calloc(sizeof(democharlist_t) * (*worknumskins), PU_STATIC, NULL); + if (!skinlist) + { + I_Error("G_LoadDemoSkins: Insufficient memory to allocate list"); + } + + skin[16] = '\0'; + + for (i = 0; i < (*worknumskins); i++) + { + INT32 result = -1; + + READMEM((*pp), skin, 16); + skinlist[i].kartspeed = READUINT8((*pp)); + skinlist[i].kartweight = READUINT8((*pp)); + skinlist[i].flags = READUINT32((*pp)); + + result = R_SkinAvailable(skin); + if (result == -1) + { + if (!getclosest) + { + result = MAXSKINS; + } + else + { + result = GetSkinNumClosestToStats(skinlist[i].kartspeed, skinlist[i].kartweight, skinlist[i].flags); + } + } + + if (result != -1) + { + skinlist[i].mapping = (UINT8)result; + } + } + + return skinlist; +} + +static void G_SkipDemoSkins(UINT8 **pp) +{ + UINT8 demonumskins; + UINT8 i; + + demonumskins = READUINT8((*pp)); + for (i = 0; i < demonumskins; ++i) + { + (*pp) += 16; // name + (*pp)++; // kartspeed + (*pp)++; // kartweight + (*pp) += 4; // flags + } +} + +void G_BeginRecording(void) +{ + UINT8 i, j, p; + char name[MAXCOLORNAME+1]; + player_t *player = &players[consoleplayer]; + + if (demo_p) + return; + memset(name,0,sizeof(name)); + + demo_p = demobuffer; + demoflags = DF_GHOST|(multiplayer ? DF_MULTIPLAYER : (modeattacking<lumpname, MAXMAPLUMPNAME); + M_Memcpy(demo_p, mapmd5, 16); demo_p += 16; + + WRITEUINT8(demo_p, demoflags); + WRITEUINT8(demo_p, gametype & 0xFF); + WRITEUINT8(demo_p, numlaps); + + // file list + G_SaveDemoExtraFiles(&demo_p); + + // character list + G_SaveDemoSkins(&demo_p); + + switch ((demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) + { + case ATTACKING_NONE: // 0 + break; + case ATTACKING_TIME: // 1 + demotime_p = demo_p; + WRITEUINT32(demo_p,UINT32_MAX); // time + WRITEUINT32(demo_p,UINT32_MAX); // lap + break; + case ATTACKING_CAPSULES: // 2 + demotime_p = demo_p; + WRITEUINT32(demo_p,UINT32_MAX); // time + break; + default: // 3 + break; + } + + for (i = 0; i < PRNUMCLASS; i++) + { + WRITEUINT32(demo_p, P_GetInitSeed(i)); + } + + // Reserved for extrainfo location from start of file + demoinfo_p = demo_p; + WRITEUINT32(demo_p, 0); + + // Save netvar data + CV_SaveDemoVars(&demo_p); + + // Now store some info for each in-game player + + // Lat' 12/05/19: Do note that for the first game you load, everything that gets saved here is total garbage; + // The name will always be Player , the skin sonic, the color None and the follower 0. This is only correct on subsequent games. + // In the case of said first game, the skin and the likes are updated with Got_NameAndColor, which are then saved in extradata for the demo with DXD_SKIN in r_things.c for instance. + + + for (p = 0; p < MAXPLAYERS; p++) { + if (playeringame[p]) { + player = &players[p]; + WRITEUINT8(demo_p, p); + + i = 0; + if (player->spectator == true) + i |= DEMO_SPECTATOR; + if (player->pflags & PF_KICKSTARTACCEL) + i |= DEMO_KICKSTART; + if (player->pflags & PF_SHRINKME) + i |= DEMO_SHRINKME; + if (player->bot == true) + i |= DEMO_BOT; + WRITEUINT8(demo_p, i); + + if (i & DEMO_BOT) + { + WRITEUINT8(demo_p, player->botvars.difficulty); + WRITEUINT8(demo_p, player->botvars.diffincrease); // needed to avoid having to duplicate logic + WRITEUINT8(demo_p, (UINT8)player->botvars.rival); + } + + // Name + memset(name, 0, 16); + strncpy(name, player_names[p], 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Skin (now index into demo.skinlist) + WRITEUINT8(demo_p, player->skin); + WRITEUINT8(demo_p, player->lastfakeskin); + + // Color + memset(name, 0, 16); + strncpy(name, skincolors[player->skincolor].name, 16); + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Save follower's skin name + // PS: We must check for 'follower' to determine if the followerskin is valid. It's going to be 0 if we don't have a follower, but 0 is also absolutely a valid follower! + // Doesn't really matter if the follower mobj is valid so long as it exists in a way or another. + + memset(name, 0, 16); + if (player->follower) + strncpy(name, followers[player->followerskin].name, 16); + else + strncpy(name, "None", 16); // Say we don't have one, then. + + M_Memcpy(demo_p,name,16); + demo_p += 16; + + // Save follower's colour + memset(name, 0, 16); + for (j = (numskincolors+2)-1; j > 0; j--) + { + if (Followercolor_cons_t[j].value == players[i].followercolor) + break; + } + strncpy(name, Followercolor_cons_t[j].strvalue, 16); // Not KartColor_Names because followercolor has extra values such as "Match" + M_Memcpy(demo_p, name, 16); + demo_p += 16; + + // Score, since Kart uses this to determine where you start on the map + WRITEUINT32(demo_p, player->score); + + // Power Levels + j = gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE; + WRITEUINT16(demo_p, clientpowerlevels[p][j]); + + // And mobjtype_t is best with UINT32 too... + WRITEUINT32(demo_p, player->followitem); + } + } + + WRITEUINT8(demo_p, 0xFF); // Denote the end of the player listing + + // player lua vars, always saved even if empty + if (demoflags & DF_LUAVARS) + LUA_Archive(&demo_p); + + memset(&oldcmd,0,sizeof(oldcmd)); + memset(&oldghost,0,sizeof(oldghost)); + memset(&ghostext,0,sizeof(ghostext)); + + for (i = 0; i < MAXPLAYERS; i++) + { + ghostext[i].lastcolor = ghostext[i].color = GHC_NORMAL; + ghostext[i].lastscale = ghostext[i].scale = FRACUNIT; + ghostext[i].skinid = players[i].skin; + ghostext[i].kartspeed = players[i].kartspeed; + ghostext[i].kartweight = players[i].kartweight; + ghostext[i].charflags = players[i].charflags; + + if (players[i].mo) + { + oldghost[i].x = players[i].mo->x; + oldghost[i].y = players[i].mo->y; + oldghost[i].z = players[i].mo->z; + oldghost[i].angle = players[i].mo->angle; + + // preticker started us gravity flipped + if (players[i].mo->eflags & MFE_VERTICALFLIP) + ghostext[i].flags |= EZT_FLIP; + } + } +} + +void G_BeginMetal(void) +{ + mobj_t *mo = players[consoleplayer].mo; + +#if 0 + if (demo_p) + return; +#endif + + demo_p = demobuffer; + + // Write header. + M_Memcpy(demo_p, DEMOHEADER, 12); demo_p += 12; + WRITEUINT8(demo_p,VERSION); + WRITEUINT8(demo_p,SUBVERSION); + WRITEUINT16(demo_p,DEMOVERSION); + + // demo checksum + demo_p += 16; + + M_Memcpy(demo_p, "METL", 4); demo_p += 4; + + memset(&ghostext,0,sizeof(ghostext)); + ghostext[0].lastscale = ghostext[0].scale = FRACUNIT; + + // Set up our memory. + memset(&oldmetal,0,sizeof(oldmetal)); + oldmetal.x = mo->x; + oldmetal.y = mo->y; + oldmetal.z = mo->z; + oldmetal.angle = mo->angle>>24; +} + +void G_WriteStanding(UINT8 ranking, char *name, INT32 skinnum, UINT16 color, UINT32 val) +{ + char temp[16]; + + if (demoinfo_p && *(UINT32 *)demoinfo_p == 0) + { + WRITEUINT8(demo_p, DEMOMARKER); // add the demo end marker + *(UINT32 *)demoinfo_p = demo_p - demobuffer; + } + + WRITEUINT8(demo_p, DW_STANDING); + WRITEUINT8(demo_p, ranking); + + // Name + memset(temp, 0, 16); + strncpy(temp, name, 16); + M_Memcpy(demo_p,temp,16); + demo_p += 16; + + // Skin + WRITEUINT8(demo_p, skinnum); + + // Color + memset(temp, 0, 16); + strncpy(temp, skincolors[color].name, 16); + M_Memcpy(demo_p,temp,16); + demo_p += 16; + + // Score/time/whatever + WRITEUINT32(demo_p, val); +} + +void G_SetDemoTime(UINT32 ptime, UINT32 plap) +{ + if (!demo.recording || !demotime_p) + return; + if (demoflags & DF_TIMEATTACK) + { + WRITEUINT32(demotime_p, ptime); + WRITEUINT32(demotime_p, plap); + demotime_p = NULL; + } + else if (demoflags & DF_BREAKTHECAPSULES) + { + WRITEUINT32(demotime_p, ptime); + (void)plap; + demotime_p = NULL; + } +} + // Returns bitfield: // 1 == new demo has lower time // 2 == new demo has higher score @@ -2445,6 +2647,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) p++; // numlaps G_SkipDemoExtraFiles(&p); + G_SkipDemoSkins(&p); + aflags = flags & (DF_TIMEATTACK|DF_BREAKTHECAPSULES); I_Assert(aflags); @@ -2509,6 +2713,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) return UINT8_MAX; } + G_SkipDemoSkins(&p); + oldtime = READUINT32(p); if (uselaps) oldlap = READUINT32(p); @@ -2540,7 +2746,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) void G_LoadDemoInfo(menudemo_t *pdemo) { UINT8 *infobuffer, *info_p, *extrainfo_p; - UINT8 version, subversion, pdemoflags; + UINT8 version, subversion, pdemoflags, worknumskins, skinid; + democharlist_t *skinlist = NULL; UINT16 pdemoversion, count; char mapname[MAXMAPLUMPNAME]; INT32 i; @@ -2611,6 +2818,13 @@ void G_LoadDemoInfo(menudemo_t *pdemo) pdemo->addonstatus = G_CheckDemoExtraFiles(&info_p, true); + skinlist = G_LoadDemoSkins(&info_p, &worknumskins, false); + if (!skinlist) + { + CONS_Alert(CONS_ERROR, M_GetText("%s has an invalid skin list.\n"), pdemo->filepath); + goto badreplay; + } + for (i = 0; i < PRNUMCLASS; i++) { info_p += 4; // RNG seed @@ -2657,15 +2871,10 @@ void G_LoadDemoInfo(menudemo_t *pdemo) extrainfo_p += 16; // Skin - M_Memcpy(temp,extrainfo_p,16); - extrainfo_p += 16; - pdemo->standings[count].skin = UINT8_MAX; - for (i = 0; i < numskins; i++) - if (stricmp(skins[i].name, temp) == 0) - { - pdemo->standings[count].skin = i; - break; - } + skinid = READUINT8(extrainfo_p); + if (skinid > worknumskins) + skinid = 0; + pdemo->standings[count].skin = skinlist[skinid].mapping; // Color M_Memcpy(temp,extrainfo_p,16); @@ -2687,12 +2896,14 @@ void G_LoadDemoInfo(menudemo_t *pdemo) } // I think that's everything we need? + Z_Free(skinlist); Z_Free(infobuffer); return; badreplay: pdemo->type = MD_INVALID; sprintf(pdemo->title, "INVALID REPLAY"); + Z_Free(skinlist); Z_Free(infobuffer); } @@ -2714,15 +2925,15 @@ void G_DeferedPlayDemo(const char *name) void G_DoPlayDemo(char *defdemoname) { - UINT8 i, p; + UINT8 i, p, numslots = 0; lumpnum_t l; - char skin[17],color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; + char color[MAXCOLORNAME+1],follower[17],mapname[MAXMAPLUMPNAME],*n,*pdemoname; UINT8 version,subversion; UINT32 randseed[PRNUMCLASS]; char msg[1024]; - boolean spectator; - UINT8 slots[MAXPLAYERS], kartspeed[MAXPLAYERS], kartweight[MAXPLAYERS], numslots = 0; + boolean spectator, bot; + UINT8 slots[MAXPLAYERS], lastfakeskin[MAXPLAYERS]; #if defined(SKIPERRORS) && !defined(DEVELOP) boolean skiperrors = false; @@ -2730,7 +2941,6 @@ void G_DoPlayDemo(char *defdemoname) G_InitDemoRewind(); - skin[16] = '\0'; follower[16] = '\0'; color[MAXCOLORNAME] = '\0'; @@ -2902,6 +3112,20 @@ void G_DoPlayDemo(char *defdemoname) } } + // character list + demo.skinlist = G_LoadDemoSkins(&demo_p, &demo.numskins, true); + if (!demo.skinlist) + { + snprintf(msg, 1024, M_GetText("%s has an invalid skin list and cannot be played.\n"), pdemoname); + CONS_Alert(CONS_ERROR, "%s", msg); + M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(pdemoname); + Z_Free(demobuffer); + demo.playback = false; + demo.title = false; + return; + } + modeattacking = (demoflags & DF_ATTACKMASK)>>DF_ATTACKSHIFT; multiplayer = !!(demoflags & DF_MULTIPLAYER); demo.netgame = (multiplayer && !(demoflags & DF_NONETMP)); @@ -2940,6 +3164,8 @@ void G_DoPlayDemo(char *defdemoname) snprintf(msg, 1024, M_GetText("%s features a course that is not currently loaded.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(demo.skinlist); + demo.skinlist = NULL; Z_Free(pdemoname); Z_Free(demobuffer); demo.playback = false; @@ -2956,6 +3182,8 @@ void G_DoPlayDemo(char *defdemoname) snprintf(msg, 1024, M_GetText("%s contains no data to be played.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(demo.skinlist); + demo.skinlist = NULL; Z_Free(pdemoname); Z_Free(demobuffer); demo.playback = false; @@ -3000,14 +3228,17 @@ void G_DoPlayDemo(char *defdemoname) UINT8 flags = READUINT8(demo_p); spectator = !!(flags & DEMO_SPECTATOR); + bot = !!(flags & DEMO_BOT); - if (spectator == true) + if ((spectator || bot)) { if (modeattacking) { - snprintf(msg, 1024, M_GetText("%s is a Record Attack replay with spectators, and is thus invalid.\n"), pdemoname); + snprintf(msg, 1024, M_GetText("%s is a Record Attack replay with %s, and is thus invalid.\n"), pdemoname, (bot ? "bots" : "spectators")); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(demo.skinlist); + demo.skinlist = NULL; Z_Free(pdemoname); Z_Free(demobuffer); demo.playback = false; @@ -3024,6 +3255,8 @@ void G_DoPlayDemo(char *defdemoname) snprintf(msg, 1024, M_GetText("%s is a Record Attack replay with multiple players, and is thus invalid.\n"), pdemoname); CONS_Alert(CONS_ERROR, "%s", msg); M_StartMessage(msg, NULL, MM_NOTHING); + Z_Free(demo.skinlist); + demo.skinlist = NULL; Z_Free(pdemoname); Z_Free(demobuffer); demo.playback = false; @@ -3047,21 +3280,28 @@ void G_DoPlayDemo(char *defdemoname) else players[p].pflags &= ~PF_SHRINKME; + if ((players[p].bot = bot) == true) + { + players[p].botvars.difficulty = READUINT8(demo_p); + players[p].botvars.diffincrease = READUINT8(demo_p); // needed to avoid having to duplicate logic + players[p].botvars.rival = (boolean)READUINT8(demo_p); + } + K_UpdateShrinkCheat(&players[p]); // Name M_Memcpy(player_names[p],demo_p,16); demo_p += 16; - /*if (players[p].spectator) - { - CONS_Printf("player %s is spectator at start\n", player_names[p]); - }*/ - // Skin - M_Memcpy(skin,demo_p,16); - demo_p += 16; - SetPlayerSkin(p, skin); + + i = READUINT8(demo_p); + if (i >= demo.numskins) + i = 0; + SetPlayerSkinByNum(p, demo.skinlist[i].mapping); + demo.currentskinid[p] = ghostext[p].skinid = i; + + lastfakeskin[p] = READUINT8(demo_p); // Color M_Memcpy(color,demo_p,16); @@ -3096,13 +3336,6 @@ void G_DoPlayDemo(char *defdemoname) // Power Levels clientpowerlevels[p][gametype == GT_BATTLE ? PWRLV_BATTLE : PWRLV_RACE] = READUINT16(demo_p); - // Kart stats, temporarily - kartspeed[p] = READUINT8(demo_p); - kartweight[p] = READUINT8(demo_p); - - if (stricmp(skins[players[p].skin].name, skin) != 0) - FindClosestSkinForStats(p, kartspeed[p], kartweight[p]); - // Followitem players[p].followitem = READUINT32(demo_p); @@ -3141,21 +3374,24 @@ void G_DoPlayDemo(char *defdemoname) G_InitNew(demoflags & DF_ENCORE, gamemap, true, true, false); // Doesn't matter whether you reset or not here, given changes to resetplayer. - for (i = 0; i < MAXPLAYERS; i++) + for (i = 0; i < numslots; i++) { - if (players[i].mo) + p = slots[i]; + if (players[p].mo) { - players[i].mo->color = players[i].skincolor; - oldghost[i].x = players[i].mo->x; - oldghost[i].y = players[i].mo->y; - oldghost[i].z = players[i].mo->z; + players[p].mo->color = players[p].skincolor; + oldghost[p].x = players[p].mo->x; + oldghost[p].y = players[p].mo->y; + oldghost[p].z = players[p].mo->z; } // Set saved attribute values // No cheat checking here, because even if they ARE wrong... // it would only break the replay if we clipped them. - players[i].kartspeed = kartspeed[i]; - players[i].kartweight = kartweight[i]; + players[p].kartspeed = ghostext[p].kartspeed = demo.skinlist[demo.currentskinid[p]].kartspeed; + players[p].kartweight = ghostext[p].kartweight = demo.skinlist[demo.currentskinid[p]].kartweight; + players[p].charflags = ghostext[p].charflags = demo.skinlist[demo.currentskinid[p]].flags; + players[p].lastfakeskin = lastfakeskin[p]; } demo.deferstart = true; @@ -3165,17 +3401,17 @@ void G_AddGhost(char *defdemoname) { INT32 i; lumpnum_t l; - char name[17],skin[17],color[MAXCOLORNAME+1],*n,*pdemoname,md5[16]; + char name[17],color[MAXCOLORNAME+1],*n,*pdemoname,md5[16]; demoghost *gh; UINT8 flags; UINT8 *buffer,*p; mapthing_t *mthing; UINT16 count, ghostversion; skin_t *ghskin = &skins[0]; - UINT8 kartspeed = UINT8_MAX, kartweight = UINT8_MAX; + UINT8 worknumskins; + democharlist_t *skinlist = NULL; name[16] = '\0'; - skin[16] = '\0'; color[16] = '\0'; n = defdemoname+strlen(defdemoname); @@ -3278,6 +3514,15 @@ void G_AddGhost(char *defdemoname) p++; // numlaps G_SkipDemoExtraFiles(&p); // Don't wanna modify the file list for ghosts. + skinlist = G_LoadDemoSkins(&p, &worknumskins, true); + if (!skinlist) + { + CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Replay data has invalid skin list, cannot continue.\n"), pdemoname); + Z_Free(pdemoname); + Z_Free(buffer); + return; + } + switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { case ATTACKING_NONE: // 0 @@ -3311,6 +3556,7 @@ void G_AddGhost(char *defdemoname) if (*p == DEMOMARKER) { CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Replay is empty.\n"), pdemoname); + Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); return; @@ -3319,9 +3565,10 @@ void G_AddGhost(char *defdemoname) p++; // player number - doesn't really need to be checked, TODO maybe support adding multiple players' ghosts at once // any invalidating flags? - if ((READUINT8(p) & (DEMO_SPECTATOR)) != 0) + if ((READUINT8(p) & (DEMO_SPECTATOR|DEMO_BOT)) != 0) { CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); return; @@ -3332,8 +3579,10 @@ void G_AddGhost(char *defdemoname) p += 16; // Skin - M_Memcpy(skin, p, 16); - p += 16; + i = READUINT8(p); + if (i < worknumskins) + ghskin = &skins[skinlist[i].mapping]; + p++; // lastfakeskin // Color M_Memcpy(color, p, 16); @@ -3345,34 +3594,17 @@ void G_AddGhost(char *defdemoname) p += 4; // score p += 2; // powerlevel - kartspeed = READUINT8(p); - kartweight = READUINT8(p); - p += 4; // followitem (maybe change later) if (READUINT8(p) != 0xFF) { CONS_Alert(CONS_NOTICE, M_GetText("Failed to add ghost %s: Invalid player slot.\n"), pdemoname); + Z_Free(skinlist); Z_Free(pdemoname); Z_Free(buffer); return; } - for (i = 0; i < numskins; i++) - if (!stricmp(skins[i].name,skin)) - { - ghskin = &skins[i]; - break; - } - - if (i == numskins) - { - if (kartspeed != UINT8_MAX && kartweight != UINT8_MAX) - ghskin = &skins[GetSkinNumClosestToStats(kartspeed, kartweight)]; - - CONS_Alert(CONS_NOTICE, M_GetText("Ghost %s: Invalid character. Falling back to %s.\n"), pdemoname, ghskin->name); - } - gh = Z_Calloc(sizeof(demoghost), PU_LEVEL, NULL); gh->next = ghosts; @@ -3380,6 +3612,9 @@ void G_AddGhost(char *defdemoname) M_Memcpy(gh->checksum, md5, 16); gh->p = p; + gh->numskins = worknumskins; + gh->skinlist = skinlist; + ghosts = gh; gh->version = ghostversion; @@ -3442,6 +3677,7 @@ void G_FreeGhosts(void) while (ghosts) { demoghost *next = ghosts->next; + Z_Free(ghosts->skinlist); Z_Free(ghosts); ghosts = next; } @@ -3501,6 +3737,8 @@ void G_UpdateStaffGhostName(lumpnum_t l) p++; // numlaps G_SkipDemoExtraFiles(&p); + G_SkipDemoSkins(&p); + switch ((flags & DF_ATTACKMASK)>>DF_ATTACKSHIFT) { case ATTACKING_NONE: // 0 @@ -3768,6 +4006,9 @@ void G_StopDemo(void) democam.localaiming = 0; democam.keyboardlook = false; + Z_Free(demo.skinlist); + demo.skinlist = NULL; + if (gamestate == GS_INTERMISSION) Y_EndIntermission(); // cleanup diff --git a/src/g_demo.h b/src/g_demo.h index 2dc93eef2..a00756a74 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -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; diff --git a/src/g_game.c b/src/g_game.c index 9472c400f..f0f53ca43 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2239,6 +2239,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT16 skincolor; INT32 skin; UINT32 availabilities; + UINT8 fakeskin; + UINT8 lastfakeskin; tic_t jointime; @@ -2285,9 +2287,21 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) skincolor = players[player].skincolor; skin = players[player].skin; - // SRB2kart - kartspeed = players[player].kartspeed; - kartweight = players[player].kartweight; + if (betweenmaps) + { + fakeskin = MAXSKINS; + kartspeed = skins[players[player].skin].kartspeed; + kartweight = skins[players[player].skin].kartweight; + charflags = skins[players[player].skin].flags; + } + else + { + fakeskin = players[player].fakeskin; + kartspeed = players[player].kartspeed; + kartweight = players[player].kartweight; + charflags = players[player].charflags; + } + lastfakeskin = players[player].lastfakeskin; followerready = players[player].followerready; followercolor = players[player].followercolor; @@ -2295,8 +2309,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) availabilities = players[player].availabilities; - charflags = players[player].charflags; - followitem = players[player].followitem; bot = players[player].bot; @@ -2413,10 +2425,13 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) // save player config truth reborn p->skincolor = skincolor; p->skin = skin; + + p->fakeskin = fakeskin; p->kartspeed = kartspeed; p->kartweight = kartweight; - // p->charflags = charflags; + p->lastfakeskin = lastfakeskin; + p->availabilities = availabilities; p->followitem = followitem; diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index b4917751b..b5268986b 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -188,7 +188,10 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p offsetx = (float)(gpatch->leftoffset) * fscalew; // top offset - offsety = (float)(gpatch->topoffset) * fscaleh; + if (option & V_VFLIP) + offsety = (float)(gpatch->height - gpatch->topoffset) * fscaleh; + else + offsety = (float)(gpatch->topoffset) * fscaleh; cx -= offsetx; cy -= offsety; @@ -249,8 +252,16 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p v[2].s = v[1].s = hwrPatch->max_s; } - v[0].t = v[1].t = 0.0f; - v[2].t = v[3].t = hwrPatch->max_t; + if (option & V_VFLIP) + { + v[0].t = v[1].t = hwrPatch->max_t; + v[2].t = v[3].t = 0.0f; + } + else + { + v[0].t = v[1].t = 0.0f; + v[2].t = v[3].t = hwrPatch->max_t; + } flags = PF_NoDepthTest; diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 74385e7f7..32f46bda3 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5298,7 +5298,7 @@ static void HWR_ProjectSprite(mobj_t *thing) flip ^= (1<skin && ((skin_t *)thing->skin)->flags & SF_HIRES) + if (thing->skin && ((skin_t *)thing->skin)->highresscale != FRACUNIT) this_scale *= FIXED_TO_FLOAT(((skin_t *)thing->skin)->highresscale); spr_width = spritecachedinfo[lumpoff].width; diff --git a/src/info.c b/src/info.c index 44a4d25ae..3824731d1 100644 --- a/src/info.c +++ b/src/info.c @@ -543,6 +543,9 @@ char sprnames[NUMSPRITES + 1][5] = "KINF", // Invincibility flash "INVI", // Invincibility speedlines "ICAP", // Item capsules + "MGBX", // Heavy Magician transform box + "MGBT", // Heavy Magician transform box top + "MGBB", // Heavy Magician transform box bottom "WIPD", // Wipeout dust trail "DRIF", // Drift Sparks @@ -3891,6 +3894,10 @@ state_t states[NUMSTATES] = //{SPR_ICAP, FF_FLOORSPRITE|4, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_BOTTOM //{SPR_ICAP, FF_FLOORSPRITE|5, -1, {NULL}, 0, 0, S_NULL}, // S_ITEMCAPSULE_INSIDE + {SPR_MGBX, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX + {SPR_MGBT, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_TOP + {SPR_MGBB, FF_FLOORSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_BOTTOM + {SPR_SGNS, FF_ADD|FF_FULLBRIGHT, 1, {NULL}, 0, 0, S_SIGNSPARK2}, // S_SIGNSPARK1 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|1, 1, {NULL}, 0, 0, S_SIGNSPARK3}, // S_SIGNSPARK2 {SPR_SGNS, FF_ADD|FF_FULLBRIGHT|2, 1, {NULL}, 0, 0, S_SIGNSPARK4}, // S_SIGNSPARK3 @@ -22398,6 +22405,33 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, + { // MT_MAGICIANBOX + -1, // doomednum + S_MAGICIANBOX, // spawnstate + 1000, // spawnhealth + S_NULL, // seestate + sfx_None, // seesound + 0, // reactiontime + sfx_None, // attacksound + S_NULL, // painstate + 0, // painchance + sfx_None, // painsound + S_NULL, // meleestate + S_NULL, // missilestate + S_NULL, // deathstate + S_NULL, // xdeathstate + sfx_None, // deathsound + 0, // speed + 20*FRACUNIT, // radius + 20*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT, // flags + S_NULL // raisestate + }, + { // MT_SIGNSPARKLE -1, // doomednum S_SIGNSPARK1, // spawnstate diff --git a/src/info.h b/src/info.h index 78fc6e2db..7dff6e2de 100644 --- a/src/info.h +++ b/src/info.h @@ -1089,6 +1089,9 @@ typedef enum sprite SPR_KINF, // Invincibility flash SPR_INVI, // Invincibility speedlines SPR_ICAP, // Item capsules + SPR_MGBX, // Heavy Magician transform box + SPR_MGBT, // Heavy Magician transform box top + SPR_MGBB, // Heavy Magician transform box bottom SPR_WIPD, // Wipeout dust trail SPR_DRIF, // Drift Sparks @@ -4295,6 +4298,10 @@ typedef enum state //S_ITEMCAPSULE_BOTTOM, //S_ITEMCAPSULE_INSIDE, + S_MAGICIANBOX, + S_MAGICIANBOX_TOP, + S_MAGICIANBOX_BOTTOM, + // Signpost sparkles S_SIGNSPARK1, S_SIGNSPARK2, @@ -6333,6 +6340,7 @@ typedef enum mobj_type MT_FLOATINGITEM, MT_ITEMCAPSULE, MT_ITEMCAPSULE_PART, + MT_MAGICIANBOX, MT_SIGNSPARKLE, diff --git a/src/k_hud.c b/src/k_hud.c index 4a5644442..d9908c4c6 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1745,7 +1745,10 @@ static boolean K_drawKartPositionFaces(void) boolean completed[MAXPLAYERS]; INT32 rankplayer[MAXPLAYERS]; INT32 bumperx, emeraldx, numplayersingame = 0; + INT32 xoff, yoff, flipflag = 0; + UINT8 workingskin; UINT8 *colormap; + UINT32 skinflags; ranklines = 0; memset(completed, 0, sizeof (completed)); @@ -1830,15 +1833,36 @@ 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 (skinflags & SF_IRONMAN + && !(players[rankplayer[i]].charflags & SF_IRONMAN) ) + { + flipflag = V_FLIP|V_VFLIP; // blonic flip + xoff = yoff = 16; + } else + { + flipflag = 0; + xoff = yoff = 0; + } + if (players[rankplayer[i]].mo->color) { - colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); + if ((skin_t*)players[rankplayer[i]].mo->skin) + workingskin = (skin_t*)players[rankplayer[i]].mo->skin - skins; + else + workingskin = players[rankplayer[i]].skin; + + colormap = R_GetTranslationColormap(workingskin, players[rankplayer[i]].mo->color, GTC_CACHE); if (players[rankplayer[i]].mo->colorized) colormap = R_GetTranslationColormap(TC_RAINBOW, players[rankplayer[i]].mo->color, GTC_CACHE); else - colormap = R_GetTranslationColormap(players[rankplayer[i]].skin, players[rankplayer[i]].mo->color, GTC_CACHE); + colormap = R_GetTranslationColormap(workingskin, players[rankplayer[i]].mo->color, GTC_CACHE); - V_DrawMappedPatch(FACE_X, Y, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, faceprefix[players[rankplayer[i]].skin][FACE_RANK], colormap); + V_DrawMappedPatch(FACE_X + xoff, Y + yoff, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT|flipflag, faceprefix[workingskin][FACE_RANK], colormap); if (LUA_HudEnabled(hud_battlebumpers)) { diff --git a/src/k_kart.c b/src/k_kart.c index 6bb2be28e..033f4e3ed 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2717,6 +2717,47 @@ void K_SpawnBumpEffect(mobj_t *mo) S_StartSound(mo, sfx_s3k49); } +void K_SpawnMagicianParticles(mobj_t *mo, int spread) +{ + INT32 i; + mobj_t *target = mo->target; + + if (P_MobjWasRemoved(target)) + target = mo; + + for (i = 0; i < 16; i++) + { + fixed_t hmomentum = P_RandomRange(PR_DECORATION, spread * -1, spread) * mo->scale; + fixed_t vmomentum = P_RandomRange(PR_DECORATION, spread * -1, spread) * mo->scale; + UINT16 color = P_RandomKey(PR_DECORATION, numskincolors); + + fixed_t ang = FixedAngle(P_RandomRange(PR_DECORATION, 0, 359)*FRACUNIT); + SINT8 flip = 1; + + mobj_t *dust; + + if (i & 1) + ang -= ANGLE_90; + else + ang += ANGLE_90; + + dust = P_SpawnMobjFromMobj(mo, + FixedMul(mo->radius, FINECOSINE(ang >> ANGLETOFINESHIFT)), + FixedMul(mo->radius, FINESINE(ang >> ANGLETOFINESHIFT)), + target->height, (i%3 == 0) ? MT_SIGNSPARKLE : MT_SPINDASHDUST + ); + flip = P_MobjFlip(dust); + + dust->momx = target->momx + FixedMul(hmomentum, FINECOSINE(ang >> ANGLETOFINESHIFT)); + dust->momy = target->momy + FixedMul(hmomentum, FINESINE(ang >> ANGLETOFINESHIFT)); + dust->momz = vmomentum * flip; + dust->scale = dust->scale*4; + dust->frame |= FF_SUBTRACT|FF_TRANS90; + dust->color = color; + dust->colorized = true; + } +} + static SINT8 K_GlanceAtPlayers(player_t *glancePlayer) { const fixed_t maxdistance = FixedMul(1280 * mapobjectscale, K_GetKartGameSpeedScalar(gamespeed)); diff --git a/src/k_kart.h b/src/k_kart.h index a5d2b0185..db280afc0 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -110,6 +110,7 @@ void K_SpawnBoostTrail(player_t *player); void K_SpawnSparkleTrail(mobj_t *mo); void K_SpawnWipeoutTrail(mobj_t *mo); void K_SpawnDraftDust(mobj_t *mo); +void K_SpawnMagicianParticles(mobj_t *mo, int spread); void K_DriftDustHandling(mobj_t *spawner); void K_Squish(mobj_t *mo); mobj_t *K_ThrowKartItem(player_t *player, boolean missile, mobjtype_t mapthing, INT32 defaultDir, INT32 altthrow, angle_t angleOffset); diff --git a/src/k_menudraw.c b/src/k_menudraw.c index cfd52a459..8bfec4716 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1250,7 +1250,7 @@ static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, boolean charf addflags ^= V_FLIP; // This sprite is left/right flipped! } - if (skins[skin].flags & SF_HIRES) + if (skins[skin].highresscale != FRACUNIT) { V_DrawFixedPatch(x<skin); else if (fastcmp(field,"availabilities")) lua_pushinteger(L, plr->availabilities); + else if (fastcmp(field,"fakeskin")) + lua_pushinteger(L, plr->fakeskin); + else if (fastcmp(field,"lastfakeskin")) + lua_pushinteger(L, plr->lastfakeskin); else if (fastcmp(field,"score")) lua_pushinteger(L, plr->score); // SRB2kart @@ -575,6 +579,10 @@ static int player_set(lua_State *L) return NOSET; else if (fastcmp(field,"availabilities")) return NOSET; + else if (fastcmp(field,"fakeskin")) + return NOSET; + else if (fastcmp(field,"lastfakeskin")) + return NOSET; else if (fastcmp(field,"score")) plr->score = luaL_checkinteger(L, 3); // SRB2kart diff --git a/src/m_random.h b/src/m_random.h index 5bb5d4435..ec1f643e1 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -46,6 +46,7 @@ typedef enum PR_PLAYERSTARTS, // Player starts PR_VOICES, // Player voice sounds + PR_RANDOMSKIN, // Random skin select from Heavy Magician(?) PR_RULESCRAMBLE, // Rule scrambing events diff --git a/src/p_mobj.c b/src/p_mobj.c index 250b8b470..ac0312dec 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7699,6 +7699,109 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->renderflags = (mobj->renderflags & ~RF_TRANSMASK)|(trans << RF_TRANSSHIFT); } break; + case MT_MAGICIANBOX: + { + fixed_t destx, desty; + fixed_t zoff = 0; + + // EV1: rotation rate + // EV2: lifetime + // cusval: responsible for disappear FX (should only happen once) + + // S_MAGICANBOX: sides, starting angle is set in the spawner (SetRandomFakePlayerSkin) + // S_MAGICIANBOX_TOP, S_MAGICIANBOX_BOTTOM: splats with their own offset sprite sets + + mobj->extravalue2--; + + if (mobj->extravalue2 == 0) + { + P_RemoveMobj(mobj); + break; + } + else if (mobj->extravalue2 < TICRATE/3) + { + P_SetTarget(&mobj->target, NULL); + if (mobj->extravalue2 & 1) + mobj->renderflags |= RF_DONTDRAW; + else + mobj->renderflags &= ~RF_DONTDRAW; + } + else if (mobj->extravalue2 == TICRATE/3 && !P_MobjWasRemoved(mobj->target)) + { + mobj->momx = mobj->target->momx; + mobj->momy = mobj->target->momy; + mobj->momz = mobj->target->momz; + + if (mobj->state == &states[S_MAGICIANBOX]) // sides + P_Thrust(mobj, mobj->angle + ANGLE_90, 32*mapobjectscale); + + mobj->flags &= ~MF_NOGRAVITY; + mobj->momz += 10*mapobjectscale; + if (mobj->state == &states[S_MAGICIANBOX_BOTTOM]) + mobj->momz *= -1; + + if (!mobj->cusval) // Some stuff should only occur once per box + return true; + + S_StartSound(mobj, sfx_kc2e); + S_StartSound(mobj, sfx_s3k9f); + + if (mobj->target->player->hyudorotimer) + { + P_RemoveMobj(mobj); + break; + } + else + { + K_SpawnMagicianParticles(mobj, 5); + } + return true; + } + else if (mobj->target && !P_MobjWasRemoved(mobj->target)) + { + mobj->renderflags &= ~RF_DONTDRAW; + mobj->renderflags |= (mobj->target->renderflags & RF_DONTDRAW); + // NB: This depends on order of thinker execution! + // SetRandomFakePlayerSkin (r_skins.c) sets cusval on the bottom (last) side (i=5). + // This writes to the player's visibility only after every other side has ticked and inherited it. + if (mobj->cusval) + mobj->target->renderflags |= RF_DONTDRAW; + } + + if (P_MobjWasRemoved(mobj->target) || !mobj->target->health || !mobj->target->player) { + mobj->extravalue2 = min(mobj->extravalue2, TICRATE/3); + return true; + } + + mobj->extravalue1 += 1; + + mobj->angle += ANG1*mobj->extravalue1; + mobj->scale = mobj->target->scale; + + destx = mobj->target->x; + desty = mobj->target->y; + + if (mobj->state == &states[S_MAGICIANBOX]) // sides + { + destx += FixedMul(mobj->radius*2, FINECOSINE((mobj->angle+ANGLE_90) >> ANGLETOFINESHIFT)); + desty += FixedMul(mobj->radius*2, FINESINE((mobj->angle+ANGLE_90) >> ANGLETOFINESHIFT)); + } + else if (mobj->state == &states[S_MAGICIANBOX_TOP]) // top + { + zoff = mobj->radius*4; + } + + if (mobj->flags2 & MF2_AMBUSH) + { + P_SetOrigin(mobj, destx, desty, mobj->target->z + zoff); + mobj->flags2 &= ~MF2_AMBUSH; + } + else + { + P_MoveOrigin(mobj, destx, desty, mobj->target->z + zoff); + } + break; + } case MT_LIGHTNINGSHIELD: { fixed_t destx, desty; diff --git a/src/p_saveg.c b/src/p_saveg.c index c9ede8b78..1d58992f2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -151,6 +151,8 @@ static void P_NetArchivePlayers(void) WRITEUINT8(save_p, players[i].skincolor); WRITEINT32(save_p, players[i].skin); WRITEUINT32(save_p, players[i].availabilities); + WRITEUINT8(save_p, players[i].fakeskin); + WRITEUINT8(save_p, players[i].lastfakeskin); WRITEUINT32(save_p, players[i].score); WRITESINT8(save_p, players[i].lives); WRITESINT8(save_p, players[i].xtralife); @@ -470,6 +472,8 @@ static void P_NetUnArchivePlayers(void) players[i].skincolor = READUINT8(save_p); players[i].skin = READINT32(save_p); players[i].availabilities = READUINT32(save_p); + players[i].fakeskin = READUINT8(save_p); + players[i].lastfakeskin = READUINT8(save_p); players[i].score = READUINT32(save_p); players[i].lives = READSINT8(save_p); players[i].xtralife = READSINT8(save_p); // Ring Extra Life counter diff --git a/src/p_spec.c b/src/p_spec.c index 31af5b0e6..07ec1f102 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1928,6 +1928,17 @@ static void K_HandleLapIncrement(player_t *player) P_DoPlayerExit(player); P_SetupSignExit(player); } + else + { + UINT32 skinflags = (demo.playback) + ? demo.skinlist[demo.currentskinid[(player-players)]].flags + : skins[player->skin].flags; + if (skinflags & SF_IRONMAN) + { + SetRandomFakePlayerSkin(player, true); + } + } + if (player->laps > player->latestlap) { diff --git a/src/p_user.c b/src/p_user.c index b43311d38..1f58384df 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1264,6 +1264,8 @@ void P_DoPlayerExit(player_t *player) if (!player->spectator) { + ClearFakePlayerSkin(player); + if ((gametyperules & GTR_CIRCUIT)) // If in Race Mode, allow { K_KartUpdatePosition(player); @@ -4167,6 +4169,35 @@ void P_PlayerThink(player_t *player) { player->stairjank--; } + + // Random skin / "ironman" + { + UINT32 skinflags = (demo.playback) + ? demo.skinlist[demo.currentskinid[playeri]].flags + : skins[player->skin].flags; + + if (skinflags & SF_IRONMAN) // we are Heavy Magician + { + if (player->charflags & SF_IRONMAN) // no fakeskin yet + { + if (leveltime >= starttime && !player->exiting) + { + 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); + } + } + } K_KartPlayerThink(player, cmd); // SRB2kart diff --git a/src/r_skins.c b/src/r_skins.c index 0557e9e88..9779fdcfc 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -27,6 +27,8 @@ #include "p_local.h" #include "dehacked.h" // get_number (for thok) #include "m_cond.h" +#include "k_kart.h" +#include "m_random.h" #if 0 #include "k_kart.h" // K_KartResetPlayerColor #endif @@ -334,6 +336,136 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) SetPlayerSkinByNum(playernum, 0); // not found, put in the default skin } +// Set mo skin but not player_t skin, for ironman +void SetFakePlayerSkin(player_t* player, INT32 skinid) +{ + if (player->fakeskin != skinid) + { + if (player->fakeskin != MAXSKINS) + player->lastfakeskin = player->fakeskin; + 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; + 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); + + if (player->mo) + { + S_StartSound(player->mo, sfx_kc33); + S_StartSound(player->mo, sfx_cdfm44); + + mobj_t *parent = player->mo; + fixed_t baseangle = P_RandomRange(PR_DECORATION, 0, 359); + INT32 j; + + for (j = 0; j < 6; j++) // 0-3 = sides, 4 = top, 5 = bottom + { + mobj_t *box = P_SpawnMobjFromMobj(parent, 0, 0, 0, MT_MAGICIANBOX); + P_SetTarget(&box->target, parent); + box->angle = FixedAngle((baseangle + j*90) * FRACUNIT); + box->flags2 |= MF2_AMBUSH; + if (fast) + { + box->extravalue1 = 10; // Rotation rate + box->extravalue2 = 5*TICRATE/4; // Lifetime + } + else + { + box->extravalue1 = 1; + box->extravalue2 = 3*TICRATE/2; + } + + // cusval controls behavior that should run only once, like disappear FX and RF_DONTDRAW handling. + // NB: Order of thinker execution matters here! + // We want the other sides to inherit the player's "existing" RF_DONTDRAW before the last side writes to it. + // See the MT_MAGICIANBOX thinker in p_mobj.c. + if (j == 5) + box->cusval = 1; + else + box->cusval = 0; + + if (j > 3) + { + P_SetMobjState(box, (j == 4) ? S_MAGICIANBOX_TOP : S_MAGICIANBOX_BOTTOM); + box->renderflags |= RF_NOSPLATBILLBOARD; + box->angle = FixedAngle(baseangle*FRACUNIT); + } + } + + K_SpawnMagicianParticles(player->mo, 10); + } +} + +// Return to base skin from an SF_IRONMAN randomization +void ClearFakePlayerSkin(player_t* player) +{ + UINT8 skinid; + UINT32 flags; + + if (demo.playback) + { + 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; +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // @@ -482,8 +614,8 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) // parameters for individual character flags // these are uppercase so they can be concatenated with SF_ // 1, true, yes are all valid values - GETFLAG(HIRES) GETFLAG(MACHINE) + GETFLAG(IRONMAN) #undef GETFLAG else // let's check if it's a sound, otherwise error out diff --git a/src/r_skins.h b/src/r_skins.h index ded45a71f..1cc06cca4 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -81,6 +81,9 @@ void R_InitSkins(void); void SetPlayerSkin(INT32 playernum,const char *skinname); void SetPlayerSkinByNum(INT32 playernum,INT32 skinnum); // Tails 03-16-2002 +void SetFakePlayerSkin(player_t* player, INT32 skinnum); +void SetRandomFakePlayerSkin(player_t* player, boolean fast); +void ClearFakePlayerSkin(player_t* player); boolean R_SkinUsable(INT32 playernum, INT32 skinnum); UINT32 R_GetSkinAvailabilities(void); INT32 R_SkinAvailable(const char *name); diff --git a/src/r_splats.c b/src/r_splats.c index 80ff44743..011e1d9eb 100644 --- a/src/r_splats.c +++ b/src/r_splats.c @@ -181,7 +181,7 @@ void R_DrawFloorSplat(vissprite_t *spr) splat.height = spr->patch->height; splat.scale = mobj->scale; - if (mobj->skin && ((skin_t *)mobj->skin)->flags & SF_HIRES) + if (mobj->skin && ((skin_t *)mobj->skin)->highresscale != FRACUNIT) splat.scale = FixedMul(splat.scale, ((skin_t *)mobj->skin)->highresscale); if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD) diff --git a/src/r_things.c b/src/r_things.c index 43470152a..87b19eb6b 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -945,7 +945,7 @@ static void R_DrawVisSprite(vissprite_t *vis) frac = vis->startfrac; windowtop = windowbottom = sprbotscreen = INT32_MAX; - if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && ((skin_t *)vis->mobj->skin)->flags & SF_HIRES) + if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && ((skin_t *)vis->mobj->skin)->highresscale != FRACUNIT) this_scale = FixedMul(this_scale, ((skin_t *)vis->mobj->skin)->highresscale); if (this_scale <= 0) @@ -1410,7 +1410,7 @@ static void R_ProjectDropShadow( shadow->gzt = groundz + patch->height * shadowyscale / 2; shadow->gz = shadow->gzt - patch->height * shadowyscale; shadow->texturemid = FixedMul(interp.scale, FixedDiv(shadow->gzt - viewz, shadowyscale)); - if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES) + if (thing->skin && ((skin_t *)thing->skin)->highresscale != FRACUNIT) shadow->texturemid = FixedMul(shadow->texturemid, ((skin_t *)thing->skin)->highresscale); shadow->scalestep = 0; shadow->shear.tan = shadowskew; // repurposed variable @@ -1797,7 +1797,7 @@ static void R_ProjectSprite(mobj_t *thing) I_Assert(lump < max_spritelumps); - if (thing->skin && ((skin_t *)thing->skin)->flags & SF_HIRES) + if (thing->skin && ((skin_t *)thing->skin)->highresscale != FRACUNIT) this_scale = FixedMul(this_scale, ((skin_t *)thing->skin)->highresscale); spr_width = spritecachedinfo[lump].width; diff --git a/src/v_video.c b/src/v_video.c index 2636b9a6e..2df56c9a4 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -619,10 +619,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca colfrac = FixedDiv(FRACUNIT, fdup); rowfrac = FixedDiv(FRACUNIT, vdup); - // So it turns out offsets aren't scaled in V_NOSCALESTART unless V_OFFSET is applied ...poo, that's terrible - // For now let's just at least give V_OFFSET the ability to support V_FLIP - // I'll probably make a better fix for 2.2 where I don't have to worry about breaking existing support for stuff - // -- Monster Iestyn 29/10/18 { fixed_t offsetx = 0, offsety = 0; @@ -633,15 +629,17 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca offsetx = FixedMul(patch->leftoffset<topoffset<height - patch->topoffset)<topoffset<= vid.width) // don't draw off the right of the screen (WRAP PREVENTION) break; } + column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS])); while (column->topdelta != 0xff) @@ -709,17 +708,31 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca topdelta += prevdelta; prevdelta = topdelta; source = (const UINT8 *)(column) + 3; + dest = desttop; if (scrn & V_FLIP) - dest = deststart + (destend - desttop); + dest = deststart + (destend - dest); dest += FixedInt(FixedMul(topdelta<>FRACBITS) < column->length; ofs += rowfrac) + if (scrn & V_VFLIP) { - if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION) - *dest = patchdrawfunc(dest, source, ofs); - dest += vid.width; + for (ofs = (column->length << FRACBITS)-1; dest < deststop && ofs >= 0; ofs -= rowfrac) + { + if (dest >= screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) + *dest = patchdrawfunc(dest, source, ofs); + dest += vid.width; + } } + else + { + for (ofs = 0; dest < deststop && ofs < (column->length << FRACBITS); ofs += rowfrac) + { + if (dest >= screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) + *dest = patchdrawfunc(dest, source, ofs); + dest += vid.width; + } + } + column = (const column_t *)((const UINT8 *)column + column->length + 4); } } @@ -778,7 +791,7 @@ void V_DrawCroppedPatch(fixed_t x, fixed_t y, fixed_t pscale, INT32 scrn, patch_ y -= FixedMul(patch->topoffset<leftoffset<>FRACBITS) < column->length && (((ofs>>FRACBITS) - sy) + topdelta) < h; ofs += rowfrac) { - if (dest >= screens[scrn&V_PARAMMASK]) // don't draw off the top of the screen (CRASH PREVENTION) + if (dest >= screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION) *dest = patchdrawfunc(dest, source, ofs); dest += vid.width; } diff --git a/src/v_video.h b/src/v_video.h index 34800e7b4..1803d0818 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -82,11 +82,14 @@ void V_CubeApply(RGBA_t *input); #define V_GetColor(color) (pLocalPalette[color&0xFF]) #define V_GetMasterColor(color) (pMasterPalette[color&0xFF]) -// Bottom 8 bits are used for parameter (screen or character) +// Bottom 8 bits are used for parameter (character) #define V_PARAMMASK 0x000000FF -// strings/characters only -#define V_STRINGDANCE 0x00000002 +// Bottom bit is used for screen (patches) +#define V_SCREENMASK 0x0000000F + +#define V_STRINGDANCE 0x00000002 // (strings/characters only) funny undertale +#define V_VFLIP 0x00000010 // (patches only) Vertical flip // flags hacked in scrn (not supported by all functions (see src)) // patch scaling uses bits 9 and 10