diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0478bcb71..d865141e3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -124,6 +124,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 lua_vectorlib.c lua_matrixlib.c lua_quaternionlib.c + lua_voicelib.c k_kart.c k_collide.c k_color.c @@ -507,6 +508,7 @@ target_compile_options(SRB2SDL2 PRIVATE -Wno-error=constant-conversion -Wno-unused-but-set-variable -Wno-error=unused-but-set-variable + -Wshadow > # C++, GNU, Clang and Apple Clang diff --git a/src/d_netcmd.c b/src/d_netcmd.c index ab38a093e..a7216fc30 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -142,6 +142,11 @@ static void Followercolor2_OnChange(void); static void Followercolor3_OnChange(void); static void Followercolor4_OnChange(void); +static void Voice_OnChange(void); +static void Voice2_OnChange(void); +static void Voice3_OnChange(void); +static void Voice4_OnChange(void); + static void Color_OnChange(void); static void Color2_OnChange(void); static void Color3_OnChange(void); @@ -347,6 +352,15 @@ consvar_t cv_followercolor[MAXSPLITSCREENPLAYERS] = { CVAR_INIT ("followercolor4", "Default", CV_SAVE|CV_CALL|CV_NOINIT, Followercolor_cons_t, Followercolor4_OnChange) }; +// player's voices...also, uh, saved. +consvar_t cv_voice[MAXSPLITSCREENPLAYERS] = { + CVAR_INIT ("voice", "None", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Voice_OnChange), + CVAR_INIT ("voice2", "None", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Voice2_OnChange), + CVAR_INIT ("voice3", "None", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Voice3_OnChange), + CVAR_INIT ("voice4", "None", CV_SAVE|CV_CALL|CV_NOINIT, NULL, Voice4_OnChange) +}; + + consvar_t cv_skipmapcheck = CVAR_INIT ("skipmapcheck", "Off", CV_SAVE, CV_OnOff, NULL); consvar_t cv_usemouse = CVAR_INIT ("use_mouse", "Off", CV_SAVE|CV_CALL,usemouse_cons_t, I_StartupMouse); @@ -1196,6 +1210,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_follower[i]); CV_RegisterVar(&cv_followercolor[i]); CV_RegisterVar(&cv_jitterlegacy[i]); + CV_RegisterVar(&cv_voice[i]); } // preferred number of players @@ -1753,12 +1768,15 @@ VaguePartyDescription (int playernum, int size, int default_color) static INT32 snacpending[MAXSPLITSCREENPLAYERS] = {0,0,0,0}; static INT32 chmappending = 0; -// name, color, or skin has changed +// name, color, skin, or voice has changed // static void SendNameAndColor(UINT8 n) { const INT32 playernum = g_localplayers[n]; player_t *player = &players[playernum]; + kartvoice_t *voice; + kartvoice_t *valuevoice; + INT32 prevskin; char buf[MAXPLAYERNAME+12]; char *p; @@ -1795,11 +1813,21 @@ static void SendNameAndColor(UINT8 n) if (cv_follower[n].value >= numfollowers || cv_follower[n].value < -1) CV_StealthSet(&cv_follower[n], "-1"); + // Check the player's acoustics. + voice = P_GetMobjVoice(player->mo); + + if (!voice) + { + // ...how?! + voice = &skins[player->skin].voices[0]; + } + if (!strcmp(cv_playername[n].string, player_names[playernum]) && cv_playercolor[n].value == player->skincolor && !strcmp(cv_skin[n].string, skins[player->skin].name) && cv_follower[n].value == player->followerskin - && cv_followercolor[n].value == player->followercolor) + && cv_followercolor[n].value == player->followercolor + && !stricmp(cv_voice[n].string, voice->name)) return; player->availabilities = R_GetSkinAvailabilities(); @@ -1811,7 +1839,7 @@ static void SendNameAndColor(UINT8 n) // If you're not in a netgame, merely update the skin, color, and name. if (!netgame) { - INT32 foundskin; + INT32 foundskin, voxid; CleanupPlayerName(playernum, cv_playername[n].zstring); strcpy(player_names[playernum], cv_playername[n].zstring); @@ -1833,16 +1861,105 @@ static void SendNameAndColor(UINT8 n) } else if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin)) { + prevskin = cv_skin[n].value; + cv_skin[n].value = foundskin; SetPlayerSkin(playernum, cv_skin[n].string); CV_StealthSet(&cv_skin[n], skins[cv_skin[n].value].name); + + // Reset the voice. + if (prevskin != player->skin) + { + if (cv_voice[n].string) + { + voxid = R_FindIDForVoice(&skins[player->skin], cv_voice[n].string); + + if (voxid == MAXSKINVOICES) + { + // No dice; use the default voice. + valuevoice = &skins[player->skin].voices[0]; + } + else + { + + valuevoice = &skins[player->skin].voices[voxid]; + } + } + else + { + // NULL string; default to the skin's default. + valuevoice = &skins[player->skin].voices[0]; + } + + CV_StealthSet(&cv_voice[n], valuevoice->name); + } } else { + prevskin = player->skin; + cv_skin[n].value = players[playernum].skin; CV_StealthSet(&cv_skin[n], skins[player->skin].name); // will always be same as current SetPlayerSkin(playernum, cv_skin[n].string); + + // Reset the voice. + if (prevskin != player->skin) + { + if (cv_voice[n].string) + { + voxid = R_FindIDForVoice(&skins[player->skin], cv_voice[n].string); + + if (voxid == MAXSKINVOICES) + { + // No dice; use the default voice. + valuevoice = &skins[player->skin].voices[0]; + } + else + { + + valuevoice = &skins[player->skin].voices[voxid]; + } + } + else + { + // NULL string; default to the skin's default. + valuevoice = &skins[player->skin].voices[0]; + } + + CV_StealthSet(&cv_voice[n], valuevoice->name); + } + } + + // Need to update voices after the fact. + if (!cv_voice[n].string) + { + CV_StealthSet(&cv_voice[n], skins[player->skin].voices[0].name); + } + + SetPlayerVoice(playernum, cv_voice[n].string); + + valuevoice = P_GetMobjVoice(player->mo); + + if (valuevoice) + { + CV_StealthSet(&cv_voice[n], valuevoice->name); + } + else + { + // Get the voice from our ID. + // No need to compare parents, + valuevoice = &skins[player->skin].voices[player->voice_id]; + + if (valuevoice) + { + CV_StealthSet(&cv_voice[n], valuevoice->name); + } + else + { + // ...still nothing? + CV_StealthSet(&cv_voice[n], skins[player->skin].voices[0].name); + } } return; @@ -1863,8 +1980,16 @@ static void SendNameAndColor(UINT8 n) // Don't change skin if the server doesn't want you to. if (!CanChangeSkin(playernum)) + { CV_StealthSet(&cv_skin[n], skins[player->skin].name); + if (R_FindIDForVoice(&skins[player->skin], cv_voice[n].string) == MAXSKINVOICES) + { + // Our voice is no longer valid, set it to that of our skin's. + CV_StealthSet(&cv_voice[n], skins[player->skin].voices[0].name); + } + } + // check if player has the skin loaded (cv_skin may have // the name of a skin that was available in the previous game) cv_skin[n].value = R_SkinAvailable(cv_skin[n].string); @@ -1872,6 +1997,56 @@ static void SendNameAndColor(UINT8 n) { CV_StealthSet(&cv_skin[n], DEFAULTSKIN); cv_skin[n].value = 0; + + CV_StealthSet(&cv_voice[n], "sonic_voice"); + cv_voice[n].value = 0; + } + + // Need to update voices after the fact. + if (!cv_voice[n].string) + { + CV_StealthSet(&cv_voice[n], skins[cv_skin[n].value].voices[0].name); + } + + if (R_FindIDForVoice(&skins[cv_skin[n].value], cv_voice[n].string) == MAXSKINVOICES) + { + // In the chance our current voice isn't valid, + // rescan our current voice and send that over the network. + valuevoice = P_GetMobjVoice(player->mo); + + if (valuevoice) + { + CV_StealthSet(&cv_voice[n], valuevoice->name); + } + else + { + // Get the voice from our ID. + // No need to compare parents. + valuevoice = &skins[cv_skin[n].value].voices[player->voice_id]; + + if (valuevoice) + { + CV_StealthSet(&cv_voice[n], valuevoice->name); + } + else + { + // ...still nothing? + CV_StealthSet(&cv_voice[n], skins[cv_skin[n].value].voices[0].name); + } + } + } + + // After EVERYTHING, do one last check on our voice so we can set the value. + INT32 cvar_voxid = R_FindIDForVoice(&skins[cv_skin[n].value], cv_voice[n].string); + + if (cvar_voxid == MAXSKINVOICES) + { + // ...huh?! Reset to the default. + cv_voice[n].value = 0; + } + else + { + cv_voice[n].value = cvar_voxid; } // Finally write out the complete packet and send it off. @@ -1881,6 +2056,7 @@ static void SendNameAndColor(UINT8 n) WRITEUINT16(p, (UINT16)cv_skin[n].value); WRITEINT32(p, (INT32)cv_follower[n].value); WRITEUINT16(p, (UINT16)cv_followercolor[n].value); + WRITEUINT16(p, (UINT16)cv_voice[n].value); SendNetXCmdForPlayer(n, XD_NAMEANDCOLOR, buf, p - buf); } @@ -1890,7 +2066,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) player_t *p = &players[playernum]; char name[MAXPLAYERNAME+1]; UINT16 color, followercolor; - UINT16 skin; + UINT16 skin, voice; INT32 follower; SINT8 localplayer = -1; UINT16 i; @@ -1922,6 +2098,7 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) skin = READUINT16(*cp); follower = READINT32(*cp); followercolor = READUINT16(*cp); + voice = READUINT16(*cp); // set name if (player_name_changes[playernum] < MAXNAMECHANGES) @@ -1996,11 +2173,27 @@ static void Got_NameAndColor(UINT8 **cp, INT32 playernum) SetPlayerSkinByNum(playernum, forcedskin); if (localplayer != -1) + { CV_StealthSet(&cv_skin[localplayer], skins[forcedskin].name); + CV_StealthSet(&cv_voice[localplayer], skins[forcedskin].voices[0].name); + } + + // set voice + SetPlayerVoiceByNum(playernum, skins[forcedskin].voices[0].id); } else + { SetPlayerSkinByNum(playernum, skin); + // set voice + SetPlayerVoiceByNum(playernum, voice); + + if (localplayer != -1) + { + CV_StealthSet(&cv_voice[localplayer], skins[p->skin].voices[p->voice_id].name); + } + } + // set follower colour: // Don't bother doing garbage and kicking if we receive None, // this is both silly and a waste of time, @@ -7273,6 +7466,74 @@ static void Color4_OnChange(void) lastgoodcolor[3] = cv_playercolor[3].value; } +static void __voice_cvar_func(INT32 pid, UINT8 pnum) +{ + if (!numskins) + { + // There aren't even any skins yet! + return; + } + + kartvoice_t *myvoice = P_GetMobjVoice(players[pid].mo); + + if (!myvoice) + { + // ...how?! + myvoice = &skins[players[pid].skin].voices[0]; + } + + if (R_FindIDForVoice(&skins[players[pid].skin], cv_voice[pnum].string) == MAXSKINVOICES) + { + CONS_Alert(CONS_NOTICE, M_GetText("Voice \"%s\" does not exist for this skin.\n"), cv_voice[pnum].string); + CV_StealthSet(&cv_voice[pnum], myvoice->name); + return; + } + + if (!Playing()) + return; // do whatever you want + + if (pnum > 0 && !splitscreen) + return; // do whatever you want + + if (pnum < 1) + { + if (!(cht_debug || devparm) && !(multiplayer || netgame) // In single player. + && (gamestate != GS_WAITINGPLAYERS)) // allows command line -warp x +skin y + { + CV_StealthSet(&cv_voice[pnum], myvoice->name); + return; + } + } + + if (!P_PlayerMoving(pid)) + SendNameAndColor(pnum); + else + { + CONS_Alert(CONS_NOTICE, M_GetText("You can't change your voice at the moment.\n")); + CV_StealthSet(&cv_voice[pnum], myvoice->name); + } +} + +static void Voice_OnChange(void) +{ + __voice_cvar_func(consoleplayer, 0); +} + +static void Voice2_OnChange(void) +{ + __voice_cvar_func(g_localplayers[1], 1); +} + +static void Voice3_OnChange(void) +{ + __voice_cvar_func(g_localplayers[2], 2); +} + +static void Voice4_OnChange(void) +{ + __voice_cvar_func(g_localplayers[3], 3); +} + /** Displays the result of the chat being muted or unmuted. * The server or remote admin should already know and be able to talk * regardless, so this is only displayed to clients. diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 5c41a3ce4..3901b8a54 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -29,6 +29,7 @@ extern consvar_t cv_playercolor[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_skin[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_follower[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_followercolor[MAXSPLITSCREENPLAYERS]; +extern consvar_t cv_voice[MAXSPLITSCREENPLAYERS]; // preferred number of players extern consvar_t cv_splitplayers; diff --git a/src/d_player.h b/src/d_player.h index 4e4f3ac16..464846045 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -497,6 +497,7 @@ struct player_t UINT16 skincolor; INT32 skin; + UINT16 voice_id; UINT32 availabilities; UINT8 kartspeed; // Kart speed stat between 1 and 9 diff --git a/src/deh_soc.c b/src/deh_soc.c index c128b1691..bf6eb89a4 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -2544,7 +2544,9 @@ void readsound(MYFILE *f, INT32 num) } else if (fastcmp(word, "SKINSOUND")) { - S_sfx[num].skinsound = get_number(word2); + // ignore this for the sake of not needing an asset update + // this was only exposed in blan anyway + //S_sfx[num].skinsound = get_number(word2); } else if (fastcmp(word, "CAPTION") || fastcmp(word, "DESCRIPTION")) { @@ -4325,7 +4327,7 @@ sfxenum_t get_sfx(const char *word) if (S_sfx[i].name && fasticmp(word, S_sfx[i].name)) return i; deh_warning("Couldn't find sfx named 'SFX_%s'",word); - return -1; + return NUMSFX; } menutype_t get_menutype(const char *word) diff --git a/src/deh_tables.c b/src/deh_tables.c index 657d818e9..74d457e9f 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -41,6 +41,7 @@ strbuf_t *mobjnames; strbuf_t *skincolornames; strbuf_t *menunames; strbuf_t *kartitemnames; + UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway. const char *DEH_MobjtypeName(mobjtype_t i) @@ -1177,17 +1178,17 @@ struct int_const_s const INT_CONST[] = { // Customisable sounds for Skins, from sounds.h // SRB2kart - {"SKSKWIN",SKSKWIN}, // Win quote - {"SKSKLOSE",SKSKLOSE}, // Lose quote - {"SKSKPAN1",SKSKPAN1}, // Pain - {"SKSKPAN2",SKSKPAN2}, - {"SKSKATK1",SKSKATK1}, // Offense item taunt - {"SKSKATK2",SKSKATK2}, - {"SKSKBST1",SKSKBST1}, // Boost item taunt - {"SKSKBST2",SKSKBST2}, - {"SKSKSLOW",SKSKSLOW}, // Overtake taunt - {"SKSKHITM",SKSKHITM}, // Hit confirm taunt - {"SKSKPOWR",SKSKPOWR}, // Power item taunt + {"SKSKWIN",0}, // Win quote + {"SKSKLOSE",1}, // Lose quote + {"SKSKPAN1",2}, // Pain + {"SKSKPAN2",3}, + {"SKSKATK1",4}, // Offense item taunt + {"SKSKATK2",5}, + {"SKSKBST1",6}, // Boost item taunt + {"SKSKBST2",7}, + {"SKSKSLOW",8}, // Overtake taunt + {"SKSKHITM",9}, // Hit confirm taunt + {"SKSKPOWR",10}, // Power item taunt // 3D Floor/Fake Floor/FOF/whatever flags {"FOF_EXISTS",FOF_EXISTS}, ///< Always set, to check for validity. diff --git a/src/deh_tables.h b/src/deh_tables.h index 5aba111c0..3c558cb48 100644 --- a/src/deh_tables.h +++ b/src/deh_tables.h @@ -32,6 +32,7 @@ extern strbuf_t *mobjnames; extern strbuf_t *skincolornames; extern strbuf_t *menunames; extern strbuf_t *kartitemnames; + extern UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway. const char *DEH_MobjtypeName(mobjtype_t i); diff --git a/src/dehacked.c b/src/dehacked.c index dd0fd670e..967c06b5f 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -219,7 +219,7 @@ INT32 DEH_ReadFreeslot(const char *type, const char *word, INT32 *out) if (fastcmp(type, "SFX")) { CONS_Printf("Sound sfx_%s allocated.\n", word); - i = S_AddSoundFx(word, false, 0, false); + i = S_AddSoundFx(word, false); if (i != sfx_None) { *out = i; diff --git a/src/f_finale.c b/src/f_finale.c index d6ce50c6e..0a54a60d4 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -414,7 +414,8 @@ void F_IntroTicker(void) // Need to use M_Random otherwise it always uses the same sound INT32 rskin = M_RandomKey(numskins); UINT8 rtaunt = M_RandomKey(2); - sfxenum_t rsound = skins[rskin].soundsid[SKSKBST1+rtaunt]; + + sfxenum_t rsound = skins[rskin].voices[0].boost[rtaunt]; S_StartSound(NULL, rsound); } } @@ -443,11 +444,13 @@ void F_IntroTicker(void) // Tails: NepDisk // Chao: Alug // Aiai: GHG - // Sakura: Anon + // Sakura: Yama // Doomguy: Minenice char chars[6][10] = {"tails", "chao", "aiai", "sakura", "doom"}; SINT8 random = M_RandomRange(0, 4); - sfxenum_t rsound = skins[R_SkinAvailable(chars[random])].soundsid[SKSKWIN]; + const INT32 rskin = R_SkinAvailable(chars[random]); + + sfxenum_t rsound = skins[rskin].voices[0].win; S_StartSound(NULL, sfx_flgcap); S_StartSound(NULL, rsound); } diff --git a/src/g_demo.c b/src/g_demo.c index ee0e02364..65d347c32 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -54,7 +54,6 @@ #include "k_color.h" #include "k_follower.h" #include "k_grandprix.h" -#include "strbuf.h" static CV_PossibleValue_t recordmultiplayerdemos_cons_t[] = {{0, "Disabled"}, {1, "Manual Save"}, {2, "Auto Save"}, {0, NULL}}; consvar_t cv_recordmultiplayerdemos = CVAR_INIT ("netdemo_record", "Manual Save", CV_SAVE, recordmultiplayerdemos_cons_t, NULL); @@ -111,7 +110,7 @@ demoghost *ghosts = NULL; // DEMO RECORDING // -#define DEMOVERSION 0x000D +#define DEMOVERSION 0x000E #define DEMOHEADER "\xF0" "BlanReplay" "\x0F" #define DF_GHOST 0x01 // This demo contains ghost data too! @@ -222,6 +221,7 @@ typedef struct char color[16+1]; char follower[16+1]; char followercolor[16+1]; + char voice[32+1]; UINT32 score; UINT16 powerlevel; @@ -342,6 +342,7 @@ typedef struct // DXD_SKIN char skinname[17]; + char voicename[33]; UINT8 kartspeed; UINT8 kartweight; @@ -621,9 +622,18 @@ static UINT8 *G_ReadRawExtraData(extradata_t *extra, UINT8 *dp, UINT16 version) { READMEM(dp, extra->skinname, 16); extra->skinname[16] = '\0'; + + extra->voicename[0] = '\0'; } else + { READSTRINGL(dp, extra->skinname, 16+1); + + if (version > 0x000D) + READSTRINGL(dp, extra->voicename, 32+1); + else + extra->voicename[0] = '\0'; + } extra->kartspeed = READUINT8(dp); extra->kartweight = READUINT8(dp); } @@ -682,6 +692,7 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) boolean raflag = false; boolean serverinfo = true; boolean rapreset = true; // + extended serverinfo length + boolean dubs = true; // Multiple voices // these may not be present in old demo formats, so initialize them // also initialize them so the header can be free'd without issues @@ -716,6 +727,9 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) case 0x000C: rapreset = false; raflag = true; + /* FALLTHRU */ + case 0x000D: + dubs = false; break; default: // too old, cannot support. @@ -724,7 +738,7 @@ static headerstatus_e G_ReadDemoHeader(UINT8 *dp, demoheader_t *header) } else if (!memcmp(startdp, "\xF0" "KartReplay" "\x0F", 12)) { - rapreset = raflag = false; + dubs = rapreset = raflag = false; serverinfo = false; switch (header->demoversion) { @@ -946,6 +960,10 @@ skipfiles: READSTRINGL(dp, plr->name, 21+1); READSTRINGL(dp, plr->skin, 16+1); READSTRINGL(dp, plr->color, 16+1); + + if (dubs) + READSTRINGL(dp, plr->voice, 32+1); + READSTRINGL(dp, plr->follower, 16+1); READSTRINGL(dp, plr->followercolor, 16+1); } @@ -1249,6 +1267,26 @@ void G_ReadDemoExtraData(void) if (stricmp(skins[player->skin].name, extra.skinname) != 0) FindClosestSkinForStats(p, extra.kartspeed, extra.kartweight); + // Voice + const INT32 vce = R_FindIDForVoice(&skins[player->skin], extra.voicename); + + if (vce == MAXSKINVOICES) + { + // The above function didn't find a voice, so we'll just fall back + // to the skin's voice. + player->voice_id = 0; + } + else + { + player->voice_id = (UINT16)vce; + } + + if (!P_MobjWasRemoved(player->mo)) + { + // This player's mobj exists. So, attach the voice to the mobj! + player->mo->voice = &skins[player->skin].voices[player->voice_id]; + } + player->kartspeed = extra.kartspeed; player->kartweight = extra.kartweight; } @@ -1352,6 +1390,10 @@ void G_WriteDemoExtraData(void) // Skin WRITESTRINGL(demobuf.p, skins[players[i].skin].name, 16+1); + // Voice + WRITESTRINGL(demobuf.p, skins[players[i].skin].voices[players[i].voice_id].name, 32+1); + + // Stats WRITEUINT8(demobuf.p, skins[players[i].skin].kartspeed); WRITEUINT8(demobuf.p, skins[players[i].skin].kartweight); @@ -2057,7 +2099,7 @@ void G_GhostTicker(void) { default: case GHC_RETURNSKIN: - g->mo->skin = g->oldmo.skin; + R_ApplySkin(g->mo, g->oldmo.skin); /* FALLTHRU */ case GHC_NORMAL: // Go back to skin color g->mo->color = g->oldmo.color; @@ -2118,7 +2160,7 @@ void G_GhostTicker(void) fmo->colorized = true; if (zt.followflags & FZT_SKIN) - fmo->skin = &skins[zt.follow.skin]; + R_ApplySkin(fmo, &skins[zt.follow.skin]); } if (fmo) { @@ -2521,7 +2563,7 @@ void G_ReadMetalTic(mobj_t *metal) follow->colorized = true; if (followtic & FZT_SKIN) - follow->skin = &skins[READUINT8(metal_p)]; + R_ApplySkin(follow, &skins[READUINT8(metal_p)]); } if (follow) { @@ -2935,6 +2977,9 @@ void G_BeginRecording(void) // Color WRITESTRINGL(demobuf.p, skincolors[player->skincolor].name, 16+1); + // Voice + WRITESTRINGL(demobuf.p, skins[player->skin].voices[player->voice_id].name, 32+1); + // 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. @@ -3807,6 +3852,23 @@ void G_DoPlayDemo(char *defdemoname) break; } + // Voice + // We'll only assign IDs, since the actual voice assignment happens during + // player spawn. + + const INT32 vce = R_FindIDForVoice(&skins[player->skin], plr->voice); + + if (vce == MAXSKINVOICES) + { + // The above function didn't find a voice, so we'll just fall back to + // the skin's voice. + player->voice_id = 0; + } + else + { + player->voice_id = (UINT16)vce; + } + // Follower K_SetFollowerByName(p, plr->follower); @@ -4058,7 +4120,8 @@ void G_AddGhost(char *defdemoname) gh->mo->tics = -1; // Set skin - gh->mo->skin = gh->oldmo.skin = ghskin; + R_ApplySkin(&gh->oldmo, ghskin); + R_ApplySkin(gh->mo, gh->oldmo.skin); // Set color gh->mo->color = ((skin_t*)gh->mo->skin)->prefcolor; diff --git a/src/g_game.c b/src/g_game.c index 005bc954e..98feb0e4c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2712,6 +2712,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 latestlap; UINT16 skincolor; INT32 skin; + UINT16 voice; UINT32 availabilities; tic_t jointime; @@ -2770,6 +2771,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) skincolor = players[player].skincolor; skin = players[player].skin; + voice = players[player].voice_id; // SRB2kart kartspeed = players[player].kartspeed; @@ -2960,6 +2962,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) // save player config truth reborn p->skincolor = skincolor; p->skin = skin; + p->voice_id = voice; p->kartspeed = kartspeed; p->kartweight = kartweight; // diff --git a/src/hardware/hw3sound.c b/src/hardware/hw3sound.c index 52f691b9e..ef578a92e 100644 --- a/src/hardware/hw3sound.c +++ b/src/hardware/hw3sound.c @@ -345,6 +345,7 @@ static void make_outphase_sfx(void *dest, void *src, INT32 size) INT32 HW3S_I_StartSound(const void *origin_p, source3D_data_t *source_parm, channel_type_t c_type, sfxenum_t sfx_id, INT32 volume, INT32 pitch, INT32 sep) { sfxinfo_t *sfx; + kartvoice_t *voice; const mobj_t *origin = (const mobj_t *)origin_p; source3D_data_t source3d_data; INT32 s_num = 0; @@ -359,11 +360,17 @@ INT32 HW3S_I_StartSound(const void *origin_p, source3D_data_t *source_parm, chan sfx = &S_sfx[sfx_id]; +#error See? This shit's been unused for over a decade if (sfx->skinsound!=-1 && origin && origin->skin) { - // it redirect player sound to the sound in the skin table - sfx_id = ((skin_t *)origin->skin)->soundsid[sfx->skinsound]; - sfx = &S_sfx[sfx_id]; + // it redirect player sound to any voice values, should they exist + voice = P_GetMobjVoice(origin); + + if (voice) + { + sfx_id = sfx_id = P_GetKartVoiceSFX(voice, sfx->skinsound); + sfx = &S_sfx[sfx_id]; + } } if (!sfx->data) diff --git a/src/k_kart.c b/src/k_kart.c index a73419576..59fae6212 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1967,7 +1967,15 @@ void K_PlayPainSound(mobj_t *source, mobj_t *other) { sfxenum_t pick = P_RandomKey(2); // Gotta roll the RNG every time this is called for sync reasons - sfxenum_t sfx_id = ((skin_t *)source->skin)->soundsid[S_sfx[sfx_khurt1 + pick].skinsound]; + kartvoice_t *src_voice = P_GetMobjVoice(source); + + if (!src_voice) + { + // No voice! + return; + } + + sfxenum_t sfx_id = src_voice->pain[pick]; boolean alwaysHear = false; if (cv_kartvoices.value) @@ -1996,7 +2004,15 @@ void K_PlayPainSound(mobj_t *source, mobj_t *other) void K_PlayHitEmSound(mobj_t *source, mobj_t *other) { - sfxenum_t sfx_id = ((skin_t *)source->skin)->soundsid[S_sfx[sfx_khitem].skinsound]; + kartvoice_t *src_voice = P_GetMobjVoice(source); + + if (!src_voice) + { + // No voice! + return; + } + + sfxenum_t sfx_id = src_voice->hitem; boolean alwaysHear = false; if (cv_kartvoices.value) diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 9dc6f5217..1b3b63307 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -181,6 +181,7 @@ static const struct { {META_SOUNDSID, "skin_t.soundsid"}, {META_SKINSPRITES, "skin_t.sprites"}, {META_SKINSPRITESLIST, "skin_t.sprites[]"}, + {META_SKINVOICES, "skin_t.voices"}, {META_VERTEX, "vertex_t"}, {META_LINE, "line_t"}, @@ -249,6 +250,9 @@ static const struct { {META_VECTOR3, "vector3_t"}, {META_MATRIX, "matrix_t"}, {META_QUATERNION, "quaternion_t"}, + {META_VOICE, "kartvoice_t"}, + {META_VOICE_ARRAY, "kartvoice_t.array[]"}, + {META_VOICE_ARRAY1, "kartvoice_t.array[1]"}, {NULL, NULL} }; @@ -2611,6 +2615,7 @@ static int lib_rGetNameByColor(lua_State *L) return 1; } + // S_SOUND //////////// static int GetValidSoundOrigin(lua_State *L, void **origin) @@ -3679,12 +3684,21 @@ static int lib_kGloatSound(lua_State *L) static int lib_kLossSound(lua_State *L) { mobj_t *mobj = *((mobj_t **)luaL_checkudata(L, 1, META_MOBJ)); // let's require a mobj for consistency with the other functions + kartvoice_t *mo_voice; sfxenum_t sfx_id; NOHUD if (!mobj->player) return luaL_error(L, "K_PlayLossSound: mobj_t isn't a player object."); - sfx_id = ((skin_t *)mobj->skin)->soundsid[S_sfx[sfx_klose].skinsound]; + mo_voice = P_GetMobjVoice(mobj); + + if (!mo_voice) + { + // No voice! + return 0; + } + + sfx_id = mo_voice->lose; S_StartSound(mobj, sfx_id); return 0; } diff --git a/src/lua_infolib.c b/src/lua_infolib.c index b830ab3b8..32bde3101 100644 --- a/src/lua_infolib.c +++ b/src/lua_infolib.c @@ -1477,12 +1477,15 @@ static int sfxinfo_get(lua_State *L) lua_pushstring(L, sfx->caption); return 1; case sfxinfor_skinsound: - lua_pushinteger(L, sfx->skinsound); + if (!lua_compatmode) + goto nope; + sfxenum_t sound = sfx - S_sfx; + lua_pushinteger(L, sound >= sfx_kwin && sound <= sfx_kgloat ? sound - sfx_kwin : -1); return 1; - case sfxinfor_string: { + case sfxinfor_string: lua_pushstring(L, sfx->name); return 1; - } + nope: default: return luaL_error(L, "Field does not exist in sfxinfo_t"); } diff --git a/src/lua_libs.h b/src/lua_libs.h index bcde88696..7367b50df 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -53,6 +53,7 @@ extern lua_State *gL; #define META_SOUNDSID "SKIN_T*SOUNDSID" #define META_SKINSPRITES "SKIN_T*SPRITES" #define META_SKINSPRITESLIST "SKIN_T*SPRITES[]" +#define META_SKINVOICES "SKIN_T*VOICES" #define META_VERTEX "VERTEX_T*" #define META_LINE "LINE_T*" @@ -122,6 +123,11 @@ extern lua_State *gL; #define META_MATRIX "MATRIX_T" #define META_QUATERNION "QUATERNION_T" +#define META_VOICE "KARTVOICE_T*" +#define META_VOICE_ARRAY "KARTVOICE_T*[]" +// TODO: kill this one +#define META_VOICE_ARRAY1 "KARTVOICE_T*[1]" + boolean luaL_checkboolean(lua_State *L, int narg); int LUA_EnumLib(lua_State *L); @@ -147,6 +153,7 @@ int LUA_WaypointLib(lua_State *L); int LUA_MatrixLib(lua_State *L); int LUA_QuaternionLib(lua_State *L); int LUA_VectorLib(lua_State *L); +int LUA_VoiceLib(lua_State* L); #ifdef __cplusplus } // extern "C" diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index 3c7d03c71..da2dba14b 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -64,6 +64,7 @@ enum mobj_e { mobj_eflags, mobj_renderflags, mobj_skin, + mobj_voice, mobj_color, mobj_bnext, mobj_bprev, @@ -161,6 +162,7 @@ static const char *const mobj_opt[] = { "eflags", "renderflags", "skin", + "voice", "color", "bnext", "bprev", @@ -427,6 +429,9 @@ static int mobj_get(lua_State *L) return 0; lua_pushstring(L, ((skin_t *)mo->skin)->name); break; + case mobj_voice: // just push the struct damnit! + LUA_PushUserdata(L, mo->voice, META_VOICE); + break; case mobj_color: lua_pushinteger(L, mo->color); break; @@ -894,11 +899,19 @@ static int mobj_set(lua_State *L) if (fastcmp(skins[i].name, skin)) { if (!mo->player || R_SkinUsable(mo->player-players, i)) - mo->skin = &skins[i]; + { + R_ApplySkin(mo, &skins[i]); + } return 0; } return luaL_error(L, "mobj.skin '%s' not found!", skin); } + case mobj_voice: + // as much as i want to allow ironman without overwriting the player's skin, + // letting the mobj's voice desync with the skin has some nasty implications at the moment + return NOSET; + //mo->voice = *((kartvoice_t **)luaL_checkudata(L, 3, META_VOICE)); + //break; case mobj_color: { UINT16 newcolor = (UINT16)luaL_checkinteger(L,3); diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index edfde2aa4..e08b6c6b8 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -389,6 +389,7 @@ enum player_e player_flashpal, player_skincolor, player_skin, + player_voice_id, player_availabilities, player_score, player_kartspeed, @@ -606,6 +607,7 @@ static const char *const player_opt[] = { "flashpal", "skincolor", "skin", + "voice_id", "availabilities", "score", "kartspeed", @@ -1177,6 +1179,9 @@ static int player_get(lua_State *L) case player_skin: lua_pushinteger(L, plr->skin); break; + case player_voice_id: + lua_pushinteger(L, plr->voice_id); + break; case player_availabilities: lua_pushinteger(L, plr->availabilities); break; @@ -1502,6 +1507,8 @@ static int player_set(lua_State *L) } case player_skin: return NOSET; + case player_voice_id: + return NOSET; case player_availabilities: return NOSET; case player_score: diff --git a/src/lua_script.c b/src/lua_script.c index 6175350e4..5ebc165fe 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -68,6 +68,7 @@ static lua_CFunction liblist[] = { LUA_VectorLib, // vectors LUA_MatrixLib, // matrices LUA_QuaternionLib, // quaternions + LUA_VoiceLib, // kartvoice_t, skinvoices[] NULL }; diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c index f04ef9371..391e95550 100644 --- a/src/lua_skinlib.c +++ b/src/lua_skinlib.c @@ -39,6 +39,7 @@ enum skin { skin_highresscale, skin_rivals, skin_soundsid, + skin_voices, skin_sprites }; static const char *const skin_opt[] = { @@ -60,6 +61,7 @@ static const char *const skin_opt[] = { "highresscale", "rivals", "soundsid", + "voices", "sprites", NULL }; @@ -139,7 +141,13 @@ static int skin_get(lua_State *L) // This would be pretty cool to push return UNIMPLEMENTED; case skin_soundsid: - LUA_PushUserdata(L, skin->soundsid, META_SOUNDSID); + if (!lua_compatmode) + return UNIMPLEMENTED; // close enough + LUA_PushUserdata(L, &skin->soundsid[0], META_SOUNDSID); + break; + case skin_voices: + // ...uuuuuuughhhhh... + LUA_PushUserdata(L, &skin->soundsid[1], META_SKINVOICES); break; case skin_sprites: LUA_PushUserdata(L, skin->sprites, META_SKINSPRITES); @@ -241,11 +249,16 @@ static int lib_numSkins(lua_State *L) // soundsid, i -> soundsid[i] static int soundsid_get(lua_State *L) { + // soundsid is a route to the voice struct. That's how we're doing this. sfxenum_t *soundsid = *((sfxenum_t **)luaL_checkudata(L, 1, META_SOUNDSID)); - skinsound_t i = luaL_checkinteger(L, 2); - if (i >= NUMSKINSOUNDS) - return luaL_error(L, LUA_QL("skinsound_t") " cannot be %u", i); - lua_pushinteger(L, soundsid[i]); + skin_t *s = (skin_t*)((char*)soundsid - offsetof(skin_t, soundsid[0])); + + INT32 i = luaL_checkinteger(L, 2); + + if (i < 0 || i >= NUMSKINSOUNDS) + return luaL_error(L, "%s cannot be %s", LUA_QL("skinsound_t"), va("%u", i)); + + lua_pushinteger(L, R_GetLegacySkinSound(&s->voices[0], sfx_kwin + i)); return 1; } @@ -256,6 +269,28 @@ static int soundsid_num(lua_State *L) return 1; } +static int skinvoices_get(lua_State *L) +{ + // Use soundsid to route to the voice array so that voices[0] isn't considered THE VOICE ARRAY + sfxenum_t *svoices = *((sfxenum_t **)luaL_checkudata(L, 1, META_SKINVOICES)); + skin_t *s = (skin_t*)((char*)svoices - offsetof(skin_t, soundsid[1])); + + INT32 i = luaL_checkinteger(L, 2); + + if (i < 0 || i >= s->numvoices) + return luaL_error(L, "skin.voices[] index %s out of range (0 - %d)", va("%u", i), s->numvoices - 1); + + LUA_PushUserdata(L, &s->voices[i], META_VOICE); + return 1; +} + +// #voice -> MAXSKINVOICES +static int skinvoices_num(lua_State *L) +{ + lua_pushinteger(L, MAXSKINVOICES); + return 1; +} + enum spritesopt { numframes = 0 }; @@ -320,6 +355,7 @@ int LUA_SkinLib(lua_State *L) lua_setfield(L, -2, "__len"); lua_pop(L,1); + luaL_newmetatable(L, META_SKINSPRITES); lua_pushcfunction(L, lib_getSkinSprite); lua_setfield(L, -2, "__index"); @@ -333,6 +369,14 @@ int LUA_SkinLib(lua_State *L) lua_setfield(L, -2, "__index"); lua_pop(L,1); + luaL_newmetatable(L, META_SKINVOICES); + lua_pushcfunction(L, skinvoices_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, skinvoices_num); + lua_setfield(L, -2, "__len"); + lua_pop(L,1); + lua_newuserdata(L, 0); lua_createtable(L, 0, 2); lua_pushcfunction(L, lib_getSkin); diff --git a/src/lua_voicelib.c b/src/lua_voicelib.c new file mode 100644 index 000000000..06c4d20f4 --- /dev/null +++ b/src/lua_voicelib.c @@ -0,0 +1,232 @@ +// BLANKART +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by "yama". +// Copyright (C) 2025 by BlanKart Team. +// Copyright (C) 2014-2025 by Sonic Team Junior. +// Copyright (C) 2016 by John "JTE" Muniz. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file lua_voicelib.c +/// \brief voice structure library for Lua scripting + +#include "doomdef.h" +#include "fastcmp.h" +#include "r_skins.h" +#include "sounds.h" +#include "deh_tables.h" + +#include "lua_script.h" +#include "lua_libs.h" +#include "lua_hud.h" // hud_running errors +#include "lua_hook.h" // hook_cmd_running errors + +enum voicevars +{ + voicevars_id = 0, + voicevars_name, + voicevars_win, + voicevars_lose, + voicevars_pain, + voicevars_attack, + voicevars_boost, + voicevars_overtake, + voicevars_hitem, + voicevars_power +}; + +static const char *const voicevars_opt[] = { + "id", + "name", + "win", + "lose", + "pain", + "attack", + "boost", + "overtake", + "hitem", + "power", + NULL +}; + +#define RNOFIELD luaL_error(L, LUA_QL("kartvoice_t") " has no field named " LUA_QS, field) +#define RNOGET luaL_error(L, LUA_QL("kartvoice_t") " field " LUA_QS " cannot be get.", field) + +static int voice_get(lua_State* L) +{ + kartvoice_t* voice = *((kartvoice_t**)luaL_checkudata(L, 1, META_VOICE)); + enum voicevars field = luaL_checkoption(L, 2, NULL, voicevars_opt); + + // voices are always valid, only added, never removed + I_Assert(voice != NULL); + + switch (field) + { + case voicevars_id: + return RNOGET; + case voicevars_name: + lua_pushstring(L, voice->name); + break; + case voicevars_win: + LUA_PushUserdata(L, &voice->win, META_VOICE_ARRAY1); + break; + case voicevars_lose: + LUA_PushUserdata(L, &voice->lose, META_VOICE_ARRAY1); + break; + case voicevars_pain: + LUA_PushUserdata(L, voice->pain, META_VOICE_ARRAY); + break; + case voicevars_attack: + LUA_PushUserdata(L, voice->attack, META_VOICE_ARRAY); + break; + case voicevars_boost: + LUA_PushUserdata(L, voice->boost, META_VOICE_ARRAY); + break; + case voicevars_overtake: + LUA_PushUserdata(L, &voice->overtake, META_VOICE_ARRAY1); + break; + case voicevars_hitem: + LUA_PushUserdata(L, &voice->hitem, META_VOICE_ARRAY1); + break; + case voicevars_power: + LUA_PushUserdata(L, &voice->power, META_VOICE_ARRAY1); + break; + default: + return RNOFIELD; + } + + return 1; +} + +static int voice_set(lua_State *L) +{ + return luaL_error(L, LUA_QL("kartvoice_t") " struct cannot be edited by Lua."); +} + +static int voice_num(lua_State* L) +{ + kartvoice_t* voice = *((kartvoice_t**)luaL_checkudata(L, 1, META_VOICE)); + + // voices are always valid, only added, never removed + I_Assert(voice != NULL); + + lua_pushinteger(L, voice->id); + return 1; +} + +// voice_array, i -> voice.array[i] +static int voice_array_get(lua_State *L) +{ + sfxenum_t *voice_array = *((sfxenum_t **)luaL_checkudata(L, 1, META_VOICE_ARRAY)); + INT32 i = luaL_checkinteger(L, 2); + + if (i < 0 || i >= VOICEARRAYSIZE) + return luaL_error(L, "index %d out of range (0 - %d)", i, VOICEARRAYSIZE-1); + + lua_pushinteger(L, voice_array[i]); + return 1; +} + +static int voice_array_set(lua_State *L) +{ + sfxenum_t *voice_array = *((sfxenum_t **)luaL_checkudata(L, 1, META_VOICE_ARRAY)); + INT32 i = luaL_checkinteger(L, 2); + + if (hud_running) + return luaL_error(L, "Do not alter kartvoice_t.array[] in HUD rendering code!"); + if (hook_cmd_running) + return luaL_error(L, "Do not alter kartvoice_t.array[] in CMD building code!"); + + if (i < 0 || i >= VOICEARRAYSIZE) + return luaL_error(L, "index %d out of range (0 - %d)", i, VOICEARRAYSIZE-1); + + voice_array[i] = (sfxenum_t)luaL_checkinteger(L, 3); + return 0; +} + +// #voice.array -> VOICEARRAYSIZE +static int voice_array_num(lua_State *L) +{ + lua_pushinteger(L, VOICEARRAYSIZE); + return 1; +} + +// i would've liked to shove the length of the voice array into the userdata... +// but this codebase is not ready for that, sooooo... have yet ANOTHER userdata type! + +// voice_array, i -> voice.array[i] +static int voice_array1_get(lua_State *L) +{ + sfxenum_t *voice_array = *((sfxenum_t **)luaL_checkudata(L, 1, META_VOICE_ARRAY1)); + INT32 i = luaL_checkinteger(L, 2); + + if (i < 0 || i >= 1) + return luaL_error(L, "index %d out of range (0 - %d)", i, 1-1); + + lua_pushinteger(L, voice_array[i]); + return 1; +} + +static int voice_array1_set(lua_State *L) +{ + sfxenum_t *voice_array = *((sfxenum_t **)luaL_checkudata(L, 1, META_VOICE_ARRAY1)); + INT32 i = luaL_checkinteger(L, 2); + + if (hud_running) + return luaL_error(L, "Do not alter kartvoice_t.array[1] in HUD rendering code!"); + if (hook_cmd_running) + return luaL_error(L, "Do not alter kartvoice_t.array[1] in CMD building code!"); + + if (i < 0 || i >= 1) + return luaL_error(L, "index %d out of range (0 - %d)", i, 1-1); + + voice_array[i] = (sfxenum_t)luaL_checkinteger(L, 3); + return 0; +} + +// #voice.array -> 1 +static int voice_array1_num(lua_State *L) +{ + lua_pushinteger(L, 1); + return 1; +} + +int LUA_VoiceLib(lua_State* L) +{ + luaL_newmetatable(L, META_VOICE); + lua_pushcfunction(L, voice_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, voice_set); + lua_setfield(L, -2, "__newindex"); + + lua_pushcfunction(L, voice_num); + lua_setfield(L, -2, "__len"); + lua_pop(L, 1); + + luaL_newmetatable(L, META_VOICE_ARRAY); + lua_pushcfunction(L, voice_array_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, voice_array_set); + lua_setfield(L, -2, "__newindex"); + + lua_pushcfunction(L, voice_array_num); + lua_setfield(L, -2, "__len"); + lua_pop(L,1); + + luaL_newmetatable(L, META_VOICE_ARRAY1); + lua_pushcfunction(L, voice_array1_get); + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, voice_array1_set); + lua_setfield(L, -2, "__newindex"); + + lua_pushcfunction(L, voice_array1_num); + lua_setfield(L, -2, "__len"); + lua_pop(L,1); + + return 0; +} diff --git a/src/p_mobj.c b/src/p_mobj.c index 01a7d73d6..f7e7d4377 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12386,9 +12386,17 @@ void P_SpawnPlayer(INT32 playernum) // set 'spritedef' override in mobj for player skins.. (see ProjectSprite) // (usefulness: when body mobj is detached from player (who respawns), // the dead body mobj retains the skin through the 'spritedef' override). - mobj->skin = &skins[p->skin]; + R_ApplySkin(mobj, &skins[p->skin]); P_SetupStateAnimation(mobj, mobj->state); + if (p->voice_id) + { + // During respawns, do a quick check on our voice ID to make sure + // our skin can use it. + // This should prevent bots from always having Sonic's voice. + mobj->voice = &skins[p->skin].voices[p->voice_id]; + } + mobj->health = 1; p->playerstate = PST_LIVE; @@ -14967,6 +14975,31 @@ fixed_t P_GetMobjZMovement(mobj_t *mo) return P_ReturnThrustY(mo, slope->zangle, P_ReturnThrustX(mo, angDiff, speed)); } +kartvoice_t *P_GetMobjVoice(const mobj_t *mo) +{ + if (P_MobjWasRemoved(mo)) + { + // Nonexistent or NULL object. + return NULL; + } + + if (mo->voice) + { + return mo->voice; + } + +#ifdef PARANOIA + // Scream at the idiot that let this happen :^) + CONS_Printf( + "PARANOIA/P_GetMobjVoice: %p MT_%s passed to function with NULL voice\n", + (void*)mo, + P_MobjTypeName(mo) + ); +#endif + + return NULL; +} + // // Thing IDs / tags // diff --git a/src/p_mobj.h b/src/p_mobj.h index e605bd65e..6f83f8912 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -352,6 +352,11 @@ struct mobj_t void *skin; // overrides 'sprite' when non-NULL (for player bodies to 'remember' the skin) // Player and mobj sprites in multiplayer modes are modified // using an internal color lookup table for re-indexing. + + // Pointer to a set of voice values. If this doesn't exist, + // the game attempts to use tke skin's voice, should this object have a skin assigned. + kartvoice_t *voice; + UINT16 color; // This replaces MF_TRANSLATION. Use 0 for default (no translation). // More list: links in sector (if needed) @@ -594,6 +599,8 @@ void P_RingZMovement(mobj_t *mo); boolean P_SceneryZMovement(mobj_t *mo); void P_PlayerZMovement(mobj_t *mo); +kartvoice_t *P_GetMobjVoice(const mobj_t *mo); + extern INT32 modulothing; #define MAXHUNTEMERALDS 64 diff --git a/src/p_saveg.c b/src/p_saveg.c index 72a1444ee..a9dade76e 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -476,6 +476,7 @@ static void P_NetSyncPlayers(savebuffer_t *save) SYNC(players[i].skincolor); SYNC(players[i].skin); + SYNC(players[i].voice_id); SYNC(players[i].availabilities); SYNC(players[i].score); SYNC(players[i].lives); @@ -1847,6 +1848,7 @@ enum mobj_diff_t MD3_BAKEDOFFSET, MD3_EXTVAL3, MD3_LIFETIME, + MD3_VOICE, MD__MAX }; @@ -2009,6 +2011,7 @@ static void DiffMobj(const mobj_t *mobj, UINT32 diff[]) DIFF(mobj->bakexoff || mobj->bakeyoff || mobj->bakezoff || mobj->bakexpiv || mobj->bakeypiv || mobj->bakezpiv, MD3_BAKEDOFFSET); DIFF(mobj->extravalue3, MD3_EXTVAL3); DIFF(mobj->mobjlifetime, MD3_LIFETIME); + DIFF(mobj->voice, MD3_VOICE); } static thinker_t *SyncMobjThinker(savebuffer_t *save, actionf_p1 thinker, thinker_t *th, UINT8 type) @@ -2221,7 +2224,7 @@ static thinker_t *SyncMobjThinker(savebuffer_t *save, actionf_p1 thinker, thinke if (save->write) WRITEUINT16(save->p, (UINT16)((skin_t *)mobj->skin - skins)); else - mobj->skin = &skins[READUINT16(save->p)]; + R_ApplySkin(mobj, &skins[READUINT16(save->p)]); } SYNCF(MD2_COLOR, mobj->color); SYNCF(MD2_EXTVAL1, mobj->extravalue1); @@ -2334,6 +2337,21 @@ static thinker_t *SyncMobjThinker(savebuffer_t *save, actionf_p1 thinker, thinke SYNCF(MD3_EXTVAL3, mobj->extravalue3); SYNCF(MD3_LIFETIME, mobj->mobjlifetime); + if (GETB(MD3_VOICE) && mobj->skin) + { + if (save->write) + WRITEUINT16(save->p, (UINT16)(mobj->voice - &((skin_t *)(mobj->skin))->voices[0])); + else + { + UINT16 savedvoice = READUINT16(save->p); + + if (savedvoice > ((skin_t *)(mobj->skin))->numvoices) + savedvoice = 0; + + mobj->voice = &((skin_t *)(mobj->skin))->voices[savedvoice]; + } + } + if (!save->write) { // Reset some non-synch values diff --git a/src/p_tick.c b/src/p_tick.c index 05819a69c..e3aa7046f 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -262,7 +262,7 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) } #ifdef PARANOIA -static const char *MobjTypeName(const mobj_t *mobj) +const char *P_MobjTypeName(const mobj_t *mobj) { actionf_p1 p1 = mobj->thinker.function; @@ -338,7 +338,7 @@ void P_RemoveThinkerDelayed(thinker_t *thinker) CONS_Printf( "PARANOIA/P_RemoveThinkerDelayed: %p MT_%s references=%d\n", (void*)thinker, - MobjTypeName((mobj_t*)thinker), + P_MobjTypeName((mobj_t*)thinker), thinker->references ); @@ -428,7 +428,7 @@ mobj_t *P_SetTarget2(mobj_t **mop, mobj_t *targ CONS_Printf( "PARANOIA/P_SetTarget: %p MT_%s %s references=%d, references go negative! (%s:%d)\n", (void*)*mop, - MobjTypeName(*mop), + P_MobjTypeName(*mop), MobjThinkerName(*mop), (*mop)->thinker.references, source_file, diff --git a/src/p_tick.h b/src/p_tick.h index 6a187e7d5..e4b167439 100644 --- a/src/p_tick.h +++ b/src/p_tick.h @@ -44,6 +44,10 @@ mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target #define P_SetTarget P_SetTarget2 #endif +#ifdef PARANOIA +const char *P_MobjTypeName(const mobj_t *mobj); +#endif + extern UINT32 thinker_era; // Negate the value for tics diff --git a/src/p_user.c b/src/p_user.c index 52ff68403..ac3fe96ca 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1313,9 +1313,15 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) if (cv_kartvoices.value) { - const INT32 sfx_id = (losing ? sfx_klose : sfx_kwin); + //const INT32 sfx_id = (losing ? sfx_klose : sfx_kwin); skin_t *playerskin = &skins[player->skin]; - S_StartSound(player->mo, playerskin->soundsid[S_sfx[sfx_id].skinsound]); + + const kartvoice_t *playervoice = (!P_MobjWasRemoved(player->mo) ? P_GetMobjVoice(player->mo) : &playerskin->voices[0]); + + if (playervoice) + { + S_StartSound(player->mo, (losing ? playervoice->lose : playervoice->win)); + } } // See Y_StartIntermission timer handling diff --git a/src/r_skins.c b/src/r_skins.c index bd8d70e19..e388f155c 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -170,9 +170,72 @@ UINT8 P_KartFrameToSprite2(skin_t *skin, UINT8 inframe, UINT8 *outframe) return P_GetSkinSprite2(skin, spr2, NULL); } -static void Sk_SetDefaultValue(skin_t *skin) +// returns the sound flags that should be applied to a voice's sound array +static INT32 R_GetVoiceArraySoundFlags(const kartvoice_t *voice, const sfxenum_t *array) +{ + if (array == &voice->overtake || array == &voice->hitem) + return SF_NOINTERRUPT; + else if (array == &voice->power) + return SF_NOINTERRUPT|SF_X8AWAYSOUND; + else + return SF_NOINTERRUPT|SF_X2AWAYSOUND; +} + +// parses the name of a voice sound array from S_SKIN without the "Voice" prefix +// returns the length and pointer to the matching voice sound array, or 0 if nothing matched +static size_t R_ParseVoiceSoundKey(kartvoice_t *voice, const char *name, sfxenum_t **outarray) +{ + // woah! comma operator! +#define S(str, ret) if (!stricmp(name, #str)) return *outarray = voice->ret, sizeof(voice->ret)/sizeof(*voice->ret) +#define S1(str, ret) if (!stricmp(name, #str)) return *outarray = &voice->ret, 1 + S1(WIN, win); + S1(LOSE, lose); + S(HURT, pain); + S(PAIN, pain); + S(DAMAGE, pain); + S(ATTACK, attack); + S(BOOST, boost); + S1(SLOW, overtake); + S1(OVERTAKE, overtake); + S1(PASS, overtake); + S1(HITEM, hitem); + S1(HITCONFIRM, hitem); + S1(GLOAT, power); +#undef S + + return 0; +} + +sfxenum_t R_GetLegacySkinSound(const kartvoice_t *voice, sfxenum_t sound) +{ + switch (sound) + { + case sfx_kwin: // Win quote + return voice->win; + case sfx_klose: // Lose quote + return voice->lose; + case sfx_khurt1: // Pain + case sfx_khurt2: + return voice->pain[sound - sfx_khurt1]; + case sfx_kattk1: // Offense item taunt + case sfx_kattk2: + return voice->attack[sound - sfx_kattk1]; + case sfx_kbost1: // Boost item taunt + case sfx_kbost2: + return voice->boost[sound - sfx_kbost1]; + case sfx_kslow: // Overtake taunt + return voice->overtake; + case sfx_khitem: // Hit confirm taunt + return voice->hitem; + case sfx_kgloat: // Power item taunt + return voice->power; + default: + return sfx_thok; + } +} + +static void Sk_SetDefaultValue(skin_t *skin, kartvoice_t *skin_voice) { - INT32 i; // // set default skin values // @@ -200,9 +263,20 @@ static void Sk_SetDefaultValue(skin_t *skin) skin->highresscale = FRACUNIT; - for (i = 0; i < sfx_skinsoundslot0; i++) - if (S_sfx[i].skinsound != -1) - skin->soundsid[S_sfx[i].skinsound] = i; + skin_voice->win = sfx_thok; + skin_voice->lose = sfx_thok; + skin_voice->pain[0] = skin_voice->pain[1] = sfx_thok; + skin_voice->attack[0] = skin_voice->attack[1] = sfx_thok; + skin_voice->boost[0] = skin_voice->boost[1] = sfx_thok; + skin_voice->overtake = sfx_thok; + skin_voice->hitem = sfx_thok; + skin_voice->power = sfx_thok; + + strlcpy(skin_voice->name, "default", sizeof(skin_voice->name)); + strlcpy(skin_voice->realname, "Default", sizeof(skin_voice->realname)); + + skin->numvoices++; + CONS_Printf(M_GetText("%d %s allocated for this skin.\n"), skin->numvoices, (skin->numvoices == 1) ? "voice" : "voices"); } static void R_IHateThatHedgehog(UINT16 wadnum); @@ -540,7 +614,8 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) if (player->mo) { - player->mo->skin = skin; + R_ApplySkin(player->mo, skin); + P_SetScale(player->mo, player->mo->scale); P_SetPlayerMobjState(player->mo, player->mo->state-states); // Prevent visual errors when switching between skins with differing number of frames } @@ -564,6 +639,94 @@ void SetPlayerSkinByNum(INT32 playernum, INT32 skinnum) SetPlayerSkinByNum(playernum, 0); // not found, put in the default skin } +// Gets the corresponding voice ID for a given voice's name. +// Returns MAXSKINVOICES if nothing is found. +INT32 R_FindIDForVoice(skin_t *skin, const char* voicename) +{ + INT32 i; + + // Scan through our allocated skin voices for the wanted voice. + + // First pass: Check the actual name. + for (i = 0; i < skin->numvoices; i++) + { + if (strnicmp(voicename, skin->voices[i].name, VOICENAMESIZE) == 0) + { + // Found a voice. + return i; + } + } + + // Second pass: check the display name. + for (i = 0; i < skin->numvoices; i++) + { + // Whatever. Go, my code duplication! + if (strnicmp(voicename, skin->voices[i].realname, VOICENAMESIZE) == 0) + { + // Found a voice. + return i; + } + } + + // Couldn't find a voice. + return MAXSKINVOICES; +} + +// network code calls this when a 'voice change' is received +void SetPlayerVoice(INT32 playernum, const char *voicename) +{ + INT32 voxid = R_FindIDForVoice(&skins[players[playernum].skin], voicename); + + if (voxid == MAXSKINVOICES) + { + // Couldn't find a voice. Default to the skin's default. + voxid = 0; + } + + SetPlayerVoiceByNum(playernum, voxid); +} + +// network code calls this when a 'voice change' is received +void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum) +{ + player_t* player = &players[playernum]; + + if (player->voice_id == voicenum) + { + // Hey... this voice is the same as before! + return; + } + + player->voice_id = voicenum; + + if (!P_MobjWasRemoved(player->mo)) + player->mo->voice = &skins[player->skin].voices[voicenum]; + + // Tell the demo that we've updated our voice_id as well. + demo_extradata[playernum] |= DXD_SKIN; +} + +/** \brief Applies the skin and its default voice. + + \param mo (mobj_t) the given object the skin and voice are being assigned to + \param skin (skin_t) the skin to attach to the object. The voice is sourced from this skin +*/ +void R_ApplySkin(mobj_t *mo, skin_t *skin) +{ + mo->skin = skin; + mo->voice = &((skin_t *)mo->skin)->voices[0]; +} + +/** \brief Applies only the skin. + + \param mo (mobj_t) the given object the skin is being assigned to + \param skin (skin_t) the skin to attach to the object +*/ +void R_ApplySkinOnly(mobj_t *mo, skin_t *skin) +{ + mo->skin = skin; +} + // // Add skins from a pwad, each skin preceded by 'S_SKIN' marker // @@ -776,10 +939,18 @@ static void R_LoadSkinSprites(UINT16 wadnum, UINT16 *lump, UINT16 *lastlump, ski static void R_IHateThatHedgehog(UINT16 wadnum) { skin_t *skin = &skins[0]; - Sk_SetDefaultValue(skin); + kartvoice_t *skin_voice = &skin->voices[0]; + Sk_SetDefaultValue(skin, skin_voice); skin->wadnum = wadnum; strcpy(skin->name, "sonic"); + // Add voice + if (R_FindIDForVoice(skin, "default") == MAXSKINVOICES) + { + // Hey guy, take care! + I_Error("Failed to allocate initial skin voice\n"); + } + #ifdef SKINVALUES skin_cons_t[0].value = 0; skin_cons_t[0].strvalue = skin->name; @@ -793,6 +964,8 @@ static void R_IHateThatHedgehog(UINT16 wadnum) #endif } +static kartvoice_t *allocvoice; + // returns whether found appropriate property static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) { @@ -912,44 +1085,149 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) GETPATCH(facemmap) #undef GETPATCH - else // let's check if it's a sound, otherwise error out + else if (!stricmp(stoken, "voicename")) { - boolean found = false; - sfxenum_t i; - size_t stokenadjust; + // Change the current voice. + INT32 vox_id = R_FindIDForVoice(skin, value); - // Remove the prefix. (We need to affect an adjusting variable so that we can print error messages if it's not actually a sound.) - if ((stoken[0] == 'D' || stoken[0] == 'd') && (stoken[1] == 'S' || stoken[1] == 's')) // DS* - stokenadjust = 2; - else // sfx_* - stokenadjust = 4; - - // Remove the prefix. (We can affect this directly since we're not going to use it again.) - if ((value[0] == 'D' || value[0] == 'd') && (value[1] == 'S' || value[1] == 's')) // DS* - value += 2; - else // sfx_* - value += 4; - - // copy name of sounds that are remapped - // for this skin - for (i = 0; i < sfx_skinsoundslot0; i++) + if (vox_id == MAXSKINVOICES) { - if (!S_sfx[i].name) - continue; - if (S_sfx[i].skinsound != -1 - && !stricmp(S_sfx[i].name, - stoken + stokenadjust)) + if (!stricmp(stoken, "default")) { - skin->soundsid[S_sfx[i].skinsound] = - S_AddSoundFx(value, S_sfx[i].singularity, S_sfx[i].flags, true); - found = true; + // "default" should ALWAYS exist! + I_Error("Failed to allocate default voice for skin %s\n", skin->name); } + + // Couldn't find this voice; let's try allocating. + if (R_AllocKartVoice(skin, value, &vox_id) + 1) + { + // Allocated a voice! + allocvoice = &skin->voices[vox_id]; + CONS_Printf(M_GetText("%d %s allocated for this skin.\n"), skin->numvoices, (skin->numvoices == 1) ? "voice" : "voices"); + } + + // Found nothing, or we're full. Let's just keep moving. } - return found; + else + allocvoice = &skin->voices[vox_id]; + } + else if (!stricmp(stoken, "voicerealname")) + { + // Set the "realname" field for the current voice. + STRBUFCPY(allocvoice->realname, value); + SYMBOLCONVERT(allocvoice->realname); + } + else if (!strnicmp(stoken, "voice", 5)) + { + // (that one gif of patrick drooling where his mouth is his entire face) + size_t len = strlen(value); + size_t i; + char voicename[VOICENAMESIZE] = ""; + UINT8 pos = 0; + UINT8 numvoices = 0; + + sfxenum_t *voicearray; + size_t voicearraylen = R_ParseVoiceSoundKey(allocvoice, stoken+5, &voicearray); + if (voicearraylen == 0) + return false; + + for (i = 0; i <= len; i++) + { + if (value[i] == ',' || i == len) + { + // Automatically allocate any defined voice sounds. + // Freeslotted voice clips won't ever belong to a skin. + sfxenum_t j; + const char *sfxname = voicename; + if (toupper(voicename[0]) == 'D' && toupper(voicename[1]) == 'S') + sfxname += 2; + + j = S_AddSoundFx(sfxname, true); + if (j == sfx_None) + I_Error("Skin %s: out of skin sound slots", skin->name); + S_sfx[j].flags = R_GetVoiceArraySoundFlags(allocvoice, voicearray); + + voicearray[numvoices++] = j; + if (numvoices >= voicearraylen && i != len) + I_Error("Skin %s: voice array %s exceeds max length of %zu", skin->name, stoken, voicearraylen); + + memset(voicename, 0, sizeof (voicename)); + pos = 0; + + continue; + } + + voicename[pos] = value[i]; + pos++; + } + } + else // let's check if it's a legacy skinsound, otherwise error out + { + sfxenum_t i; + for (i = sfx_kwin; i <= sfx_kgloat; i++) + if (!stricmp(stoken+2, S_sfx[i].name)) + break; + if (i > sfx_kgloat) + return false; + + static const char *tonewname[] = { + "WIN", + "LOSE", + "PAIN", + "PAIN", + "ATTACK", + "ATTACK", + "BOOST", + "BOOST", + "SLOW", + "HITEM", + "GLOAT", + }; + + sfxenum_t *voicearray; + size_t len = R_ParseVoiceSoundKey(allocvoice, tonewname[i - sfx_kwin], &voicearray); + if (len == 0) + return false; + + // grab the flags now before messing with the pointer + INT32 flags = R_GetVoiceArraySoundFlags(allocvoice, voicearray); + + if (i == sfx_khurt2 || i == sfx_kattk2 || i == sfx_kbost2) + voicearray++; + + *voicearray = S_AddSoundFx(value+2, true); + if (*voicearray != sfx_None) + S_sfx[*voicearray].flags = flags; } return true; } +// +// Allocates a voice onto the dehacked list, and iterates the provided output pointer +// (should it exist). +// +INT32 R_AllocKartVoice(skin_t *skin, const char* name, INT32 *out) +{ + *out = R_FindIDForVoice(skin, name); + if (*out != MAXSKINVOICES) + return -1; // already allocated + + if (skin->numvoices == MAXSKINVOICES - 1) { + CONS_Alert(CONS_WARNING, "Ran out of free voice slots!\n"); + return -1; + } + + strlcpy(skin->voices[skin->numvoices].name, name, sizeof(skin->voices[skin->numvoices].name)); + strlwr(skin->voices[skin->numvoices].name); + + skin->voices[skin->numvoices].id = skin->numvoices; + + CONS_Printf("Voice %s allocated for skin %s.\n",skin->voices[skin->numvoices].name,skin->name); + + *out = skin->numvoices++; + return 0; +} + // // Find skin sprites, sounds & optional status bar face, & add them // @@ -962,6 +1240,7 @@ void R_AddSkins(UINT16 wadnum) char *value; size_t size; skin_t *skin; + kartvoice_t *skin_voice; boolean realname; boolean iscompatskin = false; @@ -991,7 +1270,10 @@ void R_AddSkins(UINT16 wadnum) // set defaults skin = &skins[numskins]; - Sk_SetDefaultValue(skin); + skin_voice = &skin->voices[0]; + allocvoice = skin_voice; + + Sk_SetDefaultValue(skin, skin_voice); skin->wadnum = wadnum; realname = false; // parse @@ -1018,7 +1300,9 @@ void R_AddSkins(UINT16 wadnum) INT32 skinnum = R_SkinAvailable(value); strlwr(value); if (skinnum == -1) + { STRBUFCPY(skin->name, value); + } // the skin name must uniquely identify a single skin // if the name is already used I make the name 'namex' // using the default skin name's number set above @@ -1031,9 +1315,11 @@ void R_AddSkins(UINT16 wadnum) "%s%d", value, numskins); value2[stringspace - 1] = '\0'; if (R_SkinAvailable(value2) == -1) + { // I'm lazy so if NEW name is already used I leave the 'skin x' // default skin name set in Sk_SetDefaultValue STRBUFCPY(skin->name, value2); + } Z_Free(value2); } @@ -1057,6 +1343,13 @@ void R_AddSkins(UINT16 wadnum) } free(buf2); + // Add voice + if (R_FindIDForVoice(skin, "default") == MAXSKINVOICES) + { + // We couldn't allocate our own skin's voice?! + I_Error("Failed to allocate voice for skin %s\n", skin->name); + } + // Add sprites R_LoadSkinSprites(wadnum, &lump, &lastlump, skin); //ST_LoadFaceGraphics(numskins); -- nah let's do this elsewhere @@ -1153,7 +1446,10 @@ void R_PatchSkins(UINT16 wadnum) strlwr(value); skinnum = R_SkinAvailable(value); if (skinnum != -1) + { skin = &skins[skinnum]; + allocvoice = &skins[skinnum].voices[0]; + } else { CONS_Debug(DBG_SETUP, "R_PatchSkins: unknown skin name in P_SKIN lump# %d(%s) in WAD %s\n", lump, W_CheckNameForNumPwad(wadnum,lump), wadfiles[wadnum]->filename); diff --git a/src/r_skins.h b/src/r_skins.h index 82669bc76..7b0c2a35d 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -27,6 +27,8 @@ extern "C" { /// Defaults #define SKINNAMESIZE 16 +#define VOICENAMESIZE 32 +#define VOICEARRAYSIZE 2 #define SKINRIVALS 3 // should be all lowercase!! S_SKIN processing does a strlwr #define DEFAULTSKIN "sonic" @@ -36,6 +38,29 @@ extern "C" { extern consvar_t cv_skinselectstyle, cv_skinselectsort; +#define MAXSKINVOICES 16 // 16 voices * 4096 skins = 65536 voices in total :^) + +// Player voice struct +struct kartvoice_t +{ + UINT32 id; + + // The voice's name. + char name[VOICENAMESIZE+1]; + + // Display name, for things such as menus + char realname[VOICENAMESIZE+1]; + + sfxenum_t win; + sfxenum_t lose; + sfxenum_t pain[VOICEARRAYSIZE]; + sfxenum_t attack[VOICEARRAYSIZE]; + sfxenum_t boost[VOICEARRAYSIZE]; + sfxenum_t overtake; + sfxenum_t hitem; + sfxenum_t power; +}; + /// The skin_t struct struct skin_t { @@ -63,8 +88,16 @@ struct skin_t char rivals[SKINRIVALS][SKINNAMESIZE+1]; // Your top 3 rivals for GP mode. Uses names so that you can reference skins that aren't added - // specific sounds per skin - sfxenum_t soundsid[NUMSKINSOUNDS]; // sound # in S_sfx table + // Pointer to a skin's voice, providing specific sounds per-skin. + // If an mobj_t doesn't have a voice, it defaults to this value, given a skin is in use. + kartvoice_t voices[MAXSKINVOICES]; + + UINT16 numvoices; + + // Proxy value for the (now removed) soundsid array. + // This is ONLY used in a Lua context. + // It doesn't actually have anything and will never actually have anything. + UINT8 soundsid[2]; // contains super versions too spritedef_t sprites[NUMPLAYERSPRITES*2]; @@ -111,10 +144,18 @@ UINT32 R_GetSkinAvailabilities(void); INT32 R_SkinAvailable(const char *name); void R_PatchSkins(UINT16 wadnum); void R_AddSkins(UINT16 wadnum); +void R_ApplySkin(mobj_t *mo, skin_t *skin); +void R_ApplySkinOnly(mobj_t *mo, skin_t *skin); UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player); UINT8 P_KartFrameToSprite2(skin_t *skin, UINT8 inframe, UINT8 *outframe); +INT32 R_AllocKartVoice(skin_t *skin, const char* name, INT32 *out); +INT32 R_FindIDForVoice(skin_t *skin, const char *voicename); +void SetPlayerVoice(INT32 playernum, const char *voicename); +void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum); +sfxenum_t R_GetLegacySkinSound(const kartvoice_t *voice, sfxenum_t sound); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/s_sound.c b/src/s_sound.c index 37a0fcce9..7c0cd193a 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -603,19 +603,20 @@ void S_StartSoundAtVolume(const void *origin_p, sfxenum_t sfx_id, INT32 volume) return; } + if (origin && sfx_id >= sfx_kwin && sfx_id <= sfx_kgloat) + { + // legacy skinsound remapping + kartvoice_t *cur_voice = P_GetMobjVoice(origin); + if (cur_voice != NULL) + sfx_id = R_GetLegacySkinSound(cur_voice, sfx_id); + } + // check for bogus sound # I_Assert(sfx_id >= 1); I_Assert(sfx_id < NUMSFX); sfx = &S_sfx[sfx_id]; - if (sfx->skinsound != -1 && origin && origin->skin) - { - // redirect player sound to the sound in the skin table - sfx_id = ((skin_t *)origin->skin)->soundsid[sfx->skinsound]; - sfx = &S_sfx[sfx_id]; - } - // Initialize sound parameters pitch = NORM_PITCH; priority = NORM_PRIORITY; @@ -1274,7 +1275,7 @@ void S_StartSoundName(void *mo, const char *soundname) return; } - soundnum = S_AddSoundFx(soundname, false, 0, false); + soundnum = S_AddSoundFx(soundname, false); newsounds[i] = soundnum; } diff --git a/src/sounds.c b/src/sounds.c index 816ef6faa..474a64212 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -86,7 +86,6 @@ void S_InitRuntimeSounds (void) S_sfx[i].volume = -1; S_sfx[i].data = NULL; S_sfx[i].length = 0; - S_sfx[i].skinsound = -1; S_sfx[i].usefulness = -1; S_sfx[i].lumpnum = LUMPERROR; //strlcpy(S_sfx[i].caption, "", 1); @@ -94,53 +93,50 @@ void S_InitRuntimeSounds (void) } } -sfxenum_t sfxfree = sfx_freeslot0; +sfxenum_t sfxfree = sfx_freeslot0, skinsfxfree = sfx_skinsoundslot0; // Add a new sound fx into a free sfx slot. // -sfxenum_t S_AddSoundFx(const char *name, boolean singular, INT32 flags, boolean skinsound) +sfxenum_t S_AddSoundFx(const char *name, boolean skinsound) { sfxenum_t i; if (skinsound) { - for (i = sfx_skinsoundslot0; i < NUMSFX; i++) + if (skinsfxfree > sfx_lastskinsoundslot) { - if (S_sfx[i].priority) - continue; - break; + CONS_Alert(CONS_WARNING, M_GetText("No more free skin sound slots\n")); + return sfx_None; } + i = skinsfxfree++; } else - i = sfxfree; - - if (i < NUMSFX) { - strncpy(soundnames[i], name, 6); - S_sfx[i].singularity = singular; - S_sfx[i].priority = 60; - S_sfx[i].flags = flags; - S_sfx[i].volume = -1; - S_sfx[i].lumpnum = LUMPERROR; - S_sfx[i].skinsound = -1; - S_sfx[i].usefulness = -1; - - /// \todo if precached load it here - S_sfx[i].data = NULL; - - if (!skinsound) - sfxfree++; - - return i; + if (sfxfree > sfx_lastfreeslot) + { + CONS_Alert(CONS_WARNING, M_GetText("No more free sound slots\n")); + return sfx_None; + } + i = sfxfree++; } - CONS_Alert(CONS_WARNING, M_GetText("No more free sound slots\n")); - return 0; + + strncpy(soundnames[i], name, 6); + S_sfx[i].singularity = false; + S_sfx[i].priority = 60; + S_sfx[i].flags = 0; + S_sfx[i].volume = -1; + S_sfx[i].lumpnum = LUMPERROR; + S_sfx[i].usefulness = -1; + + /// \todo if precached load it here + S_sfx[i].data = NULL; + + return i; } void S_RemoveSoundFx(sfxenum_t id) { - if (id >= sfx_freeslot0 && id <= sfx_lastskinsoundslot - && S_sfx[id].priority != 0) + if (id >= sfx_freeslot0 && id < NUMSFX && S_sfx[id].priority != 0) { S_sfx[id].lumpnum = LUMPERROR; I_FreeSfx(&S_sfx[id]); diff --git a/src/sounds.h b/src/sounds.h index 3c78d999a..102b22c2e 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -20,27 +20,12 @@ extern "C" { #endif -// Customisable sounds for Skins -typedef enum -{ - // SRB2kart - SKSKWIN, // Win quote - SKSKLOSE, // Lose quote - SKSKPAN1, // Pain - SKSKPAN2, - SKSKATK1, // Offense item taunt - SKSKATK2, - SKSKBST1, // Boost item taunt - SKSKBST2, - SKSKSLOW, // Overtake taunt - SKSKHITM, // Hit confirm taunt - SKSKPOWR, // Power item taunt - NUMSKINSOUNDS -} skinsound_t; +// keep this one around at least, for compatibility code +#define NUMSKINSOUNDS 11 // free sfx for S_AddSoundFx() #define NUMSFXFREESLOTS 2500 // Matches SOC Editor. -#define NUMSKINSFXSLOTS (MAXSKINS*NUMSKINSOUNDS) +#define NUMSKINSFXSLOTS (50000 - NUMSFXFREESLOTS) // // SoundFX struct. @@ -69,10 +54,6 @@ struct sfxinfo_t // length of sound data size_t length; - // sound that can be remapped for a skin, indexes skins[].skinsounds - // 0 up to (NUMSKINSOUNDS-1), -1 = not skin specifc - INT32 skinsound; - // this is checked every second to see if sound // can be thrown out (if 0, then decrement, if -1, // then throw out, if > 0, then it is in use) @@ -114,8 +95,8 @@ typedef enum void S_InitRuntimeSounds(void); -sfxenum_t S_AddSoundFx(const char *name, boolean singular, INT32 flags, boolean skinsound); -extern sfxenum_t sfxfree; // sound test and slotting +sfxenum_t S_AddSoundFx(const char *name, boolean skinsound); +extern sfxenum_t sfxfree, skinsfxfree; // sound test and slotting void S_RemoveSoundFx(sfxenum_t id); #ifdef __cplusplus diff --git a/src/typedef.h b/src/typedef.h index 88536d024..bbae58272 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -376,6 +376,7 @@ TYPEDEF (visffloor_t); TYPEDEF (portal_t); // r_skins.h +TYPEDEF (kartvoice_t); TYPEDEF (skin_t); // r_splats.h