Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HMCCitizens

A Paper plugin that lets Citizens NPCs wear HMCCosmetics cosmetics.

HMCCosmetics is built around online players: its user objects are keyed by player UUID, and several of its render paths look an entity up by that UUID before touching it. Citizens NPCs are entities without a player behind them, so equipping a cosmetic on one through HMCCosmetics alone does nothing. HMCCitizens supplies an NPC-backed cosmetic user and the packet-level rendering that the player-only paths would otherwise have done, then persists the selection in a Citizens trait so it survives restarts.

Features

  • /npc hmccosmetics equip|remove|list — manage cosmetics on the selected NPC, with tab completion for cosmetic IDs, slots, and #RRGGBB colors.
  • Cosmetics persist per NPC in Citizens' own NPC storage and are re-applied on spawn.
  • Optional dyeing for leather-based cosmetics via a #RRGGBB argument.
  • Armor, backpack, and balloon cosmetic types all render on NPCs.
  • Backpacks marked first-person-compatible get a dedicated renderer, because HMCCosmetics' own first-person path needs a real player client on the other end.

Requirements

Server Paper 1.21.4+
Java 21
Citizens 2.0.43+
HMCCosmetics 2.9.1 (with HibiscusCommons 0.9.2)

Both Citizens and HMCCosmetics must be enabled. If either is missing, HMCCitizens logs a warning and stays inactive rather than failing the server start.

Build

./gradlew build

The jar lands in build/libs/HMCCitizens-0.1.0-SNAPSHOT.jar. Drop it in plugins/.

Usage

Select an NPC first (/npc select <id>), then:

/npc hmccosmetics equip <cosmetic-id> [#RRGGBB]
/npc hmccosmetics remove <cosmetic-id|slot|all>
/npc hmccosmetics list

equip replaces whatever occupies that cosmetic's slot. remove accepts a cosmetic ID, an HMCCosmetics slot name (HELMET, BACKPACK, …), or all.

Permissions

Node Default Grants
hmccitizens.use op Use /npc hmccosmetics on NPCs you own
hmccitizens.admin op Use it on any NPC regardless of owner

How it works

HmccosmeticsTrait — a Citizens trait named hmccosmetics, saved into the NPC's data alongside every other trait. It holds a PersistedCosmeticState (slot → cosmetic ID + optional color) and, while the NPC is spawned, an NpcCosmeticUser. On spawn it rebuilds the runtime user and re-applies each saved selection; cosmetics that no longer exist in HMCCosmetics' config are skipped with a warning but kept in storage, so renaming a cosmetic back restores it. It ticks once per Citizens trait tick to keep the render in sync.

NpcCosmeticUser — a subclass of HMCCosmetics' CosmeticUser whose entity is the NPC and whose getPlayer() is null. It overrides the paths that assume a player: armor is pushed with explicit equipment-slot packets to the viewers around the NPC, balloons are teleported and re-leashed manually, and canEquipCosmetic always returns true since access is already gated by the Citizens command permissions rather than by player unlock state.

NpcBackpackRenderer / NpcBackpackRotation — a backpack is an invisible armor stand riding the NPC with the cosmetic item on its head. Because it is a fake entity, its rotation only reaches clients through packets, and clients do not apply those directly: they treat the value as a lerp target and close a third of the gap per tick. Sending the plain body yaw therefore yields a backpack that matches while the NPC stands still and visibly trails the torso through a turn.

The renderer solves that the way HMCCosmetics does for players:

  • The NPC's body yaw is simulated, not read off the entity — the body yaw a viewer sees is one their own client computed, and the server copy is at best a tick behind. The simulation reproduces vanilla's rules: ease toward the direction of travel while moving, drag the body once the head exceeds a 50° offset, and flip around when movement is backwards.
  • A per-renderer mirror of where clients are rendering the backpack lets it send an overshooting yaw that lands on the true body yaw after the lerp completes, instead of the body yaw itself. The overshoot is quantised to the byte the packet actually carries before advancing the mirror, so the mirror cannot drift away from the client over a long turn.
  • Teleports and world changes reset the simulation rather than being read as travel, which would otherwise spin the body toward the destination.
  • Yaw, rotation, and head-rotation packets all carry the same value. They each retarget the client's lerp, so disagreeing values leave the backpack oscillating. The head packet is required: a client renders a passenger's head at the passenger's own head yaw, clamped to an offset from the vehicle's body yaw, so without it the backpack holds still and then snaps once the clamp is hit.
  • Updates are skipped when the NPC has neither moved nor turned beyond the packets' own quantisation, and newly-arriving viewers get a spawn, equipment, and mount bundle before the shared rotation packets.

Tests

./gradlew test

The suite covers the rotation arithmetic (body yaw simulation, head clamp, backwards movement, overshoot, mirror advance, byte quantisation, and the 360° wrap), command-completion filtering, and the persisted state model. Rendering itself is verified in-game — packet behavior against a live client is not something the unit tests can stand in for.

Author

imonlyzuzu

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages