Repository navigation
Notifications
Two pieces put things on a player's screen besides chat, both portable across Bukkit and Hytale and both
built on the platform's HUD port (IPlatform.getHud() -> notify.IHudAccess, see
Platform Abstraction):
-
notify.Notifier- "tell the player": aNotificationdelivered through a chain of channels, the first step the platform supports. -
waypoint.Waypoints- "go here": aWaypointpointed at through every supported channel it asks for.
A plugin never asks "does this server have a boss bar?". It names what it wants and what to fall back to; the platform answers, and a chain or a waypoint that can show nothing on this platform fails at load, not in front of a player.
Notifier.get().notify(player, Notification.of(NotifyKind.PROGRESS)
.text("§aLogs: 12/64")
.build()); // the kind's chain: action bar, else chat
Notifier.get().notify(player, Notification.of(NotifyKind.COMPLETE)
.title("§6Quest complete").subtitle("§eLumberjack")
.sound(HudSounds.LEVEL_UP)
.build()); // title + sound, else chat + sound, else chat
Notifier.get().notify(player, Notification.of(NotifyKind.TRACKED).id("lumberjack")
.text("§eLumberjack").progress(0.19f)
.trackerLines(Collections.singletonList(TrackerLine.of(FancyText.of("Logs"), 12, 64)))
.build()); // boss bar, else tracker, else action bar, else chat
Notifier.get().clear(player, "lumberjack"); // un-track: hides the bar / tracker of that id
Notifier.get().notify(player, Notification.of(NotifyKind.INFO).text("§9Mana full")
.channels("action-bar", "chat").build()); // an explicit chain, overriding the kind'sNotification.Builder: id (same id updates or replaces - a boss bar, a tracker, a queued action bar), text,
title/subtitle, lines (sidebar), trackerLines, progress (0..1), duration / ticks(n), priority
(queued channels: the highest wins), sound (a platform id or a HudSounds alias), category (travels for a
future inbox channel; nothing reads it yet), channels(...).
A chain is a list of steps; a step is one or more channels joined by +, all of which must be supported. The
first fully supported step delivers; the others are skipped.
NotifyKind |
Shipped chain | Shipped duration |
|---|---|---|
PROGRESS |
action-bar, chat
|
3s |
COMPLETE |
title+sound, chat+sound, chat
|
5s |
TRACKED |
boss-bar, tracker, action-bar, chat
|
0s (until cleared) |
INFO |
chat |
3s |
DENY |
action-bar, chat
|
3s |
The admin changes them in the core's config.yml:
Settings:
Notify:
chains:
progress: ["action-bar", "chat"]
tracked: ["sidebar", "chat"] # the sidebar is opt-in: no shipped chain uses it
durations:
progress: 3sA channel name nobody registered fails the boot naming the key, the value and the channels there are; a
<plugin>:<name> of another plugin is accepted and resolves once that plugin registers it.
@Override
public void onECPluginEnable() {
Notifier.get().validate(getPluginData(), "tracked", NotifyChain.parse("boss-bar", "tracker"));
}validate throws NotifyException naming the chain, the platform and the channels it supports when no step is
supported. At run time notify never throws: a chain with no supported step shows nothing and logs once per chain
and platform.
| Channel | 1.7.10 | 1.12.2 | 1.16.5 | 1.21.x | Hytale | How it delivers |
|---|---|---|---|---|---|---|
chat |
yes | yes | yes | yes | yes | the chat line |
action-bar |
NecroTempus only | yes | yes | yes | no | queued per player (priority, same id replaces, expiry), one tick per 500 ms |
title |
NecroTempus only | yes | yes | yes | yes | queued like the action bar; Hytale's event title |
sound |
yes | yes | yes | yes | yes |
playSound; HudSounds aliases map per era / to Hytale sound events |
boss-bar |
no | yes | yes | yes | no | one bar per id with progress; hidden after the duration |
sidebar |
yes (16 chars a line) | yes | yes | yes | no | the scoreboard sidebar, one board per player |
toast |
no | yes | yes | yes | yes | Minecraft: a hidden advancement (one per distinct toast); Hytale: a notification |
tracker |
yes | yes | yes | yes | yes | Bukkit: the sidebar slot with a fixed layout; Hytale: the objective panel |
The sidebar and the tracker share Minecraft's sidebar slot: the last one shown is drawn, and clearing it draws the other again. A player who quits is forgotten by every channel (no call needed), and a plugin's own channels stop on its shutdown.
public final class InboxChannel implements NotifyChannel {
public String getKey() { return "finalquests:inbox"; }
public boolean isSupported() { return true; }
public void deliver(FPlayer player, Notification notification) { /* store it */ }
}
Notifier.get().register(getPluginData(), new InboxChannel(), "Keeps the notification for /inbox.");
// a chain names it "finalquests:inbox" (or "inbox" while the name is unique)Channels are a TypeRegistry<NotifyChannel> (Notifier.get().registry()); the key's owner must be the
registering plugin, and it leaves with that plugin. A channel with per-player state captures the IHudAccess it
delivered on and writes every later tick to that one.
WaypointHandle handle = Waypoints.get().show(player,
Waypoint.of(WaypointId.of(getPluginData(), "lumber-camp"), WaypointTarget.region(camp))
.label("§aLumber camp")
.duration(Duration.ofMinutes(10)) // optional; none = until hidden
.build()); // channels: Settings.Waypoint.channels
handle.hide(); // or Waypoints.get().hide(player, id)-
Targets:
WaypointTarget.at(position),region(region)(the centre of its bounds; a region open in height is centred on the world's height),following(supplier)(an entity, an NPC: the supplier answers null once it is gone). The target is read again everySettings.Waypoint.refresh-ticks, on the player's world thread. -
Hiding:
locate()answering null, the duration ending,hide/hideAll, the player quitting, or the owner's shutdown (Waypoints.hideOwnedBy). The same id shown again replaces the old one. State lives in memory only: a waypoint does not survive a restart - the plugin that showed it shows it again. -
Channels: every supported one the waypoint asks for is used; the unsupported ones are skipped. None
supported ->
WaypointExceptionnaming the channels and the platform.Waypoints.validate(plugin, what, channels)is the load-time check.
| Channel | Bukkit | Hytale | Per player | How |
|---|---|---|---|---|
compass |
yes | no | yes |
setCompassTarget; hiding points it back at spawn |
particle-path |
yes | yes | yes | up to particle-path-length points one block apart from the eyes towards the target, VisualStyle.PATH
|
hologram |
with DecentHolograms or FancyHolograms enabled | no | yes | the label floating over the target, shown only to that player |
web-map |
with BlueMap, dynmap or squaremap enabled | no | no | a marker named by the label in the Waypoints set/layer - everyone who opens the map sees it |
hologram and web-map reach those plugins by reflection and are soft-depends: nothing is required, and each
channel answers isSupported() only while its plugin is enabled.
Settings:
Waypoint:
channels: ["compass", "particle-path"] # hologram and web-map are opt-in
refresh-ticks: 10
particle-path-length: 16A channel of your own implements WaypointChannel (getKey, isSupported, isPerPlayer, show/refresh/hide,
all on the player's world thread) and registers with Waypoints.get().register(plugin, channel, description).
ECoreTestWorld world = Platforms.lenient().hudWithout(Capability.TITLE).recordingOutlines().install();
ECPluginData core = Plugins.fakeCore(dir.toFile()); // forget(Plugins.CORE) before world.close()
Notifier.get().registerStock(core);
Waypoints.get().registerStock(core);
...
world.platform().getTestHud().getSoundCalls(); // what reached the HUD
world.platform().getRenderedOutlines(); // particle-path frameshudWithout(...) turns the double into "1.7.10" or "Hytale" for a chain. The waypoint ticker runs on the
ECScheduler: TestClock.acquire() and TestClock.advance(...) / runDue() drive it. ECoreTestWorld.close()
stops every notify queue and forgets every waypoint. See Testing Toolkit.
-
Platform Abstraction -
getHud()and the capability table. - Hytale Platform - what Hytale answers.
-
In-Server Conformance - the
hudsuite that proves the table on real servers.
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