Cosmetics API
Developer library to simplify adding new Crossroads cosmetics to Hades II. Does nothing by itself.
| Date uploaded | a day ago |
| Version | 1.1.4 |
| Download link | NikkelM-Cosmetics_API-1.1.4.zip |
| Downloads | 248 |
| Dependency string | NikkelM-Cosmetics_API-1.1.4 |
This mod requires the following mods to function
LuaENVY-ENVY
A plugin to allow ReturnOfModding plugins greater control of their environment.
Preferred version: 1.2.0SGG_Modding-ReLoad
Provides helpers for managing hot reloading and waiting for the right time to load
Preferred version: 1.0.2SGG_Modding-ModUtil
Utility mod for mod interactions within lua for SGG's games
Preferred version: 4.0.1README
Cosmetics API
Developer library to simplify adding new Crossroads cosmetics to Hades II. Does nothing by itself.
Features
Through this library, you can easily add new cosmetic items to the Crossroads, as offered by Dora. You can add alternative versions of any existing cosmetic, both "Alt Decor" and "Extra Decor" items are supported. Adding entirely new cosmetics (in different locations), or replacing assets that aren't already a cosmetic is not supported.
The library will automatically handle grouping cosmetics so that equipping one will unequip any other already equipped cosmetic in the same group. You can provide names, descriptions and flavour texts for various languages, as well as customize the voicelines used when purchasing, equipping or unequipping the cosmetic.
For even more advanced usecases, you can also provide a function name that is called when your cosmetic is bought or equipped.
You can also register new Arcana Card Back packs. The API handles shop integration, animation hookup and pagination when the card back selection screen exceeds one page.
Usage
Start by adding NikkelM-Cosmetics_API as a dependency in your thunderstore.toml (ensure you use the latest version):
NikkelM-Cosmetics_API = "1.0.0"
Next, include the Cosmetics API in your main.lua, alongside other dependencies:
---@module "NikkelM-Cosmetics_API"
CosmeticsAPI = mods["NikkelM-Cosmetics_API"]
Registering Packages
If your cosmetics use custom textures bundled in .pkg files, you can register those packages so the API loads them when entering the Crossroads.
Place your .pkg (and .pkg_manifest) files in your mod's plugins_data folder and call RegisterCrossroadsPackages with a table of package names (without the .pkg extension):
CosmeticsAPI.RegisterCrossroadsPackages({ "AuthorName-ModNameCosmetics" })
The API will automatically load all registered packages when the player enters the Crossroads (including the Training Grounds), regardless of which transition is used (DeathAreaRoomTransition, HubPostBountyLoad, or HubPostDreamLoad).
Duplicate package names are silently ignored.
You can alternatively also load the packages yourself within your mod code.
Registering Cosmetics
Now, you can add a new cosmetic by calling CosmeticsAPI.RegisterCosmetic(cosmeticData), where cosmeticData is of type CosmeticData.
If you have your development environment set up correctly, VS Code should offer autocompletion and type hints for this table.
Otherwise, you can always refer to the def.lua file in the Cosmetics API source, or the below example, for all available fields.
CosmeticsAPI.RegisterCosmetic({
-- REQUIRED FIELDS
Id = _PLUGIN.guid .. "." .. "Cosmetic_Pillars_Chronos",
-- At least "en" must be provided for Name, Description and FlavorText
Name = {
en = "Pillars, Timeless",
de = "...",
},
Description = {
-- "{$Keywords.CosmeticSwap}:" resolves to "Alt Decor" with a tooltip for something that replaces something, "{$Keywords.CosmeticAltAdd}:" to "Extra Decor" with a tooltip that it adds or replaces something similar, and {$Keywords.CosmeticAdd} resolves to "Extra Decor" for something that did not exist before. You should not need to use CosmeticAdd, since all cosmetics added through the Cosmetics API replace an existing cosmetic.
en = "{$Keywords.CosmeticSwap}: Time-worn monoliths that stand tall to either side of the {#BoldFormatGraftDark}Cauldron{#Prev}.",
de = "...",
},
FlavorText = {
en = "Nothing stands the test of time they say, yet these pillars beg to differ.",
de = "...",
},
-- Which of Dora's shop locations to add this cosmetic to. One of "CosmeticsShop_Tent" (Mel's Tent), "CosmeticsShop_Main" (Crossroads Main Grounds & West), "CosmeticsShop_Taverna" (Taverna & Crossroads West), "CosmeticsShop_PreRun" (Training Grounds)
ShopCategory = "CosmeticsShop_Main",
-- Which existing cosmetic the new one is a variant of
CosmeticsGroup = "Cosmetic_CauldronPillars01",
-- The in-world asset when the cosmetic is equipped
CosmeticAnimationPath = "AuthorName-ModName\\FolderPath\\Pillars_Chronos",
-- You can often reuse your animation path asset as an icon if you scale it correctly, though the icon might look grainy and be harder to decipher if the cosmetic is detailed
IconPath = "AuthorName-ModName\\FolderPath\\Pillars_Chronos_Icon",
-- OPTIONAL FIELDS (with their defaults)
AnimationScale = 1,
AnimationInheritFrom = nil,
AnimationOffsetX = 0,
AnimationOffsetY = 0,
IconScale = 1,
IconOffsetX = 0,
IconOffsetY = 0,
-- Which other cosmetic in the same shop category to insert your new one after, or nil to add to the end
InsertAfterCosmetic = nil,
-- Try to limit to a maximum of four resources (including CosmeticsPoints) for optimal display in the UI
Cost = {
CosmeticsPoints = 50,
},
-- Must be met before the cosmetic becomes available for purchase
GameStateRequirements = {},
-- Note: some field validations performed by the API are disabled if you provide this property
InheritFrom = { "DefaultCosmeticItem" },
-- If the new cosmetic can show up in the shop as soon as eligible, even if the shop has already been viewed in the current Crossroads session. Otherwise, will show after the next run
AlwaysRevealImmediately = false,
-- Overrides where the camera pans when equipping the cosmetic
CameraFocusId = nil,
-- At least one of SetAnimationIds or ActivateIds must be provided (or inherited)
SetAnimationIds = nil,
ActivateIds = nil,
DeactivateIds = nil,
ToggleCollision = nil,
ActivateFunctionName = nil,
OnRevealFunctionName = nil,
PanDuration = 1,
PreActivationHoldDuration = 0.5,
PostActivationHoldDuration = 1.5,
PreRevealVoiceLines = { ... },
RevealReactionVoiceLines = { ... },
CosmeticRemovedVoiceLines = { ... },
CosmeticReEquipVoiceLines = { ... },
-- If your cosmetic is a new cauldron, you will need to set this to true so the API knows to apply cauldron-specific logic (like handling which cauldron lid the game uses)
IsCauldron = false,
-- Cauldron scaling and location is not changeable through sjson, if your lid is not aligned properly, you will need to change your asset directly
CauldronLidAnimationPath = nil,
})
Registering Card Back Packs
You can add new Arcana card back packs to the Training Grounds cosmetics shop. Each pack unlocks a set of card backs when purchased. The API handles shop display, animation hookup and pagination when the selection screen exceeds one page.
Card backs require four types of textures, in different packages. See the bottom of this section for example textures for each of the four.
| Texture | Per pack or per card? | Used Where | Example path | Package Type |
|---|---|---|---|---|
| DeckArt (idle) | Per card | Card back picker idle animation in the Arcana screen (Three overlayed copies of the card) | GUI\Screens\MetaUpgrade\DeckArt\Deck10 |
RegisterCrossroadsPackages |
| DeckArtMouseover (hover) | Per card | Card back picker hover animation in the Arcana screen (Four spread out copies of the card) | GUI\Screens\MetaUpgrade\DeckArt\DeckMouseover10 |
RegisterCrossroadsPackages |
| IconPath (shop preview) | Per pack | Training Grounds cosmetics shop icon (Three different fanned-out cards from the pack) | GUI\Screens\CosmeticIcons\cosmetic_deckMisc |
RegisterCrossroadsPackages |
| CardBack (in-combat flip) | Per card | During runs when an Arcana is added, e.g. through Judgment (Single straight-aligned copy of the card) | GUI\Screens\CardBack\CardBack10 |
RegisterCardBackPackages |
First, register your packages:
-- DeckArt (Idle and Hover, per card) + shop icon (per pack) - only needed and loaded in the Crossroads
CosmeticsAPI.RegisterCrossroadsPackages({ "AuthorName-ModName-DeckArt" })
-- CardBack (per card) - loaded at ALL times (including during runs), keep these packages small!
CosmeticsAPI.RegisterCardBackPackages({ "AuthorName-ModName-CardBacks" })
Then register a card back pack:
CosmeticsAPI.RegisterCardBackPack({
-- REQUIRED FIELDS
Id = _PLUGIN.guid .. "." .. "MyCardBackPack",
Name = {
en = "Arcana, Modded",
},
Description = {
en = "{$Keywords.CosmeticDeck}: Set of {#UpgradeFormatDark}<Number of cards in this deck> {#Prev}alternate themes, featuring <something about this deck>.",
},
FlavorText = {
en = "Every card tells a story.",
},
-- Shop preview icon - a small thumbnail (110×110 px). See vanilla examples: GUI\Screens\CosmeticIcons\cosmetic_deckMisc
IconPath = "AuthorName-ModName\\Icons\\MyPackIcon",
-- OPTIONAL FIELDS (with their defaults)
IconScale = 1,
IconOffsetX = 0,
IconOffsetY = 0,
Cost = { CosmeticsPoints = 300 },
-- Unlocking Arcana card packs will *always* require layout saving to be unlocked (WorldUpgradeMetaUpgradeSaveLayout), in addition to any custom checks added here
GameStateRequirements = nil,
-- Insert after a specific cosmetic in the Training Grounds shop, or nil to append to end
InsertAfterCosmetic = nil,
-- Defaults to "How about a new look for the old Arcana..." when nil
PreRevealVoiceLines = nil,
})
Registering Card Backs
After registering a pack, register individual card backs for it. Card backs will be ordered by registration order within their pack.
CosmeticsAPI.RegisterCardBack({
-- REQUIRED FIELDS
Id = _PLUGIN.guid .. "." .. "MyCardBack_01",
-- Must match a previously registered pack ID
PackId = _PLUGIN.guid .. "." .. "MyCardBackPack",
-- Idle card art for the selection overlay (248×318 px). See vanilla: GUI\Screens\MetaUpgrade\DeckArt\Deck10
DeckArtPath = "AuthorName-ModName\\DeckArt\\MyDeck10",
-- Card back for the in-combat flip animation (453×680 px). See vanilla: GUI\Screens\CardBack\CardBack10
CardBackPath = "AuthorName-ModName\\CardBack\\MyCardBack10",
-- OPTIONAL FIELDS
-- Highlighted variant shown on hover (330×308 px). See vanilla: GUI\Screens\MetaUpgrade\DeckArt\DeckMouseover10
-- If nil, uses DeckArtPath (no hover effect)
DeckArtMouseoverPath = "AuthorName-ModName\\DeckArt\\MyDeckMouseover10",
DeckArtScale = nil,
})
Card backs from the same pack will appear grouped together in the selection screen. The API automatically handles pagination when the total number of unlocked card backs exceeds 40 (one page).
Sample card back textures
Textures should match vanilla dimensions exactly - the engine derives the display scale from the source texture size, and mismatched dimensions will cause card backs to appear at the wrong size.
| Texture | Vanilla Dimensions | Used In | Resizable via API? |
|---|---|---|---|
| DeckArt (per card) | 248 × 318 px | Card back picker overlay (idle) | Yes - DeckArtScale |
| DeckArtMouseover (per card) | 330 × 308 px | Card back picker overlay (hover) | Yes - DeckArtScale |
| CardBack (per card) | 453 × 680 px | In-combat Arcana card flip animation | No - the game applies a hardcoded scale during the flip animation, so this texture must match vanilla dimensions |
| IconPath (per pack) | 110 × 110 px | Training Grounds cosmetics shop | Yes - IconScale |
DeckArt (per card)
GUI\Screens\MetaUpgrade\DeckArt\Deck10

