Repository navigation
Npcs
The core knows which NPC this is - not how to make one. A quest, a dialogue or a shop names an NPC by an
NpcRef, and every NPC plugin (or the platform) answers for its own through an NpcProvider. Package
br.com.finalcraft.evernifecore.npc (:common, no platform imports).
NpcRef guard = NpcRef.parse("citizens:12"); // or NpcRef.of("citizens", "12")
guard.getProvider(); // "citizens"
guard.getId(); // "12"
guard.toString(); // "citizens:12" - what parse reads back| Provider | Id | Example |
|---|---|---|
citizens |
the numeric NPC id | citizens:12 |
fancynpcs |
the id of its NpcData (a UUID) |
fancynpcs:0b7d... |
znpcsplus |
the entry id an admin chose | znpcsplus:merchant |
hytale |
the entity's UUID | hytale:4f2a... |
Both halves are lowercased: NPC plugins differ in case, and each provider compares ids ignoring it.
A malformed text throws IllegalArgumentException saying it expects provider:id. A reference to a
provider nobody registered (pixelmon:... without the plugin that provides it) is still valid: every
lookup simply finds nothing. What must fail loud at load time calls Npcs.requireProvider(ref), which
throws NpcProviderException:
The npc reference pixelmon:4f2a names the provider 'pixelmon', but no plugin registered it on this server
(registered: citizens, fancynpcs, znpcsplus). Install the plugin that provides it or fix the reference.
Optional<WorldLocPos> where = Npcs.getLocation(guard); // the stored location - a despawned NPC answers too
Optional<String> name = Npcs.getName(guard); // display name, colour codes kept
Optional<NativeEntity> live = Npcs.find(guard); // its entity now; empty when despawned or a packet NPC
Optional<NpcRef> which = Npcs.resolve(nativeEntity); // which NPC an entity is, by the first available providerEach lookup goes to the provider the reference names and answers empty while that provider is unregistered
or unavailable (its plugin absent). find runs on the main thread on Bukkit and on the entity's world
thread on Hytale; from another thread read getLocation.
Every action context the platforms build carries the NPC on its actor: Actor.getNpc() is the NpcRef a
provider recognised, null for a player, a block, the environment or an entity no provider knows. The three
triggers a quest or a dialogue compares are action events:
| Trigger | Event | NPC |
|---|---|---|
| talked to |
InteractNpcActionEvent (source interact-npc), getContext().getClickType() = RIGHT
|
event.getContext().getEntity().getNpc() |
| punched | the same event, getClickType() = LEFT
|
the same |
| handed an item | the same event | plus getContext().getItemInHandKey()
|
| killed | KillActionEvent |
getContext().getVictim().getNpc() - NPCs with an entity only |
A denied interact-npc cancels the NPC plugin's click, not the punch: the punch's damage is the damage source's,
asked on the NPC's entity when it has one (a packet NPC has none, so it takes no hit).
ECEventBus.global().subscribe(InteractNpcActionEvent.Post.class, event -> {
if (guard.equals(event.getContext().getEntity().getNpc())) {
// the player event.getContext().getPlayer() talked to the guard
}
});interact-npc is a source apart from interact: a packet NPC has no server entity to raise an interact,
so each provider raises it from its plugin's own click events - both clicks, each with its
EntityContext.ClickType (RIGHT the use, LEFT the punch; Citizens' NPCRightClickEvent and NPCLeftClickEvent,
the getInteractionType/getClickType of FancyNpcs and ZNPCsPlus). A listener that only means a talk checks
getClickType() == RIGHT; the stock consumers already do: a dialogue opens on the right click only, and an
interact-npc objective counts the right click unless it says click: left
({ id: punch, type: interact-npc, npc: "citizens:12", click: left }). Like every source it is on-demand: a provider
subscribes to its plugin's click only while somebody needs interact-npc (a region, or a listener of
InteractNpcActionEvent), and a cancelled Pre cancels the plugin's click - for a punch, the plugin's own
left-click event, so whatever that plugin does on a left click (its click actions or commands) does not run. No core
flag decides interact-npc; only a listener that cancels the Pre, or a plugin's own flag, denies it. On Hytale
the right click is the Use of the NPC (UseEntityEvent.Pre, InteractionType.Use); the left click is a hit the
player lands on it with his own hand or weapon - the server's primary attack ends in a Damage, never in a
UseEntityEvent (only UseEntityInteraction, the Use root, fires that) - so the DAMAGE System raises it as
LEFT, and a cancelled Pre cancels the hit. A projectile is no click. A UseEntityEvent a custom root fires
with InteractionType.Primary is a LEFT too.
Packet NPCs (FancyNpcs, ZNPCsPlus) have no entity on the server: they never appear as the victim of a
hit or a death, nor as the actor of a Bukkit event. Only "talked to" and "handed an item" exist for them, and
their actor is keyed <provider>:npc with no native handle.
require:
type: near-npc
npc: citizens:12
radius: 5 # blocksPasses while the player stands within radius of the NPC's stored location, in the same world; fails with
Go to <name> (23 blocks away). (or (in <world>)). getNeeds() is {MOVE}. An NPC of an unregistered
provider fails the load with the text above.
A waypoint to an NPC points at its stored location, read again on every refresh; while the provider is gone
the waypoint hides itself. The constructor refuses an unregistered provider like near-npc:
Waypoints.get().show(player, Waypoint.of(WaypointId.of(plugin, "guard"), new NpcWaypointTarget(guard)).label("Guard").build());A plugin that owns its NPCs (a Pixelmon bridge, a custom NPC system) implements NpcProvider and registers
it once, in its enable:
public final class PixelmonNpcProvider implements NpcProvider {
@Override public String getId() { return "pixelmon"; }
@Override public boolean isAvailable() { return true; } // cheap: called on every actor built
@Override public Optional<NpcRef> resolve(NativeEntity entity) { ... } // entity -> pixelmon:<uuid>
@Override public Optional<WorldLocPos> getLocation(NpcRef ref) { ... }
@Override public Optional<String> getName(NpcRef ref) { ... }
@Override public void start() { /* subscribe to the plugin's click, post interact-npc */ }
@Override public void stop() { /* unsubscribe */ }
}
Npcs.register(getPluginData(), new PixelmonNpcProvider());One provider per id: a second registration of the same id throws IllegalStateException naming both
owners. The owner's shutdown unregisters it (Npcs.unregisterAll(owner), run by the bootstrap). A provider
with entities only needs resolve: the platform's actor-building asks it. A provider that raises clicks posts
Actions.denied(context) (cancel the click when true) and then Actions.post(EventSource.INTERACT_NPC, ...)
with an EntityContext of source INTERACT_NPC whose entity actor carries the reference.
-
Bukkit -
McNpcProvidersregisters Citizens, FancyNpcs and ZNPCsPlus on every server, by reflection only (no dependency): see Integrations. -
Hytale -
HyNpcProvider: every entity with anNPCEntitycomponent ishytale:<uuid>, its role stays the actor's key (hytale:<npc type id>), and aUseof it raisesinteract-npcfrom the use System. See Hytale Platform.
common-tests has FakeNpcProvider - an NPC without a plugin:
FakeNpcProvider citizens = new FakeNpcProvider("citizens")
.at(guard, WorldLocPos.of(10, 64, 10, "world")).named(guard, "Guard")
.knows(nativeEntity, guard); // resolve/find
Npcs.register(owner, citizens);
citizens.click(player, guard); // raises interact-npc (RIGHT) while the source is needed
citizens.leftClick(player, guard); // ... the punch (LEFT)
citizens.click(player, guard, "minecraft:emerald"); // ... holding an emerald
// after the test: Npcs.unregisterAll(owner)Actors.npc("citizens:npc", guard) builds an NPC actor for a hand-made context.
-
Regions -
Actor, sources and contexts. - Logic - conditions.
- Notifications - waypoints.
-
In-Server Conformance - the
npcssuite.
EverNifeCore · Home · made by Petrus Pradella
Getting Started
Commands & Text
Player Data & Storage
- PlayerData & PDSections
- Accounts
- Storage Backends
- Inline Backends for Plugins
- Block Data (SVWorldDataManager)
- Legacy Data Migration
- Cooldowns
Config & Minecraft Systems
- Configuration
- Logging & Debug Modules
- Events
- Regions
- Groups
- Notifications
- NPCs
- Logic
- Quests
- Provenance
- Scheduler & Threading
- Items & NBT
- Screens
- Dialogues
- Menus
- Integrations
- Economy
- Boosters
- Deliveries
- Audit Log
- Dynamic Values
- Player Ignore
- Version Compatibility
Architecture & Reference