NyaaPlayerCoser is an NPC plugin for NyaaCraft servers. It provides merchant trades, command execution, player-skin NPCs (rendered via packets), and a traveling-merchant behavior.
- Paper 1.21.11 (the plugin is built against
paper-api:1.21.11-R0.1-SNAPSHOT) - Java 21
- NyaaCore 9.10+
- ProtocolLib (dev-build — required for 1.21.11 packet support)
- Optional: HamsterEcoHelper — integration is currently disabled.
HEH_SELL_SHOPNPCs are removed fromnpcs.ymlon load, and any remaining/npc hehshopusage will no longer open a shop.
Note:
plugin.ymlstill declaresapi-version: 1.13for legacy compatibility, but the code uses modern Paper 1.21.11 APIs and cannot run on older servers.
./gradlew buildThe build downloads a ProtocolLib dev-build jar to libs/ProtocolLib.jar automatically. Produced jar: build/libs/NyaaPlayerCoser-mc1.21.11-8.2.*.jar.
All files live in the plugin data folder (plugins/NyaaPlayerCoser/):
config.yml— main plugin configuration (keys listed below; file is empty by default and uses defaults).npcs.yml— NPC definitions.trades.yml— trade definitions.skins.yml— skin definitions.item-update.log— audit log for trade-item normalizations (see Trade Behavior).save.yml/bad-save.yml— used by/npc import(Shopkeepers migration input / failed entries).
- Aim at a block with 2 blocks of free headroom above it.
- Spawn the NPC:
/npc spawn VILLAGER TRADER_UNLIMITED "&aTrader" - Put
item1into hotbar slot 1,item2into slot 2 (optional — leave empty for one-ingredient trades),resultinto slot 3. - Define the trade:
/npc edit <npcId> trade:+ - Right-click the NPC to trade.
TRADER_UNLIMITED— unlimited merchant trades backed bytrades.yml.TRADER_BOX— reserved for chest-backed trading. The enum and data fields exist, but no GUI flow is implemented (/npc spawnrejects it, and opening one shows "type not supported").COMMAND— executes a configured command when interacted with.HEH_SELL_SHOP— deprecated and removed on load. HamsterEcoHelper integration is disabled in the current build.
Spawned entities are marked with two scoreboard tags:
nyaa_npc_id:<npcId>— identifies the entity as a Nyaa NPC and stores its id.rpgitem_ignore— prevents RPGItems-reloaded from applying damage/effects to the NPC.
NPCs are invulnerable, silent, cannot pick up items, have AI disabled and movement speed locked to 0 (via an attribute modifier named npc:immobile_entity). Player-skin NPCs are rendered entirely via ProtocolLib packets and require ProtocolLib to be installed.
- Each trade is stored as
item1 (+ item2) -> result.item2may be AIR to represent a single-ingredient trade. - Item matching uses type + plain-text display name + plain-text lore (
ItemStackUtils.isSimilarPlainText), ignoring other NBT. - When a player opens a trader, the plugin inspects that player's inventory and normalizes any stack that matches a trade requirement (
item1/item2) so its NBT format matches the current version's canonical form. Each update is recorded as a line initem-update.log.- The normalization runs off the main thread (snapshot on main thread, diff async, apply on main thread).
- If two trades on the same NPC disagree on the canonical form of the same item (ambiguous baseline), that item is skipped.
- Container-like items are always skipped to avoid overwriting their contents: shulker boxes (Tag.SHULKER_BOXES), bundles (
BundleMeta), and any item whoseBlockStateMetais aContainer.
The top-level command is /nyaaplayercoser (alias: /npc).
/npc reload
/npc list
/npc inspect nearby [range]
/npc inspect npc <npcId>
/npc inspect trade <tradeId>
/npc debug
inspect nearbyreports the NPC under the player's cursor (within ~6 blocks). Ifrangeis given, it also lists NPCs whose stored location falls inside the cube of that half-size centered on the player.inspect tradeprints the trade data and drops copies ofitem1,item2,resulton the ground for you to pick up and inspect.debugtoggles verbose plugin logging (NyaaPlayerCoser.debugEnabled).
/npc spawn <entityType> <npcType> <displayName> [nbt]
/npc remove <npcId>
/npc edit help
/npc edit <npcId> name:<displayName>
/npc edit <npcId> npctype:<TRADER_UNLIMITED|COMMAND|HEH_SELL_SHOP>
/npc edit <npcId> entitytype:<EntityType>
/npc edit <npcId> owner:<uuid>
/npc edit <npcId> hehowner:<uuid>
/npc edit <npcId> nbt:<nbtString>
/npc edit <npcId> location:me
/npc edit <npcId> skin:<skinId>
/npc edit <npcId> skin:default
/npc edit <npcId> trade:+
/npc edit <npcId> trade:+<tradeId>
/npc edit <npcId> trade:-<tradeId>
/npc edit <npcId> trade:<tradeId>,<tradeId>,...
/npc edit <npcId> command:<commandString>
/npc edit <npcId> command_permission:<permission>
Notes:
/npc spawnray-traces from the sender's eyes; the targeted block must exist and must have 2 air blocks above it (the NPC itself and one of clearance). The NPC is placed at the centre-top of that block.displayNamesupports&color codes and hex colors like&#RRGGBB.entityTypemust be a type that is both spawnable and alive, orPLAYER. ForPLAYER, the display name is used as the player name and must be between 3 and 16 characters.npctypeat spawn/edit time only acceptsTRADER_UNLIMITED,COMMAND, andHEH_SELL_SHOP(deprecated — editing to this type is still permitted but the NPC won't function).nbtis a raw entity NBT string applied post-spawn (e.g.{Profession:1}for a villager).trade:+reads hotbar slots 1, 2, 3 (inventory indices 0, 1, 2). Slot 2 may be empty to omititem2.trade:<id1>,<id2>,...replaces the full trade list; every listed id must already exist intrades.yml.- Changes trigger an
NpcRedefinedEventwhich closes all open merchant windows for the NPC.
/npc edit_trade <tradeId>
Replaces the trade referenced by <tradeId> with the contents of hotbar slots 1-3. All NPCs that reference this trade have their open merchant windows closed (TradeRedefinedEvent).
command_permission controls how the command is run:
console— run as the console.*— run as the player with a temporary OP grant (reverted in afinallyblock).- otherwise — treated as a
;-separated permission list (e.g.perm.one;perm.two). Each permission and all of its dot-prefixes are granted for a single-tickPermissionAttachmentwhile the command runs.
Placeholders expanded in the command string:
{player}— player name.{player.x},{player.y},{player.z}— player location.{player.yaw}— eye yaw + 90.{player.pitch}— negated eye pitch.{yaw}— body yaw + 90.{pitch}— negated body pitch.
/npc skin add <skinId> <texture_value> <texture_signature> [description]
/npc skin pin <playerName> [follow] [skinId]
/npc skin list
/npc skin setdefault <skinId>
/npc edit <npcId> skin:<skinId>
/npc edit <npcId> skin:default
Notes:
- Texture values/signatures can be obtained from https://mineskin.org/ or the
texturesproperty of any online player. skin pin <playerName>copies that player's current textures into a newSkinData. Iffollowistrue, the player's UUID is stored infollowPlayerfor downstream features. IfskinIdis omitted, the skin is saved under<playerName>.- Skins only take effect on NPCs whose
entityTypeisPLAYER.
/npc travel enable <npcId> <presentMinSec> <presentMaxSec> <absentMinSec> <absentMaxSec> <tradeMin> <tradeMax> <rndXZMax> <rndYPosMax> <rndYNegMax> <rndTryMax>
/npc travel disable <npcId>
/npc travel force_move <npcId>
Behavior:
- On first enable, the NPC must have at least one trade. Its current location becomes the centre point of the travel plan, and its existing
tradeslist becomes the "complete" trade pool (completeTradeIdList). - Each cycle: the NPC is "present" for a uniformly random time in
[presentMinSec, presentMaxSec], then "absent" for a uniformly random time in[absentMinSec, absentMaxSec]. - While present, it picks between
tradeMinandtradeMaxtrades at random from the complete pool. When it arrives, a random legal ground position is chosen withincentralX ± rndXZMax,centralZ ± rndXZMax, andY ∈ [centralY − rndYNegMax, centralY + rndYPosMax], with up torndTryMaxattempts; if none succeeds, the centre point is used. - Arrival/departure fire portal/cloud particles and villager sounds, plus a broadcast (see config).
disablerestores the NPC to its centre point and makes all pool trades available.force_moveschedules the next state transition as "now" (nextMovementTime = now − 1ms).- While a traveller is due to move, interacting with it shows "its time to move" and does not open the merchant view.
/npc hehshop
/npc hehshop remove
/npc my
HEH integration is disabled in this build:
/npc hehshopstill attempts the flow butExternalPluginUtils.hehOpenPlayerShopalways throws and the interaction ends in "HEH not supported".HEH_SELL_SHOPNPCs are pruned fromnpcs.ymlon every plugin load./npc mylists NPCs owned by the calling player (still functional and useful for auditing any NPCs you own).
All fields are optional and fall back to the defaults shown below.
| Key | Default | Description |
|---|---|---|
language |
en_US |
i18n language code; resources lang/en_US.yml and lang/zh_CN.yml are bundled. |
allowedEntityType |
auto-populated from all spawnable + alive entity types plus PLAYER |
Whitelist enforced by /npc spawn and entitytype: edits. |
tabListDelay |
15 |
Ticks that player NPCs remain in the tab list after being sent. |
playerNpcLimit |
6 |
Maximum NPCs per player owner for /npc hehshop (still enforced even though the shop is disabled). |
travel_merchant.distance_check |
50 |
Radius (blocks) around a travelling NPC inside which no player may be for it to move. |
travel_merchant.broadcast_range |
50 |
Broadcast radius for arrival/departure messages. |
travel_merchant.message_arrival |
&aTraveling merchant &r{merchant.name}&a has arrived. |
Arrival message template. Supports & colors and &#RRGGBB hex. |
travel_merchant.message_depart |
&aTraveling merchant &r{merchant.name}&a has departed. |
Departure template. |
Both messages support the {merchant.name} placeholder.
/npc import
Place the Shopkeepers plugin's save.yml into plugins/NyaaPlayerCoser/ first. Entries of type admin are imported into npcs.yml / trades.yml; unsupported or broken entries are copied into bad-save.yml. After import the plugin forces a full respawn of all NPCs.
From plugin.yml:
npc.admin(default:op) — umbrella; children:npc.player,npc.command.inspect,npc.command.import,npc.command.reload,npc.command.skin,npc.command.spawn,npc.command.remove,npc.command.edit.npc.player(default:op) — children:npc.command,npc.command.hehshop.npc.command— base permission for using/npc.npc.command.hehshop—/npc hehshop,/npc my.npc.interact(default:true) — required to right-click any NPC. Without this permission the interaction silently no-ops.npc.debug(default:false) — required for/npc debug.
/npc travel uses npc.command.edit.
- Player NPCs invisible: make sure ProtocolLib is installed and is the dev-build compatible with 1.21.11. Only player-typed NPCs require it; living-entity NPCs work without ProtocolLib but the HEH interaction packet listener will still be installed.
- "invalid npc config, skipping" on load: usually an unknown
entityTypevalue innpcs.yml. Fix the file or remove the entry. - Trade item normalization logs grow fast: this is expected when plugins change item NBT formats across updates. You can rotate or truncate
item-update.logsafely at any time.
- 8.2.x — Minecraft 1.21.11 (Paper 1.21.11, Java 21, NyaaCore 9.10). Introduced automatic trade-item NBT normalization with
item-update.log, container-like items skipped during normalization,rpgitem_ignorescoreboard tag, stricter player-NPC packet handling for 1.21.x, removal of deprecatedHEH_SELL_SHOPNPCs on config load, and a travel-plan null-world guard. - 8.1.x — Minecraft 1.17.x (Java 16, NyaaCore 8.1, ProtocolLib 4.7). Added the
COMMANDNPC type and Gradle 7 build. - 7.1.x — Minecraft 1.15.1, since build 17.
- 7.0.x — Minecraft 1.14.4, since build 8.