Server Admin

Scuffed NPCs

Requirements, NPC and profile authoring, bounty, world quest and worker config, staff commands, permissions, integrations and API.

Gameplay GuideView the players side of Scuffed NPCs

Last updated

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

PluginTypeUsed for
Hytale NPC module (Hytale:NPC)RequiredNPC roles and spawning
EndlessLevelingCoreOptional, but most features need itQuests, daily reset clock, dialogue export, quest rewards, mob-leveling exemption
EndlessGuildsOptional, needed for workers and blueprintsGuild membership, ranks, claims, level, guild bank
ScuffedHologramsOptionalPer-player ! and ? quest markers
ScuffedProfessionsOptionalProfessions bounty board, Mithril mining site, Rejuvinator
endless-elite-mobsOptional, needed for world questsElite packs
QuestLinesOptionalExtra quest provider
ScuffedNPCsData, FancyHologramsDeclared optional in the manifestNot 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

  1. Start the server once. The data folder and the default files are created.
  2. Run /snpc quests to open the NPC hub, click + Spawn, pick a model, and place the NPC where you stand.
  3. Attach a profile or a quest role (see NPC hub) so the NPC has something to say.
  4. Put down the bounty boards you want with /snpc bounty place --board <id>.
  5. 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:

ControlWhat it does
+ SpawnPlaces a new NPC where you stand
Rename, model filterSets the display name and picks any model on the server
ModeIdle, Follow, Assist, Guard, Wander or Betray (hostile)
Profile dropdownAttach, edit or create a conversation profile
Quest NPC roleBinds the NPC to an Endless Leveling quest role (auto-bind, below)
Set spawn to meMoves the NPC's post to where you stand
Record lead routeStarts recording a walking route for an escort
Despawn CompanionRemoves 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).

KeyMeaning
id, nameProfile id (also the file name) and display name
scopeSHARED (one NPC, per-player dialogue) or PERSONAL (one copy per owner, for follow, escort and betrayal). Default PERSONAL
presetAppearance preset id (below)
npcKeyKey sent to quests. Talking credits npc_talk objectives whose match equals it
speaker, roleName and role line shown in the dialogue header
questHandoff"manual" stops Endless Leveling's own pop-up from pre-empting the dialogue
startStageFirst 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
ambientbarks, 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):

