Merge pull request '[FEAT] Skin-based voice dubs' (#190) from subvsdub into next

Reviewed-on: https://codeberg.org/NepDisk/blankart/pulls/190
This commit is contained in:
yamamama 2025-12-02 17:42:00 +01:00
commit 851ca0b262
33 changed files with 1216 additions and 150 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);

View file

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

View file

@ -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);
}

View file

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

View file

@ -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;
//

View file

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

View file

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

View file

@ -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;
}

View file

@ -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");
}

View file

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

View file

@ -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);

View file

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

View file

@ -68,6 +68,7 @@ static lua_CFunction liblist[] = {
LUA_VectorLib, // vectors
LUA_MatrixLib, // matrices
LUA_QuaternionLib, // quaternions
LUA_VoiceLib, // kartvoice_t, skinvoices[]
NULL
};

View file

@ -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);

232
src/lua_voicelib.c Normal file
View file

@ -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;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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);

View file

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

View file

@ -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;
}

View file

@ -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]);

View file

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

View file

@ -376,6 +376,7 @@ TYPEDEF (visffloor_t);
TYPEDEF (portal_t);
// r_skins.h
TYPEDEF (kartvoice_t);
TYPEDEF (skin_t);
// r_splats.h