Install, staff-command, permission, content and store-integration reference for server operators. For how the shop plays (tabs, profiles, wearables, skins, emotes, guild skies and boosts) see the Scuffed Cosmetics gameplay guide.
- Plugin version: 2.39.0 (plugin name
ScuffedCosmetics) - Engine range:
>=0.7.0-pre.0 <0.8.0
Requirements
| Plugin | Version | Type |
|---|---|---|
| Hytale AssetModule | any | Required |
| EndlessLevelingCore | any | Optional. Enables shared storage between servers, the grant outbox, the /ec menu entry and the prestige lookup for the Endless Origin award |
| Hytale NPC module | any | Optional (declared in the manifest). Stand and Wander showcase stands are NPCs |
| EndlessLevelingOfficialServerPartner | any | Optional (declared in the manifest) |
| Endless Guilds | any | Optional. Guild land skies |
| ScuffedProtection | 1.1.43+ | Optional. Shows guild skies in the world |
| ScuffedProfessions | 0.25.71+ | Optional. Without it the Boosts tab is hidden, and boosts a player already owns cannot start |
Without Endless Leveling every player record, price override and code lives in local files under mods/ScuffedCosmetics/. With an active Endless Leveling shared storage backend the same data is shared by every server on the network.
The plugin ships its own asset pack (IncludesAssetPack). It also writes up to three generated packs into mods/ (see below).
Installation and generated packs
Drop the jar into mods/ and start the server. On first start the plugin creates mods/ScuffedCosmetics/ and a default config.json.
Content you add by dropping files (costumes, player skins, particles) is packed into generated jars before the engine loads assets, so one restart loads new content:
| Generated file | Built from | Holds |
|---|---|---|
mods/SC_Costumes.jar | mods/ScuffedCosmetics/costumes/ plus the sets built into the plugin jar | Costumes, back items, tails, headwear |
mods/SC_PlayerSkins.jar | mods/ScuffedCosmetics/playerskins/ | Player skins |
mods/SC_Particles.jar | mods/ScuffedCosmetics/particles/ | Custom particle systems |
Do not edit or delete these by hand while the server runs. Each is also rebuilt at shutdown so files dropped during a session load on the next boot.
An invalid model can make a whole generated pack fail to load, especially on servers started with asset validation. Test new models locally first. To recover, remove the bad folder and the matching generated jar, then restart.
Admin & Staff Commands
The player command is /cosmetic (aliases /cosmetics, /cos, /scos, /sc). Every admin command is /cosmetic admin <command>. They run from the console without any check (each console use is written to the log as an audit line) and from a player who is an operator or holds scuffedcosmetics.admin.
Where a command takes <player> it accepts an online name, a UUID, or a name the shared name index knows from any server. Where a token or cosmetic grant cannot find a player at all, it is parked under that name and delivered to the first player who logs in with it.
| Command | Description |
|---|---|
/cosmetic admin grant <player> <id> [--txn <txn>] | Give a cosmetic or profile. See list for ids |
/cosmetic admin revoke <player> <id> | Take a cosmetic or profile away. A cosmetic the player had equipped is unequipped live |
/cosmetic admin tokens give <player> <amount> [--txn <txn>] | Add Premium Tokens |
/cosmetic admin tokens take <player> <amount> | Subtract tokens. Refused when the player has fewer |
/cosmetic admin tokens set <player> <amount> | Set the balance |
/cosmetic admin list | Print every cosmetic and profile with its id, price (or award-only) and unlock node |
/cosmetic admin delete <id> | Delete an admin-created cosmetic or designer profile. Built-ins are refused. Online wearers are unequipped |
/cosmetic admin reloadconfig | Re-read config.json and print the current store link |
/cosmetic admin endlessorigin <preview|award|status|award-local> | The one-time Endless Origin award (below) |
/cosmetic admin showcase list | List every showcase stand |
/cosmetic admin showcase info <id> | Every field of one stand, plus the items it cannot show |
/cosmetic admin showcase poses [group] | List pose groups, or the poses in one group |
/cosmetic admin showcase set <id> <field> <value> | Change one field. A value of - clears it |
/cosmetic admin showcase outfit <id> <add|remove> <cosmeticId> | Add or remove an item the stand wears |
/cosmetic admin showcase tphere <id> | Move the stand to where you stand, facing the way you face. Player only |
/cosmetic admin showcase reload | Re-read showcases.json and respawn every stand |
/cosmetic editor | Open the visual particle and weapon-effect editor. Operators and scuffedcosmetics.admin only |
/cosmetic diag | Report whether your equipped effects are still loaded and re-apply them. Operators and scuffedcosmetics.admin only |
Every grant, revoke and token change is checked and written off the world thread. A change reports its result in the reply, and a failed storage write says FAILED so a store can retry.
The --txn argument
--txn is the replay-protection id for store deliveries. It must be 1 to 64 characters with no control characters and no { or }. A repeated id is refused as already delivered, so a re-fired webhook cannot double-credit.
A command without --txn, or with an unusable one, still delivers (it gets a one-off id) but is not protected against replays. A queued delivery says so in its reply, and an unusable id logs an audit warning. See Store integration below.
Endless Origin award
endlessorigin hands the Endless Origin profile once to every player whose best Endless Leveling profile is at prestige 20 or higher, online or not.
| Action | Effect |
|---|---|
preview | Lists who qualifies. Grants nothing |
award | Runs the award. It claims a marker first so no other admin or server can run it again, freezes the recipient list, and delivers through the grant outbox |
status | Shows whether the award has run, who ran it and the counts |
award-local | Same as award on a standalone server with no shared storage, where the marker is only local |
If a run is interrupted, run award again: it finishes the same frozen list and nobody is paid twice. After it closes, the profile is only available through /cosmetic admin grant <player> profile_endless_origin. The award needs Endless Leveling for the prestige lookup.
Permissions
| Node | Effect |
|---|---|
scuffedcosmetics.admin | Admin tools: the Admin View switch, the Admin and Editor tabs, /cosmetic admin ..., /cosmetic editor, /cosmetic diag. Operators have it implicitly |
scuffedcosmetics.unlock.<id> | The player can equip that cosmetic or profile without owning it. Find ids with /cosmetic admin list |
scuffedcosmetics.unlock.* | Unlocks every cosmetic and profile |
scuffedcosmetics.emote.bypass | Plays every emote without owning it. Operators have it implicitly |
- Unlocks are live. The item is never added to the player's saved collection, so removing the node removes the item, and a player who no longer qualifies has it unequipped at their next login. Weathers are the exception: an unlock node does not count as owning one.
- The checks read nodes from the permission provider as exact strings. Grant
scuffedcosmetics.unlock.*andscuffedcosmetics.adminthemselves, not a broader wildcard. /cosmeticand/emoteare open to the defaulthytale:Adventurergroup so every player can use them. The admin subcommands do their own check on top.- The plugin registers its own
/emote, which replaces the base one. It behaves the same for emotes the player may play, and for the rest it points the player to the shop.
Admin View and the in-menu tools
Operators and scuffedcosmetics.admin holders see an Admin View switch in the menu. It starts off, so staff see the shop as a player sees it. Turning it on (it is remembered until the server restarts) adds the Admin and Editor tabs and admin controls on every card.
Controls on a selected item:
| Control | Effect |
|---|---|
| Grant to Me | Gives the item to you |
| For Sale | Toggles between purchasable and award-only |
| Hide | Takes the item off every non-admin screen, owners included |
| Retire | Takes it out of the shop. Players who own it keep seeing and using it |
| Remove | Removes it for good, after a confirmation (below) |
| Rename | Sets a display name, up to 32 characters |
| Price | Sets the token price |
| Select multiple | Applies Show, Hide, For sale, Award only or a price to many items. First / Earlier / Later / Last reorder items inside their section |
The Admin tab also holds, for an online player, token Give / Take / Set and a grant and revoke list; a paged Purchase history; Giveaway Codes; the Retired From Shop and Removed For Good lists with Return to shop and Restore buttons; and the Showcase Stands manager.
These overrides are saved in admin_overrides.json and, with shared storage, apply on every server. They cover price, sale state, hidden, retired, the name, the order and removal.
Remove for good unloads the item from the next boot: the generated packs are built without it and nothing can list, buy, grant or equip it. The files stay on disk, and Restore reverses it at the following boot. A removal is refused when:
- any non-admin player owns the item or holds its unlock node;
- a store purchase of it is still waiting to be delivered;
- another item is built from it;
- other code depends on it (
costume_demonlord_wings_fast, used by the Endless Blade during Massacre, andplayerskin_default).
The owners are read again shortly after, and the removal is undone if someone bought the item on another server in the meantime.
Configuration
mods/ScuffedCosmetics/config.json is written with defaults on first start. Unknown keys are kept when the plugin rewrites it. Run /cosmetic admin reloadconfig after editing.
{
"shopUrl": "https://store.example.com",
"grantSweepSeconds": 60,
"grantRetentionDays": 14,
"contentSync": "off",
"endlessOriginLength": "medium"
}
| Key | Default | Effect |
|---|---|---|
shopUrl | A link to the Endless Leveling store | Where the Get Tokens button sends players. It must start with http:// or https://. Set your own store link, or leave it blank to hide the button |
grantSweepSeconds | 60 | How often the grant outbox is checked for deliveries owed to players on this server. Range 15 to 600. Read at startup |
grantRetentionDays | 14 | How long a delivered grant row is kept before it is pruned. Range 1 to 365 |
contentSync | off | source, mirror or off. See Content sync |
endlessOriginLength | medium | How far the Endless Origin ribbons stream: short, medium or long |
A numeric value that is out of range is clamped. An unknown contentSync or endlessOriginLength value is logged and treated as off and medium. A config file that cannot be read keeps the settings already in memory.
Other files under mods/ScuffedCosmetics/
| Path | Purpose |
|---|---|
players/<uuid>.json | One record per player: tokens, owned items, equipped items, history |
admin_overrides.json | Per-item hidden, sale, price, name, order, retired and removed state |
custom_cosmetics/<id>.json | Cosmetics created in the editor |
profiles/<id>.json | Saved designer profiles, loaded at startup |
giveaway_codes.json | Giveaway codes |
emotes.json, weathers.json, playerskins.json | Optional shop overrides, described below |
showcases.json, showcase_stats.json | Showcase stands and their interaction counts |
guild_weathers.json | The sky each guild chose per chunk on this server |
txn_seen.txt | Store transaction ids delivered while shared storage was unavailable |
server-id.txt | This server's generated identity for shared storage |
awards/endless-origin.json | Local Endless Origin award marker on a standalone server |
unsynced/<time>/ | Files a content-sync mirror moved aside, kept 30 days |
Adding content
Everything here is data. No code is needed. All prices are in Premium Tokens and can be changed later from the menu or with the override files.
Costumes, back items, tails and headwear
Put models in mods/ScuffedCosmetics/costumes/, then restart.
- Author a chest-armor-style
.blockymodel. The rig chain isPelvis > Belly > Chestwith your geometry as child nodes ofChest. The rig bones should be pieces (isPiece); the packer repairs known bone names, but authoring them correctly is safer. - Drop
<Name>.blockymodeland<Name>.png(the texture) into the folder. Add<Name>.icon.pngfor the shop icon and<Name>.jsonfor the settings. - Restart. The costume appears under Costumes with the id
costume_<name>, where the name is lowercased and runs of other characters become_.
A model with no matching .png (or <Name>_<Variant>.png) is skipped with a log line. Two files whose names reduce to the same id clash, and the later one is skipped.
Slots. One item per slot can be worn at once. The slot is set with slot in the sidecar, and each non-costume slot needs a particular anchor bone:
slot | Slot | Required anchor bone (as an isPiece node) |
|---|---|---|
absent, costume or full | Full costume | None |
back | Back item | Back-Attachment or Chest |
tail | Tail | Pelvis |
head | Headwear | Head |
An unknown or non-string slot, or a missing anchor bone, rejects the drop with a log message naming the folder. Back, tail and head items must keep decoupled at true.
Sidecar <Name>.json, all keys optional:
| Key | Default | Effect |
|---|---|---|
name | The file name with _ as spaces | Display name |
description | Custom costume. | Card text |
price | 800 | Token price |
forSale | true | false makes the item award-only |
rarity | RARE | COMMON, RARE or LEGENDARY |
slot | costume | See above |
decoupled | true | false puts a full costume on the legacy armor-visual channel: it replaces the look of the chest armor and animations are ignored |
backFit | true | false turns off the automatic back fit |
animations | none | Per-state animation files, see below |
animationSpeed | 1.0 | Multiplies the playback speed of every overridden animation. Must be above 0 |
variants | none | Names and overrides for colour variants |
allowLargerTextures | false | Lets a dropped copy use a bigger texture than the built-in copy of the same costume |
A # in a name or description is removed.
{
"name": "Phoenix Wings",
"description": "Wings of living flame.",
"slot": "back",
"price": 1200,
"forSale": true,
"rarity": "LEGENDARY",
"variants": {
"Red": "Crimson Wings",
"Gold": { "name": "Gilded Wings", "price": 2000 }
}
}
Colour variants. Extra textures named <Name>_<Variant>.png become separate shop items sharing the one model. Add <Name>_<Variant>.icon.png for their icons. A variant entry is a plain string (its name) or an object with name, description, price, forSale and rarity. If the main item is award-only, its variants stay award-only unless the variant sets forSale to true.
Animated wings. Give the model extra bones under a rig bone, author replacement player animations that also move them, drop the .blockyanim files next to the model and map them by Hytale animation state:
"animations": {
"Idle": "Idle_Wings.blockyanim",
"Fall": { "files": "Fall_Wings.blockyanim", "blend": 0.4 },
"JumpSprint": [ "Jump_Far_Wings.blockyanim", "Jump_Far2_Wings.blockyanim" ]
},
"animationSpeed": 1.5
A single file applies to every entry of a state, a list maps entry by entry, blend sets that state's blending time and a per-state speed overrides animationSpeed. The plugin bakes a player model per animated costume, and only the wearer is switched onto it.
Folders. A costume can live in its own subfolder, which keeps animated sets tidy. Names must stay unique across all folders. A file named in animations is looked for in the costume's folder, then in _shared/, then at the top level. Folders starting with _ or . are not scanned as costumes. Moving a costume into a folder changes nothing for players.
Built-in sets. The plugin jar carries the wing sets Celestial, Demonlord, Feathered, Fire, Justicar, Primordial Aatrox and the Clockwork Aegis and Servo Array pair, and six Pride Capes. Nothing needs to be dropped for them. To retune one, copy all of its files into the drop folder under the same names and edit them: a complete copy replaces the built-in one. A copy missing any file the built-in one has (the usual result of an interrupted upload) is ignored with an INCOMPLETE warning, and the built-in copy is used. A copy with a larger texture is ignored unless allowLargerTextures is set, because oversized costume textures can crash clients when an entity spawns.
By default the Celestial, Primordial Aatrox, Clockwork Aegis and Servo Array wings are on sale and the Demonlord, Feathered, Fire and Justicar wings are award-only. The six Pride Capes are on sale. Change any of this with the override controls.
Only ship models, textures and animations you made or have permission to distribute. Do not copy Hytale game assets into a costume.
Player skins
Full-body model swaps. Make one subfolder per skin in mods/ScuffedCosmetics/playerskins/<Name>/ and add the body-part models and textures, authored against the vanilla player rig:
playerskins/Samurai/
Head.blockymodel Head.png
Chest.blockymodel Chest.png (or Chest_Texture.png)
Hands.blockymodel Hands.png
Legs.blockymodel Legs.png
Cape.blockymodel Cape.png (optional)
The base body, animations and face come from the game's own player model. Restart once. All skins are packed into mods/SC_PlayerSkins.jar, appear under Skins with the id playerskin_<name>, and a free Default (Your Skin) entry is always added.
Optional skin.json in the skin folder:
| Key | Default | Effect |
|---|---|---|
name | The folder name, spaced | Display name |
price | 1500 | Token price |
icon | Ingredient_Motes_Light | Item id used as the card icon |
scale | Asset default | Model scale |
keepFace | true | Keep the vanilla face attachments |
injectHead | Automatic | Keep the wearer's own head and hair. On by default when the skin ships no head part |
keepAccessories | true | Keep the wearer's headband, glasses, earrings and cape |
keepFaceAccessory | false | Keep the wearer's face accessory on a skin that ships its own head |
shipsCape | Automatic | The skin has its own back piece, so the wearer's cape is left off. On by default when a part is named Cape, Cloak, Mantle, Wings or Backpack |
parts | Automatic | Attach order of the part names |
playerskins.json overrides name, icon, price, scale, keepAccessories, shipsCape and keepFaceAccessory per skin, keyed by the model id SCSkin_<Name>. Hand-built model assets whose id starts with SCSkin_ are also discovered.
Particles
Custom particle systems are authored in an external tool that exports a particle system and spawner pair, then dropped in:
- the exported folder to
mods/ScuffedCosmetics/particles/<Name>/, or - the exported
.ziptomods/ScuffedCosmetics/particles/<Name>.zip(extracted on boot and renamed.zip.imported).
Restart. A dropped particle is a building block, not a shop item: open /cosmetic editor (Particles mode), pick it, choose follow or trail and a bone, place it, set a name and price, and save. The editor writes a profile.json into that particle's folder under particles/ and registers the profile straight away. Bone-glued (follow) effects use temporary previews until the next restart packs them.
To publish a single particle without the editor, add a profile.json beside the export:
{
"name": "Fire Aura",
"description": "A ring of flame about you.",
"publish": true,
"price": 1200,
"forSale": true,
"rarity": "LEGENDARY",
"mode": "follow",
"system": "FireAura",
"bone": "Chest",
"offset": { "x": 0, "y": 0.2, "z": -0.1 },
"scale": 1.0,
"cadence": 0.4
}
The profile id is particle_<folder name>; defaults are price 800 and rarity RARE. mode is follow (glued to a bone and turning with the model) or trail (a wake left behind). It defaults to follow for trigger CONTINUOUS and to trail for one-shot triggers (ON_JUMP, ON_LAND, ON_HEAVY_LAND, ON_DEATH, ON_STRIKE, ON_GLIDE, ON_ROLL, ON_SPLASH, ON_EQUIP). cadence applies to trails. bone applies to follow mode (Origin, Pelvis, Belly, Chest, Head, Back-Attachment, left and right shoulder and hand). The spawner must be connected to the system in the export or nothing renders.
An invalid particle folder is skipped with a log line naming the file. It cannot stop the server.
Built-in profiles: Ember Sovereign, Frostbound Spirit, Aurora Veil and Pride Trail are on sale at 1100, 1275, 500 and 800 tokens by default. Endless Origin is award-only.
Emotes
Only emotes that mods or asset packs add are sold. The emotes built into Hytale are never listed and stay free for everyone. Each added emote becomes the item emote_<id> at 250 tokens, COMMON. Optional emotes.json, keyed by the engine emote id:
{
"Wave": { "name": "Wave", "price": 100, "icon": "Ingredient_Motes_Light" },
"Dance": { "price": 0 }
}
An emote that is for sale at price 0 is free for every player. Hide, sale and price overrides from the menu apply on top.
Weathers
Weathers a mod or pack adds are listed as guild skies. Weathers that come with the base game are never sold. Every weather starts off sale; put one on sale from the menu (the price defaults to 500 tokens), or in weathers.json, keyed by the engine weather id:
{
"Blood_Moon_Custom": { "name": "Blood Moon", "description": "A red sky.", "price": 500, "icon": "Plant_Petals_Blood" }
}
A price there puts the weather on sale from boot.
Showcase stands
A showcase is a figure that models cosmetics and opens the shop on a press of the interact key. Create and edit stands from Showcase Stands in the Admin tab, or with the commands above. The setup presets are Wing Display, Hero Mannequin, Greeter, Wandering Model, Dancer, Seated and Blank.
Field (showcase set) | Values | Meaning |
|---|---|---|
enabled | true or false | Show or hide the stand |
mode | STATUE, STAND, WANDER | Statue is a bare figure and the only one that can spin. Stand and Wander are invulnerable NPCs. Wander roams around its spot |
spin | true or false | Turn on the spot. Statue only |
spinSeconds | 1 to 120 | Seconds per revolution. Default 8 |
holdSeconds | 1 to 600 | Seconds each item in the cycle is shown. Default 8 |
yaw | radians | Facing when not spinning |
pose | pose id, or - | A pose from the pose library. Use showcase poses to list them |
poseMode | layer or idle | Leave on layer unless a pose fails to show for players who arrive late |
featured | cosmetic id | The item the interact key opens the shop on |
nameplate | true or false | Show a name above the figure |
label | text | The nameplate text. Defaults to the featured item's name |
mainHand, offHand | item id | Held items. Stand and Wander only |
armor | comma list, up to 4 | Armor shown. Stand and Wander only |
lookAtPlayers | true or false | Turn the head toward players within 10 blocks. Stand and Wander only |
wanderRange | SMALL, MEDIUM, LARGE | 4, 8 or 16 blocks from home |
A stand has a cycle (one costume or one emote at a time) and an outfit of cosmetics worn all the time. Items that cannot be shown are skipped silently; showcase info lists them.
Stands only spawn while a player is within 64 blocks. A wanderer more than 2.5 ranges from home, or 8 blocks below it, is removed and returns at home. Stand roles carry NPC in their id, so Endless Leveling mob leveling skips them. Interaction counts per stand are saved at most once a minute to showcase_stats.json.
Store integration
The shop sells nothing for money inside the game. Players buy Premium Tokens from your web store, and the store credits them by running a console command. Any store that can run a console command after a purchase works. Never put keys, secrets or your store URL in the command.
Set the store link in config.json (shopUrl) so the Get Tokens button points players to it.
The package command
Add this as the package's command action, run on purchase:
cosmetic admin tokens give <player> <amount> --txn <transaction id>
Use your store's placeholders for the player and the transaction id. The player can be a name or a UUID; a UUID is safer because a name change cannot misroute it. For example, a package worth 500 tokens:
cosmetic admin tokens give {uuid} 500 --txn {transaction}
Cosmetics can be sold directly with cosmetic admin grant <player> <id> --txn <transaction id> the same way.
Set the package so it does not require the buyer to be online when the command runs. The delivery does not need the buyer present on this server or on any server.
Always pass --txn. The transaction id becomes the key of a delivery row in the shared grant outbox. Inserting that row is atomic across servers, so a retried webhook that reaches another node cannot credit twice. Without it a re-fired command credits again.
- If the id still contains a literal
{or}(the store did not substitute the placeholder), it is refused as a replay key, the purchase is delivered anyway, and the log gets anUNUSABLE --txn idaudit warning. Test one purchase and check for that line. - If the buyer is on this server, the delivery is applied at once. If they are on another server or offline, it is queued and delivered at their next login anywhere, or within one sweep interval if they are online elsewhere.
- If the name matches no known player, it is parked and binds to the first player who logs in with that name. A typo in a hand-typed name can hand a grant to someone else, so use UUIDs for manual grants.
- Deliveries and the name index use the shared tables
scuffedcosmetics.grantsandscuffedcosmetics.nameswhen shared storage is active. - If shared storage is unavailable, delivery falls back to this server only, protected by
txn_seen.txt. Replays on other servers are not caught in that state, and each such delivery logs an audit warning.
Token balances are capped at the integer limit: a grant that would overflow a balance is refused with a FAILED reply instead of being clamped.
Giveaway codes
Staff create codes in Admin, Giveaway Codes. A code pays tokens, or one cosmetic or profile chosen from a list.
| Setting | Value |
|---|---|
| Format | Twelve characters from ABCDEFGHJKMNPQRSTUVWXYZ23456789, shown as XXXX-XXXX-XXXX |
| Label | Up to 40 characters, shown in history (a streamer or event name) |
| Expiry | 1 to 365 days, default 30 |
| Redeem limit | One redemption per code, network-wide |
| Lockout | A player with 8 wrong codes inside 10 minutes cannot redeem until the window ends |
Codes can be revoked from the list. A code for an item the player already owns is refused without being used. A created code is shown once in the panel and written to the server log, so copy it then.
Global Boosts
The Boosts tab sells two products and stores what players buy in their record.
| Product id | Starts | Default price |
|---|---|---|
global_boost_all | The all_60 Scuffed Professions boost template on every profession | 590 tokens |
global_boost_profession | The profession_60 template on one profession chosen at use | 390 tokens |
Price and sale state go through the same override layer as any item. Using a boost asks ScuffedProfessions to start it under a one-off transaction id, so it is started at most once on any server. Without ScuffedProfessions (or with a version below 0.25.71) the Boosts tab is hidden and boosts a player already owns cannot be used; they stay in stock.
Content sync
On a network, one server can be the source of the costume, skin and particle folders so content is added once.
- Set
contentSynctosourceon exactly one server andmirroron the others. It needs shared storage. - The source publishes
costumes/,playerskins/,particles/andplayerskins.json, and only in the state it booted with, so a model the engine rejects stops at the source. Mirrors copy the content, and it loads at a mirror's next restart. - A pass runs every 5 minutes. Files are downloaded to a staging folder and checked by hash before any live file changes. Nothing is deleted: replaced files and files the source does not have go to
unsynced/<time>/and are kept 30 days. - Folders changed in the last two minutes are not published, large removals wait 30 minutes, and a file that does not parse as JSON is held back.
- A second server set to
sourcewhile the first holds a live lease does not publish.
Leave it off on a single-server install.
Data & storage
| Data | Where |
|---|---|
| Player record | players/<uuid>.json, plus the scuffedcosmetics.players shared table when available |
| Price and shop overrides | admin_overrides.json, plus the scuffedcosmetics.overrides table |
| Giveaway codes | giveaway_codes.json, plus the scuffedcosmetics.codes table |
| Grant outbox and name index | The scuffedcosmetics.grants and scuffedcosmetics.names tables |
| Endless Origin award marker | The scuffedcosmetics.awards table, or awards/endless-origin.json standalone |
| Content sync | The scuffedcosmetics.content table |
- A player record holds the token balance, owned items, equipped items, owned profiles and the equipped profile, the last 400 history entries, delivered transaction ids and boost stock. Every token change carries a source such as a purchase, a code or an admin name.
- Files are written atomically (a temporary file, then a move). If a player file exists but cannot be read, the plugin refuses to write over it and refuses grants and purchases for that player until it can be read, so a disk error cannot reset a balance.
- Fields written by a newer version of the plugin are kept when an older one rewrites a record. Update every server in a network together all the same.
- A record is loaded when the player connects, saved after every change and on disconnect, and then dropped from memory.
Integrations
| Plugin | Type | Use |
|---|---|---|
| EndlessLeveling Core | Optional | Shared storage across servers, the grant outbox, the Endless Link /ec menu entry (page key cosmetics), the prestige lookup for the Endless Origin award, and exemption of showcase NPCs from mob leveling |
| Endless Guilds | Optional | Guild membership, officer ranks and claimed chunks for guild land skies |
| ScuffedProtection | Optional | Forces the chosen sky over guild land and plays sky previews |
| ScuffedProfessions | Optional | Starts and reports Global Boosts |
| Hytale NPC module | Optional | Stand and Wander showcase bodies |
Every one of these is reached through reflection and resolved lazily, so the shop still opens when one is missing. A missing or too-old ScuffedProtection or ScuffedProfessions logs a single warning.
API for other mods
Other plugins can get the service with ScuffedCosmeticsApi.get() (package com.zbeve.scuffedcosmetics.api). It returns null before the plugin finishes setup or after shutdown. All grant and spend calls take a source string that is written to the player's history, 1 to 128 characters with no control characters.
| Method | Description |
|---|---|
getTokens(uuid) | Token balance |
grantTokens(uuid, amount, source) | Add tokens. Throws if the balance would overflow or the player's data is unreadable |
spendTokens(uuid, amount, source) | Subtract tokens. Returns false when short |
ownsCosmetic(uuid, id) · isUnlocked(uuid, id) | Owned, or owned or unlocked by permission |
grantCosmetic(uuid, id, source) · revokeCosmetic(uuid, id) | Grant or revoke an item |
getOwnedCosmetics(uuid) | The owned ids |
equip(uuid, id) · unequip(uuid, category) · unequipAll(uuid) | Equip state, including the live visual |
getEquipped(uuid, category) | The equipped id for a category |
peekEquipped, peekEquippedProfile, isDataLoaded, requestLoad | Cache-only reads that never touch storage. Use these on a world thread |
buyCosmetic(uuid, id, source) | Atomic purchase that applies the override price and sale state |
getCatalog() · getCosmetic(id) | Read the catalog |
getProfiles() · getProfile(id) | Read profiles |
ownsProfile · getOwnedProfiles · getEquippedProfile | Profile ownership and equip state |
buyProfile · grantProfile · revokeProfile · equipProfile · unequipProfile | Profile actions |
Categories are TRAIL, AURA, WINGS, HEAD_ACCESSORY, FACE_ACCESSORY, EAR_ACCESSORY, GLOVES, SKIN_FEATURE, WEAPON_FX, WEAPON_SKIN, COSTUME, BACK_ITEM, TAIL, HEAD_ITEM, PLAYER_MODEL, EMOTE and WEATHER. The menu shows only the modeled wearable, skin, emote, weather and profile categories; the others exist for stored data and other plugins.
Troubleshooting
| Symptom | Check |
|---|---|
| A dropped costume does not appear | Restart once. Look for skipped, rejected, INCOMPLETE or unreadable lines naming the file. A missing texture, a missing anchor bone or a duplicate name are the usual causes |
| A purchase did not credit | Search the log for [SC][AUDIT]. UNUSABLE --txn id means the store did not fill the transaction placeholder. FAILED means storage could not be read or written, and the reply says to retry |
| Tokens credited twice | The command ran without --txn, or the store sent a different id each time |
| Equipped effects stop rendering after a reload | Run /cosmetic diag. It reports whether the effect assets are still loaded and re-applies them |
| A weather is bought but not visible | It needs Endless Guilds, ScuffedProtection 1.1.43 or newer, an officer who applied it to a chunk of claimed land, and no forced region or world weather |
| A Global Boost will not start | Check that ScuffedProfessions 0.25.71 or newer is installed and its boosts are running. The boost stays pending and starts when it answers |
| Players see a stale price | Prices come from admin_overrides.json, shared across servers when storage is shared. Check that every server has the same shared storage |