Anonymous | Login | Signup for a new account | 2025-07-26 09:48 UTC | ![]() |
My View | View Issues | Change Log | Roadmap | Zandronum Issue Support Ranking | Rules | My Account |
View Issue Details [ Jump to Notes ] | [ Issue History ] [ Print ] | ||||||||||||
ID | Project | Category | View Status | Date Submitted | Last Update | ||||||||
0004298 | Zandronum | [All Projects] Suggestion | public | 2024-05-30 04:38 | 2024-05-30 15:40 | ||||||||
Reporter | BarrelsOFun | ||||||||||||
Assigned To | |||||||||||||
Priority | high | Severity | feature | Reproducibility | N/A | ||||||||
Status | needs review | Resolution | open | ||||||||||
Platform | OS | OS Version | |||||||||||
Product Version | |||||||||||||
Target Version | Fixed in Version | ||||||||||||
Summary | 0004298: Expanded Skin Functionality (Patch) | ||||||||||||
Description | I've been working on and off for about a month and a half on a patch to increase the functionality of skins for both the skin creator and mod makers. With this patch, you can now define multiple classes for a skin, map different sprites to a skin to allow skins for classes that use more than one set of sprites, define your own color range for a skin, and grab information from a skin using GetSkinInfo (including custom parameters) new/modified values "name" checks for skins named "skin#" and marks those as 'duplicate' naming them their actual skin number. Duplicate names are allowed once per class considering every class has a skin named "Base". "scale" now supports X and Y scaling if you use two values. scale = 0.75, 1.25 "color" now uses V_GetColor and stores a hex value. So you can use color names, RGB, and hex. If defined, pressing backspace over 'Custom' colorset will set your color to this. color = red color = "255 0 0" color = FF0000 "gender" works similarly to color now, where pressing backspace over "Gender" will set your gender to the skin's defined gender. "sprites" a Sprite Array to map multiple sprites to a skin. Works with Crouch Sprites and Weapon Skins as well. (Weapon Skins can also have their own sprites array which can be interfaced with) sprites = [ frdm = crsh fdm2 = mass fdm3 = illp fdm4 = chex fdm5 = crte frdc = crac ] "displayname" is a skin equivalent to the class Displayname, using that in place of the CVAR value in menus to display long names while keeping CVAR names small and easy to type in the console. displayname = "Crash The Skin" "colorrange" overrides the class's color range with the skin's own. Useful for when you don't want certain colors to change for your skin. colorrange = 192, 207 "selectable" true by default. When set to false, the skin is only accessible through Weapon.PreferredSkin or SetPlayerSkin. "removable" false by default. When set to true, clearplayerskins mentioned below will always remove it. Though if a modder decides they can wipe all skins regardless. Custom Values for GetSkinInfo customparam = "Something" customarray = "Stuff", "More Stuff" custchararr = [ key1 = value1 key2 = value2 ] GetSkinInfo returns a string of the requested value of a skin's SKININFO parameter or "" if there is none. GetSkinInfo(int skinIndex, str skinParam) (Array Value is 0 by default) GetSkinInfo(int skinIndex, str skinParam, int ArrayValue) (If a param is a regular array) GetSkinInfo(int skinIndex, str skinParam, str ArrayValue) (If param is a uses a string key array) This should allow for greater flexibility for modders when it comes to skin functionality in their mods. The existing parameters are set in a specific way, while custom parameters are always what is written as the value, with some existing parameters having a secondary. For example, GetSkinInfo(#,"name") would return the name in the CVAR value and GetSkinInfo(#,"name",1) would return the name in the SKININFO should the CVAR value be replaced with 'skin#' This function will always return a string of the given value, so for number values, additional parsing will have to be done. GetPlayerSkin now returns the index instead of the name which can be interfaced with GetSkinInfo for more flexibility. There's also an addition to Player.CrouchSprite to allow Crouch sprites for states other than the Spawn state sprite. Player.CrouchSprite with one parameter: (Player.CrouchSprite "FRDC") will still use the Spawn state sprite as a crouch sprite. Though with two parameters: (Player.CrouchSprite "FDC2", "FDM2") will map the sprite of the first parameter to the sprite of the second parameter. Finally there's a 'clearplayerskins' command for KEYCONF with parameters. clearplayerskins [allskins] [class] - Default; Clear all 'removable' skins clearplayerskins - Remove all skins. clearplayerskins true - Uses class displayname; removes only removable skins from this class. clearplayerskins false classname - Removes all skins in this class clearplayerskins true classname | ||||||||||||
Additional Information | Provided pk3 has a test map, A few skulltag skins for the Marine class that have some modifications to showcase some new parameters and parameter tweaks. A class to showcase skins with the same name and duplicates within the same class. Another class to test Weapon Skins override. The showcase skin is "Crash" for the latter 2. "Crash" for Marine class is marked unselectable to showcase that, yet can still be chosen in the skin selector with the custom 'exception' parameter. | ||||||||||||
Attached Files | ![]() # HG changeset patch # User BarrelsOFun # Date 1717043595 25200 # Wed May 29 21:33:15 2024 -0700 # Node ID ffffffffffffffffffffffffffffffffffffffff # Parent 8755ab2487fa5054911b916c0d2f55275fa7ea3d diff -r 8755ab2487fa docs/zandronum-history.txt --- a/docs/zandronum-history.txt Fri May 24 11:09:02 2024 -0400 +++ b/docs/zandronum-history.txt Wed May 29 21:33:15 2024 -0700 @@ -15,11 +15,23 @@ 3.2 --- +*+ - Overhaul of the Skins system, improving skin customization. [Barrels O' Fun] +*+ - Added Skin Parameter : "sprites" You can define an array of sprites, which allows skins to properly be used in Classes that use multiple sprites, as well as override Weapon Skins if there's a match. Each sprite can also be individually scaled. [Barrels O' Fun] +*+ - Added ACS function : "GetSkinInfo" Allows you to grab any parameter value from a skin, including custom ones which can either be a single param, an array, or a charkey array. [Barrels O' Fun] *+ - Added the new AUTHINFO lump, allowing modders to define their own list of lumps to be authenticated. [Kaminsky] *+ - Revamped the scoreboard, and added the new SCORINFO lump that allows full customization of the scoreboard. [Kaminsky] *+ - Added support for custom vote types using the new VOTEINFO lump. [Dusk] *+ - Added support for voice chat. Audio is encoded and decoded using Opus, allowing it to be transmitted over the network with minimal bandwidth usage and decent quality. Any unwanted noise in the audio is also removed with RNNoise before it's sent to the server. [Kaminsky] *+ - Generalized the medal system and added the new MEDALDEF lump to define custom medals. Players can be awarded medals using the ACS and DECORATE functions: "GivePlayerMedal" and "A_GivePlayerMedal". [Kaminsky] ++ - Added Skin Parameter : "displayname" Used for menus similarly to a class's displayname. [Barrels O' Fun] ++ - Added Skin Parameter : "colorrange" Can be used to override the class's own colorrange. [Barrels O' Fun] ++ - Added Skin Parameter : "selectable" Prevents skin from being selected; Only accessible through SetPlayerSkin or Weapon.PreferredSkin. [Barrels O' Fun] ++ - Added Skin Parameter : "removable" Adds the skin to a whitelist of skins that can be removed using newly added KEYCONF command : "clearplayerskins" [Barrels O' Fun] ++ - Reworked Skin Paramater : "name" now checks for duplicates within the same class instead of all skins. Will prevent skins from intentionally being named "skin#" which is for duplicates. [Barrels O' Fun] ++ - Reworked Skin Parameters : "color" and "gender" are now used in the player menu, using backspace to select those options if defined. [Barrels O' Fun] ++ - Reworked Skin Parameter : "scale" now can scale X and Y individually using a second argument ("scale = 2" will still scale both x and y by 2, though "scale = 1, 2" will only modify the Y-scale.) [Barrels O' Fun] ++ - Player.CrouchSprite can now be replace states other than the Spawn State, using an optional second argument to replace said state: (Player.CrouchSprite "PL2C", "PLA2") [Barrels O' Fun] ++ - In the player menu, hitting backspace while Skin is selected, will automatically snap to the "Base" skin. [Barrels O' Fun] + - Added the +NOMORPHLIMITATIONS flag, allowing morphs to switch weapons, play sounds, and be affected by speed powerups. [geNia/Binary] + - Added CVars: "con_interpolate" and "con_speed" which interpolates and controls how fast the console moves. Based on featues from ZCC. [Kaminsky] + - Added ACS functions: "GetMapPosition", "GetEventResult", "GetActorSectorLocation", and "ChangeTeamScore". [Kaminsky] @@ -179,6 +191,9 @@ - - Fixed: the next level and countdown time didn't update on the scoreboard when the server used the "changemap" CCMD in the middle of an intermission. [Kaminsky] - - Fixed: Fraglimit changes wouldn't end the match properly if a player had more frags than the new limit (addresses 1137). [Ru5tK1ng] - - Fixed: Max bonus amounts for health and armor weren't being sync'd between map changes online (addresses 2706). [Ru5tK1ng] +! - "GetPlayerSkin" returns the skin index instead of the skin name now, which can be interfaced with GetSkinInfo. [Barrels O' Fun] +! - "SetPlayerSkin" Override Weapon Skin is now Override Skin Sounds, as Weapon Skins can now be overridden through the sprites array. [Barrels O' Fun] +! - In the player menu, when the selected class is "Random", the "Skin" option is hidden since you can't select anything other than base on "Random" anyways. ! - The result value of GAMEEVENT_MEDALS event scripts can now be used to determine whether or not the player receives the medal. [Kaminsky] ! - GAMEMODE flags are now validated after all GAMEMODE lumps have been parsed instead of after each one. The internal game mode name (e.g. "TeamLMS") is now printed with the error message instead of the actual name. [Kaminsky] ! - Added an extra check to ensure that game modes have a (short) name. [Kaminsky] diff -r 8755ab2487fa protocolspec/spec.players.txt --- a/protocolspec/spec.players.txt Fri May 24 11:09:02 2024 -0400 +++ b/protocolspec/spec.players.txt Wed May 29 21:33:15 2024 -0700 @@ -208,7 +208,7 @@ ExtendedCommand Player player Name skinName - bool overrideWeaponSkin + bool overrideSkinSounds EndCommand Command UpdatePlayerPing diff -r 8755ab2487fa src/actor.h --- a/src/actor.h Fri May 24 11:09:02 2024 -0400 +++ b/src/actor.h Wed May 29 21:33:15 2024 -0700 @@ -624,7 +624,7 @@ FBlockNode **PrevActor; // previous actor in this block FBlockNode *NextActor; // next actor in this block FBlockNode **PrevBlock; // previous block this actor is in - FBlockNode *NextBlock; // next block this actor is in + FBlockNode *NextBlock; // next block this actor is in static FBlockNode *Create (AActor *who, int x, int y); void Release (); @@ -1252,6 +1252,13 @@ // end of GZDoom specific additions size_t PropagateMark(); + + // [BOF] Last visible Skin for Player Actors. + int visibleSkin = -1; + + // [BOF] For Override Skin corpses when respawning quickly + FNameNoInit ACSSkin; + bool ACSSkinOverridesSkinSounds; }; class FActorIterator diff -r 8755ab2487fa src/bots.cpp --- a/src/bots.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/bots.cpp Wed May 29 21:33:15 2024 -0700 @@ -878,7 +878,7 @@ for ( ulIdx = 0; ulIdx < (ULONG)skins.Size(); ulIdx++ ) { - // If this bot isn't revealed, or isn't revealed by default, don't archive it. + // If this skin isn't revealed, or isn't revealed by default, don't archive it. if (( skins[ulIdx].bRevealed == false ) || ( skins[ulIdx].bRevealedByDefault )) continue; diff -r 8755ab2487fa src/c_dispatch.cpp --- a/src/c_dispatch.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/c_dispatch.cpp Wed May 29 21:33:15 2024 -0700 @@ -148,6 +148,7 @@ bool ParsingKeyConf, UnsafeExecutionContext; +int KeyConfLump; // [BOF] Getting namespace for clearplayerskins class UnsafeExecutionScope { @@ -225,7 +226,8 @@ "weaponsection", "setslot", "addplayerclass", - "clearplayerclasses" + "clearplayerclasses", + "clearplayerskins" }; // CODE -------------------------------------------------------------------- diff -r 8755ab2487fa src/c_dispatch.h --- a/src/c_dispatch.h Fri May 24 11:09:02 2024 -0400 +++ b/src/c_dispatch.h Wed May 29 21:33:15 2024 -0700 @@ -209,6 +209,7 @@ Button_VoiceRecord, // [AK] Added the "voicerecord" button. Button_SB_ScrollUp, Button_SB_ScrollDn; // [AK] Added "sb_scrollup" and "sb_scrolldn" buttons. extern bool ParsingKeyConf, UnsafeExecutionContext; +extern int KeyConfLump; // [BOF] Getting namespace for clearplayerskins void ResetButtonTriggers (); // Call ResetTriggers for all buttons void ResetButtonStates (); // Same as above, but also clear bDown diff -r 8755ab2487fa src/cl_demo.cpp --- a/src/cl_demo.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/cl_demo.cpp Wed May 29 21:33:15 2024 -0700 @@ -385,12 +385,16 @@ if ( players[consoleplayer].mo != NULL ) { if (players[consoleplayer].cls != NULL && - players[consoleplayer].mo->state->sprite == - GetDefaultByType (players[consoleplayer].cls)->SpawnState->sprite) - { // Only change the sprite if the player is using a standard one - players[consoleplayer].mo->sprite = skins[players[consoleplayer].userinfo.GetSkin()].sprite; - players[consoleplayer].mo->scaleX = skins[players[consoleplayer].userinfo.GetSkin()].ScaleX; - players[consoleplayer].mo->scaleY = skins[players[consoleplayer].userinfo.GetSkin()].ScaleY; + (players[consoleplayer].mo->state->sprite == + GetDefaultByType(players[consoleplayer].cls)->SpawnState->sprite || + skins[players[consoleplayer].userinfo.GetSkin()].sprites.CheckKey(players[consoleplayer].mo->state->sprite) ) ) + { + players[consoleplayer].mo->sprite = + skins[players[consoleplayer].userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[players[consoleplayer].mo->state->sprite].name) ? + skins[players[consoleplayer].userinfo.GetSkin()].sprites[*(DWORD*)sprites[players[consoleplayer].mo->state->sprite].name] : + skins[players[consoleplayer].userinfo.GetSkin()].sprite; + players[consoleplayer].mo->scaleX = skins[players[consoleplayer].userinfo.GetSkin()].ScaleX[0]; + players[consoleplayer].mo->scaleY = skins[players[consoleplayer].userinfo.GetSkin()].ScaleY[0]; } } } diff -r 8755ab2487fa src/cl_main.cpp --- a/src/cl_main.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/cl_main.cpp Wed May 29 21:33:15 2024 -0700 @@ -3698,7 +3698,10 @@ // [BB] There is no skin for the morphed class. if ( !isMorphed ) { - pActor->sprite = skins[lSkin].sprite; + pActor->sprite = + skins[lSkin].sprites.CheckKey(*(DWORD*)sprites[pPlayer->mo->state->sprite].name) ? + skins[lSkin].sprites[*(DWORD*)sprites[pPlayer->mo->state->sprite].name] : + skins[lSkin].sprite; } pPlayer->DesiredFOV = pPlayer->FOV = 90.f; @@ -4219,7 +4222,11 @@ if ( player->mo ) { - player->mo->sprite = skins[skin].sprite; + player->mo->sprite = + skins[skin].sprites.CheckKey(*(DWORD*)sprites[player->mo->sprite].name) ? + skins[skin].sprites[*(DWORD*)sprites[player->mo->sprite].name] : + skins[skin].sprite; + //player->mo->sprite = skins[skin].sprite; } } // Read in the player's handicap. @@ -4507,7 +4514,7 @@ void ServerCommands::SetPlayerACSSkin::Execute() { player->ACSSkin = skinName; - player->ACSSkinOverridesWeaponSkin = overrideWeaponSkin; + player->ACSSkinOverridesSkinSounds = overrideSkinSounds; } //***************************************************************************** diff -r 8755ab2487fa src/d_netinfo.cpp --- a/src/d_netinfo.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/d_netinfo.cpp Wed May 29 21:33:15 2024 -0700 @@ -614,9 +614,9 @@ // Some cvars don't copy their original value directly. // [BB] Zandronum still uses its own team code. //case NAME_Team: coninfo->TeamChanged(team); break; - case NAME_Skin: coninfo->SkinChanged(skin, players[consoleplayer].CurrentPlayerClass); break; + case NAME_PlayerClass: coninfo->PlayerClassChanged(playerclass); break; + case NAME_Skin: coninfo->SkinChanged(skin, D_PlayerClassToInt(playerclass)); break; // [BOF] CurrentPlayerClass isn't set at this point, use D_PlayerClassToInt instead. case NAME_Gender: coninfo->GenderChanged(gender); break; - case NAME_PlayerClass: coninfo->PlayerClassChanged(playerclass); break; // [BB] case NAME_RailColor: coninfo->RailColorChanged(railcolor); break; case NAME_Handicap: coninfo->HandicapChanged(handicap); break; @@ -662,9 +662,9 @@ // Some cvars have different types for their shadow copies. switch (cvarname.GetIndex()) { + case NAME_PlayerClass: type = CVAR_Int; break; case NAME_Skin: type = CVAR_Int; break; case NAME_Gender: type = CVAR_Int; break; - case NAME_PlayerClass: type = CVAR_Int; break; default: type = cvar->GetRealType(); break; } // [TP] Also respect CVAR_UNSYNCED_USERINFO @@ -691,7 +691,9 @@ { int skinnum = R_FindSkin(skinname, playerclass); *static_cast<FIntCVar *>((*this)[NAME_Skin]) = skinnum; - return skinnum; + //return skins[skinnum].bRevealedByDefault ? skinnum : playerclass; // [BOF] Don't return skins that are selectable. + Printf("%s, %i = %s \n", skinname, playerclass, skins[skinnum].name); + return skins[skinnum].bRevealedByDefault ? skinnum : playerclass; // [BOF] Don't return skins that are selectable. } int userinfo_t::SkinNumChanged(int skinnum) @@ -710,6 +712,7 @@ int userinfo_t::PlayerClassChanged(const char *classname) { int classnum = D_PlayerClassToInt(classname); + Printf("classname bitch %s \n", classname); *static_cast<FIntCVar *>((*this)[NAME_PlayerClass]) = classnum; return classnum; } @@ -1427,10 +1430,15 @@ { if (players[pnum].cls != NULL && !(players[pnum].mo->flags4 & MF4_NOSKIN) && - players[pnum].mo->state->sprite == - GetDefaultByType (players[pnum].cls)->SpawnState->sprite) - { // Only change the sprite if the player is using a standard one - players[pnum].mo->sprite = skins[info->GetSkin()].sprite; + (players[pnum].mo->state->sprite == + GetDefaultByType (players[pnum].cls)->SpawnState->sprite || + skins[info->GetSkin()].sprites.CheckKey(*(DWORD*)sprites[players[pnum].mo->state->sprite].name) ) ) + { + players[pnum].mo->sprite = + skins[info->GetSkin()].sprites.CheckKey(*(DWORD*)sprites[players[pnum].mo->state->sprite].name) ? + skins[info->GetSkin()].sprites[*(DWORD*)sprites[players[pnum].mo->state->sprite].name] : + skins[info->GetSkin()].sprite; + //players[pnum].mo->sprite = skins[info->GetSkin()].sprite; } } // Rebuild translation in case the new skin uses a different range diff -r 8755ab2487fa src/d_player.h --- a/src/d_player.h Fri May 24 11:09:02 2024 -0400 +++ b/src/d_player.h Wed May 29 21:33:15 2024 -0700 @@ -142,7 +142,7 @@ void BeginPlay (); void Die (AActor *source, AActor *inflictor, int dmgflags); - int crouchsprite; + //int crouchsprite; // [BOF] no longer used, Crouch Sprites are now stored in the class instead to allow for multiple. int MaxHealth; int MugShotMaxHealth; int RunHealth; @@ -317,7 +317,7 @@ FPlayerClass (const FPlayerClass &other); ~FPlayerClass (); - bool CheckSkin (int skin); + bool CheckSkin (int skin, bool override = false); const PClass *Type; DWORD Flags; @@ -797,8 +797,11 @@ // [geNia] Force override the player skin. This can only be set from ACS. FNameNoInit ACSSkin; - // [geNia] Should the skin set from ACS also override a weapon's preferred skin? - bool ACSSkinOverridesWeaponSkin; + // [BOF] Should the skin set from ACS also override skin's sounds? + bool ACSSkinOverridesSkinSounds; + + // [BOF] Visible Skin for rebuilding Translations + int VisibleSkin; // [Spleen] Store old information about the player for unlagged support // [AK] Converted the position members into TVector3 objects. @@ -875,7 +878,9 @@ void PLAYER_AwardDamagePointsForAllPlayers( void ); void PLAYER_SetWeapon( player_t *pPlayer, AWeapon *pWeapon, bool bClearWeaponForClientOnServer = false ); void PLAYER_ClearWeapon( player_t *pPlayer ); +int PLAYER_GetWeaponSkin( player_t *player ); int PLAYER_GetOverrideSkin( player_t *player ); +int PLAYER_GetVisibleSkin( player_t *player ); bool PLAYER_ShouldForceBaseSkin( player_t *player ); void PLAYER_ApplySkinScaleToBody( player_t *player, AActor *body, AWeapon *weapon ); void PLAYER_SetLivesLeft( player_t *pPlayer, ULONG ulLivesLeft ); diff -r 8755ab2487fa src/g_game.cpp --- a/src/g_game.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/g_game.cpp Wed May 29 21:33:15 2024 -0700 @@ -1355,6 +1355,8 @@ stricmp (cmd, "spyprev") && stricmp (cmd, "chase") && stricmp (cmd, "+showscores") && + // [BC] + stricmp (cmd, "+showmedals") && stricmp (cmd, "bumpgamma") && stricmp (cmd, "screenshot"))) { @@ -2210,7 +2212,7 @@ ulWins = p->ulWins; ulTime = p->ulTime; ACSSkin = p->ACSSkin; - ACSSkinOverridesWeaponSkin = p->ACSSkinOverridesWeaponSkin; + ACSSkinOverridesWeaponSkin = p->ACSSkinOverridesSkinSounds; timefreezer = p->timefreezer; StartingWeaponName = p->StartingWeaponName; @@ -2272,7 +2274,7 @@ p->ulWins = ulWins; p->ulTime = ulTime; p->ACSSkin = ACSSkin; - p->ACSSkinOverridesWeaponSkin = ACSSkinOverridesWeaponSkin; + p->ACSSkinOverridesSkinSounds = ACSSkinOverridesWeaponSkin; // [BB] Players who were able to move while a APowerTimeFreezer is active, // should also be able to do so after being reborn. p->timefreezer = timefreezer; diff -r 8755ab2487fa src/info.h --- a/src/info.h Fri May 24 11:09:02 2024 -0400 +++ b/src/info.h Wed May 29 21:33:15 2024 -0700 @@ -207,7 +207,6 @@ typedef TMap<int, FPlayerColorSet> FPlayerColorSetMap; - struct DamageTypeDefinition { public: @@ -273,6 +272,7 @@ PainChanceList *PainChances; PainFlashList *PainFlashes; FPlayerColorSetMap *ColorSets; + TMap<int, int> CrouchSprites; TArray<const PClass *> VisibleToPlayerClass; TArray<const PClass *> RestrictedToPlayerClass; TArray<const PClass *> ForbiddenToPlayerClass; diff -r 8755ab2487fa src/intermission/intermission.cpp --- a/src/intermission/intermission.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/intermission/intermission.cpp Wed May 29 21:33:15 2024 -0700 @@ -604,7 +604,10 @@ { if (PlayerClasses[i].Type == mClass) { - castsprite = skins[players[consoleplayer].userinfo.GetSkin()].sprite; + castsprite = + skins[players[consoleplayer].userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[castsprite].name) ? + skins[players[consoleplayer].userinfo.GetSkin()].sprites[*(DWORD*)sprites[castsprite].name] : + skins[players[consoleplayer].userinfo.GetSkin()].sprite; } } } diff -r 8755ab2487fa src/keysections.cpp --- a/src/keysections.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/keysections.cpp Wed May 29 21:33:15 2024 -0700 @@ -161,8 +161,9 @@ ParsingKeyConf = true; KeySections.Clear(); KeyConfWeapons.Clear(); + KeyConfLump = 0; - while ((lump = Wads.FindLump ("KEYCONF", &lastlump)) != -1) + while ((KeyConfLump = lump = Wads.FindLump ("KEYCONF", &lastlump)) != -1) { FMemLump data = Wads.ReadLump (lump); const char *eof = (char *)data.GetMem() + Wads.LumpLength (lump); diff -r 8755ab2487fa src/menu/menu.h --- a/src/menu/menu.h Fri May 24 11:09:02 2024 -0400 +++ b/src/menu/menu.h Wed May 29 21:33:15 2024 -0700 @@ -269,6 +269,7 @@ public: bool mEnabled; + int mRandomClass; // [BOF] Move here so it can be accessed by playermenu. FListMenuItem(int xpos = 0, int ypos = 0, FName action = NAME_None) { @@ -346,7 +347,6 @@ BYTE mMode; // 0: automatic (used by class selection), 1: manual (used by player setup) BYTE mTranslate; int mSkin; - int mRandomClass; int mRandomTimer; int mClassNum; @@ -357,6 +357,7 @@ public: + enum { PDF_ROTATION = 0x10001, diff -r 8755ab2487fa src/menu/playerdisplay.cpp --- a/src/menu/playerdisplay.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/menu/playerdisplay.cpp Wed May 29 21:33:15 2024 -0700 @@ -391,7 +391,6 @@ } mPlayerTics = mPlayerState != NULL ? mPlayerState->GetTics() : -1; mRandomTimer = 6; - // Since the newly displayed class may used a different translation // range than the old one, we need to update the translation, too. UpdateTranslation(); @@ -412,7 +411,8 @@ if (mPlayerClass != NULL) { - PlayerSkin = R_FindSkin (skins[PlayerSkin].name, int(mPlayerClass - &PlayerClasses[0])); + // [BOF] Use visible class' base skin translation when random is selected. + PlayerSkin = (mClassNum < 0 ? mRandomClass : R_FindSkin (skins[mSkin].name, int(mPlayerClass - &PlayerClasses[0]))); R_GetPlayerTranslation(PlayerColor, P_GetPlayerColorSet(mPlayerClass->Type->TypeName, PlayerColorset), &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); @@ -568,7 +568,7 @@ if (mPlayerState != NULL) { - if (mSkin == 0) + if (mSkin == 0 || 0 == stricmp(skins[mSkin].name, "base")) // Base skins use state sprites always. { sprframe = &SpriteFrames[sprites[mPlayerState->sprite].spriteframes + mPlayerState->GetFrame()]; scaleX = GetDefaultByType(mPlayerClass->Type)->scaleX; @@ -576,9 +576,17 @@ } else { - sprframe = &SpriteFrames[sprites[skins[mSkin].sprite].spriteframes + mPlayerState->GetFrame()]; - scaleX = skins[mSkin].ScaleX; - scaleY = skins[mSkin].ScaleY; + sprframe = &SpriteFrames[sprites[ + (skins[mSkin].sprites.CheckKey(*(DWORD*)sprites[mPlayerState->sprite].name) ? + skins[mSkin].sprites[*(DWORD*)sprites[mPlayerState->sprite].name] : + skins[mSkin].sprite)].spriteframes + mPlayerState->GetFrame()]; + + // [BOF] Per Sprite Scaling + scaleX = (skins[mSkin].ScaleX.CheckKey(*(DWORD*)sprites[mPlayerState->sprite].name) ? + skins[mSkin].ScaleX[*(DWORD*)sprites[mPlayerState->sprite].name] : skins[mSkin].ScaleX[0]); + scaleY = (skins[mSkin].ScaleY.CheckKey(*(DWORD*)sprites[mPlayerState->sprite].name) ? + skins[mSkin].ScaleY[*(DWORD*)sprites[mPlayerState->sprite].name] : skins[mSkin].ScaleY[0]); + } } diff -r 8755ab2487fa src/menu/playermenu.cpp --- a/src/menu/playermenu.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/menu/playermenu.cpp Wed May 29 21:33:15 2024 -0700 @@ -52,6 +52,7 @@ EXTERN_CVAR (String, playerclass) EXTERN_CVAR (String, name) +EXTERN_CVAR (String, skin) // [BB] //EXTERN_CVAR (Int, team) EXTERN_CVAR (Float, autoaim) @@ -333,6 +334,10 @@ if (++mSelection >= (int)mSelections.Size()) mSelection = 0; return true; } + else if (mkey == MKEY_Clear) + { + return true; + } } return (mkey == MKEY_Enter); // needs to eat enter keys so that Activate won't get called } @@ -531,6 +536,7 @@ TArray<int> PlayerColorSets; TArray<int> PlayerSkins; int mRotation; + int mRandomClass; void PickPlayerClass (); void UpdateColorsets(); @@ -539,7 +545,7 @@ void SendNewColor (int red, int green, int blue); void PlayerNameChanged(FListMenuItem *li); - void ColorSetChanged (FListMenuItem *li); + void ColorSetChanged (FListMenuItem *li, int mkey); void ClassChanged (FListMenuItem *li); void AutoaimChanged (FListMenuItem *li); void SkinChanged (FListMenuItem *li); @@ -734,9 +740,17 @@ int PlayerSkin = players[consoleplayer].userinfo.GetSkin(); int PlayerColorset = players[consoleplayer].userinfo.GetColorSet(); + if (PlayerClassIndex < 0) + { + FListMenuItem *li = GetItem(NAME_Playerdisplay); + mRandomClass = li != NULL ? li->mRandomClass : 0; + PlayerSkin = mRandomClass; + } + if (PlayerClass != NULL) { - PlayerSkin = R_FindSkin (skins[PlayerSkin].name, int(PlayerClass - &PlayerClasses[0])); + // [BOF] Use visible class' base skin translation when random is selected. + PlayerSkin = (PlayerClassIndex < 0 ? mRandomClass : R_FindSkin(skin, int(PlayerClass - &PlayerClasses[0]))); R_GetPlayerTranslation(PlayerColor, P_GetPlayerColorSet(PlayerClass->Type->TypeName, PlayerColorset), &skins[PlayerSkin], translationtables[TRANSLATION_Players][MAXPLAYERS]); @@ -752,6 +766,7 @@ void DPlayerMenu::PickPlayerClass() { + /* // What's the point of this? Aren't we supposed to edit the // userinfo? @@ -765,17 +780,15 @@ int pclass = 0; // [GRB] Pick a class from player class list if (PlayerClasses.Size () > 1) - { pclass = players[consoleplayer].userinfo.GetPlayerClassNum(); - if (pclass < 0) - { - pclass = (MenuTime>>7) % PlayerClasses.Size (); - } - } PlayerClassIndex = pclass; } - PlayerClass = &PlayerClasses[PlayerClassIndex]; + // [BOF] Base is the only option on 'Random', so just hide the skin item instead. + FListMenuItem* skinmenu = GetItem(NAME_Skin); + if (skinmenu != NULL) skinmenu->Enable(PlayerClassIndex >= 0); + + PlayerClass = &PlayerClasses[PlayerClassIndex < 0 ? 0 : PlayerClassIndex]; UpdateTranslation(); } @@ -838,44 +851,48 @@ void DPlayerMenu::UpdateSkins() { int sel = 0; - int skin; + // [BOF] When switching through classes, use the Skin CVAR if there's a match. + int skinnum; + int pclass = players[consoleplayer].userinfo.GetPlayerClassNum(); FListMenuItem *li = GetItem(NAME_Skin); if (li != NULL) { - if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || - players[consoleplayer].userinfo.GetPlayerClassNum() == -1) + if (GetDefaultByType (PlayerClass->Type)->flags4 & MF4_NOSKIN || pclass == -1) { li->SetString(0, "Base"); li->SetValue(0, 0); - skin = 0; + skinnum = 0; } else { PlayerSkins.Clear(); - // [BB] numskins -> skins.Size() + // [BB] numskins -> skins.Size() for(int i=0;i<(int)skins.Size(); i++) { if (PlayerClass->CheckSkin(i)) { // [BB] Support for hidden skins. - if ( skins[i].bRevealed == false ) + if (skins[i].bRevealed == false) + if (0 == stricmp(skin, skins[i].name)) // [BOF] If you're currently a skin that is hidden + skins[i].bRevealed == true; //from a previous session, reveal it and carry on. + else continue; + // [BOF] Unselectable Skins don't appear. + if (skins[i].bRevealedByDefault == false) continue; int j = PlayerSkins.Push(i); - li->SetString(j, skins[i].name); - if (players[consoleplayer].userinfo.GetSkin() == i) - { + li->SetString(j, skins[i].displayname); + if (R_FindSkin(skin, pclass) == i) sel = j; - } } } li->SetValue(0, sel); - skin = PlayerSkins[sel]; + skinnum = PlayerSkins[sel]; } li = GetItem(NAME_Playerdisplay); if (li != NULL) { - li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, skin); + li->SetValue(FListMenuItemPlayerDisplay::PDF_SKIN, skinnum); } } UpdateTranslation(); @@ -916,10 +933,10 @@ // //============================================================================= -void DPlayerMenu::ColorSetChanged (FListMenuItem *li) +void DPlayerMenu::ColorSetChanged (FListMenuItem *li, int mkey) { int sel; - + if (li->GetValue(0, &sel)) { int mycolorset = -1; @@ -931,10 +948,33 @@ FListMenuItem *blue = GetItem(NAME_Blue); // disable the sliders if a valid colorset is selected + if (red != NULL) red->Enable(mycolorset == -1); if (green != NULL) green->Enable(mycolorset == -1); if (blue != NULL) blue->Enable(mycolorset == -1); + // [BOF] Clear key on 'Custom' sets color to default skin color if it exists. + int skinnum = (PlayerClassIndex < 0 ? 0 : R_FindSkin(skin, PlayerClassIndex)); + if (mkey == MKEY_Clear && mycolorset == -1 && skins[skinnum].szColor) + { + S_Sound(CHAN_VOICE | CHAN_UI, "menu/clear", snd_menuvolume, ATTN_NONE); + players[consoleplayer].userinfo.ColorChanged(skins[skinnum].szColor); + uint32 color = players[consoleplayer].userinfo.GetColor(); + cvar_set("color", skins[skinnum].param["color"].list[0]); + if (red != NULL) + { + red->SetValue(0, RPART(color)); + } + if (green != NULL) + { + green->SetValue(0, GPART(color)); + } + if (blue != NULL) + { + blue->SetValue(0, BPART(color)); + } + } + char command[24]; players[consoleplayer].userinfo.ColorSetChanged(mycolorset); mysnprintf(command, countof(command), "colorset %d", mycolorset); @@ -962,8 +1002,8 @@ { players[consoleplayer].userinfo.PlayerClassNumChanged(gameinfo.norandomplayerclass ? sel : sel-1); PickPlayerClass(); - - cvar_set ("playerclass", sel == 0 && !gameinfo.norandomplayerclass ? "Random" : PlayerClass->Type->Meta.GetMetaString (APMETA_DisplayName)); + + cvar_set ("playerclass", sel == 0 && !gameinfo.norandomplayerclass ? "Random" : PlayerClass->Type->Meta.GetMetaString (APMETA_DisplayName)); UpdateSkins(); UpdateColorsets(); @@ -997,8 +1037,8 @@ { sel = PlayerSkins[sel]; players[consoleplayer].userinfo.SkinNumChanged(sel); + cvar_set ("skin", skins[sel].name); UpdateTranslation(); - cvar_set ("skin", skins[sel].name); li = GetItem(NAME_Playerdisplay); if (li != NULL) @@ -1062,7 +1102,7 @@ break; case NAME_Color: - ColorSetChanged(li); + ColorSetChanged(li, mkey); break; case NAME_Red: @@ -1094,14 +1134,29 @@ break; case NAME_Skin: + if (mkey == MKEY_Clear) + { // [BOF] With the other default options, may as well add a shortcut to get back to Base skin. + S_Sound(CHAN_VOICE | CHAN_UI, "menu/clear", snd_menuvolume, ATTN_NONE); + li->SetValue(0, 0); + } SkinChanged(li); break; case NAME_Gender: if (li->GetValue(0, &v)) { + // [BOF] If skin has gender defined, you can use Clear to set your gender to the skins. + int skinnum = (PlayerClassIndex < 0 ? 0 : R_FindSkin(skin, PlayerClassIndex)); + if (mkey == MKEY_Clear && skins[skinnum].gender) + { + + S_Sound(CHAN_VOICE | CHAN_UI, "menu/clear", snd_menuvolume, ATTN_NONE); + v = skins[skinnum].gender; + li->SetValue(0, v); + } cvar_set ("gender", v==0? "male" : v==1? "female" : "other"); } + break; case NAME_Autoaim: @@ -1187,7 +1242,6 @@ void DPlayerMenu::Ticker () { - Super::Ticker(); } diff -r 8755ab2487fa src/p_acs.cpp --- a/src/p_acs.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/p_acs.cpp Wed May 29 21:33:15 2024 -0700 @@ -5470,6 +5470,7 @@ ACSF_GetPlayerCountry, ACSF_SetNextMapPosition, ACSF_GivePlayerMedal, + ACSF_GetSkinInfo, // ZDaemon ACSF_GetTeamScore = 19620, // (int team) @@ -8545,24 +8546,34 @@ case ACSF_SetPlayerSkin: { + const unsigned int playerIndex = args[0]; if ( PLAYER_IsValidPlayer( playerIndex )) { const char *skinName = FBehavior::StaticLookupString( args[1] ); - const bool overrideWeaponPreferredSkin = argCount > 2 ? !!args[2] : false; + const bool overrideSounds = argCount > 2 ? !!args[2] : false; // [AK] If an empty string is used, then it should remove the skin. - if ( strlen( skinName ) > 0 ) + if ( strlen( skinName ) > 0 && + (!stricmp(skinName, "Base") || // [BOF] Make sure Skin is part of the class. + R_FindSkin(skinName, players[playerIndex].CurrentPlayerClass) != players[playerIndex].CurrentPlayerClass), true) + { players[playerIndex].ACSSkin = skinName; + players[playerIndex].ACSSkinOverridesSkinSounds = overrideSounds; + } else + { players[playerIndex].ACSSkin = NAME_None; - - players[playerIndex].ACSSkinOverridesWeaponSkin = overrideWeaponPreferredSkin; + players[playerIndex].ACSSkinOverridesSkinSounds = false; + } + if ( NETWORK_GetState( ) == NETSTATE_SERVER ) SERVERCOMMANDS_SetPlayerACSSkin( playerIndex ); + R_BuildPlayerTranslation(playerIndex); + return 1; } @@ -8609,34 +8620,50 @@ } if (( skinName != nullptr ) && ( strlen( skinName ) > 0 )) - skinIndex = R_FindSkin( skinName, player->CurrentPlayerClass ); + skinIndex = R_FindSkin( skinName, player->CurrentPlayerClass, true); // [AK] If the skin doesn't exist, return an empty string. if (( skinIndex == player->CurrentPlayerClass ) && (( skinName == nullptr ) || ( stricmp( skinName, "Base" ) != 0 ))) - return GlobalACSStrings.AddString( "" ); + return -1; } // [AK] ...or if we want to know the skin that's visible using without any // guess and check, then use their overridden skin (i.e. weapon preferred skin // or from ACS) first if available, and their personal skin last. else if ( type == PLAYERSKIN_VISIBLE ) - { - const int overrideSkin = PLAYER_GetOverrideSkin( player ); - - if ( overrideSkin != -1 ) - skinIndex = overrideSkin; - else if ( PLAYER_ShouldForceBaseSkin( player ) == false ) - skinIndex = player->userinfo.GetSkin( ); - } - - // [AK] Return the name of their skin if they're using one, or "Base" if not. - if ( skinIndex != player->CurrentPlayerClass ) - return GlobalACSStrings.AddString( skins[skinIndex].name ); - else - return GlobalACSStrings.AddString( "Base" ); + return PLAYER_GetVisibleSkin(player); + + return skinIndex; //[BOF] Return skin index number instead of name. Interface with GetSkinInfo below. } // [AK] Return an empty string for invalid players instead. - return GlobalACSStrings.AddString( "" ); + return -1; + } + + case ACSF_GetSkinInfo: // [BOF] Get the parameter of a skin through its index as a string. + { + + int skinIndex = args[0]; + if (skinIndex < 0 || skinIndex > skins.Size()-1) return GlobalACSStrings.AddString(""); + + const char* paramIndex = FBehavior::StaticLookupString(args[1]); + int keyValue = argCount >= 3 ? args[2] : 0; + char* charKeyValue = argCount >= 3 ? FBehavior::StaticLookupString(args[2]) : ""; + + if (skins[skinIndex].param.CheckKey(paramIndex)) + { + + if (skins[skinIndex].param[paramIndex].charlist.CountUsed() && + skins[skinIndex].param[paramIndex].charlist.CheckKey(charKeyValue)) + { + return GlobalACSStrings.AddString(skins[skinIndex].param[paramIndex].charlist[charKeyValue]); + } + else if (skins[skinIndex].param[paramIndex].list.Size() && + (keyValue < skins[skinIndex].param[paramIndex].list.Size())) + { + return GlobalACSStrings.AddString(skins[skinIndex].param[paramIndex].list[keyValue]); + } + } + return GlobalACSStrings.AddString(""); } case ACSF_GetPlayerCountry: @@ -8687,12 +8714,13 @@ case ACSF_GivePlayerMedal: { // [AK] Don't let the clients give medals to players. - if ( NETWORK_InClientMode( )) + if (NETWORK_InClientMode()) return 0; return MEDAL_GiveMedal( args[0], FBehavior::StaticLookupString( args[1] ), !!args[2] ); } + case ACSF_GetActorFloorTexture: { auto a = SingleActorFromTID(args[0], activator); diff -r 8755ab2487fa src/p_interaction.cpp --- a/src/p_interaction.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/p_interaction.cpp Wed May 29 21:33:15 2024 -0700 @@ -3205,8 +3205,8 @@ //***************************************************************************** // -int PLAYER_GetOverrideSkin( player_t *player ) -{ +int PLAYER_GetWeaponSkin( player_t *player ) +{ // [BOF] Split ACS and Weapon Skins. int skin = -1; if ( player != nullptr ) @@ -3214,31 +3214,15 @@ int overrideSkin = player->CurrentPlayerClass; const char *skinName = nullptr; - // [AK] Check if the player's skin was overridden from ACS. - if ( player->ACSSkin != NAME_None ) + if (( skin == -1 ) || ( player->ACSSkinOverridesSkinSounds == false )) { - skinName = player->ACSSkin; - overrideSkin = R_FindSkin( skinName, player->CurrentPlayerClass ); - - // [AK] Make sure that the overridden skin actually exists. + skinName = player->ReadyWeapon->PreferredSkin; + overrideSkin = R_FindSkin( skinName, player->CurrentPlayerClass, true); + + // [AK] Check if the weapon's PreferredSkin actually exists. if (( overrideSkin != player->CurrentPlayerClass ) || ( stricmp( skinName, "Base" ) == 0 )) skin = overrideSkin; } - - // [AK] Next, check if the player's current weapon has its own preferred - // skin. Only apply this skin if the skin from ACS doesn't override it. - if (( player->ReadyWeapon != nullptr ) && ( player->ReadyWeapon->PreferredSkin != NAME_None )) - { - if (( skin == -1 ) || ( player->ACSSkinOverridesWeaponSkin == false )) - { - skinName = player->ReadyWeapon->PreferredSkin; - overrideSkin = R_FindSkin( skinName, player->CurrentPlayerClass ); - - // [AK] Check if the weapon's PreferredSkin actually exists. - if (( overrideSkin != player->CurrentPlayerClass ) || ( stricmp( skinName, "Base" ) == 0 )) - skin = overrideSkin; - } - } } return skin; @@ -3246,6 +3230,65 @@ //***************************************************************************** // +int PLAYER_GetOverrideSkin(player_t* player) +{ + int skin = -1; + if (player != nullptr) + { + int overrideSkin = player->CurrentPlayerClass; + const char* skinName = nullptr; + + // [AK] Check if the player's skin was overridden from ACS. + if (player->ACSSkin != NAME_None) + { + skinName = player->ACSSkin; + overrideSkin = R_FindSkin(skinName, player->CurrentPlayerClass, true); + + // [AK] Make sure that the overridden skin actually exists. + if ((overrideSkin != player->CurrentPlayerClass) || (stricmp(skinName, "Base") == 0)) + skin = overrideSkin; + } + } + + return skin; +} + +//***************************************************************************** +// +int PLAYER_GetVisibleSkin(player_t* player) +{ + if (player == nullptr || player->mo == nullptr) return -1; + + AActor* actor = player->mo; + + const int weaponSkin = PLAYER_GetWeaponSkin(player); + const int overrideSkin = PLAYER_GetOverrideSkin(player); + int skinIndex = PLAYER_ShouldForceBaseSkin(player) ? player->CurrentPlayerClass : + player->userinfo.GetSkin(); + + if (weaponSkin != -1) + { + if (overrideSkin != -1) + skinIndex = overrideSkin; + + + int spritenum = + skins[weaponSkin].sprites.CheckKey(*(DWORD*)sprites[actor->sprite].name) ? + skins[weaponSkin].sprites[*(DWORD*)sprites[actor->sprite].name] : + skins[weaponSkin].sprite; + // [BOF] If Player's Current skin doesn't have a corresponding sprite then you can override. + if (!skins[skinIndex].sprites.CheckKey(*(DWORD*)sprites[spritenum].name)) + skinIndex = weaponSkin; + } + else if (overrideSkin != -1) + skinIndex = overrideSkin; + + return skinIndex; + +} + +//***************************************************************************** +// bool PLAYER_ShouldForceBaseSkin( player_t *player ) { if ( player == nullptr ) @@ -3271,58 +3314,64 @@ // void PLAYER_ApplySkinScaleToBody( player_t *player, AActor *body, AWeapon *weapon ) { - bool usingOverrideSkin = false; - int skinIdx = 0; if (( player == nullptr ) || ( body == nullptr )) return; - // [AK] Check if the player's skin was overridden from ACS and actually exists. - if ( player->ACSSkin != NAME_None ) - { - const int acsSkin = R_FindSkin( player->ACSSkin, player->CurrentPlayerClass ); - - if ( acsSkin != player->CurrentPlayerClass ) - { - skinIdx = acsSkin; - usingOverrideSkin = true; - } - } - - // [AK] Next, check if the weapon's PreferredSkin actually exists. Only apply - // this skin if the skin from ACS doesn't override it. - if (( weapon ) && ( weapon->PreferredSkin != NAME_None )) + const int weaponSkin = PLAYER_GetWeaponSkin(player); + const int overrideSkin = PLAYER_GetOverrideSkin(player); + int skinIdx = player->userinfo.GetSkin(); + int weapSprite = -1; + int spritenum = -1; + + // [BOF] Split up overrideSkin for ACS and Weapon Skins. + if ((weaponSkin != -1) && (weaponSkin != skinIdx) && (weaponSkin != overrideSkin)) { - if (( usingOverrideSkin == false ) || ( player->ACSSkinOverridesWeaponSkin == false )) - { - const int weaponSkin = R_FindSkin( weapon->PreferredSkin, player->CurrentPlayerClass ); - - if ( weaponSkin != player->CurrentPlayerClass ) - { - skinIdx = weaponSkin; - usingOverrideSkin = true; - } - } + //Printf("Weapon skin \n"); + if ((overrideSkin != -1) && (overrideSkin != skinIdx)) + skinIdx = overrideSkin; + + weapSprite = spritenum = + skins[weaponSkin].sprites.CheckKey(*(DWORD*)sprites[body->state->sprite].name) ? + skins[weaponSkin].sprites[*(DWORD*)sprites[body->state->sprite].name] : + skins[weaponSkin].sprite; + //Printf("Override weapon %s\n", sprites[spritenum].name); + + if (skins[skinIdx].sprites.CheckKey(*(DWORD*)sprites[body->state->sprite].name)) + spritenum = skins[skinIdx].sprites[*(DWORD*)sprites[body->state->sprite].name]; + + else skinIdx = weaponSkin; } - - // [AK] If the player isn't using an overridden skin, use their personal skin instead. - if ( usingOverrideSkin == false ) - skinIdx = player->userinfo.GetSkin( ); - - // [AK] An overridden skin also overrides NOSKIN. - if (( usingOverrideSkin ) || ( skinIdx != 0 && ( body->flags4 & MF4_NOSKIN ) == false )) + else if ((overrideSkin != -1) && (overrideSkin != skinIdx)) + skinIdx = overrideSkin; + + + + // [AK] Don't apply a skin's scale to the body if it's not supposed to be z`sible. + if ((weaponSkin != -1 || overrideSkin != -1) || (skinIdx != 0 && (body->flags4 & MF4_NOSKIN) == false)) { - const FPlayerSkin &skin = skins[skinIdx]; - - // [AK] Don't apply a skin's scale to the body if it's not supposed to be visible. - if ( skin.sprite == body->sprite ) - { - const AActor *const defaultActor = body->GetDefault( ); - - body->scaleX = Scale( body->scaleX, skin.ScaleX, defaultActor->scaleX ); - body->scaleY = Scale( body->scaleY, skin.ScaleY, defaultActor->scaleY ); - } + const AActor* const defaultActor = body->GetDefault(); + // Convert from default scale to skin scale. + fixed_t defscaleY = defaultActor->scaleY; + fixed_t defscaleX = defaultActor->scaleX; + + DWORD scaleSprite = + weapSprite != -1 && skinIdx != weaponSkin ? *(DWORD*)sprites[weapSprite].name : + *(DWORD*)sprites[body->state->sprite].name; + + // [BOF] Skin now uses Class's scale if scale is set to 0. Per sprite as well. + + fixed_t skinScaleY = (skins[skinIdx].ScaleY.CheckKey(scaleSprite) ? + skins[skinIdx].ScaleY[scaleSprite] : skins[skinIdx].ScaleY[0]); + fixed_t skinScaleX = (skins[skinIdx].ScaleX.CheckKey(scaleSprite) ? + skins[skinIdx].ScaleX[scaleSprite] : skins[skinIdx].ScaleX[0]); + + body->scaleY = Scale(body->scaleY, skinScaleY, defscaleY); + body->scaleX = Scale(body->scaleX, skinScaleX, defscaleX); } + + R_BuildPlayerTranslation(player - players); // Set Corpse's Translation + } //***************************************************************************** diff -r 8755ab2487fa src/p_mobj.cpp --- a/src/p_mobj.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/p_mobj.cpp Wed May 29 21:33:15 2024 -0700 @@ -401,9 +401,14 @@ if (playeringame[player - players] && player->cls != NULL && !(flags4 & MF4_NOSKIN) && - state->sprite == GetDefaultByType (player->cls)->SpawnState->sprite) + (state->sprite == GetDefaultByType (player->cls)->SpawnState->sprite || + skins[player->userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[state->sprite].name) ) ) { // Give player back the skin - sprite = skins[player->userinfo.GetSkin()].sprite; + sprite = + skins[player->userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[state->sprite].name) ? + skins[player->userinfo.GetSkin()].sprites[*(DWORD*)sprites[state->sprite].name] : + skins[player->userinfo.GetSkin()].sprite; + //sprite = skins[player->userinfo.GetSkin()].sprite; } if (Speed == 0) { @@ -541,6 +546,7 @@ tics = GetTics(newstate); renderflags = (renderflags & ~RF_FULLBRIGHT) | newstate->GetFullbright(); newsprite = newstate->sprite; + //if (player && sprite == 30) throw; if (newsprite != SPR_FIXED) { // okay to change sprite and/or frame if (!newstate->GetSameFrame()) @@ -552,24 +558,53 @@ // [AK] Check if the player is using a skin that overrides NOSKIN, // except when this actor is a player chunk. const int overrideSkin = IsKindOf( RUNTIME_CLASS( APlayerChunk )) ? -1 : PLAYER_GetOverrideSkin( this->player ); + const int weaponSkin = IsKindOf( RUNTIME_CLASS( APlayerChunk )) ? -1 : PLAYER_GetWeaponSkin( this->player ); + + // [AK] Don't change to the skin's sprite if the new sprite is TNT1A0. - if ((!(flags4 & MF4_NOSKIN) || overrideSkin != -1) && newsprite == SpawnState->sprite && newsprite != SPR_TNT1) - { // [RH] If the new sprite is the same as the original sprite, and - // this actor is attached to a player, use the player's skin's - // sprite. If a player is not attached, do not change the sprite - // unless it is different from the previous state's sprite; a - // player may have been attached, died, and respawned elsewhere, - // and we do not want to lose the skin on the body. If it wasn't - // for Dehacked, I would move sprite changing out of the states - // altogether, since actors rarely change their sprites after - // spawning. - if ( overrideSkin != -1 ) // [AK] Show the overridden skin first if valid. + if ((player != NULL && !PLAYER_ShouldForceBaseSkin(player) || (overrideSkin != -1 || weaponSkin != -1) && newsprite != SPR_TNT1) + && (newsprite == SpawnState->sprite || + // [BOF] Check if skin has a state equivalent. + (newsprite == state->sprite && state->sprite != SPR_TNT1 + && (skins[(overrideSkin != -1) ? overrideSkin : player->userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[newsprite].name) || + skins[(weaponSkin != -1) ? weaponSkin : player->userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[newsprite].name))))) + { + + visibleSkin = PLAYER_GetVisibleSkin(this->player); // [BOF] Record the last visibleSkin of the actor so it keeps track after losing player attachment. + + } + + if (visibleSkin != -1 && visibleSkin > PlayerClasses.Size()-1) + { + //if (player == NULL) + //R_BuildPlayerTranslation( IsKindOf(RUNTIME_CLASS(APlayerChunk))); + + if (weaponSkin != -1) { - sprite = skins[overrideSkin].sprite; + int skin = (overrideSkin != -1 ? overrideSkin : player->userinfo.GetSkin()); + sprite = + skins[weaponSkin].sprites.CheckKey(*(DWORD*)sprites[sprite].name) ? + skins[weaponSkin].sprites[*(DWORD*)sprites[sprite].name] : + skins[weaponSkin].sprite; + + if (skins[skin].sprites.CheckKey(*(DWORD*)sprites[sprite].name)) + sprite = skins[skin].sprites[*(DWORD*)sprites[sprite].name]; + } + else if ( overrideSkin != -1 ) // [AK] Show the overridden skin first if valid. + { + sprite = + skins[overrideSkin].sprites.CheckKey(*(DWORD*)sprites[newsprite].name) ? + skins[overrideSkin].sprites[*(DWORD*)sprites[newsprite].name] : + skins[overrideSkin].sprite; + //sprite = skins[overrideSkin].sprite; } else if (player != NULL && ( skins.Size() > static_cast<unsigned int> ( player->userinfo.GetSkin() ) ) ) // [BB] Adapted the skins check { - sprite = skins[player->userinfo.GetSkin()].sprite; + sprite = + skins[player->userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[newsprite].name) ? + skins[player->userinfo.GetSkin()].sprites[*(DWORD*)sprites[newsprite].name] : + skins[player->userinfo.GetSkin()].sprite; + //sprite = skins[player->userinfo.GetSkin()].sprite; } else if (newsprite != prevsprite) { @@ -5583,7 +5618,10 @@ if (!(mobj->flags4 & MF4_NOSKIN)) { - mobj->sprite = skins[lSkin].sprite; + mobj->sprite = + skins[lSkin].sprites.CheckKey(*(DWORD*)sprites[mobj->state->sprite].name) ? + skins[lSkin].sprites[*(DWORD*)sprites[mobj->state->sprite].name] : + skins[lSkin].sprite; } p->DesiredFOV = p->FOV = 90.f; @@ -5607,6 +5645,12 @@ p->MinPitch = p->MaxPitch = 0; // will be filled in by PostBeginPlay()/netcode p->cheats &= ~CF_FLY; + p->mo->ACSSkin = p->ACSSkin; + p->ACSSkin = NAME_None; + p->mo->ACSSkinOverridesSkinSounds = p->ACSSkinOverridesSkinSounds; + p->ACSSkinOverridesSkinSounds = false; + p->VisibleSkin = lSkin; + p->velx = p->vely = 0; // killough 10/98: initialize bobbing to 0. if ( NETWORK_GetState( ) != NETSTATE_SERVER ) @@ -5900,6 +5944,7 @@ // [AK] We've spawned now, so we don't need to remember our corpse anymore. p->pCorpse = NULL; + return mobj; } diff -r 8755ab2487fa src/p_user.cpp --- a/src/p_user.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/p_user.cpp Wed May 29 21:33:15 2024 -0700 @@ -208,6 +208,25 @@ } } +CCMD(clearplayerskins) // [BOF] Remove Player Skins : clearplayerskins [all?] [class (displayname)] +{ + // Get these into an orderly array for when skins get parsed. + if (ParsingKeyConf) + { + FPlayerSkinRemover skinremover; + memset(&skinremover, 0, sizeof(FPlayerSkinRemover)); + skinremover.KeyConf = Wads.GetParentWad(Wads.GetWadnumFromLumpnum(KeyConfLump)); // Get Parent Wad of KEYCONF and leave skins within same parent untouched. + + skinremover.RemoveAll = (argv.argc() >= 1 && + !stricmp(argv[1], "true") || !stricmp(argv[1], "yes") ? + true : false); + + skinremover.ClassNum = (argv.argc() < 2 ? -1 : D_PlayerClassToInt(argv[2])); + + skinremove.Insert(skinremove.Size(), skinremover); + } +} + CCMD (addplayerclass) { if (ParsingKeyConf && argv.argc () > 1) @@ -362,7 +381,7 @@ bSpawnTelefragged( 0 ), ulTime( 0 ), bUnarmed( false ), - ACSSkinOverridesWeaponSkin( false ) + ACSSkinOverridesSkinSounds( false ) { memset (&cmd, 0, sizeof(cmd)); // [BB] Check if this is still necessary. @@ -524,7 +543,7 @@ ulTime = p.ulTime; bUnarmed = p.bUnarmed; ACSSkin = p.ACSSkin; - ACSSkinOverridesWeaponSkin = p.ACSSkinOverridesWeaponSkin; + ACSSkinOverridesSkinSounds = p.ACSSkinOverridesSkinSounds; // [AK] Copy the old positions for the unlagged. for ( unsigned int i = 0; i < UNLAGGEDTICS; i++ ) @@ -766,13 +785,19 @@ // Check whether a PWADs normal sprite is to be combined with the base WADs // crouch sprite. In such a case the sprites normally don't match and it is // best to disable the crouch sprite. - if (crouchsprite > 0) + FName playerclass = GetClass()->TypeName; + + int crouchspr = GetClass()->ActorInfo->CrouchSprites[SpawnState->sprite] = GetClass()->ActorInfo->CrouchSprites[0]; + + + + if (crouchspr > 0) { // This assumes that player sprites always exist in rotated form and // that the front view is always a separate sprite. So far this is // true for anything that exists. FString normspritename = sprites[SpawnState->sprite].name; - FString crouchspritename = sprites[crouchsprite].name; + FString crouchspritename = sprites[crouchspr].name; int spritenorm = Wads.CheckNumForName(normspritename + "A1", ns_sprites); int spritecrouch = Wads.CheckNumForName(crouchspritename + "A1", ns_sprites); @@ -780,7 +805,7 @@ if (spritenorm==-1 || spritecrouch ==-1) { // Sprites do not exist so it is best to disable the crouch sprite. - crouchsprite = 0; + crouchspr = 0; return; } @@ -790,7 +815,7 @@ if (wadnorm > FWadCollection::IWAD_FILENUM && wadcrouch <= FWadCollection::IWAD_FILENUM) { // Question: Add an option / disable crouching or do what? - crouchsprite = 0; + crouchspr = 0; } } } @@ -1375,34 +1400,51 @@ const char *APlayerPawn::GetSoundClass() const { + + FNameNoInit ACSSkin; + bool ACSSounds; + + if (player != NULL) + { + ACSSkin = player->ACSSkin; + ACSSounds = player->ACSSkinOverridesSkinSounds; + } + // [AK] If this is a corpse, check which player it originally belonged to. player_t *corpsePlayer = nullptr; - for ( unsigned int i = 0; i < BODYQUESIZE; i++ ) + for (unsigned int i = 0; i < BODYQUESIZE; i++) { - if ( this == bodyque[i] ) + if (this == bodyque[i]) { corpsePlayer = bodyquePlayer[i]; + ACSSkin = corpsePlayer->mo->ACSSkin; + ACSSounds = corpsePlayer->mo->ACSSkinOverridesSkinSounds; break; } } + // [BC] If this player's skin is disabled, just use the base sound class. // [BB] Voodoo dolls don't have valid userinfo. // [AK] Also use the player's skin if this is a corpse that belonged to them. - if (( player != NULL ) && (( player->mo == this ) || ( player == corpsePlayer )) && - (( cl_skins == 1 ) || (( cl_skins >= 2 ) && - ( player->userinfo.GetSkin() < static_cast<signed> (skins.Size()) ) && - ( skins[player->userinfo.GetSkin()].bCheat == false )))) + // [BOF] If ACS Skin overrides CVAR skin's Sounds + if ((player != NULL) && ((player->mo == this) || (player == corpsePlayer)) && + ((cl_skins == 1) || + ((cl_skins >= 2) && + (player->userinfo.GetSkin() < static_cast<signed> (skins.Size())) && + (skins[player->userinfo.GetSkin()].bCheat == false)) || + (ACSSkin != NAME_None && ACSSounds))) { if (player != NULL && - (player->mo == NULL || !(player->mo->flags4 &MF4_NOSKIN)) && + (player->mo == NULL || + !(player->mo->flags4 &MF4_NOSKIN) && (unsigned int)player->userinfo.GetSkin() >= PlayerClasses.Size () && - (size_t)player->userinfo.GetSkin() < skins.Size()) + (size_t)player->userinfo.GetSkin() < skins.Size()) || + (ACSSkin != NAME_None && ACSSounds)) { - return skins[player->userinfo.GetSkin()].name; + return (ACSSkin != NAME_None && ACSSounds == true) ? ACSSkin.GetChars() : skins[player->userinfo.GetSkin()].name; } } - // [GRB] const char *sclass = GetClass ()->Meta.GetMetaString (APMETA_SoundClass); return sclass != NULL ? sclass : "player"; @@ -2569,72 +2611,195 @@ void P_CheckPlayerSprite(AActor *actor, int &spritenum, fixed_t &scalex, fixed_t &scaley) { player_t *player = actor->player; - int crouchspriteno; + int crouchspriteno = -1; + int weapSprite = -1; // [AK] Don't set the player's sprite if their current body doesn't match their class due to A_SkullPop. if ( actor->IsKindOf( RUNTIME_CLASS( APlayerChunk ))) return; + // [BC] Because of cl_skins, we might not necessarily use the player's desired skin. + const int weaponSkin = PLAYER_GetWeaponSkin( player ); // [BOF] const int overrideSkin = PLAYER_GetOverrideSkin( player ); // [AK] int skin = player->userinfo.GetSkin(); // [AK] Check if the player's base skin should be used instead of their personal skin. - if ( PLAYER_ShouldForceBaseSkin( player )) - skin = R_FindSkin( "base", player->CurrentPlayerClass ); - - // [BB/AK] If the skin was overridden from ACS, or the weapon has a PreferredSkin defined, make the player use it here. - if (( overrideSkin != -1 ) && ( overrideSkin != skin )) + if (PLAYER_ShouldForceBaseSkin(player)) + { + skin = R_FindSkin("base", player->CurrentPlayerClass); + } + + // [BOF] Base Skin just uses actor state sprites. + if (0 == stricmp(skins[skin].name, "Base") && (spritenum != actor->state->sprite) + && (actor->state->sprite != SPR_NOCHANGE) && (actor->state->sprite != SPR_FIXED)) + { + spritenum = actor->state->sprite; + } + + // [BOF] Split up overrideSkin for ACS and Weapon Skins. + if ((weaponSkin != -1) && (weaponSkin != skin) && (weaponSkin != overrideSkin)) { - skin = overrideSkin; - spritenum = skins[skin].sprite; - } - // [BB/AK] No longer using an overridden skin, reset the sprite. - else if ( ( spritenum != skins[skin].sprite ) && ( spritenum != skins[skin].crouchsprite ) - && ( spritenum != actor->state->sprite ) && (actor->state->sprite != SPR_NOCHANGE) && - (actor->state->sprite != SPR_FIXED)) - { - spritenum = skins[skin].sprite; + //Printf("Weapon skin \n"); + if ((overrideSkin != -1) && (overrideSkin != skin)) + skin = overrideSkin; + + weapSprite = spritenum = + skins[weaponSkin].sprites.CheckKey(*(DWORD*)sprites[spritenum].name) ? + skins[weaponSkin].sprites[*(DWORD*)sprites[spritenum].name] : + skins[weaponSkin].sprite; + //Printf("Override weapon %s\n", sprites[spritenum].name); + + if (skins[skin].sprites.CheckKey(*(DWORD*)sprites[spritenum].name)) + { + //Printf("Overridden %s\n", sprites[skins[skin].sprites[*(DWORD*)sprites[spritenum].name]].name); + spritenum = skins[skin].sprites[*(DWORD*)sprites[spritenum].name]; + } + else skin = weaponSkin; } - - // [BB/AK] An overridden skin also overrides NOSKIN. - if (skin != 0 && ( !(player->mo->flags4 & MF4_NOSKIN) || ( overrideSkin != -1 ) ) ) + // [BB/AK/BOF] If the skin was overridden from ACS, make the player use it here. + else if ((overrideSkin != -1) && (overrideSkin != skin)) { - // Convert from default scale to skin scale. - fixed_t defscaleY = actor->GetDefault()->scaleY; - fixed_t defscaleX = actor->GetDefault()->scaleX; - scaley = Scale(scaley, skins[skin].ScaleY, defscaleY); - scalex = Scale(scalex, skins[skin].ScaleX, defscaleX); + //Printf("ACS skin \n"); + skin = overrideSkin; + spritenum = + skins[skin].sprites.CheckKey(*(DWORD*)sprites[spritenum].name) ? + skins[skin].sprites[*(DWORD*)sprites[spritenum].name] : + skins[skin].sprite; } - // Set the crouch sprite? - if (player->crouchfactor < FRACUNIT*3/4) + // [BB/AK] No longer using an overridden skin, reset the sprite. + else if ( + (spritenum != ( + skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->state->sprite].name) ? + skins[skin].sprites[*(DWORD*)sprites[actor->state->sprite].name] : + skins[skin].sprite)) + + && (spritenum != ( + skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) ? + skins[skin].sprites[*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name] : + skins[skin].crouchsprite)) + + && !(spritenum == actor->state->sprite && !skins[skin].sprites.CheckKey(actor->state->sprite)) + && (actor->state->sprite != SPR_NOCHANGE) && (actor->state->sprite != SPR_FIXED) + ) { - if (spritenum == actor->SpawnState->sprite || spritenum == player->mo->crouchsprite) + spritenum = + skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->state->sprite].name) ? + skins[skin].sprites[*(DWORD*)sprites[actor->state->sprite].name] : + skins[skin].sprite; + } + + + if (player->VisibleSkin != skin) { - crouchspriteno = player->mo->crouchsprite; + R_BuildPlayerTranslation(actor->IsKindOf(RUNTIME_CLASS(APlayerChunk))); + player->VisibleSkin = skin; + } + + DWORD crouchScale = 0; + // Set the crouch sprite? + if (player->crouchfactor < FRACUNIT * 3 / 4) + { + // [BOF] Update for CrouchSprite Array + if (actor->GetClass()->ActorInfo->CrouchSprites.CheckKey(spritenum)) + { + crouchspriteno = actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]; } // [BB/AK] An overridden skin also overrides NOSKIN. - else if ( ( !(actor->flags4 & MF4_NOSKIN) || ( overrideSkin != -1 ) ) && - (spritenum == skins[skin].sprite || - spritenum == skins[skin].crouchsprite)) + else if ((!(actor->flags4 & MF4_NOSKIN) || (overrideSkin != -1)) || (weaponSkin != -1) && // Visible Skin? + (spritenum == skins[skin].sprite || // Check for sprite + (skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->state->sprite].name) && // Check Sprite Array + spritenum == skins[skin].sprites[*(DWORD*)sprites[actor->state->sprite].name]))) { - crouchspriteno = skins[skin].crouchsprite; + // [BOF] Check Skin over Weapon Skin + if (weaponSkin != -1 && skin != weaponSkin && + // Weapon Skin's Sprites Array Check + ((skins[weaponSkin].sprites.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) && // What a mouthful + skins[skin].sprites.CheckKey(*(DWORD*)sprites[skins[weaponSkin].sprites[*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name]].name)) || + + // Weapon Skin's Crouch Sprite Check + (skins[weaponSkin].crouchsprite && + skins[skin].sprites.CheckKey(*(DWORD*)sprites[skins[weaponSkin].crouchsprite].name)) + )) + + { + crouchspriteno = + skins[weaponSkin].sprites.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) && + skins[skin].sprites.CheckKey(*(DWORD*)sprites[skins[weaponSkin].sprites[*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name]].name) ? + skins[skin].sprites[*(DWORD*)sprites[skins[weaponSkin].sprites[*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name]].name] : + + skins[skin].sprites.CheckKey(*(DWORD*)sprites[skins[weaponSkin].crouchsprite].name) ? + skins[skin].sprites[*(DWORD*)sprites[skins[weaponSkin].crouchsprite].name] : + + -1; // Shouldn't Happen with the conditions above, but just to be safe. + + crouchScale = skins[weaponSkin].ScaleX.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) && + skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) ? + *(DWORD*)sprites[skins[weaponSkin].sprites[*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name]].name : + skins[skin].sprites.CheckKey(*(DWORD*)sprites[skins[weaponSkin].crouchsprite].name) ? + *(DWORD*)sprites[skins[weaponSkin].crouchsprite].name : + 0; + } + // Check Regular Skin + else if ((skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) || skins[skin].crouchsprite) && + weaponSkin == -1 || weaponSkin == skin) + { + crouchspriteno = + skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) ? + skins[skin].sprites[*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name] : + skins[skin].crouchsprite; + + crouchScale = skins[skin].sprites.CheckKey(*(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name) ? + *(DWORD*)sprites[actor->GetClass()->ActorInfo->CrouchSprites[actor->state->sprite]].name : + *(DWORD*)sprites[skins[weaponSkin].crouchsprite].name; + + } + else + { // no sprite -> squash the existing one + crouchspriteno = -1; + } } else { // no sprite -> squash the existing one crouchspriteno = -1; } - - if (crouchspriteno > 0) - { - spritenum = crouchspriteno; + } + + + + // [BB/AK] An overridden skin also overrides NOSKIN. + if ((weaponSkin != -1 || overrideSkin != -1) || (skin != 0 && (player->mo->flags4 & MF4_NOSKIN) == false)) + { + // Convert from default scale to skin scale. + fixed_t defscaleY = actor->GetDefault()->scaleY; + fixed_t defscaleX = actor->GetDefault()->scaleX; + + DWORD scaleSprite = crouchScale ? crouchScale : + weapSprite != -1 && skin != weaponSkin ? *(DWORD*)sprites[weapSprite].name : + *(DWORD*)sprites[actor->state->sprite].name; + + // [BOF] Skin now uses Class's scale if scale is set to 0. Per sprite as well. + + fixed_t skinScaleY = (skins[skin].ScaleY.CheckKey(scaleSprite) ? + skins[skin].ScaleY[scaleSprite] : skins[skin].ScaleY[0]); + fixed_t skinScaleX = (skins[skin].ScaleX.CheckKey(scaleSprite) ? + skins[skin].ScaleX[scaleSprite] : skins[skin].ScaleX[0]); + + scaley = Scale(scaley, skinScaleY, defscaleY); + scalex = Scale(scalex, skinScaleX, defscaleX); } - else if (player->playerstate != PST_DEAD && player->crouchfactor < FRACUNIT*3/4) - { - scaley /= 2; - } + + + if (crouchspriteno > 0) + { + spritenum = crouchspriteno; } + else if (player->playerstate != PST_DEAD && player->crouchfactor < FRACUNIT*3/4) + { + scaley /= 2; + } + } /* @@ -4410,7 +4575,7 @@ << MaxHealthBonus << cheats2 << ACSSkin - << ACSSkinOverridesWeaponSkin + << ACSSkinOverridesSkinSounds // [BB] Skulltag additions - end ; if (SaveVersion < 3427) @@ -4526,6 +4691,8 @@ return NULL; } + + FPlayerColorSet *P_GetPlayerColorSet(FName classname, int setnum) { FPlayerColorSetMap *map = GetPlayerColors(classname); diff -r 8755ab2487fa src/r_data/r_translate.cpp --- a/src/r_data/r_translate.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/r_data/r_translate.cpp Wed May 29 21:33:15 2024 -0700 @@ -1113,13 +1113,14 @@ void R_BuildPlayerTranslation (int player) { + int skin = PLAYER_GetVisibleSkin(&players[player]) != -1 ? PLAYER_GetVisibleSkin(&players[player]) : players[player].userinfo.GetSkin(); float h, s, v; FPlayerColorSet *colorset; D_GetPlayerColor (player, &h, &s, &v, &colorset); R_CreatePlayerTranslation (h, s, v, colorset, - &skins[players[player].userinfo.GetSkin()], + &skins[skin], translationtables[TRANSLATION_Players][player], translationtables[TRANSLATION_PlayersExtra][player]); } diff -r 8755ab2487fa src/r_data/sprites.cpp --- a/src/r_data/sprites.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/r_data/sprites.cpp Wed May 29 21:33:15 2024 -0700 @@ -14,6 +14,7 @@ #include "r_data/sprites.h" #include "r_data/voxels.h" #include "textures/textures.h" +#include "v_video.h" void gl_InitModels(); @@ -34,6 +35,8 @@ // [RH] skin globals // [BL] Changed to TArray TArray<FPlayerSkin> skins; + +TArray<FPlayerSkinRemover> skinremove; // [BOF] BYTE OtherGameSkinRemap[256]; PalEntry OtherGameSkinPalette[256]; @@ -507,23 +510,34 @@ static void R_CreateSkin(); void R_InitSkins (void) { + Printf(TEXTCOLOR_RED, "BEGIN SKINNING\n"); FSoundID playersoundrefs[NUMSKINSOUNDS]; spritedef_t temp; int sndlumps[NUMSKINSOUNDS]; - char key[65]; - DWORD intname, crouchname; + + //[BOF] Allow key to be any length + FString key; + + //[BOF] 'intname' turned into an array to parse and store multiple sprites with. + TMap<DWORD, DWORD> intname; + + //[BOF] 'basetype' is also turned into an array to account multiple classes on a skin. + const PClass *basetype, *transtype; + int pclass; int i; int j, k, base; int lastlump; int aliasid; bool remove; - const PClass *basetype, *transtype; + char replace[MAX_SKIN_NAME + 1]; + int iterate; + bool gameDefined; int s_skin = 1; // (0 = skininfo, 1 = s_skin, 2 = s_skin non-changeable) - bool lumpSkininfo = false; // are we parsing the SKININFO lumps - key[sizeof(key)-1] = 0; + //key[sizeof(key)-1] = 0; i = PlayerClasses.Size () - 1; lastlump = 0; + bool lumpSkininfo = false; // are we parsing the SKININFO lumps for (j = 0; j < NUMSKINSOUNDS; ++j) { @@ -553,104 +567,172 @@ FScanner sc(base); + + + + + + // [BOF] Slight parser rework using Tokens instead of Strings. + // Data is stored as "key = data". - while (sc.GetString ()) + while (sc.GetToken()) { // [BB] The original SKININFO parser ate everything before the starting bracket. // To retain compatibility with existing wads, we need to keep this behavior. - if ( lumpSkininfo ) + if (lumpSkininfo) { // Parse until we find a starting bracket. - while ( sc.String[0] != '{' ) + if (sc.String[0] != '{') { - if ( !sc.GetString( ) ) - break; + continue; } } - if(sc.String[0] == '{') + if (sc.String[0] == '{') { - if(s_skin == 1) + if (s_skin == 1) s_skin = 0; // Change to SKININFO - else if(s_skin == 0) + else if (s_skin == 0) { R_CreateSkin(); i++; // new skin } - if(s_skin != 2) // If this is at S_SKIN unchangeable then no nothing else get a new string. - sc.GetString(); + if (s_skin != 2) // If this is at S_SKIN unchangeable then no nothing else get a new string. + sc.GetToken(); } - for (j = 0; j < NUMSKINSOUNDS; j++) - sndlumps[j] = -1; - skins[i].namespc = Wads.GetLumpNamespace (base); - intname = 0; - crouchname = 0; - remove = false; - basetype = NULL; - transtype = NULL; + // Ready up for the next potential skin. + skins[i].namespc = Wads.GetLumpNamespace (base); + for (j = 0; j < NUMSKINSOUNDS; j++) sndlumps[j] = -1; // Clear temp sndlumps + remove = false; // + replace[0] = NULL; - if(s_skin == 1) - s_skin = 2; // If it doesn't find a '{' permanently use S_SKIN format. + skins[i].wadnum = Wads.GetParentWad(Wads.GetWadnumFromLumpnum(base)); // [BOF] For Removal of Skins outside of KEYCONF wad. + //Maybe this could be taken advantage of in the future to isolate skin sprites to their own section per wad to prevent sprite overlap. - //[BL] We'll now go until we hit a '}' in SKININFO format + intname.Clear(); // Clear temp sprites list + pclass = NULL; + basetype = transtype = NULL; // Clear class value. + if (s_skin == 1) s_skin = 2; // If it doesn't find a '{' permanently use S_SKIN format. + do { - if(s_skin == 0 && sc.String[0] == '}') + + if (!s_skin && sc.End && !sc.CheckToken('}')) // Remove skin at end of SKININFO with no ending bracket + { + Printf(PRINT_BOLD, "Unexpected end of file for skin %i. %s\n", i, + (strlen(skins[i].name)) ? skins[i].name : ""); + remove = true; break; - - strncpy (key, sc.String, sizeof(key)-1); - if (!sc.GetString() || sc.String[0] != '=') + } + + key = sc.String;key.ToLower(); // Keep all lowercase to prevent inconsistencies with GetSkinInfo + + if (!sc.CheckToken('=')) { - Printf (PRINT_BOLD, "Bad format for skin %d: %s\n", (int)i, key); + Printf(PRINT_BOLD, "Bad format for skin %d: %s\n", (int)i, key); // [BB] If there was a problem parsing the skin, remove it. Otherwise bad things may happen. remove = true; break; } - sc.GetString (); - if (0 == stricmp (key, "name")) + + sc.GetString(); + + // Name + if (!key.Compare("name")) { // [BC] MAX_SKIN_NAME. - strncpy (skins[i].name, sc.String, MAX_SKIN_NAME); - for (j = 0; j < i; j++) + strncpy(skins[i].name, sc.String, MAX_SKIN_NAME); + + skins[i].param[key].list.Resize(2); + skins[i].param[key].list[1] = skins[i].param[key].list[0] = skins[i].name; + + // [BOF] Prevent skins from intentionally being named 'skin#' + if (strncmp(skins[i].name, "skin", 4) == 0 && sc.StringLen > 4) { - if (stricmp (skins[i].name, skins[j].name) == 0) + char check[MAX_SKIN_NAME - 4]; + strncpy(check, &sc.String[4], MAX_SKIN_NAME - 4); + if (IsNum(check)) { - mysnprintf (skins[i].name, countof(skins[i].name), "skin%d", (int)i); - Printf (PRINT_BOLD, "Skin %s duplicated as %s\n", - skins[j].name, skins[i].name); - break; + Printf(PRINT_BOLD, "Skin %s renamed to skin%d\n", + skins[i].name, (int)i); + mysnprintf(skins[i].name, countof(skins[i].name), "skin%d", (int)i); } } + } - else if (0 == stricmp (key, "sprite")) + + // Sprite + else if (!key.Compare("sprite")) + { + for (j = 3; j >= 0; j--) + sc.String[j] = toupper(sc.String[j]); + intname[0] = *((DWORD*)sc.String); + + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = sc.String; + skins[i].param[key].list[0].Truncate(4); + + } + + // Crouching Sprite + else if (!key.Compare("crouchsprite")) { for (j = 3; j >= 0; j--) - sc.String[j] = toupper (sc.String[j]); - intname = *((DWORD *)sc.String); + sc.String[j] = toupper(sc.String[j]); + intname[-1] = *((DWORD*)sc.String); + + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = sc.String; + skins[i].param[key].list[0].Truncate(4); } - else if (0 == stricmp (key, "crouchsprite")) - { - for (j = 3; j >= 0; j--) - sc.String[j] = toupper (sc.String[j]); - crouchname = *((DWORD *)sc.String); - } - else if (0 == stricmp (key, "face")) + + // HUD Face + else if (!key.Compare("face")) { for (j = 2; j >= 0; j--) - skins[i].face[j] = toupper (sc.String[j]); + skins[i].face[j] = toupper(sc.String[j]); skins[i].face[3] = '\0'; + + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = skins[i].face; + } - else if (0 == stricmp (key, "gender")) + + // Gender + else if (!key.Compare("gender")) { - skins[i].gender = D_GenderToInt (sc.String); + skins[i].gender = D_GenderToInt(sc.String); + + skins[i].param[key].list.Resize(2); + skins[i].param[key].list[0].Format("%i", skins[i].gender); // Integer value of gender + skins[i].param[key].list[1] = sc.String; // Actual field entry } - else if (0 == stricmp (key, "scale")) - { - skins[i].ScaleX = clamp<fixed_t> (FLOAT2FIXED(atof (sc.String)), 1, 256*FRACUNIT); - skins[i].ScaleY = skins[i].ScaleX; + + // Scale + else if (!key.Compare("scale")) + { // [BOF] You can set both X and Y scale now, Just one Param will set both to the same value. + sc.UnGet(); + sc.GetToken(); + skins[i].ScaleX[-1] = skins[i].ScaleX[0] = clamp<fixed_t>(FLOAT2FIXED(atof(sc.String)), 1, 256 * FRACUNIT); + skins[i].param[key].list.Resize(2); + skins[i].param[key].list[0].Format("%i", skins[i].ScaleX); + + if (sc.CheckToken(',')) + { + sc.GetToken(); + skins[i].ScaleY[-1] = skins[i].ScaleY[0] = clamp<fixed_t>(FLOAT2FIXED(atof(sc.String)), 1, 256 * FRACUNIT); + skins[i].param[key].list[1].Format("%i", skins[i].ScaleY); + } + else + { + skins[i].ScaleY[-1] = skins[i].ScaleY[0] = skins[i].ScaleX[0]; + skins[i].param[key].list[1] = skins[i].param[key].list[0]; + } } - else if (0 == stricmp (key, "game")) + + // Game + else if (!key.Compare("game")) { if (gameinfo.gametype == GAME_Heretic) basetype = PClass::FindClass (NAME_HereticPlayer); @@ -695,10 +777,15 @@ if (remove) break; + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = sc.String; + } - else if (0 == stricmp (key, "class")) + + // Class + else if (!key.Compare("class")) { // [GRB] Define the skin for a specific player class - int pclass = D_PlayerClassToInt (sc.String); + pclass = D_PlayerClassToInt (sc.String); if (pclass < 0) { @@ -707,77 +794,308 @@ } basetype = transtype = PlayerClasses[pclass].Type; + + skins[i].param[key].list.Resize(2); + skins[i].param[key].list[0].Format("%i", pclass); //Class Number + skins[i].param[key].list[1] = sc.String; //Class Name } + + // [BL] Skulltag additions - else if (0 == stricmp (key, "hidden")) + + // Hidden Skin + else if (!key.Compare("hidden")) + { // [BOF] This is backwards, but should be left untouched for compatability. + if ((stricmp(sc.String, "true") == 0) || (stricmp(sc.String, "yes") == 0)) + { + skins[i].bRevealed = true; + skins[i].param[key].list[0] = "0"; // Is not hidden, so 0. + } + else if ((stricmp(sc.String, "false") == 0) || (stricmp(sc.String, "no") == 0)) + { + skins[i].bRevealed = false; + skins[i].param[key].list[0] = "1"; // Is hidden, so 1. + } + + + } + + // Cheat Skin + else if (!key.Compare("cheat")) { - if (( stricmp(sc.String, "true") == 0 ) || ( stricmp(sc.String, "yes") == 0 )) - skins[i].bRevealed = true; - else if (( stricmp(sc.String, "false") == 0 ) || ( stricmp(sc.String, "no") == 0 )) - skins[i].bRevealed = false; + if ((stricmp(sc.String, "true") == 0) || (stricmp(sc.String, "yes") == 0)) + skins[i].bCheat = true; + else if ((stricmp(sc.String, "false") == 0) || (stricmp(sc.String, "no") == 0)) + skins[i].bCheat = false; + + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = sc.String; + + } + + // Color + else if (!key.Compare("color")) + { + // [BOF] Set an actual color now + if (skins[i].szColor = V_GetColor(NULL, sc.String)) + if (skins[i].szColor = V_GetColor(NULL, sc.String)) + { + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0].Format("%06X", skins[i].szColor); + } + else skins[i].szColor = NULL; } - else if (0 == stricmp (key, "cheat")) + + + // [BOF] Zandronum additions + + // Array of Sprites + else if (!key.Compare("sprites")) { - if (( stricmp(sc.String, "true") == 0 ) || ( stricmp(sc.String, "yes") == 0 )) - skins[i].bCheat = true; - else if (( stricmp(sc.String, "false") == 0 ) || ( stricmp(sc.String, "no") == 0 )) - skins[i].bCheat = false; + sc.UnGet(); + if (!sc.CheckToken('[')) + { + Printf(PRINT_BOLD, "Bad format for skin %d: sprites\n", (int)i); + remove = true; + break; + } + + skins[i].param[key].charlist.Clear(); + while (!sc.CheckToken(']')) + { + + DWORD spritename; + + for (j = 3; j >= 0; j--) + sc.String[j] = toupper(sc.String[j]); + spritename = *((DWORD*)sc.String); + + FString charkey = sc.String; + charkey.Truncate(4); + charkey.ToLower(); // Keep all keys lowercase for GetSkinInfo + + sc.GetToken(); + if (!sc.CheckToken('=')) + { + Printf(PRINT_BOLD, "Bad format for skin %d: sprites \"%.4s\"\n", (int)i, &spritename); + remove = true; + break; + } + + sc.GetToken(); + for (j = 3; j >= 0; j--) + sc.String[j] = toupper(sc.String[j]); + intname[spritename] = *((DWORD*)sc.String); + + skins[i].param[key].charlist[charkey] = sc.String; + skins[i].param[key].charlist[charkey].Truncate(4); + + if (!sc.CheckToken(',')) //Set Scale for a sprite, + { + skins[i].ScaleX[spritename] = -1; + skins[i].ScaleY[spritename] = -1; + continue; + } + if (sc.GetToken() && atof(sc.String)) + skins[i].ScaleX[spritename] = clamp<fixed_t>(FLOAT2FIXED(atof(sc.String)), 1, 256 * FRACUNIT); + if (sc.CheckToken(',') && sc.GetString() && atof(sc.String)) + skins[i].ScaleY[spritename] = clamp<fixed_t>(FLOAT2FIXED(atof(sc.String)), 1, 256 * FRACUNIT); + else if (skins[i].ScaleX.CheckKey(spritename)) + { + skins[i].ScaleY[spritename] = skins[i].ScaleX[spritename]; + } + + } + if (remove == true) break; } - else if (0 == stricmp( key, "color" )) + + // Display Name for Menus + else if (!key.Compare("displayname")) { - sprintf( skins[i].szColor, "%s", sc.String ); + strncpy(skins[i].displayname, sc.String, MAX_SKIN_NAME); + + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = skins[i].displayname; } + + // Color Range + else if (!key.Compare("colorrange")) + { // [BOF] You can set both X and Y scale now, Just one Param will set both to the same value. + sc.UnGet(); + sc.GetToken(); + BYTE tempbyte[2]; + + tempbyte[0] = (BYTE)atoi(sc.String); + + if (sc.CheckToken(',')) + { + sc.GetToken(); + tempbyte[1] = (BYTE)atoi(sc.String); + } + else + { + break; + } + + skins[i].range0start = MIN(tempbyte[0], tempbyte[1]); + skins[i].range0end = MAX(tempbyte[0], tempbyte[1]); + + //skins[i].param[key].list[0] = skins[i].param[key].list[1] = ""; + skins[i].param[key].list[0].Format("%i", skins[i].range0start); + skins[i].param[key].list[1].Format("%i", skins[i].range0end); + } + + // Selectablility + else if (!key.Compare("selectable")) + { // Only accessible by Overriding (Weapon/ACS) + if ((stricmp(sc.String, "false") == 0) || (stricmp(sc.String, "no") == 0)) + { + skins[i].countSkin = skins[i].bRevealedByDefault = false; + skins[i].param[key].list[0] = "0"; + } + else + { + skins[i].countSkin = skins[i].bRevealedByDefault = true; + skins[i].param[key].list[0] = "1"; + } + + } + + // Removable + else if (!key.Compare("removable")) + { + if ((stricmp(sc.String, "true") == 0) || (stricmp(sc.String, "yes") == 0)) + skins[i].removable = true; + else skins[i].removable = false; + + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = sc.String; + } + + // ZDoom Sound Replacement else if (key[0] == '*') { // Player sound replacment (ZDoom extension) - int lump = Wads.CheckNumForName (sc.String, skins[i].namespc); + int lump = Wads.CheckNumForName(sc.String, skins[i].namespc); if (lump == -1) { - lump = Wads.CheckNumForFullName (sc.String, true, ns_sounds); + lump = Wads.CheckNumForFullName(sc.String, true, ns_sounds); } if (lump != -1) { - if (stricmp (key, "*pain") == 0) + if (stricmp(key, "*pain") == 0) { // Replace all pain sounds in one go - aliasid = S_AddPlayerSound (skins[i].name, skins[i].gender, + aliasid = S_AddPlayerSound(skins[i].name, skins[i].gender, playersoundrefs[0], lump, true); for (int l = 3; l > 0; --l) { - S_AddPlayerSoundExisting (skins[i].name, skins[i].gender, + S_AddPlayerSoundExisting(skins[i].name, skins[i].gender, playersoundrefs[l], aliasid, true); } } else { - int sndref = S_FindSoundNoHash (key); + int sndref = S_FindSoundNoHash(key); if (sndref != 0) { - S_AddPlayerSound (skins[i].name, skins[i].gender, sndref, lump, true); + S_AddPlayerSound(skins[i].name, skins[i].gender, sndref, lump, true); } } } - } + + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = sc.String; + + } + + // Sound Replacement / Custom Parameters else { + bool cont = false; for (j = 0; j < NUMSKINSOUNDS; j++) { - if (stricmp (key, skinsoundnames[j][0]) == 0) + if (stricmp(key, skinsoundnames[j][0]) == 0) { - sndlumps[j] = Wads.CheckNumForName (sc.String, skins[i].namespc); + sndlumps[j] = Wads.CheckNumForName(sc.String, skins[i].namespc); if (sndlumps[j] == -1) { // [BL] no replacement, search all wads? - sndlumps[j] = Wads.CheckNumForName (sc.String); + sndlumps[j] = Wads.CheckNumForName(sc.String); } if (sndlumps[j] == -1) { // Replacement not found, try finding it in the global namespace - sndlumps[j] = Wads.CheckNumForFullName (sc.String, true, ns_sounds); + sndlumps[j] = Wads.CheckNumForFullName(sc.String, true, ns_sounds); } + skins[i].param[key].list.Resize(1); + skins[i].param[key].list[0] = sc.String; + cont = true; } + } - //if (j == 8) - // Printf ("Funny info for skin %i: %s = %s\n", i, key, sc.String); + if (cont) continue; // Don't parse these audio files again below + + // [BOF] Custom value support for GetSkinInfo + + // Custom string array + if (sc.String[0] == '[') + { + skins[i].param[key].charlist.Clear(); + skins[i].param[key].list.Clear(); + sc.GetString(); + do + { + if (sc.String[0] == ']') + break; + FString charkey = sc.String; + + // If there's no more parsing without hitting ']' or you hit '}' the skin is considered invalid + if (!sc.GetString() || sc.String[0] != '=' || sc.String[0] == '}') + { + Printf(PRINT_BOLD, "Bad format for skin %d: %s\n", (int)i, key); + remove = true; + break; + } + sc.GetString(); + skins[i].param[key].charlist[charkey] = sc.String; + //Printf(PRINT_BOLD,"%s : %s = %s\n", key, charkey, skins[i].param[key].charlist[charkey]); + } while (sc.GetString()); + if (remove == true) break; + } + // Custom param or array + else + { + skins[i].param[key].charlist.Clear(); + skins[i].param[key].list.Clear(); + do + { + if (skins[i].param[key].list.Size() != 0) + sc.GetString(); + //Inserts valid classes into a paramlist, if no classes are valid, param["class"] will be empty. + skins[i].param[key].list.Insert( + skins[i].param[key].list.Size(), + sc.String); + //Printf(PRINT_BOLD, "%s #%i = %s\n", key, skins[i].param[key].list.Size(), sc.String); + } while (sc.CheckString(",")); + } + } + + } while ((!s_skin && !sc.CheckToken('}') && sc.GetToken()) || (s_skin && sc.GetToken())); // Check for closing bracket in SKININFO, and end in S_SKIN + + // [BOF] Remove skins through clearplayerskins + if (!remove) + { // (clearplayerskins [all?] [class]) + //pclass = D_PlayerClassToInt(skins[i].param["key"].list[1]); // Have to reinitialize pclass; use skin param. + for (j = 0; j < skinremove.Size(); j++) + { + if (skins[i].wadnum < skinremove[j].KeyConf && // Don't delete skins in or after the same wad as the KEYCONF + (skinremove[j].ClassNum < 0 || (skinremove[j].ClassNum >= 0 && // If ClassNum is -1 + (pclass == skinremove[j].ClassNum))) && // or pclass matches Classnum + (skinremove[j].RemoveAll || // Remove all skins if true. + skins[i].removable)) //Remove removable skins regardless + remove = true; } } - while(sc.GetString()); + + + // Check Classes // [GRB] Assume Doom skin by default if (!remove && basetype == NULL) @@ -796,13 +1114,20 @@ { remove = true; } + pclass = 0; } if (!remove) { - skins[i].range0start = transtype->Meta.GetMetaInt (APMETA_ColorRange) & 0xff; - skins[i].range0end = transtype->Meta.GetMetaInt (APMETA_ColorRange) >> 8; - + BYTE range0start = transtype->Meta.GetMetaInt(APMETA_ColorRange) & 0xff; + BYTE range0end = transtype->Meta.GetMetaInt(APMETA_ColorRange) >> 8; + + if (range0end == 0) // Don't translate if the Class doesn't want to be translated. + { + skins[i].range0start = range0start; + skins[i].range0end = range0end; + } + remove = true; for (j = 0; j < (int)PlayerClasses.Size (); j++) { @@ -813,100 +1138,168 @@ type->Meta.GetMetaInt (APMETA_ColorRange) == basetype->Meta.GetMetaInt (APMETA_ColorRange)) { PlayerClasses[j].Skins.Push ((int)i); + + // [BOF] Set default Scale for undefined Sprite array scales + intmap::Pair* scalepair; + intmap::Iterator checkScaleX(skins[i].ScaleX); + while (checkScaleX.NextPair(scalepair)) + { + if (scalepair->Value == -1) scalepair->Value = GetDefaultByType(type)->scaleX; + } + intmap::Iterator checkScaleY(skins[i].ScaleY); + while (checkScaleY.NextPair(scalepair)) + { + if (scalepair->Value == -1) scalepair->Value = GetDefaultByType(type)->scaleY; + } + + remove = false; } } + } - + + // Check Sprites if (!remove) { + + for (j = 0; j < i; j++) // Mark all previous skins of the same name as hidden if such, and remove from numSkins value for 'skins' command + { + if (stricmp(skins[i].name, skins[j].name) == 0) + { + skins[i].countSkin = false; // Don't count this skin through 'skins' command + skins[j].bRevealed = skins[i].bRevealed; // Set all skins of the same anme's hidden status as this skins. + break; + } + } + + if (skins[i].name[0] == 0) - mysnprintf (skins[i].name, countof(skins[i].name), "skin%d", (int)i); + { + mysnprintf(skins[i].name, countof(skins[i].name), "skin%d", (int)i); + skins[i].param["name"].list.Resize(2); + skins[i].param["name"].list[0] = skins[i].param["name"].list[1] = skins[i].name; + } + + // [BOF] Check Skin name within its own class instead of globally. + bool initialname = false; + for (j = 0; j < PlayerClasses[pclass].Skins.Size(); j++) + { + if (stricmp(skins[i].name, skins[PlayerClasses[pclass].Skins[j]].name) == 0) + { + if (initialname == true) + { + mysnprintf(skins[i].name, countof(skins[i].name), "skin%d", (int)i); + Printf(PRINT_BOLD, "Skin %s duplicated as %s\n", + skins[PlayerClasses[pclass].Skins[j]].name, skins[i].name); + skins[i].param["name"].list[0] = skins[i].name; + break; + } + initialname = true; + } + } + + + if (skins[i].displayname[0] == 0) + { + strcpy(skins[i].displayname, skins[i].name); //Use CVAR name if no Displayname is available + skins[i].param["displayname"].list.Resize(1); + skins[i].param["displayname"].list[0] = skins[i].displayname; + } // Now collect the sprite frames for this skin. If the sprite name was not // specified, use whatever immediately follows the specifier lump. // [BL] S_SKIN only - if (intname == 0 && s_skin != 0) + // [BOF] or if a skin has no intnames... + if (intname.CountUsed() == 0 || (!intname.CheckKey(0) && s_skin != 0)) { char name[9]; - Wads.GetLumpName (name, base+1); - memcpy(&intname, name, 4); + Wads.GetLumpName(name, base + 1); + intname[0] = *(DWORD*)name; } int basens = Wads.GetLumpNamespace(base); - for(int spr = 0; spr<2; spr++) + //[BOF] Iterate list of sprites instead of just potentially 2 + const TMap<DWORD, DWORD>::Pair* sprpair; + TMap<DWORD, DWORD>::ConstIterator addSprite(intname); + int spritesUsed = intname.CountUsed(); + //for (int spr = 0; spr < 2; spr++) + while (addSprite.NextPair(sprpair)) { - memset (sprtemp, 0xFFFF, sizeof(sprtemp)); + auto sprkey = sprpair->Key; + auto sprval = sprpair->Value; + memset(sprtemp, 0xFFFF, sizeof(sprtemp)); for (k = 0; k < MAX_SPRITE_FRAMES; ++k) { sprtemp[k].Flip = 0; - sprtemp[k].Voxel = NULL; + sprtemp[k].Voxel = NULL; } maxframe = -1; - if (spr == 1) - { - if (crouchname !=0 && crouchname != intname) - { - intname = crouchname; - } - else - { - skins[i].crouchsprite = -1; - break; - } - } - - if (intname == 0) - continue; - // [BL] From the SKININFO parser // Loop through all the lumps searching for frames for this skin. - for ( k = 0; static_cast<signed> (k) < Wads.GetNumLumps( ); k++ ) + for (k = 0; static_cast<signed> (k) < Wads.GetNumLumps(); k++) { // Only process skin entries from the wad the SKININFO lump is in. // NOTE: If this isn't done, Skulltag doesn't work with hr.wad. - if ( Wads.GetLumpFile( base ) != Wads.GetLumpFile( k )) + if (Wads.GetLumpFile(base) != Wads.GetLumpFile(k)) continue; char lname[9]; DWORD lnameint; - Wads.GetLumpName( lname, k ); + Wads.GetLumpName(lname, k); memcpy(&lnameint, lname, 4); - if (lnameint == intname) + if (lnameint == sprval) { FTextureID picnum = TexMan.CreateTexture(k, FTexture::TEX_SkinSprite); if (!picnum.isValid()) continue; - bool res = R_InstallSpriteLump (picnum, lname[4] - 'A', lname[5], false); + bool res = R_InstallSpriteLump(picnum, lname[4] - 'A', lname[5], false); if (lname[6] && res) - R_InstallSpriteLump (picnum, lname[6] - 'A', lname[7], true); + R_InstallSpriteLump(picnum, lname[6] - 'A', lname[7], true); } } - if (spr == 0 && maxframe <= 0) + //Go on to the next sprite if there's no frames. + if (maxframe <= 0) { - Printf (PRINT_BOLD, "Skin %s (#%d) has no frames. Removing.\n", skins[i].name, (int)i); - remove = true; - break; + /*Printf(PRINT_BOLD, "Skin %s (#%d) - Sprite %.4s has no frames.\n", + skins[i].name, (int)i, &sprval);*/ + intname.Remove(sprval); + spritesUsed--; + continue; } - // [BB] S_SKIN only, unless we don't have a proper intname. - if ( ( s_skin != 0 ) || ( intname == 0 ) ) - Wads.GetLumpName (temp.name, base+1); - else - memcpy(temp.name, &intname, 4); + memcpy(temp.name, &sprval, 4); temp.name[4] = 0; - int sprno = (int)sprites.Push (temp); - if (spr==0) skins[i].sprite = sprno; - else skins[i].crouchsprite = sprno; - R_InstallSprite (sprno); + int sprno = (int)sprites.Push(temp); + + if (sprkey == 0) + skins[i].sprites[0] = skins[i].sprite = sprno; + + else if (sprkey == -1) + skins[i].sprites[-1] = skins[i].crouchsprite = sprno; + + else //if (GetSpriteIndex((char*)&sprkey) != -1) + skins[i].sprites[*(DWORD*)&sprkey] = sprno; + + R_InstallSprite(sprno); } + + //If there's no sprites at the end, then remove the skin. + if (spritesUsed == 0) + { + Printf(PRINT_BOLD, "Skin %s (#%d) has no frames. Removing.\n", skins[i].name, (int)i); + remove = true; + break; + } + } + if (remove) { skins.Delete(i); @@ -914,7 +1307,7 @@ // [TRSR] If a skin is deleted, we need to finish parsing it until its end to prevent the // last skin in Zandronum's SKININFO lump from double-removing in niche circumstances. if (s_skin == 0) - { + { while (sc.String[0] != '}') // Finish Parsing That Skin { @@ -935,12 +1328,12 @@ { if (j == 0 || sndlumps[j] != sndlumps[j-1]) { - aliasid = S_AddPlayerSound (skins[i].name, skins[i].gender, + aliasid = S_AddPlayerSound(skins[i].name, skins[i].gender, playersoundrefs[j], sndlumps[j], true); } else { - S_AddPlayerSoundExisting (skins[i].name, skins[i].gender, + S_AddPlayerSoundExisting(skins[i].name, skins[i].gender, playersoundrefs[j], aliasid, true); } } @@ -950,10 +1343,10 @@ if (skins[i].face[1] == 0 || skins[i].face[2] == 0) { skins[i].face[0] = 0; + skins[i].param["face"].list.Clear(); } } } - if (skins.Size() > PlayerClasses.Size ()) { // The sound table may have changed, so rehash it. S_HashSounds (); @@ -961,10 +1354,22 @@ } } + // [RH] Find a skin by name -int R_FindSkin (const char *name, int pclass) +int R_FindSkin (const char *name, int pclass, bool override = false) { - if (stricmp ("base", name) == 0) + if (pclass == -1) // [BOF] Just get the skin name itself if pclass isn't set. (Will use first instance) + { + for (unsigned int i = 0; i < skins.Size(); i++) + { + if (strnicmp(skins[i].name, name, MAX_SKIN_NAME) == 0 && + (skins[i].bRevealedByDefault || override)) + return i; + } + return NULL; + } + + if (stricmp("base", name) == 0) { return pclass; } @@ -972,12 +1377,14 @@ for (unsigned i = PlayerClasses.Size(); i < skins.Size(); i++) { // [BC] Changed from 16 to MAX_SKIN_NAME. - if (strnicmp (skins[i].name, name, MAX_SKIN_NAME) == 0) + if (strnicmp (skins[i].name, name, MAX_SKIN_NAME) == 0 && + (skins[i].bRevealedByDefault || override) ) { - if (PlayerClasses[pclass].CheckSkin (i)) + if (PlayerClasses[pclass].CheckSkin(i, override)) + { + //Printf("BITCH %s\n",skins[i].name); return i; - else - return pclass; + } } } return pclass; @@ -988,25 +1395,27 @@ { int i; ULONG ulNumSkins; + ULONG ulUniqueSkins; ULONG ulNumHiddenSkins; ulNumSkins = 0; + ulUniqueSkins = 0; ulNumHiddenSkins = 0; - for (i = PlayerClasses.Size ()-1; i < (int)skins.Size(); i++) + for (i = 0 ; i < (int)skins.Size(); i++) { - if ( skins[i].bRevealed ) + if (!skins[i].countSkin) continue; + if ( skins[i].bRevealed && skins[i].bRevealedByDefault ) { - Printf ("% 3d %s\n", static_cast<unsigned int> (ulNumSkins), skins[i].name); - ulNumSkins++; + Printf ("% 3d %s\n", static_cast<unsigned int> (++ulNumSkins), skins[i].name); } else ulNumHiddenSkins++; } if ( ulNumHiddenSkins == 0 ) - Printf( "\n%d skins; All hidden skins unlocked!\n", (int)skins.Size() ); + Printf( "\n%d skins; All hidden skins unlocked!\n", ulNumSkins); else - Printf( "\n%d skins; %d remain%s hidden.\n", (int)skins.Size(), static_cast<unsigned int> (ulNumHiddenSkins), ulNumHiddenSkins == 1 ? "s" : "" ); + Printf( "\n%d skins; %d remain%s hidden.\n", ulNumSkins, static_cast<unsigned int> (ulNumHiddenSkins), ulNumHiddenSkins == 1 ? "s" : "" ); } //***************************************************************************** @@ -1019,16 +1428,42 @@ const PClass *type = PlayerClasses[0].Type; skin.range0start = type->Meta.GetMetaInt (APMETA_ColorRange) & 255; skin.range0end = type->Meta.GetMetaInt (APMETA_ColorRange) >> 8; - skin.ScaleX = GetDefaultByType (type)->scaleX; - skin.ScaleY = GetDefaultByType (type)->scaleY; + skin.ScaleX = (intmap)skin.ScaleX; + skin.ScaleY = (intmap)skin.ScaleY; + skin.ScaleX[-1] = skin.ScaleX[0] = GetDefaultByType(type)->scaleX; + skin.ScaleY[-1] = skin.ScaleY[0] = GetDefaultByType(type)->scaleY; + skin.removable = false; + // [BC/BB] We need to initialize the default sprite, because when we create a skin // using SKININFO, we don't necessarily specify a sprite. skin.sprite = GetDefaultByType (type)->SpawnState->sprite; + skin.crouchsprite = 0; // [BL] Hidden skins skin.bRevealed = true; skin.bRevealedByDefault = true; + skin.countSkin = true; //For 'skins' command + // [BOF] Default Param Values + skin.param = (paramlist)skin.param; + + skin.param["class"].list.Resize(2); + skin.param["class"].list[0] = "0"; + skin.param["class"].list[1] = type->Meta.GetMetaString(APMETA_DisplayName); + + skin.param["sprite"].list.Resize(1); + skin.param["sprite"].list[0] = sprites[skin.sprite].name; + + skin.param["hidden"].list.Resize(1); + skin.param["hidden"].list[0] = "0"; + + skin.param["selectable"].list.Resize(1); + skin.param["selectable"].list[0] = "1"; + + skin.param["colorrange"].list.Resize(2); + skin.param["colorrange"].list[0].Format("%i", skin.range0start); + skin.param["colorrange"].list[1].Format("%i", skin.range0end); + skins.Push(skin); } @@ -1088,6 +1523,7 @@ for (i = 0; i < PlayerClasses.Size (); i++) { R_CreateSkin(); + if (i != 0) skins[i].countSkin = false; // [BOF] Exclude all other Base skins from 'skins' command. } R_InitSpriteDefs (); @@ -1096,13 +1532,16 @@ R_InitSkins (); // [RH] Finish loading skin data // [RH] Set up base skin - // [GRB] Each player class has its own base skin - for (i = 0; i < PlayerClasses.Size (); i++) +// [GRB] Each player class has its own base skin + for (i = 0; i < PlayerClasses.Size(); i++) { const PClass *basetype = PlayerClasses[i].Type; const char *pclassface = basetype->Meta.GetMetaString (APMETA_Face); strcpy (skins[i].name, "Base"); + strcpy (skins[i].displayname, "Base"); + + if (pclassface == NULL || strcmp(pclassface, "None") == 0) { skins[i].face[0] = 'S'; @@ -1116,11 +1555,10 @@ } skins[i].range0start = basetype->Meta.GetMetaInt (APMETA_ColorRange) & 255; skins[i].range0end = basetype->Meta.GetMetaInt (APMETA_ColorRange) >> 8; - skins[i].ScaleX = GetDefaultByType (basetype)->scaleX; - skins[i].ScaleY = GetDefaultByType (basetype)->scaleY; skins[i].sprite = GetDefaultByType (basetype)->SpawnState->sprite; skins[i].namespc = ns_global; + PlayerClasses[i].Skins.Push (i); if (memcmp (sprites[skins[i].sprite].name, "PLAY", 4) == 0) @@ -1134,152 +1572,192 @@ } } } + + skins[i].sprites[0] = skins[i].sprite; + skins[i].sprites[-1] = skins[i].crouchsprite; + + skins[i].param["name"].list.Resize(2); // [BOF] Base GetSkinInfo + skins[i].param["displayname"].list.Resize(1); + skins[i].param["face"].list.Resize(1); + + skins[i].param["displayname"].list[0] = + skins[i].param["name"].list[1] = skins[i].param["name"].list[0] = skins[i].name; + skins[i].param["face"].list[0] = skins[i].face; + skins[i].param["sprite"].list[0] = sprites[skins[i].sprite].name; + + skins[i].param["class"].list[0] = 0; + skins[i].param["class"].list[0].Format("%i", i); + skins[i].param["class"].list[1] = basetype->Meta.GetMetaString(APMETA_DisplayName); + + skins[i].param["colorrange"].list[0].Format("%i", skins[i].range0start); + skins[i].param["colorrange"].list[1].Format("%i", skins[i].range0end); } // [BB] Check if any of the skin sprites are ridiculously big to prevent // abusing the possibility to replace the skin sprites. - for ( unsigned int skinIdx = 0; skinIdx < skins.Size(); skinIdx++ ) + for (unsigned int skinIdx = 0; skinIdx < skins.Size(); skinIdx++) { // [BB] If the skin doesn't have a name, it's removed and doesn't need to be checked. // Removed skins for example are Doom skins in a Hexen games. - if ( skins[skinIdx].name[0] == 0 ) + if (skins[skinIdx].name[0] == 0) continue; - int maxwidth = 0, maxheight = 0; - char szTempLumpName[9]; - szTempLumpName[8]=0; - FString maxwidthSprite, maxheightSprite; + // [BB] Loop through all the lumps searching for sprites of this skin. // This may look very inefficient, but since this is only called once on // startup it's ok. - for ( ULONG ulIdx = 0; static_cast<signed> (ulIdx) < Wads.GetNumLumps( ); ulIdx++ ) + + // [BOF] Make it even longer, parsing through potentially + // arrays of sprites per skin as well, while we're at it. + + const TMap<int, int>::Pair* sprpair; + TMap<int, int>::ConstIterator checkSprite(skins[skinIdx].sprites); + while (checkSprite.NextPair(sprpair)) { - Wads.GetLumpName( szTempLumpName, ulIdx ); - if ( ( strnicmp ( szTempLumpName, sprites[skins[skinIdx].sprite].name, 4 ) == 0 ) - // [BB] Only check lumps that possibly can be used as sprite frames. - && ( static_cast<unsigned> ( szTempLumpName[4] - 'A' ) < MAX_SPRITE_FRAMES ) - // [BB] No need to check Death/XDeath frames. - && ( szTempLumpName[4] < 'H' ) ) + int maxwidth = 0, maxheight = 0; + char szTempLumpName[9]; + szTempLumpName[8] = 0; + FString maxwidthSprite, maxheightSprite; + + int sprval = sprpair->Value; + int sprkey = sprpair->Key; + + if (sprval == NULL) continue; + + for (ULONG ulIdx = 0; static_cast<signed> (ulIdx) < Wads.GetNumLumps(); ulIdx++) { - // [BB] Check if the lump can be used as sprite. If not, no need to check it. - if ( R_IsCharUsuableAsSpriteRotation ( szTempLumpName[5] ) == false ) - continue; - - if ( szTempLumpName[6] ) + Wads.GetLumpName(szTempLumpName, ulIdx); + if ((strnicmp(szTempLumpName, sprites[sprval].name, 4) == 0) + // [BB] Only check lumps that possibly can be used as sprite frames. + && (static_cast<unsigned> (szTempLumpName[4] - 'A') < MAX_SPRITE_FRAMES) + // [BB] No need to check Death/XDeath frames. + && (szTempLumpName[4] < 'H')) { - // [BB] Only check lumps that possibly can be used as sprite frames. - if ( static_cast<unsigned> ( szTempLumpName[6] - 'A' ) >= MAX_SPRITE_FRAMES ) - continue; - - // [BB] No need to check Death/XDeath frames. - if ( szTempLumpName[6] >= 'H' ) + // [BB] Check if the lump can be used as sprite. If not, no need to check it. + if (R_IsCharUsuableAsSpriteRotation(szTempLumpName[5]) == false) continue; - if ( R_IsCharUsuableAsSpriteRotation ( szTempLumpName[7] ) == false ) - continue; - } + if (szTempLumpName[6]) + { + // [BB] Only check lumps that possibly can be used as sprite frames. + if (static_cast<unsigned> (szTempLumpName[6] - 'A') >= MAX_SPRITE_FRAMES) + continue; - FTextureID texnum = TexMan.CheckForTexture (szTempLumpName, FTexture::TEX_Sprite); - FTexture *tex = (texnum.Exists()) ? TexMan[ texnum ] : NULL; - if ( tex ) - { - if ( tex->GetScaledHeight() > maxheight ) - { - maxheight = tex->GetScaledHeight(); - maxheightSprite = szTempLumpName; - } - if ( tex->GetScaledWidth() > maxwidth ) - { - maxwidth = tex->GetScaledWidth(); - maxwidthSprite = szTempLumpName; + // [BB] No need to check Death/XDeath frames. + if (szTempLumpName[6] >= 'H') + continue; + + if (R_IsCharUsuableAsSpriteRotation(szTempLumpName[7]) == false) + continue; } - } - } - } - int classSkinIdx = -1; - - // [BB] Find the player class this skin belongs to. - if ( !skins[skinIdx].othergame ) - { - for ( unsigned int pcIdx = 0; pcIdx < PlayerClasses.Size(); pcIdx++ ) - { - if ( classSkinIdx != -1 ) - break; - - for ( unsigned int pcSkinIdx = 0; pcSkinIdx < PlayerClasses[pcIdx].Skins.Size(); pcSkinIdx++ ) - { - if ( PlayerClasses[pcIdx].Skins[pcSkinIdx] == static_cast<int> (skinIdx) ) + FTextureID texnum = TexMan.CheckForTexture(szTempLumpName, FTexture::TEX_Sprite); + FTexture* tex = (texnum.Exists()) ? TexMan[texnum] : NULL; + if (tex) { - classSkinIdx = pcIdx; - break; + if (tex->GetScaledHeight() > maxheight) + { + maxheight = tex->GetScaledHeight(); + maxheightSprite = szTempLumpName; + } + if (tex->GetScaledWidth() > maxwidth) + { + maxwidth = tex->GetScaledWidth(); + maxwidthSprite = szTempLumpName; + } } } } - // [BB] The skin doesn't seem to belong to any of the the available player classes, so just check it against the standard player class. - if ( classSkinIdx == -1 ) - classSkinIdx = 0; - } - else - { - // [BB] The skin doesn't belong to this game, so just check it against the standard player class. - classSkinIdx = 0; - } + + + int classSkinIdx = -1; + + // [BB] Find the player class this skin belongs to. + if (!skins[skinIdx].othergame) + { + for (unsigned int pcIdx = 0; pcIdx < PlayerClasses.Size(); pcIdx++) + { + if (classSkinIdx != -1) + break; - // [TP] How big can the skin be? - const FMetaTable& meta = PlayerClasses[classSkinIdx].Type->Meta; - fixed_t maxwidthfactor = meta.GetMetaFixed( APMETA_MaxSkinWidthFactor ); - fixed_t maxheightfactor = meta.GetMetaFixed( APMETA_MaxSkinHeightFactor ); + for (unsigned int pcSkinIdx = 0; pcSkinIdx < PlayerClasses[pcIdx].Skins.Size(); pcSkinIdx++) + { + if (PlayerClasses[pcIdx].Skins[pcSkinIdx] == static_cast<int> (skinIdx)) + { + classSkinIdx = pcIdx; + break; + } + } + } + // [BB] The skin doesn't seem to belong to any of the the available player classes, so just check it against the standard player class. + if (classSkinIdx == -1) + classSkinIdx = 0; + } + else + { + // [BB] The skin doesn't belong to this game, so just check it against the standard player class. + classSkinIdx = 0; + } - // [TP] If either of the size factors are 0, we can just skip this. - if (( maxwidthfactor == 0 ) || ( maxheightfactor == 0 )) - continue; + // [TP] How big can the skin be? + const FMetaTable& meta = PlayerClasses[classSkinIdx].Type->Meta; + fixed_t maxwidthfactor = meta.GetMetaFixed(APMETA_MaxSkinWidthFactor); + fixed_t maxheightfactor = meta.GetMetaFixed(APMETA_MaxSkinHeightFactor); - AActor* def = GetDefaultByType( PlayerClasses[classSkinIdx].Type ); - fixed_t maxAllowedHeight = FixedMul( maxheightfactor, def->height ); - // [BB] 2*radius is approximately the actor width. - fixed_t maxAllowedWidth = FixedMul( maxwidthfactor, 2 * def->radius ); - FPlayerSkin& skin = skins[skinIdx]; + // [TP] If either of the size factors are 0, we can just skip this. + if ((maxwidthfactor == 0) || (maxheightfactor == 0)) + continue; - // [BB] If a skin sprite violates the limits, just downsize it. - bool sizeLimitsExceeded = false; - // [BB] Compare the maximal sprite height/width to the height/radius of the player class this skin belongs to. - // Massmouth is very big, so we have to be pretty lenient here with the checks. - // [TP] Also, allow 1px lee-way so that we won't complain about scale being off by a - // fraction of a pixel (would cause messages such as "52px, max is 52px"). - if ( maxheight * skin.ScaleY > maxAllowedHeight + FRACUNIT ) - { - sizeLimitsExceeded = true; - Printf ( TEXTCOLOR_RED "Sprite %s of skin %s is too tall (%dpx, max is %dpx). Downsizing.\n", - maxheightSprite.GetChars(), - skin.name, - ( maxheight * skin.ScaleY ) >> FRACBITS, - maxAllowedHeight >> FRACBITS ); - const fixed_t oldScaleY = skin.ScaleY; - skin.ScaleY = maxAllowedHeight / maxheight; - // [BB] Preserve the aspect ration of the sprites. - skin.ScaleX = static_cast<fixed_t> ( skin.ScaleX * ( FIXED2FLOAT( skin.ScaleY ) / FIXED2FLOAT( oldScaleY ) ) ); - } + AActor* def = GetDefaultByType(PlayerClasses[classSkinIdx].Type); + fixed_t maxAllowedHeight = FixedMul(maxheightfactor, def->height); + // [BB] 2*radius is approximately the actor width. + fixed_t maxAllowedWidth = FixedMul(maxwidthfactor, 2 * def->radius); + FPlayerSkin& skin = skins[skinIdx]; + + // [BB] If a skin sprite violates the limits, just downsize it. + bool sizeLimitsExceeded = false; + // [BB] Compare the maximal sprite height/width to the height/radius of the player class this skin belongs to. + // Massmouth is very big, so we have to be pretty lenient here with the checks. + // [TP] Also, allow 1px lee-way so that we won't complain about scale being off by a + // fraction of a pixel (would cause messages such as "52px, max is 52px"). + int scalekey = sprkey == 0 ? 0 : sprkey == -1 ? -1 : *(DWORD*)&sprkey; + if (maxheight * skin.ScaleY[scalekey] > maxAllowedHeight + FRACUNIT) + { - if ( maxwidth * skin.ScaleX > maxAllowedWidth + FRACUNIT ) - { - sizeLimitsExceeded = true; - Printf ( TEXTCOLOR_RED "Sprite %s of skin %s is too wide (%dpx, max is %dpx). Downsizing.\n", - maxwidthSprite.GetChars(), - skin.name, - ( maxwidth * skin.ScaleX ) >> FRACBITS, - ( maxAllowedWidth ) >> FRACBITS ); - const fixed_t oldScaleX = skin.ScaleX; - skin.ScaleX = maxAllowedWidth / maxwidth; - // [BB] Preserve the aspect ration of the sprites. - skin.ScaleY = static_cast<fixed_t> ( skin.ScaleY * ( FIXED2FLOAT( skin.ScaleX ) / FIXED2FLOAT( oldScaleX ) ) ); - } + sizeLimitsExceeded = true; + Printf(TEXTCOLOR_RED "Sprite (%s -> %s) of skin %s is too tall (%dpx, max is %dpx). Downsizing.\n", + (sprkey == 0 ? "sprite" : sprkey == -1 ? "crouchsprite" : sprites[sprval].name), maxheightSprite.GetChars(), + skin.name, + (maxheight * skin.ScaleY[scalekey]) >> FRACBITS, + maxAllowedHeight >> FRACBITS); + const fixed_t oldScaleY = skin.ScaleY[scalekey]; + skin.ScaleY[scalekey] = maxAllowedHeight / maxheight; + // [BB] Preserve the aspect ration of the sprites. + skin.ScaleX[scalekey] = + static_cast<fixed_t> (skin.ScaleX[scalekey] * (FIXED2FLOAT(skin.ScaleY[scalekey]) / FIXED2FLOAT(oldScaleY))); + } - // [BB] Don't allow the base skin sprites of the player classes to exceed the limits. - if ( sizeLimitsExceeded && ( skinIdx < PlayerClasses.Size () ) ) - { - I_FatalError ( "The base skin sprite of player class %s exceeds the limits!\n", PlayerClasses[skinIdx].Type->TypeName.GetChars() ); + if (maxwidth * skin.ScaleX[scalekey] > maxAllowedWidth + FRACUNIT) + { + sizeLimitsExceeded = true; + Printf(TEXTCOLOR_RED "Sprite (%s -> %s) of skin %s is too wide (%dpx, max is %dpx). Downsizing.\n", + (sprkey == 0 ? "sprite" : sprkey == -1 ? "crouchsprite" : sprites[sprval].name), maxwidthSprite.GetChars(), + skin.name, + (maxwidth * skin.ScaleX[scalekey]) >> FRACBITS, + (maxAllowedWidth) >> FRACBITS); + const fixed_t oldScaleX = skin.ScaleX[scalekey]; + skin.ScaleX[scalekey] = maxAllowedWidth / maxwidth; + // [BB] Preserve the aspect ration of the sprites. + skin.ScaleY[scalekey] = + static_cast<fixed_t> (skin.ScaleY[scalekey] * (FIXED2FLOAT(skin.ScaleX[scalekey]) / FIXED2FLOAT(oldScaleX))); + } + + // [BB] Don't allow the base skin sprites of the player classes to exceed the limits. + if (sizeLimitsExceeded && (skinIdx < PlayerClasses.Size())) + { + I_FatalError("The base skin sprite of player class %s exceeds the limits!\n", PlayerClasses[skinIdx].Type->TypeName.GetChars()); + } } } @@ -1311,17 +1789,18 @@ // If cl_skins == 0, then the user wishes to disable all skins. if ( self <= 0 ) { - lSkin = R_FindSkin( "base", players[ulIdx].CurrentPlayerClass ); + lSkin = R_FindSkin( "base", players[ulIdx].CurrentPlayerClass); // Make sure the player doesn't change sprites when his state changes. players[ulIdx].mo->flags4 |= MF4_NOSKIN; + } // If cl_skins >= 2, then the user wants to disable cheat skins, but allow all others. else if ( self >= 2 ) { if ( skins[players[ulIdx].userinfo.GetSkin()].bCheat ) { - lSkin = R_FindSkin( "base", players[ulIdx].CurrentPlayerClass ); + lSkin = R_FindSkin( "base", players[ulIdx].CurrentPlayerClass); // Make sure the player doesn't change sprites when his state changes. players[ulIdx].mo->flags4 |= MF4_NOSKIN; @@ -1331,7 +1810,9 @@ lSkin = players[ulIdx].userinfo.GetSkin(); if (( players[ulIdx].mo->GetDefault( )->flags4 & MF4_NOSKIN ) == false ) + { players[ulIdx].mo->flags4 &= ~MF4_NOSKIN; + } } } // If cl_skins == 1, allow all skins to be used. @@ -1344,9 +1825,17 @@ } // If the skin is valid, set the player's sprite to the skin's sprite. - if (( lSkin >= 0 ) && ( static_cast<unsigned> (lSkin) < skins.Size() )) + if (( lSkin >= 0 ) && ( static_cast<unsigned> (lSkin) < skins.Size() ) + // [BOF] Also add this check for cl_skins to not change to state frames that the skin doesn't have + && (players[ulIdx].mo->state->sprite == + GetDefaultByType(players[ulIdx].cls)->SpawnState->sprite || + skins[players[ulIdx].userinfo.GetSkin()].sprites.CheckKey(*(DWORD*)sprites[players[ulIdx].mo->state->sprite].name)) + && !(players[ulIdx].mo->flags4 & MF4_NOSKIN)) { - players[ulIdx].mo->sprite = skins[lSkin].sprite; + players[ulIdx].mo->sprite = + skins[lSkin].sprites.CheckKey(*(DWORD*)sprites[players[ulIdx].mo->state->sprite].name) ? + skins[lSkin].sprites[*(DWORD*)sprites[players[ulIdx].mo->state->sprite].name] : + skins[lSkin].sprite; /* players[ulIdx].mo->scaleX = skins[lSkin].ScaleX; players[ulIdx].mo->scaleY = skins[lSkin].ScaleY; @@ -1360,6 +1849,7 @@ players[ulIdx].mo->flags4 &= ~MF4_NOSKIN; } */ + } } } diff -r 8755ab2487fa src/r_data/sprites.h --- a/src/r_data/sprites.h Fri May 24 11:09:02 2024 -0400 +++ b/src/r_data/sprites.h Wed May 29 21:33:15 2024 -0700 @@ -39,9 +39,18 @@ WORD spriteframes; }; +struct params +{ + TArray<FString> list; //Regular array + TMap<FString, FString> charlist; //Array with string keys +}; + + + extern TArray<spriteframe_t> SpriteFrames; - +typedef TMap<int, int> intmap; +typedef TMap<FString, params> paramlist; // // [RH] Internal "skin" definition. // @@ -55,29 +64,56 @@ BYTE range0start; BYTE range0end; bool othergame; // [GRB] - fixed_t ScaleX; - fixed_t ScaleY; + intmap ScaleX; + intmap ScaleY; int sprite; int crouchsprite; int namespc; // namespace for this skin + bool countSkin; // For 'skins' command to not list duplicates. // [BC] New skin properties for Skulltag. // Default color used for this skin. - char szColor[16]; + int szColor; // [BOF] Change to an int; use V_GetColor to store value. // Can this skin be selected from the menu? bool bRevealed; // Is this skin hidden by default? - bool bRevealedByDefault; + bool bRevealedByDefault; // [BOF] If false, this skin can only be seen as a Weapon or ACS skin. True by default. // Is this skin a cheat skin? bool bCheat; + + // [BOF] Extra SKININFO values + paramlist param; // A dynamic list for GetSkinInfo to grab values from SKININFO, including custom ones. + intmap sprites; // Array variant using the sprite to replace as the key + char displayname[MAX_SKIN_NAME+1]; // Display name for menu (Perhaps we don't need this for skins? Quick to remove if so.) + + bool removable; // Allow removal with clearplayerskins when true. False by default. + // For mods of mods with skins already bundled in them. + + int wadnum; // For Removal of Skins outside of KEYCONF wad. + // Maybe this could be taken advantage of in the future to isolate + // skin sprites to their own section per wad to prevent sprite overlap. + + + // [BC] End of new skin properties. }; +extern class FPlayerSkinRemover +{ + public: + int KeyConf; //KeyConf Lump to keep skins in the same namespace. + bool RemoveAll; //Remove all skins including those not marked as removable? + int ClassNum; // Remove from a class specifically +}; + + // [BL] Use a TArray instead of trying to manage this manually extern TArray<FPlayerSkin> skins; // [RH] +extern TArray<FPlayerSkinRemover> skinremove; // Array to correctly iterate should multiple KEYCONFs that do this. + extern BYTE OtherGameSkinRemap[256]; extern PalEntry OtherGameSkinPalette[256]; diff -r 8755ab2487fa src/r_state.h --- a/src/r_state.h Fri May 24 11:09:02 2024 -0400 +++ b/src/r_state.h Wed May 29 21:33:15 2024 -0700 @@ -87,6 +87,6 @@ extern angle_t xtoviewangle[MAXWIDTH+1]; extern int FieldOfView; -int R_FindSkin (const char *name, int pclass); // [RH] Find a skin +int R_FindSkin (const char *name, int pclass = -1, bool override = false); // [RH] Find a skin #endif // __R_STATE_H__ diff -r 8755ab2487fa src/sv_commands.cpp --- a/src/sv_commands.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/sv_commands.cpp Wed May 29 21:33:15 2024 -0700 @@ -661,7 +661,7 @@ ServerCommands::SetPlayerACSSkin command; command.SetPlayer( &players[player] ); command.SetSkinName( players[player].ACSSkin ); - command.SetOverrideWeaponSkin( players[player].ACSSkinOverridesWeaponSkin ); + command.SetOverrideSkinSounds( players[player].ACSSkinOverridesSkinSounds ); command.sendCommandToClients( playerExtra, flags ); } diff -r 8755ab2487fa src/thingdef/thingdef_properties.cpp --- a/src/thingdef/thingdef_properties.cpp Fri May 24 11:09:02 2024 -0400 +++ b/src/thingdef/thingdef_properties.cpp Wed May 29 21:33:15 2024 -0700 @@ -2597,23 +2597,32 @@ } //========================================================================== -// -//========================================================================== -DEFINE_CLASS_PROPERTY_PREFIX(player, crouchsprite, S, PlayerPawn) +// [BOF] You can define crouch sprites for +// specific states in your class now to complete the package. +//========================================================================== +DEFINE_CLASS_PROPERTY_PREFIX(player, crouchsprite, Ss, PlayerPawn) { - PROP_STRING_PARM(z, 0); - if (strlen(z) == 4) + + PROP_STRING_PARM(z, 0); // Changed Sprite + PROP_STRING_PARM(z2, 1); // Sprite to Change + + if (PROP_PARM_COUNT > 1 && *z2 != 0 && strlen(z) == 4 && strlen(z2) == 4) { - defaults->crouchsprite = GetSpriteIndex (z); + info->CrouchSprites[GetSpriteIndex(z2)] = GetSpriteIndex(z); + } + else if (strlen(z) == 4) + { + info->CrouchSprites[0] = GetSpriteIndex(z); // Index 0 is for the Spawn State, the default. } else if (*z == 0) { - defaults->crouchsprite = 0; + info->CrouchSprites[0] = 0; } else { - I_Error("Sprite name must have exactly 4 characters"); + I_Error("Sprite name%s must have exactly 4 characters", *z2 == 0 ? "" : "s"); } + } //========================================================================== ![]() | ||||||||||||
Only registered users can voice their support. Click here to register, or here to log in. | |
Supporters: | No one explicitly supports this issue yet. |
Opponents: | No one explicitly opposes this issue yet. |
![]() |
|||
Date Modified | Username | Field | Change |
2024-05-30 04:38 | BarrelsOFun | New Issue | |
2024-05-30 04:38 | BarrelsOFun | File Added: skin_expansion.patch | |
2024-05-30 04:39 | BarrelsOFun | File Added: skinexpansion.pk3 | |
2024-05-30 15:40 | unknownna | Status | new => needs review |
Copyright © 2012-2025, Torr Samaho & Zandronum Team.
Doom and Doom II are the property of id Software.
Copyright © 2000 - 2025 MantisBT Team |