diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 7721b3747..c4c57938f 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -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; diff --git a/src/d_netcmd.h b/src/d_netcmd.h index 7fca388e6..eee52d485 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 50ce06aa8..8722eb700 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..dfbf19e01 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -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; } diff --git a/src/deh_soc.h b/src/deh_soc.h index 143c21a3d..ed97e3338 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -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" diff --git a/src/deh_tables.c b/src/deh_tables.c index 633ce0c6f..cb90f9467 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; +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) diff --git a/src/deh_tables.h b/src/deh_tables.h index 5aba111c0..0db150418 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 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; diff --git a/src/dehacked.c b/src/dehacked.c index dd0fd670e..a8b0ef5c5 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -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 diff --git a/src/g_game.c b/src/g_game.c index a93f85bc9..5f865acae 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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; // diff --git a/src/info.c b/src/info.c index 28513667c..827fb8eb9 100644 --- a/src/info.c +++ b/src/info.c @@ -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(); + } } diff --git a/src/p_mobj.c b/src/p_mobj.c index b7be3dcfa..58cf2582c 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -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; diff --git a/src/p_saveg.c b/src/p_saveg.c index 0ed4fbff3..aeacc00e5 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); diff --git a/src/r_skins.c b/src/r_skins.c index 64c23d4ac..a4796e315 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -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; diff --git a/src/r_skins.h b/src/r_skins.h index 363ef8738..bde9eec23 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -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);