Voice dub freeslotting

Full transparency: the way I set up dub reading from an SOC is Yanderedev-tier garbage.
This commit is contained in:
yamamama 2025-11-12 17:04:42 -05:00
parent 69cb50b379
commit b7a60c2fea
14 changed files with 330 additions and 6 deletions

View file

@ -1190,6 +1190,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
@ -1406,6 +1407,13 @@ static void Skin_FindValidDub(consvar_t *cvar, UINT16 skin_id, kartvoice_t *voic
const char *value = cvar->string;
kartvoice_t *myvoice = voice;
if (!Playing())
{
// Don't bother.
CONS_Printf(M_GetText("You must be in a game to use this.\n"));
return;
}
if (!myvoice)
{
myvoice = skins[skin_id].voice;
@ -1437,6 +1445,7 @@ static void Skin_FindValidDub(consvar_t *cvar, UINT16 skin_id, kartvoice_t *voic
// *Reassign* the voice to whatever the current voice is, at least.
CV_StealthSet(cvar, myvoice->name);
cvar->value = myvoice->id;
}
// Return regardless, as we've found the voice we were looking for.
@ -1464,12 +1473,20 @@ static void Skin_FindValidDub(consvar_t *cvar, UINT16 skin_id, kartvoice_t *voic
// *Reassign* the voice cvar to whatever the current voice is, at least.
CV_StealthSet(cvar, myvoice->name);
cvar->value = myvoice->id;
}
// Return regardless, as we've found the voice we were looking for.
return;
}
}
// Found diddly-squat, say as much.
CONS_Alert(CONS_NOTICE, M_GetText("Voice \"%s\" does not exist.\n"), value);
// Reassign the voice cvar to our current voice.
CV_StealthSet(cvar, myvoice->name);
cvar->value = myvoice->id;
}
/** Checks if a name (as received from another player) is okay.
@ -1828,6 +1845,7 @@ static void SendNameAndColor(UINT8 n)
const INT32 playernum = g_localplayers[n];
player_t *player = &players[playernum];
kartvoice_t *voice;
INT32 prevskin;
// We have no voice, but don't want to topple the house of cards.
boolean permamute = false;
@ -1920,16 +1938,34 @@ static void SendNameAndColor(UINT8 n)
}
else if ((foundskin = R_SkinAvailable(cv_skin[n].string)) != -1 && R_SkinUsable(playernum, foundskin))
{
prevskin = player->skin;
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 to that of the skin's.
if (prevskin != player->skin && skins[player->skin].voice)
{
CV_StealthSet(&cv_voice[n], skins[player->skin].voice->name);
cv_voice[n].value = (UINT16)skins[player->skin].voice->id;
}
}
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 to that of the skin's.
if (prevskin != player->skin && skins[player->skin].voice)
{
CV_StealthSet(&cv_voice[n], skins[player->skin].voice->name);
cv_voice[n].value = (UINT16)skins[player->skin].voice->id;
}
}
// Need to update voices after the fact.
@ -1947,9 +1983,20 @@ static void SendNameAndColor(UINT8 n)
}
else
{
// ...this is my paranoia speaking.
CV_StealthSet(&cv_voice[n], "sonic_voice");
cv_voice[n].value = 0;
// Try getting the skin's voice.
valuevoice = skins[player->skin].voice;
if (valuevoice)
{
CV_StealthSet(&cv_voice[n], valuevoice->name);
cv_voice[n].value = (UINT16)valuevoice->id;
}
else
{
// ...still nothing?
CV_StealthSet(&cv_voice[n], "sonic_voice");
cv_voice[n].value = 0;
}
}
return;

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

@ -4241,6 +4241,166 @@ void readkartresult(MYFILE *f, kartresult_t *result)
#undef WARN
#undef WARN0
#define WARN(str, ...) deh_warning("KartVoice %s: " str, strbuf_get(voicenames, voice->info.nameofs), __VA_ARGS__)
#define WARN0(str) deh_warning("KartVoice %s: " str, strbuf_get(voicenames, voice->info.nameofs))
// Extremely shitty and lifted almost entirely from r_skins.c
// Sets the sound effect value for a voice, given a valid skinsound value is passed.
static UINT8 reallygrossvoicesfxadder(INT32 skinsound, const char *value, kartvoice_t *voice)
{
sfxenum_t i;
char printval[6];
// 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;
strncpy(printval, value, 6);
// copy name of sounds that are remapped
// for this skin
for (i = 0; i < sfx_skinsoundslot0; i++)
{
if (!S_sfx[i].name)
continue;
if (!stricmp(S_sfx[i].name, value))
{
if (voice)
{
Sk_SetSkinVoiceValue(voice, skinsound, i);
return 0;
}
}
}
strlwr(printval);
WARN("'sfx_%s' does not exist", printval);
return 1;
}
void readkartvoice(MYFILE *f, kartvoice_t *voice)
{
char *s = Z_Malloc(MAXLINELEN, PU_STATIC, NULL);
char *word = s;
char *word2;
char *tmp;
INT32 i;
do if (myfgets(s, MAXLINELEN, f))
{
if (s[0] == '\n')
break;
// First remove trailing newline, if there is one
tmp = strchr(s, '\n');
if (tmp)
*tmp = '\0';
tmp = strchr(s, '#');
if (tmp)
*tmp = '\0';
if (s == tmp)
continue; // Skip comment lines, but don't break.
// Get the part before the " = "
tmp = strchr(s, '=');
if (tmp)
*(tmp-1) = '\0';
else
break;
strupr(word);
// Now get the part after
word2 = (tmp += 2);
strupr(word2);
if (fastcmp(word, "NAME"))
{
strlwr(word2);
// Catch any voices with similar names.
for (i = KSKVC_FIRSTFREESLOT; i < MAXSKINVOICES; i++)
{
if (strnicmp(word2, skinvoices[i].name, sizeof skinvoices[i].name) == 0)
{
// Name collision!
// Append the ID to differentiate... poorly.
snprintf(word2, sizeof voice->name, "%u_%s", voice->id, skinvoices[i].name);
}
}
strncpy(voice->name, word2, sizeof voice->name);
CONS_Printf("KartVoice %s: renamed to \"%s\"\n", strbuf_get(voicenames, voice->info.nameofs), voice->name);
}
else if (fastcmp(word, "SKIN"))
{
i = R_SkinAvailable(word2);
if (i == -1)
{
strlwr(word2);
WARN("skin '%s' does not exist or cannot be used", word2);
}
else
{
voice->parent = (UINT16)i;
}
}
else if (fastcmp(word, "GLOAT"))
{
reallygrossvoicesfxadder(SKSKPOWR, word2, voice);
}
else if (fastcmp(word, "WIN"))
{
reallygrossvoicesfxadder(SKSKWIN, word2, voice);
}
else if (fastcmp(word, "LOSE"))
{
reallygrossvoicesfxadder(SKSKLOSE, word2, voice);
}
else if (fastcmp(word, "SLOW") || fastcmp(word, "OVERTAKE") || fastcmp(word, "PASS"))
{
reallygrossvoicesfxadder(SKSKSLOW, word2, voice);
}
else if (fastcmp(word, "PAIN1") || fastcmp(word, "HURT1") || fastcmp(word, "DAMAGE1"))
{
reallygrossvoicesfxadder(SKSKPAN1, word2, voice);
}
else if (fastcmp(word, "PAIN2") || fastcmp(word, "HURT2") || fastcmp(word, "DAMAGE2"))
{
reallygrossvoicesfxadder(SKSKPAN2, word2, voice);
}
else if (fastcmp(word, "ATTACK1"))
{
reallygrossvoicesfxadder(SKSKATK1, word2, voice);
}
else if (fastcmp(word, "ATTACK2"))
{
reallygrossvoicesfxadder(SKSKATK2, word2, voice);
}
else if (fastcmp(word, "BOOST1"))
{
reallygrossvoicesfxadder(SKSKBST1, word2, voice);
}
else if (fastcmp(word, "BOOST2"))
{
reallygrossvoicesfxadder(SKSKBST2, word2, voice);
}
else if (fastcmp(word, "HITEM") || fastcmp(word, "HITCONFIRM"))
{
reallygrossvoicesfxadder(SKSKHITM, word2, voice);
}
else
WARN("unknown word '%s'", word);
}
while (!myfeof(f)); // finish when the line is empty
Z_Free(s);
}
#undef WARN
#undef WARN0
//
//
//
@ -4421,6 +4581,17 @@ useoddsfunc_f *get_useoddsfunc(const char *word)
return NULL;
}
kartvoicetype_e get_kartvoice(const char *word)
{ // Returns the value of KVOICE_ enumerations
kartvoicetype_e i;
if (fastncmp("KVOICE_",word,7))
word += 7; // take off the KVOICE_
i = DEH_FindVoice(word);
if (i == MAXVOICEFREESLOTS)
deh_warning("Couldn't find voice named 'KVOICE_%s'", word);
return i;
}
/// \todo Make ANY of this completely over-the-top math craziness obey the order of operations.
static fixed_t op_mul(fixed_t a, fixed_t b) { return a*b; }
static fixed_t op_div(fixed_t a, fixed_t b) { return a/b; }

View file

@ -65,6 +65,7 @@ menudrawer_f *get_menudrawer(const char *word);
skincolornum_t get_skincolor(const char *word);
kartitemtype_e get_kartitem(const char *word);
useoddsfunc_f *get_useoddsfunc(const char *word);
kartvoicetype_e get_kartvoice(const char *word);
void readwipes(MYFILE *f);
void readmaincfg(MYFILE *f);
@ -96,6 +97,7 @@ preciptype_t get_precip(const char *word);
void readweather(MYFILE *f, INT32 num);
void readkartitem(MYFILE *f, kartitem_t *item);
void readkartresult(MYFILE *f, kartresult_t *result);
void readkartvoice(MYFILE *f, kartvoice_t *voice);
#ifdef __cplusplus
} // extern "C"

View file

@ -41,6 +41,7 @@ strbuf_t *mobjnames;
strbuf_t *skincolornames;
strbuf_t *menunames;
strbuf_t *kartitemnames;
strbuf_t *voicenames;
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)
@ -68,6 +69,11 @@ const char *DEH_KartItemName(kartitemtype_e i)
return strbuf_get(kartitemnames, kartitems[i].info.nameofs);
}
const char *DEH_VoiceName(kartvoicetype_e i)
{
return strbuf_get(voicenames, skinvoices[i + KSKVC_FIRSTFREESLOT].info.nameofs);
}
mobjtype_t DEH_FindMobjtype(const char *word)
{
mobjtype_t i;
@ -133,6 +139,19 @@ kartitemtype_e DEH_FindKartItem(const char *word)
return MAXKARTITEMS;
}
kartvoicetype_e DEH_FindVoice(const char *word)
{
INT32 i;
UINT32 hash = HASH32(word, strlen(word));
for (i = 0; i < numfreeslotvoices; i++) {
if (hash != skinvoices[i + KSKVC_FIRSTFREESLOT].info.namehash)
continue;
if (fastcmp(word, DEH_VoiceName(i)))
return (kartvoicetype_e)i;
}
return MAXVOICEFREESLOTS;
}
struct flickytypes_s FLICKYTYPES[] = {
{"BLUEBIRD", MT_FLICKY_01}, // Flicky (Flicky)
{"RABBIT", MT_FLICKY_02}, // Pocky (1)

View file

@ -32,6 +32,7 @@ extern strbuf_t *mobjnames;
extern strbuf_t *skincolornames;
extern strbuf_t *menunames;
extern strbuf_t *kartitemnames;
extern strbuf_t *voicenames;
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);
@ -39,12 +40,14 @@ const char *DEH_StateName(statenum_t i);
const char *DEH_SkincolorName(skincolornum_t i);
const char *DEH_MenutypeName(menutype_t i);
const char *DEH_KartItemName(kartitemtype_e i);
const char *DEH_VoiceName(kartvoicetype_e i);
mobjtype_t DEH_FindMobjtype(const char *word);
statenum_t DEH_FindState(const char *word);
skincolornum_t DEH_FindSkincolor(const char *word);
menutype_t DEH_FindMenutype(const char *word);
kartitemtype_e DEH_FindKartItem(const char *word);
kartvoicetype_e DEH_FindVoice(const char *word);
struct flickytypes_s {
const char *name;

View file

@ -390,6 +390,20 @@ INT32 DEH_ReadFreeslot(const char *type, const char *word, INT32 *out)
DEH_Link(word, &kartitems[numkartitems].info, &kartitemnames);
K_RegisterItem(numkartitems);
*out = numkartitems++;
}
else if (fastcmp(type, "KVOICE"))
{
*out = DEH_FindVoice(word);
if (*out != MAXVOICEFREESLOTS)
return 0; // already allocated
if (numfreeslotvoices == MAXVOICEFREESLOTS) {
CONS_Alert(CONS_WARNING, "Ran out of free voice slots!\n");
return -1;
}
CONS_Printf("Voice KVOICE_%s allocated.\n",word);
DEH_Link(word, &skinvoices[numfreeslotvoices + KSKVC_FIRSTFREESLOT].info, &voicenames);
*out = numfreeslotvoices++;
return 0;
}
@ -787,6 +801,19 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile)
result = K_RegisterResult(word2, alternate);
readkartresult(f, result);
}
else if (fastncmp(word, "KARTVOICE", 9))
{
if (i == 0 && word2[0] != '0') // If word2 isn't a number
i = get_kartvoice(word2); // find a voice by name
if ((i + 1) >= 1 && i < numfreeslotvoices)
readkartvoice(f, &skinvoices[i + KSKVC_FIRSTFREESLOT]);
else
{
// zero-based, but let's start at 1
deh_warning("KartVoice number %d out of range (1 - %d)", i, numfreeslotvoices);
ignorelines(f);
}
}
else if (fastcmp(word, "WEATHER") || fastcmp(word, "PRECIP") || fastcmp(word, "PRECIPITATION"))
{
if (i == 0 && word2[0] != '0') // If word2 isn't a number

View file

@ -2703,6 +2703,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
UINT8 latestlap;
UINT16 skincolor;
INT32 skin;
kartvoicetype_e voice;
UINT32 availabilities;
tic_t jointime;
@ -2761,6 +2762,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;
@ -2951,6 +2953,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

@ -240,7 +240,7 @@ void P_ResetData(INT32 flags)
for (i = 0; i < MN_FIRSTFREESLOT; i++, name += strlen(name)+1)
DEH_Link(name, &menudefs[i].info, &menunames);
}
// kartitems
if (init)
{
@ -258,4 +258,19 @@ void P_ResetData(INT32 flags)
K_RegisterItem(i);
}
}
// voices
if (init)
{
for (i = KSKVC_FIRSTFREESLOT; i < MAXSKINVOICES; i++)
{
R_ClearVoice(i);
}
numfreeslotvoices = 0;
if (voicenames)
Z_Free(voicenames);
voicenames = strbuf_alloc();
}
}

View file

@ -12371,6 +12371,11 @@ void P_SpawnPlayer(INT32 playernum)
mobj->skin = &skins[p->skin];
P_SetupStateAnimation(mobj, mobj->state);
if (p->voice_id != skins[p->skin].voice->id)
{
mobj->voice = &skinvoices[p->voice_id];
}
mobj->health = 1;
p->playerstate = PST_LIVE;

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

View file

@ -173,7 +173,7 @@ UINT8 P_KartFrameToSprite2(skin_t *skin, UINT8 inframe, UINT8 *outframe)
return P_GetSkinSprite2(skin, spr2, NULL);
}
static void Sk_SetSkinVoiceValue(kartvoice_t *voice, INT32 skinsound, INT32 writesound)
void Sk_SetSkinVoiceValue(kartvoice_t *voice, INT32 skinsound, INT32 writesound)
{
INT32 usesound;
@ -298,6 +298,13 @@ sfxenum_t P_GetVoiceSFX(kartvoice_t *voice, INT32 skinsound)
return sfx_thok;
}
void R_ClearVoice(kartvoicetype_e voicetype)
{
kartvoice_t *voice = &skinvoices[voicetype];
memset(voice, 0, sizeof(kartvoice_t));
}
static void Sk_SetDefaultValue(skin_t *skin, kartvoice_t *skin_voice)
{
INT32 i;
@ -649,6 +656,8 @@ void SetPlayerVoice(INT32 playernum, const char *voicename)
if (P_MobjWasRemoved(player->mo))
{
// No object to give a voice to.
// Fall back to our skin.
player->voice_id = skins[player->skin].voice->id;
return;
}
@ -657,6 +666,8 @@ void SetPlayerVoice(INT32 playernum, const char *voicename)
if (!strncmp(voicename, myvoice->name, sizeof myvoice->name))
{
// Hey... this voice is the same as before!
// WAIT! Before we go, reset the player's voice ID!
player->voice_id = myvoice->id;
return;
}
@ -670,12 +681,13 @@ void SetPlayerVoice(INT32 playernum, const char *voicename)
{
// Found a voice.
player->mo->voice = &skinvoices[i + KSKVC_FIRSTFREESLOT];
player->voice_id = (kartvoicetype_e)(i + KSKVC_FIRSTFREESLOT);
return;
}
}
}
// Freeslotted voices don't exist or found nothing; check skin voices.
// Freeslotted voices don't exist, or we've found nothing; check skin voices.
for (i = 0; i < numskinvoices; i++)
{
// Whatever. Go, my code duplication!
@ -683,12 +695,14 @@ void SetPlayerVoice(INT32 playernum, const char *voicename)
{
// Found a voice.
player->mo->voice = &skinvoices[i];
player->voice_id = (kartvoicetype_e)i;
return;
}
}
// Couldn't find a voice. Default to the skin's.
player->mo->voice = NULL;
player->voice_id = skins[player->skin].voice->id;
}
// Same as SetPlayerSkin, but uses the skin #.
@ -774,6 +788,8 @@ void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum)
if (P_MobjWasRemoved(player->mo))
{
// No object to give a voice to.
// Fall back to our skin.
player->voice_id = skins[player->skin].voice->id;
return;
}
@ -782,6 +798,8 @@ void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum)
if (myvoice->id == voicenum)
{
// Hey... this voice is the same as before!
// WAIT! Before we go, reset the player's voice ID!
player->voice_id = myvoice->id;
return;
}
@ -795,6 +813,7 @@ void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum)
{
// Found a voice.
player->mo->voice = &skinvoices[i + KSKVC_FIRSTFREESLOT];
player->voice_id = (kartvoicetype_e)(i + KSKVC_FIRSTFREESLOT);
return;
}
}
@ -808,12 +827,14 @@ void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum)
{
// Found a voice.
player->mo->voice = &skinvoices[i];
player->voice_id = (kartvoicetype_e)i;
return;
}
}
// Couldn't find a voice. Default to the skin's.
player->mo->voice = NULL;
player->voice_id = skins[player->skin].voice->id;
}
//
@ -1355,6 +1376,7 @@ void R_AddSkins(UINT16 wadnum)
skinsorted[numskins] = numskins;
numskins++;
numskinvoices++;
}
SortSkins();
return;

View file

@ -45,6 +45,8 @@ typedef enum
MAXSKINVOICES
} kartvoicetype_e;
#define MAXVOICEFREESLOTS (MAXSKINVOICES - KSKVC_FIRSTFREESLOT)
// Player voice struct
// ## TODO:
// - Dub implementation (freeslotting, voice lookup, assigning voices on race start)
@ -53,6 +55,8 @@ struct kartvoice_t
{
UINT32 id;
dehinfo_t info;
char name[VOICENAMESIZE+1]; // INT16 descriptive name of the voice.
// UINT16 ID of the parent skin this voice belongs to.
@ -153,6 +157,9 @@ void R_AddSkins(UINT16 wadnum);
UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player);
UINT8 P_KartFrameToSprite2(skin_t *skin, UINT8 inframe, UINT8 *outframe);
void R_ClearVoice(kartvoicetype_e voicetype);
void Sk_SetSkinVoiceValue(kartvoice_t *voice, INT32 skinsound, INT32 writesound);
void SetPlayerVoice(INT32 playernum, const char *voicename);
void SetPlayerVoiceByNum(INT32 playernum, UINT16 voicenum);
sfxenum_t P_GetVoiceSFX(kartvoice_t *voice, INT32 skinsound);