TypeEffect
SET_STAGE, SET_MODEChange stage or behaviour mode
MESSAGEChat message to the player
BECOME_HOSTILE, RETURN_FRIENDLYTurn on or off the player
ASSIGN_QUEST, TRACK_QUEST, CLAIM_QUESTAssign, pin or claim a quest by id (CLAIM_QUEST uses the quest engine's own payout)
RECORD_PROGRESSCredit an npc_talk objective (value = npc key)
EL_INTERACTReport a full interaction to Endless Leveling (runs its act-gated flow)
FIRE_QUEST_EVENTAdvance an EXTERNAL objective by key (value2 = amount)
SET_FLAGSet a per-player flag (blank value2 removes it)
DELIVER_ITEMSTake value2 of item value and report the delivery
OPEN_DIALOGUEReopen the dialogue so a conversation chains
PLAY_SOUND, PLAY_ANIMATIONSound event or animation at the NPC
RUN_COMMANDRun 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
DESPAWNRemove the NPC
CONFIRM_IDENTITY, CREDIT_IDENTITY, OPEN_IDENTITY, TRY_CLASS, TRY_BUILD, COMMIT_CLASSEndless 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

FilePurpose
presets/*.jsonExtra appearance presets: {"id": "...", "name": "...", "role": "<NPC role id>"}. Built in: merchant, gentleman, feran, bounty_farmer, bounty_giver
npc_names.jsonName and role line per npcKey or quest role: {"Guild_Master": {"name": "...", "role": "..."}}
npc_skins.jsonOutfit per key, as engine skin components (haircut, overtop, pants, ...)
portraits.jsonDialogue 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

CommandDescription
/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 reloadRe-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:

FieldDefaultMeaning
slots3Contracts per day, 1 to 5 (clamped)
npcKeyBOUNTY_<ID>Key for names, skins and portraits
profileIdbounty_<id>Generated profile id
questIdPrefixsnpc_bounty_<id>Prefix of the generated quest ids
roleScuffedNPC_Bounty_GiverNPC role used for the body
requiresPluginnoneMod id or list. The board posts nothing while one is missing
groupsnoneNamed payouts that entries share
entriesnoneThe 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 fieldMeaning
idUnique in the pool
enabledfalse parks the entry
groupWhich group pays it
triggerdeliver_item (default, settled in person), mob_kill or block_harvest
matchExact 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
displayNameText name. Required in practice for non-hand-in contracts
amountA number or {min, max, step}
chunkPartial hand-in size. It must divide every possible amount. Hand-ins only
keeptrue checks the items without taking them. Hand-ins only
valueWhat one unit is worth, the source of {total}
xpExperience per unit, the source of {xp}. Default 0
requiresPluginMod id or list. The entry sits out while one is missing
weightDraw weight, default 1
questName, descriptionContract title and journal line
rewardCommands, rewardItemsOverride the group's payout for this entry. The two override separately
dialogueoffer, 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:

CapabilityWhat Scuffed NPCs runs
event_questsRandom per-player gather and rescue event NPCs
daily_giversWandering shared daily-quest givers
bounty_boardThe 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.

CommandDescription
/snpc questhost statusWhat 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 reloadRe-read questhost.json and Endless Leveling's catalog
/snpc questhost board place · remove · refreshManage 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.keyDefaultMeaning
events.min_minutes · max_minutes12 · 25Random delay between event quests
events.repeat_cooldown_minutes90Cooldown before a player can help with the same event quest again
events.despawn_after_claim_seconds15How long the event NPC lingers after a claim
events.worldsdefault, endless, survival, worldWorlds events may start in
events.blocked_worldsname patterns for adventurer, horizons, arena, rift, raid, dungeon and tower worldsNever used for events
daily_givers.min_minutes · max_minutes · first_minutes6 · 12 · 3Giver spawn timing
daily_givers.offers3Offers per giver
daily_givers.max_players3Distinct players per giver
daily_givers.giver_ttl_seconds600Giver lifetime
daily_givers.despawn_after_accept_seconds15Lingering after an accept
daily_givers.spawn_distance_min · spawn_distance_max8 · 14Spawn ring around the player
daily_givers.min_giver_distance128Spacing between givers
daily_givers.max_per_world6Givers per world
daily_givers.announce · announce_radiustrue · 96Announcement
bounties.slots5Slots on the hosted board
bounties.max_active1Unfinished bounties per player
bounties.refresh_minutes60Refresh 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.

CommandDescription
/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 templatesList templates with kind, count range, weight and tier bonus
/snpc worldquest listLive sites, per-world population, players due, the five most overdue, offers today
/snpc worldquest due <player>One player's offer clock
/snpc worldquest clearTear every live site down
/snpc worldquest reloadRe-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.

KeyDefaultMeaning
enabledtrueMaster 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
tickSeconds30Director heartbeat. One site per tick
offerGapSeconds1200Gap between a player's offers
firstOfferDelaySeconds180Wait for a never-offered player
offersPerDayCap30Offers per player per UTC day (minimum 1)
anchorPoolSize3Weighted roll among this many most-overdue players
maxActivePerWorld12Live sites per world
spawnCooldownSeconds60Between stand-ups in one world
globalSpawnCooldownSeconds30Between stand-ups anywhere
ledgerRetentionSeconds86400How long an offline player's clock is kept
minSiteDistance200Spacing between sites
spawnDistanceMin · spawnDistanceMax60 · 120Ring around the anchor player
lifetimeSeconds900Hard life of a site
unacceptedLifetimeSeconds600Life of a site nobody accepted
acceptedExtraSeconds0Accepting pushes expiry to at least now plus this
acceptMinRemainingSeconds120No new hunters below this time left
maxCompletionsPerSite3Players who may take one site
claimGraceSeconds120How long a finished site stays up for turn-ins
creditRadius60How near an owner must be to a kill
announceRadius160Who is told a site appeared
titleEnabled · titleText · titleSecondstrue · World Quest · 4The on-screen title
packRadiusMin · packRadiusMax10 · 22Where the elites stand around the giver
tierByLevelE 0, D 15, C 35, B 60, A 90, S 140Tier from the average level of nearby players
npcPresetsmerchant, gentleman, feranGiver appearances
npcNameseight namesGiver name pool
templateshunt, bossSee 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.keyDefaultMeaning
lineage.enabledtrueStamp, never save and sweep quest hostiles
lineage.graceSeconds60Seconds past the owner's end before a lineage mob despawns
lineage.maxRemovalsPerPass32Removals per world per sweep
lineage.sweepSeconds15Sweep interval
afk.enabledtrueNobody is served a quest while away
afk.idleMinutes5Minutes without moving or turning before a player counts as away
afk.moveBlocks2.0Movement that counts as active
adaptive.enabledtrueTrim new content when TPS is low
adaptive.soft · adaptive.hard0.75 · 0.5Fractions of target TPS. Soft halves offers and shrinks packs. Hard stops new sites and rescues
adaptive.sustainSeconds · recoverSeconds · recoverMargin60 · 120 · 0.05Hysteresis
adaptive.summonerRolesTrork and Outlander summoners, Scarak DefenderAttackers avoided under soft load
worldQuest.maxReplacementsPerSlot3Failed spawns before an unaccepted site closes. 0 means unlimited
rescue.failClosedtrueTreat a failing claim or protection query as denied
orphans.autoPurgefalsePeriodic purge of old saved summons. Leave off until scan has been read
orphans.autoPurgeSeconds · maxRemovalsPerPass · confirmMinutes300 · 16 · 10Purge pacing and the two-scan confirmation
orphans.summonRolesWolf and Scarak summon rolesRoles treated as orphan candidates
CommandDescription
/snpc orphans scanCount leftover quest summons in loaded chunks, per world and role
/snpc orphans purgeRemove them, rate limited
/snpc orphans reloadRe-read mobsafety.json
/snpc orphans selftestCheck 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 idUse
Sacrificial_AltarUpgrade altar. Anyone can open it; offerings need leader, co-leader or officer rank
Mining_Site_Copper · _Iron · _Cobalt · _Thorium · _AdamantiteMiner stations
Mining_Site_MithrilNot craftable or sold. ScuffedProfessions places it from its capstone item
SNPC_RejuvinatorWorker refill station (claimed through ScuffedProfessions)
Blueprint_BrushTemporary "Blueprint Placer" given during placement

Worksite and altar interactions respect cancelled place, break and use events from protection plugins.

Staff commands

CommandDescription
/snpc workersadminOpens 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 dummySpawn 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 listShow 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 listList 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 clearTest helper: clear blessings on the nearest deployed worker
/snpc setbank <amount>Test helper: set your guild's bank balance
/snpc test fillminers · clearminersTest 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

FileContent
worker_costs.jsonHire 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:

GroupLever idDefault
Deploydeploy.base · deploy.max · deploy.freeDummies1 · 8 · 3 (cap = base + floor(log2(guild level)), clamped)
Generalmax.tier4
Minerminer.yield_s720 s per ore at the station's own tier
Minerminer.site_dist200 blocks
Minerminer.tier_speedup0.8 per tier above the station
Minerminer.carry.t1 to t45, 6, 7, 8
Minermineryield.<station>.t<tier>1 ore per payout
Builderbuilder.period_s · tier_cut_s · min_period_s1.0 · 0.25 · 0.5 s
Builderbuilder.multi_tier · multi_counttier 4 lays 2 blocks per swing
Builderbuilder.reach · build_dist3.5 · 200
Builderbuilder.carry_base · carry_per_tier64 · 64
Harvesterharvester.base_s · tier_cut_s · min_s10.5 · 2.1 · 3.0 s
Harvesterharvester.tier_penalty4.0 reap-time multiplier per crop tier above the worker
Harvesterharvester.carry_base · carry_per_tier64 · 64
Harvesterharvester.scan_r · scan_interval_s · cooldown_s20 · 15 · 45
Fatiguefatigue.harvest_reaps · fatigue.mine_ores100 reaps · 5 ore at tier 1
Fatiguefatigue.tier_resist · wave_grace · wave_breather_s0.5 · 2.0 · 8
Upkeepupkeep.harvester_count · upkeep.miner_count.1 to 41 · 3, 2, 1, 1
Blessingsbless.hermes_speed · tyche_extra_crops · hephaestus_fatigue_mult · atlas_chest_dist1.5 · 1 · 0.5 · 1.5
Blessingsharvester.zbeve_eggplant_speed · bless.zbeve_tier3.0 · 4
Haulinghaul.chest_dist · chest_cooldown_s · affinity_pull96 · 30 · 0.45
Altaraltar.max_fodder10
Altaraltar.odds.1 to 101, 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

FileContent
workers.json (+ .bak)Worker roster
blessings.jsonPer-guild blessing pools
mining_sites.jsonRegistered stations and their priorities
disbanded_guilds.txt, claim_orphans_seen.txt, held_guilds.txtDisband 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.

CommandWhoDescription
/snpc prefabsStaffPrefab catalog admin: scan, set buyable, set price and display name, approve or reject guild submissions
/snpc buildinfoStaffBuild job health report
/snpc givebuildStaffGive yourself the blocks the nearest build still needs
/snpc selftestStaffPlace a 3x3x3 brick at your feet to test placement
/snpc blueprintsGuildShop for officers; sell flow for any member with a selection
/snpc projectsGuild officersManage projects
/snpc rotate, /snpc pinPlayers placing a blueprintRecovery commands for the placer's buttons
SettingValue
Selection size128 blocks per side
Submission name40 characters
Submission price0 to 100,000,000
SubmissionsOne per player per day, held until staff approve them
Seller payout90 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": []
}
SettingMeaningAllowed range
enabledNatural spawns on or off
spawnIntervalSecondsTime between rolls per world's playersat least 30
spawnChanceChance per eligible player per roll0 to 1
areaCooldownSecondsCooldown for raids within 64 blocks of a recent raidat least 60
maxPerWorldActive thieves per world1 to 64
chestRadiusContainer search radius around the player4 to 64
despawnDistanceBeyond this no player keeps a thief alive64 to 192
despawnGraceSecondsTime without a nearby player before removalat least 5
excludedWorldsWorld 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.

StateMeaning
DRAFTConfigured, deposits rejected
ACTIVERunning, deposits accepted
PAUSEDSuspended by staff, the clock keeps running
ENDEDDeposits locked, payout not finished
PAIDEverything granted

Event defaults (editable in the console):

SettingDefault
ItemPlant_Crop_Aubergine_Item
Lootbox id (BetterLootBox)mythos
Community goal1,000,000
Minimum personal deposit for the goal reward100
Minimum deposit to count as a guild contributor500
Guild key prizes, ranks 1 to 33, 2, 1 per qualifying contributor
Individual key prizes, ranks 1 to 310, 5, 3
Keys per qualifying player when the goal is met1
Top guilds that receive a free guild level2
Blackouton, 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.

CommandDescription
/snpc tutorial setupOpen the setup checklist page
/snpc tutorial statusPrint 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:

CommandDescription
/snpc campaigncheckReport campaign setup and your location without changing anything
/snpc campaignskipComplete the objective you are tracking and advance
/snpc campaignselftestWalk 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 · listRecord 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.

CommandDescription
/snpc story listLoaded 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 groupGrants
scuffednpcs.adminEvery staff command above. Can be given to a user or to a group
Operator group (OP)Same as scuffednpcs.admin
hytale:AdventurerThe /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

CommandDescription
/snpc reloadRe-read profiles, spawn NPCs newly added to companions.json, reload names, skins and bounty pools, re-dress live NPCs
/snpc debugToggle verbose diagnostics. Also set at startup with -Dscuffednpcs.debug=true
/snpc eventOpen the server events menu
/snpc dialogueanimsList 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.

PathContent
companions.jsonPlaced NPCs. companions_import.json is an import inbox
profiles/Conversation profiles
presets/Extra appearance presets
npc_names.json, npc_skins.json, portraits.jsonNames, outfits, portraits
paths.json, campaign_points.json, campaign_flags.jsonRoutes, captured points, campaign flags
bounties*.json, bounty_*.template.jsonBounty pools and voices
questhost.json, questhost/Quest host toggles and state
worldquests.json, mobsafety.json, thief_goblins.jsonFeature config
questlines.jsonAuthored questlines
dialogue_prefs.jsonPer-player dialogue speed and camera
workers.json and the worker files aboveWorker system
event.json, event_payouts.txtEvent state
stories/Story scripts
tutorial_points.json, tutorial_state.jsonTutorial 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

ModUse
Endless LevelingQuest provider (assignment, state, progress, claim), dialogue export, daily reset clock, rewards and bundles, NPC mob-leveling exemption, class trial and identity actions
EndlessGuildsGuild membership, ranks, claims, level and bank for workers and blueprints. The Guilds panel can open the workers menu
ScuffedHologramsPer-player quest markers
ScuffedProfessionsProfessions board seeding, Mithril station, Rejuvinator, ledger of worker builds
endless-elite-mobsWorld-quest elites
ScuffedProtectionquest-npcs region flag and build/container checks
WorldProtect, OrbisGuard, SimpleClaimsBuild and container checks for workers
BetterLootBoxEvent keys
QuestLinesExtra 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

MethodDescription
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, setNpcInvulnerableQuest 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

SymptomCheck
The plugin does not load and the log says Cannot load thief_goblins.jsonA 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 foundEndless Leveling has not exported it yet. Reload its quests, or set -Dscuffednpcs.eldialogues
Bounty boards stand but post nothingEndless Leveling is missing, or a board's requiresPlugin mod is absent. Run /snpc bounty list
Reward commands do nothingThe shipped pools call eco give. Install a plugin that provides it
/snpc questhost status says it is hosting nothingThe 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 OFFEndless 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 hiredEndlessGuilds is missing, the player is not a leader, co-leader or officer, or the guild bank is short
A profile edit does not applyRun /snpc reload. Check the log for Bad profile file
Markers do not showScuffedHolograms is not installed