DeckArtMouseover (per card)
GUI\Screens\MetaUpgrade\DeckArt\DeckMouseover10

IconPath (per pack)
GUI\Screens\CosmeticIcons\cosmetic_deckMisc

CardBack (per card)
GUI\Screens\CardBack\CardBack10

Important Note & Contributing
If you want to add a variant cosmetic for a base game "Extra Decor" item, the Cosmetics API must know the CosmeticAnimationPath (SetAnimationValue) of the base game cosmetic, as these are not part of the cosmetics definition for all "Extra Decor" items.
The Cosmetics API has a list of known animation names for some cosmetics (naturally growing on-demand), so if you want to add a new variant, you must first open a PR against the Cosmetics API to add the unknown animation name to the list.
If the Cosmetics API does not know the base animation name, it will throw an error when you try to register your new cosmetic.
You can find the list under ./src/Scripts/Utils.lua, mod.KnownExtraDecorBaseAnimations.
You can most often get the SetAnimationValue from the Game/Obstacles/Crossroads.sjson file.
Most obstacles in this file are named "Crossroads<Cosmetic name without "Cosmetic_" prefix>01".
The SetAnimationValue we need is the Thing.Graphic of the obstacle, NOT the obstacle name itself.
Some cosmetics may require additional overrides to work correctly with modded cosmetics, you can add them there as well.
This is e.g. the case if the vanilla cosmetic defines ActivateIds, which would all get set to the new SetAnimationValue if no separate SetAnimationIds table is provided.
CHANGELOG
Changelog
v1.1.4
- Fixed: If a modded cosmetic is added to a
RotateOnlygroup (cosmetics that cannot be unequipped), the option to unequip it still shows, but does nothing. - Fixed: If a cosmetic in a non-
RotateOnlygroup is unequipped without replacement, the CosmeticsAPI will re-equip an owned cosmetic when the map is next loaded, instead of leaving the group unequipped.
v1.1.3
- Fixed: Table formatting in Readme is broken.
v1.1.2
- Fixed: Navigating right from a rightmost Arcana card takes additional navigation events to select the Grasp count.
v1.1.1
- Removed
CardBackScaleproperty, as the game won't honour it. - Added recommended texture sizes for card back (pack) textures.
- Added example textures for card back (packs) to the Readme.
v1.1.0
- The API now supports adding new Arcana card backs, and adds additional pages to the selection screen to display them. Use
RegisterCardBackPack(packData)to register a card back pack andRegisterCardBack(cardBackData)to register card backs in a pack. RegisterCardBackPackages(packageNamesArray)allows registering one or more.pkgpackages that the Cosmetics API will automatically load everywhere. This should only contain the in-run card pack textures required when a new Arcana is equipped during a run, e.g. through Judgment.RegisterCrossroadsPackages(packageNamesArray)allows registering one or more.pkgpackages that the Cosmetics API will automatically load in the Crossroads. Use this to register packages containing cosmetics textures.
v1.0.4
- Fixed an issue where the game could fall back and display a cosmetic you do not own if a mod is disabled while a cosmetic it added is equipped.
- Fixed an issue where returning from a Dream Dive would not run some validation logic.
v1.0.3
- Added
ToggleCollisionproperty. - Added vanilla
SetAnimationValueforCosmetic_SkellyZagreusStatue.
v1.0.2
- Fixed potential issues with modded and vanilla cosmetics being equipped at the same time if the mod was temporarily disabled and the player returns from a Chaos Trial.
v1.0.1
- The default
RevealReactionVoiceLineshave been updated to include more varied voicelines by Melinoë.
v1.0.0
- Initial release.