From 86366b000cc27f43cc03d8f10b3100fa9f91d70c Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 30 Oct 2022 05:58:15 -0700 Subject: [PATCH 01/31] HOSTCODE 'ironman' - WIP --- src/d_player.h | 4 ++++ src/deh_tables.c | 1 + src/g_demo.c | 7 ++++++- src/g_game.c | 10 ++++++++++ src/k_hud.c | 17 ++++++++++++++--- src/lua_playerlib.c | 8 ++++++++ src/m_random.h | 2 ++ src/p_saveg.c | 4 ++++ src/p_user.c | 22 ++++++++++++++++++++++ src/r_skins.c | 12 ++++++++++++ src/r_skins.h | 1 + 11 files changed, 84 insertions(+), 4 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index d61360ded..10a9cf3a9 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -37,6 +37,7 @@ typedef enum { SF_HIRES = 1, // Draw the sprite at different size? SF_MACHINE = 1<<1, // Beep boop. Are you a robot? + SF_IRONMAN = 1<<2, // Pick a new skin during POSITION. I main Random! // free up to and including 1<<31 } skinflags_t; @@ -386,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 2f68edf2c..6d9c8c7f2 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6318,6 +6318,7 @@ struct int_const_s const INT_CONST[] = { // Character flags (skinflags_t) {"SF_HIRES",SF_HIRES}, {"SF_MACHINE",SF_MACHINE}, + {"SF_IRONMAN",SF_IRONMAN}, // Sound flags {"SF_TOTALLYSINGLE",SF_TOTALLYSINGLE}, diff --git a/src/g_demo.c b/src/g_demo.c index 103a60a41..baad4e3fc 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -274,6 +274,7 @@ void G_ReadDemoExtraData(void) kartspeed = READUINT8(demo_p); kartweight = READUINT8(demo_p); + demo_p++; // lastfakeskin if (stricmp(skins[players[p].skin].name, name) != 0) FindClosestSkinForStats(p, kartspeed, kartweight); @@ -2110,6 +2111,7 @@ void G_BeginRecording(void) // Kart speed and weight WRITEUINT8(demo_p, skins[player->skin].kartspeed); WRITEUINT8(demo_p, skins[player->skin].kartweight); + WRITEUINT8(demo_p, player->lastfakeskin); // And mobjtype_t is best with UINT32 too... WRITEUINT32(demo_p, player->followitem); @@ -2709,7 +2711,7 @@ void G_DoPlayDemo(char *defdemoname) char msg[1024]; boolean spectator; - UINT8 slots[MAXPLAYERS], kartspeed[MAXPLAYERS], kartweight[MAXPLAYERS], numslots = 0; + UINT8 slots[MAXPLAYERS], kartspeed[MAXPLAYERS], kartweight[MAXPLAYERS], lastfakeskin[MAXPLAYERS], numslots = 0; #if defined(SKIPERRORS) && !defined(DEVELOP) boolean skiperrors = false; @@ -3086,6 +3088,7 @@ void G_DoPlayDemo(char *defdemoname) // Kart stats, temporarily kartspeed[p] = READUINT8(demo_p); kartweight[p] = READUINT8(demo_p); + lastfakeskin[p] = READUINT8(demo_p); if (stricmp(skins[players[p].skin].name, skin) != 0) FindClosestSkinForStats(p, kartspeed[p], kartweight[p]); @@ -3143,6 +3146,7 @@ void G_DoPlayDemo(char *defdemoname) // it would only break the replay if we clipped them. players[i].kartspeed = kartspeed[i]; players[i].kartweight = kartweight[i]; + players[i].fakeskin = lastfakeskin[i]; } demo.deferstart = true; @@ -3334,6 +3338,7 @@ void G_AddGhost(char *defdemoname) kartspeed = READUINT8(p); kartweight = READUINT8(p); + p += 1; // lastfakeskin p += 4; // followitem (maybe change later) diff --git a/src/g_game.c b/src/g_game.c index bf37aadbc..3d06aed1c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2233,6 +2233,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT16 skincolor; INT32 skin; UINT32 availabilities; + UINT8 fakeskin; + UINT8 lastfakeskin; tic_t jointime; @@ -2278,6 +2280,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) skincolor = players[player].skincolor; skin = players[player].skin; + fakeskin = players[player].fakeskin; + lastfakeskin = players[player].lastfakeskin; // SRB2kart kartspeed = players[player].kartspeed; @@ -2431,6 +2435,12 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->botvars.rival = botrival; p->xtralife = xtralife; + if (betweenmaps) + p->fakeskin = MAXSKINS; + else + p->fakeskin = fakeskin; + p->lastfakeskin = lastfakeskin; + // SRB2kart p->itemroulette = itemroulette; p->roulettetype = roulettetype; diff --git a/src/k_hud.c b/src/k_hud.c index d4127e131..a2e2baa1c 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1699,6 +1699,7 @@ static boolean K_drawKartPositionFaces(void) boolean completed[MAXPLAYERS]; INT32 rankplayer[MAXPLAYERS]; INT32 bumperx, emeraldx, numplayersingame = 0; + INT32 xoff, yoff, flipflag = 0; UINT8 *colormap; ranklines = 0; @@ -1784,15 +1785,25 @@ static boolean K_drawKartPositionFaces(void) bumperx = FACE_X+19; emeraldx = FACE_X+16; + if (skins[players[rankplayer[i]].skin].flags & SF_IRONMAN) + { + flipflag = V_FLIP; + xoff = 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); + colormap = R_GetTranslationColormap(((skin_t*)players[rankplayer[i]].mo->skin) - skins, 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(((skin_t*)players[rankplayer[i]].mo->skin) - skins, 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[((skin_t*)players[rankplayer[i]].mo->skin) - skins][FACE_RANK], colormap); if (LUA_HudEnabled(hud_battlebumpers)) { diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index c5f5cc4d0..2a3b9e46a 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -410,6 +410,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->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 c08e32993..dc6f1884b 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -62,6 +62,8 @@ typedef enum PR_MOVINGTARGET, // Randomised moving targets + PR_RANDOMSKIN, // Random skin select from Heavy Magician(?) + PRNUMCLASS } pr_class_t; diff --git a/src/p_saveg.c b/src/p_saveg.c index e35fa823f..b75dfe591 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_user.c b/src/p_user.c index be7beff57..3c64604f4 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4167,6 +4167,28 @@ void P_PlayerThink(player_t *player) { player->stairjank--; } + // Random skin / "ironman" + if (((skin_t *)player->mo->skin)->flags & SF_IRONMAN) + { + if (player->mo) { + if (player->fakeskin != MAXSKINS) + { + SetFakePlayerSkin(player, player->fakeskin); + } + else + { + INT32 i; + do { + i = P_RandomKey(PR_RANDOMSKIN, numskins); + } while (skins[i].flags & SF_IRONMAN || i == player->lastfakeskin); + + SetFakePlayerSkin(player, i); + + S_StartSound(NULL, sfx_kc33); + K_SpawnDriftElectricSparks(player, player->skincolor, false); + } + } + } K_KartPlayerThink(player, cmd); // SRB2kart diff --git a/src/r_skins.c b/src/r_skins.c index 0557e9e88..ba081efd6 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -334,6 +334,17 @@ 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 skinnum) +{ + 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; +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // @@ -484,6 +495,7 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) // 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..9e51f025c 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -81,6 +81,7 @@ 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); boolean R_SkinUsable(INT32 playernum, INT32 skinnum); UINT32 R_GetSkinAvailabilities(void); INT32 R_SkinAvailable(const char *name); From c2224d2edca2c4b44e1639c60f107d73c96ca41e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 30 Oct 2022 06:19:21 -0700 Subject: [PATCH 02/31] Ironman: demo fixes --- src/g_demo.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/g_demo.c b/src/g_demo.c index baad4e3fc..c0aeb5839 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -274,7 +274,6 @@ void G_ReadDemoExtraData(void) kartspeed = READUINT8(demo_p); kartweight = READUINT8(demo_p); - demo_p++; // lastfakeskin if (stricmp(skins[players[p].skin].name, name) != 0) FindClosestSkinForStats(p, kartspeed, kartweight); From 8cc535925ba3b23c4b1146f812e032ca46278db1 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 30 Oct 2022 17:04:19 -0700 Subject: [PATCH 03/31] Ironman: Randomize every lap and after intro --- src/g_demo.c | 2 +- src/k_hud.c | 12 +++++++++--- src/p_spec.c | 3 +++ src/p_user.c | 12 ++---------- src/r_skins.c | 16 ++++++++++++++++ src/r_skins.h | 1 + 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index c0aeb5839..bacc64063 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -3145,7 +3145,7 @@ void G_DoPlayDemo(char *defdemoname) // it would only break the replay if we clipped them. players[i].kartspeed = kartspeed[i]; players[i].kartweight = kartweight[i]; - players[i].fakeskin = lastfakeskin[i]; + players[i].lastfakeskin = lastfakeskin[i]; } demo.deferstart = true; diff --git a/src/k_hud.c b/src/k_hud.c index a2e2baa1c..47f103f34 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1700,6 +1700,7 @@ static boolean K_drawKartPositionFaces(void) INT32 rankplayer[MAXPLAYERS]; INT32 bumperx, emeraldx, numplayersingame = 0; INT32 xoff, yoff, flipflag = 0; + UINT8 workingskin; UINT8 *colormap; ranklines = 0; @@ -1797,13 +1798,18 @@ static boolean K_drawKartPositionFaces(void) if (players[rankplayer[i]].mo->color) { - colormap = R_GetTranslationColormap(((skin_t*)players[rankplayer[i]].mo->skin) - skins, 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(((skin_t*)players[rankplayer[i]].mo->skin) - skins, players[rankplayer[i]].mo->color, GTC_CACHE); + colormap = R_GetTranslationColormap(workingskin, players[rankplayer[i]].mo->color, GTC_CACHE); - V_DrawMappedPatch(FACE_X + xoff, Y + yoff, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT|flipflag, faceprefix[((skin_t*)players[rankplayer[i]].mo->skin) - skins][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/p_spec.c b/src/p_spec.c index 25d75f3ff..7063b6dc5 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1919,6 +1919,9 @@ static void K_HandleLapIncrement(player_t *player) player->karthud[khud_lapanimation] = 80; } + if (skins[player->skin].flags & SF_IRONMAN) + SetRandomFakePlayerSkin(player); + if (rainbowstartavailable == true) { S_StartSound(player->mo, sfx_s23c); diff --git a/src/p_user.c b/src/p_user.c index 3c64604f4..f19211f9f 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4168,7 +4168,7 @@ void P_PlayerThink(player_t *player) player->stairjank--; } // Random skin / "ironman" - if (((skin_t *)player->mo->skin)->flags & SF_IRONMAN) + if (leveltime >= introtime && ((skin_t *)player->mo->skin)->flags & SF_IRONMAN) { if (player->mo) { if (player->fakeskin != MAXSKINS) @@ -4177,15 +4177,7 @@ void P_PlayerThink(player_t *player) } else { - INT32 i; - do { - i = P_RandomKey(PR_RANDOMSKIN, numskins); - } while (skins[i].flags & SF_IRONMAN || i == player->lastfakeskin); - - SetFakePlayerSkin(player, i); - - S_StartSound(NULL, sfx_kc33); - K_SpawnDriftElectricSparks(player, player->skincolor, false); + SetRandomFakePlayerSkin(player); } } } diff --git a/src/r_skins.c b/src/r_skins.c index ba081efd6..d4708b597 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 @@ -345,6 +347,20 @@ void SetFakePlayerSkin(player_t* player, INT32 skinnum) player->charflags = skins[skinnum].flags; } +// Loudly rerandomize +void SetRandomFakePlayerSkin(player_t* player) +{ + INT32 i; + do { + i = P_RandomKey(PR_RANDOMSKIN, numskins); + } while (skins[i].flags & SF_IRONMAN || i == player->lastfakeskin); + + SetFakePlayerSkin(player, i); + + S_StartSound(NULL, sfx_kc33); + K_SpawnDriftElectricSparks(player, player->skincolor, false); +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // diff --git a/src/r_skins.h b/src/r_skins.h index 9e51f025c..66fdaf3a6 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -82,6 +82,7 @@ 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 R_SkinUsable(INT32 playernum, INT32 skinnum); UINT32 R_GetSkinAvailabilities(void); INT32 R_SkinAvailable(const char *name); From 4a0666dcf367069a37964c76d6f860f4d620a9ed Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 30 Oct 2022 20:32:10 -0700 Subject: [PATCH 04/31] Ironman: vaguely Heavy Magician-esque transform particles --- src/r_skins.c | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/r_skins.c b/src/r_skins.c index d4708b597..8688d8648 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -357,8 +357,46 @@ void SetRandomFakePlayerSkin(player_t* player) SetFakePlayerSkin(player, i); - S_StartSound(NULL, sfx_kc33); - K_SpawnDriftElectricSparks(player, player->skincolor, false); + if (player->mo) + { + S_StartSound(player->mo, sfx_kc33); + + mobj_t *parent = player->mo; + fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale); + INT32 j; + + for (j = 0; j < 16; j++) + { + fixed_t hmomentum = P_RandomRange(PR_DECORATION, 3, 6) * parent->scale; + fixed_t vmomentum = P_RandomRange(PR_DECORATION, 1, 3) * parent->scale; + UINT16 color = P_RandomKey(PR_DECORATION, numskincolors); + + angle_t ang = R_PointToAngle(parent->momx, parent->momy); + SINT8 flip = 1; + + mobj_t *dust; + + if (j & 1) + ang -= ANGLE_90; + else + ang += ANGLE_90; + + dust = P_SpawnMobjFromMobj(parent, + FixedMul(rad, FINECOSINE(ang >> ANGLETOFINESHIFT)), + FixedMul(rad, FINESINE(ang >> ANGLETOFINESHIFT)), + parent->height, (j%3 == 0) ? MT_SIGNSPARKLE : MT_SPINDASHDUST + ); + flip = P_MobjFlip(dust); + + dust->momx = parent->momx + FixedMul(hmomentum, FINECOSINE(ang >> ANGLETOFINESHIFT)); + dust->momy = parent->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; + } + } } // From 8fe89c3dcdbeb59af460486b4de1350bb1ac57ef Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Sun, 30 Oct 2022 23:23:35 -0700 Subject: [PATCH 05/31] Ironman: papersprite box hell --- src/deh_tables.c | 5 +++ src/info.c | 58 +++++++++++++++++++++++++++++++ src/info.h | 6 ++++ src/p_mobj.c | 90 ++++++++++++++++++++++++++++++++++++++++++++++++ src/r_skins.c | 20 +++++++++-- 5 files changed, 176 insertions(+), 3 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 6d9c8c7f2..bd908e8b3 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3281,6 +3281,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi //"S_ITEMCAPSULE_BOTTOM", //"S_ITEMCAPSULE_INSIDE", + "S_MAGICIANBOX", + "S_MAGICIANBOXTOP", + // Signpost sparkles "S_SIGNSPARK1", "S_SIGNSPARK2", @@ -5278,6 +5281,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_FLOATINGITEM", "MT_ITEMCAPSULE", "MT_ITEMCAPSULE_PART", + "MT_MAGICIANBOX", + "MT_MAGICIANBOX_SIDE", "MT_SIGNSPARKLE", diff --git a/src/info.c b/src/info.c index b1f4bc1be..0d8d3e462 100644 --- a/src/info.c +++ b/src/info.c @@ -542,6 +542,7 @@ char sprnames[NUMSPRITES + 1][5] = "KINF", // Invincibility flash "INVI", // Invincibility speedlines "ICAP", // Item capsules + "MGBX", // Heavy Magician transform box "WIPD", // Wipeout dust trail "DRIF", // Drift Sparks @@ -3888,6 +3889,9 @@ 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_MGBX, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_TOP + {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 @@ -22390,6 +22394,60 @@ 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 + 26*FRACUNIT, // radius + 26*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT, // flags + S_NULL // raisestate + }, + + { // MT_MAGICIANBOX_SIDE + -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 + 26*FRACUNIT, // radius + 14*FRACUNIT, // height + 0, // display offset + 100, // mass + 0, // damage + sfx_None, // activesound + MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|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 176705a41..05caa8010 100644 --- a/src/info.h +++ b/src/info.h @@ -1088,6 +1088,7 @@ typedef enum sprite SPR_KINF, // Invincibility flash SPR_INVI, // Invincibility speedlines SPR_ICAP, // Item capsules + SPR_MGBX, // Heavy Magician transform box SPR_WIPD, // Wipeout dust trail SPR_DRIF, // Drift Sparks @@ -4292,6 +4293,9 @@ typedef enum state //S_ITEMCAPSULE_BOTTOM, //S_ITEMCAPSULE_INSIDE, + S_MAGICIANBOX, + S_MAGICIANBOX_TOP, + // Signpost sparkles S_SIGNSPARK1, S_SIGNSPARK2, @@ -6325,6 +6329,8 @@ typedef enum mobj_type MT_FLOATINGITEM, MT_ITEMCAPSULE, MT_ITEMCAPSULE_PART, + MT_MAGICIANBOX, + MT_MAGICIANBOX_SIDE, MT_SIGNSPARKLE, diff --git a/src/p_mobj.c b/src/p_mobj.c index bdf0064f0..cf58bf6c9 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7609,6 +7609,96 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->renderflags = (mobj->renderflags & ~RF_TRANSMASK)|(trans << RF_TRANSSHIFT); } break; + case MT_MAGICIANBOX: + fixed_t destx, desty, fakeangle; + INT32 j; + + // EV1: rotation rate + // EV2: lifetime + // cusval: should play sounds (limit 1) + + mobj->extravalue2--; + + if (mobj->extravalue2 == 0) + { + P_RemoveMobj(mobj); + break; + } + else if (mobj->extravalue2 < TICRATE/3) + { + mobj->target = NULL; + if (mobj->extravalue2 & 1) + mobj->renderflags |= RF_DONTDRAW; + else + mobj->renderflags &= ~RF_DONTDRAW; + } + else if (mobj->extravalue2 == TICRATE/3 && mobj->target) + { + mobj->target->renderflags &= ~RF_DONTDRAW; + + if (mobj->cusval) // Are we the side selected to play a sound? + { + S_StartSound(mobj, sfx_kc2e); + S_StartSound(mobj, sfx_s3k9f); + } + + for (j = 0; j < 16; j++) + { + fixed_t hmomentum = P_RandomRange(PR_DECORATION, 3, 6) * mobj->scale; + fixed_t vmomentum = P_RandomRange(PR_DECORATION, 1, 3) * mobj->scale; + UINT16 color = P_RandomKey(PR_DECORATION, numskincolors); + + angle_t ang = R_PointToAngle(mobj->target->momx, mobj->target->momy); + SINT8 flip = 1; + + mobj_t *dust; + + if (j & 1) + ang -= ANGLE_90; + else + ang += ANGLE_90; + + dust = P_SpawnMobjFromMobj(mobj, + FixedMul(mobj->radius, FINECOSINE(ang >> ANGLETOFINESHIFT)), + FixedMul(mobj->radius, FINESINE(ang >> ANGLETOFINESHIFT)), + mobj->target->height, (j%3 == 0) ? MT_SIGNSPARKLE : MT_SPINDASHDUST + ); + flip = P_MobjFlip(dust); + + dust->momx = mobj->target->momx + FixedMul(hmomentum, FINECOSINE(ang >> ANGLETOFINESHIFT)); + dust->momy = mobj->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; + } + } + else + { + mobj->target->renderflags |= RF_DONTDRAW; + } + + if (!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; + + fakeangle = (FixedInt(AngleFixed(mobj->angle)) + 90)%360; // What + + destx += FixedMul(mobj->radius*2, FINECOSINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); + desty += FixedMul(mobj->radius*2, FINESINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); + + P_MoveOrigin(mobj, destx, desty, mobj->target->z); + break; case MT_LIGHTNINGSHIELD: { fixed_t destx, desty; diff --git a/src/r_skins.c b/src/r_skins.c index 8688d8648..92299a9d2 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -360,15 +360,29 @@ void SetRandomFakePlayerSkin(player_t* player) if (player->mo) { S_StartSound(player->mo, sfx_kc33); + S_StartSound(player->mo, sfx_cdfm44); mobj_t *parent = player->mo; fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale); - INT32 j; + INT32 j, k; + + for (k = 0; k < 4; k++) + { + mobj_t *box = P_SpawnMobjFromMobj(parent, 0, 0, 0, MT_MAGICIANBOX); + box->target = parent; + box->angle = ANGLE_90 * k; + box->extravalue1 = 1; // Rotation rate + box->extravalue2 = 3*TICRATE/2; // Lifetime + if (k == 0) + box->cusval = 1; // Should play sounds when disappearing + else + box->cusval = 0; + } for (j = 0; j < 16; j++) { - fixed_t hmomentum = P_RandomRange(PR_DECORATION, 3, 6) * parent->scale; - fixed_t vmomentum = P_RandomRange(PR_DECORATION, 1, 3) * parent->scale; + fixed_t hmomentum = P_RandomRange(PR_DECORATION, 10, 20) * parent->scale; + fixed_t vmomentum = P_RandomRange(PR_DECORATION, 5, 10) * parent->scale; UINT16 color = P_RandomKey(PR_DECORATION, numskincolors); angle_t ang = R_PointToAngle(parent->momx, parent->momy); From 2a46af5a443661a98026f75eca136a8a2cc2310e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 15:45:07 -0700 Subject: [PATCH 06/31] Ironman: animation fixes, don't transform in POSITION --- src/deh_tables.c | 1 - src/info.c | 37 +++++-------------------------------- src/info.h | 1 - src/p_mobj.c | 21 ++++++++++++++++++++- src/p_user.c | 3 ++- src/r_skins.c | 18 ++++++++++++++---- src/r_skins.h | 2 +- 7 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index bd908e8b3..3b6ad291f 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5282,7 +5282,6 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_ITEMCAPSULE", "MT_ITEMCAPSULE_PART", "MT_MAGICIANBOX", - "MT_MAGICIANBOX_SIDE", "MT_SIGNSPARKLE", diff --git a/src/info.c b/src/info.c index 0d8d3e462..b3d377532 100644 --- a/src/info.c +++ b/src/info.c @@ -22394,7 +22394,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL // raisestate }, - { // MT_MAGICIANBOX + { // MT_MAGICIANBOX -1, // doomednum S_MAGICIANBOX, // spawnstate 1000, // spawnhealth @@ -22411,43 +22411,16 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = S_NULL, // xdeathstate sfx_None, // deathsound 0, // speed - 26*FRACUNIT, // radius - 26*FRACUNIT, // height + 20*FRACUNIT, // radius + 20*FRACUNIT, // height 0, // display offset 100, // mass 0, // damage sfx_None, // activesound - MF_NOGRAVITY|MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT, // flags + MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPTHING|MF_NOCLIPHEIGHT, // flags S_NULL // raisestate }, - - { // MT_MAGICIANBOX_SIDE - -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 - 26*FRACUNIT, // radius - 14*FRACUNIT, // height - 0, // display offset - 100, // mass - 0, // damage - sfx_None, // activesound - MF_SCENERY|MF_NOGRAVITY|MF_NOBLOCKMAP|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 05caa8010..f70925273 100644 --- a/src/info.h +++ b/src/info.h @@ -6330,7 +6330,6 @@ typedef enum mobj_type MT_ITEMCAPSULE, MT_ITEMCAPSULE_PART, MT_MAGICIANBOX, - MT_MAGICIANBOX_SIDE, MT_SIGNSPARKLE, diff --git a/src/p_mobj.c b/src/p_mobj.c index cf58bf6c9..d3f50c475 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7610,6 +7610,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } break; case MT_MAGICIANBOX: + { fixed_t destx, desty, fakeangle; INT32 j; @@ -7635,6 +7636,14 @@ static boolean P_MobjRegularThink(mobj_t *mobj) else if (mobj->extravalue2 == TICRATE/3 && mobj->target) { mobj->target->renderflags &= ~RF_DONTDRAW; + + mobj->momx = mobj->target->momx; + mobj->momy = mobj->target->momy; + mobj->momz = mobj->target->momz; + + P_Thrust(mobj, mobj->angle + ANGLE_90, 32*mapobjectscale); + mobj->flags &= ~MF_NOGRAVITY; + mobj->momz += 10*mapobjectscale; if (mobj->cusval) // Are we the side selected to play a sound? { @@ -7673,6 +7682,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) dust->color = color; dust->colorized = true; } + return true; } else { @@ -7697,8 +7707,17 @@ static boolean P_MobjRegularThink(mobj_t *mobj) destx += FixedMul(mobj->radius*2, FINECOSINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); desty += FixedMul(mobj->radius*2, FINESINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); - P_MoveOrigin(mobj, destx, desty, mobj->target->z); + if (mobj->flags2 & MF2_AMBUSH) + { + P_SetOrigin(mobj, destx, desty, mobj->target->z); + mobj->flags2 &= ~MF2_AMBUSH; + } + else + { + P_MoveOrigin(mobj, destx, desty, mobj->target->z); + } break; + } case MT_LIGHTNINGSHIELD: { fixed_t destx, desty; diff --git a/src/p_user.c b/src/p_user.c index f19211f9f..8d07207ed 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4177,7 +4177,8 @@ void P_PlayerThink(player_t *player) } else { - SetRandomFakePlayerSkin(player); + // "Don't halfass" - Oni + // SetRandomFakePlayerSkin(player, false); } } } diff --git a/src/r_skins.c b/src/r_skins.c index 92299a9d2..3c689ea22 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -348,7 +348,7 @@ void SetFakePlayerSkin(player_t* player, INT32 skinnum) } // Loudly rerandomize -void SetRandomFakePlayerSkin(player_t* player) +void SetRandomFakePlayerSkin(player_t* player, boolean fast) { INT32 i; do { @@ -364,15 +364,25 @@ void SetRandomFakePlayerSkin(player_t* player) mobj_t *parent = player->mo; fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale); + fixed_t baseangle = P_RandomRange(PR_DECORATION, 0, 359); INT32 j, k; for (k = 0; k < 4; k++) { mobj_t *box = P_SpawnMobjFromMobj(parent, 0, 0, 0, MT_MAGICIANBOX); box->target = parent; - box->angle = ANGLE_90 * k; - box->extravalue1 = 1; // Rotation rate - box->extravalue2 = 3*TICRATE/2; // Lifetime + box->angle = FixedAngle((baseangle + k*90) * FRACUNIT); + box->flags2 |= MF2_AMBUSH; + if (fast) + { + box->extravalue1 = 25; // Rotation rate + box->extravalue2 = 3*TICRATE/4; // Lifetime + } + else + { + box->extravalue1 = 1; + box->extravalue2 = 3*TICRATE/2; + } if (k == 0) box->cusval = 1; // Should play sounds when disappearing else diff --git a/src/r_skins.h b/src/r_skins.h index 66fdaf3a6..512e476f8 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -82,7 +82,7 @@ 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); +void SetRandomFakePlayerSkin(player_t* player, boolean fast); boolean R_SkinUsable(INT32 playernum, INT32 skinnum); UINT32 R_GetSkinAvailabilities(void); INT32 R_SkinAvailable(const char *name); From 8c800cf88eb036c376106917b8ca307d3c7cc92e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 16:03:24 -0700 Subject: [PATCH 07/31] Ironman: use introtime transform in non-race --- src/p_spec.c | 2 +- src/p_user.c | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index 7063b6dc5..deb197a6e 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1920,7 +1920,7 @@ static void K_HandleLapIncrement(player_t *player) } if (skins[player->skin].flags & SF_IRONMAN) - SetRandomFakePlayerSkin(player); + SetRandomFakePlayerSkin(player, true); if (rainbowstartavailable == true) { diff --git a/src/p_user.c b/src/p_user.c index 8d07207ed..24317d014 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4175,10 +4175,9 @@ void P_PlayerThink(player_t *player) { SetFakePlayerSkin(player, player->fakeskin); } - else + else if (!(gametyperules & GTR_CIRCUIT)) { - // "Don't halfass" - Oni - // SetRandomFakePlayerSkin(player, false); + SetRandomFakePlayerSkin(player, false); } } } From dd93f322cc6fcc2e3d8e60947d603869073c4144 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 16:46:54 -0700 Subject: [PATCH 08/31] Ironman: fix weird particle angles --- src/p_mobj.c | 18 +++++++++--------- src/r_skins.c | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index d3f50c475..f3f3cb97c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7644,20 +7644,20 @@ static boolean P_MobjRegularThink(mobj_t *mobj) P_Thrust(mobj, mobj->angle + ANGLE_90, 32*mapobjectscale); mobj->flags &= ~MF_NOGRAVITY; mobj->momz += 10*mapobjectscale; - - if (mobj->cusval) // Are we the side selected to play a sound? - { - S_StartSound(mobj, sfx_kc2e); - S_StartSound(mobj, sfx_s3k9f); - } + + if (!mobj->cusval) // Some stuff should only occur once per box + return true; + + S_StartSound(mobj, sfx_kc2e); + S_StartSound(mobj, sfx_s3k9f); for (j = 0; j < 16; j++) { - fixed_t hmomentum = P_RandomRange(PR_DECORATION, 3, 6) * mobj->scale; - fixed_t vmomentum = P_RandomRange(PR_DECORATION, 1, 3) * mobj->scale; + fixed_t hmomentum = P_RandomRange(PR_DECORATION, -5, 5) * mobj->scale; + fixed_t vmomentum = P_RandomRange(PR_DECORATION, -5, 5) * mobj->scale; UINT16 color = P_RandomKey(PR_DECORATION, numskincolors); - angle_t ang = R_PointToAngle(mobj->target->momx, mobj->target->momy); + fixed_t ang = FixedAngle(P_RandomRange(PR_DECORATION, 0, 359)*FRACUNIT); SINT8 flip = 1; mobj_t *dust; diff --git a/src/r_skins.c b/src/r_skins.c index 3c689ea22..9fbeaf038 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -391,11 +391,11 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) for (j = 0; j < 16; j++) { - fixed_t hmomentum = P_RandomRange(PR_DECORATION, 10, 20) * parent->scale; - fixed_t vmomentum = P_RandomRange(PR_DECORATION, 5, 10) * parent->scale; + fixed_t hmomentum = P_RandomRange(PR_DECORATION, -10, 10) * parent->scale; + fixed_t vmomentum = P_RandomRange(PR_DECORATION, -10, 10) * parent->scale; UINT16 color = P_RandomKey(PR_DECORATION, numskincolors); - angle_t ang = R_PointToAngle(parent->momx, parent->momy); + angle_t ang = FixedAngle(P_RandomRange(PR_DECORATION, 0, 359)*FRACUNIT); SINT8 flip = 1; mobj_t *dust; From b9517d3a890e1bfb1261763c733ac7488e20ba1b Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 16:56:53 -0700 Subject: [PATCH 09/31] Ironman: longer lap-advance animation --- src/r_skins.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/r_skins.c b/src/r_skins.c index 9fbeaf038..4db1d7963 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -375,8 +375,8 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) box->flags2 |= MF2_AMBUSH; if (fast) { - box->extravalue1 = 25; // Rotation rate - box->extravalue2 = 3*TICRATE/4; // Lifetime + box->extravalue1 = 10; // Rotation rate + box->extravalue2 = 5*TICRATE/4; // Lifetime } else { From 02bf3bc695257f14662f545227ec51373a99bea7 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 18:09:02 -0700 Subject: [PATCH 10/31] Ironman: splat top/bottom WIP --- src/deh_tables.c | 1 + src/info.c | 5 ++++- src/info.h | 3 +++ src/p_mobj.c | 30 ++++++++++++++++++++++++------ src/r_skins.c | 9 ++++++++- 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 3b6ad291f..17b5a7bde 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3283,6 +3283,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_MAGICIANBOX", "S_MAGICIANBOXTOP", + "S_MAGICIANBOXBOTTOM", // Signpost sparkles "S_SIGNSPARK1", diff --git a/src/info.c b/src/info.c index b3d377532..d04bd078d 100644 --- a/src/info.c +++ b/src/info.c @@ -543,6 +543,8 @@ char sprnames[NUMSPRITES + 1][5] = "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 @@ -3890,7 +3892,8 @@ state_t states[NUMSTATES] = //{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_MGBX, FF_PAPERSPRITE|0, -1, {NULL}, 0, 0, S_NULL}, // S_MAGICIANBOX_TOP + {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 diff --git a/src/info.h b/src/info.h index f70925273..b272e5176 100644 --- a/src/info.h +++ b/src/info.h @@ -1089,6 +1089,8 @@ typedef enum sprite 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 +4297,7 @@ typedef enum state S_MAGICIANBOX, S_MAGICIANBOX_TOP, + S_MAGICIANBOX_BOTTOM, // Signpost sparkles S_SIGNSPARK1, diff --git a/src/p_mobj.c b/src/p_mobj.c index f3f3cb97c..1d0d11371 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7612,6 +7612,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_MAGICIANBOX: { fixed_t destx, desty, fakeangle; + fixed_t zoff = 0; INT32 j; // EV1: rotation rate @@ -7635,15 +7636,20 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } else if (mobj->extravalue2 == TICRATE/3 && mobj->target) { + mobj->target->renderflags &= ~RF_DONTDRAW; mobj->momx = mobj->target->momx; mobj->momy = mobj->target->momy; mobj->momz = mobj->target->momz; - P_Thrust(mobj, mobj->angle + ANGLE_90, 32*mapobjectscale); + 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; @@ -7702,19 +7708,31 @@ static boolean P_MobjRegularThink(mobj_t *mobj) destx = mobj->target->x; desty = mobj->target->y; - fakeangle = (FixedInt(AngleFixed(mobj->angle)) + 90)%360; // What + if (mobj->state == &states[S_MAGICIANBOX]) // sides + { + CONS_Printf("side\n"); + fakeangle = (FixedInt(AngleFixed(mobj->angle)) + 90)%360; // What - destx += FixedMul(mobj->radius*2, FINECOSINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); - desty += FixedMul(mobj->radius*2, FINESINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); + destx += FixedMul(mobj->radius*2, FINECOSINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); + desty += FixedMul(mobj->radius*2, FINESINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); + } + else if (mobj->state == &states[S_MAGICIANBOX_TOP]) // top + { + CONS_Printf("top\n"); + zoff = mobj->radius*4; + } + else { + CONS_Printf("bottom\n"); + } if (mobj->flags2 & MF2_AMBUSH) { - P_SetOrigin(mobj, destx, desty, mobj->target->z); + P_SetOrigin(mobj, destx, desty, mobj->target->z + zoff); mobj->flags2 &= ~MF2_AMBUSH; } else { - P_MoveOrigin(mobj, destx, desty, mobj->target->z); + P_MoveOrigin(mobj, destx, desty, mobj->target->z + zoff); } break; } diff --git a/src/r_skins.c b/src/r_skins.c index 4db1d7963..7ffcc7f7b 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -367,7 +367,7 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) fixed_t baseangle = P_RandomRange(PR_DECORATION, 0, 359); INT32 j, k; - for (k = 0; k < 4; k++) + for (k = 0; k < 6; k++) { mobj_t *box = P_SpawnMobjFromMobj(parent, 0, 0, 0, MT_MAGICIANBOX); box->target = parent; @@ -387,6 +387,13 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) box->cusval = 1; // Should play sounds when disappearing else box->cusval = 0; + + if (k > 3) + { + P_SetMobjState(box, (k == 4) ? S_MAGICIANBOX_TOP : S_MAGICIANBOX_BOTTOM); + box->renderflags |= RF_NOSPLATBILLBOARD; + box->angle = FixedAngle(baseangle*FRACUNIT); + } } for (j = 0; j < 16; j++) From 8d2d6bfc78cb288eb332b7fdc577ad1ada0d3a1b Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 19:01:28 -0700 Subject: [PATCH 11/31] Ironman: return to base character when exiting --- src/p_mobj.c | 5 ----- src/p_spec.c | 7 ++++--- src/p_user.c | 26 ++++++++++++++++++-------- src/r_skins.c | 9 +++++++++ src/r_skins.h | 1 + 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 1d0d11371..0967eff2e 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7710,7 +7710,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->state == &states[S_MAGICIANBOX]) // sides { - CONS_Printf("side\n"); fakeangle = (FixedInt(AngleFixed(mobj->angle)) + 90)%360; // What destx += FixedMul(mobj->radius*2, FINECOSINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); @@ -7718,12 +7717,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } else if (mobj->state == &states[S_MAGICIANBOX_TOP]) // top { - CONS_Printf("top\n"); zoff = mobj->radius*4; } - else { - CONS_Printf("bottom\n"); - } if (mobj->flags2 & MF2_AMBUSH) { diff --git a/src/p_spec.c b/src/p_spec.c index deb197a6e..ca2f32009 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1919,9 +1919,6 @@ static void K_HandleLapIncrement(player_t *player) player->karthud[khud_lapanimation] = 80; } - if (skins[player->skin].flags & SF_IRONMAN) - SetRandomFakePlayerSkin(player, true); - if (rainbowstartavailable == true) { S_StartSound(player->mo, sfx_s23c); @@ -1965,7 +1962,11 @@ static void K_HandleLapIncrement(player_t *player) { P_DoPlayerExit(player); P_SetupSignExit(player); + } else if (skins[player->skin].flags & SF_IRONMAN) + { + SetRandomFakePlayerSkin(player, true); } + if (player->laps > player->latestlap) { diff --git a/src/p_user.c b/src/p_user.c index 24317d014..03c35affe 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); @@ -4168,18 +4170,26 @@ void P_PlayerThink(player_t *player) player->stairjank--; } // Random skin / "ironman" - if (leveltime >= introtime && ((skin_t *)player->mo->skin)->flags & SF_IRONMAN) + if ((!P_MobjWasRemoved(player->mo)) & (skins[player->skin].flags & SF_IRONMAN)) // we are Heavy Magician with a mobj { - if (player->mo) { - if (player->fakeskin != MAXSKINS) + if (((skin_t *)player->mo->skin)->flags & SF_IRONMAN) // no fakeskin yet + { + if (leveltime >= introtime && !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); + } } K_KartPlayerThink(player, cmd); // SRB2kart diff --git a/src/r_skins.c b/src/r_skins.c index 7ffcc7f7b..71ea80f1f 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -430,6 +430,15 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) } } +// Return to base skin from an SF_IRONMAN randomization +void ClearFakePlayerSkin(player_t* player) +{ + if (player->mo) + { + player->mo->skin = &skins[player->skin]; + } +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // diff --git a/src/r_skins.h b/src/r_skins.h index 512e476f8..1cc06cca4 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -83,6 +83,7 @@ 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); From 770378eea066bb1487ca149d7358441e8c7fd55e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 19:14:30 -0700 Subject: [PATCH 12/31] Ironman: move transform VFX into function for reuse --- src/k_kart.c | 41 +++++++++++++++++++++++++++++++++++++++++ src/k_kart.h | 1 + src/p_mobj.c | 38 +++++--------------------------------- src/r_skins.c | 45 +++++++-------------------------------------- 4 files changed, 54 insertions(+), 71 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index dff8c406a..d8fe83541 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -2727,6 +2727,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/p_mobj.c b/src/p_mobj.c index 0967eff2e..c2ef43d05 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7613,11 +7613,13 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { fixed_t destx, desty, fakeangle; fixed_t zoff = 0; - INT32 j; // EV1: rotation rate // EV2: lifetime - // cusval: should play sounds (limit 1) + // 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--; @@ -7657,37 +7659,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) S_StartSound(mobj, sfx_kc2e); S_StartSound(mobj, sfx_s3k9f); - for (j = 0; j < 16; j++) - { - fixed_t hmomentum = P_RandomRange(PR_DECORATION, -5, 5) * mobj->scale; - fixed_t vmomentum = P_RandomRange(PR_DECORATION, -5, 5) * mobj->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 (j & 1) - ang -= ANGLE_90; - else - ang += ANGLE_90; - - dust = P_SpawnMobjFromMobj(mobj, - FixedMul(mobj->radius, FINECOSINE(ang >> ANGLETOFINESHIFT)), - FixedMul(mobj->radius, FINESINE(ang >> ANGLETOFINESHIFT)), - mobj->target->height, (j%3 == 0) ? MT_SIGNSPARKLE : MT_SPINDASHDUST - ); - flip = P_MobjFlip(dust); - - dust->momx = mobj->target->momx + FixedMul(hmomentum, FINECOSINE(ang >> ANGLETOFINESHIFT)); - dust->momy = mobj->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; - } + K_SpawnMagicianParticles(mobj, 5); return true; } else diff --git a/src/r_skins.c b/src/r_skins.c index 71ea80f1f..59156ba06 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -363,15 +363,14 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) S_StartSound(player->mo, sfx_cdfm44); mobj_t *parent = player->mo; - fixed_t rad = FixedDiv(FixedHypot(parent->radius, parent->radius), parent->scale); fixed_t baseangle = P_RandomRange(PR_DECORATION, 0, 359); - INT32 j, k; + INT32 j; - for (k = 0; k < 6; k++) + for (j = 0; j < 6; j++) // 0-3 = sides, 4 = top, 5 = bottom { mobj_t *box = P_SpawnMobjFromMobj(parent, 0, 0, 0, MT_MAGICIANBOX); box->target = parent; - box->angle = FixedAngle((baseangle + k*90) * FRACUNIT); + box->angle = FixedAngle((baseangle + j*90) * FRACUNIT); box->flags2 |= MF2_AMBUSH; if (fast) { @@ -383,50 +382,20 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) box->extravalue1 = 1; box->extravalue2 = 3*TICRATE/2; } - if (k == 0) + if (j == 0) box->cusval = 1; // Should play sounds when disappearing else box->cusval = 0; - if (k > 3) + if (j > 3) { - P_SetMobjState(box, (k == 4) ? S_MAGICIANBOX_TOP : S_MAGICIANBOX_BOTTOM); + P_SetMobjState(box, (j == 4) ? S_MAGICIANBOX_TOP : S_MAGICIANBOX_BOTTOM); box->renderflags |= RF_NOSPLATBILLBOARD; box->angle = FixedAngle(baseangle*FRACUNIT); } } - for (j = 0; j < 16; j++) - { - fixed_t hmomentum = P_RandomRange(PR_DECORATION, -10, 10) * parent->scale; - fixed_t vmomentum = P_RandomRange(PR_DECORATION, -10, 10) * parent->scale; - UINT16 color = P_RandomKey(PR_DECORATION, numskincolors); - - angle_t ang = FixedAngle(P_RandomRange(PR_DECORATION, 0, 359)*FRACUNIT); - SINT8 flip = 1; - - mobj_t *dust; - - if (j & 1) - ang -= ANGLE_90; - else - ang += ANGLE_90; - - dust = P_SpawnMobjFromMobj(parent, - FixedMul(rad, FINECOSINE(ang >> ANGLETOFINESHIFT)), - FixedMul(rad, FINESINE(ang >> ANGLETOFINESHIFT)), - parent->height, (j%3 == 0) ? MT_SIGNSPARKLE : MT_SPINDASHDUST - ); - flip = P_MobjFlip(dust); - - dust->momx = parent->momx + FixedMul(hmomentum, FINECOSINE(ang >> ANGLETOFINESHIFT)); - dust->momy = parent->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; - } + K_SpawnMagicianParticles(player->mo, 10); } } From 26eb1cf9e2ba2dce47a9b7b192985bff2f664723 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 31 Oct 2022 19:01:44 -0700 Subject: [PATCH 13/31] Use interpolated mobj x/y/z/angle for splats --- src/hardware/hw_glob.h | 2 ++ src/hardware/hw_main.c | 77 ++++++++++++++++++++++++------------------ src/r_splats.c | 8 ++--- src/r_things.c | 1 + src/r_things.h | 2 +- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index c13da6889..bc05ea4d4 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -87,6 +87,8 @@ typedef struct gl_vissprite_s boolean rotated; UINT8 translucency; //alpha level 0-255 + angle_t angle; // for splats + //Hurdler: 25/04/2000: now support colormap in hardware mode UINT8 *colormap; INT32 dispoffset; // copy of info->dispoffset, affects ordering but not drawing diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 3fecba478..c0db0fe5c 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -4179,7 +4179,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) scale *= spr->shadowscale; if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD) - angle = spr->mobj->angle; + angle = spr->angle; else angle = viewangle; @@ -4234,8 +4234,8 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) // Translate for (i = 0; i < 4; i++) { - wallVerts[i].x = rotated[i].x + FIXED_TO_FLOAT(spr->mobj->x); - wallVerts[i].z = rotated[i].y + FIXED_TO_FLOAT(spr->mobj->y); + wallVerts[i].x = rotated[i].x + spr->x1; + wallVerts[i].z = rotated[i].y + spr->z1; } if (renderflags & (RF_SLOPESPLAT | RF_OBJECTSLOPESPLAT)) @@ -4262,7 +4262,7 @@ static void HWR_DrawSprite(gl_vissprite_t *spr) else { for (i = 0; i < 4; i++) - wallVerts[i].y = FIXED_TO_FLOAT(spr->mobj->z) + zoffset; + wallVerts[i].y = FIXED_TO_FLOAT(spr->gz) + zoffset; } } else @@ -5383,43 +5383,52 @@ static void HWR_ProjectSprite(mobj_t *thing) this_xscale = spritexscale * this_scale; this_yscale = spriteyscale * this_scale; - if (flip) + if (splat) { - x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); - x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + z1 = z2 = tr_y; + x1 = x2 = tr_x; + gz = gzt = interp.z; } else { - x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); - x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); - } + if (flip) + { + x1 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); + x2 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + } + else + { + x1 = (FIXED_TO_FLOAT(spr_offset) * this_xscale); + x2 = (FIXED_TO_FLOAT(spr_width - spr_offset) * this_xscale); + } - // test if too close -/* - if (papersprite) - { - z1 = tz - x1 * angle_scalez; - z2 = tz + x2 * angle_scalez; + // test if too close + /* + if (papersprite) + { + z1 = tz - x1 * angle_scalez; + z2 = tz + x2 * angle_scalez; - if (max(z1, z2) < ZCLIP_PLANE) - return; - } -*/ + if (max(z1, z2) < ZCLIP_PLANE) + return; + } + */ - z1 = tr_y + x1 * rightsin; - z2 = tr_y - x2 * rightsin; - x1 = tr_x + x1 * rightcos; - x2 = tr_x - x2 * rightcos; + z1 = tr_y + x1 * rightsin; + z2 = tr_y - x2 * rightsin; + x1 = tr_x + x1 * rightcos; + x2 = tr_x - x2 * rightcos; - if (vflip) - { - gz = FIXED_TO_FLOAT(interp.z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); - gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale); - } - else - { - gzt = FIXED_TO_FLOAT(interp.z) + (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); - gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale); + if (vflip) + { + gz = FIXED_TO_FLOAT(interp.z + thing->height) - (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); + gzt = gz + (FIXED_TO_FLOAT(spr_height) * this_yscale); + } + else + { + gzt = FIXED_TO_FLOAT(interp.z) + (FIXED_TO_FLOAT(spr_topoffset) * this_yscale); + gz = gzt - (FIXED_TO_FLOAT(spr_height) * this_yscale); + } } if (thing->subsector->sector->cullheight) @@ -5575,6 +5584,8 @@ static void HWR_ProjectSprite(mobj_t *thing) vis->precip = false; vis->bbox = false; + + vis->angle = interp.angle; } #ifdef HWPRECIP diff --git a/src/r_splats.c b/src/r_splats.c index 27cc5566a..80ff44743 100644 --- a/src/r_splats.c +++ b/src/r_splats.c @@ -185,7 +185,7 @@ void R_DrawFloorSplat(vissprite_t *spr) splat.scale = FixedMul(splat.scale, ((skin_t *)mobj->skin)->highresscale); if (spr->rotateflags & SRF_3D || renderflags & RF_NOSPLATBILLBOARD) - splatangle = mobj->angle; + splatangle = spr->centerangle; else splatangle = spr->viewpoint.angle; @@ -209,14 +209,14 @@ void R_DrawFloorSplat(vissprite_t *spr) xoffset = FixedMul(leftoffset, splat.xscale); yoffset = FixedMul(topoffset, splat.yscale); - x = mobj->x; - y = mobj->y; + x = spr->gx; + y = spr->gy; w = (splat.width * splat.xscale); h = (splat.height * splat.yscale); splat.x = x; splat.y = y; - splat.z = mobj->z; + splat.z = spr->pz; splat.slope = NULL; // Set positions diff --git a/src/r_things.c b/src/r_things.c index 5b43beed2..9e042c6af 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -2056,6 +2056,7 @@ static void R_ProjectSprite(mobj_t *thing) tr_y = (interp.y - sort_y) - viewy; sort_z = FixedMul(tr_x, viewcos) + FixedMul(tr_y, viewsin); sortsplat = FixedDiv(projectiony[viewssnum], sort_z); + centerangle = interp.angle; } // PORTAL SPRITE CLIPPING diff --git a/src/r_things.h b/src/r_things.h index 881de773e..18c55629b 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -171,7 +171,7 @@ typedef struct vissprite_s fixed_t paperoffset, paperdistance; // for paper sprites, offset/dist relative to the angle fixed_t xiscale; // negative if flipped - angle_t centerangle; // for paper sprites + angle_t centerangle; // for paper sprites / splats // for floor sprites struct { From 603a1671b46286d0570f8f2ff11b57a53bddae60 Mon Sep 17 00:00:00 2001 From: James R Date: Mon, 31 Oct 2022 19:03:09 -0700 Subject: [PATCH 14/31] Use angle between viewz and mobj z to determine splat sprite angle --- src/hardware/hw_main.c | 13 ++++++++++--- src/r_things.c | 6 +++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index c0db0fe5c..74385e7f7 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -5254,9 +5254,16 @@ static void HWR_ProjectSprite(mobj_t *thing) I_Error("sprframes NULL for sprite %d\n", thing->sprite); #endif - ang = R_PointToAngle (interp.x, interp.y) - interp.angle; - if (mirrored) - ang = InvAngle(ang); + if (splat) + { + ang = R_PointToAngle2(0, viewz, 0, interp.z); + } + else + { + ang = R_PointToAngle (interp.x, interp.y) - interp.angle; + if (mirrored) + ang = InvAngle(ang); + } if (sprframe->rotate == SRF_SINGLE) { diff --git a/src/r_things.c b/src/r_things.c index 9e042c6af..41dfc8585 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -1755,7 +1755,11 @@ static void R_ProjectSprite(mobj_t *thing) I_Error("R_ProjectSprite: sprframes NULL for sprite %d\n", thing->sprite); #endif - if (sprframe->rotate != SRF_SINGLE || papersprite) + if (splat) + { + ang = R_PointToAngle2(0, viewz, 0, interp.z); + } + else if (sprframe->rotate != SRF_SINGLE || papersprite) { ang = R_PointToAngle (interp.x, interp.y) - interp.angle; if (mirrored) From 69f70964fa348c776e09c85d51d33ad5c4959044 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Mon, 31 Oct 2022 19:20:30 -0700 Subject: [PATCH 15/31] Ironman: Add FX to exit transform --- src/r_skins.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/r_skins.c b/src/r_skins.c index 59156ba06..180ee2678 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -402,9 +402,11 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) // Return to base skin from an SF_IRONMAN randomization void ClearFakePlayerSkin(player_t* player) { - if (player->mo) + if (!P_MobjWasRemoved(player->mo)) { player->mo->skin = &skins[player->skin]; + S_StartSound(player->mo, sfx_s3k9f); + K_SpawnMagicianParticles(player->mo, 5); } } From 216bad471209e9be32dc2f2e7caeb2637232256b Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 03:57:43 -0700 Subject: [PATCH 16/31] Ironman: Don't do 5 angle conversions for no reason --- src/p_mobj.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index c2ef43d05..5fb2b7114 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7611,7 +7611,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) break; case MT_MAGICIANBOX: { - fixed_t destx, desty, fakeangle; + fixed_t destx, desty; fixed_t zoff = 0; // EV1: rotation rate @@ -7682,10 +7682,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (mobj->state == &states[S_MAGICIANBOX]) // sides { - fakeangle = (FixedInt(AngleFixed(mobj->angle)) + 90)%360; // What - - destx += FixedMul(mobj->radius*2, FINECOSINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); - desty += FixedMul(mobj->radius*2, FINESINE(FixedAngle(fakeangle*FRACUNIT) >> ANGLETOFINESHIFT)); + 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 { From 565a6de546a6bee0d7791932f6dfacd0c3aa01e1 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 04:15:38 -0700 Subject: [PATCH 17/31] Ironman: Don't flip minirankings until you're actually transformed --- src/k_hud.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/k_hud.c b/src/k_hud.c index 47f103f34..e9f8204d5 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1786,7 +1786,10 @@ static boolean K_drawKartPositionFaces(void) bumperx = FACE_X+19; emeraldx = FACE_X+16; - if (skins[players[rankplayer[i]].skin].flags & SF_IRONMAN) + // 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) ) { flipflag = V_FLIP; xoff = 16; From 29595f946b968a9cb514560532a5192437198041 Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Thu, 3 Nov 2022 08:47:44 -0400 Subject: [PATCH 18/31] Add V_VFLIP Ironman can now be shitty --- src/deh_tables.c | 1 + src/hardware/hw_draw.c | 17 ++++++++++++++--- src/k_hud.c | 4 ++-- src/v_video.c | 41 +++++++++++++++++++++++++++-------------- src/v_video.h | 9 ++++++--- 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/deh_tables.c b/src/deh_tables.c index 17b5a7bde..63b076d30 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6690,6 +6690,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/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/k_hud.c b/src/k_hud.c index e9f8204d5..18d133844 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1791,8 +1791,8 @@ static boolean K_drawKartPositionFaces(void) && !P_MobjWasRemoved(players[rankplayer[i]].mo) && !(((skin_t*)players[rankplayer[i]].mo->skin)->flags & SF_IRONMAN) ) { - flipflag = V_FLIP; - xoff = 16; + flipflag = V_FLIP|V_VFLIP; // blonic flip + xoff = yoff = 16; } else { flipflag = 0; diff --git a/src/v_video.c b/src/v_video.c index 0e85bf2fa..1369ca627 100644 --- a/src/v_video.c +++ b/src/v_video.c @@ -593,10 +593,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; @@ -607,15 +603,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) @@ -683,17 +682,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); } } @@ -752,7 +765,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 35be68b2f..c3467d31e 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -81,11 +81,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 From 8d4688af12ffc157f048eae422ec61581ae65110 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 06:15:45 -0700 Subject: [PATCH 19/31] Ironman: fix non-Race transformations and true death skin restore --- src/p_user.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/p_user.c b/src/p_user.c index 03c35affe..1ef60277a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -4169,12 +4169,13 @@ void P_PlayerThink(player_t *player) { player->stairjank--; } + // Random skin / "ironman" - if ((!P_MobjWasRemoved(player->mo)) & (skins[player->skin].flags & SF_IRONMAN)) // we are Heavy Magician with a mobj + 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 { - if (leveltime >= introtime && !player->exiting) + if (leveltime >= starttime && !player->exiting) { if (player->fakeskin != MAXSKINS) { From 0a8e3046e609754bd5a6f040d431ac6dd7060748 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 14:58:45 -0700 Subject: [PATCH 20/31] Ironman: appease pedantry --- src/m_random.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/m_random.h b/src/m_random.h index dc6f1884b..34e4c34c8 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 @@ -62,8 +63,6 @@ typedef enum PR_MOVINGTARGET, // Randomised moving targets - PR_RANDOMSKIN, // Random skin select from Heavy Magician(?) - PRNUMCLASS } pr_class_t; From 7c839e841aac87f04591bcd7965744d2ae263b1e Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 15:05:43 -0700 Subject: [PATCH 21/31] Ironman: use P_SetTarget helper where appropriate --- src/p_mobj.c | 2 +- src/r_skins.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 5fb2b7114..31ad11939 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7630,7 +7630,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } else if (mobj->extravalue2 < TICRATE/3) { - mobj->target = NULL; + P_SetTarget(&mobj->target, NULL); if (mobj->extravalue2 & 1) mobj->renderflags |= RF_DONTDRAW; else diff --git a/src/r_skins.c b/src/r_skins.c index 180ee2678..eb92bc806 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -369,7 +369,7 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) for (j = 0; j < 6; j++) // 0-3 = sides, 4 = top, 5 = bottom { mobj_t *box = P_SpawnMobjFromMobj(parent, 0, 0, 0, MT_MAGICIANBOX); - box->target = parent; + P_SetTarget(&box->target, parent); box->angle = FixedAngle((baseangle + j*90) * FRACUNIT); box->flags2 |= MF2_AMBUSH; if (fast) From 1410b6c0e0e6796829a6d6a017d8884a3a6d9c76 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 15:44:49 -0700 Subject: [PATCH 22/31] Ironman: visibility/hyudoro behavior cleanup --- src/p_mobj.c | 27 +++++++++++++++++++-------- src/r_skins.c | 9 +++++++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/p_mobj.c b/src/p_mobj.c index 31ad11939..05fd8669d 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -7636,11 +7636,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) else mobj->renderflags &= ~RF_DONTDRAW; } - else if (mobj->extravalue2 == TICRATE/3 && mobj->target) + else if (mobj->extravalue2 == TICRATE/3 && !P_MobjWasRemoved(mobj->target)) { - - mobj->target->renderflags &= ~RF_DONTDRAW; - mobj->momx = mobj->target->momx; mobj->momy = mobj->target->momy; mobj->momz = mobj->target->momz; @@ -7659,15 +7656,29 @@ static boolean P_MobjRegularThink(mobj_t *mobj) S_StartSound(mobj, sfx_kc2e); S_StartSound(mobj, sfx_s3k9f); - K_SpawnMagicianParticles(mobj, 5); + if (mobj->target->player->hyudorotimer) + { + P_RemoveMobj(mobj); + break; + } + else + { + K_SpawnMagicianParticles(mobj, 5); + } return true; } - else + else if (mobj->target && !P_MobjWasRemoved(mobj->target)) { - mobj->target->renderflags |= RF_DONTDRAW; + 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 (!mobj->target || !mobj->target->health || !mobj->target->player) { + if (P_MobjWasRemoved(mobj->target) || !mobj->target->health || !mobj->target->player) { mobj->extravalue2 = min(mobj->extravalue2, TICRATE/3); return true; } diff --git a/src/r_skins.c b/src/r_skins.c index eb92bc806..ec1eaf1cb 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -382,8 +382,13 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) box->extravalue1 = 1; box->extravalue2 = 3*TICRATE/2; } - if (j == 0) - box->cusval = 1; // Should play sounds when disappearing + + // 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; From 0353512b053c45dbbcfab81fb378bf82b90cd2e2 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 16:01:25 -0700 Subject: [PATCH 23/31] Ironman: Keep player_t charflags up to date in demos --- src/g_demo.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/g_demo.c b/src/g_demo.c index bacc64063..d5d01cc43 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -266,6 +266,7 @@ void G_ReadDemoExtraData(void) if (extradata & DXD_SKIN) { UINT8 kartspeed, kartweight; + UINT32 charflags; // Skin M_Memcpy(name, demo_p, 16); @@ -274,12 +275,14 @@ void G_ReadDemoExtraData(void) kartspeed = READUINT8(demo_p); kartweight = READUINT8(demo_p); + charflags = READUINT32(demo_p); if (stricmp(skins[players[p].skin].name, name) != 0) FindClosestSkinForStats(p, kartspeed, kartweight); players[p].kartspeed = kartspeed; players[p].kartweight = kartweight; + players[p].charflags = charflags; } if (extradata & DXD_COLOR) { @@ -428,7 +431,7 @@ void G_WriteDemoExtraData(void) WRITEUINT8(demo_p, skins[players[i].skin].kartspeed); WRITEUINT8(demo_p, skins[players[i].skin].kartweight); - + WRITEUINT32(demo_p, skins[players[i].skin].flags); } if (demo_extradata[i] & DXD_COLOR) { @@ -2111,6 +2114,7 @@ void G_BeginRecording(void) WRITEUINT8(demo_p, skins[player->skin].kartspeed); WRITEUINT8(demo_p, skins[player->skin].kartweight); WRITEUINT8(demo_p, player->lastfakeskin); + WRITEUINT32(demo_p, player->charflags); // And mobjtype_t is best with UINT32 too... WRITEUINT32(demo_p, player->followitem); @@ -2711,6 +2715,7 @@ void G_DoPlayDemo(char *defdemoname) boolean spectator; UINT8 slots[MAXPLAYERS], kartspeed[MAXPLAYERS], kartweight[MAXPLAYERS], lastfakeskin[MAXPLAYERS], numslots = 0; + UINT32 charflags[MAXPLAYERS]; #if defined(SKIPERRORS) && !defined(DEVELOP) boolean skiperrors = false; @@ -3088,6 +3093,7 @@ void G_DoPlayDemo(char *defdemoname) kartspeed[p] = READUINT8(demo_p); kartweight[p] = READUINT8(demo_p); lastfakeskin[p] = READUINT8(demo_p); + charflags[p] = READUINT32(demo_p); if (stricmp(skins[players[p].skin].name, skin) != 0) FindClosestSkinForStats(p, kartspeed[p], kartweight[p]); @@ -3146,6 +3152,7 @@ void G_DoPlayDemo(char *defdemoname) players[i].kartspeed = kartspeed[i]; players[i].kartweight = kartweight[i]; players[i].lastfakeskin = lastfakeskin[i]; + players[i].charflags = charflags[i]; } demo.deferstart = true; @@ -3338,6 +3345,7 @@ void G_AddGhost(char *defdemoname) kartspeed = READUINT8(p); kartweight = READUINT8(p); p += 1; // lastfakeskin + p += 4; // charflags p += 4; // followitem (maybe change later) From c4f0390ad65b24d5488d62ddcd08c6186f46acba Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 16:04:23 -0700 Subject: [PATCH 24/31] Fix reading follower color from demos --- src/g_demo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_demo.c b/src/g_demo.c index d5d01cc43..a4400d4ff 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -318,7 +318,7 @@ void G_ReadDemoExtraData(void) { if (!stricmp(Followercolor_cons_t[i].strvalue, name)) { - players[p].followercolor = i; + players[p].followercolor = Followercolor_cons_t[i].value; break; } } From 80252717c93453c69fe15003eecb9f6fb4abd94c Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 16:19:17 -0700 Subject: [PATCH 25/31] Kill SF_HIRES, use skin_t highresscale instead --- src/d_player.h | 5 ++--- src/deh_tables.c | 1 - src/hardware/hw_main.c | 2 +- src/k_menudraw.c | 2 +- src/p_user.c | 2 +- src/r_skins.c | 1 - src/r_splats.c | 2 +- src/r_things.c | 6 +++--- 8 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 10a9cf3a9..c5dcb495a 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -35,9 +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_IRONMAN = 1<<2, // Pick a new skin during POSITION. I main Random! + 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; diff --git a/src/deh_tables.c b/src/deh_tables.c index 63b076d30..4f0829415 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -6321,7 +6321,6 @@ 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}, 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/k_menudraw.c b/src/k_menudraw.c index 92e47cb2f..5e20c0790 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1178,7 +1178,7 @@ static boolean M_DrawCharacterSprite(INT16 x, INT16 y, INT16 skin, INT32 addflag if (sprframe->flip & 1) // Only for first sprite flags |= V_FLIP; // This sprite is left/right flipped! - if (skins[skin].flags & SF_HIRES) + if (skins[skin].highresscale != FRACUNIT) { V_DrawFixedPatch(x<stairjank--; } - + // Random skin / "ironman" if ((!P_MobjWasRemoved(player->mo)) && (skins[player->skin].flags & SF_IRONMAN)) // we are Heavy Magician with a mobj { diff --git a/src/r_skins.c b/src/r_skins.c index ec1eaf1cb..fe9007652 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -563,7 +563,6 @@ 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 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 41dfc8585..d1398e9ea 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; From d95244a693afe6bd589e81528f0a48c0dec8a7e1 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 22:15:40 -0700 Subject: [PATCH 26/31] Ironman: Don't carry modified stats into the next race --- src/g_game.c | 9 ++++++++- src/r_skins.c | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/g_game.c b/src/g_game.c index acf3b9c6d..8b27e6d46 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2441,10 +2441,17 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->botvars.rival = botrival; p->xtralife = xtralife; - if (betweenmaps) + if (betweenmaps) + { p->fakeskin = MAXSKINS; + p->kartspeed = skins[p->skin].kartspeed; + p->kartweight = skins[p->skin].kartweight; + } else + { p->fakeskin = fakeskin; + } + p->lastfakeskin = lastfakeskin; // SRB2kart diff --git a/src/r_skins.c b/src/r_skins.c index fe9007652..3bf406fe3 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -410,6 +410,10 @@ void ClearFakePlayerSkin(player_t* player) if (!P_MobjWasRemoved(player->mo)) { 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; S_StartSound(player->mo, sfx_s3k9f); K_SpawnMagicianParticles(player->mo, 5); } From 01c35b7957416d249051f9b298e341cc57e7a3b8 Mon Sep 17 00:00:00 2001 From: AJ Martinez Date: Thu, 3 Nov 2022 22:22:47 -0700 Subject: [PATCH 27/31] Ironman: Don't do detransform unless your real skin is actually SF_IRONMAN --- src/r_skins.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r_skins.c b/src/r_skins.c index 3bf406fe3..9068a1021 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -407,7 +407,7 @@ void SetRandomFakePlayerSkin(player_t* player, boolean fast) // Return to base skin from an SF_IRONMAN randomization void ClearFakePlayerSkin(player_t* player) { - if (!P_MobjWasRemoved(player->mo)) + if ((skins[player->skin].flags & SF_IRONMAN) && !P_MobjWasRemoved(player->mo)) { player->mo->skin = &skins[player->skin]; player->fakeskin = MAXSKINS; From b8f59fd2279ec0b9b673068b4096af1291d43444 Mon Sep 17 00:00:00 2001 From: toaster Date: Tue, 22 Nov 2022 14:28:48 +0000 Subject: [PATCH 28/31] 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) --- src/d_clisrv.c | 2 + src/d_player.h | 2 + src/g_demo.c | 1111 ++++++++++++++++++++++++++++-------------------- src/g_demo.h | 28 +- src/k_hud.c | 10 +- src/p_spec.c | 11 +- src/p_user.c | 32 +- src/r_skins.c | 74 +++- 8 files changed, 771 insertions(+), 499 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index b71ab15a5..3b59d5225 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -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 } diff --git a/src/d_player.h b/src/d_player.h index 23834f7e9..50076e3aa 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -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 diff --git a/src/g_demo.c b/src/g_demo.c index 482c05aaa..4f56dfd36 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -97,6 +97,9 @@ static struct { // EZT_KART INT32 kartitem, kartamount, kartbumpers; + // EZT_IRONMAN + UINT8 skinid; + UINT8 desyncframes; // Don't try to resync unless we've been off for two frames, to monkeypatch a few trouble spots // EZT_HIT @@ -133,6 +136,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 @@ -173,12 +177,13 @@ static ticcmd_t oldcmd[MAXPLAYERS]; #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 +#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 // Changed current held item/quantity and bumpers for battle +#define EZT_IRONMAN 0x040 // Changed object skin // GZT_FOLLOW flags #define FZT_SPAWNED 0x01 // just been spawned @@ -206,14 +211,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 +235,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 +274,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,24 +342,19 @@ void G_ReadDemoExtraData(void) } if (extradata & DXD_SKIN) { - UINT8 kartspeed, kartweight; - UINT32 charflags; + UINT8 skinid; // Skin - M_Memcpy(name, demo_p, 16); - demo_p += 16; - SetPlayerSkin(p, name); - kartspeed = READUINT8(demo_p); - kartweight = READUINT8(demo_p); - charflags = READUINT32(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].charflags = charflags; + players[p].kartspeed = demo.skinlist[skinid].kartspeed; + players[p].kartweight = demo.skinlist[skinid].kartweight; + players[p].charflags = demo.skinlist[skinid].flags; } if (extradata & DXD_COLOR) { @@ -323,48 +395,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); @@ -378,6 +408,7 @@ void G_ReadDemoExtraData(void) while (p != DW_END) { UINT32 rng; + boolean storesynced = demosynced; switch (p) { @@ -391,10 +422,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); @@ -420,18 +452,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); - WRITEUINT32(demo_p, skins[players[i].skin].flags); + WRITEUINT8(demo_p, players[i].skin); } if (demo_extradata[i] & DXD_COLOR) { @@ -472,19 +529,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); @@ -802,6 +846,14 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) ghostext[playernum].kartbumpers = ghost->player->bumpers; } + if (ghost->player && ( + ghostext[playernum].skinid != (UINT8)(((skin_t *)ghost->skin)-skins) + )) + { + ghostext[playernum].flags |= EZT_IRONMAN; + ghostext[playernum].skinid = (UINT8)(((skin_t *)ghost->skin)-skins); + } + if (ghostext[playernum].flags) { ziptic |= GZT_EXTRA; @@ -849,6 +901,8 @@ void G_WriteGhostTic(mobj_t *ghost, INT32 playernum) WRITEINT32(demo_p, ghostext[playernum].kartamount); WRITEINT32(demo_p, ghostext[playernum].kartbumpers); } + if (ghostext[playernum].flags & EZT_IRONMAN) + WRITEUINT8(demo_p,ghostext[playernum].skinid); ghostext[playernum].flags = 0; } @@ -1025,6 +1079,12 @@ void G_ConsGhostTic(INT32 playernum) ghostext[playernum].kartamount = READINT32(demo_p); ghostext[playernum].kartbumpers = READINT32(demo_p); } + if (xziptic & EZT_IRONMAN) + { + ghostext[playernum].skinid = READUINT8(demo_p); + if (ghostext[playernum].skinid >= demo.numskins) + ghostext[playernum].skinid = 0; + } } if (ziptic & GZT_FOLLOW) @@ -1094,6 +1154,18 @@ void G_ConsGhostTic(INT32 playernum) players[playernum].itemamount = ghostext[playernum].kartamount; players[playernum].bumpers = ghostext[playernum].kartbumpers; } + + if (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)!\n")); + demosynced = false; + + testmo->skin = &skins[demo.skinlist[ghostext[playernum].skinid].mapping]; + players[playernum].kartspeed = demo.skinlist[ghostext[playernum].skinid].kartspeed; + players[playernum].kartweight = demo.skinlist[ghostext[playernum].skinid].kartweight; + players[playernum].charflags = demo.skinlist[ghostext[playernum].skinid].flags; + } } if (*demo_p == DEMOMARKER) @@ -1118,16 +1190,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 } @@ -1204,13 +1276,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! @@ -1221,9 +1286,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; @@ -1231,9 +1293,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) @@ -1275,6 +1334,22 @@ void G_GhostTicker(void) g->mo->sprite = READUINT16(g->p); if (xziptic & EZT_KART) g->p += 12; // kartitem, kartamount, kartbumpers + if (xziptic & EZT_IRONMAN) + { + UINT8 skinid = READUINT8(g->p); + if (skinid >= g->numskins) + skinid = 0; + g->mo->skin = &skins[g->skinlist[skinid].mapping]; + } + } + + // 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 @@ -1384,6 +1459,7 @@ skippedghosttic: p->next = g->next; else ghosts = g->next; + Z_Free(g->skinlist); Z_Free(g); continue; } @@ -1957,293 +2033,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); - WRITEUINT8(demo_p, player->lastfakeskin); - WRITEUINT32(demo_p, player->charflags); - - // 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) @@ -2408,6 +2215,364 @@ 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; + + 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 @@ -2450,6 +2615,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); @@ -2514,6 +2681,8 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) return UINT8_MAX; } + G_SkipDemoSkins(&p); + oldtime = READUINT32(p); if (uselaps) oldlap = READUINT32(p); @@ -2545,7 +2714,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; @@ -2616,6 +2786,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 @@ -2662,15 +2839,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); @@ -2692,12 +2864,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); } @@ -2719,16 +2893,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], lastfakeskin[MAXPLAYERS], numslots = 0; - UINT32 charflags[MAXPLAYERS]; + boolean spectator, bot; + UINT8 slots[MAXPLAYERS], lastfakeskin[MAXPLAYERS]; #if defined(SKIPERRORS) && !defined(DEVELOP) boolean skiperrors = false; @@ -2736,7 +2909,6 @@ void G_DoPlayDemo(char *defdemoname) G_InitDemoRewind(); - skin[16] = '\0'; follower[16] = '\0'; color[MAXCOLORNAME] = '\0'; @@ -2908,6 +3080,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)); @@ -2946,6 +3132,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; @@ -2962,6 +3150,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; @@ -3006,14 +3196,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; @@ -3030,6 +3223,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; @@ -3053,21 +3248,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); @@ -3102,15 +3304,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); - lastfakeskin[p] = READUINT8(demo_p); - charflags[p] = READUINT32(demo_p); - - if (stricmp(skins[players[p].skin].name, skin) != 0) - FindClosestSkinForStats(p, kartspeed[p], kartweight[p]); - // Followitem players[p].followitem = READUINT32(demo_p); @@ -3149,23 +3342,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[i].lastfakeskin = lastfakeskin[i]; - players[i].charflags = charflags[i]; + players[p].kartspeed = demo.skinlist[demo.currentskinid[p]].kartspeed; + players[p].kartweight = demo.skinlist[demo.currentskinid[p]].kartweight; + players[p].charflags = demo.skinlist[demo.currentskinid[p]].flags; + players[p].lastfakeskin = lastfakeskin[p]; } demo.deferstart = true; @@ -3175,17 +3369,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); @@ -3288,6 +3482,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 @@ -3321,6 +3524,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; @@ -3329,9 +3533,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; @@ -3342,8 +3547,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); @@ -3355,36 +3562,17 @@ void G_AddGhost(char *defdemoname) p += 4; // score p += 2; // powerlevel - kartspeed = READUINT8(p); - kartweight = READUINT8(p); - p += 1; // lastfakeskin - p += 4; // charflags - 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; @@ -3392,6 +3580,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; @@ -3454,6 +3645,7 @@ void G_FreeGhosts(void) while (ghosts) { demoghost *next = ghosts->next; + Z_Free(ghosts->skinlist); Z_Free(ghosts); ghosts = next; } @@ -3513,6 +3705,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 @@ -3780,6 +3974,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/k_hud.c b/src/k_hud.c index 29525a1c4..d9908c4c6 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -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; diff --git a/src/p_spec.c b/src/p_spec.c index 7a1e4072a..07ec1f102 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -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); + } } diff --git a/src/p_user.c b/src/p_user.c index 0a95a143c..1f58384df 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -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); + } } } diff --git a/src/r_skins.c b/src/r_skins.c index 9068a1021..db1e81345 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -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; } // From 3ee8bbbfc487c6184a41522cd70fb285a596f87f Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Nov 2022 18:01:23 +0000 Subject: [PATCH 29/31] Adjust various EZT_s. - EZT_KART is now EZT_ITEMDATA. - Uses the new one-byte widths to reduce spurious filesize use for item type, item amount, and bumpers. - EZT_IRONMAN is now EZT_STATDATA. - Now includes kartspeed, kartweight, and charflags to be tolerant to restat. --- src/g_demo.c | 126 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 75 insertions(+), 51 deletions(-) diff --git a/src/g_demo.c b/src/g_demo.c index 4f56dfd36..dc816856f 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -94,11 +94,13 @@ static struct { // EZT_SCALE fixed_t scale, lastscale; - // EZT_KART - INT32 kartitem, kartamount, kartbumpers; + // EZT_ITEMDATA + SINT8 itemtype; + UINT8 itemamount, bumpers; - // EZT_IRONMAN - UINT8 skinid; + // 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 @@ -176,14 +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 // Changed current held item/quantity and bumpers for battle -#define EZT_IRONMAN 0x040 // Changed object skin +// 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 @@ -352,9 +354,9 @@ void G_ReadDemoExtraData(void) SetPlayerSkinByNum(p, demo.skinlist[skinid].mapping); demo.currentskinid[p] = skinid; - players[p].kartspeed = demo.skinlist[skinid].kartspeed; - players[p].kartweight = demo.skinlist[skinid].kartweight; - players[p].charflags = demo.skinlist[skinid].flags; + 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) { @@ -835,23 +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].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_IRONMAN; + 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) @@ -895,14 +903,19 @@ 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_IRONMAN) + 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; } @@ -1073,17 +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_IRONMAN) + 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); } } @@ -1142,29 +1158,33 @@ 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 (demo.skinlist[ghostext[playernum].skinid].mapping != (UINT8)(((skin_t *)testmo->skin)-skins)) + 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)!\n")); + 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 = demo.skinlist[ghostext[playernum].skinid].kartspeed; - players[playernum].kartweight = demo.skinlist[ghostext[playernum].skinid].kartweight; - players[playernum].charflags = demo.skinlist[ghostext[playernum].skinid].flags; + players[playernum].kartspeed = ghostext[playernum].kartspeed; + players[playernum].kartweight = ghostext[playernum].kartweight; + players[playernum].charflags = ghostext[playernum].charflags; + } } @@ -1332,14 +1352,15 @@ 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_IRONMAN) + 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 } } @@ -2475,6 +2496,9 @@ void G_BeginRecording(void) 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) { @@ -3356,9 +3380,9 @@ void G_DoPlayDemo(char *defdemoname) // 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[p].kartspeed = demo.skinlist[demo.currentskinid[p]].kartspeed; - players[p].kartweight = demo.skinlist[demo.currentskinid[p]].kartweight; - players[p].charflags = demo.skinlist[demo.currentskinid[p]].flags; + 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]; } From c2183a626c63d12eb578d67ce4be649813d35bec Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Nov 2022 18:03:27 +0000 Subject: [PATCH 30/31] Actually track lastfakeskin! Tremendous whoopsie: turns out there's no code that actually saves it outside of netsync. This is now fixed. Also now allows you to roll Eggman as your first skin, and doesn't cause immediate characterswaps for midgame joiners. --- src/d_clisrv.c | 1 + src/g_demo.c | 8 ++++++++ src/r_skins.c | 7 ++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index f826519a2..6ce85db27 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2551,6 +2551,7 @@ 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/g_demo.c b/src/g_demo.c index dc816856f..8566a4c22 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -1185,6 +1185,14 @@ void G_ConsGhostTic(INT32 playernum) 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; + } } } diff --git a/src/r_skins.c b/src/r_skins.c index db1e81345..9779fdcfc 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -339,7 +339,12 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) // Set mo skin but not player_t skin, for ironman void SetFakePlayerSkin(player_t* player, INT32 skinid) { - player->fakeskin = skinid; + if (player->fakeskin != skinid) + { + if (player->fakeskin != MAXSKINS) + player->lastfakeskin = player->fakeskin; + player->fakeskin = skinid; + } if (demo.playback) { From 86813b96a3cceea936cd9a5f0016fa7ba1719de8 Mon Sep 17 00:00:00 2001 From: toaster Date: Wed, 23 Nov 2022 18:11:50 +0000 Subject: [PATCH 31/31] Adjust how properties are handled in G_PlayerReborn a little. For consistency, all conditionals are done when data is being preserved in local variables, not on write back to player struct. --- src/g_game.c | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/g_game.c b/src/g_game.c index 5e722fe4f..7dd33fec3 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2286,12 +2286,22 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) skincolor = players[player].skincolor; skin = players[player].skin; - fakeskin = players[player].fakeskin; - lastfakeskin = players[player].lastfakeskin; - // 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; @@ -2299,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; @@ -2417,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; @@ -2441,19 +2452,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->botvars.rival = botrival; p->xtralife = xtralife; - if (betweenmaps) - { - p->fakeskin = MAXSKINS; - p->kartspeed = skins[p->skin].kartspeed; - p->kartweight = skins[p->skin].kartweight; - } - else - { - p->fakeskin = fakeskin; - } - - p->lastfakeskin = lastfakeskin; - // SRB2kart p->itemroulette = itemroulette; p->roulettetype = roulettetype;