Install, authoring, staff-command, permission and integration reference for server operators. For how the NPCs, quests, bounty boards and guild workers play, see the Scuffed NPCs gameplay guide.
- Plugin version: 1.98.34 (plugin name
ScuffedNPCs)
All commands live under one root: /snpc (alias /scuffednpcs). Data lives in mods/Zbeve_ScuffedNPCs/.
Requirements
| Plugin | Type | Used for |
|---|---|---|
Hytale NPC module (Hytale:NPC) | Required | NPC roles and spawning |
| EndlessLevelingCore | Optional, but most features need it | Quests, daily reset clock, dialogue export, quest rewards, mob-leveling exemption |
| EndlessGuilds | Optional, needed for workers and blueprints | Guild membership, ranks, claims, level, guild bank |
| ScuffedHolograms | Optional | Per-player ! and ? quest markers |
| ScuffedProfessions | Optional | Professions bounty board, Mithril mining site, Rejuvinator |
| endless-elite-mobs | Optional, needed for world quests | Elite packs |
| QuestLines | Optional | Extra quest provider |
| ScuffedNPCsData, FancyHolograms | Declared optional in the manifest | Not referenced by the code |
Engine version: >=0.7.0-pre.0 <0.8.0. The mod ships its own asset pack (NPC roles, models, UI pages, items, sounds). Nothing else is required to boot: every optional integration is looked up at runtime and switched off with a log line when missing.
Other soft integrations that are detected by class name and used only when present: ScuffedProtection, WorldProtect, OrbisGuard, SimpleClaims (workers never build or open containers where the owner could not), BetterLootBox (event keys), Endless Fates, Endless Rifts, EndlessLink (tutorial steps), and ZenShop (a tutorial purchase step).
The shipped bounty pools pay coin with the console command eco give {total} {player}. Install an economy plugin that provides that command, or edit the pools.
Quick start
- Start the server once. The data folder and the default files are created.
- Run
/snpc queststo open the NPC hub, click + Spawn, pick a model, and place the NPC where you stand. - Attach a profile or a quest role (see NPC hub) so the NPC has something to say.
- Put down the bounty boards you want with
/snpc bounty place --board <id>. - Give guild leaders the worker items (Sacrificial Altar and Mining Site blocks) and check the hire prices with
/snpc cost list.
NPC hub (/snpc quests)
/snpc quests (also /snpc npcs) opens the admin hub. It has three tabs along the top: Quests, Questlines and Quest Wizard. The left list shows placed NPCs, the right panel edits the selected one:
| Control | What it does |
|---|---|
| + Spawn | Places a new NPC where you stand |
| Rename, model filter | Sets the display name and picks any model on the server |
| Mode | Idle, Follow, Assist, Guard, Wander or Betray (hostile) |
| Profile dropdown | Attach, edit or create a conversation profile |
| Quest NPC role | Binds the NPC to an Endless Leveling quest role (auto-bind, below) |
| Set spawn to me | Moves the NPC's post to where you stand |
| Record lead route | Starts recording a walking route for an escort |
| Despawn Companion | Removes the NPC |
Placed NPCs are saved to companions.json and come back after restarts. A tool that adds records to companions.json can be picked up without a restart with /snpc reload.
Two ways to make a quest giver
Auto-bind (about 30 seconds). Write the quest and its dialogue in Endless Leveling: a dialogue block with giver_npc, turnin_npc and offer, active, turnin, done lines. Endless Leveling exports campaign_dialogues.json when it loads quests. In the hub, spawn an NPC and pick the role in Quest NPC role. The NPC then shows the right stage per player, with markers, and credits npc_talk objectives. One role handles every quest it gives or turns in. Scuffed NPCs finds the export by scanning mods/*/campaign_dialogues.json.
Custom profile (full control). Use New Profile for a ready-made offer, active, turn-in and done skeleton, then edit it in the profile editor (Stages, Dialogue, Quest Rules and Hooks tabs). Saving writes profiles/<id>.json and applies at once. Attach it to the NPC from the Profile dropdown.
The Quest Wizard builds a quest giver in four steps: pick a quest, pick a model, write one line per quest state, and choose a position. The Quests tab (also /snpc questeditor, alias /snpc editquests) browses and edits every quest on the server (rewards, trigger, prerequisite, objective, dialogue).
Profile files
Profiles live in mods/Zbeve_ScuffedNPCs/profiles/*.json. Hand-written files are picked up on boot and by /snpc reload. A reload only adds or replaces profiles; deleting a file does not remove the profile from memory (use the editor's delete for that).
{
"id": "elder",
"name": "Village Elder",
"scope": "SHARED",
"npcKey": "elder",
"startStage": "offer",
"stages": {
"offer": { "mode": "idle", "dialogue": "offer", "marker": "available" },
"active": { "mode": "idle", "dialogue": "active", "marker": "progress" },
"turnin": { "mode": "idle", "dialogue": "turnin", "marker": "turnin" },
"done": { "mode": "idle", "dialogue": "done" }
},
"dialogues": {
"offer": { "text": "Boars are trampling our fields. Cull five of them.", "choices": [] },
"active": { "text": "The hunt is not finished.", "choices": [] },
"turnin": { "text": "You did it. Claim your reward in your quest log.", "choices": [] },
"done": { "text": "Thank you again.", "choices": [] }
},
"rules": [
{ "when": [ { "type": "QUEST_STATE", "quest": "elder_boar_cull", "states": ["TURN_IN_READY"] } ], "stage": "turnin" },
{ "when": [ { "type": "QUEST_STATE", "quest": "elder_boar_cull", "states": ["COMPLETED"] } ], "stage": "done" },
{ "when": [ { "type": "QUEST_STATE", "quest": "elder_boar_cull", "states": ["IN_PROGRESS"] } ], "stage": "active" },
{ "when": [], "stage": "offer" }
],
"transitions": []
}
Rules are checked top to bottom and the first match wins, so put the most specific first. Quest states are NOT_STARTED, IN_PROGRESS, TURN_IN_READY (Endless Leveling status completed but not claimed) and COMPLETED (claimed).
| Key | Meaning |
|---|---|
id, name | Profile id (also the file name) and display name |
scope | SHARED (one NPC, per-player dialogue) or PERSONAL (one copy per owner, for follow, escort and betrayal). Default PERSONAL |
preset | Appearance preset id (below) |
npcKey | Key sent to quests. Talking credits npc_talk objectives whose match equals it |
speaker, role | Name and role line shown in the dialogue header |
questHandoff | "manual" stops Endless Leveling's own pop-up from pre-empting the dialogue |
startStage | First stage. Defaults to the first one listed |
stages.<id> | mode (idle, follow, assist, wander, guard, hostile, lead), dialogue node id, leadTag, marker (available, progress, turnin), protect, defend, retireCopy, onEnter actions |
dialogues.<id> | text and choices (id, label, optional when conditions) |
rules[] | when conditions and the stage they select |
transitions[] | from (default any), on, value, to, actions |
ambient | barks, idleAnims, minSeconds (default 8), maxSeconds (default 22), radius (default 14) for proximity chatter |
Conditions (type): QUEST_STATE (quest, states), QUEST_PROGRESS_ATLEAST (quest, amount), FLAG (key, optional value).
Transition triggers (on): ON_SPAWN, DIALOGUE_CHOICE (value = choice id), VOLUME_TAG (trigger volume id), QUEST_STARTED, QUEST_COMPLETED, QUEST_CLAIMED (value = quest id), ON_HIT, ON_DEATH, ON_REACH, ON_WORLD_CHANGE (value = world name).
Actions (type, value, value2, optional cooldownMs):
| Type | Effect |
|---|---|
SET_STAGE, SET_MODE | Change stage or behaviour mode |
MESSAGE | Chat message to the player |
BECOME_HOSTILE, RETURN_FRIENDLY | Turn on or off the player |
ASSIGN_QUEST, TRACK_QUEST, CLAIM_QUEST | Assign, pin or claim a quest by id (CLAIM_QUEST uses the quest engine's own payout) |
RECORD_PROGRESS | Credit an npc_talk objective (value = npc key) |
EL_INTERACT | Report a full interaction to Endless Leveling (runs its act-gated flow) |
FIRE_QUEST_EVENT | Advance an EXTERNAL objective by key (value2 = amount) |
SET_FLAG | Set a per-player flag (blank value2 removes it) |
DELIVER_ITEMS | Take value2 of item value and report the delivery |
OPEN_DIALOGUE | Reopen the dialogue so a conversation chains |
PLAY_SOUND, PLAY_ANIMATION | Sound event or animation at the NPC |
RUN_COMMAND | Run a command. It runs as the player by default (needed for another mod's menu to work). value2 of console, op or server runs it as the console. {player} becomes the username |
DESPAWN | Remove the NPC |
CONFIRM_IDENTITY, CREDIT_IDENTITY, OPEN_IDENTITY, TRY_CLASS, TRY_BUILD, COMMIT_CLASS | Endless Leveling identity and class-trial helpers. They do nothing without it |
RUN_COMMAND is also how a dialogue answer opens another mod's shop or menu.
Appearance and naming data
| File | Purpose |
|---|---|
presets/*.json | Extra appearance presets: {"id": "...", "name": "...", "role": "<NPC role id>"}. Built in: merchant, gentleman, feran, bounty_farmer, bounty_giver |
npc_names.json | Name and role line per npcKey or quest role: {"Guild_Master": {"name": "...", "role": "..."}} |
npc_skins.json | Outfit per key, as engine skin components (haircut, overtop, pants, ...) |
portraits.json | Dialogue portrait per key: player:<account uuid> for a live head, or an image name under Common/UI/Custom/Pages/ScuffedNPCs/Dialogue/Portraits/ |
npc_names.json, npc_skins.json, portraits.json, paths.json, campaign_points.json and the bounty files are seeded from the jar when missing and never overwritten. The seeded npc_names.json has entries for the Endless Leveling starter campaign roles (Tutorial_Guide, Guild_Registrar, Auctioneer, Trial_Master, Guild_Master) and the five bounty boards, and npc_skins.json has class outfits (Class_Adventurer, Class_Arcanist, ...), so edit or delete them to suit your server. /snpc reload re-reads names and skins and re-dresses live NPCs.
Questline designer (/snpc questlines)
Authors a multi-step questline end to end and publishes it live through Endless Leveling's quest engine. It needs an Endless Leveling build with the quest-authoring provider. Each step has a name, an objective, a match key, a target count, rewards (XP, essence, cores, console commands, reward text), a giver NPC and four dialogue sets (offer, active, turn-in, done). The designer scaffolds a profile for each giver. Lines are saved in questlines.json (with a .bak copy).
Objective types: talk to an NPC, kill mobs, harvest blocks, visit a zone, clear a wave gate, clear a raid, gain levels, prestige, collect items, craft items, bring items to an NPC (optionally consumed), pick race, pick class, confirm identity, try a temporary build, spend skill points, use a dungeon portal, use a raid portal, and a scripted step. Steps that need a match key (kill, talk, and so on) refuse to publish with it blank, because an empty key would match anything.
Bounty boards
A board is an NPC that posts a short list of contracts every day. Everything is data-driven.
Placing boards
| Command | Description |
|---|---|
/snpc bounty place [--board <id>] | Put the board's NPC where you stand. Refuses if that board already has one |
/snpc bounty move [--board <id>] | Move it to where you stand |
/snpc bounty remove [--board <id>] | Remove the NPC. The board keeps running |
/snpc bounty list [--board <id>] | Today's contracts, payouts and NPC locations, plus any pool problems |
/snpc bounty reload | Re-scan pools, re-read them and republish |
/snpc bounty preview [--board <id>] [--date yyyy-MM-dd] | Show the roll for a date without publishing |
/snpc bounty reroll [--board <id>] | Republish the board |
/snpc bounty simulate <yyyy-MM-dd or off> | Pin every board to a date, or release the pin |
The board id can be left off when only one board is loaded. A simulate pin never expires by itself and survives restarts, so release it on a live server. /snpc reload also re-reads the bounty pools.
Files
A board is one file: bounties_<id>.json is the <id> board, and bounties.json with no suffix is the farmer board. The optional bounty_<id>.template.json holds what its NPC says; a board without one uses bounty_generic.template.json. Five boards ship: farmer, butcher, miner, hunter and professions. The professions files are only written while ScuffedProfessions is installed. Deleting a pool file stops that board on the next reload, except that missing shipped files are restored on boot.
The day's roll depends only on the date, so a restart gives the same contracts. The date follows Endless Leveling's daily reset clock and falls back to the UTC date, with one warning, when that clock is unavailable. Editing a pool re-rolls the current day.
Pool top level:
| Field | Default | Meaning |
|---|---|---|
slots | 3 | Contracts per day, 1 to 5 (clamped) |
npcKey | BOUNTY_<ID> | Key for names, skins and portraits |
profileId | bounty_<id> | Generated profile id |
questIdPrefix | snpc_bounty_<id> | Prefix of the generated quest ids |
role | ScuffedNPC_Bounty_Giver | NPC role used for the body |
requiresPlugin | none | Mod id or list. The board posts nothing while one is missing |
groups | none | Named payouts that entries share |
entries | none | The contracts that can be posted |
Two boards may not share an npcKey, profileId or questIdPrefix. The second one is refused and both are named in the log. The legacy farmer pool also reads farmerNpcKey and farmerProfileId.
One entry from the shipped farmer pool:
{
"id": "wheat_granary",
"group": "staple",
"match": "Plant_Crop_Wheat_Item",
"displayName": "Wheat",
"amount": { "min": 16, "max": 32, "step": 8 },
"chunk": 8,
"value": 12,
"weight": 1.0,
"questName": "Wheat for the Granary",
"description": "The granary is running low. Bring the farmer {amount} Wheat.",
"rewardItems": [ { "item": "Plant_Seeds_Wheat_Eternal", "amount": 2 } ],
"dialogue": {
"offer": ["Granary's near empty. {amount} Wheat, {total} coin."],
"active": ["Still short on Wheat."],
"turnin": ["That's the lot. Come see me for your pay."],
"done": ["Granary's full. Board resets at first light."]
}
}
| Entry field | Meaning |
|---|---|
id | Unique in the pool |
enabled | false parks the entry |
group | Which group pays it |
trigger | deliver_item (default, settled in person), mob_kill or block_harvest |
match | Exact item id for deliver_item. For mob_kill an NPC role name (wildcards allowed, Trork_*). For block_harvest a block id, a wildcard or a gather type such as OreIron, Rocks, Woods |
displayName | Text name. Required in practice for non-hand-in contracts |
amount | A number or {min, max, step} |
chunk | Partial hand-in size. It must divide every possible amount. Hand-ins only |
keep | true checks the items without taking them. Hand-ins only |
value | What one unit is worth, the source of {total} |
xp | Experience per unit, the source of {xp}. Default 0 |
requiresPlugin | Mod id or list. The entry sits out while one is missing |
weight | Draw weight, default 1 |
questName, description | Contract title and journal line |
rewardCommands, rewardItems | Override the group's payout for this entry. The two override separately |
dialogue | offer, active, turnin, done lines |
A day never posts the same match twice on one board and prefers not to post two from the same group. Problems are reported in the log and by /snpc bounty list. An entry that pays nothing, has a duplicate id, or puts a wildcard in a hand-in match is rejected on its own while the rest of the pool still loads. A chunk that does not divide every possible amount is dropped (the entry stays), chunk and keep on a kill or mining contract are ignored, and item ids the server does not know are named in the log once per boot.
Payouts. rewardCommands run on the console when the contract is claimed. {player} and {player_uuid} are filled by Endless Leveling. rewardItems are handed over by the NPC with amount (flat) or perUnit (scales with the ask). A count of at least 1 always comes out, and extras that do not fit land at the player's feet.
Tokens usable in names, descriptions, dialogue and reward commands: {amount}, {chunk}, {display}, {item}, {value}, {total} (value times amount), {xp}, {group}, {items}, {items+}.
Voice templates. bounty_<id>.template.json holds the words and labels. Anything repeated per contract uses {{slot. tokens and is cloned once per posted contract. Gates __ifChunk, __ifTrigger and __ifNotTrigger choose which contracts an element is cloned for. The generated profile is rewritten every day, so never edit it.
Quest host (Endless Leveling directors)
Scuffed NPCs can run three of Endless Leveling's quest directors itself, so their NPCs use the Scuffed NPCs dialogue page:
| Capability | What Scuffed NPCs runs |
|---|---|
event_quests | Random per-player gather and rescue event NPCs |
daily_givers | Wandering shared daily-quest givers |
bounty_board | The timed first-come bounty board NPC |
Endless Leveling keeps the quest catalog (quests.json and its config.events, config.daily_givers, config.bounties sections) and player quest progress. At boot Scuffed NPCs claims a capability only when the Endless Leveling build exposes every method the host needs, then Endless Leveling stands its own director down. Older builds without the provider API keep running all three themselves, and /snpc questhost status reports that nothing is hosted and why. When Scuffed NPCs stops, its NPCs despawn and Endless Leveling's directors resume.
mods/Zbeve_ScuffedNPCs/questhost.json is written on first boot with every toggle on:
{
"event_quests": true,
"daily_givers": true,
"bounty_board": true
}
Set one to false and restart or run /snpc questhost reload to leave that capability to Endless Leveling. Host state is stored under questhost/ (bounty_board.json, daily_givers.json, event_quests.json, migrated.json). On the first claim of each capability, Endless Leveling's exported director state is imported once.
| Command | Description |
|---|---|
/snpc questhost status | What is hosted, what Endless Leveling keeps and why, live NPC counts, board placement |
/snpc questhost migrate <event_quests|daily_givers|bounty_board|all> [--force] | Import Endless Leveling's export again |
/snpc questhost release <capability> | Hand a capability back until claim or a restart |
/snpc questhost claim <capability> | Host a released capability again |
/snpc questhost reload | Re-read questhost.json and Endless Leveling's catalog |
/snpc questhost board place · remove · refresh | Manage the hosted bounty board. Endless Leveling's own /el bounty manages only its own board |
/snpc questhost event trigger [--kind gather|rescue] [--player <name>] | Fire an event quest now |
/snpc questhost event clear [--player <name>] | Withdraw a player's hosted event quests |
/snpc questhost giver spawn [--player <name>] | Try a daily giver spawn now |
/snpc questhost giver clear [--world <name>] | Despawn every hosted giver in a world |
/snpc questhost choose <npc> <row> is the player verb that dialogue rows run. It only acts on the caller's own open conversation.
Director settings
The directors are tuned in Endless Leveling's quests.json (config.events, config.daily_givers, config.bounties). Scuffed NPCs reads those sections and uses these values when a key is absent:
| Section.key | Default | Meaning |
|---|---|---|
events.min_minutes · max_minutes | 12 · 25 | Random delay between event quests |
events.repeat_cooldown_minutes | 90 | Cooldown before a player can help with the same event quest again |
events.despawn_after_claim_seconds | 15 | How long the event NPC lingers after a claim |
events.worlds | default, endless, survival, world | Worlds events may start in |
events.blocked_worlds | name patterns for adventurer, horizons, arena, rift, raid, dungeon and tower worlds | Never used for events |
daily_givers.min_minutes · max_minutes · first_minutes | 6 · 12 · 3 | Giver spawn timing |
daily_givers.offers | 3 | Offers per giver |
daily_givers.max_players | 3 | Distinct players per giver |
daily_givers.giver_ttl_seconds | 600 | Giver lifetime |
daily_givers.despawn_after_accept_seconds | 15 | Lingering after an accept |
daily_givers.spawn_distance_min · spawn_distance_max | 8 · 14 | Spawn ring around the player |
daily_givers.min_giver_distance | 128 | Spacing between givers |
daily_givers.max_per_world | 6 | Givers per world |
daily_givers.announce · announce_radius | true · 96 | Announcement |
bounties.slots | 5 | Slots on the hosted board |
bounties.max_active | 1 | Unfinished bounties per player |
bounties.refresh_minutes | 60 | Refresh interval |
A finished but unclaimed bounty stays claimable from the journal for 24 hours after its board window ends.
World quests
Wild quest givers appear near players and ask for an elite pack to be cleared. The giver, dialogue, credit and expiry are Scuffed NPCs. The quest definition and rewards are Endless Leveling's, and the elites come from endless-elite-mobs. Both must be installed and new enough. When one is missing the boot log says worldquest: director is OFF and names what is absent. /snpc worldquest list repeats that report.
Pacing is a per-player offer clock. A player becomes due again offerGapSeconds after their last offer, or firstOfferDelaySeconds after the director first sees them. At most one site stands up per director tick, in the world with the most overdue player. Nothing about a site is persisted, and the director deletes every world_* custom quest at boot.
Commands
All except accept are staff only and in-game only.
| Command | Description |
|---|---|
/snpc worldquest spawn [--template <id or random>] [--player <name>] | Force a site around you or a named player. Bypasses the offer clock and cooldowns, not the per-world cap |
/snpc worldquest templates | List templates with kind, count range, weight and tier bonus |
/snpc worldquest list | Live sites, per-world population, players due, the five most overdue, offers today |
/snpc worldquest due <player> | One player's offer clock |
/snpc worldquest clear | Tear every live site down |
/snpc worldquest reload | Re-read worldquests.json. Live sites keep their old settings |
/snpc worldquest accept <id> | Player verb run by the giver's accept answer |
worldquests.json
Seeded with defaults when missing. Every key falls back to its default on its own, so a bad edit costs one setting.
| Key | Default | Meaning |
|---|---|---|
enabled | true | Master switch |
worlds | ["endless", "default", "survival", "world"] | Allow-list of world names. An empty list resets to this. Instance worlds (dungeons, raids, event arenas) are always refused |
tickSeconds | 30 | Director heartbeat. One site per tick |
offerGapSeconds | 1200 | Gap between a player's offers |
firstOfferDelaySeconds | 180 | Wait for a never-offered player |
offersPerDayCap | 30 | Offers per player per UTC day (minimum 1) |
anchorPoolSize | 3 | Weighted roll among this many most-overdue players |
maxActivePerWorld | 12 | Live sites per world |
spawnCooldownSeconds | 60 | Between stand-ups in one world |
globalSpawnCooldownSeconds | 30 | Between stand-ups anywhere |
ledgerRetentionSeconds | 86400 | How long an offline player's clock is kept |
minSiteDistance | 200 | Spacing between sites |
spawnDistanceMin · spawnDistanceMax | 60 · 120 | Ring around the anchor player |
lifetimeSeconds | 900 | Hard life of a site |
unacceptedLifetimeSeconds | 600 | Life of a site nobody accepted |
acceptedExtraSeconds | 0 | Accepting pushes expiry to at least now plus this |
acceptMinRemainingSeconds | 120 | No new hunters below this time left |
maxCompletionsPerSite | 3 | Players who may take one site |
claimGraceSeconds | 120 | How long a finished site stays up for turn-ins |
creditRadius | 60 | How near an owner must be to a kill |
announceRadius | 160 | Who is told a site appeared |
titleEnabled · titleText · titleSeconds | true · World Quest · 4 | The on-screen title |
packRadiusMin · packRadiusMax | 10 · 22 | Where the elites stand around the giver |
tierByLevel | E 0, D 15, C 35, B 60, A 90, S 140 | Tier from the average level of nearby players |
npcPresets | merchant, gentleman, feran | Giver appearances |
npcNames | eight names | Giver name pool |
templates | hunt, boss | See below |
The old keys spawnEverySeconds, playersPerQuest and minActivePerWorld are retired and ignored.
Templates are picked by weight. Fields: id, name, weight, countMin, countMax, mobClass (an endless-elite-mobs class name, empty for a natural roll), tierBonus (rungs above the level-derived tier), rewardMultiplier, lifetimeExempt, rearm, roles, rewardCommands and rewards.
{
"id": "hunt", "name": "Hunt", "weight": 4,
"countMin": 3, "countMax": 5, "mobClass": "",
"tierBonus": 0, "rewardMultiplier": 1.0,
"lifetimeExempt": false, "rearm": false,
"roles": ["Trork_Warrior", "Trork_Hunter", "Outlander_Brute",
"Outlander_Berserker", "Goblin_Scrapper", "Skeleton_Fighter"],
"rewardCommands": [],
"rewards": ["common_augment_reroll"]
}
rewards are Endless Leveling rewards.json bundle ids paid on turn-in, [] disables them, and rewardCommands are console commands bundled per site. With rearm false (the default) a dead pack is not mustered again for a later hunter. With true, each later accepter gets a fresh pack until maxCompletionsPerSite. lifetimeExempt sites ignore lifetimeSeconds.
The shipped boss template is one BOSS-class elite, tierBonus 1 and rewardMultiplier 2.0, with roles Trork_Warrior, Outlander_Brute and Skeleton_Knight. Base rewards per tier (before the multiplier): XP 1500, 3000, 5000, 8000, 12000, 18000 for E to S; a floor of 6, 8, 10, 12, 15 and 18 percent of the claimer's next-level XP (sent to Endless Leveling as xp_level_percent); essence 1, 1, 2, 2, 3, 4.
Sites also refuse ground inside a guild claim and inside ScuffedProtection claims that deny the quest-npcs region flag.
Mob safety (mobsafety.json) and /snpc orphans
Hostiles spawned by quests (world-quest elites and rescue attackers) and whatever they summon carry an snpc- spawn lineage. They are never saved to disk and are swept when their owner ends. mobsafety.json is written with every key on first boot (and rewritten once if keys are missing):
| Section.key | Default | Meaning |
|---|---|---|
lineage.enabled | true | Stamp, never save and sweep quest hostiles |
lineage.graceSeconds | 60 | Seconds past the owner's end before a lineage mob despawns |
lineage.maxRemovalsPerPass | 32 | Removals per world per sweep |
lineage.sweepSeconds | 15 | Sweep interval |
afk.enabled | true | Nobody is served a quest while away |
afk.idleMinutes | 5 | Minutes without moving or turning before a player counts as away |
afk.moveBlocks | 2.0 | Movement that counts as active |
adaptive.enabled | true | Trim new content when TPS is low |
adaptive.soft · adaptive.hard | 0.75 · 0.5 | Fractions of target TPS. Soft halves offers and shrinks packs. Hard stops new sites and rescues |
adaptive.sustainSeconds · recoverSeconds · recoverMargin | 60 · 120 · 0.05 | Hysteresis |
adaptive.summonerRoles | Trork and Outlander summoners, Scarak Defender | Attackers avoided under soft load |
worldQuest.maxReplacementsPerSlot | 3 | Failed spawns before an unaccepted site closes. 0 means unlimited |
rescue.failClosed | true | Treat a failing claim or protection query as denied |
orphans.autoPurge | false | Periodic purge of old saved summons. Leave off until scan has been read |
orphans.autoPurgeSeconds · maxRemovalsPerPass · confirmMinutes | 300 · 16 · 10 | Purge pacing and the two-scan confirmation |
orphans.summonRoles | Wolf and Scarak summon roles | Roles treated as orphan candidates |
| Command | Description |
|---|---|
/snpc orphans scan | Count leftover quest summons in loaded chunks, per world and role |
/snpc orphans purge | Remove them, rate limited |
/snpc orphans reload | Re-read mobsafety.json |
/snpc orphans selftest | Check that quest lineage makes a hunter and its wolf temporary |
These work from the console or by an op or scuffednpcs.admin player.
Guild workers
Worker NPCs (miner, harvester, builder, training dummy) are guild-only and need EndlessGuilds. Hiring is charged to the guild bank. Rank checks use the leader, co-leader and officer ranks.
Items
There is no crafting recipe for the placed blocks. Give them to players with your item tools, shops or rewards:
| Item id | Use |
|---|---|
Sacrificial_Altar | Upgrade altar. Anyone can open it; offerings need leader, co-leader or officer rank |
Mining_Site_Copper · _Iron · _Cobalt · _Thorium · _Adamantite | Miner stations |
Mining_Site_Mithril | Not craftable or sold. ScuffedProfessions places it from its capstone item |
SNPC_Rejuvinator | Worker refill station (claimed through ScuffedProfessions) |
Blueprint_Brush | Temporary "Blueprint Placer" given during placement |
Worksite and altar interactions respect cancelled place, break and use events from protection plugins.
Staff commands
| Command | Description |
|---|---|
/snpc workersadmin | Opens the balancing console (pages: System, one per lever category, Tools, Dummies) |
/snpc workersadmin give <player> <type> [--tier <n>] | Give a player's guild a worker for free (miner, harvester, builder, dummy) |
/snpc workersadmin list <player> | List a player's guild workers with ids and tiers |
/snpc workersadmin settier <workerId> <tier> | Set a worker's tier |
/snpc workersadmin release <workerId> | Permanently release a worker |
/snpc workersadmin recallall <player> | Recall every deployed worker of a player's guild |
/snpc workersadmin dummy | Spawn your own persistent training dummy where you stand |
/snpc workersadmin disbanded <guildId or list> | Confirm a missing guild is disbanded and release its workers |
/snpc cost list | Show hire prices |
/snpc cost set <type> <amount> | Set the guild-bank hire price |
/snpc blessing give <player> <blessing> [--count <n>] | Grant blessings to a player's guild pool |
/snpc blessing take <player> <blessing> [--count <n>] | Remove blessings from a pool |
/snpc blessing pool [--player <name>] | Show a guild's pool |
/snpc blessing list | List blessing ids and effects |
/snpc blessing grant <id> <count> | Test helper: add to your own guild's pool |
/snpc blessing apply <id> | Test helper: apply a pooled blessing to the nearest deployed worker |
/snpc blessing clear | Test helper: clear blessings on the nearest deployed worker |
/snpc setbank <amount> | Test helper: set your guild's bank balance |
/snpc test fillminers · clearminers | Test helpers for hauling |
/snpc workersadmin subcommands, /snpc blessing give|take|pool|list and /snpc orphans accept the console. Blessing ids are ZBEVE, ENKI, HERMES, TYCHE, HEPHAESTUS and ATLAS. The cost aliases are /snpc costs and /snpc blessings.
Hire costs and the kill switch
| File | Content |
|---|---|
worker_costs.json | Hire price per type, keyed MINER, DUMMY, BUILDER, HARVESTER and MERCENARY (the mercenary type has no hire button). Default 50,000 each |
workers_enabled.json | {"enabled": true}. Set false (or use the System page) to disable the altar, stations, /snpc workers and deployment, and recall every deployed worker. Quest NPCs are unaffected |
Balance levers (worker_balance.json)
The balancing console edits live values and saves a flat map of lever id to number. Defaults:
| Group | Lever id | Default |
|---|---|---|
| Deploy | deploy.base · deploy.max · deploy.freeDummies | 1 · 8 · 3 (cap = base + floor(log2(guild level)), clamped) |
| General | max.tier | 4 |
| Miner | miner.yield_s | 720 s per ore at the station's own tier |
| Miner | miner.site_dist | 200 blocks |
| Miner | miner.tier_speedup | 0.8 per tier above the station |
| Miner | miner.carry.t1 to t4 | 5, 6, 7, 8 |
| Miner | mineryield.<station>.t<tier> | 1 ore per payout |
| Builder | builder.period_s · tier_cut_s · min_period_s | 1.0 · 0.25 · 0.5 s |
| Builder | builder.multi_tier · multi_count | tier 4 lays 2 blocks per swing |
| Builder | builder.reach · build_dist | 3.5 · 200 |
| Builder | builder.carry_base · carry_per_tier | 64 · 64 |
| Harvester | harvester.base_s · tier_cut_s · min_s | 10.5 · 2.1 · 3.0 s |
| Harvester | harvester.tier_penalty | 4.0 reap-time multiplier per crop tier above the worker |
| Harvester | harvester.carry_base · carry_per_tier | 64 · 64 |
| Harvester | harvester.scan_r · scan_interval_s · cooldown_s | 20 · 15 · 45 |
| Fatigue | fatigue.harvest_reaps · fatigue.mine_ores | 100 reaps · 5 ore at tier 1 |
| Fatigue | fatigue.tier_resist · wave_grace · wave_breather_s | 0.5 · 2.0 · 8 |
| Upkeep | upkeep.harvester_count · upkeep.miner_count.1 to 4 | 1 · 3, 2, 1, 1 |
| Blessings | bless.hermes_speed · tyche_extra_crops · hephaestus_fatigue_mult · atlas_chest_dist | 1.5 · 1 · 0.5 · 1.5 |
| Blessings | harvester.zbeve_eggplant_speed · bless.zbeve_tier | 3.0 · 4 |
| Hauling | haul.chest_dist · chest_cooldown_s · affinity_pull | 96 · 30 · 0.45 |
| Altar | altar.max_fodder | 10 |
| Altar | altar.odds.1 to 10 | 1, 7, 13, 19, 25, 35, 45, 55, 70, 100 percent |
Movement levers (miner.move_base, move_per_tier, and the same for builder and harvester) default to 1.0 and 0.0. Station times are 12, 15, 20, 20, 30 and 40 minutes for Copper, Iron, Cobalt, Thorium, Adamantite and Mithril (mining levers cover the yields). worker_tools.json stores per type and tier held-tool overrides, set on the Tools page. The default curves are pickaxe, sickle and hammer from Crude, Copper, Iron to Steel.
Other worker files
| File | Content |
|---|---|
workers.json (+ .bak) | Worker roster |
blessings.json | Per-guild blessing pools |
mining_sites.json | Registered stations and their priorities |
disbanded_guilds.txt, claim_orphans_seen.txt, held_guilds.txt | Disband bookkeeping. A guild missing from EndlessGuilds is confirmed disbanded before its workers are released |
Blueprints and build projects
Builders place blueprints from the prefab catalog or from guild-sold builds. Materials come from chests, and every placement and chest access is checked against EndlessGuilds, ScuffedProtection, WorldProtect, OrbisGuard and SimpleClaims as the guild's current leader.
| Command | Who | Description |
|---|---|---|
/snpc prefabs | Staff | Prefab catalog admin: scan, set buyable, set price and display name, approve or reject guild submissions |
/snpc buildinfo | Staff | Build job health report |
/snpc givebuild | Staff | Give yourself the blocks the nearest build still needs |
/snpc selftest | Staff | Place a 3x3x3 brick at your feet to test placement |
/snpc blueprints | Guild | Shop for officers; sell flow for any member with a selection |
/snpc projects | Guild officers | Manage projects |
/snpc rotate, /snpc pin | Players placing a blueprint | Recovery commands for the placer's buttons |
| Setting | Value |
|---|---|
| Selection size | 128 blocks per side |
| Submission name | 40 characters |
| Submission price | 0 to 100,000,000 |
| Submissions | One per player per day, held until staff approve them |
| Seller payout | 90 percent of the price to the seller guild's bank |
Files: prefab_config.json (per prefab: buyable, price, display name), guild_blueprints.json (owned blueprints), guild_prefab_market.json (submissions and sales), guild_prefabs/ (captured builds), build_jobs.json (projects).
While a player is in blueprint selection mode, the mod grants them the engine permission hytale.editor.selection.use temporarily, together with a tagged Selection Tool. It is revoked on exit, submit, disconnect and by a periodic sweep, and any stray tool is reclaimed at next login.
Thief goblins
thief_goblins.json is created on first boot and read at boot only.
{
"enabled": true,
"spawnIntervalSeconds": 120.0,
"spawnChance": 0.25,
"areaCooldownSeconds": 600.0,
"maxPerWorld": 8,
"chestRadius": 32.0,
"despawnDistance": 96.0,
"despawnGraceSeconds": 15.0,
"excludedWorlds": []
}
| Setting | Meaning | Allowed range |
|---|---|---|
enabled | Natural spawns on or off | |
spawnIntervalSeconds | Time between rolls per world's players | at least 30 |
spawnChance | Chance per eligible player per roll | 0 to 1 |
areaCooldownSeconds | Cooldown for raids within 64 blocks of a recent raid | at least 60 |
maxPerWorld | Active thieves per world | 1 to 64 |
chestRadius | Container search radius around the player | 4 to 64 |
despawnDistance | Beyond this no player keeps a thief alive | 64 to 192 |
despawnGraceSeconds | Time without a nearby player before removal | at least 5 |
excludedWorlds | World names with no natural spawns |
A value outside its range makes the plugin refuse to load, so keep edits inside the table. Worlds with NPC spawning disabled, creative players and dead players do not trigger raids. /snpc test thief spawns one goblin 6 to 10 blocks away, aimed at the nearest stocked container within chestRadius, ignoring spawn rolls, exclusions and cooldowns. It still obeys the per-world maximum, roof and ground checks.
Eggplant Galore event
/snpc event opens the event picker and the Eggplant Galore console. State is stored in event.json (with a .bak), and payout progress in event_payouts.txt.
| State | Meaning |
|---|---|
DRAFT | Configured, deposits rejected |
ACTIVE | Running, deposits accepted |
PAUSED | Suspended by staff, the clock keeps running |
ENDED | Deposits locked, payout not finished |
PAID | Everything granted |
Event defaults (editable in the console):
| Setting | Default |
|---|---|
| Item | Plant_Crop_Aubergine_Item |
| Lootbox id (BetterLootBox) | mythos |
| Community goal | 1,000,000 |
| Minimum personal deposit for the goal reward | 100 |
| Minimum deposit to count as a guild contributor | 500 |
| Guild key prizes, ranks 1 to 3 | 3, 2, 1 per qualifying contributor |
| Individual key prizes, ranks 1 to 3 | 10, 5, 3 |
| Keys per qualifying player when the goal is met | 1 |
| Top guilds that receive a free guild level | 2 |
| Blackout | on, last 12 hours |
Keys are granted through BetterLootBox, including to offline players. Guild levels are granted through EndlessGuilds. Each reward line is journaled after it succeeds, so re-running Pay Winners after a restart skips what was already granted.
Prologue tutorial (Tutorial V2)
The mod ships a prologue campaign for Endless Leveling servers (prologue_v2): 23 required steps and 11 optional ones with a Guide, a Guild Registrar, an Auctioneer and a Guild Master, a Mournhollow catacombs run, and steps that depend on Endless Leveling, Fates, Rifts, Guilds, EndlessLink and ZenShop. The definition is inside the jar. A builder places it on the server with the setup commands.
At boot it registers the whole campaign with Endless Leveling in one call. If Endless Leveling lacks both the batch and the per-step registration methods, or refuses any step, the steps already registered are taken back and the older campaign keeps running. If Endless Leveling is not ready yet, activation is retried every 2 seconds for up to 10 minutes. It also writes the campaign's reward bundles into Endless Leveling when they differ. The mod has no setting to switch the campaign off, so a server that does not want it should not pair this mod with an Endless Leveling build that accepts runtime campaign registration.
| Command | Description |
|---|---|
/snpc tutorial setup | Open the setup checklist page |
/snpc tutorial status | Print the checklist to chat |
/snpc tutorial set <beat> | Place an NPC, area, teleporter or point beat where you stand |
/snpc tutorial area <beat> | Place an area or teleporter beat |
/snpc tutorial portal <from>><to> (alias door) | Place a portal between worlds, for example hub>open |
/snpc tutorial world <alias> [--name <world>] | Point hub, mournhollow or open at a world. Default: the current one |
/snpc tutorial arena <add|remove|list> | Mournhollow fighting points |
Default world aliases are hub = AdventurersGuild, mournhollow = instance-mournhollow-shared and open = endless. Builder choices are saved in tutorial_points.json (previous copy kept as .bak; a file that cannot be read is never overwritten, and setup refuses changes until it is fixed). Player progress is in tutorial_state.json. Lines that still read PLACEHOLDER: are counted by the readiness check.
Campaign helpers
These commands support the older built-in campaign (Act 0 to 2) and are staff only:
| Command | Description |
|---|---|
/snpc campaigncheck | Report campaign setup and your location without changing anything |
/snpc campaignskip | Complete the objective you are tracking and advance |
/snpc campaignselftest | Walk the campaign server-side and report every objective |
/snpc elitespawn [--clear <any>] | Set where the campaign Elite is summoned. Stand there first |
/snpc dummyspawn [--clear <any>] | Add a training dummy stand for the Act 2 kill step |
/snpc path record · save <name> · cancel · list | Record escort routes by walking them. Routes are stored in paths.json |
Captured points are stored in campaign_points.json. Per-player campaign flags are in campaign_flags.json, and parked guides in parked_guides.json.
Instance stories
Party-aware story characters for instanced dungeons are driven by other mods through InstanceStoryApi (see below). Four bundled scripts ship (breach_outlander, breach_undead, breach_void, breach_infernal) plus nine sky_* scripts. Copies are written to stories/ when missing; the disk copy wins. A disk copy with an older version than the jar's is moved to <id>.json.v<n>.bak and reseeded. Any other *.json in stories/ is loaded as an extra story whose file name must equal its id.
| Command | Description |
|---|---|
/snpc story list | Loaded scripts and live runs |
/snpc story test <script> | Run a story end to end without a party and print PASS or FAIL |
/snpc story fire <runId> <key> | Fire a story key into a live run |
/snpc story end <runId> | End a run |
These work from the console or by staff. A file story_selftest.txt in the data folder listing script ids (or all) runs the same test once at boot and is then deleted.
Permissions
Scuffed NPCs defines one permission node and uses the built-in operator group.
| Node or group | Grants |
|---|---|
scuffednpcs.admin | Every staff command above. Can be given to a user or to a group |
Operator group (OP) | Same as scuffednpcs.admin |
hytale:Adventurer | The /snpc tree, /snpc worldquest and /snpc questhost are opened to this default group so players can reach their own verbs. Every staff subcommand checks for op or scuffednpcs.admin itself |
Player-facing commands are gated by guild rank, not by nodes: /snpc workers, /snpc projects and the blueprint shop need the guild leader, co-leader or officer rank (or staff). Selling a blueprint is open to any guild member.
Four read-only or authoring helpers (/snpc cost list, /snpc path list, /snpc path cancel and /snpc dialogueanims) take the same op or scuffednpcs.admin check on builds after 1.98.34.
Staff commands run from the console are accepted for the console-capable groups noted above (workersadmin roster commands, blessing give|take|pool|list, story, orphans, debug). The /snpc quests, bounty, worldquest, questhost, tutorial and most other commands are in-game only.
Other staff commands
| Command | Description |
|---|---|
/snpc reload | Re-read profiles, spawn NPCs newly added to companions.json, reload names, skins and bounty pools, re-dress live NPCs |
/snpc debug | Toggle verbose diagnostics. Also set at startup with -Dscuffednpcs.debug=true |
/snpc event | Open the server events menu |
/snpc dialogueanims | List the animations the nearest NPC's model binds (for authors) |
Advanced JVM properties: scuffednpcs.dialogue.cps (default typing speed), scuffednpcs.dialogue.playerPortraits (false turns off live player-head portraits), scuffednpcs.dialogue.lockinput, and scuffednpcs.eldialogues (explicit path to campaign_dialogues.json).
Data & storage
All files are in mods/Zbeve_ScuffedNPCs/. Hand-authored files are restored from the jar only when missing.
| Path | Content |
|---|---|
companions.json | Placed NPCs. companions_import.json is an import inbox |
profiles/ | Conversation profiles |
presets/ | Extra appearance presets |
npc_names.json, npc_skins.json, portraits.json | Names, outfits, portraits |
paths.json, campaign_points.json, campaign_flags.json | Routes, captured points, campaign flags |
bounties*.json, bounty_*.template.json | Bounty pools and voices |
questhost.json, questhost/ | Quest host toggles and state |
worldquests.json, mobsafety.json, thief_goblins.json | Feature config |
questlines.json | Authored questlines |
dialogue_prefs.json | Per-player dialogue speed and camera |
workers.json and the worker files above | Worker system |
event.json, event_payouts.txt | Event state |
stories/ | Story scripts |
tutorial_points.json, tutorial_state.json | Tutorial V2 |
The mod also keeps small marker components on NPC entities (SCNPCID, SCWORKERID) so it can re-link NPCs that the engine restores with their chunk.
Integrations
| Mod | Use |
|---|---|
| Endless Leveling | Quest provider (assignment, state, progress, claim), dialogue export, daily reset clock, rewards and bundles, NPC mob-leveling exemption, class trial and identity actions |
| EndlessGuilds | Guild membership, ranks, claims, level and bank for workers and blueprints. The Guilds panel can open the workers menu |
| ScuffedHolograms | Per-player quest markers |
| ScuffedProfessions | Professions board seeding, Mithril station, Rejuvinator, ledger of worker builds |
| endless-elite-mobs | World-quest elites |
| ScuffedProtection | quest-npcs region flag and build/container checks |
| WorldProtect, OrbisGuard, SimpleClaims | Build and container checks for workers |
| BetterLootBox | Event keys |
| QuestLines | Extra quest provider |
Quest providers are wired by reflection and retried every 30 seconds for up to 15 minutes if a provider loads late. The mod registers no PlaceholderAPI placeholders and does not call an economy API directly: money is moved through the guild bank (workers, blueprints) or through the console commands in bounty pools and rewards.
API for other mods
All entry points are static, use JDK and engine types only, never throw, and are meant to be reached by reflection.
com.zbeve.scuffednpcs.ScuffedNPCsApi
| Method | Description |
|---|---|
grantBlessing(UUID guildId, String blessingId, int count) | Add blessings to a guild pool. Returns false on an unknown id or count of 0 or less |
openWorkersMenu(Ref, Store, PlayerRef) | Open the workers menu for a player |
registerMiningSite(String world, int x, int y, int z) | Tell miners a station block now stands there |
canDamageTrainingDummy(Ref victim, UUID attacker) | Training-dummy damage policy |
spawnQuestNpc(...), ensureSharedNpc(...), despawnNpc, isNpcAlive, npcEntityRef, npcPosition, setNpcInvulnerable | Quest NPC surface used by Endless Leveling |
questHostHosts(String capability), questHostBoardWorld(), questHostBoardPlacement() | Read the hosted state |
com.zbeve.scuffednpcs.api.DialogueHostApi (API_VERSION 1) lets another mod write an NPC's conversation and have it shown in the Scuffed NPCs dialogue page. register(prefix, host) hosts every npcKey starting with the prefix (longest prefix wins). The host is a BiFunction<UUID, String, String> called on the player's world thread with a JSON request {"npc","action","speaker"} and returning JSON {"speaker","tag","body","rows":[{"id","label","kind"}],"close"}. Row kinds are offer, more, leave, quest, track, done and claim, with up to six rows. open(player, npcKey) and openAt(player, npcKey, speakerName, x, y, z) open the page, and isAvailable() and unregister(prefix) complete it. Pressing F on a matching NPC opens the hosted page and nothing else.
com.zbeve.scuffednpcs.api.InstanceStoryApi (API_VERSION 1) runs party-aware story characters in instance worlds: isAvailable, hasScript, registerScript(id, json) (in memory only, bundled ids refused), startRun(world, scriptId, party, points, vars), addPartyMember, removePartyMember, fire(runId, key, uuid), setListener(runId, listener), flag, payout, endRun(runId, success), plus the cutscene helpers moveCharacter, releaseCharacter, setSceneHold, setLeadTarget and clearLeadTarget. Story keys in include player_joined, intro_done, section_armed:<k>, section_cleared:<k>, boss_engaged, boss_defeated, wipe and beat:<k>. Events out are flag, objective, say and talk. Each live step becomes an Endless Leveling instance quest under the provider id scuffednpcs_stories.
Troubleshooting
| Symptom | Check |
|---|---|
The plugin does not load and the log says Cannot load thief_goblins.json | A value in thief_goblins.json is outside its range. Fix or delete the file. Builds after 1.98.34 log a warning and run on the defaults instead of refusing to load |
NPCs say nothing and the log says campaign_dialogues.json not found | Endless Leveling has not exported it yet. Reload its quests, or set -Dscuffednpcs.eldialogues |
| Bounty boards stand but post nothing | Endless Leveling is missing, or a board's requiresPlugin mod is absent. Run /snpc bounty list |
| Reward commands do nothing | The shipped pools call eco give. Install a plugin that provides it |
/snpc questhost status says it is hosting nothing | The Endless Leveling build lacks the provider API (or a method the host needs), so it keeps its own directors. The status line names what is missing |
worldquest: director is OFF | Endless Leveling or endless-elite-mobs is missing or too old. /snpc worldquest list names the cause |
| "Worker NPCs are currently disabled" | workers_enabled.json is false. Use the System page of /snpc workersadmin |
| Workers cannot be hired | EndlessGuilds is missing, the player is not a leader, co-leader or officer, or the guild bank is short |
| A profile edit does not apply | Run /snpc reload. Check the log for Bad profile file |
| Markers do not show | ScuffedHolograms is not installed |